From 3dfd260569baa43ff6199a424dc6fa4615826fdd Mon Sep 17 00:00:00 2001 From: matheus23 Date: Mon, 11 May 2026 16:08:02 +0000 Subject: [PATCH] deploy: b296347a6167b1a31b131df4e306df383846c96e --- pr/451/docs/noq/all.html | 2 +- pr/451/docs/noq/congestion/index.html | 6 +- pr/451/docs/noq/congestion/sidebar-items.js | 2 +- .../congestion/struct.Bbr3.html} | 39 +- .../congestion/struct.Bbr3Config.html} | 17 +- .../congestion/struct.ControllerMetrics.html | 12 +- pr/451/docs/noq/congestion/struct.Cubic.html | 18 +- .../noq/congestion/struct.CubicConfig.html | 8 +- .../docs/noq/congestion/struct.NewReno.html | 18 +- .../noq/congestion/struct.NewRenoConfig.html | 10 +- .../docs/noq/congestion/trait.Controller.html | 62 +- .../congestion/trait.ControllerFactory.html | 6 +- pr/451/docs/noq/connection/struct.Closed.html | 11 + pr/451/docs/noq/crypto/index.html | 2 +- pr/451/docs/noq/crypto/rustls/enum.Error.html | 2 +- pr/451/docs/noq/crypto/rustls/index.html | 2 +- .../crypto/rustls/struct.HandshakeData.html | 9 +- .../rustls/struct.NoInitialCipherSuite.html | 2 +- .../rustls/struct.QuicClientConfig.html | 2 +- .../rustls/struct.QuicServerConfig.html | 2 +- .../noq/crypto/rustls/struct.TlsSession.html | 8 +- .../docs/noq/crypto/struct.CryptoError.html | 6 +- .../struct.ExportKeyingMaterialError.html | 2 +- pr/451/docs/noq/crypto/struct.KeyPair.html | 2 +- pr/451/docs/noq/crypto/struct.Keys.html | 2 +- .../noq/crypto/struct.UnsupportedVersion.html | 2 +- .../docs/noq/crypto/trait.ClientConfig.html | 2 +- .../noq/crypto/trait.HandshakeTokenKey.html | 2 +- pr/451/docs/noq/crypto/trait.HeaderKey.html | 4 +- pr/451/docs/noq/crypto/trait.HmacKey.html | 2 +- pr/451/docs/noq/crypto/trait.PacketKey.html | 2 +- .../docs/noq/crypto/trait.ServerConfig.html | 2 +- pr/451/docs/noq/crypto/trait.Session.html | 2 +- pr/451/docs/noq/enum.ClosePathError.html | 40 + pr/451/docs/noq/enum.ConfigError.html | 6 +- pr/451/docs/noq/enum.ConnectError.html | 6 +- pr/451/docs/noq/enum.ConnectionError.html | 6 +- pr/451/docs/noq/enum.Dir.html | 16 +- pr/451/docs/noq/enum.EcnCodepoint.html | 2 +- pr/451/docs/noq/enum.FrameType.html | 2 +- pr/451/docs/noq/enum.PathError.html | 45 + .../enum.PathEvent.html} | 52 +- ...ruct.Written.html => enum.PathStatus.html} | 23 +- pr/451/docs/noq/enum.ReadError.html | 6 +- pr/451/docs/noq/enum.ReadExactError.html | 6 +- pr/451/docs/noq/enum.ReadToEndError.html | 6 +- pr/451/docs/noq/enum.ResetError.html | 6 +- pr/451/docs/noq/enum.SendDatagramError.html | 6 +- pr/451/docs/noq/enum.SetPathStatusError.html | 37 + pr/451/docs/noq/enum.Side.html | 20 +- pr/451/docs/noq/enum.StoppedError.html | 6 +- pr/451/docs/noq/enum.WriteError.html | 6 +- pr/451/docs/noq/fn.default_runtime.html | 2 +- pr/451/docs/noq/index.html | 6 +- pr/451/docs/noq/sidebar-items.js | 2 +- pr/451/docs/noq/struct.Accept.html | 6 +- pr/451/docs/noq/struct.AcceptBi.html | 6 +- pr/451/docs/noq/struct.AcceptUni.html | 6 +- .../docs/noq/struct.AckFrequencyConfig.html | 10 +- pr/451/docs/noq/struct.AddressDiscovery.html | 4 +- pr/451/docs/noq/struct.ApplicationClose.html | 6 +- pr/451/docs/noq/struct.BloomTokenLog.html | 2 +- pr/451/docs/noq/struct.Chunk.html | 2 +- pr/451/docs/noq/struct.ClientConfig.html | 18 +- pr/451/docs/noq/struct.Closed.html | 37 + pr/451/docs/noq/struct.ClosedPath.html | 32 + pr/451/docs/noq/struct.ClosedStream.html | 6 +- pr/451/docs/noq/struct.Connecting.html | 12 +- pr/451/docs/noq/struct.Connection.html | 122 +- pr/451/docs/noq/struct.ConnectionClose.html | 6 +- pr/451/docs/noq/struct.ConnectionId.html | 2 +- pr/451/docs/noq/struct.ConnectionStats.html | 16 +- pr/451/docs/noq/struct.DecryptedInitial.html | 30 + pr/451/docs/noq/struct.Endpoint.html | 52 +- pr/451/docs/noq/struct.EndpointConfig.html | 26 +- pr/451/docs/noq/struct.EndpointStats.html | 4 +- pr/451/docs/noq/struct.FrameStats.html | 2 +- pr/451/docs/noq/struct.IdleTimeout.html | 14 +- pr/451/docs/noq/struct.Incoming.html | 4 +- pr/451/docs/noq/struct.IncomingFuture.html | 2 +- pr/451/docs/noq/struct.InvalidCid.html | 2 +- pr/451/docs/noq/struct.Lagged.html | 2 +- .../docs/noq/struct.MtuDiscoveryConfig.html | 12 +- .../docs/noq/struct.NatTraversalUpdates.html | 2 +- pr/451/docs/noq/struct.NoneTokenLog.html | 2 +- pr/451/docs/noq/struct.NoneTokenStore.html | 2 +- .../docs/noq/struct.ObservedExternalAddr.html | 2 +- pr/451/docs/noq/struct.OnClosed.html | 6 +- pr/451/docs/noq/struct.OpenBi.html | 6 +- pr/451/docs/noq/struct.OpenPath.html | 6 +- pr/451/docs/noq/struct.OpenUni.html | 6 +- pr/451/docs/noq/struct.Path.html | 39 +- pr/451/docs/noq/struct.PathEvents.html | 4 +- pr/451/docs/noq/struct.PathId.html | 2 +- pr/451/docs/noq/struct.PathStats.html | 10 +- pr/451/docs/noq/struct.QlogConfig.html | 2 +- pr/451/docs/noq/struct.QlogFileFactory.html | 2 +- pr/451/docs/noq/struct.ReadDatagram.html | 6 +- pr/451/docs/noq/struct.RecvStream.html | 66 +- pr/451/docs/noq/struct.RetryError.html | 2 +- pr/451/docs/noq/struct.SendDatagram.html | 6 +- pr/451/docs/noq/struct.SendStream.html | 67 +- pr/451/docs/noq/struct.ServerConfig.html | 32 +- pr/451/docs/noq/struct.SmolRuntime.html | 2 +- pr/451/docs/noq/struct.StdSystemTime.html | 4 +- pr/451/docs/noq/struct.Stopped.html | 6 +- pr/451/docs/noq/struct.StreamId.html | 26 +- pr/451/docs/noq/struct.TokenMemoryCache.html | 2 +- pr/451/docs/noq/struct.TokenReuseError.html | 2 +- pr/451/docs/noq/struct.TokioRuntime.html | 2 +- pr/451/docs/noq/struct.Transmit.html | 4 +- pr/451/docs/noq/struct.TransportConfig.html | 76 +- .../docs/noq/struct.TransportErrorCode.html | 2 +- pr/451/docs/noq/struct.UdpStats.html | 2 +- .../docs/noq/struct.UnorderedRecvStream.html | 14 +- .../noq/struct.ValidationTokenConfig.html | 10 +- pr/451/docs/noq/struct.VarInt.html | 4 +- .../docs/noq/struct.VarIntBoundsExceeded.html | 4 +- .../docs/noq/struct.WeakConnectionHandle.html | 10 +- pr/451/docs/noq/struct.WeakPathHandle.html | 10 +- pr/451/docs/noq/struct.ZeroRttAccepted.html | 4 +- pr/451/docs/noq/trait.AsyncTimer.html | 2 +- pr/451/docs/noq/trait.AsyncUdpSocket.html | 2 +- .../docs/noq/trait.ConnectionIdGenerator.html | 2 +- pr/451/docs/noq/trait.NetworkChangeHint.html | 4 +- pr/451/docs/noq/trait.QlogFactory.html | 2 +- pr/451/docs/noq/trait.Runtime.html | 2 +- pr/451/docs/noq/trait.TimeSource.html | 6 +- pr/451/docs/noq/trait.TokenLog.html | 2 +- pr/451/docs/noq/trait.TokenStore.html | 2 +- pr/451/docs/noq/trait.UdpSender.html | 2 +- pr/451/docs/noq_proto/all.html | 2 +- .../docs/noq_proto/bench_exports/index.html | 2 +- .../fn.get_into_many_segments.html | 2 +- .../fn.get_loop_many_segments.html | 2 +- .../send_buffer_benches/index.html | 2 +- pr/451/docs/noq_proto/coding/index.html | 2 +- .../coding/struct.UnexpectedEnd.html | 4 +- .../noq_proto/coding/trait.Decodable.html | 4 +- .../noq_proto/coding/trait.Encodable.html | 4 +- pr/451/docs/noq_proto/coding/type.Result.html | 2 +- ...NCURRENT_MULTIPATH_PATHS_WHEN_ENABLED.html | 11 - .../struct.Bbr.html => bbr3/struct.Bbr3.html} | 6 +- .../struct.Bbr3Config.html} | 6 +- pr/451/docs/noq_proto/congestion/index.html | 6 +- .../noq_proto/congestion/sidebar-items.js | 2 +- .../congestion/struct.Bbr3.html} | 39 +- .../congestion/struct.Bbr3Config.html} | 17 +- .../congestion/struct.ControllerMetrics.html | 12 +- .../noq_proto/congestion/struct.Cubic.html | 18 +- .../congestion/struct.CubicConfig.html | 6 +- .../noq_proto/congestion/struct.NewReno.html | 18 +- .../congestion/struct.NewRenoConfig.html | 8 +- .../congestion/trait.Controller.html | 62 +- .../congestion/trait.ControllerFactory.html | 6 +- .../streams/send/struct.Written.html | 11 - ...NCURRENT_MULTIPATH_PATHS_WHEN_ENABLED.html | 3 - .../constant.DEFAULT_SUPPORTED_VERSIONS.html | 2 +- pr/451/docs/noq_proto/crypto/index.html | 2 +- .../noq_proto/crypto/rustls/enum.Error.html | 2 +- .../docs/noq_proto/crypto/rustls/index.html | 2 +- .../crypto/rustls/struct.HandshakeData.html | 9 +- .../rustls/struct.NoInitialCipherSuite.html | 2 +- .../rustls/struct.QuicClientConfig.html | 2 +- .../rustls/struct.QuicServerConfig.html | 2 +- .../crypto/rustls/struct.TlsSession.html | 10 +- .../noq_proto/crypto/struct.CryptoError.html | 6 +- .../struct.ExportKeyingMaterialError.html | 2 +- .../docs/noq_proto/crypto/struct.KeyPair.html | 2 +- pr/451/docs/noq_proto/crypto/struct.Keys.html | 2 +- .../crypto/struct.UnsupportedVersion.html | 2 +- .../noq_proto/crypto/trait.ClientConfig.html | 2 +- .../crypto/trait.HandshakeTokenKey.html | 2 +- .../noq_proto/crypto/trait.HeaderKey.html | 4 +- .../docs/noq_proto/crypto/trait.HmacKey.html | 2 +- .../noq_proto/crypto/trait.PacketKey.html | 2 +- .../noq_proto/crypto/trait.ServerConfig.html | 2 +- .../docs/noq_proto/crypto/trait.Session.html | 4 +- .../docs/noq_proto/enum.ClosePathError.html | 6 +- pr/451/docs/noq_proto/enum.ConfigError.html | 6 +- pr/451/docs/noq_proto/enum.ConnectError.html | 6 +- .../docs/noq_proto/enum.ConnectionError.html | 6 +- pr/451/docs/noq_proto/enum.DatagramEvent.html | 2 +- pr/451/docs/noq_proto/enum.Dir.html | 16 +- pr/451/docs/noq_proto/enum.EcnCodepoint.html | 2 +- pr/451/docs/noq_proto/enum.Event.html | 9 +- pr/451/docs/noq_proto/enum.FinishError.html | 2 +- pr/451/docs/noq_proto/enum.FrameType.html | 2 +- pr/451/docs/noq_proto/enum.LongType.html | 6 +- pr/451/docs/noq_proto/enum.MaybeFrame.html | 6 +- .../noq_proto/enum.PacketDecodeError.html | 12 +- .../noq_proto/enum.PathAbandonReason.html | 6 +- pr/451/docs/noq_proto/enum.PathError.html | 6 +- pr/451/docs/noq_proto/enum.PathEvent.html | 12 +- pr/451/docs/noq_proto/enum.PathStatus.html | 6 +- .../docs/noq_proto/enum.ProtectedHeader.html | 8 +- pr/451/docs/noq_proto/enum.ReadError.html | 2 +- pr/451/docs/noq_proto/enum.ReadableError.html | 2 +- .../noq_proto/enum.SendDatagramError.html | 2 +- .../noq_proto/enum.SetPathStatusError.html | 6 +- pr/451/docs/noq_proto/enum.Side.html | 20 +- pr/451/docs/noq_proto/enum.StreamEvent.html | 6 +- pr/451/docs/noq_proto/enum.WriteError.html | 2 +- pr/451/docs/noq_proto/index.html | 4 +- .../n0_nat_traversal/enum.Error.html | 4 +- .../n0_nat_traversal/enum.Event.html | 4 +- .../noq_proto/n0_nat_traversal/index.html | 2 +- pr/451/docs/noq_proto/sidebar-items.js | 2 +- pr/451/docs/noq_proto/struct.AcceptError.html | 4 +- .../noq_proto/struct.AckFrequencyConfig.html | 10 +- .../noq_proto/struct.ApplicationClose.html | 6 +- .../docs/noq_proto/struct.BloomTokenLog.html | 2 +- pr/451/docs/noq_proto/struct.Chunk.html | 2 +- pr/451/docs/noq_proto/struct.Chunks.html | 2 +- .../docs/noq_proto/struct.ClientConfig.html | 18 +- pr/451/docs/noq_proto/struct.ClosedPath.html | 6 +- .../docs/noq_proto/struct.ClosedStream.html | 6 +- pr/451/docs/noq_proto/struct.Connection.html | 135 +- .../noq_proto/struct.ConnectionClose.html | 6 +- .../noq_proto/struct.ConnectionEvent.html | 2 +- .../noq_proto/struct.ConnectionHandle.html | 12 +- .../docs/noq_proto/struct.ConnectionId.html | 2 +- .../noq_proto/struct.ConnectionStats.html | 16 +- pr/451/docs/noq_proto/struct.Datagram.html | 4 +- .../docs/noq_proto/struct.DatagramInfo.html | 4 +- pr/451/docs/noq_proto/struct.Datagrams.html | 2 +- .../noq_proto/struct.DecryptedInitial.html | 4 +- pr/451/docs/noq_proto/struct.Endpoint.html | 20 +- .../docs/noq_proto/struct.EndpointConfig.html | 24 +- .../docs/noq_proto/struct.EndpointEvent.html | 2 +- .../struct.FixedLengthConnectionIdParser.html | 6 +- pr/451/docs/noq_proto/struct.FourTuple.html | 20 +- pr/451/docs/noq_proto/struct.FrameStats.html | 2 +- .../struct.HashedConnectionIdGenerator.html | 2 +- pr/451/docs/noq_proto/struct.IdleTimeout.html | 12 +- pr/451/docs/noq_proto/struct.Incoming.html | 16 +- .../docs/noq_proto/struct.IncomingAlpns.html | 4 +- pr/451/docs/noq_proto/struct.InvalidCid.html | 2 +- .../docs/noq_proto/struct.InvalidFrameId.html | 2 +- .../noq_proto/struct.MtuDiscoveryConfig.html | 12 +- .../struct.MultipathNotNegotiated.html | 4 +- .../docs/noq_proto/struct.NoneTokenLog.html | 2 +- .../docs/noq_proto/struct.NoneTokenStore.html | 2 +- .../docs/noq_proto/struct.PartialDecode.html | 2 +- pr/451/docs/noq_proto/struct.PathId.html | 2 +- pr/451/docs/noq_proto/struct.PathStats.html | 10 +- .../struct.ProtectedInitialHeader.html | 4 +- pr/451/docs/noq_proto/struct.QlogConfig.html | 2 +- .../noq_proto/struct.QlogFileFactory.html | 2 +- .../struct.RandomConnectionIdGenerator.html | 2 +- pr/451/docs/noq_proto/struct.RecvStream.html | 11 +- pr/451/docs/noq_proto/struct.RetryError.html | 6 +- .../docs/noq_proto/struct.RttEstimator.html | 10 +- pr/451/docs/noq_proto/struct.SendStream.html | 26 +- .../docs/noq_proto/struct.ServerConfig.html | 32 +- .../docs/noq_proto/struct.ShouldTransmit.html | 8 +- .../docs/noq_proto/struct.StdSystemTime.html | 4 +- pr/451/docs/noq_proto/struct.StreamId.html | 22 +- pr/451/docs/noq_proto/struct.StreamInfo.html | 4 +- pr/451/docs/noq_proto/struct.Streams.html | 10 +- .../noq_proto/struct.TokenMemoryCache.html | 2 +- .../noq_proto/struct.TokenReuseError.html | 2 +- pr/451/docs/noq_proto/struct.Transmit.html | 4 +- .../noq_proto/struct.TransportConfig.html | 73 +- .../docs/noq_proto/struct.TransportError.html | 4 +- .../noq_proto/struct.TransportErrorCode.html | 2 +- pr/451/docs/noq_proto/struct.UdpStats.html | 2 +- .../struct.ValidationTokenConfig.html | 10 +- pr/451/docs/noq_proto/struct.VarInt.html | 4 +- .../struct.VarIntBoundsExceeded.html | 4 +- .../trait.ConnectionIdGenerator.html | 2 +- .../noq_proto/trait.ConnectionIdParser.html | 6 +- .../noq_proto/trait.NetworkChangeHint.html | 4 +- pr/451/docs/noq_proto/trait.QlogFactory.html | 2 +- pr/451/docs/noq_proto/trait.TimeSource.html | 6 +- pr/451/docs/noq_proto/trait.TokenLog.html | 2 +- pr/451/docs/noq_proto/trait.TokenStore.html | 2 +- .../transport_parameters/enum.Error.html | 2 +- .../noq_proto/transport_parameters/index.html | 2 +- .../struct.TransportParameters.html | 2 +- pr/451/docs/noq_udp/all.html | 2 +- pr/451/docs/noq_udp/constant.BATCH_SIZE.html | 2 +- pr/451/docs/noq_udp/enum.EcnCodepoint.html | 8 +- pr/451/docs/noq_udp/index.html | 2 +- pr/451/docs/noq_udp/struct.RecvMeta.html | 2 +- pr/451/docs/noq_udp/struct.Transmit.html | 2 +- pr/451/docs/noq_udp/struct.UdpSockRef.html | 6 +- .../docs/noq_udp/struct.UdpSocketState.html | 24 +- pr/451/docs/perf/client/fn.run.html | 2 +- pr/451/docs/perf/client/index.html | 2 +- .../docs/perf/enum.CongestionAlgorithm.html | 6 +- pr/451/docs/perf/server/index.html | 2 +- pr/451/docs/perf/stats/index.html | 2 +- .../perf/stats/struct.OpenStreamStats.html | 8 +- pr/451/docs/perf/stats/struct.Stats.html | 2 +- .../docs/perf/stats/struct.StreamStats.html | 2 +- pr/451/docs/search.index/0033f3568d59.js | 1 - pr/451/docs/search.index/005f416926ad.js | 1 + pr/451/docs/search.index/00c4f658f46f.js | 1 + pr/451/docs/search.index/0125cbd5bba3.js | 1 + pr/451/docs/search.index/016287c739af.js | 1 - pr/451/docs/search.index/01818b9a5dfd.js | 1 - pr/451/docs/search.index/01dabba3dd0f.js | 1 - pr/451/docs/search.index/0258058d4a15.js | 1 - pr/451/docs/search.index/02dd940163a0.js | 1 + pr/451/docs/search.index/0302cc930c05.js | 1 + pr/451/docs/search.index/03099968b016.js | 1 - pr/451/docs/search.index/03aae57c1bdc.js | 1 + pr/451/docs/search.index/03d8571d8861.js | 1 - pr/451/docs/search.index/04067d1944a2.js | 1 - pr/451/docs/search.index/040989688a54.js | 1 + pr/451/docs/search.index/0435c5f11e89.js | 1 - pr/451/docs/search.index/04cd208f0148.js | 1 - pr/451/docs/search.index/0527185c0cd7.js | 1 + pr/451/docs/search.index/0530b4c13347.js | 1 - pr/451/docs/search.index/058523a6e4f8.js | 1 - pr/451/docs/search.index/05daabf53ac2.js | 1 + pr/451/docs/search.index/05ff51e69bf3.js | 1 + pr/451/docs/search.index/0697183800b7.js | 1 - pr/451/docs/search.index/06c25d8359bd.js | 1 - pr/451/docs/search.index/06c6b77500fe.js | 1 - pr/451/docs/search.index/070f6c0257e1.js | 1 + pr/451/docs/search.index/0717d66ebc8c.js | 1 - pr/451/docs/search.index/0740e388ca16.js | 1 - pr/451/docs/search.index/0777c1b794b3.js | 1 + pr/451/docs/search.index/080595ed9c0d.js | 1 - pr/451/docs/search.index/082363d032b8.js | 1 + pr/451/docs/search.index/08d9a5c3e851.js | 1 - pr/451/docs/search.index/09031ecd90af.js | 1 - pr/451/docs/search.index/090c885d9b86.js | 1 + pr/451/docs/search.index/09d1502998bb.js | 1 + pr/451/docs/search.index/0a8ad4b154a4.js | 1 - pr/451/docs/search.index/0a98c9fc8112.js | 1 + pr/451/docs/search.index/0ab5109df87a.js | 1 + pr/451/docs/search.index/0add2bce1a52.js | 1 + pr/451/docs/search.index/0bac143bcb50.js | 1 - pr/451/docs/search.index/0bca21001963.js | 1 - pr/451/docs/search.index/0bd42362d0ac.js | 1 - pr/451/docs/search.index/0bdf527b5d3f.js | 1 + pr/451/docs/search.index/0c258f5e1776.js | 1 + pr/451/docs/search.index/0c57b4f8c6b4.js | 1 + pr/451/docs/search.index/0ca93a7be19c.js | 1 - pr/451/docs/search.index/0d6bfa8c2af1.js | 1 - pr/451/docs/search.index/0d6fbddc62d7.js | 1 + pr/451/docs/search.index/0e2aac44d870.js | 1 - pr/451/docs/search.index/0e4d0f710b2b.js | 1 - pr/451/docs/search.index/0f30d894e39f.js | 1 - pr/451/docs/search.index/100cd8e013cf.js | 1 - pr/451/docs/search.index/1074492a6ce1.js | 1 - pr/451/docs/search.index/10ae14a3503e.js | 1 + pr/451/docs/search.index/116465ed6584.js | 1 + pr/451/docs/search.index/117405adffd2.js | 1 - pr/451/docs/search.index/120419dec60e.js | 1 - pr/451/docs/search.index/120ac1721451.js | 1 + pr/451/docs/search.index/12ff91055174.js | 1 + pr/451/docs/search.index/133a2aaf2619.js | 1 - pr/451/docs/search.index/1363513dee0c.js | 1 + pr/451/docs/search.index/139d49b47758.js | 1 + pr/451/docs/search.index/13baa5793c64.js | 1 - pr/451/docs/search.index/1507fd45a0d0.js | 1 - pr/451/docs/search.index/15883d436710.js | 1 - pr/451/docs/search.index/1593ad1b0859.js | 1 - pr/451/docs/search.index/15d820844df2.js | 1 - pr/451/docs/search.index/162fe2003634.js | 1 + pr/451/docs/search.index/16850befe1eb.js | 1 + pr/451/docs/search.index/169efb3fe210.js | 1 + pr/451/docs/search.index/16ea9b1933a4.js | 1 - pr/451/docs/search.index/17a463b91cd9.js | 1 - pr/451/docs/search.index/17ac3cf6f003.js | 1 + pr/451/docs/search.index/17b7a87bec39.js | 1 + pr/451/docs/search.index/18eec5319d0e.js | 1 - pr/451/docs/search.index/190a93e1a007.js | 1 + pr/451/docs/search.index/19705dd8c3fb.js | 1 + pr/451/docs/search.index/19baf510d15e.js | 1 - pr/451/docs/search.index/19d0359d569b.js | 1 - pr/451/docs/search.index/1a66cd07757a.js | 1 - pr/451/docs/search.index/1b2b113ba497.js | 1 - pr/451/docs/search.index/1b7cc07f1f34.js | 1 - pr/451/docs/search.index/1bf00b4f0c44.js | 1 - pr/451/docs/search.index/1c262f510d82.js | 1 - pr/451/docs/search.index/1c28e9635eec.js | 1 + pr/451/docs/search.index/1c36524cf913.js | 1 + pr/451/docs/search.index/1c43b144cda4.js | 1 + pr/451/docs/search.index/1cbd4fd00132.js | 1 + pr/451/docs/search.index/1cd53c18f140.js | 1 + pr/451/docs/search.index/1d05d97e46ea.js | 1 + pr/451/docs/search.index/1d3f2d3bd8f6.js | 1 - pr/451/docs/search.index/1d8c8f6f3340.js | 1 - pr/451/docs/search.index/1d98360e7dff.js | 1 + pr/451/docs/search.index/1dbcf54491ae.js | 1 - pr/451/docs/search.index/1ddac7964cac.js | 1 + pr/451/docs/search.index/1de07b6bd5ed.js | 1 - pr/451/docs/search.index/1dff5e6ecf7f.js | 1 - pr/451/docs/search.index/1e2e1d96899e.js | 1 + pr/451/docs/search.index/1e93e59e94d9.js | 1 - pr/451/docs/search.index/1ebad890e62e.js | 1 - pr/451/docs/search.index/1ec93e54aaec.js | 1 + pr/451/docs/search.index/1ee39b692949.js | 1 - pr/451/docs/search.index/1f0b55d3dbc2.js | 1 + pr/451/docs/search.index/1f242d58f36b.js | 1 + pr/451/docs/search.index/1f4450a92511.js | 1 - pr/451/docs/search.index/1f54ccf79fde.js | 1 + pr/451/docs/search.index/1f8374f280ae.js | 1 - pr/451/docs/search.index/1f9a8e3654ea.js | 1 + pr/451/docs/search.index/1fac5249737c.js | 1 - pr/451/docs/search.index/201011f148a9.js | 1 - pr/451/docs/search.index/20a41ecff08f.js | 1 - pr/451/docs/search.index/20afefeb7ec4.js | 1 - pr/451/docs/search.index/20da1b8e8e24.js | 1 - pr/451/docs/search.index/20fbab33fc2f.js | 1 - pr/451/docs/search.index/2160978e1be3.js | 1 + pr/451/docs/search.index/21816dd58603.js | 1 + pr/451/docs/search.index/21a316596b65.js | 1 + pr/451/docs/search.index/21a6ad5e522a.js | 1 + pr/451/docs/search.index/21f47a3ec65b.js | 1 + pr/451/docs/search.index/221b8a1321dc.js | 1 + pr/451/docs/search.index/227000c17c1b.js | 1 - pr/451/docs/search.index/22b02790a247.js | 1 + pr/451/docs/search.index/22f66fe81db4.js | 1 - pr/451/docs/search.index/230d9ae0255e.js | 1 - pr/451/docs/search.index/235b929c3e77.js | 1 + pr/451/docs/search.index/238fff737ae7.js | 1 + pr/451/docs/search.index/23daddc85a2a.js | 1 - pr/451/docs/search.index/240436689105.js | 1 - pr/451/docs/search.index/243924e3924e.js | 1 + pr/451/docs/search.index/247d5e509be4.js | 1 + pr/451/docs/search.index/24b98fc4a43b.js | 1 + pr/451/docs/search.index/24fd72fc1f6c.js | 1 + pr/451/docs/search.index/25143dd2528f.js | 1 - pr/451/docs/search.index/25a2359dd30d.js | 1 - pr/451/docs/search.index/25fce273e5ba.js | 1 + pr/451/docs/search.index/2609108e1ba5.js | 1 + pr/451/docs/search.index/260cd4c5a9b2.js | 1 + pr/451/docs/search.index/2657ace38ed9.js | 1 - pr/451/docs/search.index/26625634ad63.js | 1 - pr/451/docs/search.index/2685e428a65d.js | 1 + pr/451/docs/search.index/26eaff1ecc2b.js | 1 - pr/451/docs/search.index/280b57a12fe8.js | 1 - pr/451/docs/search.index/28125c7f5b42.js | 1 + pr/451/docs/search.index/28183bdd4ff4.js | 1 + pr/451/docs/search.index/285de43c88ed.js | 1 + pr/451/docs/search.index/2888725fe937.js | 1 + pr/451/docs/search.index/2892218972b3.js | 1 - pr/451/docs/search.index/28d7b5f5abc8.js | 1 + pr/451/docs/search.index/29ac0c736fc1.js | 1 + pr/451/docs/search.index/29d270c7a752.js | 1 + pr/451/docs/search.index/29dd87e5e6be.js | 1 + pr/451/docs/search.index/2a552ae8044e.js | 1 - pr/451/docs/search.index/2a56df498b28.js | 1 + pr/451/docs/search.index/2a67720851d1.js | 1 - pr/451/docs/search.index/2a6d66bb35a1.js | 1 + pr/451/docs/search.index/2a8e4b0e890f.js | 1 - pr/451/docs/search.index/2bbea166f1f8.js | 1 - pr/451/docs/search.index/2caeb0254b5a.js | 1 + pr/451/docs/search.index/2d7629068747.js | 1 + pr/451/docs/search.index/2d92cd1657e9.js | 1 - pr/451/docs/search.index/2e327fd2bb24.js | 1 + pr/451/docs/search.index/2eaf4ab981c3.js | 1 - pr/451/docs/search.index/2eeacf2c8ee0.js | 1 - pr/451/docs/search.index/2ef1f9ee4b1b.js | 1 + pr/451/docs/search.index/2f7ecc5dbb1a.js | 1 - pr/451/docs/search.index/2f88dcc3b5f8.js | 1 + pr/451/docs/search.index/2fd38d8465d2.js | 1 + pr/451/docs/search.index/2febb6ceaf92.js | 1 + pr/451/docs/search.index/2ff30af08e21.js | 1 + pr/451/docs/search.index/3036a9249b54.js | 1 - pr/451/docs/search.index/306dd97f0179.js | 1 + pr/451/docs/search.index/31b45cf6ecff.js | 1 - pr/451/docs/search.index/320b0f4af278.js | 1 - pr/451/docs/search.index/321190b83abb.js | 1 - pr/451/docs/search.index/3257c10e66d5.js | 1 + pr/451/docs/search.index/3289f09b047b.js | 1 - pr/451/docs/search.index/32b740d07761.js | 1 + pr/451/docs/search.index/32d5e2081a15.js | 1 + pr/451/docs/search.index/32d9da1d0315.js | 1 + pr/451/docs/search.index/337d979fd6b5.js | 1 + pr/451/docs/search.index/3381b6452880.js | 1 - pr/451/docs/search.index/34191c056afb.js | 1 + pr/451/docs/search.index/342a647c77a6.js | 1 - pr/451/docs/search.index/355e9ccc3e7d.js | 1 - pr/451/docs/search.index/3580644ae6d8.js | 1 - pr/451/docs/search.index/35dbfa9e409c.js | 1 + pr/451/docs/search.index/3650ebdc9bae.js | 1 - pr/451/docs/search.index/3654024d46ce.js | 1 + pr/451/docs/search.index/36590b411474.js | 1 - pr/451/docs/search.index/368640dd3a84.js | 1 - pr/451/docs/search.index/36e021190be2.js | 1 + pr/451/docs/search.index/37ae2f2cc265.js | 1 - pr/451/docs/search.index/381301744cd9.js | 1 + pr/451/docs/search.index/384f556a9613.js | 1 + pr/451/docs/search.index/3874f15e7771.js | 1 - pr/451/docs/search.index/38b236771a65.js | 1 - pr/451/docs/search.index/38e853561a8a.js | 1 - pr/451/docs/search.index/39a52dda198c.js | 1 - pr/451/docs/search.index/39e247c24669.js | 1 + pr/451/docs/search.index/3a0723146c92.js | 1 - pr/451/docs/search.index/3a20dad21292.js | 1 + pr/451/docs/search.index/3b114748649d.js | 1 - pr/451/docs/search.index/3b4e28ed9312.js | 1 - pr/451/docs/search.index/3b71704824be.js | 1 + pr/451/docs/search.index/3c0f5abd09f3.js | 1 - pr/451/docs/search.index/3c791ceb8da1.js | 1 + pr/451/docs/search.index/3c8f419d6519.js | 1 - pr/451/docs/search.index/3ccd0c7a2788.js | 1 - pr/451/docs/search.index/3d12e183783e.js | 1 + pr/451/docs/search.index/3d1419c1550f.js | 1 + pr/451/docs/search.index/3d5f153390be.js | 1 - pr/451/docs/search.index/3d8d9ef3a31c.js | 1 + pr/451/docs/search.index/3d998da360d9.js | 1 + pr/451/docs/search.index/3dc6d08edc6b.js | 1 + pr/451/docs/search.index/3e10313c5965.js | 1 - pr/451/docs/search.index/3e3cbb04f471.js | 1 - pr/451/docs/search.index/3e3f10d5e550.js | 1 + pr/451/docs/search.index/3e98557ba4a3.js | 1 - pr/451/docs/search.index/3eddab2797bd.js | 1 + pr/451/docs/search.index/3f128b5def95.js | 1 + pr/451/docs/search.index/3f243484e57a.js | 1 - pr/451/docs/search.index/3f24cd5aa4fa.js | 1 - pr/451/docs/search.index/3f8645c00d7c.js | 1 - pr/451/docs/search.index/3fb5642e59ae.js | 1 + pr/451/docs/search.index/4031cc647b10.js | 1 - pr/451/docs/search.index/404afcce0ada.js | 1 - pr/451/docs/search.index/4100782a5d31.js | 1 - pr/451/docs/search.index/4145ba7520f0.js | 1 + pr/451/docs/search.index/41556e3d4595.js | 1 + pr/451/docs/search.index/4159f6c35981.js | 1 - pr/451/docs/search.index/41c9d94a3575.js | 1 + pr/451/docs/search.index/42b98052bb96.js | 1 + pr/451/docs/search.index/4314e4d51001.js | 1 + pr/451/docs/search.index/4351feb790fe.js | 1 - pr/451/docs/search.index/436e158b126a.js | 1 - pr/451/docs/search.index/43bef58741e0.js | 1 + pr/451/docs/search.index/441e425635ce.js | 1 - pr/451/docs/search.index/444d4497d47d.js | 1 + pr/451/docs/search.index/450719784301.js | 1 - pr/451/docs/search.index/4513c73d7beb.js | 1 + pr/451/docs/search.index/451831b31f8f.js | 1 + pr/451/docs/search.index/4539bd8aa823.js | 1 + pr/451/docs/search.index/4571532c4b5c.js | 1 + pr/451/docs/search.index/4581b8194ca6.js | 1 + pr/451/docs/search.index/45c7f13213d0.js | 1 - pr/451/docs/search.index/45cefb4b1fe7.js | 1 - pr/451/docs/search.index/46615455c43f.js | 1 - pr/451/docs/search.index/4663a4e119e7.js | 1 - pr/451/docs/search.index/4683fef789b0.js | 1 + pr/451/docs/search.index/46d02c2cf6f3.js | 1 - pr/451/docs/search.index/4701aa0c1499.js | 1 + pr/451/docs/search.index/4702bdda8368.js | 1 + pr/451/docs/search.index/470732838eeb.js | 1 - pr/451/docs/search.index/474b31e6c524.js | 1 - pr/451/docs/search.index/4805b324248d.js | 1 - pr/451/docs/search.index/48769e729a3a.js | 1 + pr/451/docs/search.index/4aa7b4743afe.js | 1 - pr/451/docs/search.index/4bd0dc5c36db.js | 1 + pr/451/docs/search.index/4c05ce7be64f.js | 1 - pr/451/docs/search.index/4c89ae5762cb.js | 1 - pr/451/docs/search.index/4d134b527dd8.js | 1 + pr/451/docs/search.index/4d420ba30216.js | 1 + pr/451/docs/search.index/4d60442fb3dc.js | 1 + pr/451/docs/search.index/4d88a08a2ae4.js | 1 - pr/451/docs/search.index/4e0fe03427ba.js | 1 - pr/451/docs/search.index/4e2f866a26d7.js | 1 + pr/451/docs/search.index/4eac59928f18.js | 1 - pr/451/docs/search.index/4ec9d96b60ba.js | 1 + pr/451/docs/search.index/4eed4a0cfe1e.js | 1 - pr/451/docs/search.index/4f8c49c523f4.js | 1 + pr/451/docs/search.index/4fae9a79a161.js | 1 - pr/451/docs/search.index/4fc7afbad082.js | 1 + pr/451/docs/search.index/5043dc5e7d82.js | 1 + pr/451/docs/search.index/50abe47e037f.js | 1 - pr/451/docs/search.index/5132048f5f5c.js | 1 + pr/451/docs/search.index/51f65056eec1.js | 1 + pr/451/docs/search.index/521c1cdfffb6.js | 1 - pr/451/docs/search.index/523d09888781.js | 1 + pr/451/docs/search.index/528006bc24f9.js | 1 + pr/451/docs/search.index/52b7dbce0a40.js | 1 - pr/451/docs/search.index/52ccda238ef2.js | 1 + pr/451/docs/search.index/536f57712388.js | 1 - pr/451/docs/search.index/5376c77b4a8d.js | 1 + pr/451/docs/search.index/538b5fc4e1ed.js | 1 - pr/451/docs/search.index/5392889fe77e.js | 1 - pr/451/docs/search.index/53dbf4b2839c.js | 1 + pr/451/docs/search.index/53ffa8e8252a.js | 1 - pr/451/docs/search.index/54278f1995a0.js | 1 + pr/451/docs/search.index/5478b18519c4.js | 1 - pr/451/docs/search.index/548eac6e4034.js | 1 - pr/451/docs/search.index/549a18f8219c.js | 1 - pr/451/docs/search.index/54a3a24e98f0.js | 1 + pr/451/docs/search.index/54aec76a13de.js | 1 + pr/451/docs/search.index/5535b627edd5.js | 1 - pr/451/docs/search.index/559b896a9166.js | 1 + pr/451/docs/search.index/56a96cba0f24.js | 1 + pr/451/docs/search.index/571482a0f60a.js | 1 - pr/451/docs/search.index/571c0d56c455.js | 1 + pr/451/docs/search.index/573b61c91606.js | 1 + pr/451/docs/search.index/5775f8d2dd1c.js | 1 + pr/451/docs/search.index/579677a41519.js | 1 - pr/451/docs/search.index/579724b12c01.js | 1 + pr/451/docs/search.index/57d12a4129f3.js | 1 + pr/451/docs/search.index/5802da10e9df.js | 1 - pr/451/docs/search.index/58045aae0759.js | 1 - pr/451/docs/search.index/58b912fa1a43.js | 1 + pr/451/docs/search.index/58fc7e75991d.js | 1 + pr/451/docs/search.index/596040a74c77.js | 1 + pr/451/docs/search.index/598001827816.js | 1 + pr/451/docs/search.index/59b3fb7f8021.js | 1 + pr/451/docs/search.index/59be03fa81eb.js | 1 - pr/451/docs/search.index/59c5b0e1f8ad.js | 1 - pr/451/docs/search.index/59fb890d29b8.js | 1 - pr/451/docs/search.index/5a22fd2fcd2b.js | 1 - pr/451/docs/search.index/5aaa90b55fc2.js | 1 + pr/451/docs/search.index/5ad143d05f59.js | 1 + pr/451/docs/search.index/5b4386429020.js | 1 + pr/451/docs/search.index/5b62c7da471c.js | 1 - pr/451/docs/search.index/5bbccf0d1c5c.js | 1 + pr/451/docs/search.index/5bd4900427a6.js | 1 - pr/451/docs/search.index/5bf126ddcd97.js | 1 - pr/451/docs/search.index/5c04ad06faf3.js | 1 + pr/451/docs/search.index/5c174cbd368c.js | 1 + pr/451/docs/search.index/5c2ba014f923.js | 1 + pr/451/docs/search.index/5c53e8bde1cc.js | 1 + pr/451/docs/search.index/5ccc63e2df25.js | 1 - pr/451/docs/search.index/5d45dfdef8cc.js | 1 - pr/451/docs/search.index/5d4cba66f76d.js | 1 + pr/451/docs/search.index/5d5b1b8d153b.js | 1 - pr/451/docs/search.index/5dbdf61761a4.js | 1 - pr/451/docs/search.index/5dd78db990e0.js | 1 + pr/451/docs/search.index/5e2391be14db.js | 1 + pr/451/docs/search.index/5e3e1ae9d4f1.js | 1 + pr/451/docs/search.index/5e60a22686e2.js | 1 + pr/451/docs/search.index/5ed9ecced0b1.js | 1 - pr/451/docs/search.index/5f1f66ca795b.js | 1 + pr/451/docs/search.index/5f940ff8b47f.js | 1 - pr/451/docs/search.index/6025c125c951.js | 1 + pr/451/docs/search.index/604e947796a4.js | 1 + pr/451/docs/search.index/608f704c3516.js | 1 + pr/451/docs/search.index/60fbf2ca92b7.js | 1 + pr/451/docs/search.index/6116929fe6af.js | 1 + pr/451/docs/search.index/61405852662c.js | 1 + pr/451/docs/search.index/618145397e6f.js | 1 + pr/451/docs/search.index/6186d9628089.js | 1 + pr/451/docs/search.index/61fa9fe339d6.js | 1 + pr/451/docs/search.index/6243561514b9.js | 1 + pr/451/docs/search.index/624a2471dcc3.js | 1 - pr/451/docs/search.index/62bb0fe614ca.js | 1 - pr/451/docs/search.index/62ce08268925.js | 1 - pr/451/docs/search.index/635558f41241.js | 1 - pr/451/docs/search.index/639489df947c.js | 1 + pr/451/docs/search.index/639fc837a5d5.js | 1 + pr/451/docs/search.index/63d5d6c31ff0.js | 1 - pr/451/docs/search.index/6441524172dd.js | 1 + pr/451/docs/search.index/64f401d0105c.js | 1 - pr/451/docs/search.index/655cb440fbba.js | 1 - pr/451/docs/search.index/657959d9dfd0.js | 1 - pr/451/docs/search.index/667f942da920.js | 1 - pr/451/docs/search.index/6693e2a7afc5.js | 1 + pr/451/docs/search.index/66dcaa05b5bb.js | 1 + pr/451/docs/search.index/670f0a9091b9.js | 1 + pr/451/docs/search.index/6718ecfbb765.js | 1 + pr/451/docs/search.index/67a942abe161.js | 1 + pr/451/docs/search.index/67bd888b1183.js | 1 + pr/451/docs/search.index/680921c7e0dc.js | 1 - pr/451/docs/search.index/688c1e1d0f3f.js | 1 + pr/451/docs/search.index/6902fbabc407.js | 1 + pr/451/docs/search.index/690e03933450.js | 1 - pr/451/docs/search.index/694efa293f2d.js | 1 - pr/451/docs/search.index/69efc07ee8fe.js | 1 + pr/451/docs/search.index/6a152aadbb4b.js | 1 - pr/451/docs/search.index/6a4e9b07d877.js | 1 - pr/451/docs/search.index/6a81a15ea6e9.js | 1 - pr/451/docs/search.index/6ab71af3e20b.js | 1 - pr/451/docs/search.index/6b3565993718.js | 1 + pr/451/docs/search.index/6b804baa315c.js | 1 + pr/451/docs/search.index/6b91b3b9e417.js | 1 + pr/451/docs/search.index/6c530d02ab41.js | 1 - pr/451/docs/search.index/6c8b565dd978.js | 1 - pr/451/docs/search.index/6d6ee039d782.js | 1 + pr/451/docs/search.index/6d8cc2ed1313.js | 1 - pr/451/docs/search.index/6d8f629c95e4.js | 1 + pr/451/docs/search.index/6dfbbd7cf8df.js | 1 - pr/451/docs/search.index/6e41df2f5abc.js | 1 + pr/451/docs/search.index/6e4c3193eddd.js | 1 - pr/451/docs/search.index/6e7e72e7f0eb.js | 1 - pr/451/docs/search.index/6e98d18aefa1.js | 1 - pr/451/docs/search.index/6e9cbfa350f1.js | 1 + pr/451/docs/search.index/6ede7ccd0b48.js | 1 - pr/451/docs/search.index/6f1ace4f0243.js | 1 - pr/451/docs/search.index/6f3b4b773035.js | 1 - pr/451/docs/search.index/6f920086d797.js | 1 + pr/451/docs/search.index/6fa3f569eb52.js | 1 - pr/451/docs/search.index/6ffa824fa9c9.js | 1 - pr/451/docs/search.index/7032cf56e781.js | 1 + pr/451/docs/search.index/7106b5f1eec5.js | 1 + pr/451/docs/search.index/710e3a47aed6.js | 1 + pr/451/docs/search.index/711105b55fc6.js | 1 - pr/451/docs/search.index/715f6cda5699.js | 1 - pr/451/docs/search.index/7163b34c9425.js | 1 + pr/451/docs/search.index/71f423a61c5f.js | 1 - pr/451/docs/search.index/731cfc3d033b.js | 1 - pr/451/docs/search.index/732444e4dd16.js | 1 + pr/451/docs/search.index/735698ad2cbf.js | 1 - pr/451/docs/search.index/7359d03f24c8.js | 1 - pr/451/docs/search.index/736cd7e92e7f.js | 1 + pr/451/docs/search.index/73772e5a75b7.js | 1 - pr/451/docs/search.index/738edcf2da0b.js | 1 + pr/451/docs/search.index/73e05ea17e51.js | 1 - pr/451/docs/search.index/74220e663fcd.js | 1 + pr/451/docs/search.index/74b289fd1fbb.js | 1 + pr/451/docs/search.index/74cc3e63f115.js | 1 - pr/451/docs/search.index/765cffa6a271.js | 1 - pr/451/docs/search.index/766d90f1d785.js | 1 - pr/451/docs/search.index/76f703a56322.js | 1 - pr/451/docs/search.index/771f0ba905a1.js | 1 - pr/451/docs/search.index/77798d930b56.js | 1 + pr/451/docs/search.index/77822d716af7.js | 1 + pr/451/docs/search.index/77be044378b5.js | 1 - pr/451/docs/search.index/77bf703f157b.js | 1 - pr/451/docs/search.index/77e679070a1d.js | 1 + pr/451/docs/search.index/780398a1ffe5.js | 1 + pr/451/docs/search.index/781f99b9d65a.js | 1 - pr/451/docs/search.index/7844f0d84b02.js | 1 + pr/451/docs/search.index/78878d5dabd9.js | 1 + pr/451/docs/search.index/78cce7048e1e.js | 1 + pr/451/docs/search.index/78f59f62da93.js | 1 + pr/451/docs/search.index/7918be142114.js | 1 - pr/451/docs/search.index/7969e190031a.js | 1 - pr/451/docs/search.index/799a0178fd53.js | 1 + pr/451/docs/search.index/79de9416e93c.js | 1 + pr/451/docs/search.index/7a61dea0bfb9.js | 1 - pr/451/docs/search.index/7b1673ee6360.js | 1 - pr/451/docs/search.index/7bb203f6c904.js | 1 + pr/451/docs/search.index/7bd7bca0a11f.js | 1 - pr/451/docs/search.index/7be830c7f373.js | 1 + pr/451/docs/search.index/7cc498cd5f4e.js | 1 + pr/451/docs/search.index/7ccfc824e9f7.js | 1 - pr/451/docs/search.index/7d0b131779fc.js | 1 + pr/451/docs/search.index/7da5ade5b83b.js | 1 + pr/451/docs/search.index/7dc6410c642a.js | 1 - pr/451/docs/search.index/7dca73af94f0.js | 1 + pr/451/docs/search.index/7dfde7047aaf.js | 1 + pr/451/docs/search.index/7e3b19027434.js | 1 - pr/451/docs/search.index/7e508d623850.js | 1 + pr/451/docs/search.index/7e644c6ffb35.js | 1 - pr/451/docs/search.index/7e7ee16d46d1.js | 1 + pr/451/docs/search.index/7ebdb25ba28a.js | 1 - pr/451/docs/search.index/7f6612368049.js | 1 - pr/451/docs/search.index/7f9dc1f44330.js | 1 + pr/451/docs/search.index/7f9fb0e71342.js | 1 + pr/451/docs/search.index/desc/59e6f7c5fb07.js | 1 - pr/451/docs/search.index/desc/791dbc1ca79a.js | 1 + .../docs/search.index/entry/7780e033398e.js | 1 - .../docs/search.index/entry/8a78b0eadc5e.js | 1 + .../search.index/function/378ad5ea86b4.js | 1 - .../search.index/function/bcf4aac8ded5.js | 1 + .../generic_inverted_index/af48d3aa5b7a.js | 1 - .../generic_inverted_index/e9848908a123.js | 1 + pr/451/docs/search.index/name/14e0528539da.js | 1 + pr/451/docs/search.index/name/edb16650d3a7.js | 1 - .../normalizedName/de3a57d53d3b.js | 1 - .../normalizedName/deb8ebf00d7a.js | 1 + pr/451/docs/search.index/path/934c7b66edd3.js | 1 + pr/451/docs/search.index/path/b5fe16f07d66.js | 1 - pr/451/docs/search.index/root.js | 2 +- pr/451/docs/search.index/type/42acd02dfb5a.js | 1 - pr/451/docs/search.index/type/5d148ce6032d.js | 1 + pr/451/docs/src-files.js | 4 +- pr/451/docs/src/bench/lib.rs.html | 2 +- pr/451/docs/src/noq/connection.rs.html | 3694 ++-- pr/451/docs/src/noq/endpoint.rs.html | 1915 +- pr/451/docs/src/noq/event_stream.rs.html | 4 +- pr/451/docs/src/noq/incoming.rs.html | 4 +- pr/451/docs/src/noq/lib.rs.html | 157 +- pr/451/docs/src/noq/path.rs.html | 812 +- pr/451/docs/src/noq/recv_stream.rs.html | 1213 +- pr/451/docs/src/noq/send_stream.rs.html | 805 +- pr/451/docs/src/noq_proto/cid_queue.rs.html | 774 +- pr/451/docs/src/noq_proto/config/mod.rs.html | 1277 +- .../src/noq_proto/config/transport.rs.html | 1730 +- pr/451/docs/src/noq_proto/congestion.rs.html | 224 +- .../congestion/bbr/bw_estimation.rs.html | 100 - .../noq_proto/congestion/bbr/min_max.rs.html | 152 - .../src/noq_proto/congestion/bbr/mod.rs.html | 647 - .../congestion/bbr3/max_filter.rs.html | 151 + .../src/noq_proto/congestion/bbr3/mod.rs.html | 1743 ++ .../src/noq_proto/congestion/cubic.rs.html | 377 +- .../src/noq_proto/congestion/new_reno.rs.html | 253 +- .../docs/src/noq_proto/connection/mod.rs.html | 14480 ++++++++-------- .../src/noq_proto/connection/pacing.rs.html | 725 +- .../connection/packet_builder.rs.html | 390 +- .../connection/packet_crypto.rs.html | 606 +- .../src/noq_proto/connection/paths.rs.html | 1981 ++- .../src/noq_proto/connection/qlog.rs.html | 1926 +- .../src/noq_proto/connection/spaces.rs.html | 3074 ++-- .../src/noq_proto/connection/stats.rs.html | 254 +- .../noq_proto/connection/streams/mod.rs.html | 1063 +- .../noq_proto/connection/streams/send.rs.html | 6 +- .../connection/streams/state.rs.html | 3746 ++-- .../src/noq_proto/connection/timer.rs.html | 940 +- .../src/noq_proto/crypto/ring_like.rs.html | 38 +- .../docs/src/noq_proto/crypto/rustls.rs.html | 412 +- pr/451/docs/src/noq_proto/endpoint.rs.html | 1894 +- pr/451/docs/src/noq_proto/frame.rs.html | 5179 +++--- pr/451/docs/src/noq_proto/lib.rs.html | 830 +- .../src/noq_proto/n0_nat_traversal.rs.html | 1629 +- pr/451/docs/src/noq_proto/packet.rs.html | 1686 +- .../range_set/array_range_set.rs.html | 2 +- pr/451/docs/src/noq_udp/lib.rs.html | 275 +- pr/451/docs/src/noq_udp/unix.rs.html | 2241 +-- pr/451/docs/src/perf/client.rs.html | 499 +- pr/451/docs/src/perf/lib.rs.html | 4 +- pr/451/docs/src/perf/server.rs.html | 197 +- pr/451/docs/src/perf/stats.rs.html | 581 +- .../docs/trait.impl/core/clone/trait.Clone.js | 4 +- pr/451/docs/trait.impl/core/cmp/trait.Eq.js | 4 +- .../trait.impl/core/cmp/trait.PartialEq.js | 4 +- .../trait.impl/core/convert/trait.From.js | 4 +- .../trait.impl/core/default/trait.Default.js | 4 +- .../docs/trait.impl/core/fmt/trait.Debug.js | 4 +- .../docs/trait.impl/core/marker/trait.Copy.js | 4 +- .../trait.impl/core/marker/trait.Freeze.js | 4 +- .../docs/trait.impl/core/marker/trait.Send.js | 4 +- .../core/marker/trait.StructuralPartialEq.js | 4 +- .../docs/trait.impl/core/marker/trait.Sync.js | 4 +- .../trait.impl/core/marker/trait.Unpin.js | 4 +- .../core/marker/trait.UnsafeUnpin.js | 4 +- .../panic/unwind_safe/trait.RefUnwindSafe.js | 4 +- .../panic/unwind_safe/trait.UnwindSafe.js | 4 +- 827 files changed, 33354 insertions(+), 30612 deletions(-) rename pr/451/docs/{noq_proto/congestion/struct.Bbr.html => noq/congestion/struct.Bbr3.html} (53%) rename pr/451/docs/{noq_proto/congestion/struct.BbrConfig.html => noq/congestion/struct.Bbr3Config.html} (62%) create mode 100644 pr/451/docs/noq/connection/struct.Closed.html create mode 100644 pr/451/docs/noq/enum.ClosePathError.html create mode 100644 pr/451/docs/noq/enum.PathError.html rename pr/451/docs/{noq_proto/struct.Written.html => noq/enum.PathEvent.html} (53%) rename pr/451/docs/noq/{struct.Written.html => enum.PathStatus.html} (60%) create mode 100644 pr/451/docs/noq/enum.SetPathStatusError.html create mode 100644 pr/451/docs/noq/struct.Closed.html create mode 100644 pr/451/docs/noq/struct.ClosedPath.html create mode 100644 pr/451/docs/noq/struct.DecryptedInitial.html delete mode 100644 pr/451/docs/noq_proto/config/transport/constant.DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED.html rename pr/451/docs/noq_proto/congestion/{bbr/struct.Bbr.html => bbr3/struct.Bbr3.html} (64%) rename pr/451/docs/noq_proto/congestion/{bbr/struct.BbrConfig.html => bbr3/struct.Bbr3Config.html} (61%) rename pr/451/docs/{noq/congestion/struct.Bbr.html => noq_proto/congestion/struct.Bbr3.html} (55%) rename pr/451/docs/{noq/congestion/struct.BbrConfig.html => noq_proto/congestion/struct.Bbr3Config.html} (61%) delete mode 100644 pr/451/docs/noq_proto/connection/streams/send/struct.Written.html delete mode 100644 pr/451/docs/noq_proto/constant.DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED.html delete mode 100644 pr/451/docs/search.index/0033f3568d59.js create mode 100644 pr/451/docs/search.index/005f416926ad.js create mode 100644 pr/451/docs/search.index/00c4f658f46f.js create mode 100644 pr/451/docs/search.index/0125cbd5bba3.js delete mode 100644 pr/451/docs/search.index/016287c739af.js delete mode 100644 pr/451/docs/search.index/01818b9a5dfd.js delete mode 100644 pr/451/docs/search.index/01dabba3dd0f.js delete mode 100644 pr/451/docs/search.index/0258058d4a15.js create mode 100644 pr/451/docs/search.index/02dd940163a0.js create mode 100644 pr/451/docs/search.index/0302cc930c05.js delete mode 100644 pr/451/docs/search.index/03099968b016.js create mode 100644 pr/451/docs/search.index/03aae57c1bdc.js delete mode 100644 pr/451/docs/search.index/03d8571d8861.js delete mode 100644 pr/451/docs/search.index/04067d1944a2.js create mode 100644 pr/451/docs/search.index/040989688a54.js delete mode 100644 pr/451/docs/search.index/0435c5f11e89.js delete mode 100644 pr/451/docs/search.index/04cd208f0148.js create mode 100644 pr/451/docs/search.index/0527185c0cd7.js delete mode 100644 pr/451/docs/search.index/0530b4c13347.js delete mode 100644 pr/451/docs/search.index/058523a6e4f8.js create mode 100644 pr/451/docs/search.index/05daabf53ac2.js create mode 100644 pr/451/docs/search.index/05ff51e69bf3.js delete mode 100644 pr/451/docs/search.index/0697183800b7.js delete mode 100644 pr/451/docs/search.index/06c25d8359bd.js delete mode 100644 pr/451/docs/search.index/06c6b77500fe.js create mode 100644 pr/451/docs/search.index/070f6c0257e1.js delete mode 100644 pr/451/docs/search.index/0717d66ebc8c.js delete mode 100644 pr/451/docs/search.index/0740e388ca16.js create mode 100644 pr/451/docs/search.index/0777c1b794b3.js delete mode 100644 pr/451/docs/search.index/080595ed9c0d.js create mode 100644 pr/451/docs/search.index/082363d032b8.js delete mode 100644 pr/451/docs/search.index/08d9a5c3e851.js delete mode 100644 pr/451/docs/search.index/09031ecd90af.js create mode 100644 pr/451/docs/search.index/090c885d9b86.js create mode 100644 pr/451/docs/search.index/09d1502998bb.js delete mode 100644 pr/451/docs/search.index/0a8ad4b154a4.js create mode 100644 pr/451/docs/search.index/0a98c9fc8112.js create mode 100644 pr/451/docs/search.index/0ab5109df87a.js create mode 100644 pr/451/docs/search.index/0add2bce1a52.js delete mode 100644 pr/451/docs/search.index/0bac143bcb50.js delete mode 100644 pr/451/docs/search.index/0bca21001963.js delete mode 100644 pr/451/docs/search.index/0bd42362d0ac.js create mode 100644 pr/451/docs/search.index/0bdf527b5d3f.js create mode 100644 pr/451/docs/search.index/0c258f5e1776.js create mode 100644 pr/451/docs/search.index/0c57b4f8c6b4.js delete mode 100644 pr/451/docs/search.index/0ca93a7be19c.js delete mode 100644 pr/451/docs/search.index/0d6bfa8c2af1.js create mode 100644 pr/451/docs/search.index/0d6fbddc62d7.js delete mode 100644 pr/451/docs/search.index/0e2aac44d870.js delete mode 100644 pr/451/docs/search.index/0e4d0f710b2b.js delete mode 100644 pr/451/docs/search.index/0f30d894e39f.js delete mode 100644 pr/451/docs/search.index/100cd8e013cf.js delete mode 100644 pr/451/docs/search.index/1074492a6ce1.js create mode 100644 pr/451/docs/search.index/10ae14a3503e.js create mode 100644 pr/451/docs/search.index/116465ed6584.js delete mode 100644 pr/451/docs/search.index/117405adffd2.js delete mode 100644 pr/451/docs/search.index/120419dec60e.js create mode 100644 pr/451/docs/search.index/120ac1721451.js create mode 100644 pr/451/docs/search.index/12ff91055174.js delete mode 100644 pr/451/docs/search.index/133a2aaf2619.js create mode 100644 pr/451/docs/search.index/1363513dee0c.js create mode 100644 pr/451/docs/search.index/139d49b47758.js delete mode 100644 pr/451/docs/search.index/13baa5793c64.js delete mode 100644 pr/451/docs/search.index/1507fd45a0d0.js delete mode 100644 pr/451/docs/search.index/15883d436710.js delete mode 100644 pr/451/docs/search.index/1593ad1b0859.js delete mode 100644 pr/451/docs/search.index/15d820844df2.js create mode 100644 pr/451/docs/search.index/162fe2003634.js create mode 100644 pr/451/docs/search.index/16850befe1eb.js create mode 100644 pr/451/docs/search.index/169efb3fe210.js delete mode 100644 pr/451/docs/search.index/16ea9b1933a4.js delete mode 100644 pr/451/docs/search.index/17a463b91cd9.js create mode 100644 pr/451/docs/search.index/17ac3cf6f003.js create mode 100644 pr/451/docs/search.index/17b7a87bec39.js delete mode 100644 pr/451/docs/search.index/18eec5319d0e.js create mode 100644 pr/451/docs/search.index/190a93e1a007.js create mode 100644 pr/451/docs/search.index/19705dd8c3fb.js delete mode 100644 pr/451/docs/search.index/19baf510d15e.js delete mode 100644 pr/451/docs/search.index/19d0359d569b.js delete mode 100644 pr/451/docs/search.index/1a66cd07757a.js delete mode 100644 pr/451/docs/search.index/1b2b113ba497.js delete mode 100644 pr/451/docs/search.index/1b7cc07f1f34.js delete mode 100644 pr/451/docs/search.index/1bf00b4f0c44.js delete mode 100644 pr/451/docs/search.index/1c262f510d82.js create mode 100644 pr/451/docs/search.index/1c28e9635eec.js create mode 100644 pr/451/docs/search.index/1c36524cf913.js create mode 100644 pr/451/docs/search.index/1c43b144cda4.js create mode 100644 pr/451/docs/search.index/1cbd4fd00132.js create mode 100644 pr/451/docs/search.index/1cd53c18f140.js create mode 100644 pr/451/docs/search.index/1d05d97e46ea.js delete mode 100644 pr/451/docs/search.index/1d3f2d3bd8f6.js delete mode 100644 pr/451/docs/search.index/1d8c8f6f3340.js create mode 100644 pr/451/docs/search.index/1d98360e7dff.js delete mode 100644 pr/451/docs/search.index/1dbcf54491ae.js create mode 100644 pr/451/docs/search.index/1ddac7964cac.js delete mode 100644 pr/451/docs/search.index/1de07b6bd5ed.js delete mode 100644 pr/451/docs/search.index/1dff5e6ecf7f.js create mode 100644 pr/451/docs/search.index/1e2e1d96899e.js delete mode 100644 pr/451/docs/search.index/1e93e59e94d9.js delete mode 100644 pr/451/docs/search.index/1ebad890e62e.js create mode 100644 pr/451/docs/search.index/1ec93e54aaec.js delete mode 100644 pr/451/docs/search.index/1ee39b692949.js create mode 100644 pr/451/docs/search.index/1f0b55d3dbc2.js create mode 100644 pr/451/docs/search.index/1f242d58f36b.js delete mode 100644 pr/451/docs/search.index/1f4450a92511.js create mode 100644 pr/451/docs/search.index/1f54ccf79fde.js delete mode 100644 pr/451/docs/search.index/1f8374f280ae.js create mode 100644 pr/451/docs/search.index/1f9a8e3654ea.js delete mode 100644 pr/451/docs/search.index/1fac5249737c.js delete mode 100644 pr/451/docs/search.index/201011f148a9.js delete mode 100644 pr/451/docs/search.index/20a41ecff08f.js delete mode 100644 pr/451/docs/search.index/20afefeb7ec4.js delete mode 100644 pr/451/docs/search.index/20da1b8e8e24.js delete mode 100644 pr/451/docs/search.index/20fbab33fc2f.js create mode 100644 pr/451/docs/search.index/2160978e1be3.js create mode 100644 pr/451/docs/search.index/21816dd58603.js create mode 100644 pr/451/docs/search.index/21a316596b65.js create mode 100644 pr/451/docs/search.index/21a6ad5e522a.js create mode 100644 pr/451/docs/search.index/21f47a3ec65b.js create mode 100644 pr/451/docs/search.index/221b8a1321dc.js delete mode 100644 pr/451/docs/search.index/227000c17c1b.js create mode 100644 pr/451/docs/search.index/22b02790a247.js delete mode 100644 pr/451/docs/search.index/22f66fe81db4.js delete mode 100644 pr/451/docs/search.index/230d9ae0255e.js create mode 100644 pr/451/docs/search.index/235b929c3e77.js create mode 100644 pr/451/docs/search.index/238fff737ae7.js delete mode 100644 pr/451/docs/search.index/23daddc85a2a.js delete mode 100644 pr/451/docs/search.index/240436689105.js create mode 100644 pr/451/docs/search.index/243924e3924e.js create mode 100644 pr/451/docs/search.index/247d5e509be4.js create mode 100644 pr/451/docs/search.index/24b98fc4a43b.js create mode 100644 pr/451/docs/search.index/24fd72fc1f6c.js delete mode 100644 pr/451/docs/search.index/25143dd2528f.js delete mode 100644 pr/451/docs/search.index/25a2359dd30d.js create mode 100644 pr/451/docs/search.index/25fce273e5ba.js create mode 100644 pr/451/docs/search.index/2609108e1ba5.js create mode 100644 pr/451/docs/search.index/260cd4c5a9b2.js delete mode 100644 pr/451/docs/search.index/2657ace38ed9.js delete mode 100644 pr/451/docs/search.index/26625634ad63.js create mode 100644 pr/451/docs/search.index/2685e428a65d.js delete mode 100644 pr/451/docs/search.index/26eaff1ecc2b.js delete mode 100644 pr/451/docs/search.index/280b57a12fe8.js create mode 100644 pr/451/docs/search.index/28125c7f5b42.js create mode 100644 pr/451/docs/search.index/28183bdd4ff4.js create mode 100644 pr/451/docs/search.index/285de43c88ed.js create mode 100644 pr/451/docs/search.index/2888725fe937.js delete mode 100644 pr/451/docs/search.index/2892218972b3.js create mode 100644 pr/451/docs/search.index/28d7b5f5abc8.js create mode 100644 pr/451/docs/search.index/29ac0c736fc1.js create mode 100644 pr/451/docs/search.index/29d270c7a752.js create mode 100644 pr/451/docs/search.index/29dd87e5e6be.js delete mode 100644 pr/451/docs/search.index/2a552ae8044e.js create mode 100644 pr/451/docs/search.index/2a56df498b28.js delete mode 100644 pr/451/docs/search.index/2a67720851d1.js create mode 100644 pr/451/docs/search.index/2a6d66bb35a1.js delete mode 100644 pr/451/docs/search.index/2a8e4b0e890f.js delete mode 100644 pr/451/docs/search.index/2bbea166f1f8.js create mode 100644 pr/451/docs/search.index/2caeb0254b5a.js create mode 100644 pr/451/docs/search.index/2d7629068747.js delete mode 100644 pr/451/docs/search.index/2d92cd1657e9.js create mode 100644 pr/451/docs/search.index/2e327fd2bb24.js delete mode 100644 pr/451/docs/search.index/2eaf4ab981c3.js delete mode 100644 pr/451/docs/search.index/2eeacf2c8ee0.js create mode 100644 pr/451/docs/search.index/2ef1f9ee4b1b.js delete mode 100644 pr/451/docs/search.index/2f7ecc5dbb1a.js create mode 100644 pr/451/docs/search.index/2f88dcc3b5f8.js create mode 100644 pr/451/docs/search.index/2fd38d8465d2.js create mode 100644 pr/451/docs/search.index/2febb6ceaf92.js create mode 100644 pr/451/docs/search.index/2ff30af08e21.js delete mode 100644 pr/451/docs/search.index/3036a9249b54.js create mode 100644 pr/451/docs/search.index/306dd97f0179.js delete mode 100644 pr/451/docs/search.index/31b45cf6ecff.js delete mode 100644 pr/451/docs/search.index/320b0f4af278.js delete mode 100644 pr/451/docs/search.index/321190b83abb.js create mode 100644 pr/451/docs/search.index/3257c10e66d5.js delete mode 100644 pr/451/docs/search.index/3289f09b047b.js create mode 100644 pr/451/docs/search.index/32b740d07761.js create mode 100644 pr/451/docs/search.index/32d5e2081a15.js create mode 100644 pr/451/docs/search.index/32d9da1d0315.js create mode 100644 pr/451/docs/search.index/337d979fd6b5.js delete mode 100644 pr/451/docs/search.index/3381b6452880.js create mode 100644 pr/451/docs/search.index/34191c056afb.js delete mode 100644 pr/451/docs/search.index/342a647c77a6.js delete mode 100644 pr/451/docs/search.index/355e9ccc3e7d.js delete mode 100644 pr/451/docs/search.index/3580644ae6d8.js create mode 100644 pr/451/docs/search.index/35dbfa9e409c.js delete mode 100644 pr/451/docs/search.index/3650ebdc9bae.js create mode 100644 pr/451/docs/search.index/3654024d46ce.js delete mode 100644 pr/451/docs/search.index/36590b411474.js delete mode 100644 pr/451/docs/search.index/368640dd3a84.js create mode 100644 pr/451/docs/search.index/36e021190be2.js delete mode 100644 pr/451/docs/search.index/37ae2f2cc265.js create mode 100644 pr/451/docs/search.index/381301744cd9.js create mode 100644 pr/451/docs/search.index/384f556a9613.js delete mode 100644 pr/451/docs/search.index/3874f15e7771.js delete mode 100644 pr/451/docs/search.index/38b236771a65.js delete mode 100644 pr/451/docs/search.index/38e853561a8a.js delete mode 100644 pr/451/docs/search.index/39a52dda198c.js create mode 100644 pr/451/docs/search.index/39e247c24669.js delete mode 100644 pr/451/docs/search.index/3a0723146c92.js create mode 100644 pr/451/docs/search.index/3a20dad21292.js delete mode 100644 pr/451/docs/search.index/3b114748649d.js delete mode 100644 pr/451/docs/search.index/3b4e28ed9312.js create mode 100644 pr/451/docs/search.index/3b71704824be.js delete mode 100644 pr/451/docs/search.index/3c0f5abd09f3.js create mode 100644 pr/451/docs/search.index/3c791ceb8da1.js delete mode 100644 pr/451/docs/search.index/3c8f419d6519.js delete mode 100644 pr/451/docs/search.index/3ccd0c7a2788.js create mode 100644 pr/451/docs/search.index/3d12e183783e.js create mode 100644 pr/451/docs/search.index/3d1419c1550f.js delete mode 100644 pr/451/docs/search.index/3d5f153390be.js create mode 100644 pr/451/docs/search.index/3d8d9ef3a31c.js create mode 100644 pr/451/docs/search.index/3d998da360d9.js create mode 100644 pr/451/docs/search.index/3dc6d08edc6b.js delete mode 100644 pr/451/docs/search.index/3e10313c5965.js delete mode 100644 pr/451/docs/search.index/3e3cbb04f471.js create mode 100644 pr/451/docs/search.index/3e3f10d5e550.js delete mode 100644 pr/451/docs/search.index/3e98557ba4a3.js create mode 100644 pr/451/docs/search.index/3eddab2797bd.js create mode 100644 pr/451/docs/search.index/3f128b5def95.js delete mode 100644 pr/451/docs/search.index/3f243484e57a.js delete mode 100644 pr/451/docs/search.index/3f24cd5aa4fa.js delete mode 100644 pr/451/docs/search.index/3f8645c00d7c.js create mode 100644 pr/451/docs/search.index/3fb5642e59ae.js delete mode 100644 pr/451/docs/search.index/4031cc647b10.js delete mode 100644 pr/451/docs/search.index/404afcce0ada.js delete mode 100644 pr/451/docs/search.index/4100782a5d31.js create mode 100644 pr/451/docs/search.index/4145ba7520f0.js create mode 100644 pr/451/docs/search.index/41556e3d4595.js delete mode 100644 pr/451/docs/search.index/4159f6c35981.js create mode 100644 pr/451/docs/search.index/41c9d94a3575.js create mode 100644 pr/451/docs/search.index/42b98052bb96.js create mode 100644 pr/451/docs/search.index/4314e4d51001.js delete mode 100644 pr/451/docs/search.index/4351feb790fe.js delete mode 100644 pr/451/docs/search.index/436e158b126a.js create mode 100644 pr/451/docs/search.index/43bef58741e0.js delete mode 100644 pr/451/docs/search.index/441e425635ce.js create mode 100644 pr/451/docs/search.index/444d4497d47d.js delete mode 100644 pr/451/docs/search.index/450719784301.js create mode 100644 pr/451/docs/search.index/4513c73d7beb.js create mode 100644 pr/451/docs/search.index/451831b31f8f.js create mode 100644 pr/451/docs/search.index/4539bd8aa823.js create mode 100644 pr/451/docs/search.index/4571532c4b5c.js create mode 100644 pr/451/docs/search.index/4581b8194ca6.js delete mode 100644 pr/451/docs/search.index/45c7f13213d0.js delete mode 100644 pr/451/docs/search.index/45cefb4b1fe7.js delete mode 100644 pr/451/docs/search.index/46615455c43f.js delete mode 100644 pr/451/docs/search.index/4663a4e119e7.js create mode 100644 pr/451/docs/search.index/4683fef789b0.js delete mode 100644 pr/451/docs/search.index/46d02c2cf6f3.js create mode 100644 pr/451/docs/search.index/4701aa0c1499.js create mode 100644 pr/451/docs/search.index/4702bdda8368.js delete mode 100644 pr/451/docs/search.index/470732838eeb.js delete mode 100644 pr/451/docs/search.index/474b31e6c524.js delete mode 100644 pr/451/docs/search.index/4805b324248d.js create mode 100644 pr/451/docs/search.index/48769e729a3a.js delete mode 100644 pr/451/docs/search.index/4aa7b4743afe.js create mode 100644 pr/451/docs/search.index/4bd0dc5c36db.js delete mode 100644 pr/451/docs/search.index/4c05ce7be64f.js delete mode 100644 pr/451/docs/search.index/4c89ae5762cb.js create mode 100644 pr/451/docs/search.index/4d134b527dd8.js create mode 100644 pr/451/docs/search.index/4d420ba30216.js create mode 100644 pr/451/docs/search.index/4d60442fb3dc.js delete mode 100644 pr/451/docs/search.index/4d88a08a2ae4.js delete mode 100644 pr/451/docs/search.index/4e0fe03427ba.js create mode 100644 pr/451/docs/search.index/4e2f866a26d7.js delete mode 100644 pr/451/docs/search.index/4eac59928f18.js create mode 100644 pr/451/docs/search.index/4ec9d96b60ba.js delete mode 100644 pr/451/docs/search.index/4eed4a0cfe1e.js create mode 100644 pr/451/docs/search.index/4f8c49c523f4.js delete mode 100644 pr/451/docs/search.index/4fae9a79a161.js create mode 100644 pr/451/docs/search.index/4fc7afbad082.js create mode 100644 pr/451/docs/search.index/5043dc5e7d82.js delete mode 100644 pr/451/docs/search.index/50abe47e037f.js create mode 100644 pr/451/docs/search.index/5132048f5f5c.js create mode 100644 pr/451/docs/search.index/51f65056eec1.js delete mode 100644 pr/451/docs/search.index/521c1cdfffb6.js create mode 100644 pr/451/docs/search.index/523d09888781.js create mode 100644 pr/451/docs/search.index/528006bc24f9.js delete mode 100644 pr/451/docs/search.index/52b7dbce0a40.js create mode 100644 pr/451/docs/search.index/52ccda238ef2.js delete mode 100644 pr/451/docs/search.index/536f57712388.js create mode 100644 pr/451/docs/search.index/5376c77b4a8d.js delete mode 100644 pr/451/docs/search.index/538b5fc4e1ed.js delete mode 100644 pr/451/docs/search.index/5392889fe77e.js create mode 100644 pr/451/docs/search.index/53dbf4b2839c.js delete mode 100644 pr/451/docs/search.index/53ffa8e8252a.js create mode 100644 pr/451/docs/search.index/54278f1995a0.js delete mode 100644 pr/451/docs/search.index/5478b18519c4.js delete mode 100644 pr/451/docs/search.index/548eac6e4034.js delete mode 100644 pr/451/docs/search.index/549a18f8219c.js create mode 100644 pr/451/docs/search.index/54a3a24e98f0.js create mode 100644 pr/451/docs/search.index/54aec76a13de.js delete mode 100644 pr/451/docs/search.index/5535b627edd5.js create mode 100644 pr/451/docs/search.index/559b896a9166.js create mode 100644 pr/451/docs/search.index/56a96cba0f24.js delete mode 100644 pr/451/docs/search.index/571482a0f60a.js create mode 100644 pr/451/docs/search.index/571c0d56c455.js create mode 100644 pr/451/docs/search.index/573b61c91606.js create mode 100644 pr/451/docs/search.index/5775f8d2dd1c.js delete mode 100644 pr/451/docs/search.index/579677a41519.js create mode 100644 pr/451/docs/search.index/579724b12c01.js create mode 100644 pr/451/docs/search.index/57d12a4129f3.js delete mode 100644 pr/451/docs/search.index/5802da10e9df.js delete mode 100644 pr/451/docs/search.index/58045aae0759.js create mode 100644 pr/451/docs/search.index/58b912fa1a43.js create mode 100644 pr/451/docs/search.index/58fc7e75991d.js create mode 100644 pr/451/docs/search.index/596040a74c77.js create mode 100644 pr/451/docs/search.index/598001827816.js create mode 100644 pr/451/docs/search.index/59b3fb7f8021.js delete mode 100644 pr/451/docs/search.index/59be03fa81eb.js delete mode 100644 pr/451/docs/search.index/59c5b0e1f8ad.js delete mode 100644 pr/451/docs/search.index/59fb890d29b8.js delete mode 100644 pr/451/docs/search.index/5a22fd2fcd2b.js create mode 100644 pr/451/docs/search.index/5aaa90b55fc2.js create mode 100644 pr/451/docs/search.index/5ad143d05f59.js create mode 100644 pr/451/docs/search.index/5b4386429020.js delete mode 100644 pr/451/docs/search.index/5b62c7da471c.js create mode 100644 pr/451/docs/search.index/5bbccf0d1c5c.js delete mode 100644 pr/451/docs/search.index/5bd4900427a6.js delete mode 100644 pr/451/docs/search.index/5bf126ddcd97.js create mode 100644 pr/451/docs/search.index/5c04ad06faf3.js create mode 100644 pr/451/docs/search.index/5c174cbd368c.js create mode 100644 pr/451/docs/search.index/5c2ba014f923.js create mode 100644 pr/451/docs/search.index/5c53e8bde1cc.js delete mode 100644 pr/451/docs/search.index/5ccc63e2df25.js delete mode 100644 pr/451/docs/search.index/5d45dfdef8cc.js create mode 100644 pr/451/docs/search.index/5d4cba66f76d.js delete mode 100644 pr/451/docs/search.index/5d5b1b8d153b.js delete mode 100644 pr/451/docs/search.index/5dbdf61761a4.js create mode 100644 pr/451/docs/search.index/5dd78db990e0.js create mode 100644 pr/451/docs/search.index/5e2391be14db.js create mode 100644 pr/451/docs/search.index/5e3e1ae9d4f1.js create mode 100644 pr/451/docs/search.index/5e60a22686e2.js delete mode 100644 pr/451/docs/search.index/5ed9ecced0b1.js create mode 100644 pr/451/docs/search.index/5f1f66ca795b.js delete mode 100644 pr/451/docs/search.index/5f940ff8b47f.js create mode 100644 pr/451/docs/search.index/6025c125c951.js create mode 100644 pr/451/docs/search.index/604e947796a4.js create mode 100644 pr/451/docs/search.index/608f704c3516.js create mode 100644 pr/451/docs/search.index/60fbf2ca92b7.js create mode 100644 pr/451/docs/search.index/6116929fe6af.js create mode 100644 pr/451/docs/search.index/61405852662c.js create mode 100644 pr/451/docs/search.index/618145397e6f.js create mode 100644 pr/451/docs/search.index/6186d9628089.js create mode 100644 pr/451/docs/search.index/61fa9fe339d6.js create mode 100644 pr/451/docs/search.index/6243561514b9.js delete mode 100644 pr/451/docs/search.index/624a2471dcc3.js delete mode 100644 pr/451/docs/search.index/62bb0fe614ca.js delete mode 100644 pr/451/docs/search.index/62ce08268925.js delete mode 100644 pr/451/docs/search.index/635558f41241.js create mode 100644 pr/451/docs/search.index/639489df947c.js create mode 100644 pr/451/docs/search.index/639fc837a5d5.js delete mode 100644 pr/451/docs/search.index/63d5d6c31ff0.js create mode 100644 pr/451/docs/search.index/6441524172dd.js delete mode 100644 pr/451/docs/search.index/64f401d0105c.js delete mode 100644 pr/451/docs/search.index/655cb440fbba.js delete mode 100644 pr/451/docs/search.index/657959d9dfd0.js delete mode 100644 pr/451/docs/search.index/667f942da920.js create mode 100644 pr/451/docs/search.index/6693e2a7afc5.js create mode 100644 pr/451/docs/search.index/66dcaa05b5bb.js create mode 100644 pr/451/docs/search.index/670f0a9091b9.js create mode 100644 pr/451/docs/search.index/6718ecfbb765.js create mode 100644 pr/451/docs/search.index/67a942abe161.js create mode 100644 pr/451/docs/search.index/67bd888b1183.js delete mode 100644 pr/451/docs/search.index/680921c7e0dc.js create mode 100644 pr/451/docs/search.index/688c1e1d0f3f.js create mode 100644 pr/451/docs/search.index/6902fbabc407.js delete mode 100644 pr/451/docs/search.index/690e03933450.js delete mode 100644 pr/451/docs/search.index/694efa293f2d.js create mode 100644 pr/451/docs/search.index/69efc07ee8fe.js delete mode 100644 pr/451/docs/search.index/6a152aadbb4b.js delete mode 100644 pr/451/docs/search.index/6a4e9b07d877.js delete mode 100644 pr/451/docs/search.index/6a81a15ea6e9.js delete mode 100644 pr/451/docs/search.index/6ab71af3e20b.js create mode 100644 pr/451/docs/search.index/6b3565993718.js create mode 100644 pr/451/docs/search.index/6b804baa315c.js create mode 100644 pr/451/docs/search.index/6b91b3b9e417.js delete mode 100644 pr/451/docs/search.index/6c530d02ab41.js delete mode 100644 pr/451/docs/search.index/6c8b565dd978.js create mode 100644 pr/451/docs/search.index/6d6ee039d782.js delete mode 100644 pr/451/docs/search.index/6d8cc2ed1313.js create mode 100644 pr/451/docs/search.index/6d8f629c95e4.js delete mode 100644 pr/451/docs/search.index/6dfbbd7cf8df.js create mode 100644 pr/451/docs/search.index/6e41df2f5abc.js delete mode 100644 pr/451/docs/search.index/6e4c3193eddd.js delete mode 100644 pr/451/docs/search.index/6e7e72e7f0eb.js delete mode 100644 pr/451/docs/search.index/6e98d18aefa1.js create mode 100644 pr/451/docs/search.index/6e9cbfa350f1.js delete mode 100644 pr/451/docs/search.index/6ede7ccd0b48.js delete mode 100644 pr/451/docs/search.index/6f1ace4f0243.js delete mode 100644 pr/451/docs/search.index/6f3b4b773035.js create mode 100644 pr/451/docs/search.index/6f920086d797.js delete mode 100644 pr/451/docs/search.index/6fa3f569eb52.js delete mode 100644 pr/451/docs/search.index/6ffa824fa9c9.js create mode 100644 pr/451/docs/search.index/7032cf56e781.js create mode 100644 pr/451/docs/search.index/7106b5f1eec5.js create mode 100644 pr/451/docs/search.index/710e3a47aed6.js delete mode 100644 pr/451/docs/search.index/711105b55fc6.js delete mode 100644 pr/451/docs/search.index/715f6cda5699.js create mode 100644 pr/451/docs/search.index/7163b34c9425.js delete mode 100644 pr/451/docs/search.index/71f423a61c5f.js delete mode 100644 pr/451/docs/search.index/731cfc3d033b.js create mode 100644 pr/451/docs/search.index/732444e4dd16.js delete mode 100644 pr/451/docs/search.index/735698ad2cbf.js delete mode 100644 pr/451/docs/search.index/7359d03f24c8.js create mode 100644 pr/451/docs/search.index/736cd7e92e7f.js delete mode 100644 pr/451/docs/search.index/73772e5a75b7.js create mode 100644 pr/451/docs/search.index/738edcf2da0b.js delete mode 100644 pr/451/docs/search.index/73e05ea17e51.js create mode 100644 pr/451/docs/search.index/74220e663fcd.js create mode 100644 pr/451/docs/search.index/74b289fd1fbb.js delete mode 100644 pr/451/docs/search.index/74cc3e63f115.js delete mode 100644 pr/451/docs/search.index/765cffa6a271.js delete mode 100644 pr/451/docs/search.index/766d90f1d785.js delete mode 100644 pr/451/docs/search.index/76f703a56322.js delete mode 100644 pr/451/docs/search.index/771f0ba905a1.js create mode 100644 pr/451/docs/search.index/77798d930b56.js create mode 100644 pr/451/docs/search.index/77822d716af7.js delete mode 100644 pr/451/docs/search.index/77be044378b5.js delete mode 100644 pr/451/docs/search.index/77bf703f157b.js create mode 100644 pr/451/docs/search.index/77e679070a1d.js create mode 100644 pr/451/docs/search.index/780398a1ffe5.js delete mode 100644 pr/451/docs/search.index/781f99b9d65a.js create mode 100644 pr/451/docs/search.index/7844f0d84b02.js create mode 100644 pr/451/docs/search.index/78878d5dabd9.js create mode 100644 pr/451/docs/search.index/78cce7048e1e.js create mode 100644 pr/451/docs/search.index/78f59f62da93.js delete mode 100644 pr/451/docs/search.index/7918be142114.js delete mode 100644 pr/451/docs/search.index/7969e190031a.js create mode 100644 pr/451/docs/search.index/799a0178fd53.js create mode 100644 pr/451/docs/search.index/79de9416e93c.js delete mode 100644 pr/451/docs/search.index/7a61dea0bfb9.js delete mode 100644 pr/451/docs/search.index/7b1673ee6360.js create mode 100644 pr/451/docs/search.index/7bb203f6c904.js delete mode 100644 pr/451/docs/search.index/7bd7bca0a11f.js create mode 100644 pr/451/docs/search.index/7be830c7f373.js create mode 100644 pr/451/docs/search.index/7cc498cd5f4e.js delete mode 100644 pr/451/docs/search.index/7ccfc824e9f7.js create mode 100644 pr/451/docs/search.index/7d0b131779fc.js create mode 100644 pr/451/docs/search.index/7da5ade5b83b.js delete mode 100644 pr/451/docs/search.index/7dc6410c642a.js create mode 100644 pr/451/docs/search.index/7dca73af94f0.js create mode 100644 pr/451/docs/search.index/7dfde7047aaf.js delete mode 100644 pr/451/docs/search.index/7e3b19027434.js create mode 100644 pr/451/docs/search.index/7e508d623850.js delete mode 100644 pr/451/docs/search.index/7e644c6ffb35.js create mode 100644 pr/451/docs/search.index/7e7ee16d46d1.js delete mode 100644 pr/451/docs/search.index/7ebdb25ba28a.js delete mode 100644 pr/451/docs/search.index/7f6612368049.js create mode 100644 pr/451/docs/search.index/7f9dc1f44330.js create mode 100644 pr/451/docs/search.index/7f9fb0e71342.js delete mode 100644 pr/451/docs/search.index/desc/59e6f7c5fb07.js create mode 100644 pr/451/docs/search.index/desc/791dbc1ca79a.js delete mode 100644 pr/451/docs/search.index/entry/7780e033398e.js create mode 100644 pr/451/docs/search.index/entry/8a78b0eadc5e.js delete mode 100644 pr/451/docs/search.index/function/378ad5ea86b4.js create mode 100644 pr/451/docs/search.index/function/bcf4aac8ded5.js delete mode 100644 pr/451/docs/search.index/generic_inverted_index/af48d3aa5b7a.js create mode 100644 pr/451/docs/search.index/generic_inverted_index/e9848908a123.js create mode 100644 pr/451/docs/search.index/name/14e0528539da.js delete mode 100644 pr/451/docs/search.index/name/edb16650d3a7.js delete mode 100644 pr/451/docs/search.index/normalizedName/de3a57d53d3b.js create mode 100644 pr/451/docs/search.index/normalizedName/deb8ebf00d7a.js create mode 100644 pr/451/docs/search.index/path/934c7b66edd3.js delete mode 100644 pr/451/docs/search.index/path/b5fe16f07d66.js delete mode 100644 pr/451/docs/search.index/type/42acd02dfb5a.js create mode 100644 pr/451/docs/search.index/type/5d148ce6032d.js delete mode 100644 pr/451/docs/src/noq_proto/congestion/bbr/bw_estimation.rs.html delete mode 100644 pr/451/docs/src/noq_proto/congestion/bbr/min_max.rs.html delete mode 100644 pr/451/docs/src/noq_proto/congestion/bbr/mod.rs.html create mode 100644 pr/451/docs/src/noq_proto/congestion/bbr3/max_filter.rs.html create mode 100644 pr/451/docs/src/noq_proto/congestion/bbr3/mod.rs.html diff --git a/pr/451/docs/noq/all.html b/pr/451/docs/noq/all.html index 17028e65b..a9ab61b80 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 bfd676f25..7b4789267 100644 --- a/pr/451/docs/noq/congestion/index.html +++ b/pr/451/docs/noq/congestion/index.html @@ -1,2 +1,4 @@ -noq::congestion - Rust

Module congestion

Module congestion 

Source
Expand description

Logic for controlling the rate at which data is sent

-

Structs§

Bbr
Experimental! Use at your own risk.
BbrConfig
Configuration for the Bbr congestion controller
ControllerMetrics
Common congestion controller metrics
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 +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/sidebar-items.js b/pr/451/docs/noq/congestion/sidebar-items.js index 888f550d4..07e9dc7b1 100644 --- a/pr/451/docs/noq/congestion/sidebar-items.js +++ b/pr/451/docs/noq/congestion/sidebar-items.js @@ -1 +1 @@ -window.SIDEBAR_ITEMS = {"struct":["Bbr","BbrConfig","ControllerMetrics","Cubic","CubicConfig","NewReno","NewRenoConfig"],"trait":["Controller","ControllerFactory"]}; \ No newline at end of file +window.SIDEBAR_ITEMS = {"struct":["Bbr3","Bbr3Config","ControllerMetrics","Cubic","CubicConfig","NewReno","NewRenoConfig"],"trait":["Controller","ControllerFactory"]}; \ No newline at end of file diff --git a/pr/451/docs/noq_proto/congestion/struct.Bbr.html b/pr/451/docs/noq/congestion/struct.Bbr3.html similarity index 53% rename from pr/451/docs/noq_proto/congestion/struct.Bbr.html rename to pr/451/docs/noq/congestion/struct.Bbr3.html index ae3b529a8..e20076bcb 100644 --- a/pr/451/docs/noq_proto/congestion/struct.Bbr.html +++ b/pr/451/docs/noq/congestion/struct.Bbr3.html @@ -1,30 +1,39 @@ -Bbr in noq_proto::congestion - Rust

Bbr

Struct Bbr 

Source
pub struct Bbr { /* 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 google’s quiche implementation https://source.chromium.org/chromium/chromium/src/+/master:net/third_party/quiche/src/quic/core/congestion_control/bbr_sender.cc -of BBR https://datatracker.ietf.org/doc/html/draft-cardwell-iccrg-bbr-congestion-control. -More discussion and links at https://groups.google.com/g/bbr-dev.

-

Implementations§

Source§

impl Bbr

Source

pub fn new(config: Arc<BbrConfig>, current_mtu: u16) -> Self

Construct a state using the given config and current time now

-

Trait Implementations§

Source§

impl Clone for Bbr

Source§

fn clone(&self) -> Bbr

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 Bbr

Trait Implementations§

Source§

impl Clone for Bbr3

Source§

fn clone(&self) -> Bbr3

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 Bbr3

Source§

fn on_spurious_congestion_event(&mut self)

Source§

fn on_packet_sent(&mut self, now: Instant, bytes: u16, pn: u64)

One packet was just sent
Source§

fn on_ack( &mut self, now: Instant, sent: Instant, bytes: u64, - app_limited: bool, - rtt: &RttEstimator, -)

Packet deliveries were confirmed Read more
Source§

fn on_end_acks( + pn: u64, + _app_limited: bool, + rtt: &RttEstimator, +)

Packet deliveries were confirmed Read more
Source§

fn on_end_acks( &mut self, - now: Instant, + _now: Instant, in_flight: u64, app_limited: bool, largest_packet_num_acked: Option<u64>, -)

Packets are acked in batches, all with the same now argument. This indicates one of those batches has completed.
Source§

fn on_congestion_event( +)

Packets are acked in batches, all with the same now argument. This indicates one of those batches has completed.
Source§

fn on_congestion_event( &mut self, - _now: Instant, + now: Instant, _sent: Instant, - _is_persistent_congestion: bool, - _is_ecn: bool, + is_persistent_congestion: bool, + is_ecn: bool, lost_bytes: u64, -)

Packets were deemed lost or marked congested Read more
Source§

fn on_mtu_update(&mut self, new_mtu: u16)

The known MTU for the current network path has been updated
Source§

fn window(&self) -> u64

Number of ack-eliciting bytes that may be in flight
Source§

fn metrics(&self) -> ControllerMetrics

Retrieve implementation-specific metrics used to populate qlog traces when they are enabled
Source§

fn clone_box(&self) -> Box<dyn Controller>

Duplicate the controller’s state
Source§

fn initial_window(&self) -> u64

Initial congestion window
Source§

fn into_any(self: Box<Self>) -> Box<dyn Any>

Returns Self for use in down-casting to extract implementation details
Source§

fn on_spurious_congestion_event(&mut self)

Packets were incorrectly deemed lost Read more
Source§

impl Debug for Bbr

Source§

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

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl Freeze for Bbr

§

impl RefUnwindSafe for Bbr

§

impl Send for Bbr

§

impl Sync for Bbr

§

impl Unpin for Bbr

§

impl UnwindSafe for Bbr

Blanket Implementations§

Source§

impl<T> Any for T
where + largest_lost_pn: u64, +)

Packets were deemed lost or marked congested Read more
Source§

fn on_packet_lost(&mut self, lost_bytes: u16, pn: u64, now: Instant)

One packet was just lost
Source§

fn on_mtu_update(&mut self, new_mtu: u16)

The known MTU for the current network path has been updated
Source§

fn on_ack_frequency_update( + &mut self, + ack_eliciting_threshold: u64, + requested_max_ack_delay: Duration, +)

The peer’s ACK-frequency parameters have changed Read more
Source§

fn window(&self) -> u64

Number of ack-eliciting bytes that may be in flight
Source§

fn metrics(&self) -> ControllerMetrics

Retrieve implementation-specific metrics used to populate qlog traces when they are enabled +This is also used to alter the pacing of the connection with +pacing_rate and send_quantum
Source§

fn clone_box(&self) -> Box<dyn Controller>

Duplicate the controller’s state
Source§

fn initial_window(&self) -> u64

Initial congestion window
Source§

fn into_any(self: Box<Bbr3>) -> Box<dyn Any>

Returns Self for use in down-casting to extract implementation details
Source§

fn on_sent(&mut self, now: Instant, bytes: u64, largest_pn: u64)

One or more packets were just sent
Source§

impl Debug for Bbr3

Source§

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

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl Freeze for Bbr3

§

impl RefUnwindSafe for Bbr3

§

impl Send for Bbr3

§

impl Sync for Bbr3

§

impl Unpin for Bbr3

§

impl UnwindSafe for Bbr3

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/congestion/struct.BbrConfig.html b/pr/451/docs/noq/congestion/struct.Bbr3Config.html similarity index 62% rename from pr/451/docs/noq_proto/congestion/struct.BbrConfig.html rename to pr/451/docs/noq/congestion/struct.Bbr3Config.html index 94eeecf13..70744366a 100644 --- a/pr/451/docs/noq_proto/congestion/struct.BbrConfig.html +++ b/pr/451/docs/noq/congestion/struct.Bbr3Config.html @@ -1,11 +1,18 @@ -BbrConfig in noq_proto::congestion - Rust

BbrConfig

Struct BbrConfig 

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

Configuration for the Bbr congestion controller

-

Implementations§

Source§

impl BbrConfig

Source

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

Default limit on the amount of outstanding data in bytes.

+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 +the congestion windows. +All of these parameters are specific to different states of the algorithm: see BbrState +pacing_margin_percent is used to set a margin when calculating the pacing_rate in order +to not send at 100% capacity when calculating pacing.

+

Implementations§

Source§

impl Bbr3Config

Source

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

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 BbrConfig

Source§

fn clone(&self) -> BbrConfig

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 BbrConfig

Source§

fn build( - self: Arc<Self>, +

Trait Implementations§

Source§

impl Clone for Bbr3Config

Source§

fn clone(&self) -> Bbr3Config

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 Bbr3Config

Source§

fn build( + self: Arc<Bbr3Config>, _now: Instant, current_mtu: u16, -) -> Box<dyn Controller>

Construct a fresh Controller
Source§

impl Debug for BbrConfig

Source§

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

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

impl Default for BbrConfig

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 +) -> Box<dyn Controller>

Construct a fresh Controller
Source§

impl Debug for Bbr3Config

Source§

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

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

impl Default for Bbr3Config

Source§

fn default() -> Bbr3Config

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/congestion/struct.ControllerMetrics.html b/pr/451/docs/noq/congestion/struct.ControllerMetrics.html index 83727dbb4..e5c0772bb 100644 --- a/pr/451/docs/noq/congestion/struct.ControllerMetrics.html +++ b/pr/451/docs/noq/congestion/struct.ControllerMetrics.html @@ -1,12 +1,16 @@ -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>, -}
Expand description

Common congestion controller metrics

+ pub send_quantum: Option<u64>, +}
Expand description

Common congestion controller metrics used both for logging purposes +but also to alter the pacing of the connection with +pacing_rate and send_quantum

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.
§congestion_window: u64

Congestion window (bytes)

§ssthresh: Option<u64>

Slow start threshold (bytes)

-
§pacing_rate: Option<u64>

Pacing rate (bits/s)

-

Trait Implementations§

Source§

impl Debug for ControllerMetrics

Source§

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

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

impl Default for ControllerMetrics

Source§

fn default() -> ControllerMetrics

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

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +
§pacing_rate: Option<u64>

Pacing rate (bytes/s)

+
§send_quantum: Option<u64>

Send Quantum (bytes) used to control the size of packet bursts

+

Trait Implementations§

Source§

impl Default for ControllerMetrics

Source§

fn default() -> ControllerMetrics

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> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

diff --git a/pr/451/docs/noq/congestion/struct.Cubic.html b/pr/451/docs/noq/congestion/struct.Cubic.html index cbf414f0d..8fc498e57 100644 --- a/pr/451/docs/noq/congestion/struct.Cubic.html +++ b/pr/451/docs/noq/congestion/struct.Cubic.html @@ -1,26 +1,34 @@ -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

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, now: Instant, sent: Instant, bytes: u64, + _pn: u64, app_limited: bool, rtt: &RttEstimator, -)

Packet deliveries were confirmed Read more
Source§

fn on_congestion_event( +)

Packet deliveries were confirmed Read more
Source§

fn on_congestion_event( &mut self, now: Instant, sent: Instant, is_persistent_congestion: bool, is_ecn: bool, _lost_bytes: u64, -)

Packets were deemed lost or marked congested Read more
Source§

fn on_spurious_congestion_event(&mut self)

Packets were incorrectly deemed lost Read more
Source§

fn on_mtu_update(&mut self, new_mtu: u16)

The known MTU for the current network path has been updated
Source§

fn window(&self) -> u64

Number of ack-eliciting bytes that may be in flight
Source§

fn metrics(&self) -> ControllerMetrics

Retrieve implementation-specific metrics used to populate qlog traces when they are enabled
Source§

fn clone_box(&self) -> Box<dyn Controller>

Duplicate the controller’s state
Source§

fn initial_window(&self) -> u64

Initial congestion window
Source§

fn into_any(self: Box<Cubic>) -> Box<dyn Any>

Returns Self for use in down-casting to extract implementation details
Source§

fn on_sent(&mut self, now: Instant, bytes: u64, last_packet_number: u64)

One or more packets were just sent
Source§

fn on_end_acks( + _largest_lost_pn: u64, +)

Packets were deemed lost or marked congested Read more
Source§

fn on_spurious_congestion_event(&mut self)

Packets were incorrectly deemed lost Read more
Source§

fn on_mtu_update(&mut self, new_mtu: u16)

The known MTU for the current network path has been updated
Source§

fn window(&self) -> u64

Number of ack-eliciting bytes that may be in flight
Source§

fn metrics(&self) -> ControllerMetrics

Retrieve implementation-specific metrics used to populate qlog traces when they are enabled +This is also used to alter the pacing of the connection with +pacing_rate and send_quantum
Source§

fn clone_box(&self) -> Box<dyn Controller>

Duplicate the controller’s state
Source§

fn initial_window(&self) -> u64

Initial congestion window
Source§

fn into_any(self: Box<Cubic>) -> Box<dyn Any>

Returns Self for use in down-casting to extract implementation details
Source§

fn on_sent(&mut self, now: Instant, bytes: u64, largest_pn: u64)

One or more packets were just sent
Source§

fn on_packet_sent(&mut self, now: Instant, bytes: u16, pn: u64)

One packet was just sent
Source§

fn on_end_acks( &mut self, now: Instant, in_flight: u64, app_limited: bool, largest_packet_num_acked: Option<u64>, -)

Packets are acked in batches, all with the same now argument. This indicates one of those batches has completed.
Source§

impl Debug for Cubic

Source§

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

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl Freeze for Cubic

§

impl RefUnwindSafe for Cubic

§

impl Send for Cubic

§

impl Sync for Cubic

§

impl Unpin for Cubic

§

impl UnwindSafe for Cubic

Blanket Implementations§

Source§

impl<T> Any for T
where +)

Packets are acked in batches, all with the same now argument. This indicates one of those batches has completed.
Source§

fn on_packet_lost(&mut self, lost_bytes: u16, pn: u64, now: Instant)

One packet was just lost
Source§

fn on_ack_frequency_update( + &mut self, + ack_eliciting_threshold: u64, + requested_max_ack_delay: Duration, +)

The peer’s ACK-frequency parameters have changed Read more
Source§

impl Debug for Cubic

Source§

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

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl Freeze for Cubic

§

impl RefUnwindSafe for Cubic

§

impl Send for Cubic

§

impl Sync for Cubic

§

impl Unpin for Cubic

§

impl UnwindSafe for Cubic

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/congestion/struct.CubicConfig.html b/pr/451/docs/noq/congestion/struct.CubicConfig.html index 30dd7d958..a41c39a3b 100644 --- a/pr/451/docs/noq/congestion/struct.CubicConfig.html +++ b/pr/451/docs/noq/congestion/struct.CubicConfig.html @@ -1,11 +1,11 @@ -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.

+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

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<CubicConfig>, 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<(), Error>

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

impl Default for CubicConfig

Source§

fn default() -> CubicConfig

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

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +) -> Box<dyn Controller>

Construct a fresh Controller
Source§

impl Debug for CubicConfig

Source§

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

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

impl Default for CubicConfig

Source§

fn default() -> CubicConfig

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/congestion/struct.NewReno.html b/pr/451/docs/noq/congestion/struct.NewReno.html index db55ce790..56b75d807 100644 --- a/pr/451/docs/noq/congestion/struct.NewReno.html +++ b/pr/451/docs/noq/congestion/struct.NewReno.html @@ -1,30 +1,38 @@ -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, current_mtu: u16, ) -> NewReno

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

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, _now: Instant, sent: Instant, bytes: u64, + _pn: u64, app_limited: bool, _rtt: &RttEstimator, -)

Packet deliveries were confirmed Read more
Source§

fn on_congestion_event( +)

Packet deliveries were confirmed Read more
Source§

fn on_congestion_event( &mut self, now: Instant, sent: Instant, is_persistent_congestion: bool, _is_ecn: bool, _lost_bytes: u64, -)

Packets were deemed lost or marked congested Read more
Source§

fn on_mtu_update(&mut self, new_mtu: u16)

The known MTU for the current network path has been updated
Source§

fn window(&self) -> u64

Number of ack-eliciting bytes that may be in flight
Source§

fn metrics(&self) -> ControllerMetrics

Retrieve implementation-specific metrics used to populate qlog traces when they are enabled
Source§

fn clone_box(&self) -> Box<dyn Controller>

Duplicate the controller’s state
Source§

fn initial_window(&self) -> u64

Initial congestion window
Source§

fn into_any(self: Box<NewReno>) -> Box<dyn Any>

Returns Self for use in down-casting to extract implementation details
Source§

fn on_sent(&mut self, now: Instant, bytes: u64, last_packet_number: u64)

One or more packets were just sent
Source§

fn on_end_acks( + _largest_lost_pn: u64, +)

Packets were deemed lost or marked congested Read more
Source§

fn on_mtu_update(&mut self, new_mtu: u16)

The known MTU for the current network path has been updated
Source§

fn window(&self) -> u64

Number of ack-eliciting bytes that may be in flight
Source§

fn metrics(&self) -> ControllerMetrics

Retrieve implementation-specific metrics used to populate qlog traces when they are enabled +This is also used to alter the pacing of the connection with +pacing_rate and send_quantum
Source§

fn clone_box(&self) -> Box<dyn Controller>

Duplicate the controller’s state
Source§

fn initial_window(&self) -> u64

Initial congestion window
Source§

fn into_any(self: Box<NewReno>) -> Box<dyn Any>

Returns Self for use in down-casting to extract implementation details
Source§

fn on_sent(&mut self, now: Instant, bytes: u64, largest_pn: u64)

One or more packets were just sent
Source§

fn on_packet_sent(&mut self, now: Instant, bytes: u16, pn: u64)

One packet was just sent
Source§

fn on_end_acks( &mut self, now: Instant, in_flight: u64, app_limited: bool, largest_packet_num_acked: Option<u64>, -)

Packets are acked in batches, all with the same now argument. This indicates one of those batches has completed.
Source§

fn on_spurious_congestion_event(&mut self)

Packets were incorrectly deemed lost Read more
Source§

impl Debug for NewReno

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 +)

Packets are acked in batches, all with the same now argument. This indicates one of those batches has completed.
Source§

fn on_packet_lost(&mut self, lost_bytes: u16, pn: u64, now: Instant)

One packet was just lost
Source§

fn on_spurious_congestion_event(&mut self)

Packets were incorrectly deemed lost Read more
Source§

fn on_ack_frequency_update( + &mut self, + ack_eliciting_threshold: u64, + requested_max_ack_delay: Duration, +)

The peer’s ACK-frequency parameters have changed Read more

Source§

impl Debug for NewReno

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 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/congestion/struct.NewRenoConfig.html b/pr/451/docs/noq/congestion/struct.NewRenoConfig.html index 45bf5627e..4b226f8c0 100644 --- a/pr/451/docs/noq/congestion/struct.NewRenoConfig.html +++ b/pr/451/docs/noq/congestion/struct.NewRenoConfig.html @@ -1,12 +1,12 @@ -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.

+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.

-

Trait Implementations§

Source§

impl Clone for NewRenoConfig

Source§

fn clone(&self) -> NewRenoConfig

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 NewRenoConfig

Source

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

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

+

Trait Implementations§

Source§

impl Clone for NewRenoConfig

Source§

fn clone(&self) -> NewRenoConfig

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 NewRenoConfig

Source§

fn build( self: Arc<NewRenoConfig>, now: Instant, current_mtu: u16, -) -> Box<dyn Controller>

Construct a fresh Controller
Source§

impl Debug for NewRenoConfig

Source§

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

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

impl Default for NewRenoConfig

Source§

fn default() -> NewRenoConfig

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

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +) -> Box<dyn Controller>

Construct a fresh Controller
Source§

impl Debug for NewRenoConfig

Source§

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

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

impl Default for NewRenoConfig

Source§

fn default() -> NewRenoConfig

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/congestion/trait.Controller.html b/pr/451/docs/noq/congestion/trait.Controller.html index e28ccb4d5..6c44b51fa 100644 --- a/pr/451/docs/noq/congestion/trait.Controller.html +++ b/pr/451/docs/noq/congestion/trait.Controller.html @@ -1,8 +1,8 @@ -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 {
-    // Required methods
+
Show 14 methods // Required methods fn on_congestion_event( &mut self, now: Instant, @@ -10,6 +10,7 @@ is_persistent_congestion: bool, is_ecn: bool, lost_bytes: u64, + largest_lost_pn: u64, ); fn on_mtu_update(&mut self, new_mtu: u16); fn window(&self) -> u64; @@ -18,12 +19,14 @@ fn into_any(self: Box<Self>) -> Box<dyn Any>; // Provided methods - fn on_sent(&mut self, now: Instant, bytes: u64, last_packet_number: u64) { ... } + fn on_sent(&mut self, now: Instant, bytes: u64, largest_pn: u64) { ... } + fn on_packet_sent(&mut self, now: Instant, bytes: u16, pn: u64) { ... } fn on_ack( &mut self, now: Instant, sent: Instant, bytes: u64, + pn: u64, app_limited: bool, rtt: &RttEstimator, ) { ... } @@ -34,46 +37,71 @@ app_limited: bool, largest_packet_num_acked: Option<u64>, ) { ... } + fn on_packet_lost(&mut self, lost_bytes: u16, pn: u64, now: Instant) { ... } fn on_spurious_congestion_event(&mut self) { ... } + fn on_ack_frequency_update( + &mut self, + ack_eliciting_threshold: u64, + requested_max_ack_delay: Duration, + ) { ... } fn metrics(&self) -> ControllerMetrics { ... } -}
Expand description

Common interface for different congestion controllers

-

Required Methods§

Expand description

Common interface for different congestion controllers

+

Required Methods§

Source

fn on_congestion_event( &mut self, now: Instant, sent: Instant, is_persistent_congestion: bool, is_ecn: bool, lost_bytes: u64, + largest_lost_pn: u64, )

Packets were deemed lost or marked congested

in_persistent_congestion indicates whether all packets sent within the persistent congestion threshold period ending when the most recent packet in this batch was sent were lost. -lost_bytes indicates how many bytes were lost. This value will be 0 for ECN triggers.

-
Source

fn on_mtu_update(&mut self, new_mtu: u16)

The known MTU for the current network path has been updated

-
Source

fn window(&self) -> u64

Number of ack-eliciting bytes that may be in flight

-
Source

fn clone_box(&self) -> Box<dyn Controller>

Duplicate the controller’s state

-
Source

fn initial_window(&self) -> u64

Initial congestion window

-
Source

fn into_any(self: Box<Self>) -> Box<dyn Any>

Returns Self for use in down-casting to extract implementation details

-

Provided Methods§

Source

fn on_sent(&mut self, now: Instant, bytes: u64, last_packet_number: u64)

One or more packets were just sent

-
Source

fn on_ack( +lost_bytes indicates how many bytes were lost. This value will be 0 for ECN triggers. +largest_lost_pn indicates the packet number of the packet with the highest packet number +in the congestion event.

+

Source

fn on_mtu_update(&mut self, new_mtu: u16)

The known MTU for the current network path has been updated

+
Source

fn window(&self) -> u64

Number of ack-eliciting bytes that may be in flight

+
Source

fn clone_box(&self) -> Box<dyn Controller>

Duplicate the controller’s state

+
Source

fn initial_window(&self) -> u64

Initial congestion window

+
Source

fn into_any(self: Box<Self>) -> Box<dyn Any>

Returns Self for use in down-casting to extract implementation details

+

Provided Methods§

Source

fn on_sent(&mut self, now: Instant, bytes: u64, largest_pn: u64)

One or more packets were just sent

+
Source

fn on_packet_sent(&mut self, now: Instant, bytes: u16, pn: u64)

One packet was just sent

+
Source

fn on_ack( &mut self, now: Instant, sent: Instant, bytes: u64, + pn: u64, app_limited: bool, rtt: &RttEstimator, )

Packet deliveries were confirmed

app_limited indicates whether the connection was blocked on outgoing application data prior to receiving these acknowledgements.

-
Source

fn on_end_acks( +

Source

fn on_end_acks( &mut self, now: Instant, in_flight: u64, app_limited: bool, largest_packet_num_acked: Option<u64>, )

Packets are acked in batches, all with the same now argument. This indicates one of those batches has completed.

-
Source

fn on_spurious_congestion_event(&mut self)

Packets were incorrectly deemed lost

+
Source

fn on_packet_lost(&mut self, lost_bytes: u16, pn: u64, now: Instant)

One packet was just lost

+
Source

fn on_spurious_congestion_event(&mut self)

Packets were incorrectly deemed lost

This function is called when all packets that were deemed lost (for instance because of packet reordering) are acknowledged after the congestion event was raised.

-
Source

fn metrics(&self) -> ControllerMetrics

Retrieve implementation-specific metrics used to populate qlog traces when they are enabled

-

Implementors§

\ No newline at end of file +

Source

fn on_ack_frequency_update( + &mut self, + ack_eliciting_threshold: u64, + requested_max_ack_delay: Duration, +)

The peer’s ACK-frequency parameters have changed

+

ack_eliciting_threshold is the number of ack-eliciting packets the peer may receive +before being required to send an immediate ACK (per the QUIC ACK frequency extension). +requested_max_ack_delay is the maximum delay we asked the peer to wait before sending +an ACK when the threshold hasn’t been reached.

+

Controllers can use this to refine estimates that depend on peer ACK behavior (e.g. +BBR’s offload budget).

+
Source

fn metrics(&self) -> ControllerMetrics

Retrieve implementation-specific metrics used to populate qlog traces when they are enabled +This is also used to alter the pacing of the connection with +pacing_rate and send_quantum

+

Implementors§

\ No newline at end of file diff --git a/pr/451/docs/noq/congestion/trait.ControllerFactory.html b/pr/451/docs/noq/congestion/trait.ControllerFactory.html index f6dd7b6ee..4c9343f49 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>,
@@ -6,5 +6,5 @@
         current_mtu: u16,
     ) -> Box<dyn Controller>;
 }
Expand description

Constructs controllers on demand

-

Required Methods§

Source

fn build(self: Arc<Self>, now: Instant, current_mtu: u16) -> Box<dyn Controller>

Construct a fresh Controller

-

Implementors§

\ No newline at end of file +

Required Methods§

Source

fn build(self: Arc<Self>, now: Instant, current_mtu: u16) -> Box<dyn Controller>

Construct a fresh Controller

+

Implementors§

\ No newline at end of file diff --git a/pr/451/docs/noq/connection/struct.Closed.html b/pr/451/docs/noq/connection/struct.Closed.html new file mode 100644 index 000000000..cf268f536 --- /dev/null +++ b/pr/451/docs/noq/connection/struct.Closed.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../noq/struct.Closed.html...

+ + + \ No newline at end of file diff --git a/pr/451/docs/noq/crypto/index.html b/pr/451/docs/noq/crypto/index.html index 3266d8e96..0d19d1acc 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 ed0c09a3a..836ac7220 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 ca1fc066c..90d332226 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 7c8e71954..616b610ef 100644 --- a/pr/451/docs/noq/crypto/rustls/struct.HandshakeData.html +++ b/pr/451/docs/noq/crypto/rustls/struct.HandshakeData.html @@ -1,13 +1,14 @@ -HandshakeData in noq::crypto::rustls - Rust

HandshakeData

Struct HandshakeData 

Source
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: NamedGroup, + pub negotiated_key_exchange_group: Option<NamedGroup>, }
Expand description

Authentication data for (rustls) TLS session

-

Fields§

§protocol: Option<Vec<u8>>

The negotiated application protocol, if ALPN is in use

+

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.
§protocol: Option<Vec<u8>>

The negotiated application protocol, if ALPN is in use

Guaranteed to be set if a nonempty list of protocols was specified for this connection.

§server_name: Option<String>

The server name specified by the client, if any

Always None for outgoing connections

-
§negotiated_key_exchange_group: NamedGroup

The key exchange group negotiated with the peer

+
§negotiated_key_exchange_group: Option<NamedGroup>

The key exchange group negotiated with the peer. Useful for logging and metrics.

+

None until the handshake has completed.

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/rustls/struct.NoInitialCipherSuite.html b/pr/451/docs/noq/crypto/rustls/struct.NoInitialCipherSuite.html index 4b1d48e9a..e019c918e 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 284c48f71..6dbcb6b30 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 66348a7f1..c2a99906d 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 29977f219..9bd57d417 100644 --- a/pr/451/docs/noq/crypto/rustls/struct.TlsSession.html +++ b/pr/451/docs/noq/crypto/rustls/struct.TlsSession.html @@ -1,11 +1,11 @@ -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( +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, orig_dst_cid: ConnectionId, header: &[u8], payload: &[u8], -) -> bool

Verify the integrity of a retry packet Read more
Source§

fn export_keying_material( +) -> bool

Verify the integrity of a retry packet Read more
Source§

fn export_keying_material( &self, output: &mut [u8], label: &[u8], diff --git a/pr/451/docs/noq/crypto/struct.CryptoError.html b/pr/451/docs/noq/crypto/struct.CryptoError.html index 9972e9f65..569eb4367 100644 --- a/pr/451/docs/noq/crypto/struct.CryptoError.html +++ b/pr/451/docs/noq/crypto/struct.CryptoError.html @@ -1,8 +1,8 @@ -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
Source§

impl From<Unspecified> for CryptoError

Source§

fn from(_: Unspecified) -> CryptoError

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +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 - 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.

+ 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.

§

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/crypto/struct.ExportKeyingMaterialError.html b/pr/451/docs/noq/crypto/struct.ExportKeyingMaterialError.html index 0c542ab5e..ba9c3d145 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 b04e27a1d..0a703377b 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 9a2f6ee75..620d9df84 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 f77ec3a68..52ba3fac8 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 613ecf973..2df73cbd7 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 d1afef56b..cf51ee2ce 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 bd43de09f..0270f5240 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]);
@@ -7,4 +7,4 @@
 

Required Methods§

Source

fn decrypt(&self, pn_offset: usize, packet: &mut [u8])

Decrypt the given packet’s header

Source

fn encrypt(&self, pn_offset: usize, packet: &mut [u8])

Encrypt the given packet’s header

Source

fn sample_size(&self) -> usize

The sample size used for this key’s algorithm

-

Implementations on Foreign Types§

Source§

impl HeaderKey for Box<dyn HeaderProtectionKey>

Source§

fn decrypt(&self, pn_offset: usize, packet: &mut [u8])

Source§

fn encrypt(&self, pn_offset: usize, packet: &mut [u8])

Source§

fn sample_size(&self) -> usize

Implementors§

\ No newline at end of file +

Implementations on Foreign Types§

Source§

impl HeaderKey for Box<dyn HeaderProtectionKey>

Source§

fn decrypt(&self, pn_offset: usize, packet: &mut [u8])

Source§

fn encrypt(&self, pn_offset: usize, packet: &mut [u8])

Source§

fn sample_size(&self) -> usize

Implementors§

\ No newline at end of file diff --git a/pr/451/docs/noq/crypto/trait.HmacKey.html b/pr/451/docs/noq/crypto/trait.HmacKey.html index 9b9937114..f57593ce2 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 c670d4f6b..a3baef92e 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 1bab34898..b535a589c 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 b85c826d9..813e78b20 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
new file mode 100644
index 000000000..d584d3078
--- /dev/null
+++ b/pr/451/docs/noq/enum.ClosePathError.html
@@ -0,0 +1,40 @@
+ClosePathError in noq - Rust

ClosePathError

Enum ClosePathError 

Source
pub enum ClosePathError {
+    MultipathNotNegotiated,
+    ClosedPath,
+    LastOpenPath,
+}
Expand description

Errors triggered when abandoning a path

+

Variants§

§

MultipathNotNegotiated

Multipath is not negotiated

+
§

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 + 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/enum.ConfigError.html b/pr/451/docs/noq/enum.ConfigError.html index d9577900f..6813b219d 100644 --- a/pr/451/docs/noq/enum.ConfigError.html +++ b/pr/451/docs/noq/enum.ConfigError.html @@ -1,9 +1,9 @@ -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

-

Trait Implementations§

Source§

impl Clone for ConfigError

Source§

fn clone(&self) -> ConfigError

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 ConfigError

Source§

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

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

impl Display for ConfigError

Source§

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

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

impl Error for ConfigError

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<TryFromIntError> for ConfigError

Source§

fn from(_: TryFromIntError) -> ConfigError

Converts to this type from the input type.
Source§

impl From<VarIntBoundsExceeded> for ConfigError

Source§

fn from(_: VarIntBoundsExceeded) -> ConfigError

Converts to this type from the input type.
Source§

impl PartialEq for ConfigError

Source§

fn eq(&self, other: &ConfigError) -> 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 ConfigError

Source§

impl StructuralPartialEq for ConfigError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for ConfigError

Source§

fn clone(&self) -> ConfigError

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 ConfigError

Source§

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

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

impl Display for ConfigError

Source§

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

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

impl Error for ConfigError

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<TryFromIntError> for ConfigError

Source§

fn from(_: TryFromIntError) -> ConfigError

Converts to this type from the input type.
Source§

impl From<VarIntBoundsExceeded> for ConfigError

Source§

fn from(_: VarIntBoundsExceeded) -> ConfigError

Converts to this type from the input type.
Source§

impl PartialEq for ConfigError

Source§

fn eq(&self, other: &ConfigError) -> 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 ConfigError

Source§

impl StructuralPartialEq for ConfigError

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.ConnectError.html b/pr/451/docs/noq/enum.ConnectError.html index 1bef64fbb..07520f04b 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 a59fddc42..60b0997ae 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 d7de578ea..183c2bdbf 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 78ed5f186..98a256056 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, diff --git a/pr/451/docs/noq/enum.FrameType.html b/pr/451/docs/noq/enum.FrameType.html index 5fbd94817..ded15b19f 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 new file mode 100644 index 000000000..1d570cbef --- /dev/null +++ b/pr/451/docs/noq/enum.PathError.html @@ -0,0 +1,45 @@ +PathError in noq - Rust

PathError

Enum PathError 

Source
pub enum PathError {
+    MultipathNotNegotiated,
+    ServerSideNotAllowed,
+    MaxPathIdReached,
+    RemoteCidsExhausted,
+    ValidationFailed,
+    InvalidRemoteAddress(SocketAddr),
+}
Expand description

Errors that might trigger a path being closed

+

Variants§

§

MultipathNotNegotiated

The extension was not negotiated with the peer

+
§

ServerSideNotAllowed

Paths can only be opened client-side

+
§

MaxPathIdReached

Current limits do not allow us to open more paths

+
§

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 + 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_proto/struct.Written.html b/pr/451/docs/noq/enum.PathEvent.html similarity index 53% rename from pr/451/docs/noq_proto/struct.Written.html rename to pr/451/docs/noq/enum.PathEvent.html index 37fa8be43..b4d26b655 100644 --- a/pr/451/docs/noq_proto/struct.Written.html +++ b/pr/451/docs/noq/enum.PathEvent.html @@ -1,12 +1,46 @@ -Written in noq_proto - Rust

Written

Struct Written 

Source
pub struct Written {
-    pub bytes: usize,
-    pub chunks: usize,
-}
Expand description

Indicates how many bytes and chunks had been transferred in a write operation

-

Fields§

§bytes: usize

The amount of bytes which had been written

-
§chunks: usize

The amount of full chunks which had been written

-

If a chunk was only partially written, it will not be counted by this field.

-

Trait Implementations§

Source§

impl Clone for Written

Source§

fn clone(&self) -> Written

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 Written

Source§

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

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

impl Default for Written

Source§

fn default() -> Written

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

impl PartialEq for Written

Source§

fn eq(&self, other: &Written) -> 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 Written

Source§

impl Eq for Written

Source§

impl StructuralPartialEq for Written

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +PathEvent in noq - Rust

PathEvent

Enum PathEvent 

Source
pub enum PathEvent {
+    Established {
+        id: PathId,
+    },
+    Abandoned {
+        id: PathId,
+        reason: PathAbandonReason,
+    },
+    Discarded {
+        id: PathId,
+        path_stats: Box<PathStats>,
+    },
+    RemoteStatus {
+        id: PathId,
+        status: PathStatus,
+    },
+    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.

+

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

+

Fields

§id: PathId

With path was abandoned.

+
§reason: PathAbandonReason

Reason why this path was abandoned.

+
§

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

+
§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

+

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

+
§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 +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 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.Written.html b/pr/451/docs/noq/enum.PathStatus.html similarity index 60% rename from pr/451/docs/noq/struct.Written.html rename to pr/451/docs/noq/enum.PathStatus.html index 7109bfe1f..8e85a1c2f 100644 --- a/pr/451/docs/noq/struct.Written.html +++ b/pr/451/docs/noq/enum.PathStatus.html @@ -1,12 +1,17 @@ -Written in noq - Rust

Written

Struct Written 

Source
pub struct Written {
-    pub bytes: usize,
-    pub chunks: usize,
-}
Expand description

Indicates how many bytes and chunks had been transferred in a write operation

-

Fields§

§bytes: usize

The amount of bytes which had been written

-
§chunks: usize

The amount of full chunks which had been written

-

If a chunk was only partially written, it will not be counted by this field.

-

Trait Implementations§

Source§

impl Clone for Written

Source§

fn clone(&self) -> Written

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 Written

Source§

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

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

impl Default for Written

Source§

fn default() -> Written

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

impl PartialEq for Written

Source§

fn eq(&self, other: &Written) -> 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 Written

Source§

impl Eq for Written

Source§

impl StructuralPartialEq for Written

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +PathStatus in noq - Rust

PathStatus

Enum PathStatus 

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

The QUIC-MULTIPATH path status

+

See section “3.3 Path Status Management”: +https://quicwg.org/multipath/draft-ietf-quic-multipath.html#name-path-status-management

+

Variants§

§

Available

Paths marked with as available will be used when scheduling packets

+

If multiple paths are available, packets will be scheduled on whichever has +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 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 525ae0910..ee88e31be 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,
@@ -11,8 +11,8 @@
 
§

ZeroRttRejected

This was a 0-RTT stream and the server rejected it

Can only occur on clients for 0-RTT streams, which can be opened using Connecting::into_0rtt().

-

Trait Implementations§

Source§

impl Clone for ReadError

Source§

fn clone(&self) -> ReadError

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 ReadError

Source§

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

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

impl Display for ReadError

Source§

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

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

impl Error for ReadError

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 ReadError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<ReadError> for Error

Source§

fn from(x: ReadError) -> Self

Converts to this type from the input type.
Source§

impl From<ReadError> for ReadExactError

Source§

fn from(source: ReadError) -> Self

Converts to this type from the input type.
Source§

impl From<ReadError> for ReadToEndError

Source§

fn from(source: ReadError) -> Self

Converts to this type from the input type.
Source§

impl From<ResetError> for ReadError

Source§

fn from(e: ResetError) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for ReadError

Source§

fn eq(&self, other: &ReadError) -> 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 ReadError

Source§

impl StructuralPartialEq for ReadError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for ReadError

Source§

fn clone(&self) -> ReadError

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 ReadError

Source§

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

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

impl Display for ReadError

Source§

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

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

impl Error for ReadError

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 ReadError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<ReadError> for Error

Source§

fn from(x: ReadError) -> Self

Converts to this type from the input type.
Source§

impl From<ReadError> for ReadExactError

Source§

fn from(source: ReadError) -> Self

Converts to this type from the input type.
Source§

impl From<ReadError> for ReadToEndError

Source§

fn from(source: ReadError) -> Self

Converts to this type from the input type.
Source§

impl From<ResetError> for ReadError

Source§

fn from(e: ResetError) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for ReadError

Source§

fn eq(&self, other: &ReadError) -> 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 ReadError

Source§

impl StructuralPartialEq for ReadError

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.ReadExactError.html b/pr/451/docs/noq/enum.ReadExactError.html index 10945e3ad..0b972c610 100644 --- a/pr/451/docs/noq/enum.ReadExactError.html +++ b/pr/451/docs/noq/enum.ReadExactError.html @@ -1,11 +1,11 @@ -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.

Variants§

§

FinishedEarly(usize)

The stream finished before all bytes were read

§

ReadError(ReadError)

A read error occurred

-

Trait Implementations§

Source§

impl Clone for ReadExactError

Source§

fn clone(&self) -> ReadExactError

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 ReadExactError

Source§

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

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

impl Display for ReadExactError

Source§

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

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

impl Error for ReadExactError

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<ReadError> for ReadExactError

Source§

fn from(source: ReadError) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for ReadExactError

Source§

fn eq(&self, other: &ReadExactError) -> 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 ReadExactError

Source§

impl StructuralPartialEq for ReadExactError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for ReadExactError

Source§

fn clone(&self) -> ReadExactError

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 ReadExactError

Source§

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

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

impl Display for ReadExactError

Source§

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

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

impl Error for ReadExactError

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<ReadError> for ReadExactError

Source§

fn from(source: ReadError) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for ReadExactError

Source§

fn eq(&self, other: &ReadExactError) -> 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 ReadExactError

Source§

impl StructuralPartialEq for ReadExactError

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.ReadToEndError.html b/pr/451/docs/noq/enum.ReadToEndError.html index dd9bd3050..7c91e932c 100644 --- a/pr/451/docs/noq/enum.ReadToEndError.html +++ b/pr/451/docs/noq/enum.ReadToEndError.html @@ -1,11 +1,11 @@ -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

Variants§

§

Read(ReadError)

An error occurred during reading

§

TooLong

The stream is larger than the user-supplied limit

-

Trait Implementations§

Source§

impl Clone for ReadToEndError

Source§

fn clone(&self) -> ReadToEndError

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 ReadToEndError

Source§

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

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

impl Display for ReadToEndError

Source§

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

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

impl Error for ReadToEndError

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<ReadError> for ReadToEndError

Source§

fn from(source: ReadError) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for ReadToEndError

Source§

fn eq(&self, other: &ReadToEndError) -> 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 ReadToEndError

Source§

impl StructuralPartialEq for ReadToEndError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for ReadToEndError

Source§

fn clone(&self) -> ReadToEndError

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 ReadToEndError

Source§

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

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

impl Display for ReadToEndError

Source§

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

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

impl Error for ReadToEndError

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<ReadError> for ReadToEndError

Source§

fn from(source: ReadError) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for ReadToEndError

Source§

fn eq(&self, other: &ReadToEndError) -> 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 ReadToEndError

Source§

impl StructuralPartialEq for ReadToEndError

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.ResetError.html b/pr/451/docs/noq/enum.ResetError.html index ffe8f37e1..bc3349d44 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

@@ -6,8 +6,8 @@
§

ZeroRttRejected

This was a 0-RTT stream and the server rejected it

Can only occur on clients for 0-RTT streams, which can be opened using Connecting::into_0rtt().

-

Trait Implementations§

Source§

impl Clone for ResetError

Source§

fn clone(&self) -> ResetError

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 ResetError

Source§

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

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

impl Display for ResetError

Source§

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

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

impl Error for ResetError

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 ResetError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<ResetError> for Error

Source§

fn from(x: ResetError) -> Self

Converts to this type from the input type.
Source§

impl From<ResetError> for ReadError

Source§

fn from(e: ResetError) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for ResetError

Source§

fn eq(&self, other: &ResetError) -> 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 ResetError

Source§

impl StructuralPartialEq for ResetError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for ResetError

Source§

fn clone(&self) -> ResetError

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 ResetError

Source§

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

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

impl Display for ResetError

Source§

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

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

impl Error for ResetError

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 ResetError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<ResetError> for Error

Source§

fn from(x: ResetError) -> Self

Converts to this type from the input type.
Source§

impl From<ResetError> for ReadError

Source§

fn from(e: ResetError) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for ResetError

Source§

fn eq(&self, other: &ResetError) -> 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 ResetError

Source§

impl StructuralPartialEq for ResetError

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.SendDatagramError.html b/pr/451/docs/noq/enum.SendDatagramError.html index 4b8a4e090..7b7c4bac5 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 new file mode 100644 index 000000000..d03b36116 --- /dev/null +++ b/pr/451/docs/noq/enum.SetPathStatusError.html @@ -0,0 +1,37 @@ +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 + 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/enum.Side.html b/pr/451/docs/noq/enum.Side.html index b8e235db9..cd20a1d92 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 db56529df..2401e7bfe 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

@@ -6,8 +6,8 @@
§

ZeroRttRejected

This was a 0-RTT stream and the server rejected it

Can only occur on clients for 0-RTT streams, which can be opened using Connecting::into_0rtt().

-

Trait Implementations§

Source§

impl Clone for StoppedError

Source§

fn clone(&self) -> StoppedError

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 StoppedError

Source§

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

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

impl Display for StoppedError

Source§

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

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

impl Error for StoppedError

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 StoppedError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<StoppedError> for Error

Source§

fn from(x: StoppedError) -> Self

Converts to this type from the input type.
Source§

impl From<StoppedError> for WriteError

Source§

fn from(x: StoppedError) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for StoppedError

Source§

fn eq(&self, other: &StoppedError) -> 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 StoppedError

Source§

impl StructuralPartialEq for StoppedError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for StoppedError

Source§

fn clone(&self) -> StoppedError

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 StoppedError

Source§

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

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

impl Display for StoppedError

Source§

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

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

impl Error for StoppedError

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 StoppedError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<StoppedError> for Error

Source§

fn from(x: StoppedError) -> Self

Converts to this type from the input type.
Source§

impl From<StoppedError> for WriteError

Source§

fn from(x: StoppedError) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for StoppedError

Source§

fn eq(&self, other: &StoppedError) -> 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 StoppedError

Source§

impl StructuralPartialEq for StoppedError

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.WriteError.html b/pr/451/docs/noq/enum.WriteError.html index 606752321..81128fb54 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,
@@ -11,8 +11,8 @@
 
§

ZeroRttRejected

This was a 0-RTT stream and the server rejected it

Can only occur on clients for 0-RTT streams, which can be opened using Connecting::into_0rtt().

-

Trait Implementations§

Source§

impl Clone for WriteError

Source§

fn clone(&self) -> WriteError

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 WriteError

Source§

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

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

impl Display for WriteError

Source§

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

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

impl Error for WriteError

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 From<ConnectionError> for WriteError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<StoppedError> for WriteError

Source§

fn from(x: StoppedError) -> Self

Converts to this type from the input type.
Source§

impl From<WriteError> for Error

Source§

fn from(x: WriteError) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for WriteError

Source§

fn eq(&self, other: &WriteError) -> 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 WriteError

Source§

impl StructuralPartialEq for WriteError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for WriteError

Source§

fn clone(&self) -> WriteError

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 WriteError

Source§

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

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

impl Display for WriteError

Source§

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

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

impl Error for WriteError

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 From<ConnectionError> for WriteError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<StoppedError> for WriteError

Source§

fn from(x: StoppedError) -> Self

Converts to this type from the input type.
Source§

impl From<WriteError> for Error

Source§

fn from(x: WriteError) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for WriteError

Source§

fn eq(&self, other: &WriteError) -> 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 WriteError

Source§

impl StructuralPartialEq for WriteError

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/fn.default_runtime.html b/pr/451/docs/noq/fn.default_runtime.html index 849241019..b7d92b411 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 5554daa1f..4c80a5d26 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
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.
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 -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.
Written
Indicates how many bytes and chunks had been transferred in a write operation
ZeroRttAccepted
Future that completes when a connection is fully established

Enums§

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
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
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 +

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 +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 831f580ce..8e14b6da5 100644 --- a/pr/451/docs/noq/sidebar-items.js +++ b/pr/451/docs/noq/sidebar-items.js @@ -1 +1 @@ -window.SIDEBAR_ITEMS = {"enum":["ConfigError","ConnectError","ConnectionError","Dir","EcnCodepoint","FrameType","ReadError","ReadExactError","ReadToEndError","ResetError","SendDatagramError","Side","StoppedError","WriteError"],"fn":["default_runtime"],"mod":["congestion","crypto"],"struct":["Accept","AcceptBi","AcceptUni","AckFrequencyConfig","AddressDiscovery","ApplicationClose","BloomTokenLog","Chunk","ClientConfig","ClosedStream","Connecting","Connection","ConnectionClose","ConnectionId","ConnectionStats","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","Written","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","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 595f406ee..b9e3a15b2 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 180bbe0f8..3e461acfa 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 0e6b55eaa..37d0e3897 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 846cbe3af..24267c9b1 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 f4b48f499..32e720a5e 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 70412de6a..e13b4bff3 100644 --- a/pr/451/docs/noq/struct.ApplicationClose.html +++ b/pr/451/docs/noq/struct.ApplicationClose.html @@ -1,11 +1,11 @@ -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

Fields§

§error_code: VarInt

Application-specific reason code

§reason: Bytes

Human-readable reason for the close

-

Trait Implementations§

Source§

impl Clone for ApplicationClose

Source§

fn clone(&self) -> ApplicationClose

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 ApplicationClose

Source§

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

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

impl Display for ApplicationClose

Source§

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

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

impl PartialEq for ApplicationClose

Source§

fn eq(&self, other: &ApplicationClose) -> 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 ApplicationClose

Source§

impl StructuralPartialEq for ApplicationClose

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for ApplicationClose

Source§

fn clone(&self) -> ApplicationClose

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 ApplicationClose

Source§

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

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

impl Display for ApplicationClose

Source§

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

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

impl PartialEq for ApplicationClose

Source§

fn eq(&self, other: &ApplicationClose) -> 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 ApplicationClose

Source§

impl StructuralPartialEq for ApplicationClose

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.BloomTokenLog.html b/pr/451/docs/noq/struct.BloomTokenLog.html index b71a39ab7..c7937e4fa 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 84aae8417..036e1b9f0 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 7692a2bb1..21299ee51 100644 --- a/pr/451/docs/noq/struct.ClientConfig.html +++ b/pr/451/docs/noq/struct.ClientConfig.html @@ -1,7 +1,7 @@ -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( +

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( &mut self, initial_dst_cid_provider: Arc<dyn Fn() -> ConnectionId + Send + Sync>, ) -> &mut ClientConfig

Configure how to populate the destination CID of the initial packet when attempting to @@ -10,18 +10,18 @@ establish a new connection

a good reason, you do not need to change it.

When prefer to override the default, please note that the generated connection ID MUST be at least 8 bytes long and unpredictable, as per section 7.2 of RFC 9000.

-
Source

pub fn transport_config( +

Source

pub fn transport_config( &mut self, transport: Arc<TransportConfig>, ) -> &mut ClientConfig

Set a custom TransportConfig

-
Source

pub fn token_store(&mut self, store: Arc<dyn TokenStore>) -> &mut ClientConfig

Set a custom TokenStore

+
Source

pub fn token_store(&mut self, store: Arc<dyn TokenStore>) -> &mut ClientConfig

Set a custom TokenStore

Defaults to TokenMemoryCache, which is suitable for most internet applications.

-
Source

pub fn version(&mut self, version: u32) -> &mut ClientConfig

Set the QUIC version to use

-
Source§

impl ClientConfig

Source

pub fn try_with_platform_verifier() -> Result<ClientConfig, Error>

Create a client configuration that trusts the platform’s native roots

-
Source

pub fn with_root_certificates( +

Source

pub fn version(&mut self, version: u32) -> &mut ClientConfig

Set the QUIC version to use

+
Source§

impl ClientConfig

Source

pub fn try_with_platform_verifier() -> Result<ClientConfig, Error>

Create a client configuration that trusts the platform’s native roots

+
Source

pub fn with_root_certificates( roots: Arc<RootCertStore>, ) -> Result<ClientConfig, VerifierBuilderError>

Create a client configuration that trusts specified trust anchors

-

Trait Implementations§

Source§

impl Clone for ClientConfig

Source§

fn clone(&self) -> ClientConfig

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 ClientConfig

Source§

fn fmt(&self, fmt: &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 +

Trait Implementations§

Source§

impl Clone for ClientConfig

Source§

fn clone(&self) -> ClientConfig

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 ClientConfig

Source§

fn fmt(&self, fmt: &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 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.Closed.html b/pr/451/docs/noq/struct.Closed.html new file mode 100644 index 000000000..d7ec51bac --- /dev/null +++ b/pr/451/docs/noq/struct.Closed.html @@ -0,0 +1,37 @@ +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)>, +}
Expand description

State of a Connection at the moment it was closed.

+

Returned by the OnClosed future from Connection::on_closed.

+

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.
§reason: ConnectionError

The reason the connection was closed.

+
§stats: ConnectionStats

Aggregate connection statistics at the moment of close.

+
§path_stats: Vec<(PathId, PathStats)>

Per-path statistics for every path the connection knew about at close time.

+

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 + 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
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, 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.ClosedPath.html b/pr/451/docs/noq/struct.ClosedPath.html new file mode 100644 index 000000000..75f10da12 --- /dev/null +++ b/pr/451/docs/noq/struct.ClosedPath.html @@ -0,0 +1,32 @@ +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 + 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.ClosedStream.html b/pr/451/docs/noq/struct.ClosedStream.html index 0453153c1..23ec58b77 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 347289200..db197a0ae 100644 --- a/pr/451/docs/noq/struct.Connecting.html +++ b/pr/451/docs/noq/struct.Connecting.html @@ -1,5 +1,5 @@ -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

+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 the rest of the handshake to complete, at the cost of weakened cryptographic security @@ -32,12 +32,12 @@ replay attacks, and should therefore never invoke non-idempotent operations.

On incoming connections, this enables transmission of 0.5-RTT data, which may be sent before TLS client authentication has occurred, and should therefore not be used to send data for which client authentication is being used.

-
Source

pub async fn handshake_data(&mut self) -> Result<Box<dyn Any>, ConnectionError>

Parameters negotiated during the handshake

+
Source

pub async fn handshake_data(&mut self) -> Result<Box<dyn Any>, ConnectionError>

Parameters negotiated during the handshake

The dynamic type returned is determined by the configured Session. For the default rustls session, the return value can be downcast to a crypto::rustls::HandshakeData.

-
Source

pub fn local_ip(&self) -> Option<IpAddr>

The local IP address which was used when the peer established +

Source

pub fn local_ip(&self) -> Option<IpAddr>

The local IP address which was used when the peer established the connection

This can be different from the address the endpoint is bound to, in case the endpoint is bound to a wildcard address like 0.0.0.0 or ::.

@@ -45,9 +45,9 @@ the endpoint is bound to a wildcard address like 0.0.0.0 or : information. See noq_udp::RecvMeta::dst_ip for a list of supported platforms when using noq_udp for I/O, which is the default.

Will panic if called after poll has returned Ready.

-
Source

pub fn remote_address(&self) -> SocketAddr

The peer’s UDP addresses

+
Source

pub fn remote_address(&self) -> SocketAddr

The peer’s UDP addresses

Will panic if called after poll has returned Ready.

-

Trait Implementations§

Source§

impl Debug for Connecting

Source§

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

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

impl Future for Connecting

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 +

Trait Implementations§

Source§

impl Debug for Connecting

Source§

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

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

impl Future for Connecting

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 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.Connection.html b/pr/451/docs/noq/struct.Connection.html index 7a59936d7..a5137126e 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,67 +8,71 @@ 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.

-
Source

pub fn open_uni(&self) -> OpenUni<'_>

Initiate a new outgoing unidirectional stream.

+

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 actually used.

-
Source

pub fn open_bi(&self) -> OpenBi<'_>

Initiate a new outgoing bidirectional stream.

+
Source

pub fn open_bi(&self) -> OpenBi<'_>

Initiate a new outgoing bidirectional 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 actually used. Calling open_bi() then waiting on the RecvStream without writing anything to SendStream will never succeed.

-
Source

pub fn accept_uni(&self) -> AcceptUni<'_>

Accept the next incoming uni-directional stream

-
Source

pub fn accept_bi(&self) -> AcceptBi<'_>

Accept the next incoming bidirectional stream

+
Source

pub fn accept_uni(&self) -> AcceptUni<'_>

Accept the next incoming uni-directional stream

+
Source

pub fn accept_bi(&self) -> AcceptBi<'_>

Accept the next incoming bidirectional stream

Important Note: The Connection that calls open_bi() must write to its SendStream before the other 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 read_datagram(&self) -> ReadDatagram<'_>

Receive an application datagram

+
Source

pub fn open_path_ensure( &self, addr: SocketAddr, - initial_status: PathStatus, + initial_status: PathStatus, ) -> OpenPath

Opens a new path if no path exists yet for the remote address.

Otherwise behaves exactly as open_path.

-
Source

pub fn open_path( +

Source

pub fn open_path( &self, addr: SocketAddr, - initial_status: PathStatus, + initial_status: PathStatus, ) -> OpenPath

Opens an additional path if the multipath extension is negotiated.

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 -PathEvent::Opened event will still be emitted from Self::path_events if the -path opens. The PathId for the events can be extracted from +PathEvent::Established event will still be emitted from Self::path_events if +the path opens. The PathId for the events can be extracted from OpenPath::path_id.

Failure to open a path can either occur immediately, before polling the returned future, or at a later time. If the failure is immediate 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.

-

The stream will yield a PathEvent whenever there is a change in the state of any path in this connection. +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.

+

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

-

Returns a future that resolves, once the connection is closed, to a tuple of -(ConnectionError, ConnectionStats).

+
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>

If the connection is closed, the reason why.

-

Returns None if the connection is still open.

-
Source

pub fn close(&self, error_code: VarInt, reason: &[u8])

Close the connection immediately.

+
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.

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.

@@ -89,7 +93,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. @@ -97,56 +101,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 remote_address(&self) -> SocketAddr

The peer’s UDP address

-

If ServerConfig::migration is true, clients may change addresses at will, -e.g. when switching to a cellular internet connection.

-

If multipath is enabled this will return the address of any -path, and may not be consistent. Prefer Path::remote_address instead.

-
Source

pub fn local_ip(&self) -> Option<IpAddr>

The local IP address which was used when the peer established -the connection

-

This can be different from the address the endpoint is bound to, in case -the endpoint is bound to a wildcard address like 0.0.0.0 or ::.

-

This will return None for clients, or when the platform does not expose this -information. See noq_udp::RecvMeta::dst_ip for a list of -supported platforms when using noq_udp for I/O, which is the default.

-
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], @@ -157,17 +149,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

@@ -175,7 +167,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

@@ -184,18 +176,18 @@ 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.

Returns the server addresses that are now being probed.

-

Trait Implementations§

Source§

impl Clone for Connection

Source§

fn clone(&self) -> Connection

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 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 Clone for Connection

Source§

fn clone(&self) -> Connection

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 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> CloneToUninit for T
where @@ -219,4 +211,4 @@ otherwise. 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 +[WithDispatch] wrapper. Read more
\ No newline at end of file diff --git a/pr/451/docs/noq/struct.ConnectionClose.html b/pr/451/docs/noq/struct.ConnectionClose.html index 570286a9d..f99d64e9e 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,
@@ -6,8 +6,8 @@
 

Fields§

§error_code: Code

Class of error as encoded in the specification

§frame_type: MaybeFrame

Type of frame that caused the close

§reason: Bytes

Human-readable reason for the close

-

Trait Implementations§

Source§

impl Clone for ConnectionClose

Source§

fn clone(&self) -> ConnectionClose

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 ConnectionClose

Source§

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

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

impl Display for ConnectionClose

Source§

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

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

impl From<Error> for ConnectionClose

Source§

fn from(x: Error) -> ConnectionClose

Converts to this type from the input type.
Source§

impl PartialEq for ConnectionClose

Source§

fn eq(&self, other: &ConnectionClose) -> 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 ConnectionClose

Source§

impl StructuralPartialEq for ConnectionClose

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for ConnectionClose

Source§

fn clone(&self) -> ConnectionClose

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 ConnectionClose

Source§

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

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

impl Display for ConnectionClose

Source§

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

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

impl From<Error> for ConnectionClose

Source§

fn from(x: Error) -> ConnectionClose

Converts to this type from the input type.
Source§

impl PartialEq for ConnectionClose

Source§

fn eq(&self, other: &ConnectionClose) -> 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 ConnectionClose

Source§

impl StructuralPartialEq for ConnectionClose

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.ConnectionId.html b/pr/451/docs/noq/struct.ConnectionId.html index 2c1548a9b..d2f0fc923 100644 --- a/pr/451/docs/noq/struct.ConnectionId.html +++ b/pr/451/docs/noq/struct.ConnectionId.html @@ -1,4 +1,4 @@ -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

diff --git a/pr/451/docs/noq/struct.ConnectionStats.html b/pr/451/docs/noq/struct.ConnectionStats.html index cd16a9d7b..31792da81 100644 --- a/pr/451/docs/noq/struct.ConnectionStats.html +++ b/pr/451/docs/noq/struct.ConnectionStats.html @@ -1,16 +1,20 @@ -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, pub frame_rx: FrameStats, + pub lost_packets: u64, + pub lost_bytes: u64, }
Expand description

Connection statistics.

The fields here are a sum of the respective fields in the PathStats for all the paths that exist as well as all paths that previously existed.

-

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.
§udp_tx: UdpStats

Statistics about UDP datagrams transmitted on a connection.

-
§udp_rx: UdpStats

Statistics about UDP datagrams received on a connection.

-
§frame_tx: FrameStats

Statistics about frames transmitted on a connection.

-
§frame_rx: FrameStats

Statistics about frames received on a 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 +

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.
§udp_tx: UdpStats

Statistics about UDP datagrams transmitted on the connection.

+
§udp_rx: UdpStats

Statistics about UDP datagrams received on the connection.

+
§frame_tx: FrameStats

Statistics about frames transmitted on the connection.

+
§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 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 new file mode 100644 index 000000000..d6bbc9bd8 --- /dev/null +++ b/pr/451/docs/noq/struct.DecryptedInitial.html @@ -0,0 +1,30 @@ +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

+

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.

+

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.

+
§

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, 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.Endpoint.html b/pr/451/docs/noq/struct.Endpoint.html index 49b135a52..07ad1fca1 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.

@@ -14,19 +14,25 @@ address. For example:

noq::Endpoint::client((std::net::Ipv6Addr::UNSPECIFIED, 0).into());

Some environments may not allow creation of dual-stack sockets, in which case an IPv6 client will only be able to connect to IPv6 servers. An IPv4 client is never dual-stack.

-
Source

pub fn stats(&self) -> EndpointStats

Returns relevant stats from this Endpoint

-
Source

pub fn server(config: ServerConfig, addr: SocketAddr) -> Result<Self>

Helper to construct an endpoint for use with both incoming and outgoing connections

-

Platform defaults for dual-stack sockets vary. For example, any socket bound to a wildcard -IPv6 address on Windows will not by default be able to communicate with IPv4 -addresses. Portable applications should bind an address that matches the family they wish to -communicate within.

-
Source

pub fn new( +

Source

pub fn stats(&self) -> EndpointStats

Returns relevant stats from this Endpoint

+
Source

pub fn server(config: ServerConfig, addr: SocketAddr) -> Result<Self>

Helper to construct an endpoint for use with both incoming and outgoing connections

+

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.

+

If an IPv6 address is provided, attempts to make the socket dual-stack so as to allow +communication with both IPv4 and IPv6 clients. As such, calling Endpoint::server with +the address [::]:0 is a reasonable default to maximize the ability to accept connections +from any address.

+

Some environments may not allow creation of dual-stack sockets, in which case an IPv6 +server will only be able to accept connections from IPv6 clients. An IPv4 server is never +dual-stack.

+
Source

pub fn new( config: EndpointConfig, server_config: Option<ServerConfig>, socket: UdpSocket, runtime: Arc<dyn Runtime>, ) -> Result<Self>

Construct an endpoint with arbitrary configuration and socket

-
Source

pub fn new_with_abstract_socket( +

Source

pub fn new_with_abstract_socket( config: EndpointConfig, server_config: Option<ServerConfig>, socket: Box<dyn AsyncUdpSocket>, @@ -34,13 +40,13 @@ communicate within.

) -> Result<Self>

Construct an endpoint with arbitrary configuration and pre-constructed abstract socket

Useful when socket has additional state (e.g. sidechannels) attached for which shared ownership is needed.

-
Source

pub fn accept(&self) -> Accept<'_>

Get the next incoming connection attempt from a client

+
Source

pub fn accept(&self) -> Accept<'_>

Get the next incoming connection attempt from a client

Yields Incomings, or None if the endpoint is closed. Incoming can be awaited to obtain the final Connection, or used to e.g. filter connection attempts or force address validation, or converted into an intermediate Connecting future which can be used to e.g. send 0.5-RTT data.

-
Source

pub fn set_default_client_config(&self, config: ClientConfig)

Set the client configuration used by connect

-
Source

pub fn connect( +

Source

pub fn set_default_client_config(&self, config: ClientConfig)

Set the client configuration used by connect

+
Source

pub fn connect( &self, addr: SocketAddr, server_name: &str, @@ -50,20 +56,20 @@ connection from being intercepted by an attacker with a valid certificate for so server.

May fail immediately due to configuration errors, or in the future if the connection could not be established.

-

Source

pub fn connect_with( +

Source

pub fn connect_with( &self, config: ClientConfig, addr: SocketAddr, server_name: &str, ) -> Result<Connecting, ConnectError>

Connect to a remote endpoint using a custom configuration.

See connect() for details.

-
Source

pub fn rebind(&self, socket: UdpSocket) -> Result<()>

Switch to a new UDP socket

+
Source

pub fn rebind(&self, socket: UdpSocket) -> Result<()>

Switch to a new UDP socket

See Endpoint::rebind_abstract() for details.

-
Source

pub fn rebind_abstract(&self, socket: Box<dyn AsyncUdpSocket>) -> Result<()>

Switch to a new UDP socket

+
Source

pub fn rebind_abstract(&self, socket: Box<dyn AsyncUdpSocket>) -> Result<()>

Switch to a new UDP socket

Allows the endpoint’s address to be updated live, affecting all active connections. Incoming connections and connections to servers unreachable from the new address will be lost.

On error, the old UDP socket is retained.

-
Source

pub fn handle_network_change( +

Source

pub fn handle_network_change( &self, hint: Option<Arc<dyn NetworkChangeHint + Sync + Send>>, )

Notify connections that the local network address has changed.

@@ -77,19 +83,19 @@ address and switching from WiFi to cellular, or when the local IP address change the network change. If None, all paths are assumed to be non-recoverable. For client-side multipath connections, unrecoverable paths will be closed and replaced with new paths to the same remote addresses.

-
Source

pub fn set_server_config(&self, server_config: Option<ServerConfig>)

Replace the server configuration, affecting new incoming connections only

+
Source

pub fn set_server_config(&self, server_config: Option<ServerConfig>)

Replace the server configuration, affecting new incoming connections only

Useful for e.g. refreshing TLS certificates without disrupting existing connections.

-
Source

pub fn local_addr(&self) -> Result<SocketAddr>

Get the local SocketAddr the underlying socket is bound to

-
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.

+
Source

pub fn local_addr(&self) -> Result<SocketAddr>

Get the local SocketAddr the underlying socket is bound to

+
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)

Wait for all connections on the endpoint to be cleanly shut 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.

-

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 +

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 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.EndpointConfig.html b/pr/451/docs/noq/struct.EndpointConfig.html index 119aa6e09..525a6f015 100644 --- a/pr/451/docs/noq/struct.EndpointConfig.html +++ b/pr/451/docs/noq/struct.EndpointConfig.html @@ -1,16 +1,18 @@ -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<F>(&mut self, factory: F) -> &mut EndpointConfig
where - F: Fn() -> Box<dyn ConnectionIdGenerator> + Send + Sync + 'static,

Supply a custom connection ID generator factory

+

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( + &mut self, + factory: Arc<dyn Fn() -> Box<dyn ConnectionIdGenerator> + Send + Sync>, +) -> &mut EndpointConfig

Supply a custom connection ID generator factory

Called once by each Endpoint constructed from this configuration to obtain the CID generator which will be used to generate the CIDs used for incoming packets on all connections involving that Endpoint. A custom CID generator allows applications to embed information in local connection IDs, e.g. to support stateless packet-level load balancers.

Defaults to HashedConnectionIdGenerator.

-
Source

pub fn reset_key(&mut self, key: Arc<dyn HmacKey>) -> &mut EndpointConfig

Private key used to send authenticated connection resets to peers who were +

Source

pub fn reset_key(&mut self, key: Arc<dyn HmacKey>) -> &mut EndpointConfig

Private key used to send authenticated connection resets to peers who were communicating with a previous instance of this endpoint.

-
Source

pub fn max_udp_payload_size( +

Source

pub fn max_udp_payload_size( &mut self, value: u16, ) -> Result<&mut EndpointConfig, ConfigError>

Maximum UDP payload size accepted from peers (excluding UDP and IP overhead).

@@ -19,27 +21,27 @@ communicating with a previous instance of this endpoint.

1500 byte Ethernet MTU. Deployments on links with larger MTUs (e.g. loopback or Ethernet with jumbo frames) can raise this to improve performance at the cost of a linear increase in datagram receive buffer size.

-
Source

pub fn get_max_udp_payload_size(&self) -> u64

Get the current value of max_udp_payload_size

-
Source

pub fn supported_versions( +

Source

pub fn get_max_udp_payload_size(&self) -> u64

Get the current value of max_udp_payload_size

+
Source

pub fn supported_versions( &mut self, supported_versions: Vec<u32>, ) -> &mut EndpointConfig

Override supported QUIC versions

-
Source

pub fn grease_quic_bit(&mut self, value: bool) -> &mut EndpointConfig

Whether to accept QUIC packets containing any value for the fixed bit

+
Source

pub fn grease_quic_bit(&mut self, value: bool) -> &mut EndpointConfig

Whether to accept QUIC packets containing any value for the fixed bit

Enabled by default. Helps protect against protocol ossification and makes traffic less identifiable to observers. Disable if helping observers identify this traffic as QUIC is desired.

-
Source

pub fn min_reset_interval(&mut self, value: Duration) -> &mut EndpointConfig

Minimum interval between outgoing stateless reset packets

+
Source

pub fn min_reset_interval(&mut self, value: Duration) -> &mut EndpointConfig

Minimum interval between outgoing stateless reset packets

Defaults to 20ms. Limits the impact of attacks which flood an endpoint with garbage packets, e.g. ISAKMP/IKE amplification. Larger values provide a stronger defense, but may delay detection of some error conditions by clients. Using a ConnectionIdGenerator with a low rate of false positives in validate reduces the risk incurred by a small minimum reset interval.

-
Source

pub fn rng_seed(&mut self, seed: Option<[u8; 32]>) -> &mut EndpointConfig

Optional seed to be used internally for random number generation

+
Source

pub fn rng_seed(&mut self, seed: Option<[u8; 32]>) -> &mut EndpointConfig

Optional seed to be used internally for random number generation

By default, noq will initialize an endpoint’s rng using a platform entropy source. However, you can seed the rng yourself through this method (e.g. if you need to run noq deterministically or if you are using noq in an environment that doesn’t have a source of entropy available).

-

Trait Implementations§

Source§

impl Clone for EndpointConfig

Source§

fn clone(&self) -> EndpointConfig

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 EndpointConfig

Source§

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

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

impl Default for EndpointConfig

Source§

fn default() -> EndpointConfig

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 EndpointConfig

Source§

fn clone(&self) -> EndpointConfig

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 EndpointConfig

Source§

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

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

impl Default for EndpointConfig

Source§

fn default() -> EndpointConfig

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.EndpointStats.html b/pr/451/docs/noq/struct.EndpointStats.html index a5fb8a31d..43ee2b0a9 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.FrameStats.html b/pr/451/docs/noq/struct.FrameStats.html index 88a37d94e..d9fe495e1 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, diff --git a/pr/451/docs/noq/struct.IdleTimeout.html b/pr/451/docs/noq/struct.IdleTimeout.html index 879ea2d79..873dea7d8 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 5886d3b86..3f96ee9ac 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, @@ -20,7 +20,7 @@ The inverse is not guaranteed.

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 CID when initiating the connection

-
Source

pub fn decrypt(&self) -> Option<DecryptedInitial>

Decrypt the Initial packet payload

+
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, f: &mut Formatter<'_>) -> Result

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

impl Drop for Incoming

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

impl IntoFuture for Incoming

Source§

type Output = Result<Connection, ConnectionError>

The output that the future will produce on completion.
Source§

type IntoFuture = IncomingFuture

Which kind of future are we turning this into?
Source§

fn into_future(self) -> Self::IntoFuture

Creates a future from a value. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where diff --git a/pr/451/docs/noq/struct.IncomingFuture.html b/pr/451/docs/noq/struct.IncomingFuture.html index e0cd9eb5e..b7adf40a4 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 2cc9c3850..ee1b40acc 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 711b8f45f..645120f7f 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 49e9d38cd..451766089 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 d28398c9b..89ac28187 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 35a1aa5fe..adfff5de5 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 ce07dc3f6..474592733 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 7e5f62e37..63d343347 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 530c1e14e..9935b9f80 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

-

Resolves to a tuple of (ConnectionError, ConnectionStats).

-

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 = (ConnectionError, ConnectionStats)

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 +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 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 c878e5abb..722182738 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 847bb563d..c62f031f0 100644 --- a/pr/451/docs/noq/struct.OpenPath.html +++ b/pr/451/docs/noq/struct.OpenPath.html @@ -1,9 +1,9 @@ -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.

+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.

The returned value remains the same for the entire lifetime of this future.

-

Trait Implementations§

Source§

impl Future for OpenPath

Source§

type Output = Result<Path, PathError>

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 +

Trait Implementations§

Source§

impl Future for OpenPath

Source§

type Output = Result<Path, PathError>

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

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.OpenUni.html b/pr/451/docs/noq/struct.OpenUni.html index dbe517c1f..90d7c0f41 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 a786c9318..43031dc61 100644 --- a/pr/451/docs/noq/struct.Path.html +++ b/pr/451/docs/noq/struct.Path.html @@ -1,35 +1,42 @@ -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.

-
Source

pub fn id(&self) -> PathId

The PathId of this path.

-
Source

pub fn status(&self) -> Result<PathStatus, ClosedPath>

The current local PathStatus of this path.

-
Source

pub fn set_status(&self, status: PathStatus) -> Result<(), SetPathStatusError>

Sets the PathStatus of this path.

-
Source

pub fn stats(&self) -> PathStats

Returns the PathStats for this path.

-
Source

pub fn close(&self) -> Result<(), ClosePathError>

Closes this path.

+
Source

pub fn id(&self) -> PathId

The PathId of this path.

+
Source

pub fn status(&self) -> Result<PathStatus, ClosedPath>

The current local PathStatus of this path.

+
Source

pub fn set_status( + &self, + status: PathStatus, +) -> Result<PathStatus, SetPathStatusError>

Sets the PathStatus of this path.

+

Returns the previous status of the path.

+
Source

pub fn stats(&self) -> PathStats

Returns the PathStats for this path.

+
Source

pub fn close(&self) -> Result<(), ClosePathError>

Closes this path.

The path is immediately considered closed by the local endpoint. Once the state is removed, -after a short period of time for any in-flight packets, a PathEvent::Abandoned is +after a short period of time for any in-flight packets, a PathEvent::Abandoned is returned.

-
Source

pub fn set_max_idle_timeout( +

Source

pub fn set_max_idle_timeout( &self, timeout: Option<Duration>, -) -> Result<Option<Duration>, ClosedPath>

Sets the max idle timeout for a specific path

+) -> Result<Option<Duration>, ClosedPath>

Sets the max idle timeout for a specific path

See TransportConfig::default_path_max_idle_timeout for details.

Returns the previous value of the setting.

-
Source

pub fn set_keep_alive_interval( +

Source

pub fn set_keep_alive_interval( &self, interval: Option<Duration>, -) -> Result<Option<Duration>, ClosedPath>

Sets the keep_alive_interval for a specific path

+) -> 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 observed_external_addr(&self) -> Result<AddressDiscovery, ClosedPath>

Track changes on our external address as reported by the peer.

+
Source

pub fn observed_external_addr(&self) -> Result<AddressDiscovery, ClosedPath>

Track changes on our external address as reported by the peer.

If the address-discovery extension is not negotiated, the stream will never return.

-
Source

pub fn remote_address(&self) -> Result<SocketAddr, ClosedPath>

The peer’s UDP address for this path.

-
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) -> Self

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 remote_address(&self) -> Result<SocketAddr, ClosedPath>

The peer’s UDP address for this path.

+
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, 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 e0f317942..0557005d2 100644 --- a/pr/451/docs/noq/struct.PathEvents.html +++ b/pr/451/docs/noq/struct.PathEvents.html @@ -1,5 +1,5 @@ -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( +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<'_>, ) -> Poll<Option<Self::Item>>

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

PathId

Struct PathId 

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

Id representing different paths when using multipath extension

+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 diff --git a/pr/451/docs/noq/struct.PathStats.html b/pr/451/docs/noq/struct.PathStats.html index 0a167df85..51b362682 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 13 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, @@ -6,6 +6,7 @@ pub frame_rx: FrameStats, pub cwnd: u64, pub congestion_events: u64, + pub spurious_congestion_events: u64, pub lost_packets: u64, pub lost_bytes: u64, pub sent_plpmtud_probes: u64, @@ -20,15 +21,16 @@
§frame_rx: FrameStats

Statistics about frames received on this path.

§cwnd: u64

Current congestion window of the connection.

§congestion_events: u64

Congestion events on the connection.

-
§lost_packets: u64

The amount of packets lost on this path.

-
§lost_bytes: u64

The amount of bytes lost on this path.

+
§spurious_congestion_events: u64

Spurious congestion events on the connection.

+
§lost_packets: u64

The number of packets lost on this path.

+
§lost_bytes: u64

The number of bytes lost on this path.

§sent_plpmtud_probes: u64

The number of PLPMTUD probe packets sent on this path.

These are also counted by UdpStats::datagrams.

§lost_plpmtud_probes: u64

The number of PLPMTUD probe packets lost on this path.

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, +

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 diff --git a/pr/451/docs/noq/struct.QlogConfig.html b/pr/451/docs/noq/struct.QlogConfig.html index 5196e362e..4f036077e 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 8b399cd24..0f5a7038f 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 cac5d37c3..9d8069388 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 662f80561..7fc5eff9a 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:

  • A variant of ReadError has been yielded by a read call
  • @@ -20,7 +20,7 @@ logging the StreamId. For example, even if a sender first transmits on bidirectional stream 1, the first stream yielded by Connection::accept_bi on the receiver will be bidirectional stream 0.

    -

Implementations§

Source§

impl RecvStream

Source

pub async fn read(&mut self, buf: &mut [u8]) -> Result<Option<usize>, ReadError>

Read data contiguously from the stream.

+

Implementations§

Source§

impl RecvStream

Source

pub async fn read(&mut self, buf: &mut [u8]) -> Result<Option<usize>, ReadError>

Read data contiguously from the stream.

Yields the number of bytes read into buf on success, or None if the stream was finished.

This operation is cancel-safe.

Source

pub async fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), ReadExactError>

Read an exact number of bytes contiguously from the stream.

@@ -36,40 +36,29 @@ side has
Source

pub fn poll_read_buf( - &mut self, - cx: &mut Context<'_>, - buf: &mut ReadBuf<'_>, -) -> Poll<Result<(), ReadError>>

Attempts to read from the stream into the provided buffer, which may be uninitialized

-

On success, returns Poll::Ready(Ok(())) and places data into the unfilled portion of -buf. If this does not write any bytes to buf (and buf.remaining() is non-zero), that -indicates that the remote side has finished the stream and the local side has already -read all bytes.

-

If no data is available for reading, this returns Poll::Pending and arranges for the -current task (via cx.waker()) to be notified when the stream becomes readable or is -closed.

-
Source

pub async fn read_chunk( +

Source

pub async fn read_chunk( &mut self, max_length: usize, -) -> Result<Option<Chunk>, ReadError>

Read the next segment of data

-

Yields None if the stream was finished. Otherwise, yields a segment of data and its -offset in the stream. The chunk’s offset will be immediately after -the last data yielded by RecvStream::read or RecvStream::read_chunk.

+) -> Result<Option<Bytes>, ReadError>

Reads the next segment of data as zero-copy [Bytes].

+

Yields None if the stream was finished. Otherwise, yields the next segment of data. The +chunk’s offset will be immediately after the last data yielded by RecvStream::read or +RecvStream::read_chunk; use bytes_read() to query that offset +explicitly.

For unordered reads, convert the stream into an unordered stream using Self::into_unordered.

-

Slightly more efficient than RecvStream::read due to not copying. Chunk boundaries do not correspond -to peer writes, and hence cannot be used as framing.

+

Slightly more efficient than RecvStream::read due to not copying. Chunk boundaries do +not correspond to peer writes, and hence cannot be used as framing.

This operation is cancel-safe.

-
Source

pub async fn read_chunks( +

Source

pub async fn read_many_chunks( &mut self, bufs: &mut [Bytes], -) -> Result<Option<usize>, ReadError>

Read the next segments of data

-

Fills bufs with the segments of data beginning immediately after the -last data yielded by read or read_chunk, or None if the stream was -finished.

-

Slightly more efficient than read due to not copying. Chunk boundaries -do not correspond to peer writes, and hence cannot be used as framing.

+) -> Result<Option<usize>, ReadError>

Reads the next segments of data.

+

Fills bufs with the segments of data beginning immediately after the last data yielded +by read, read_chunk, or +read_many_chunks, or None if the stream was finished.

+

Slightly more efficient than read due to not copying. Chunk boundaries do +not correspond to peer writes, and hence cannot be used as framing.

This operation is cancel-safe.

-
Source

pub async fn read_to_end( +

Source

pub async fn read_to_end( &mut self, size_limit: usize, ) -> Result<Vec<u8>, ReadToEndError>

Convenience method to read all remaining data into a buffer

@@ -79,21 +68,24 @@ allow. size_limit should be set to limit worst-case memory use.

If unordered reads have already been made, the resulting buffer may have gaps containing arbitrary data.

This operation is not cancel-safe.

-
Source

pub fn stop(&mut self, error_code: VarInt) -> Result<(), ClosedStream>

Stop accepting data

+
Source

pub fn stop(&mut self, error_code: VarInt) -> Result<(), ClosedStream>

Stop accepting data

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 is_0rtt(&self) -> bool

Check if this stream has been opened during 0-RTT.

+
Source

pub fn is_0rtt(&self) -> bool

Check if this stream has been opened during 0-RTT.

In which case any non-idempotent request should be considered dangerous at the application level. Because read data is subject to replay attacks.

-
Source

pub fn id(&self) -> StreamId

Get the identity of this stream

-
Source

pub async fn received_reset(&mut self) -> Result<Option<VarInt>, ResetError>

Completes when the stream has been reset by the peer or otherwise closed

+
Source

pub fn id(&self) -> StreamId

Get the identity of 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 async fn received_reset(&mut self) -> Result<Option<VarInt>, ResetError>

Completes when the stream has been reset by the peer or otherwise closed

Yields Some with the reset error code when the stream is reset by the peer. Yields None when the stream was previously stop()ed, or when the stream was finish()ed by the peer and all data has been received, after which it is no longer meaningful for the stream to be reset.

This operation is cancel-safe.

-
Source

pub fn into_unordered(self) -> UnorderedRecvStream

Converts this stream into an unordered stream.

-

Trait Implementations§

Source§

impl AsyncRead for RecvStream

Available on crate feature futures-io only.
Source

pub fn into_unordered(self) -> UnorderedRecvStream

Converts this stream into an unordered stream.

+

Trait Implementations§

Source§

impl AsyncRead for RecvStream

Available on crate feature futures-io only.
Source§

fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], @@ -102,11 +94,11 @@ which it is no longer meaningful for the stream to be reset.

cx: &mut Context<'_>, bufs: &mut [IoSliceMut<'_>], ) -> Poll<Result<usize, Error>>

Attempt to read from the AsyncRead into bufs using vectored -IO operations. Read more
Source§

impl AsyncRead for RecvStream

Source§

fn poll_read( +IO operations. Read more

Source§

impl AsyncRead for RecvStream

Source§

fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>, -) -> Poll<Result<()>>

Attempts to read from the AsyncRead into buf. Read more
Source§

impl Debug for RecvStream

Source§

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

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

impl Drop for RecvStream

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +) -> Poll<Result<()>>

Attempts to read from the AsyncRead into buf. Read more
Source§

impl Debug for RecvStream

Source§

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

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

impl Drop for RecvStream

Source§

fn drop(&mut self)

Executes the destructor for this 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
§

impl<R> AsyncReadExt for R
where R: AsyncRead + ?Sized,

§

fn chain<R>(self, next: R) -> Chain<Self, R>
where Self: Sized, diff --git a/pr/451/docs/noq/struct.RetryError.html b/pr/451/docs/noq/struct.RetryError.html index 616b6d381..2c3212db4 100644 --- a/pr/451/docs/noq/struct.RetryError.html +++ b/pr/451/docs/noq/struct.RetryError.html @@ -1,4 +1,4 @@ -RetryError in noq - 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

+RetryError in noq - 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 diff --git a/pr/451/docs/noq/struct.SendDatagram.html b/pr/451/docs/noq/struct.SendDatagram.html index 04c0cba2f..1847e1298 100644 --- a/pr/451/docs/noq/struct.SendDatagram.html +++ b/pr/451/docs/noq/struct.SendDatagram.html @@ -1,6 +1,6 @@ -SendDatagram in noq - Rust

SendDatagram

Struct SendDatagram 

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

Future produced by Connection::send_datagram_wait

-

Trait Implementations§

Source§

impl Future for SendDatagram<'_>

Source§

type Output = Result<(), SendDatagramError>

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 SendDatagram<'a>
where +SendDatagram in noq - Rust

SendDatagram

Struct SendDatagram 

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

Future produced by Connection::send_datagram_wait

+

Trait Implementations§

Source§

impl Future for SendDatagram<'_>

Source§

type Output = Result<(), SendDatagramError>

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 SendDatagram<'a>
where PinnedFieldsOf<__Origin<'__pin, 'a>>: Unpin,

Auto Trait Implementations§

§

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

§

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

§

impl<'a> Send for SendDatagram<'a>

§

impl<'a> Sync for SendDatagram<'a>

§

impl<'a> !UnwindSafe for SendDatagram<'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.SendStream.html b/pr/451/docs/noq/struct.SendStream.html index b6a413534..42ab4da14 100644 --- a/pr/451/docs/noq/struct.SendStream.html +++ b/pr/451/docs/noq/struct.SendStream.html @@ -1,4 +1,4 @@ -SendStream in noq - Rust

SendStream

Struct SendStream 

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

A stream that can only be used to send data

+SendStream in noq - Rust

SendStream

Struct SendStream 

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

A stream that can only be used to send data

If dropped, streams that haven’t been explicitly reset() will be implicitly finish()ed, continuing to (re)transmit previously written data until it has been fully acknowledged or the connection is closed.

@@ -8,7 +8,7 @@ ready will always result in no data being written to the stream. This is true of succeed immediately when any progress is made, and is not true of methods which might need to perform multiple writes internally before succeeding. Each write method documents whether it is cancel-safe.

-

Implementations§

Source§

impl SendStream

Source

pub async fn write(&mut self, buf: &[u8]) -> Result<usize, WriteError>

Write a buffer into this stream, returning how many bytes were written

+

Implementations§

Source§

impl SendStream

Source

pub async fn write(&mut self, buf: &[u8]) -> Result<usize, WriteError>

Write a buffer into this stream, returning how many bytes were written

Unless this method errors, it waits until some amount of buf can be written into this stream, and then writes as much as it can without waiting again. Due to congestion and flow control, this may be shorter than buf.len(). On success this yields the length of the @@ -21,44 +21,41 @@ error occurs.

§Cancel safety

This method is not cancellation safe. Even if this does not resolve, some prefix of buf may have been written when previously polled.

-
Source

pub async fn write_chunks( +

Source

pub async fn write_many_chunks( &mut self, - bufs: &mut [Bytes], -) -> Result<Written, WriteError>

Write a slice of [Bytes] into this stream, returning how much was written

+ bufs: &mut &mut [Bytes], +) -> Result<usize, WriteError>

Writes [Bytes] from a slice of buffers into this stream, returning how many bytes were. +written

Bytes to try to write are provided to this method as an array of cheaply cloneable chunks. Unless this method errors, it waits until some amount of those bytes can be written into this stream, and then writes as much as it can without waiting again. Due to congestion and flow control, this may be less than the total number of bytes.

-

On success, this method both mutates bufs and yields an informative Written struct -indicating how much was written:

+

On success, this method both mutates bufs and returns the number of bytes written:

    -
  • [Bytes] chunks that were fully written are mutated to be empty.
  • -
  • If a [Bytes] chunk was partially written, it is split to contain -only the suffix of bytes that were not written.
  • -
  • The yielded Written struct indicates how many chunks were fully written as well as -how many bytes were written.
  • +
  • bufs is advanced past chunks that were fully written.
  • +
  • If a [Bytes] chunk was partially written, the chunk at the new front of bufs is +split to contain only the suffix of bytes that were not written.
§Cancel safety

This method is cancellation safe. If this does not resolve, no bytes were written.

-
Source

pub async fn write_chunk(&mut self, buf: Bytes) -> Result<(), WriteError>

Write a single [Bytes] into this stream in its entirety

-

Bytes to write are provided to this method as an single cheaply cloneable chunk. This -method repeatedly calls write_chunks until all bytes are written, -or an error occurs.

+
Source

pub async fn write_chunk(&mut self, buf: Bytes) -> Result<(), WriteError>

Writes a single [Bytes] into this stream in its entirety.

+

Bytes to write are provided to this method as a single cheaply cloneable chunk. This +method repeatedly calls write_many_chunks until all bytes +are written, or an error occurs.

§Cancel safety

This method is not cancellation safe. Even if this does not resolve, some bytes may have been written when previously polled.

-
Source

pub async fn write_all_chunks( +

Source

pub async fn write_all_chunks( &mut self, bufs: &mut [Bytes], -) -> Result<(), WriteError>

Write a slice of [Bytes] into this stream in its entirety

+) -> Result<(), WriteError>

Writes a slice of [Bytes] into this stream in its entirety.

Bytes to write are provided to this method as an array of cheaply cloneable chunks. This -method repeatedly calls write_chunks until all bytes are written, -or an error occurs. This method mutates bufs by mutating all chunks to be -empty.

+method repeatedly calls write_many_chunks until all bytes are +written, or an error occurs.

§Cancel safety

This method is not cancellation safe. Even if this does not resolve, some bytes may have been written when previously polled.

-
Source

pub fn finish(&mut self) -> Result<(), ClosedStream>

Notify the peer that no more data will ever be written to this stream

+
Source

pub fn finish(&mut self) -> Result<(), ClosedStream>

Notify the peer that no more data will ever be written to this stream

It is an error to write to a SendStream after finish()ing it. reset() may still be called after finish to abandon transmission of any stream data that might still be buffered.

@@ -66,21 +63,21 @@ still be buffered.

May fail if finish() or reset() was previously called. This error is harmless and serves only to indicate that the caller may have incorrect assumptions about the stream’s state.

-
Source

pub fn reset(&mut self, error_code: VarInt) -> Result<(), ClosedStream>

Close the send stream immediately.

+
Source

pub fn reset(&mut self, error_code: VarInt) -> Result<(), ClosedStream>

Close the send stream immediately.

No new data can be written after calling this method. Locally buffered data is dropped, and previously transmitted data will no longer be retransmitted if lost. If an attempt has already been made to finish the stream, the peer may still receive all written data.

May fail if finish() or reset() was previously called. This error is harmless and serves only to indicate that the caller may have incorrect assumptions about the stream’s state.

-
Source

pub fn set_priority(&self, priority: i32) -> Result<(), ClosedStream>

Set the priority of the send stream

+
Source

pub fn set_priority(&self, priority: i32) -> Result<(), ClosedStream>

Set the priority of the send stream

Every send stream has an initial priority of 0. Locally buffered data from streams with higher priority will be transmitted before data from streams with lower priority. Changing the priority of a stream with pending data may only take effect after that data has been transmitted. Using many different priority levels per connection may have a negative impact on performance.

-
Source

pub fn priority(&self) -> Result<i32, ClosedStream>

Get the priority of the send stream

-
Source

pub fn stopped(&self) -> Stopped

Completes when the peer stops the stream or reads the stream to completion

+
Source

pub fn priority(&self) -> Result<i32, ClosedStream>

Get the priority of the send stream

+
Source

pub fn stopped(&self) -> Stopped

Completes when the peer stops the stream or reads the stream to completion

Yields Some with the stop error code if the peer stops the stream. Yields None if the local side finish()es the stream and then the peer acknowledges receipt of all stream data (although not necessarily the processing of it), after which the peer @@ -88,8 +85,8 @@ closing the stream is no longer meaningful.

For a variety of reasons, the peer may not send acknowledgements immediately upon receiving data. As such, relying on stopped to know when the peer has read a stream to completion may introduce more latency than using an application-level response of some sort.

-
Source

pub fn id(&self) -> StreamId

Get the identity of this stream

-
Source

pub fn poll_write( +

Source

pub fn id(&self) -> StreamId

Get the identity of this stream

+
Source

pub fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], @@ -98,22 +95,22 @@ may introduce more latency than using an application-level response of some sort

If the stream is not ready for writing, the method returns Poll::Pending and arranges for the current task (via cx.waker().wake_by_ref()) to receive a notification when the stream becomes writable or is closed.

-

Trait Implementations§

Source§

impl AsyncWrite for SendStream

Available on crate feature futures-io only.

Trait Implementations§

Source§

impl AsyncWrite for SendStream

Available on crate feature futures-io only.
Source§

fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], -) -> Poll<Result<usize>>

Attempt to write bytes from buf into the object. Read more
Source§

fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<()>>

Attempt to flush the object, ensuring that any buffered data reach -their destination. Read more
Source§

fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<()>>

Attempt to close the object. Read more
§

fn poll_write_vectored( +) -> Poll<Result<usize>>

Attempt to write bytes from buf into the object. Read more
Source§

fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<()>>

Attempt to flush the object, ensuring that any buffered data reach +their destination. Read more
Source§

fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<()>>

Attempt to close the object. Read more
§

fn poll_write_vectored( self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &[IoSlice<'_>], ) -> Poll<Result<usize, Error>>

Attempt to write bytes from bufs into the object using vectored -IO operations. Read more
Source§

impl AsyncWrite for SendStream

Source§

fn poll_write( +IO operations. Read more

Source§

impl AsyncWrite for SendStream

Source§

fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], -) -> Poll<Result<usize>>

Attempt to write bytes from buf into the object. Read more
Source§

fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<()>>

Attempts to flush the object, ensuring that any buffered data reach -their destination. Read more
Source§

fn poll_shutdown( +) -> Poll<Result<usize>>

Attempt to write bytes from buf into the object. Read more
Source§

fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<()>>

Attempts to flush the object, ensuring that any buffered data reach +their destination. Read more
Source§

fn poll_shutdown( self: Pin<&mut Self>, _cx: &mut Context<'_>, ) -> Poll<Result<()>>

Initiates or attempts to shut down this writer, returning success when @@ -122,7 +119,7 @@ the I/O connection has completely shut down. Read more
Context<'_>, bufs: &[IoSlice<'_>], ) -> Poll<Result<usize, Error>>

Like poll_write, except that it writes from a slice of buffers. Read more
§

fn is_write_vectored(&self) -> bool

Determines if this writer has an efficient poll_write_vectored -implementation. Read more
Source§

impl Debug for SendStream

Source§

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

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

impl Drop for SendStream

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +implementation. Read more

Source§

impl Debug for SendStream

Source§

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

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

impl Drop for SendStream

Source§

fn drop(&mut self)

Executes the destructor for this 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
§

impl<W> AsyncWriteExt for W
where W: AsyncWrite + ?Sized,

§

fn write<'a>(&'a mut self, src: &'a [u8]) -> Write<'a, Self>
where Self: Unpin,

Writes a buffer into this writer, returning how many bytes were diff --git a/pr/451/docs/noq/struct.ServerConfig.html b/pr/451/docs/noq/struct.ServerConfig.html index 58897f301..792f20a53 100644 --- a/pr/451/docs/noq/struct.ServerConfig.html +++ b/pr/451/docs/noq/struct.ServerConfig.html @@ -1,4 +1,4 @@ -ServerConfig in noq - Rust

ServerConfig

Struct ServerConfig 

Source
pub struct ServerConfig {
+ServerConfig in noq - Rust

ServerConfig

Struct ServerConfig 

Source
pub struct ServerConfig {
     pub transport: Arc<TransportConfig>,
     pub crypto: Arc<dyn ServerConfig>,
     pub validation_token: ValidationTokenConfig,
@@ -9,38 +9,38 @@
 
§crypto: Arc<dyn ServerConfig>

TLS configuration used for incoming connections

Must be set to use TLS 1.3 only.

§validation_token: ValidationTokenConfig

Configuration for sending and handling validation tokens

-

Implementations§

Source§

impl ServerConfig

Source

pub fn new( +

Implementations§

Source§

impl ServerConfig

Source

pub fn new( crypto: Arc<dyn ServerConfig>, token_key: Arc<dyn HandshakeTokenKey>, ) -> ServerConfig

Create a default config with a particular handshake token key

-
Source

pub fn transport_config( +

Source

pub fn transport_config( &mut self, transport: Arc<TransportConfig>, ) -> &mut ServerConfig

Set a custom TransportConfig

-
Source

pub fn validation_token_config( +

Source

pub fn validation_token_config( &mut self, validation_token: ValidationTokenConfig, ) -> &mut ServerConfig

Set a custom ValidationTokenConfig

-
Source

pub fn token_key( +

Source

pub fn token_key( &mut self, value: Arc<dyn HandshakeTokenKey>, ) -> &mut ServerConfig

Private key used to authenticate data included in handshake tokens

-
Source

pub fn retry_token_lifetime(&mut self, value: Duration) -> &mut ServerConfig

Duration after a retry token was issued for which it’s considered valid

+
Source

pub fn retry_token_lifetime(&mut self, value: Duration) -> &mut ServerConfig

Duration after a retry token was issued for which it’s considered valid

Defaults to 15 seconds.

-
Source

pub fn migration(&mut self, value: bool) -> &mut ServerConfig

Whether to allow clients to migrate to new addresses

+
Source

pub fn migration(&mut self, value: bool) -> &mut ServerConfig

Whether to allow clients to migrate to new addresses

Improves behavior for clients that move between different internet connections or suffer NAT rebinding. Enabled by default.

-
Source

pub fn preferred_address_v4( +

Source

pub fn preferred_address_v4( &mut self, address: Option<SocketAddrV4>, ) -> &mut ServerConfig

The preferred IPv4 address that will be communicated to clients during handshaking

If the client is able to reach this address, it will switch to it.

-
Source

pub fn preferred_address_v6( +

Source

pub fn preferred_address_v6( &mut self, address: Option<SocketAddrV6>, ) -> &mut ServerConfig

The preferred IPv6 address that will be communicated to clients during handshaking

If the client is able to reach this address, it will switch to it.

-
Source

pub fn max_incoming(&mut self, max_incoming: usize) -> &mut ServerConfig

Maximum number of Incoming to allow to exist at a time

+
Source

pub fn max_incoming(&mut self, max_incoming: usize) -> &mut ServerConfig

Maximum number of Incoming to allow to exist at a time

An Incoming comes into existence when an incoming connection attempt is received and stops existing when the application either accepts it or otherwise disposes of it. While this limit is reached, new incoming connection attempts are immediately @@ -49,7 +49,7 @@ application latency in handling incoming connection attempts.

The default value is set to 65536. With a typical Ethernet MTU of 1500 bytes, this limits memory consumption from this to under 100 MiB–a generous amount that still prevents memory exhaustion in most contexts.

-
Source

pub fn incoming_buffer_size( +

Source

pub fn incoming_buffer_size( &mut self, incoming_buffer_size: u64, ) -> &mut ServerConfig

Maximum number of received bytes to buffer for each Incoming

@@ -61,7 +61,7 @@ the first packet. Packets received in excess of this limit are dropped, which ma

The default value is set to 10 MiB–an amount such that in most situations a client would not transmit that much 0-RTT data faster than the server handles the corresponding Incoming.

-
Source

pub fn incoming_buffer_size_total( +

Source

pub fn incoming_buffer_size_total( &mut self, incoming_buffer_size_total: u64, ) -> &mut ServerConfig

Maximum number of received bytes to buffer for all Incoming @@ -73,20 +73,20 @@ the first packet. Packets received in excess of this limit are dropped, which ma 0-RTT or handshake data to have to be retransmitted.

The default value is set to 100 MiB–a generous amount that still prevents memory exhaustion in most contexts.

-
Source

pub fn time_source( +

Source

pub fn time_source( &mut self, time_source: Arc<dyn TimeSource>, ) -> &mut ServerConfig

Object to get current SystemTime

This exists to allow system time to be mocked in tests, or wherever else desired.

Defaults to StdSystemTime, which simply calls SystemTime::now().

-
Source§

impl ServerConfig

Source§

impl ServerConfig

Source

pub fn with_single_cert( cert_chain: Vec<CertificateDer<'static>>, key: PrivateKeyDer<'static>, ) -> Result<ServerConfig, Error>

Create a server config with the given certificate chain to be presented to clients

Uses a randomized handshake token key.

-
Source§

impl ServerConfig

Source

pub fn with_crypto(crypto: Arc<dyn ServerConfig>) -> ServerConfig

Create a server config with the given crypto::ServerConfig

+
Source§

impl ServerConfig

Source

pub fn with_crypto(crypto: Arc<dyn ServerConfig>) -> ServerConfig

Create a server config with the given crypto::ServerConfig

Uses a randomized handshake token key.

-

Trait Implementations§

Source§

impl Clone for ServerConfig

Source§

fn clone(&self) -> ServerConfig

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 ServerConfig

Source§

fn fmt(&self, fmt: &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 +

Trait Implementations§

Source§

impl Clone for ServerConfig

Source§

fn clone(&self) -> ServerConfig

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 ServerConfig

Source§

fn fmt(&self, fmt: &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 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.SmolRuntime.html b/pr/451/docs/noq/struct.SmolRuntime.html index ddfef8b79..0e1e06709 100644 --- a/pr/451/docs/noq/struct.SmolRuntime.html +++ b/pr/451/docs/noq/struct.SmolRuntime.html @@ -1,4 +1,4 @@ -SmolRuntime in noq - Rust

SmolRuntime

Struct SmolRuntime 

Source
pub struct SmolRuntime;
Expand description

A noq runtime for smol

+SmolRuntime in noq - Rust

SmolRuntime

Struct SmolRuntime 

Source
pub struct SmolRuntime;
Expand description

A noq runtime for smol

Trait Implementations§

Source§

impl Debug for SmolRuntime

Source§

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

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

impl Runtime for SmolRuntime

Source§

fn new_timer(&self, t: Instant) -> Pin<Box<dyn AsyncTimer>>

Construct a timer that will expire at i
Source§

fn spawn(&self, future: Pin<Box<dyn Future<Output = ()> + Send>>)

Drive future to completion in the background
Source§

fn wrap_udp_socket(&self, sock: UdpSocket) -> Result<Box<dyn AsyncUdpSocket>>

Convert t into the socket type used by this runtime
Source§

fn now(&self) -> Instant

Look up the current time 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.StdSystemTime.html b/pr/451/docs/noq/struct.StdSystemTime.html index e05944e7b..c16cb0823 100644 --- a/pr/451/docs/noq/struct.StdSystemTime.html +++ b/pr/451/docs/noq/struct.StdSystemTime.html @@ -1,6 +1,6 @@ -StdSystemTime in noq - Rust

StdSystemTime

Struct StdSystemTime 

Source
pub struct StdSystemTime;
Expand description

Default implementation of TimeSource

+StdSystemTime in noq - Rust

StdSystemTime

Struct StdSystemTime 

Source
pub struct StdSystemTime;
Expand description

Default implementation of TimeSource

Implements now by calling SystemTime::now().

-

Trait Implementations§

Source§

impl TimeSource for StdSystemTime

Source§

fn now(&self) -> SystemTime

Get SystemTime::now() or the mocked equivalent

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl TimeSource for StdSystemTime

Source§

fn now(&self) -> SystemTime

Get SystemTime::now() or the mocked equivalent

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/struct.Stopped.html b/pr/451/docs/noq/struct.Stopped.html index 4bb4f74f6..cb0ec42df 100644 --- a/pr/451/docs/noq/struct.Stopped.html +++ b/pr/451/docs/noq/struct.Stopped.html @@ -1,6 +1,6 @@ -Stopped in noq - Rust

Stopped

Struct Stopped 

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

Future returned from SendStream::stopped.

-

Trait Implementations§

Source§

impl Debug for Stopped

Source§

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

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

impl Future for Stopped

Source§

type Output = Result<Option<VarInt>, StoppedError>

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
Source§

impl<'__pin> Unpin for Stopped
where +Stopped in noq - Rust

Stopped

Struct Stopped 

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

Future returned from SendStream::stopped.

+

Trait Implementations§

Source§

impl Debug for Stopped

Source§

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

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

impl Future for Stopped

Source§

type Output = Result<Option<VarInt>, StoppedError>

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
Source§

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

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.StreamId.html b/pr/451/docs/noq/struct.StreamId.html index 65302a06e..95125c081 100644 --- a/pr/451/docs/noq/struct.StreamId.html +++ b/pr/451/docs/noq/struct.StreamId.html @@ -1,26 +1,26 @@ -StreamId in noq - Rust

StreamId

Struct StreamId 

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

Identifier for a stream within a particular connection

-

Implementations§

Source§

impl StreamId

Source

pub fn new(initiator: Side, dir: Dir, index: u64) -> StreamId

Create a new StreamId

-
Source

pub fn initiator(self) -> Side

Which side of a connection initiated the stream

-
Source

pub fn dir(self) -> Dir

Which directions data flows in

-
Source

pub fn index(self) -> u64

Distinguishes streams of the same initiator and directionality

-

Trait Implementations§

Source§

impl<'arbitrary> Arbitrary<'arbitrary> for StreamId

Source§

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

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

fn arbitrary_take_rest(u: Unstructured<'a>) -> Result<Self, Error>

Generate an arbitrary value of Self from the entirety of the given +StreamId in noq - Rust

StreamId

Struct StreamId 

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

Identifier for a stream within a particular connection

+

Implementations§

Source§

impl StreamId

Source

pub fn new(initiator: Side, dir: Dir, index: u64) -> StreamId

Create a new StreamId

+
Source

pub fn initiator(self) -> Side

Which side of a connection initiated the stream

+
Source

pub fn dir(self) -> Dir

Which directions data flows in

+
Source

pub fn index(self) -> u64

Distinguishes streams of the same initiator and directionality

+

Trait Implementations§

Source§

impl<'arbitrary> Arbitrary<'arbitrary> for StreamId

Source§

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

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

fn arbitrary_take_rest(u: Unstructured<'a>) -> Result<Self, Error>

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

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 StreamId

Source§

fn clone(&self) -> StreamId

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 StreamId

Source§

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

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

impl Decodable for StreamId

Source§

fn decode<B>(buf: &mut B) -> Result<StreamId, UnexpectedEnd>
where - B: Buf,

Decode a Self from the provided buffer, if the buffer is large enough
Source§

impl Display for StreamId

Source§

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

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

impl Encodable for StreamId

Source§

fn encode<B>(&self, buf: &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<VarInt> for StreamId

Source§

fn from(v: VarInt) -> StreamId

Converts to this type from the input type.
Source§

impl Hash for StreamId

Source§

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

Source§

impl Clone for StreamId

Source§

fn clone(&self) -> StreamId

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 StreamId

Source§

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

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

impl Decodable for StreamId

Source§

fn decode<B>(buf: &mut B) -> Result<StreamId, UnexpectedEnd>
where + B: Buf,

Decode a Self from the provided buffer, if the buffer is large enough
Source§

impl Display for StreamId

Source§

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

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

impl Encodable for StreamId

Source§

fn encode<B>(&self, buf: &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<VarInt> for StreamId

Source§

fn from(v: VarInt) -> StreamId

Converts to this type from the input type.
Source§

impl Hash for StreamId

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 StreamId

Source§

fn cmp(&self, other: &StreamId) -> 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 StreamId

Source§

fn cmp(&self, other: &StreamId) -> 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 StreamId

Source§

fn eq(&self, other: &StreamId) -> 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 StreamId

Source§

fn partial_cmp(&self, other: &StreamId) -> 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 StreamId

Source§

fn eq(&self, other: &StreamId) -> 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 StreamId

Source§

fn partial_cmp(&self, other: &StreamId) -> 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 StreamId

Source§

impl Eq for StreamId

Source§

impl StructuralPartialEq for StreamId

Auto Trait Implementations§

Blanket Implementations§

Source§

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

Source§

impl Copy for StreamId

Source§

impl Eq for StreamId

Source§

impl StructuralPartialEq for StreamId

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.TokenMemoryCache.html b/pr/451/docs/noq/struct.TokenMemoryCache.html index 6caa3cf45..7f5f7923c 100644 --- a/pr/451/docs/noq/struct.TokenMemoryCache.html +++ b/pr/451/docs/noq/struct.TokenMemoryCache.html @@ -1,4 +1,4 @@ -TokenMemoryCache in noq - Rust

TokenMemoryCache

Struct TokenMemoryCache 

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

TokenStore implementation that stores up to N tokens per server name for up to a +TokenMemoryCache in noq - Rust

TokenMemoryCache

Struct TokenMemoryCache 

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

TokenStore implementation that stores up to N tokens per server name for up to a limited number of server names, in-memory

Implementations§

Source§

impl TokenMemoryCache

Source

pub fn new( max_server_names: u32, diff --git a/pr/451/docs/noq/struct.TokenReuseError.html b/pr/451/docs/noq/struct.TokenReuseError.html index 671777fd3..e09deff3a 100644 --- a/pr/451/docs/noq/struct.TokenReuseError.html +++ b/pr/451/docs/noq/struct.TokenReuseError.html @@ -1,4 +1,4 @@ -TokenReuseError in noq - Rust

TokenReuseError

Struct TokenReuseError 

Source
pub struct TokenReuseError;
Expand description

Error for when a validation token may have been reused

+TokenReuseError in noq - Rust

TokenReuseError

Struct TokenReuseError 

Source
pub struct TokenReuseError;
Expand description

Error for when a validation token may have been reused

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.TokioRuntime.html b/pr/451/docs/noq/struct.TokioRuntime.html index c0414fb40..e48e1e983 100644 --- a/pr/451/docs/noq/struct.TokioRuntime.html +++ b/pr/451/docs/noq/struct.TokioRuntime.html @@ -1,4 +1,4 @@ -TokioRuntime in noq - Rust

TokioRuntime

Struct TokioRuntime 

Source
pub struct TokioRuntime;
Expand description

A noq runtime for Tokio

+TokioRuntime in noq - Rust

TokioRuntime

Struct TokioRuntime 

Source
pub struct TokioRuntime;
Expand description

A noq runtime for Tokio

Trait Implementations§

Source§

impl Debug for TokioRuntime

Source§

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

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

impl Runtime for TokioRuntime

Source§

fn new_timer(&self, t: Instant) -> Pin<Box<dyn AsyncTimer>>

Construct a timer that will expire at i
Source§

fn spawn(&self, future: Pin<Box<dyn Future<Output = ()> + Send>>)

Drive future to completion in the background
Source§

fn wrap_udp_socket(&self, sock: UdpSocket) -> Result<Box<dyn AsyncUdpSocket>>

Convert t into the socket type used by this runtime
Source§

fn now(&self) -> Instant

Look up the current time 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.Transmit.html b/pr/451/docs/noq/struct.Transmit.html index f9d06ac01..77bbf6cd2 100644 --- a/pr/451/docs/noq/struct.Transmit.html +++ b/pr/451/docs/noq/struct.Transmit.html @@ -1,4 +1,4 @@ -Transmit in noq - Rust

Transmit

Struct Transmit 

Source
pub struct Transmit {
+Transmit in noq - Rust

Transmit

Struct Transmit 

Source
pub struct Transmit {
     pub destination: SocketAddr,
     pub ecn: Option<EcnCodepoint>,
     pub size: usize,
@@ -11,7 +11,7 @@
 
§segment_size: Option<usize>

The segment size if this transmission contains multiple datagrams. This is None if the transmit only contains a single datagram

§src_ip: Option<IpAddr>

Optional source IP address for the datagram

-

Trait Implementations§

Source§

impl Debug for Transmit

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 +

Trait Implementations§

Source§

impl Debug for Transmit

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 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/struct.TransportConfig.html b/pr/451/docs/noq/struct.TransportConfig.html index 363483ef1..80a3a1a44 100644 --- a/pr/451/docs/noq/struct.TransportConfig.html +++ b/pr/451/docs/noq/struct.TransportConfig.html @@ -1,4 +1,4 @@ -TransportConfig in noq - Rust

TransportConfig

Struct TransportConfig 

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

Parameters governing the core QUIC state machine

+TransportConfig in noq - Rust

TransportConfig

Struct TransportConfig 

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

Parameters governing the core QUIC state machine

Default values should be suitable for most internet applications. Applications protocols which forbid remotely-initiated streams should set max_concurrent_bidi_streams and max_concurrent_uni_streams to zero.

@@ -8,17 +8,17 @@ tuned for a particular expected round trip time, link capacity, and memory avail for higher bandwidths and latencies increases worst-case memory consumption, but does not impair performance at lower bandwidths and latencies. The default configuration is tuned for a 100Mbps link with a 100ms round trip time.

-

Implementations§

Source§

impl TransportConfig

Implementations§

Source§

impl TransportConfig

Source

pub fn max_concurrent_bidi_streams( &mut self, value: VarInt, ) -> &mut TransportConfig

Maximum number of incoming bidirectional streams that may be open concurrently

Must be nonzero for the peer to open any bidirectional streams.

Worst-case memory use is directly proportional to max_concurrent_bidi_streams * stream_receive_window, with an upper bound proportional to receive_window.

-
Source

pub fn max_concurrent_uni_streams( +

Source

pub fn max_concurrent_uni_streams( &mut self, value: VarInt, ) -> &mut TransportConfig

Variant of max_concurrent_bidi_streams affecting unidirectional streams

-
Source

pub fn max_idle_timeout( +

Source

pub fn max_idle_timeout( &mut self, value: Option<IdleTimeout>, ) -> &mut TransportConfig

Maximum duration of inactivity to accept before timing out the connection.

@@ -33,43 +33,43 @@ idle timeout can result in permanently hung futures!

config.max_idle_timeout(Some(VarInt::from_u32(10_000).into())); // Set the idle timeout as a `Duration` -config.max_idle_timeout(Some(Duration::from_secs(10).try_into()?));

Source

pub fn stream_receive_window(&mut self, value: VarInt) -> &mut TransportConfig

Maximum number of bytes the peer may transmit without acknowledgement on any one stream +config.max_idle_timeout(Some(Duration::from_secs(10).try_into()?));

Source

pub fn stream_receive_window(&mut self, value: VarInt) -> &mut TransportConfig

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 TransportConfig

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 TransportConfig

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 TransportConfig

Maximum number of bytes to transmit to a peer without acknowledgment

+
Source

pub fn send_window(&mut self, value: u64) -> &mut TransportConfig

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 TransportConfig

Whether to implement fair queuing for send streams having the same priority.

+
Source

pub fn send_fairness(&mut self, value: bool) -> &mut TransportConfig

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 TransportConfig

Maximum reordering in packet number space before FACK style loss detection considers a +

Source

pub fn packet_threshold(&mut self, value: u32) -> &mut TransportConfig

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 TransportConfig

Maximum reordering in time space before time based loss detection considers a packet lost, +

Source

pub fn time_threshold(&mut self, value: f32) -> &mut TransportConfig

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 TransportConfig

The RTT used before an RTT sample is taken

-
Source

pub fn initial_mtu(&mut self, value: u16) -> &mut TransportConfig

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 TransportConfig

The RTT used before an RTT sample is taken

+
Source

pub fn initial_mtu(&mut self, value: u16) -> &mut TransportConfig

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 TransportConfig

The maximum UDP payload size guaranteed to be supported by the network.

+
Source

pub fn min_mtu(&mut self, value: u16) -> &mut TransportConfig

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 @@ -80,19 +80,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 TransportConfig

Specifies the MTU discovery config (see MtuDiscoveryConfig for details).

Enabled by default.

-
Source

pub fn pad_to_mtu(&mut self, value: bool) -> &mut TransportConfig

Pad UDP datagrams carrying application data to current maximum UDP payload size

+
Source

pub fn pad_to_mtu(&mut self, value: bool) -> &mut TransportConfig

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 TransportConfig

Specifies the ACK frequency config (see AckFrequencyConfig for details)

@@ -101,11 +101,16 @@ 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 persistent_congestion_threshold( +

Source

pub fn max_outgoing_bytes_per_second( + &mut self, + value: Option<u64>, +) -> &mut TransportConfig

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 TransportConfig

Number of consecutive PTOs after which network is considered to be experiencing persistent congestion.

-
Source

pub fn keep_alive_interval( +

Source

pub fn keep_alive_interval( &mut self, value: Option<Duration>, ) -> &mut TransportConfig

Period of inactivity before sending a keep-alive packet

@@ -113,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 @@ -125,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

@@ -133,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

@@ -141,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 @@ -152,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.

@@ -166,7 +171,7 @@ will not be easily available to them.

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 reachable address, which in most NATd networks will not be easily available.

-
Source

pub fn max_concurrent_multipath_paths( +

Source

pub fn max_concurrent_multipath_paths( &mut self, max_concurrent: u32, ) -> &mut TransportConfig

Enables the Multipath Extension for QUIC.

@@ -175,7 +180,7 @@ reachable address, which in most NATd networks will not be easily available.

The value provided specifies the number maximum number of paths this endpoint may open concurrently when multipath is negotiated. For any path to be opened, the remote must enable multipath as well.

-
Source

pub fn default_path_max_idle_timeout( +

Source

pub fn default_path_max_idle_timeout( &mut self, timeout: Option<Duration>, ) -> &mut TransportConfig

Sets a default per-path maximum idle timeout

@@ -184,7 +189,7 @@ interact with the

You can also change this using Connection::set_path_max_idle_timeout for existing paths.

-
Source

pub fn default_path_keep_alive_interval( +

Source

pub fn default_path_keep_alive_interval( &mut self, interval: Option<Duration>, ) -> &mut TransportConfig

Sets a default per-path keep alive interval

@@ -194,7 +199,7 @@ existing paths.

control over which path is used for this.

You can also change this using Connection::set_path_keep_alive_interval for existing path.

-
Source

pub fn set_max_remote_nat_traversal_addresses( +

Source

pub fn max_remote_nat_traversal_addresses( &mut self, max_addresses: u8, ) -> &mut TransportConfig

Sets the maximum number of nat traversal addresses this endpoint allows the remote to @@ -203,28 +208,27 @@ advertise

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, a default value of -[DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED] will be used.

-
Source

pub fn qlog_factory( +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 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 78952d263..33617fda7 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 e8ef7eba8..1d5318a99 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, diff --git a/pr/451/docs/noq/struct.UnorderedRecvStream.html b/pr/451/docs/noq/struct.UnorderedRecvStream.html index ed00ac178..0346d209d 100644 --- a/pr/451/docs/noq/struct.UnorderedRecvStream.html +++ b/pr/451/docs/noq/struct.UnorderedRecvStream.html @@ -1,11 +1,11 @@ -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 longer possible since data may have been consumed out of order.

The stream state related fns like Self::id, Self::is_0rtt, Self::stop, and Self::received_reset behave exactly as on RecvStream.

-

Implementations§

Source§

impl UnorderedRecvStream

Source

pub async fn read_chunk( +

Implementations§

Source§

impl UnorderedRecvStream

Source

pub async fn read_chunk( &mut self, max_length: usize, ) -> Result<Option<Chunk>, ReadError>

Reads the next segment of data.

@@ -15,20 +15,20 @@ field can be used to determine ordering in the caller. Unordered reads are less to head-of-line blocking within a stream, but require the application to manage reassembling the original data.

This operation is cancel-safe.

-
Source

pub fn id(&self) -> StreamId

Get the identity of this stream

-
Source

pub fn is_0rtt(&self) -> bool

Check if this stream has been opened during 0-RTT.

+
Source

pub fn id(&self) -> StreamId

Get the identity of this stream

+
Source

pub fn is_0rtt(&self) -> bool

Check if this stream has been opened during 0-RTT.

In which case any non-idempotent request should be considered dangerous at the application level. Because read data is subject to replay attacks.

-
Source

pub fn stop(&mut self, error_code: VarInt) -> Result<(), ClosedStream>

Stop accepting data

+
Source

pub fn stop(&mut self, error_code: VarInt) -> Result<(), ClosedStream>

Stop accepting data

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 async fn received_reset(&mut self) -> Result<Option<VarInt>, ResetError>

Completes when the stream has been reset by the peer or otherwise closed

+
Source

pub async fn received_reset(&mut self) -> Result<Option<VarInt>, ResetError>

Completes when the stream has been reset by the peer or otherwise closed

Yields Some with the reset error code when the stream is reset by the peer. Yields None when the stream was previously stop()ed, or when the stream was finish()ed by the peer and all data has been received, after which it is no longer meaningful for the stream to be reset.

This operation is cancel-safe.

-

Trait Implementations§

Source§

impl Debug for UnorderedRecvStream

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 UnorderedRecvStream

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/struct.ValidationTokenConfig.html b/pr/451/docs/noq/struct.ValidationTokenConfig.html index d9ef92094..3c6d9b8f2 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 @@ -33,20 +33,20 @@ is the main situation where a server might be sending application data to an add be validated by token usage earlier than it would become validated without token usage.

These tokens should not be confused with “stateless reset tokens,” which are similarly named but entirely unrelated.

-

Implementations§

Source§

impl ValidationTokenConfig

Source

pub fn lifetime(&mut self, value: Duration) -> &mut ValidationTokenConfig

Duration after an address validation token was issued for which it’s considered valid

+

Implementations§

Source§

impl ValidationTokenConfig

Source

pub fn lifetime(&mut self, value: Duration) -> &mut ValidationTokenConfig

Duration after an address validation token was issued for which it’s considered valid

This refers only to tokens sent in NEW_TOKEN frames, in contrast to retry tokens.

Defaults to 2 weeks.

-
Source

pub fn log(&mut self, log: Arc<dyn TokenLog>) -> &mut ValidationTokenConfig

Set a custom TokenLog

+
Source

pub fn log(&mut self, log: Arc<dyn TokenLog>) -> &mut ValidationTokenConfig

Set a custom TokenLog

If the bloom feature is enabled (which it is by default), defaults to a default BloomTokenLog, which is suitable for most internet applications.

If the bloom feature is disabled, defaults to NoneTokenLog, which makes the server ignore all address validation tokens (that is, tokens originating from NEW_TOKEN frames–retry tokens are not affected).

-
Source

pub fn sent(&mut self, value: u32) -> &mut ValidationTokenConfig

Number of address validation tokens sent to a client when its path is validated

+
Source

pub fn sent(&mut self, value: u32) -> &mut ValidationTokenConfig

Number of address validation tokens sent to a client when its path is validated

This refers only to tokens sent in NEW_TOKEN frames, in contrast to retry tokens.

If the bloom feature is enabled (which it is by default), defaults to 2. Otherwise, defaults to 0.

-

Trait Implementations§

Source§

impl Clone for ValidationTokenConfig

Source§

fn clone(&self) -> ValidationTokenConfig

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 ValidationTokenConfig

Source§

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

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

impl Default for ValidationTokenConfig

Source§

fn default() -> ValidationTokenConfig

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 ValidationTokenConfig

Source§

fn clone(&self) -> ValidationTokenConfig

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 ValidationTokenConfig

Source§

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

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

impl Default for ValidationTokenConfig

Source§

fn default() -> ValidationTokenConfig

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.VarInt.html b/pr/451/docs/noq/struct.VarInt.html index 2aa2930e0..019c86b4c 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 dffcca1f8..932d303ac 100644 --- a/pr/451/docs/noq/struct.VarIntBoundsExceeded.html +++ b/pr/451/docs/noq/struct.VarIntBoundsExceeded.html @@ -1,5 +1,5 @@ -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, +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 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.WeakConnectionHandle.html b/pr/451/docs/noq/struct.WeakConnectionHandle.html index 68daf3016..1564e2801 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 d7a9ad9a1..9a89357ef 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) -> Self

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 1849df0f8..619eea487 100644 --- a/pr/451/docs/noq/struct.ZeroRttAccepted.html +++ b/pr/451/docs/noq/struct.ZeroRttAccepted.html @@ -1,7 +1,7 @@ -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 +

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 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/trait.AsyncTimer.html b/pr/451/docs/noq/trait.AsyncTimer.html index 18ac49179..69a410798 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 0e161a88a..5f1665cfe 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 04eb50827..3f1194e89 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 b8c605ae8..ecab8b037 100644
--- a/pr/451/docs/noq/trait.NetworkChangeHint.html
+++ b/pr/451/docs/noq/trait.NetworkChangeHint.html
@@ -1,4 +1,4 @@
-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,
@@ -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/trait.QlogFactory.html b/pr/451/docs/noq/trait.QlogFactory.html index ac57dec70..dacb6954d 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 6452974d5..6a41acc6a 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 2869c06bb..50381acdd 100644
--- a/pr/451/docs/noq/trait.TimeSource.html
+++ b/pr/451/docs/noq/trait.TimeSource.html
@@ -1,7 +1,7 @@
-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

This exists to allow system time to be mocked in tests, or wherever else desired.

-

Required Methods§

Source

fn now(&self) -> SystemTime

Get SystemTime::now() or the mocked equivalent

-

Implementors§

\ No newline at end of file +

Required Methods§

Source

fn now(&self) -> SystemTime

Get SystemTime::now() or the mocked equivalent

+

Implementors§

\ No newline at end of file diff --git a/pr/451/docs/noq/trait.TokenLog.html b/pr/451/docs/noq/trait.TokenLog.html index 60db43f8c..1c9909080 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 aff114b1b..5aab57592 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 21a10c856..815e79037 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 f6ff6a81e..47122df23 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 ab3e699ae..979bc1d0c 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 3e097348b..4a9e578b1 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 7d944d5cf..5e22a090a 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 11caeae58..79793736f 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 45eb32cde..f1afc845f 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 d9e851330..566244dec 100644 --- a/pr/451/docs/noq_proto/coding/struct.UnexpectedEnd.html +++ b/pr/451/docs/noq_proto/coding/struct.UnexpectedEnd.html @@ -1,5 +1,5 @@ -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, +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 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/coding/trait.Decodable.html b/pr/451/docs/noq_proto/coding/trait.Decodable.html index cbbe961c9..a41385918 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 b5929c683..3afc587dc 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 8c628b2be..e17ede1f4 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/config/transport/constant.DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED.html b/pr/451/docs/noq_proto/config/transport/constant.DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED.html
deleted file mode 100644
index 50d5e1b71..000000000
--- a/pr/451/docs/noq_proto/config/transport/constant.DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED.html
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-    
-    Redirection
-
-
-    

Redirecting to ../../../noq_proto/constant.DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED.html...

- - - \ No newline at end of file diff --git a/pr/451/docs/noq_proto/congestion/bbr/struct.Bbr.html b/pr/451/docs/noq_proto/congestion/bbr3/struct.Bbr3.html similarity index 64% rename from pr/451/docs/noq_proto/congestion/bbr/struct.Bbr.html rename to pr/451/docs/noq_proto/congestion/bbr3/struct.Bbr3.html index 64b7a24b3..67f06ef21 100644 --- a/pr/451/docs/noq_proto/congestion/bbr/struct.Bbr.html +++ b/pr/451/docs/noq_proto/congestion/bbr3/struct.Bbr3.html @@ -1,11 +1,11 @@ - + Redirection -

Redirecting to ../../../noq_proto/congestion/struct.Bbr.html...

- +

Redirecting to ../../../noq_proto/congestion/struct.Bbr3.html...

+ \ No newline at end of file diff --git a/pr/451/docs/noq_proto/congestion/bbr/struct.BbrConfig.html b/pr/451/docs/noq_proto/congestion/bbr3/struct.Bbr3Config.html similarity index 61% rename from pr/451/docs/noq_proto/congestion/bbr/struct.BbrConfig.html rename to pr/451/docs/noq_proto/congestion/bbr3/struct.Bbr3Config.html index a2722d201..ed6003847 100644 --- a/pr/451/docs/noq_proto/congestion/bbr/struct.BbrConfig.html +++ b/pr/451/docs/noq_proto/congestion/bbr3/struct.Bbr3Config.html @@ -1,11 +1,11 @@ - + Redirection -

Redirecting to ../../../noq_proto/congestion/struct.BbrConfig.html...

- +

Redirecting to ../../../noq_proto/congestion/struct.Bbr3Config.html...

+ \ No newline at end of file diff --git a/pr/451/docs/noq_proto/congestion/index.html b/pr/451/docs/noq_proto/congestion/index.html index 690781691..ee312440e 100644 --- a/pr/451/docs/noq_proto/congestion/index.html +++ b/pr/451/docs/noq_proto/congestion/index.html @@ -1,2 +1,4 @@ -noq_proto::congestion - Rust

Module congestion

Module congestion 

Source
Expand description

Logic for controlling the rate at which data is sent

-

Structs§

Bbr
Experimental! Use at your own risk.
BbrConfig
Configuration for the Bbr congestion controller
ControllerMetrics
Common congestion controller metrics
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 +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/sidebar-items.js b/pr/451/docs/noq_proto/congestion/sidebar-items.js index 888f550d4..07e9dc7b1 100644 --- a/pr/451/docs/noq_proto/congestion/sidebar-items.js +++ b/pr/451/docs/noq_proto/congestion/sidebar-items.js @@ -1 +1 @@ -window.SIDEBAR_ITEMS = {"struct":["Bbr","BbrConfig","ControllerMetrics","Cubic","CubicConfig","NewReno","NewRenoConfig"],"trait":["Controller","ControllerFactory"]}; \ No newline at end of file +window.SIDEBAR_ITEMS = {"struct":["Bbr3","Bbr3Config","ControllerMetrics","Cubic","CubicConfig","NewReno","NewRenoConfig"],"trait":["Controller","ControllerFactory"]}; \ No newline at end of file diff --git a/pr/451/docs/noq/congestion/struct.Bbr.html b/pr/451/docs/noq_proto/congestion/struct.Bbr3.html similarity index 55% rename from pr/451/docs/noq/congestion/struct.Bbr.html rename to pr/451/docs/noq_proto/congestion/struct.Bbr3.html index 9e92b09c0..dbb5082d5 100644 --- a/pr/451/docs/noq/congestion/struct.Bbr.html +++ b/pr/451/docs/noq_proto/congestion/struct.Bbr3.html @@ -1,30 +1,39 @@ -Bbr in noq::congestion - Rust

Bbr

Struct Bbr 

Source
pub struct Bbr { /* 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 google’s quiche implementation https://source.chromium.org/chromium/chromium/src/+/master:net/third_party/quiche/src/quic/core/congestion_control/bbr_sender.cc -of BBR https://datatracker.ietf.org/doc/html/draft-cardwell-iccrg-bbr-congestion-control. -More discussion and links at https://groups.google.com/g/bbr-dev.

-

Implementations§

Source§

impl Bbr

Source

pub fn new(config: Arc<BbrConfig>, current_mtu: u16) -> Bbr

Construct a state using the given config and current time now

-

Trait Implementations§

Source§

impl Clone for Bbr

Source§

fn clone(&self) -> Bbr

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 Bbr

Trait Implementations§

Source§

impl Clone for Bbr3

Source§

fn clone(&self) -> Bbr3

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 Bbr3

Source§

fn on_spurious_congestion_event(&mut self)

Source§

fn on_packet_sent(&mut self, now: Instant, bytes: u16, pn: u64)

One packet was just sent
Source§

fn on_ack( &mut self, now: Instant, sent: Instant, bytes: u64, - app_limited: bool, - rtt: &RttEstimator, -)

Packet deliveries were confirmed Read more
Source§

fn on_end_acks( + pn: u64, + _app_limited: bool, + rtt: &RttEstimator, +)

Packet deliveries were confirmed Read more
Source§

fn on_end_acks( &mut self, - now: Instant, + _now: Instant, in_flight: u64, app_limited: bool, largest_packet_num_acked: Option<u64>, -)

Packets are acked in batches, all with the same now argument. This indicates one of those batches has completed.
Source§

fn on_congestion_event( +)

Packets are acked in batches, all with the same now argument. This indicates one of those batches has completed.
Source§

fn on_congestion_event( &mut self, - _now: Instant, + now: Instant, _sent: Instant, - _is_persistent_congestion: bool, - _is_ecn: bool, + is_persistent_congestion: bool, + is_ecn: bool, lost_bytes: u64, -)

Packets were deemed lost or marked congested Read more
Source§

fn on_mtu_update(&mut self, new_mtu: u16)

The known MTU for the current network path has been updated
Source§

fn window(&self) -> u64

Number of ack-eliciting bytes that may be in flight
Source§

fn metrics(&self) -> ControllerMetrics

Retrieve implementation-specific metrics used to populate qlog traces when they are enabled
Source§

fn clone_box(&self) -> Box<dyn Controller>

Duplicate the controller’s state
Source§

fn initial_window(&self) -> u64

Initial congestion window
Source§

fn into_any(self: Box<Bbr>) -> Box<dyn Any>

Returns Self for use in down-casting to extract implementation details
Source§

fn on_spurious_congestion_event(&mut self)

Packets were incorrectly deemed lost Read more
Source§

impl Debug for Bbr

Source§

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

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl Freeze for Bbr

§

impl RefUnwindSafe for Bbr

§

impl Send for Bbr

§

impl Sync for Bbr

§

impl Unpin for Bbr

§

impl UnwindSafe for Bbr

Blanket Implementations§

Source§

impl<T> Any for T
where + largest_lost_pn: u64, +)

Packets were deemed lost or marked congested Read more
Source§

fn on_packet_lost(&mut self, lost_bytes: u16, pn: u64, now: Instant)

One packet was just lost
Source§

fn on_mtu_update(&mut self, new_mtu: u16)

The known MTU for the current network path has been updated
Source§

fn on_ack_frequency_update( + &mut self, + ack_eliciting_threshold: u64, + requested_max_ack_delay: Duration, +)

The peer’s ACK-frequency parameters have changed Read more
Source§

fn window(&self) -> u64

Number of ack-eliciting bytes that may be in flight
Source§

fn metrics(&self) -> ControllerMetrics

Retrieve implementation-specific metrics used to populate qlog traces when they are enabled +This is also used to alter the pacing of the connection with +pacing_rate and send_quantum
Source§

fn clone_box(&self) -> Box<dyn Controller>

Duplicate the controller’s state
Source§

fn initial_window(&self) -> u64

Initial congestion window
Source§

fn into_any(self: Box<Self>) -> Box<dyn Any>

Returns Self for use in down-casting to extract implementation details
Source§

fn on_sent(&mut self, now: Instant, bytes: u64, largest_pn: u64)

One or more packets were just sent
Source§

impl Debug for Bbr3

Source§

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

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl Freeze for Bbr3

§

impl RefUnwindSafe for Bbr3

§

impl Send for Bbr3

§

impl Sync for Bbr3

§

impl Unpin for Bbr3

§

impl UnwindSafe for Bbr3

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/congestion/struct.BbrConfig.html b/pr/451/docs/noq_proto/congestion/struct.Bbr3Config.html similarity index 61% rename from pr/451/docs/noq/congestion/struct.BbrConfig.html rename to pr/451/docs/noq_proto/congestion/struct.Bbr3Config.html index 1cbda7adb..6ee9ef72b 100644 --- a/pr/451/docs/noq/congestion/struct.BbrConfig.html +++ b/pr/451/docs/noq_proto/congestion/struct.Bbr3Config.html @@ -1,11 +1,18 @@ -BbrConfig in noq::congestion - Rust

BbrConfig

Struct BbrConfig 

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

Configuration for the Bbr congestion controller

-

Implementations§

Source§

impl BbrConfig

Source

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

Default limit on the amount of outstanding data in bytes.

+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 +the congestion windows. +All of these parameters are specific to different states of the algorithm: see BbrState +pacing_margin_percent is used to set a margin when calculating the pacing_rate in order +to not send at 100% capacity when calculating pacing.

+

Implementations§

Source§

impl Bbr3Config

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 BbrConfig

Source§

fn clone(&self) -> BbrConfig

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 BbrConfig

Source§

fn build( - self: Arc<BbrConfig>, +

Trait Implementations§

Source§

impl Clone for Bbr3Config

Source§

fn clone(&self) -> Bbr3Config

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 Bbr3Config

Source§

fn build( + self: Arc<Self>, _now: Instant, current_mtu: u16, -) -> Box<dyn Controller>

Construct a fresh Controller
Source§

impl Debug for BbrConfig

Source§

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

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

impl Default for BbrConfig

Source§

fn default() -> BbrConfig

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

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +) -> Box<dyn Controller>

Construct a fresh Controller
Source§

impl Debug for Bbr3Config

Source§

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

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

impl Default for Bbr3Config

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/congestion/struct.ControllerMetrics.html b/pr/451/docs/noq_proto/congestion/struct.ControllerMetrics.html index ae1e03386..83b9686be 100644 --- a/pr/451/docs/noq_proto/congestion/struct.ControllerMetrics.html +++ b/pr/451/docs/noq_proto/congestion/struct.ControllerMetrics.html @@ -1,12 +1,16 @@ -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>, -}
Expand description

Common congestion controller metrics

+ pub send_quantum: Option<u64>, +}
Expand description

Common congestion controller metrics used both for logging purposes +but also to alter the pacing of the connection with +pacing_rate and send_quantum

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.
§congestion_window: u64

Congestion window (bytes)

§ssthresh: Option<u64>

Slow start threshold (bytes)

-
§pacing_rate: Option<u64>

Pacing rate (bits/s)

-

Trait Implementations§

Source§

impl Debug for ControllerMetrics

Source§

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

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

impl Default for ControllerMetrics

Source§

fn default() -> ControllerMetrics

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

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +
§pacing_rate: Option<u64>

Pacing rate (bytes/s)

+
§send_quantum: Option<u64>

Send Quantum (bytes) used to control the size of packet bursts

+

Trait Implementations§

Source§

impl Default for ControllerMetrics

Source§

fn default() -> ControllerMetrics

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> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

diff --git a/pr/451/docs/noq_proto/congestion/struct.Cubic.html b/pr/451/docs/noq_proto/congestion/struct.Cubic.html index d23665386..2011b0107 100644 --- a/pr/451/docs/noq_proto/congestion/struct.Cubic.html +++ b/pr/451/docs/noq_proto/congestion/struct.Cubic.html @@ -1,26 +1,34 @@ -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

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, now: Instant, sent: Instant, bytes: u64, + _pn: u64, app_limited: bool, rtt: &RttEstimator, -)

Packet deliveries were confirmed Read more
Source§

fn on_congestion_event( +)

Packet deliveries were confirmed Read more
Source§

fn on_congestion_event( &mut self, now: Instant, sent: Instant, is_persistent_congestion: bool, is_ecn: bool, _lost_bytes: u64, -)

Packets were deemed lost or marked congested Read more
Source§

fn on_spurious_congestion_event(&mut self)

Packets were incorrectly deemed lost Read more
Source§

fn on_mtu_update(&mut self, new_mtu: u16)

The known MTU for the current network path has been updated
Source§

fn window(&self) -> u64

Number of ack-eliciting bytes that may be in flight
Source§

fn metrics(&self) -> ControllerMetrics

Retrieve implementation-specific metrics used to populate qlog traces when they are enabled
Source§

fn clone_box(&self) -> Box<dyn Controller>

Duplicate the controller’s state
Source§

fn initial_window(&self) -> u64

Initial congestion window
Source§

fn into_any(self: Box<Self>) -> Box<dyn Any>

Returns Self for use in down-casting to extract implementation details
Source§

fn on_sent(&mut self, now: Instant, bytes: u64, last_packet_number: u64)

One or more packets were just sent
Source§

fn on_end_acks( + _largest_lost_pn: u64, +)

Packets were deemed lost or marked congested Read more
Source§

fn on_spurious_congestion_event(&mut self)

Packets were incorrectly deemed lost Read more
Source§

fn on_mtu_update(&mut self, new_mtu: u16)

The known MTU for the current network path has been updated
Source§

fn window(&self) -> u64

Number of ack-eliciting bytes that may be in flight
Source§

fn metrics(&self) -> ControllerMetrics

Retrieve implementation-specific metrics used to populate qlog traces when they are enabled +This is also used to alter the pacing of the connection with +pacing_rate and send_quantum
Source§

fn clone_box(&self) -> Box<dyn Controller>

Duplicate the controller’s state
Source§

fn initial_window(&self) -> u64

Initial congestion window
Source§

fn into_any(self: Box<Self>) -> Box<dyn Any>

Returns Self for use in down-casting to extract implementation details
Source§

fn on_sent(&mut self, now: Instant, bytes: u64, largest_pn: u64)

One or more packets were just sent
Source§

fn on_packet_sent(&mut self, now: Instant, bytes: u16, pn: u64)

One packet was just sent
Source§

fn on_end_acks( &mut self, now: Instant, in_flight: u64, app_limited: bool, largest_packet_num_acked: Option<u64>, -)

Packets are acked in batches, all with the same now argument. This indicates one of those batches has completed.
Source§

impl Debug for Cubic

Source§

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

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl Freeze for Cubic

§

impl RefUnwindSafe for Cubic

§

impl Send for Cubic

§

impl Sync for Cubic

§

impl Unpin for Cubic

§

impl UnwindSafe for Cubic

Blanket Implementations§

Source§

impl<T> Any for T
where +)

Packets are acked in batches, all with the same now argument. This indicates one of those batches has completed.
Source§

fn on_packet_lost(&mut self, lost_bytes: u16, pn: u64, now: Instant)

One packet was just lost
Source§

fn on_ack_frequency_update( + &mut self, + ack_eliciting_threshold: u64, + requested_max_ack_delay: Duration, +)

The peer’s ACK-frequency parameters have changed Read more
Source§

impl Debug for Cubic

Source§

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

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl Freeze for Cubic

§

impl RefUnwindSafe for Cubic

§

impl Send for Cubic

§

impl Sync for Cubic

§

impl Unpin for Cubic

§

impl UnwindSafe for Cubic

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/congestion/struct.CubicConfig.html b/pr/451/docs/noq_proto/congestion/struct.CubicConfig.html index 97dcf2a8f..fd0aaf23c 100644 --- a/pr/451/docs/noq_proto/congestion/struct.CubicConfig.html +++ b/pr/451/docs/noq_proto/congestion/struct.CubicConfig.html @@ -1,7 +1,7 @@ -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.

+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 +

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 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/congestion/struct.NewReno.html b/pr/451/docs/noq_proto/congestion/struct.NewReno.html index a69d0f6b6..87e0e4fc8 100644 --- a/pr/451/docs/noq_proto/congestion/struct.NewReno.html +++ b/pr/451/docs/noq_proto/congestion/struct.NewReno.html @@ -1,26 +1,34 @@ -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

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, _now: Instant, sent: Instant, bytes: u64, + _pn: u64, app_limited: bool, _rtt: &RttEstimator, -)

Packet deliveries were confirmed Read more
Source§

fn on_congestion_event( +)

Packet deliveries were confirmed Read more
Source§

fn on_congestion_event( &mut self, now: Instant, sent: Instant, is_persistent_congestion: bool, _is_ecn: bool, _lost_bytes: u64, -)

Packets were deemed lost or marked congested Read more
Source§

fn on_mtu_update(&mut self, new_mtu: u16)

The known MTU for the current network path has been updated
Source§

fn window(&self) -> u64

Number of ack-eliciting bytes that may be in flight
Source§

fn metrics(&self) -> ControllerMetrics

Retrieve implementation-specific metrics used to populate qlog traces when they are enabled
Source§

fn clone_box(&self) -> Box<dyn Controller>

Duplicate the controller’s state
Source§

fn initial_window(&self) -> u64

Initial congestion window
Source§

fn into_any(self: Box<Self>) -> Box<dyn Any>

Returns Self for use in down-casting to extract implementation details
Source§

fn on_sent(&mut self, now: Instant, bytes: u64, last_packet_number: u64)

One or more packets were just sent
Source§

fn on_end_acks( + _largest_lost_pn: u64, +)

Packets were deemed lost or marked congested Read more
Source§

fn on_mtu_update(&mut self, new_mtu: u16)

The known MTU for the current network path has been updated
Source§

fn window(&self) -> u64

Number of ack-eliciting bytes that may be in flight
Source§

fn metrics(&self) -> ControllerMetrics

Retrieve implementation-specific metrics used to populate qlog traces when they are enabled +This is also used to alter the pacing of the connection with +pacing_rate and send_quantum
Source§

fn clone_box(&self) -> Box<dyn Controller>

Duplicate the controller’s state
Source§

fn initial_window(&self) -> u64

Initial congestion window
Source§

fn into_any(self: Box<Self>) -> Box<dyn Any>

Returns Self for use in down-casting to extract implementation details
Source§

fn on_sent(&mut self, now: Instant, bytes: u64, largest_pn: u64)

One or more packets were just sent
Source§

fn on_packet_sent(&mut self, now: Instant, bytes: u16, pn: u64)

One packet was just sent
Source§

fn on_end_acks( &mut self, now: Instant, in_flight: u64, app_limited: bool, largest_packet_num_acked: Option<u64>, -)

Packets are acked in batches, all with the same now argument. This indicates one of those batches has completed.
Source§

fn on_spurious_congestion_event(&mut self)

Packets were incorrectly deemed lost Read more
Source§

impl Debug for NewReno

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 +)

Packets are acked in batches, all with the same now argument. This indicates one of those batches has completed.
Source§

fn on_packet_lost(&mut self, lost_bytes: u16, pn: u64, now: Instant)

One packet was just lost
Source§

fn on_spurious_congestion_event(&mut self)

Packets were incorrectly deemed lost Read more
Source§

fn on_ack_frequency_update( + &mut self, + ack_eliciting_threshold: u64, + requested_max_ack_delay: Duration, +)

The peer’s ACK-frequency parameters have changed Read more

Source§

impl Debug for NewReno

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_proto/congestion/struct.NewRenoConfig.html b/pr/451/docs/noq_proto/congestion/struct.NewRenoConfig.html index 34c5b1044..d840b20d5 100644 --- a/pr/451/docs/noq_proto/congestion/struct.NewRenoConfig.html +++ b/pr/451/docs/noq_proto/congestion/struct.NewRenoConfig.html @@ -1,8 +1,8 @@ -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.

+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.

-

Trait Implementations§

Source§

impl Clone for NewRenoConfig

Source§

fn clone(&self) -> NewRenoConfig

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 NewRenoConfig

Source§

fn build(self: Arc<Self>, now: Instant, current_mtu: u16) -> Box<dyn Controller>

Construct a fresh Controller
Source§

impl Debug for NewRenoConfig

Source§

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

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

impl Default for NewRenoConfig

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 +

Source

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

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

+

Trait Implementations§

Source§

impl Clone for NewRenoConfig

Source§

fn clone(&self) -> NewRenoConfig

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 NewRenoConfig

Source§

fn build(self: Arc<Self>, now: Instant, current_mtu: u16) -> Box<dyn Controller>

Construct a fresh Controller
Source§

impl Debug for NewRenoConfig

Source§

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

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

impl Default for NewRenoConfig

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/congestion/trait.Controller.html b/pr/451/docs/noq_proto/congestion/trait.Controller.html index 93cfaa4b1..7a8d1b47a 100644 --- a/pr/451/docs/noq_proto/congestion/trait.Controller.html +++ b/pr/451/docs/noq_proto/congestion/trait.Controller.html @@ -1,8 +1,8 @@ -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 {
-    // Required methods
+
Show 14 methods // Required methods fn on_congestion_event( &mut self, now: Instant, @@ -10,6 +10,7 @@ is_persistent_congestion: bool, is_ecn: bool, lost_bytes: u64, + largest_lost_pn: u64, ); fn on_mtu_update(&mut self, new_mtu: u16); fn window(&self) -> u64; @@ -18,12 +19,14 @@ fn into_any(self: Box<Self>) -> Box<dyn Any>; // Provided methods - fn on_sent(&mut self, now: Instant, bytes: u64, last_packet_number: u64) { ... } + fn on_sent(&mut self, now: Instant, bytes: u64, largest_pn: u64) { ... } + fn on_packet_sent(&mut self, now: Instant, bytes: u16, pn: u64) { ... } fn on_ack( &mut self, now: Instant, sent: Instant, bytes: u64, + pn: u64, app_limited: bool, rtt: &RttEstimator, ) { ... } @@ -34,46 +37,71 @@ app_limited: bool, largest_packet_num_acked: Option<u64>, ) { ... } + fn on_packet_lost(&mut self, lost_bytes: u16, pn: u64, now: Instant) { ... } fn on_spurious_congestion_event(&mut self) { ... } + fn on_ack_frequency_update( + &mut self, + ack_eliciting_threshold: u64, + requested_max_ack_delay: Duration, + ) { ... } fn metrics(&self) -> ControllerMetrics { ... } -}
Expand description

Common interface for different congestion controllers

-

Required Methods§

Expand description

Common interface for different congestion controllers

+

Required Methods§

Source

fn on_congestion_event( &mut self, now: Instant, sent: Instant, is_persistent_congestion: bool, is_ecn: bool, lost_bytes: u64, + largest_lost_pn: u64, )

Packets were deemed lost or marked congested

in_persistent_congestion indicates whether all packets sent within the persistent congestion threshold period ending when the most recent packet in this batch was sent were lost. -lost_bytes indicates how many bytes were lost. This value will be 0 for ECN triggers.

-
Source

fn on_mtu_update(&mut self, new_mtu: u16)

The known MTU for the current network path has been updated

-
Source

fn window(&self) -> u64

Number of ack-eliciting bytes that may be in flight

-
Source

fn clone_box(&self) -> Box<dyn Controller>

Duplicate the controller’s state

-
Source

fn initial_window(&self) -> u64

Initial congestion window

-
Source

fn into_any(self: Box<Self>) -> Box<dyn Any>

Returns Self for use in down-casting to extract implementation details

-

Provided Methods§

Source

fn on_sent(&mut self, now: Instant, bytes: u64, last_packet_number: u64)

One or more packets were just sent

-
Source

fn on_ack( +lost_bytes indicates how many bytes were lost. This value will be 0 for ECN triggers. +largest_lost_pn indicates the packet number of the packet with the highest packet number +in the congestion event.

+

Source

fn on_mtu_update(&mut self, new_mtu: u16)

The known MTU for the current network path has been updated

+
Source

fn window(&self) -> u64

Number of ack-eliciting bytes that may be in flight

+
Source

fn clone_box(&self) -> Box<dyn Controller>

Duplicate the controller’s state

+
Source

fn initial_window(&self) -> u64

Initial congestion window

+
Source

fn into_any(self: Box<Self>) -> Box<dyn Any>

Returns Self for use in down-casting to extract implementation details

+

Provided Methods§

Source

fn on_sent(&mut self, now: Instant, bytes: u64, largest_pn: u64)

One or more packets were just sent

+
Source

fn on_packet_sent(&mut self, now: Instant, bytes: u16, pn: u64)

One packet was just sent

+
Source

fn on_ack( &mut self, now: Instant, sent: Instant, bytes: u64, + pn: u64, app_limited: bool, rtt: &RttEstimator, )

Packet deliveries were confirmed

app_limited indicates whether the connection was blocked on outgoing application data prior to receiving these acknowledgements.

-
Source

fn on_end_acks( +

Source

fn on_end_acks( &mut self, now: Instant, in_flight: u64, app_limited: bool, largest_packet_num_acked: Option<u64>, )

Packets are acked in batches, all with the same now argument. This indicates one of those batches has completed.

-
Source

fn on_spurious_congestion_event(&mut self)

Packets were incorrectly deemed lost

+
Source

fn on_packet_lost(&mut self, lost_bytes: u16, pn: u64, now: Instant)

One packet was just lost

+
Source

fn on_spurious_congestion_event(&mut self)

Packets were incorrectly deemed lost

This function is called when all packets that were deemed lost (for instance because of packet reordering) are acknowledged after the congestion event was raised.

-
Source

fn metrics(&self) -> ControllerMetrics

Retrieve implementation-specific metrics used to populate qlog traces when they are enabled

-

Implementors§

\ No newline at end of file +

Source

fn on_ack_frequency_update( + &mut self, + ack_eliciting_threshold: u64, + requested_max_ack_delay: Duration, +)

The peer’s ACK-frequency parameters have changed

+

ack_eliciting_threshold is the number of ack-eliciting packets the peer may receive +before being required to send an immediate ACK (per the QUIC ACK frequency extension). +requested_max_ack_delay is the maximum delay we asked the peer to wait before sending +an ACK when the threshold hasn’t been reached.

+

Controllers can use this to refine estimates that depend on peer ACK behavior (e.g. +BBR’s offload budget).

+
Source

fn metrics(&self) -> ControllerMetrics

Retrieve implementation-specific metrics used to populate qlog traces when they are enabled +This is also used to alter the pacing of the connection with +pacing_rate and send_quantum

+

Implementors§

\ No newline at end of file diff --git a/pr/451/docs/noq_proto/congestion/trait.ControllerFactory.html b/pr/451/docs/noq_proto/congestion/trait.ControllerFactory.html index aeec83b69..f4defe94a 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>,
@@ -6,5 +6,5 @@
         current_mtu: u16,
     ) -> Box<dyn Controller>;
 }
Expand description

Constructs controllers on demand

-

Required Methods§

Source

fn build(self: Arc<Self>, now: Instant, current_mtu: u16) -> Box<dyn Controller>

Construct a fresh Controller

-

Implementors§

\ No newline at end of file +

Required Methods§

Source

fn build(self: Arc<Self>, now: Instant, current_mtu: u16) -> Box<dyn Controller>

Construct a fresh Controller

+

Implementors§

\ No newline at end of file diff --git a/pr/451/docs/noq_proto/connection/streams/send/struct.Written.html b/pr/451/docs/noq_proto/connection/streams/send/struct.Written.html deleted file mode 100644 index 8e7619f45..000000000 --- a/pr/451/docs/noq_proto/connection/streams/send/struct.Written.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - Redirection - - -

Redirecting to ../../../../noq_proto/struct.Written.html...

- - - \ No newline at end of file diff --git a/pr/451/docs/noq_proto/constant.DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED.html b/pr/451/docs/noq_proto/constant.DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED.html deleted file mode 100644 index a1be1f1b1..000000000 --- a/pr/451/docs/noq_proto/constant.DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED.html +++ /dev/null @@ -1,3 +0,0 @@ -DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED in noq_proto - Rust

DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED

Constant DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED 

Source
pub const DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED: NonZeroU32;
Expand description

When multipath is required and has not been explicitly enabled, this value will be used for

-

TransportConfig::max_concurrent_multipath_paths.

-
\ No newline at end of file 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 cc71da09d..056e4ed87 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 76a182cbf..72a4880e1 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 eec669095..04bd994c9 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 d655ca7fe..c684d0969 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 c9eebbbbd..1b2e811e7 100644 --- a/pr/451/docs/noq_proto/crypto/rustls/struct.HandshakeData.html +++ b/pr/451/docs/noq_proto/crypto/rustls/struct.HandshakeData.html @@ -1,13 +1,14 @@ -HandshakeData in noq_proto::crypto::rustls - Rust

HandshakeData

Struct HandshakeData 

Source
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: NamedGroup, + pub negotiated_key_exchange_group: Option<NamedGroup>, }
Expand description

Authentication data for (rustls) TLS session

-

Fields§

§protocol: Option<Vec<u8>>

The negotiated application protocol, if ALPN is in use

+

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.
§protocol: Option<Vec<u8>>

The negotiated application protocol, if ALPN is in use

Guaranteed to be set if a nonempty list of protocols was specified for this connection.

§server_name: Option<String>

The server name specified by the client, if any

Always None for outgoing connections

-
§negotiated_key_exchange_group: NamedGroup

The key exchange group negotiated with the peer

+
§negotiated_key_exchange_group: Option<NamedGroup>

The key exchange group negotiated with the peer. Useful for logging and metrics.

+

None until the handshake has completed.

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/rustls/struct.NoInitialCipherSuite.html b/pr/451/docs/noq_proto/crypto/rustls/struct.NoInitialCipherSuite.html index 5c7aed0ca..9d63550e6 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 4db04004c..f1e661327 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 ea911b360..36765b5e8 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 72a0abbc2..60bb11931 100644 --- a/pr/451/docs/noq_proto/crypto/rustls/struct.TlsSession.html +++ b/pr/451/docs/noq_proto/crypto/rustls/struct.TlsSession.html @@ -1,13 +1,13 @@ -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( +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, -) -> Result<Option<TransportParameters>, TransportError>

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( +) -> Result<Option<TransportParameters>, TransportError>

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, orig_dst_cid: ConnectionId, header: &[u8], payload: &[u8], -) -> bool

Verify the integrity of a retry packet Read more
Source§

fn export_keying_material( +) -> bool

Verify the integrity of a retry packet Read more
Source§

fn export_keying_material( &self, output: &mut [u8], label: &[u8], diff --git a/pr/451/docs/noq_proto/crypto/struct.CryptoError.html b/pr/451/docs/noq_proto/crypto/struct.CryptoError.html index 48bd651c6..210ea40f9 100644 --- a/pr/451/docs/noq_proto/crypto/struct.CryptoError.html +++ b/pr/451/docs/noq_proto/crypto/struct.CryptoError.html @@ -1,8 +1,8 @@ -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
Source§

impl From<Unspecified> for CryptoError

Source§

fn from(_: Unspecified) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +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 - 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.

+ 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.

§

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/crypto/struct.ExportKeyingMaterialError.html b/pr/451/docs/noq_proto/crypto/struct.ExportKeyingMaterialError.html index ff278aeb1..d54cca3e8 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 a356c5c14..8c22779ba 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 b92f7b651..137d8a10f 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 bfcecfa73..5e1f49729 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 9a0f417b2..2ee668b52 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 d5c6e9370..0aebdede2 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 ccdd53c55..0e3bdd651 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]);
@@ -7,4 +7,4 @@
 

Required Methods§

Source

fn decrypt(&self, pn_offset: usize, packet: &mut [u8])

Decrypt the given packet’s header

Source

fn encrypt(&self, pn_offset: usize, packet: &mut [u8])

Encrypt the given packet’s header

Source

fn sample_size(&self) -> usize

The sample size used for this key’s algorithm

-

Implementations on Foreign Types§

Source§

impl HeaderKey for Box<dyn HeaderProtectionKey>

Source§

fn decrypt(&self, pn_offset: usize, packet: &mut [u8])

Source§

fn encrypt(&self, pn_offset: usize, packet: &mut [u8])

Source§

fn sample_size(&self) -> usize

Implementors§

\ No newline at end of file +

Implementations on Foreign Types§

Source§

impl HeaderKey for Box<dyn HeaderProtectionKey>

Source§

fn decrypt(&self, pn_offset: usize, packet: &mut [u8])

Source§

fn encrypt(&self, pn_offset: usize, packet: &mut [u8])

Source§

fn sample_size(&self) -> usize

Implementors§

\ No newline at end of file diff --git a/pr/451/docs/noq_proto/crypto/trait.HmacKey.html b/pr/451/docs/noq_proto/crypto/trait.HmacKey.html index 058355ee8..5fae9f16e 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 298552117..329f611a5 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 0643156e9..1d910fe9d 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 81418100f..cbef4f7e0 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 {
@@ -70,4 +70,4 @@ from the S
 separation.

This function will fail, returning ExportKeyingMaterialError, if the requested output length is too large.

-

Implementors§

\ No newline at end of file +

Implementors§

\ No newline at end of file diff --git a/pr/451/docs/noq_proto/enum.ClosePathError.html b/pr/451/docs/noq_proto/enum.ClosePathError.html index 9ec7f612d..2e7167172 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 0b02ad618..3f8b299fe 100644 --- a/pr/451/docs/noq_proto/enum.ConfigError.html +++ b/pr/451/docs/noq_proto/enum.ConfigError.html @@ -1,9 +1,9 @@ -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

-

Trait Implementations§

Source§

impl Clone for ConfigError

Source§

fn clone(&self) -> ConfigError

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 ConfigError

Source§

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

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

impl Display for ConfigError

Source§

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

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

impl Error for ConfigError

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<TryFromIntError> for ConfigError

Source§

fn from(_: TryFromIntError) -> Self

Converts to this type from the input type.
Source§

impl From<VarIntBoundsExceeded> for ConfigError

Source§

fn from(_: VarIntBoundsExceeded) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for ConfigError

Source§

fn eq(&self, other: &ConfigError) -> 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 ConfigError

Source§

impl StructuralPartialEq for ConfigError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for ConfigError

Source§

fn clone(&self) -> ConfigError

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 ConfigError

Source§

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

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

impl Display for ConfigError

Source§

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

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

impl Error for ConfigError

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<TryFromIntError> for ConfigError

Source§

fn from(_: TryFromIntError) -> Self

Converts to this type from the input type.
Source§

impl From<VarIntBoundsExceeded> for ConfigError

Source§

fn from(_: VarIntBoundsExceeded) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for ConfigError

Source§

fn eq(&self, other: &ConfigError) -> 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 ConfigError

Source§

impl StructuralPartialEq for ConfigError

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.ConnectError.html b/pr/451/docs/noq_proto/enum.ConnectError.html index 65e26e950..29c5d6a71 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 13cfac397..c1c3a8edc 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 423af451e..e741fb6f0 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 b3339fd51..6e6be725a 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 04520d921..92b339977 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, diff --git a/pr/451/docs/noq_proto/enum.Event.html b/pr/451/docs/noq_proto/enum.Event.html index a353600c7..d94bb13ab 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,
@@ -15,14 +15,17 @@
 
§

Connected

The connection was successfully established

§

HandshakeConfirmed

The TLS handshake was confirmed

§

ConnectionLost

The connection was lost

-

Emitted if the peer closes the connection or an error is encountered.

+

Emitted when the connection is closed due to an error, a timeout, or the peer closing it. +This is not emitted when the local application closes the connection via +Connection::close(). In that case, pending operations will +fail with ConnectionError::LocallyClosed.

Fields

§reason: ConnectionError

Reason that the connection was closed

§

Stream(StreamEvent)

Stream events

§

DatagramReceived

One or more application datagrams have been received

§

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 a6b9d3a05..813eaad94 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 553901442..047a105c4 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 19814fb00..77ee4f3ff 100644 --- a/pr/451/docs/noq_proto/enum.LongType.html +++ b/pr/451/docs/noq_proto/enum.LongType.html @@ -1,11 +1,11 @@ -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

Variants§

§

Handshake

Handshake packet

§

ZeroRtt

0-RTT packet

-

Trait Implementations§

Source§

impl Clone for LongType

Source§

fn clone(&self) -> LongType

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 LongType

Source§

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

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

impl PartialEq for LongType

Source§

fn eq(&self, other: &LongType) -> 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 LongType

Source§

impl Eq for LongType

Source§

impl StructuralPartialEq for LongType

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for LongType

Source§

fn clone(&self) -> LongType

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 LongType

Source§

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

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

impl PartialEq for LongType

Source§

fn eq(&self, other: &LongType) -> 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 LongType

Source§

impl Eq for LongType

Source§

impl StructuralPartialEq for LongType

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.MaybeFrame.html b/pr/451/docs/noq_proto/enum.MaybeFrame.html index a87a0ba77..5951bf116 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),
@@ -6,8 +6,8 @@
 

Variants§

§

None

Not attributed to any particular FrameType.

§

Unknown(u64)

Attributed to some frame type this implementation does not recognize.

§

Known(FrameType)

Attributed to a specific FrameType, never FrameType::Padding.

-

Trait Implementations§

Source§

impl Clone for MaybeFrame

Source§

fn clone(&self) -> MaybeFrame

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 MaybeFrame

Source§

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

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

impl Decodable for MaybeFrame

Source§

fn decode<B: Buf>(buf: &mut B) -> Result<Self>

Decode a Self from the provided buffer, if the buffer is large enough
Source§

impl Display for MaybeFrame

Source§

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

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

impl Encodable for MaybeFrame

Source§

fn encode<B: BufMut>(&self, buf: &mut B)

Append the encoding of self to the provided buffer.
Source§

impl PartialEq for MaybeFrame

Source§

fn eq(&self, other: &MaybeFrame) -> 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 MaybeFrame

Source§

impl Eq for MaybeFrame

Source§

impl StructuralPartialEq for MaybeFrame

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for MaybeFrame

Source§

fn clone(&self) -> MaybeFrame

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 MaybeFrame

Source§

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

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

impl Decodable for MaybeFrame

Source§

fn decode<B: Buf>(buf: &mut B) -> Result<Self>

Decode a Self from the provided buffer, if the buffer is large enough
Source§

impl Display for MaybeFrame

Source§

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

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

impl Encodable for MaybeFrame

Source§

fn encode<B: BufMut>(&self, buf: &mut B)

Append the encoding of self to the provided buffer.
Source§

impl PartialEq for MaybeFrame

Source§

fn eq(&self, other: &MaybeFrame) -> 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 MaybeFrame

Source§

impl Eq for MaybeFrame

Source§

impl StructuralPartialEq for MaybeFrame

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.PacketDecodeError.html b/pr/451/docs/noq_proto/enum.PacketDecodeError.html index 9724d85c6..35066327b 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,
@@ -11,16 +11,16 @@
 
§dst_cid: ConnectionId

Destination Connection ID

§version: u32

The version that was unsupported

§

InvalidHeader(&'static str)

The packet header is invalid

-

Trait Implementations§

Source§

impl Clone for PacketDecodeError

Source§

fn clone(&self) -> PacketDecodeError

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 PacketDecodeError

Source§

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

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

impl Display for PacketDecodeError

Source§

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

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

impl Error for PacketDecodeError

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 PacketDecodeError

Source§

fn from(_: UnexpectedEnd) -> Self

Converts to this type from the input type.
Source§

impl Hash for PacketDecodeError

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 PacketDecodeError

Source§

fn clone(&self) -> PacketDecodeError

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 PacketDecodeError

Source§

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

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

impl Display for PacketDecodeError

Source§

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

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

impl Error for PacketDecodeError

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 PacketDecodeError

Source§

fn from(_: UnexpectedEnd) -> Self

Converts to this type from the input type.
Source§

impl Hash for PacketDecodeError

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 PacketDecodeError

Source§

fn cmp(&self, other: &PacketDecodeError) -> 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 PacketDecodeError

Source§

fn cmp(&self, other: &PacketDecodeError) -> 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 PacketDecodeError

Source§

fn eq(&self, other: &PacketDecodeError) -> 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 PacketDecodeError

Source§

fn partial_cmp(&self, other: &PacketDecodeError) -> 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 PacketDecodeError

Source§

fn eq(&self, other: &PacketDecodeError) -> 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 PacketDecodeError

Source§

fn partial_cmp(&self, other: &PacketDecodeError) -> 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 Eq for PacketDecodeError

Source§

impl StructuralPartialEq for PacketDecodeError

Auto Trait Implementations§

Blanket Implementations§

Source§

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

Source§

impl Eq for PacketDecodeError

Source§

impl StructuralPartialEq for PacketDecodeError

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.PathAbandonReason.html b/pr/451/docs/noq_proto/enum.PathAbandonReason.html index 26d8d24f2..af7ea07a1 100644 --- a/pr/451/docs/noq_proto/enum.PathAbandonReason.html +++ b/pr/451/docs/noq_proto/enum.PathAbandonReason.html @@ -1,4 +1,4 @@ -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,
     },
@@ -18,8 +18,8 @@
 
§

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 789a6b297..759f245a2 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 a1c355787..7b0341c06 100644 --- a/pr/451/docs/noq_proto/enum.PathEvent.html +++ b/pr/451/docs/noq_proto/enum.PathEvent.html @@ -1,5 +1,5 @@ -PathEvent in noq_proto - Rust

PathEvent

Enum PathEvent 

Source
pub enum PathEvent {
-    Opened {
+PathEvent in noq_proto - Rust

PathEvent

Enum PathEvent 

Source
pub enum PathEvent {
+    Established {
         id: PathId,
     },
     Abandoned {
@@ -19,8 +19,8 @@
         addr: SocketAddr,
     },
 }
Expand description

Application events about paths

-

Variants§

§

Opened

A new path has been opened

-

Fields

§id: PathId

Which path is now open

+

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.

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

Fields

§id: PathId

With path was abandoned.

@@ -39,8 +39,8 @@ changes the status.

Fields

§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 6a4cdc499..38ab81b67 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 899a9947b..a1b9dbb82 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,
@@ -40,14 +40,14 @@
 

Fields

§random: u8

Random value

§dst_cid: ConnectionId

Destination Connection ID

§src_cid: ConnectionId

Source Connection ID

-

Implementations§

Source§

impl ProtectedHeader

Source

pub fn dst_cid(&self) -> ConnectionId

The destination Connection ID of the packet

-
Source

pub fn decode( +

Implementations§

Source§

impl ProtectedHeader

Source

pub fn dst_cid(&self) -> ConnectionId

The destination Connection ID of the packet

+
Source

pub fn decode( buf: &mut Cursor<BytesMut>, cid_parser: &(impl ConnectionIdParser + ?Sized), supported_versions: &[u32], grease_quic_bit: bool, ) -> Result<Self, PacketDecodeError>

Decode a plain header from given buffer, with given ConnectionIdParser.

-

Trait Implementations§

Source§

impl Clone for ProtectedHeader

Source§

fn clone(&self) -> ProtectedHeader

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 ProtectedHeader

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 Clone for ProtectedHeader

Source§

fn clone(&self) -> ProtectedHeader

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 ProtectedHeader

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_proto/enum.ReadError.html b/pr/451/docs/noq_proto/enum.ReadError.html index 2c32b29dc..6d5d42590 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 cfb2f8ba6..f90d4ba0c 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 6bcb2f4d9..c29ae2a77 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 dfe74a9c1..577cc27a3 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 100415f13..60e140589 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 82cb27f8a..016fbe344 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 805dbaf39..daf5f6ffb 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 6371a69e3..b5abd6492 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.

@@ -10,5 +10,5 @@ related Connection. Connection types contain the bulk managing a single connection and all the related state (such as streams).

Re-exports§

pub use rustls;

Modules§

bench_exports
Exports for benchmarks
coding
Coding related traits.
congestion
Logic for controlling the rate at which data is sent
crypto
Traits and implementations for the QUIC cryptography protocol
n0_nat_traversal
n0’s (https://n0.computer) NAT Traversal protocol implementation.
transport_parameters
QUIC connection transport parameters

Structs§

AcceptError
Error type for attempting to accept an Incoming
AckFrequencyConfig
Parameters for controlling the peer’s acknowledgement frequency
ApplicationClose
Reason given by an application for closing the connection
BloomTokenLog
Bloom filter-based TokenLog
Chunk
A chunk of data from the receive stream
Chunks
Chunks returned from RecvStream::read().
ClientConfig
Configuration for outgoing connections
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
Connection
Protocol state and logic for a single QUIC connection
ConnectionClose
Reason given by the transport for closing the connection
ConnectionEvent
Events sent from an Endpoint to a Connection
ConnectionHandle
Internal identifier for a Connection currently associated with an endpoint
ConnectionId
Protocol-level identifier for a connection.
ConnectionStats
Connection statistics.
Datagram
An unreliable datagram
DatagramInfo
Datagrams
API to control datagram traffic
DecryptedInitial
Decrypted payload of a QUIC Initial packet
Endpoint
The main entry point to the library
EndpointConfig
Global configuration for the endpoint, affecting all connections
EndpointEvent
Events sent from a Connection to an Endpoint
FixedLengthConnectionIdParser
A ConnectionIdParser implementation that assumes the connection ID is of fixed length
FourTuple
Identifies a network path by the combination of remote and local addresses
FrameStats
Number of frames transmitted or received of each frame type.
HashedConnectionIdGenerator
Generates 8-byte connection IDs that can be efficiently validated
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.
IncomingAlpns
Iterator over ALPN protocol names from a TLS ClientHello
InvalidCid
The connection ID was not recognized by the ConnectionIdGenerator
InvalidFrameId
Encounter a frame ID that was not valid.
MtuDiscoveryConfig
Parameters governing MTU discovery.
MultipathNotNegotiated
Error when the multipath extension was not negotiated, but attempted to be used.
NoneTokenLog
Null implementation of TokenLog, which never accepts tokens
NoneTokenStore
Null implementation of TokenStore, which does not store any tokens
PartialDecode
Decodes a QUIC packet’s invariant header
PathId
Id representing different paths when using multipath extension
PathStats
Statistics related to a transmission path.
ProtectedInitialHeader
Header of an Initial packet, before decryption
QlogConfig
Configuration for qlog trace logging.
QlogFileFactory
Enables writing qlog traces to a directory.
RandomConnectionIdGenerator
Generates purely random connection IDs of a specified length
RecvStream
Access to streams
RetryError
Error for attempting to retry an Incoming which already bears a token from a previous retry
RttEstimator
RTT estimation for a particular network path
SendStream
Access to streams
ServerConfig
Parameters governing incoming connections
ShouldTransmit
Indicates whether a frame needs to be transmitted
StdSystemTime
Default implementation of TimeSource
StreamId
Identifier for a stream within a particular connection
StreamInfo
Streams
Access to streams
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
Transmit
An outgoing packet
TransportConfig
Parameters governing the core QUIC state machine
TransportError
Transport-level errors occur when a peer violates the protocol specification
TransportErrorCode
Transport-level error code
UdpStats
Statistics about UDP datagrams transmitted or received on a connection.
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
Written
Indicates how many bytes and chunks had been transferred in a write operation

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
DatagramEvent
Event resulting from processing a single datagram
Dir
Whether a stream communicates data in both directions or only from the initiator
EcnCodepoint
Explicit congestion notification codepoint
Event
Events of interest to the application
FinishError
Reasons why attempting to finish a stream might fail
FrameType
LongType
Long packet types with uniform header structure
MaybeFrame
The type used to refer to FrameTypes in closing and transport errors.
PacketDecodeError
Packet decode error
PathAbandonReason
Reason for why a path was abandoned.
PathError
Errors that might trigger a path being closed
PathEvent
Application events about paths
PathStatus
The QUIC-MULTIPATH path status
ProtectedHeader
Plain packet header
ReadError
Errors triggered when reading from a recv stream
ReadableError
Errors triggered when opening a recv stream for reading
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
StreamEvent
Application events about streams
WriteError
Errors triggered while writing to a send stream

Constants§

DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED
When multipath is required and has not been explicitly enabled, this value will be used for
DEFAULT_SUPPORTED_VERSIONS
The QUIC protocol version implemented.

Traits§

ConnectionIdGenerator
Generates connection IDs for incoming connections
ConnectionIdParser
Parse connection id in short header packet
NetworkChangeHint
Hints when the caller identifies a network change.
QlogFactory
Constructs a QlogConfig for individual connections.
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 +limited number of server names, in-memory
TokenReuseError
Error for when a validation token may have been reused
Transmit
An outgoing packet
TransportConfig
Parameters governing the core QUIC state machine
TransportError
Transport-level errors occur when a peer violates the protocol specification
TransportErrorCode
Transport-level error code
UdpStats
Statistics about UDP datagrams transmitted or received on a connection.
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

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
DatagramEvent
Event resulting from processing a single datagram
Dir
Whether a stream communicates data in both directions or only from the initiator
EcnCodepoint
Explicit congestion notification codepoint
Event
Events of interest to the application
FinishError
Reasons why attempting to finish a stream might fail
FrameType
LongType
Long packet types with uniform header structure
MaybeFrame
The type used to refer to FrameTypes in closing and transport errors.
PacketDecodeError
Packet decode error
PathAbandonReason
Reason for why a path was abandoned.
PathError
Errors that might trigger a path being closed
PathEvent
Application events about paths
PathStatus
The QUIC-MULTIPATH path status
ProtectedHeader
Plain packet header
ReadError
Errors triggered when reading from a recv stream
ReadableError
Errors triggered when opening a recv stream for reading
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
StreamEvent
Application events about streams
WriteError
Errors triggered while writing to a send stream

Constants§

DEFAULT_SUPPORTED_VERSIONS
The QUIC protocol version implemented.

Traits§

ConnectionIdGenerator
Generates connection IDs for incoming connections
ConnectionIdParser
Parse connection id in short header packet
NetworkChangeHint
Hints when the caller identifies a network change.
QlogFactory
Constructs a QlogConfig for individual connections.
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
\ No newline at end of file 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 31266816e..2a505f88a 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,
@@ -12,7 +12,7 @@
 
§

NotEnoughAddresses

Not enough addresses to complete the operation

§

Multipath(PathError)

Nat traversal attempt failed due to a multipath error

§

Closed

Attempted to initiate NAT traversal on a closed, or closing connection.

-

Trait Implementations§

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, __formatter: &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

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 +

Trait Implementations§

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, __formatter: &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

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 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/n0_nat_traversal/enum.Event.html b/pr/451/docs/noq_proto/n0_nat_traversal/enum.Event.html index 7ffde4d86..0ec00c454 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,10 +1,10 @@ -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.

Variants§

§

AddressAdded(SocketAddr)

An ADD_ADDRESS frame was received.

§

AddressRemoved(SocketAddr)

A REMOVE_ADDRESS frame was received.

-

Trait Implementations§

Source§

impl Clone for Event

Source§

fn clone(&self) -> Event

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 Event

Source§

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

Formats the value using the given formatter. Read more

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 Clone for Event

Source§

fn clone(&self) -> Event

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 Event

Source§

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

Formats the value using the given formatter. Read more

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> CloneToUninit for T
where 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 741d131a5..0ab258d9b 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/sidebar-items.js b/pr/451/docs/noq_proto/sidebar-items.js index e9a741d67..5173b4a61 100644 --- a/pr/451/docs/noq_proto/sidebar-items.js +++ b/pr/451/docs/noq_proto/sidebar-items.js @@ -1 +1 @@ -window.SIDEBAR_ITEMS = {"constant":["DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED","DEFAULT_SUPPORTED_VERSIONS"],"enum":["ClosePathError","ConfigError","ConnectError","ConnectionError","DatagramEvent","Dir","EcnCodepoint","Event","FinishError","FrameType","LongType","MaybeFrame","PacketDecodeError","PathAbandonReason","PathError","PathEvent","PathStatus","ProtectedHeader","ReadError","ReadableError","SendDatagramError","SetPathStatusError","Side","StreamEvent","WriteError"],"mod":["bench_exports","coding","congestion","crypto","n0_nat_traversal","transport_parameters"],"struct":["AcceptError","AckFrequencyConfig","ApplicationClose","BloomTokenLog","Chunk","Chunks","ClientConfig","ClosedPath","ClosedStream","Connection","ConnectionClose","ConnectionEvent","ConnectionHandle","ConnectionId","ConnectionStats","Datagram","DatagramInfo","Datagrams","DecryptedInitial","Endpoint","EndpointConfig","EndpointEvent","FixedLengthConnectionIdParser","FourTuple","FrameStats","HashedConnectionIdGenerator","IdleTimeout","Incoming","IncomingAlpns","InvalidCid","InvalidFrameId","MtuDiscoveryConfig","MultipathNotNegotiated","NoneTokenLog","NoneTokenStore","PartialDecode","PathId","PathStats","ProtectedInitialHeader","QlogConfig","QlogFileFactory","RandomConnectionIdGenerator","RecvStream","RetryError","RttEstimator","SendStream","ServerConfig","ShouldTransmit","StdSystemTime","StreamId","StreamInfo","Streams","TokenMemoryCache","TokenReuseError","Transmit","TransportConfig","TransportError","TransportErrorCode","UdpStats","ValidationTokenConfig","VarInt","VarIntBoundsExceeded","Written"],"trait":["ConnectionIdGenerator","ConnectionIdParser","NetworkChangeHint","QlogFactory","TimeSource","TokenLog","TokenStore"]}; \ No newline at end of file +window.SIDEBAR_ITEMS = {"constant":["DEFAULT_SUPPORTED_VERSIONS"],"enum":["ClosePathError","ConfigError","ConnectError","ConnectionError","DatagramEvent","Dir","EcnCodepoint","Event","FinishError","FrameType","LongType","MaybeFrame","PacketDecodeError","PathAbandonReason","PathError","PathEvent","PathStatus","ProtectedHeader","ReadError","ReadableError","SendDatagramError","SetPathStatusError","Side","StreamEvent","WriteError"],"mod":["bench_exports","coding","congestion","crypto","n0_nat_traversal","transport_parameters"],"struct":["AcceptError","AckFrequencyConfig","ApplicationClose","BloomTokenLog","Chunk","Chunks","ClientConfig","ClosedPath","ClosedStream","Connection","ConnectionClose","ConnectionEvent","ConnectionHandle","ConnectionId","ConnectionStats","Datagram","DatagramInfo","Datagrams","DecryptedInitial","Endpoint","EndpointConfig","EndpointEvent","FixedLengthConnectionIdParser","FourTuple","FrameStats","HashedConnectionIdGenerator","IdleTimeout","Incoming","IncomingAlpns","InvalidCid","InvalidFrameId","MtuDiscoveryConfig","MultipathNotNegotiated","NoneTokenLog","NoneTokenStore","PartialDecode","PathId","PathStats","ProtectedInitialHeader","QlogConfig","QlogFileFactory","RandomConnectionIdGenerator","RecvStream","RetryError","RttEstimator","SendStream","ServerConfig","ShouldTransmit","StdSystemTime","StreamId","StreamInfo","Streams","TokenMemoryCache","TokenReuseError","Transmit","TransportConfig","TransportError","TransportErrorCode","UdpStats","ValidationTokenConfig","VarInt","VarIntBoundsExceeded"],"trait":["ConnectionIdGenerator","ConnectionIdParser","NetworkChangeHint","QlogFactory","TimeSource","TokenLog","TokenStore"]}; \ 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 3fb98285b..308d5fcaf 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 487a28455..b16a2f361 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 68f9dba6a..2d38fcb75 100644 --- a/pr/451/docs/noq_proto/struct.ApplicationClose.html +++ b/pr/451/docs/noq_proto/struct.ApplicationClose.html @@ -1,11 +1,11 @@ -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

Fields§

§error_code: VarInt

Application-specific reason code

§reason: Bytes

Human-readable reason for the close

-

Trait Implementations§

Source§

impl Clone for ApplicationClose

Source§

fn clone(&self) -> ApplicationClose

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 ApplicationClose

Source§

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

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

impl Display for ApplicationClose

Source§

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

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

impl PartialEq for ApplicationClose

Source§

fn eq(&self, other: &ApplicationClose) -> 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 ApplicationClose

Source§

impl StructuralPartialEq for ApplicationClose

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for ApplicationClose

Source§

fn clone(&self) -> ApplicationClose

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 ApplicationClose

Source§

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

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

impl Display for ApplicationClose

Source§

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

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

impl PartialEq for ApplicationClose

Source§

fn eq(&self, other: &ApplicationClose) -> 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 ApplicationClose

Source§

impl StructuralPartialEq for ApplicationClose

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.BloomTokenLog.html b/pr/451/docs/noq_proto/struct.BloomTokenLog.html index 0d2181d6f..db432addb 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 8f2868250..7eda38bc5 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 6e8cc0db0..ee465f92f 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 210e2f6aa..b23dbf153 100644 --- a/pr/451/docs/noq_proto/struct.ClientConfig.html +++ b/pr/451/docs/noq_proto/struct.ClientConfig.html @@ -1,7 +1,7 @@ -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( +

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( &mut self, initial_dst_cid_provider: Arc<dyn Fn() -> ConnectionId + Send + Sync>, ) -> &mut Self

Configure how to populate the destination CID of the initial packet when attempting to @@ -10,15 +10,15 @@ establish a new connection

a good reason, you do not need to change it.

When prefer to override the default, please note that the generated connection ID MUST be at least 8 bytes long and unpredictable, as per section 7.2 of RFC 9000.

-
Source

pub fn transport_config(&mut self, transport: Arc<TransportConfig>) -> &mut Self

Set a custom TransportConfig

-
Source

pub fn token_store(&mut self, store: Arc<dyn TokenStore>) -> &mut Self

Set a custom TokenStore

+
Source

pub fn transport_config(&mut self, transport: Arc<TransportConfig>) -> &mut Self

Set a custom TransportConfig

+
Source

pub fn token_store(&mut self, store: Arc<dyn TokenStore>) -> &mut Self

Set a custom TokenStore

Defaults to TokenMemoryCache, which is suitable for most internet applications.

-
Source

pub fn version(&mut self, version: u32) -> &mut Self

Set the QUIC version to use

-
Source§

impl ClientConfig

Source

pub fn try_with_platform_verifier() -> Result<Self, Error>

Create a client configuration that trusts the platform’s native roots

-
Source

pub fn with_root_certificates( +

Source

pub fn version(&mut self, version: u32) -> &mut Self

Set the QUIC version to use

+
Source§

impl ClientConfig

Source

pub fn try_with_platform_verifier() -> Result<Self, Error>

Create a client configuration that trusts the platform’s native roots

+
Source

pub fn with_root_certificates( roots: Arc<RootCertStore>, ) -> Result<Self, VerifierBuilderError>

Create a client configuration that trusts specified trust anchors

-

Trait Implementations§

Source§

impl Clone for ClientConfig

Source§

fn clone(&self) -> ClientConfig

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 ClientConfig

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 Clone for ClientConfig

Source§

fn clone(&self) -> ClientConfig

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 ClientConfig

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> CloneToUninit for T
where diff --git a/pr/451/docs/noq_proto/struct.ClosedPath.html b/pr/451/docs/noq_proto/struct.ClosedPath.html index a9094a8c3..efc287838 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 afc659bd3..a3e015f5a 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 508defef8..0f23fdf1a 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,17 +41,17 @@ 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, @@ -65,15 +65,16 @@ remote addresses.

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( +

Source

pub fn open_path( &mut self, network_path: FourTuple, initial_status: PathStatus, now: Instant, -) -> Result<PathId, PathError>

Opens a new path

-

Further errors might occur and they will be emitted in PathEvent::Abandoned events with this path id. -When the path is opened it will be reported as an PathEvent::Opened.

-
Source

pub fn close_path( +) -> Result<PathId, PathError>

Opens a new path.

+

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( &mut self, now: Instant, path_id: PathId, @@ -82,18 +83,18 @@ When the path is opened it will be reported as an 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, @@ -103,14 +104,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, @@ -124,60 +125,67 @@ 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 message respectively.

-
Source

pub fn is_closed(&self) -> bool

Whether the connection is closed

+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

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.

-

A ConnectionLost event is emitted with details when the connection becomes closed.

-
Source

pub fn is_drained(&self) -> bool

Whether there is no longer any need to keep the connection around

+

A ConnectionLost event is emitted with details when the +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

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, @@ -188,16 +196,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, @@ -212,36 +220,37 @@ 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

-

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.

-

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 +

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.

+

For all probes that succeed, if any, a new path will be opened on the successful +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 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 6e3808271..628b5a5c8 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,
@@ -6,8 +6,8 @@
 

Fields§

§error_code: TransportErrorCode

Class of error as encoded in the specification

§frame_type: MaybeFrame

Type of frame that caused the close

§reason: Bytes

Human-readable reason for the close

-

Trait Implementations§

Source§

impl Clone for ConnectionClose

Source§

fn clone(&self) -> ConnectionClose

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 ConnectionClose

Source§

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

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

impl Display for ConnectionClose

Source§

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

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

impl From<Error> for ConnectionClose

Source§

fn from(x: TransportError) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for ConnectionClose

Source§

fn eq(&self, other: &ConnectionClose) -> 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 ConnectionClose

Source§

impl StructuralPartialEq for ConnectionClose

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for ConnectionClose

Source§

fn clone(&self) -> ConnectionClose

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 ConnectionClose

Source§

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

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

impl Display for ConnectionClose

Source§

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

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

impl From<Error> for ConnectionClose

Source§

fn from(x: TransportError) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for ConnectionClose

Source§

fn eq(&self, other: &ConnectionClose) -> 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 ConnectionClose

Source§

impl StructuralPartialEq for ConnectionClose

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.ConnectionEvent.html b/pr/451/docs/noq_proto/struct.ConnectionEvent.html index ff7e41f0b..f64a215b7 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 2e6688189..b5f203192 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 b451b4db9..ae022ba89 100644 --- a/pr/451/docs/noq_proto/struct.ConnectionId.html +++ b/pr/451/docs/noq_proto/struct.ConnectionId.html @@ -1,4 +1,4 @@ -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

diff --git a/pr/451/docs/noq_proto/struct.ConnectionStats.html b/pr/451/docs/noq_proto/struct.ConnectionStats.html index 8e521c858..78f941775 100644 --- a/pr/451/docs/noq_proto/struct.ConnectionStats.html +++ b/pr/451/docs/noq_proto/struct.ConnectionStats.html @@ -1,16 +1,20 @@ -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, pub frame_rx: FrameStats, + pub lost_packets: u64, + pub lost_bytes: u64, }
Expand description

Connection statistics.

The fields here are a sum of the respective fields in the PathStats for all the paths that exist as well as all paths that previously existed.

-

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.
§udp_tx: UdpStats

Statistics about UDP datagrams transmitted on a connection.

-
§udp_rx: UdpStats

Statistics about UDP datagrams received on a connection.

-
§frame_tx: FrameStats

Statistics about frames transmitted on a connection.

-
§frame_rx: FrameStats

Statistics about frames received on a 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 +

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.
§udp_tx: UdpStats

Statistics about UDP datagrams transmitted on the connection.

+
§udp_rx: UdpStats

Statistics about UDP datagrams received on the connection.

+
§frame_tx: FrameStats

Statistics about frames transmitted on the connection.

+
§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 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 388a731be..632739b3a 100644 --- a/pr/451/docs/noq_proto/struct.Datagram.html +++ b/pr/451/docs/noq_proto/struct.Datagram.html @@ -1,8 +1,8 @@ -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

-

Trait Implementations§

Source§

impl Clone for Datagram

Source§

fn clone(&self) -> Datagram

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 Datagram

Source§

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

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

impl Display for Datagram

Source§

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

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

impl Encodable for Datagram

Source§

fn encode<B: BufMut>(&self, out: &mut B)

Append the encoding of self to the provided buffer.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for Datagram

Source§

fn clone(&self) -> Datagram

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 Datagram

Source§

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

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

impl Display for Datagram

Source§

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

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

impl Encodable for Datagram

Source§

fn encode<B: BufMut>(&self, out: &mut B)

Append the encoding of self to the provided 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 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.DatagramInfo.html b/pr/451/docs/noq_proto/struct.DatagramInfo.html index 5b86b8bf7..d3970a5d0 100644 --- a/pr/451/docs/noq_proto/struct.DatagramInfo.html +++ b/pr/451/docs/noq_proto/struct.DatagramInfo.html @@ -1,5 +1,5 @@ -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 +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 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.Datagrams.html b/pr/451/docs/noq_proto/struct.Datagrams.html index 0d5a8568f..858631338 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 d2b070a38..f5883e266 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 1c249427c..5918ae37f 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, @@ -31,28 +31,28 @@ achieved via e.g. the IPV6_DONTFRAG socket option.

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 46a340cec..48ef8058f 100644 --- a/pr/451/docs/noq_proto/struct.EndpointConfig.html +++ b/pr/451/docs/noq_proto/struct.EndpointConfig.html @@ -1,18 +1,18 @@ -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<F: Fn() -> Box<dyn ConnectionIdGenerator> + Send + Sync + 'static>( +

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( &mut self, - factory: F, + factory: Arc<dyn Fn() -> Box<dyn ConnectionIdGenerator> + Send + Sync>, ) -> &mut Self

Supply a custom connection ID generator factory

Called once by each Endpoint constructed from this configuration to obtain the CID generator which will be used to generate the CIDs used for incoming packets on all connections involving that Endpoint. A custom CID generator allows applications to embed information in local connection IDs, e.g. to support stateless packet-level load balancers.

Defaults to HashedConnectionIdGenerator.

-
Source

pub fn reset_key(&mut self, key: Arc<dyn HmacKey>) -> &mut Self

Private key used to send authenticated connection resets to peers who were +

Source

pub fn reset_key(&mut self, key: Arc<dyn HmacKey>) -> &mut Self

Private key used to send authenticated connection resets to peers who were communicating with a previous instance of this endpoint.

-
Source

pub fn max_udp_payload_size( +

Source

pub fn max_udp_payload_size( &mut self, value: u16, ) -> Result<&mut Self, ConfigError>

Maximum UDP payload size accepted from peers (excluding UDP and IP overhead).

@@ -21,24 +21,24 @@ communicating with a previous instance of this endpoint.

1500 byte Ethernet MTU. Deployments on links with larger MTUs (e.g. loopback or Ethernet with jumbo frames) can raise this to improve performance at the cost of a linear increase in datagram receive buffer size.

-
Source

pub fn get_max_udp_payload_size(&self) -> u64

Get the current value of max_udp_payload_size

-
Source

pub fn supported_versions(&mut self, supported_versions: Vec<u32>) -> &mut Self

Override supported QUIC versions

-
Source

pub fn grease_quic_bit(&mut self, value: bool) -> &mut Self

Whether to accept QUIC packets containing any value for the fixed bit

+
Source

pub fn get_max_udp_payload_size(&self) -> u64

Get the current value of max_udp_payload_size

+
Source

pub fn supported_versions(&mut self, supported_versions: Vec<u32>) -> &mut Self

Override supported QUIC versions

+
Source

pub fn grease_quic_bit(&mut self, value: bool) -> &mut Self

Whether to accept QUIC packets containing any value for the fixed bit

Enabled by default. Helps protect against protocol ossification and makes traffic less identifiable to observers. Disable if helping observers identify this traffic as QUIC is desired.

-
Source

pub fn min_reset_interval(&mut self, value: Duration) -> &mut Self

Minimum interval between outgoing stateless reset packets

+
Source

pub fn min_reset_interval(&mut self, value: Duration) -> &mut Self

Minimum interval between outgoing stateless reset packets

Defaults to 20ms. Limits the impact of attacks which flood an endpoint with garbage packets, e.g. ISAKMP/IKE amplification. Larger values provide a stronger defense, but may delay detection of some error conditions by clients. Using a ConnectionIdGenerator with a low rate of false positives in validate reduces the risk incurred by a small minimum reset interval.

-
Source

pub fn rng_seed(&mut self, seed: Option<[u8; 32]>) -> &mut Self

Optional seed to be used internally for random number generation

+
Source

pub fn rng_seed(&mut self, seed: Option<[u8; 32]>) -> &mut Self

Optional seed to be used internally for random number generation

By default, noq will initialize an endpoint’s rng using a platform entropy source. However, you can seed the rng yourself through this method (e.g. if you need to run noq deterministically or if you are using noq in an environment that doesn’t have a source of entropy available).

-

Trait Implementations§

Source§

impl Clone for EndpointConfig

Source§

fn clone(&self) -> EndpointConfig

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 EndpointConfig

Source§

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

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

impl Default for EndpointConfig

Available on crate features aws-lc-rs or ring only.
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 EndpointConfig

Source§

fn clone(&self) -> EndpointConfig

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 EndpointConfig

Source§

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

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

impl Default for EndpointConfig

Available on crate features aws-lc-rs or ring only.
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.EndpointEvent.html b/pr/451/docs/noq_proto/struct.EndpointEvent.html index 5e8b9c38a..88f1ee3a0 100644 --- a/pr/451/docs/noq_proto/struct.EndpointEvent.html +++ b/pr/451/docs/noq_proto/struct.EndpointEvent.html @@ -1,4 +1,4 @@ -EndpointEvent in noq_proto - Rust

EndpointEvent

Struct EndpointEvent 

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

Events sent from a Connection to an Endpoint

+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.

diff --git a/pr/451/docs/noq_proto/struct.FixedLengthConnectionIdParser.html b/pr/451/docs/noq_proto/struct.FixedLengthConnectionIdParser.html index e7afb8ffa..c22ffd7ec 100644 --- a/pr/451/docs/noq_proto/struct.FixedLengthConnectionIdParser.html +++ b/pr/451/docs/noq_proto/struct.FixedLengthConnectionIdParser.html @@ -1,6 +1,6 @@ -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 +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 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.FourTuple.html b/pr/451/docs/noq_proto/struct.FourTuple.html index d1c14a67b..2182e3ce2 100644 --- a/pr/451/docs/noq_proto/struct.FourTuple.html +++ b/pr/451/docs/noq_proto/struct.FourTuple.html @@ -1,12 +1,12 @@ -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 +

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. @@ -16,17 +16,17 @@ If we don’t have a local IP set, then we only check the remote addresses for e

  • 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

    +
    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 +

    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 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 diff --git a/pr/451/docs/noq_proto/struct.FrameStats.html b/pr/451/docs/noq_proto/struct.FrameStats.html index ff4a18df9..2d3075205 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, diff --git a/pr/451/docs/noq_proto/struct.HashedConnectionIdGenerator.html b/pr/451/docs/noq_proto/struct.HashedConnectionIdGenerator.html index b7ba382a0..cabe55e59 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 4fe5fe091..ca46a047f 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 7a0ea9dc3..fe376fcd5 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 ed5b07145..94b368ae4 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 9fa3eec8b..7e2d5aa1b 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 719d54630..a234efae8 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 bd3b629e8..f5e56801a 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 43f739021..dc82c3e51 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 fa9b73290..a10849572 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 6448383df..0b7459e4e 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 bd680451f..7c94e1266 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 5c42e4c7a..8a7b74c4c 100644 --- a/pr/451/docs/noq_proto/struct.PathId.html +++ b/pr/451/docs/noq_proto/struct.PathId.html @@ -1,4 +1,4 @@ -PathId in noq_proto - Rust

    PathId

    Struct PathId 

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

    Id representing different paths when using multipath extension

    +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 diff --git a/pr/451/docs/noq_proto/struct.PathStats.html b/pr/451/docs/noq_proto/struct.PathStats.html index 218e8cddc..d4d27a8d3 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 13 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, @@ -6,6 +6,7 @@ pub frame_rx: FrameStats, pub cwnd: u64, pub congestion_events: u64, + pub spurious_congestion_events: u64, pub lost_packets: u64, pub lost_bytes: u64, pub sent_plpmtud_probes: u64, @@ -20,15 +21,16 @@
    §frame_rx: FrameStats

    Statistics about frames received on this path.

    §cwnd: u64

    Current congestion window of the connection.

    §congestion_events: u64

    Congestion events on the connection.

    -
    §lost_packets: u64

    The amount of packets lost on this path.

    -
    §lost_bytes: u64

    The amount of bytes lost on this path.

    +
    §spurious_congestion_events: u64

    Spurious congestion events on the connection.

    +
    §lost_packets: u64

    The number of packets lost on this path.

    +
    §lost_bytes: u64

    The number of bytes lost on this path.

    §sent_plpmtud_probes: u64

    The number of PLPMTUD probe packets sent on this path.

    These are also counted by UdpStats::datagrams.

    §lost_plpmtud_probes: u64

    The number of PLPMTUD probe packets lost on this path.

    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, +

    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 diff --git a/pr/451/docs/noq_proto/struct.ProtectedInitialHeader.html b/pr/451/docs/noq_proto/struct.ProtectedInitialHeader.html index 516227f7b..6f3019e13 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>,
    @@ -10,7 +10,7 @@
     
    §token_pos: Range<usize>

    The position of a token in the packet buffer

    §len: u64

    Length of the packet payload

    §version: u32

    QUIC version

    -

    Trait Implementations§

    Source§

    impl Clone for ProtectedInitialHeader

    Source§

    fn clone(&self) -> ProtectedInitialHeader

    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 ProtectedInitialHeader

    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 Clone for ProtectedInitialHeader

    Source§

    fn clone(&self) -> ProtectedInitialHeader

    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 ProtectedInitialHeader

    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_proto/struct.QlogConfig.html b/pr/451/docs/noq_proto/struct.QlogConfig.html index bcf7a862f..d4f085a03 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 c02459478..1a77e7ba8 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 6f17b2298..c6ead5e9f 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 64312fab1..b8f4ea9e1 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,10 +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 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 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

    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 b0ab2a623..ab9b7fe1e 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 781875bef..d5f2287a6 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 93a894b75..a3200d250 100644 --- a/pr/451/docs/noq_proto/struct.SendStream.html +++ b/pr/451/docs/noq_proto/struct.SendStream.html @@ -1,28 +1,28 @@ -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 [Bytes], -) -> Result<Written, WriteError>

    Send data on the given stream

    -

    Returns the number of bytes and chunks successfully written. -Note that this method might also write a partial chunk. In this case -Written::chunks will not count this chunk as fully written. However + data: &mut &mut [Bytes], +) -> Result<usize, WriteError>

    Send data on the given stream

    +

    Returns the number of bytes written and advances the provided Bytes +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 preferred_address_v6( &mut self, address: Option<SocketAddrV6>, ) -> &mut Self

    The preferred IPv6 address that will be communicated to clients during handshaking

    If the client is able to reach this address, it will switch to it.

    -
    Source

    pub fn max_incoming(&mut self, max_incoming: usize) -> &mut Self

    Maximum number of Incoming to allow to exist at a time

    +
    Source

    pub fn max_incoming(&mut self, max_incoming: usize) -> &mut Self

    Maximum number of Incoming to allow to exist at a time

    An Incoming comes into existence when an incoming connection attempt is received and stops existing when the application either accepts it or otherwise disposes of it. While this limit is reached, new incoming connection attempts are immediately @@ -43,7 +43,7 @@ application latency in handling incoming connection attempts.

    The default value is set to 65536. With a typical Ethernet MTU of 1500 bytes, this limits memory consumption from this to under 100 MiB–a generous amount that still prevents memory exhaustion in most contexts.

    -
    Source

    pub fn incoming_buffer_size(&mut self, incoming_buffer_size: u64) -> &mut Self

    Maximum number of received bytes to buffer for each Incoming

    +
    Source

    pub fn incoming_buffer_size(&mut self, incoming_buffer_size: u64) -> &mut Self

    Maximum number of received bytes to buffer for each Incoming

    An Incoming comes into existence when an incoming connection attempt is received and stops existing when the application either accepts it or otherwise disposes of it. This limit governs only packets received within that period, and does not include @@ -52,7 +52,7 @@ the first packet. Packets received in excess of this limit are dropped, which ma

    The default value is set to 10 MiB–an amount such that in most situations a client would not transmit that much 0-RTT data faster than the server handles the corresponding Incoming.

    -
    Source

    pub fn incoming_buffer_size_total( +

    Source

    pub fn incoming_buffer_size_total( &mut self, incoming_buffer_size_total: u64, ) -> &mut Self

    Maximum number of received bytes to buffer for all Incoming @@ -64,17 +64,17 @@ the first packet. Packets received in excess of this limit are dropped, which ma 0-RTT or handshake data to have to be retransmitted.

    The default value is set to 100 MiB–a generous amount that still prevents memory exhaustion in most contexts.

    -
    Source

    pub fn time_source(&mut self, time_source: Arc<dyn TimeSource>) -> &mut Self

    Object to get current SystemTime

    +
    Source

    pub fn time_source(&mut self, time_source: Arc<dyn TimeSource>) -> &mut Self

    Object to get current SystemTime

    This exists to allow system time to be mocked in tests, or wherever else desired.

    Defaults to StdSystemTime, which simply calls SystemTime::now().

    -
    Source§

    impl ServerConfig

    Source§

    impl ServerConfig

    Source

    pub fn with_single_cert( cert_chain: Vec<CertificateDer<'static>>, key: PrivateKeyDer<'static>, ) -> Result<Self, Error>

    Create a server config with the given certificate chain to be presented to clients

    Uses a randomized handshake token key.

    -
    Source§

    impl ServerConfig

    Source

    pub fn with_crypto(crypto: Arc<dyn ServerConfig>) -> Self

    Create a server config with the given crypto::ServerConfig

    +
    Source§

    impl ServerConfig

    Source

    pub fn with_crypto(crypto: Arc<dyn ServerConfig>) -> Self

    Create a server config with the given crypto::ServerConfig

    Uses a randomized handshake token key.

    -

    Trait Implementations§

    Source§

    impl Clone for ServerConfig

    Source§

    fn clone(&self) -> ServerConfig

    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 ServerConfig

    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 Clone for ServerConfig

    Source§

    fn clone(&self) -> ServerConfig

    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 ServerConfig

    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> CloneToUninit for T
    where diff --git a/pr/451/docs/noq_proto/struct.ShouldTransmit.html b/pr/451/docs/noq_proto/struct.ShouldTransmit.html index 2d6a7264b..49d7aba77 100644 --- a/pr/451/docs/noq_proto/struct.ShouldTransmit.html +++ b/pr/451/docs/noq_proto/struct.ShouldTransmit.html @@ -1,9 +1,9 @@ -ShouldTransmit in noq_proto - Rust

    ShouldTransmit

    Struct ShouldTransmit 

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

    Indicates whether a frame needs to be transmitted

    +ShouldTransmit in noq_proto - Rust

    ShouldTransmit

    Struct ShouldTransmit 

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

    Indicates whether a frame needs to be transmitted

    This type wraps around bool and uses the #[must_use] attribute in order to prevent accidental loss of the frame transmission requirement.

    -

    Implementations§

    Source§

    impl ShouldTransmit

    Source

    pub fn should_transmit(self) -> bool

    Returns whether a frame should be transmitted

    -

    Trait Implementations§

    Source§

    impl Clone for ShouldTransmit

    Source§

    fn clone(&self) -> ShouldTransmit

    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 ShouldTransmit

    Source§

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

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

    impl Default for ShouldTransmit

    Source§

    fn default() -> ShouldTransmit

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

    impl PartialEq for ShouldTransmit

    Source§

    fn eq(&self, other: &ShouldTransmit) -> 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 ShouldTransmit

    Source§

    impl Eq for ShouldTransmit

    Source§

    impl StructuralPartialEq for ShouldTransmit

    Auto Trait Implementations§

    Blanket Implementations§

    Source§

    impl<T> Any for T
    where +

    Implementations§

    Source§

    impl ShouldTransmit

    Source

    pub fn should_transmit(self) -> bool

    Returns whether a frame should be transmitted

    +

    Trait Implementations§

    Source§

    impl Clone for ShouldTransmit

    Source§

    fn clone(&self) -> ShouldTransmit

    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 ShouldTransmit

    Source§

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

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

    impl Default for ShouldTransmit

    Source§

    fn default() -> ShouldTransmit

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

    impl PartialEq for ShouldTransmit

    Source§

    fn eq(&self, other: &ShouldTransmit) -> 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 ShouldTransmit

    Source§

    impl Eq for ShouldTransmit

    Source§

    impl StructuralPartialEq for ShouldTransmit

    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.StdSystemTime.html b/pr/451/docs/noq_proto/struct.StdSystemTime.html index 282fe6264..7a5b73d39 100644 --- a/pr/451/docs/noq_proto/struct.StdSystemTime.html +++ b/pr/451/docs/noq_proto/struct.StdSystemTime.html @@ -1,6 +1,6 @@ -StdSystemTime in noq_proto - Rust

    StdSystemTime

    Struct StdSystemTime 

    Source
    pub struct StdSystemTime;
    Expand description

    Default implementation of TimeSource

    +StdSystemTime in noq_proto - Rust

    StdSystemTime

    Struct StdSystemTime 

    Source
    pub struct StdSystemTime;
    Expand description

    Default implementation of TimeSource

    Implements now by calling SystemTime::now().

    -

    Trait Implementations§

    Source§

    impl TimeSource for StdSystemTime

    Source§

    fn now(&self) -> SystemTime

    Get SystemTime::now() or the mocked equivalent

    Auto Trait Implementations§

    Blanket Implementations§

    Source§

    impl<T> Any for T
    where +

    Trait Implementations§

    Source§

    impl TimeSource for StdSystemTime

    Source§

    fn now(&self) -> SystemTime

    Get SystemTime::now() or the mocked equivalent

    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.StreamId.html b/pr/451/docs/noq_proto/struct.StreamId.html index 871dd5c5c..6a6be5f99 100644 --- a/pr/451/docs/noq_proto/struct.StreamId.html +++ b/pr/451/docs/noq_proto/struct.StreamId.html @@ -1,23 +1,23 @@ -StreamId in noq_proto - Rust

    StreamId

    Struct StreamId 

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

    Identifier for a stream within a particular connection

    -

    Implementations§

    Source§

    impl StreamId

    Source

    pub fn new(initiator: Side, dir: Dir, index: u64) -> Self

    Create a new StreamId

    -
    Source

    pub fn initiator(self) -> Side

    Which side of a connection initiated the stream

    -
    Source

    pub fn dir(self) -> Dir

    Which directions data flows in

    -
    Source

    pub fn index(self) -> u64

    Distinguishes streams of the same initiator and directionality

    -

    Trait Implementations§

    Source§

    impl<'arbitrary> Arbitrary<'arbitrary> for StreamId

    Available on crate feature arbitrary only.
    Source§

    fn arbitrary(u: &mut Unstructured<'arbitrary>) -> Result<Self>

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

    fn arbitrary_take_rest(u: Unstructured<'a>) -> Result<Self, Error>

    Generate an arbitrary value of Self from the entirety of the given +StreamId in noq_proto - Rust

    StreamId

    Struct StreamId 

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

    Identifier for a stream within a particular connection

    +

    Implementations§

    Source§

    impl StreamId

    Source

    pub fn new(initiator: Side, dir: Dir, index: u64) -> Self

    Create a new StreamId

    +
    Source

    pub fn initiator(self) -> Side

    Which side of a connection initiated the stream

    +
    Source

    pub fn dir(self) -> Dir

    Which directions data flows in

    +
    Source

    pub fn index(self) -> u64

    Distinguishes streams of the same initiator and directionality

    +

    Trait Implementations§

    Source§

    impl<'arbitrary> Arbitrary<'arbitrary> for StreamId

    Available on crate feature arbitrary only.
    Source§

    fn arbitrary(u: &mut Unstructured<'arbitrary>) -> Result<Self>

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

    fn arbitrary_take_rest(u: Unstructured<'a>) -> Result<Self, Error>

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

    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 StreamId

    Source§

    fn clone(&self) -> StreamId

    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 StreamId

    Source§

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

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

    impl Decodable for StreamId

    Source§

    fn decode<B: Buf>(buf: &mut B) -> Result<Self>

    Decode a Self from the provided buffer, if the buffer is large enough
    Source§

    impl Display for StreamId

    Source§

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

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

    impl Encodable for StreamId

    Source§

    fn encode<B: BufMut>(&self, buf: &mut B)

    Append the encoding of self to the provided buffer.
    Source§

    impl From<StreamId> for VarInt

    Source§

    fn from(x: StreamId) -> Self

    Converts to this type from the input type.
    Source§

    impl From<StreamId> for u64

    Source§

    fn from(x: StreamId) -> 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 Hash for StreamId

    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 StreamId

    Source§

    fn clone(&self) -> StreamId

    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 StreamId

    Source§

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

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

    impl Decodable for StreamId

    Source§

    fn decode<B: Buf>(buf: &mut B) -> Result<Self>

    Decode a Self from the provided buffer, if the buffer is large enough
    Source§

    impl Display for StreamId

    Source§

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

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

    impl Encodable for StreamId

    Source§

    fn encode<B: BufMut>(&self, buf: &mut B)

    Append the encoding of self to the provided buffer.
    Source§

    impl From<StreamId> for VarInt

    Source§

    fn from(x: StreamId) -> Self

    Converts to this type from the input type.
    Source§

    impl From<StreamId> for u64

    Source§

    fn from(x: StreamId) -> 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 Hash for StreamId

    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 StreamId

    Source§

    fn cmp(&self, other: &StreamId) -> 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 StreamId

    Source§

    fn cmp(&self, other: &StreamId) -> 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 StreamId

    Source§

    fn eq(&self, other: &StreamId) -> 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 StreamId

    Source§

    fn partial_cmp(&self, other: &StreamId) -> 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 StreamId

    Source§

    fn eq(&self, other: &StreamId) -> 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 StreamId

    Source§

    fn partial_cmp(&self, other: &StreamId) -> 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 StreamId

    Source§

    impl Eq for StreamId

    Source§

    impl StructuralPartialEq for StreamId

    Auto Trait Implementations§

    Blanket Implementations§

    Source§

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

    Source§

    impl Copy for StreamId

    Source§

    impl Eq for StreamId

    Source§

    impl StructuralPartialEq for StreamId

    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.StreamInfo.html b/pr/451/docs/noq_proto/struct.StreamInfo.html index 239a160b4..668963336 100644 --- a/pr/451/docs/noq_proto/struct.StreamInfo.html +++ b/pr/451/docs/noq_proto/struct.StreamInfo.html @@ -1,5 +1,5 @@ -StreamInfo in noq_proto - Rust

    StreamInfo

    Struct StreamInfo 

    Source
    pub struct StreamInfo(/* private fields */);

    Trait Implementations§

    Source§

    impl Clone for StreamInfo

    Source§

    fn clone(&self) -> StreamInfo

    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 StreamInfo

    Source§

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

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

    impl Display for StreamInfo

    Source§

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

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

    impl PartialEq for StreamInfo

    Source§

    fn eq(&self, other: &StreamInfo) -> 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 StreamInfo

    Source§

    impl Eq for StreamInfo

    Source§

    impl StructuralPartialEq for StreamInfo

    Auto Trait Implementations§

    Blanket Implementations§

    Source§

    impl<T> Any for T
    where +StreamInfo in noq_proto - Rust

    StreamInfo

    Struct StreamInfo 

    Source
    pub struct StreamInfo(/* private fields */);

    Trait Implementations§

    Source§

    impl Clone for StreamInfo

    Source§

    fn clone(&self) -> StreamInfo

    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 StreamInfo

    Source§

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

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

    impl Display for StreamInfo

    Source§

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

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

    impl PartialEq for StreamInfo

    Source§

    fn eq(&self, other: &StreamInfo) -> 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 StreamInfo

    Source§

    impl Eq for StreamInfo

    Source§

    impl StructuralPartialEq for StreamInfo

    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.Streams.html b/pr/451/docs/noq_proto/struct.Streams.html index 2d3038625..46e6a856d 100644 --- a/pr/451/docs/noq_proto/struct.Streams.html +++ b/pr/451/docs/noq_proto/struct.Streams.html @@ -1,11 +1,11 @@ -Streams in noq_proto - Rust

    Streams

    Struct Streams 

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

    Access to streams

    -

    Implementations§

    Source§

    impl<'a> Streams<'a>

    Source

    pub fn open(&mut self, dir: Dir) -> Option<StreamId>

    Open a single stream if possible

    +Streams in noq_proto - Rust

    Streams

    Struct Streams 

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

    Access to streams

    +

    Implementations§

    Source§

    impl<'a> Streams<'a>

    Source

    pub fn open(&mut self, dir: Dir) -> Option<StreamId>

    Open a single stream if possible

    Returns None if the streams in the given direction are currently exhausted.

    -
    Source

    pub fn accept(&mut self, dir: Dir) -> Option<StreamId>

    Accept a remotely initiated stream of a certain directionality, if possible

    +
    Source

    pub fn accept(&mut self, dir: Dir) -> Option<StreamId>

    Accept a remotely initiated stream of a certain directionality, if possible

    Returns None if there are no new incoming streams for this connection. Has no impact on the data flow-control or stream concurrency limits.

    -
    Source

    pub fn send_streams(&self) -> usize

    The number of streams that may have unacknowledged data.

    -
    Source

    pub fn remote_open_streams(&self, dir: Dir) -> u64

    The number of remotely initiated open streams of a certain directionality.

    +
    Source

    pub fn send_streams(&self) -> usize

    The number of streams that may have unacknowledged data.

    +
    Source

    pub fn remote_open_streams(&self, dir: Dir) -> u64

    The number of remotely initiated open streams of a certain directionality.

    Includes remotely initiated streams, which have not been accepted via accept. These streams count against the respective concurrency limit reported by Connection::max_concurrent_streams.

    diff --git a/pr/451/docs/noq_proto/struct.TokenMemoryCache.html b/pr/451/docs/noq_proto/struct.TokenMemoryCache.html index ee19e6d49..9e707a23c 100644 --- a/pr/451/docs/noq_proto/struct.TokenMemoryCache.html +++ b/pr/451/docs/noq_proto/struct.TokenMemoryCache.html @@ -1,4 +1,4 @@ -TokenMemoryCache in noq_proto - Rust

    TokenMemoryCache

    Struct TokenMemoryCache 

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

    TokenStore implementation that stores up to N tokens per server name for up to a +TokenMemoryCache in noq_proto - Rust

    TokenMemoryCache

    Struct TokenMemoryCache 

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

    TokenStore implementation that stores up to N tokens per server name for up to a limited number of server names, in-memory

    Implementations§

    Source§

    impl TokenMemoryCache

    Source

    pub fn new(max_server_names: u32, max_tokens_per_server: usize) -> Self

    Construct empty

    Trait Implementations§

    Source§

    impl Debug for TokenMemoryCache

    Source§

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

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

    impl Default for TokenMemoryCache

    Defaults to a maximum of 256 servers and 2 tokens per server

    diff --git a/pr/451/docs/noq_proto/struct.TokenReuseError.html b/pr/451/docs/noq_proto/struct.TokenReuseError.html index c7259bc8b..7f694595f 100644 --- a/pr/451/docs/noq_proto/struct.TokenReuseError.html +++ b/pr/451/docs/noq_proto/struct.TokenReuseError.html @@ -1,4 +1,4 @@ -TokenReuseError in noq_proto - Rust

    TokenReuseError

    Struct TokenReuseError 

    Source
    pub struct TokenReuseError;
    Expand description

    Error for when a validation token may have been reused

    +TokenReuseError in noq_proto - Rust

    TokenReuseError

    Struct TokenReuseError 

    Source
    pub struct TokenReuseError;
    Expand description

    Error for when a validation token may have been reused

    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.Transmit.html b/pr/451/docs/noq_proto/struct.Transmit.html index 02c484e93..ca0d54dfa 100644 --- a/pr/451/docs/noq_proto/struct.Transmit.html +++ b/pr/451/docs/noq_proto/struct.Transmit.html @@ -1,4 +1,4 @@ -Transmit in noq_proto - Rust

    Transmit

    Struct Transmit 

    Source
    pub struct Transmit {
    +Transmit in noq_proto - Rust

    Transmit

    Struct Transmit 

    Source
    pub struct Transmit {
         pub destination: SocketAddr,
         pub ecn: Option<EcnCodepoint>,
         pub size: usize,
    @@ -11,7 +11,7 @@
     
    §segment_size: Option<usize>

    The segment size if this transmission contains multiple datagrams. This is None if the transmit only contains a single datagram

    §src_ip: Option<IpAddr>

    Optional source IP address for the datagram

    -

    Trait Implementations§

    Source§

    impl Debug for Transmit

    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 Transmit

    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.TransportConfig.html b/pr/451/docs/noq_proto/struct.TransportConfig.html index ff69e42bb..9b66154ec 100644 --- a/pr/451/docs/noq_proto/struct.TransportConfig.html +++ b/pr/451/docs/noq_proto/struct.TransportConfig.html @@ -1,4 +1,4 @@ -TransportConfig in noq_proto - Rust

    TransportConfig

    Struct TransportConfig 

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

    Parameters governing the core QUIC state machine

    +TransportConfig in noq_proto - Rust

    TransportConfig

    Struct TransportConfig 

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

    Parameters governing the core QUIC state machine

    Default values should be suitable for most internet applications. Applications protocols which forbid remotely-initiated streams should set max_concurrent_bidi_streams and max_concurrent_uni_streams to zero.

    @@ -8,11 +8,11 @@ tuned for a particular expected round trip time, link capacity, and memory avail for higher bandwidths and latencies increases worst-case memory consumption, but does not impair performance at lower bandwidths and latencies. The default configuration is tuned for a 100Mbps link with a 100ms round trip time.

    -

    Implementations§

    Source§

    impl TransportConfig

    Source

    pub fn max_concurrent_bidi_streams(&mut self, value: VarInt) -> &mut Self

    Maximum number of incoming bidirectional streams that may be open concurrently

    +

    Implementations§

    Source§

    impl TransportConfig

    Source

    pub fn max_concurrent_bidi_streams(&mut self, value: VarInt) -> &mut Self

    Maximum number of incoming bidirectional streams that may be open concurrently

    Must be nonzero for the peer to open any bidirectional streams.

    Worst-case memory use is directly proportional to max_concurrent_bidi_streams * stream_receive_window, with an upper bound proportional to receive_window.

    -
    Source

    pub fn max_concurrent_uni_streams(&mut self, value: VarInt) -> &mut Self

    Variant of max_concurrent_bidi_streams affecting unidirectional streams

    -
    Source

    pub fn max_idle_timeout(&mut self, value: Option<IdleTimeout>) -> &mut Self

    Maximum duration of inactivity to accept before timing out the connection.

    +
    Source

    pub fn max_concurrent_uni_streams(&mut self, value: VarInt) -> &mut Self

    Variant of max_concurrent_bidi_streams affecting unidirectional streams

    +
    Source

    pub fn max_idle_timeout(&mut self, value: Option<IdleTimeout>) -> &mut Self

    Maximum duration of inactivity to accept before timing out the connection.

    The true idle timeout is the minimum of this and the peer’s own max idle timeout. None represents an infinite timeout. Defaults to 30 seconds.

    WARNING: If a peer or its network path malfunctions or acts maliciously, an infinite @@ -24,43 +24,43 @@ idle timeout can result in permanently hung futures!

    config.max_idle_timeout(Some(VarInt::from_u32(10_000).into())); // Set the idle timeout as a `Duration` -config.max_idle_timeout(Some(Duration::from_secs(10).try_into()?));

    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 +config.max_idle_timeout(Some(Duration::from_secs(10).try_into()?));

    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,17 +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 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 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

    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 @@ -110,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

    @@ -123,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 @@ -131,15 +133,15 @@ 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 reachable address, which in most NATd networks will not be easily available.

    -
    Source

    pub fn max_concurrent_multipath_paths( +

    Source

    pub fn max_concurrent_multipath_paths( &mut self, max_concurrent: u32, ) -> &mut Self

    Enables the Multipath Extension for QUIC.

    @@ -148,7 +150,7 @@ reachable address, which in most NATd networks will not be easily available.

    The value provided specifies the number maximum number of paths this endpoint may open concurrently when multipath is negotiated. For any path to be opened, the remote must enable multipath as well.

    -
    Source

    pub fn default_path_max_idle_timeout( +

    Source

    pub fn default_path_max_idle_timeout( &mut self, timeout: Option<Duration>, ) -> &mut Self

    Sets a default per-path maximum idle timeout

    @@ -157,7 +159,7 @@ interact with the

    You can also change this using Connection::set_path_max_idle_timeout for existing paths.

    -
    Source

    pub fn default_path_keep_alive_interval( +

    Source

    pub fn default_path_keep_alive_interval( &mut self, interval: Option<Duration>, ) -> &mut Self

    Sets a default per-path keep alive interval

    @@ -167,7 +169,7 @@ existing paths.

    control over which path is used for this.

    You can also change this using Connection::set_path_keep_alive_interval for existing path.

    -
    Source

    pub fn set_max_remote_nat_traversal_addresses( +

    Source

    pub fn max_remote_nat_traversal_addresses( &mut self, max_addresses: u8, ) -> &mut Self

    Sets the maximum number of nat traversal addresses this endpoint allows the remote to @@ -176,25 +178,24 @@ advertise

    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, a default value of -DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED will be used.

    -
    Source

    pub fn qlog_factory(&mut self, factory: Arc<dyn QlogFactory>) -> &mut Self

    Configures qlog capturing by setting a QlogFactory.

    +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.

    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 95420ab2a..79bb2ba3c 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 8214e70db..d6b2a9bbc 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 b9162933f..9b596b615 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, diff --git a/pr/451/docs/noq_proto/struct.ValidationTokenConfig.html b/pr/451/docs/noq_proto/struct.ValidationTokenConfig.html index 2c275b901..82ff44e81 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 @@ -33,20 +33,20 @@ is the main situation where a server might be sending application data to an add be validated by token usage earlier than it would become validated without token usage.

    These tokens should not be confused with “stateless reset tokens,” which are similarly named but entirely unrelated.

    -

    Implementations§

    Source§

    impl ValidationTokenConfig

    Source

    pub fn lifetime(&mut self, value: Duration) -> &mut Self

    Duration after an address validation token was issued for which it’s considered valid

    +

    Implementations§

    Source§

    impl ValidationTokenConfig

    Source

    pub fn lifetime(&mut self, value: Duration) -> &mut Self

    Duration after an address validation token was issued for which it’s considered valid

    This refers only to tokens sent in NEW_TOKEN frames, in contrast to retry tokens.

    Defaults to 2 weeks.

    -
    Source

    pub fn log(&mut self, log: Arc<dyn TokenLog>) -> &mut Self

    Set a custom TokenLog

    +
    Source

    pub fn log(&mut self, log: Arc<dyn TokenLog>) -> &mut Self

    Set a custom TokenLog

    If the bloom feature is enabled (which it is by default), defaults to a default BloomTokenLog, which is suitable for most internet applications.

    If the bloom feature is disabled, defaults to NoneTokenLog, which makes the server ignore all address validation tokens (that is, tokens originating from NEW_TOKEN frames–retry tokens are not affected).

    -
    Source

    pub fn sent(&mut self, value: u32) -> &mut Self

    Number of address validation tokens sent to a client when its path is validated

    +
    Source

    pub fn sent(&mut self, value: u32) -> &mut Self

    Number of address validation tokens sent to a client when its path is validated

    This refers only to tokens sent in NEW_TOKEN frames, in contrast to retry tokens.

    If the bloom feature is enabled (which it is by default), defaults to 2. Otherwise, defaults to 0.

    -

    Trait Implementations§

    Source§

    impl Clone for ValidationTokenConfig

    Source§

    fn clone(&self) -> ValidationTokenConfig

    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 ValidationTokenConfig

    Source§

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

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

    impl Default for ValidationTokenConfig

    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 ValidationTokenConfig

    Source§

    fn clone(&self) -> ValidationTokenConfig

    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 ValidationTokenConfig

    Source§

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

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

    impl Default for ValidationTokenConfig

    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.VarInt.html b/pr/451/docs/noq_proto/struct.VarInt.html index 9968b2975..8204132a7 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 7c7300f1f..bbf796052 100644 --- a/pr/451/docs/noq_proto/struct.VarIntBoundsExceeded.html +++ b/pr/451/docs/noq_proto/struct.VarIntBoundsExceeded.html @@ -1,5 +1,5 @@ -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, +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 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/trait.ConnectionIdGenerator.html b/pr/451/docs/noq_proto/trait.ConnectionIdGenerator.html index 5fd001016..4bda392db 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 2d9346733..e9d81941a 100644
    --- a/pr/451/docs/noq_proto/trait.ConnectionIdParser.html
    +++ b/pr/451/docs/noq_proto/trait.ConnectionIdParser.html
    @@ -1,9 +1,9 @@
    -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,
             buf: &mut dyn Buf,
         ) -> Result<ConnectionId, PacketDecodeError>;
     }
    Expand description

    Parse connection id in short header packet

    -

    Required Methods§

    Source

    fn parse(&self, buf: &mut dyn Buf) -> Result<ConnectionId, PacketDecodeError>

    Parse a connection id from given buffer

    -

    Implementors§

    \ No newline at end of file +

    Required Methods§

    Source

    fn parse(&self, buf: &mut dyn Buf) -> Result<ConnectionId, PacketDecodeError>

    Parse a connection id from given buffer

    +

    Implementors§

    \ No newline at end of file diff --git a/pr/451/docs/noq_proto/trait.NetworkChangeHint.html b/pr/451/docs/noq_proto/trait.NetworkChangeHint.html index da46ba665..eed69fcd3 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 276659d23..9aada7d50 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 50b677c9b..bdf9b3c8e 100644
    --- a/pr/451/docs/noq_proto/trait.TimeSource.html
    +++ b/pr/451/docs/noq_proto/trait.TimeSource.html
    @@ -1,7 +1,7 @@
    -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

    This exists to allow system time to be mocked in tests, or wherever else desired.

    -

    Required Methods§

    Source

    fn now(&self) -> SystemTime

    Get SystemTime::now() or the mocked equivalent

    -

    Implementors§

    \ No newline at end of file +

    Required Methods§

    Source

    fn now(&self) -> SystemTime

    Get SystemTime::now() or the mocked equivalent

    +

    Implementors§

    \ No newline at end of file diff --git a/pr/451/docs/noq_proto/trait.TokenLog.html b/pr/451/docs/noq_proto/trait.TokenLog.html index 5ec6afc2f..223cbfa33 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 6f46f6bb6..adb21f7e5 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 bc7738994..7237a41b5 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 056cd0c5c..cc1b10a32 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 b28171a38..35de07fb4 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 45414c58e..e2c3b0d59 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 a6dcb01f0..e00fd2532 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 4d9005819..a590db43f 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, @@ -6,9 +6,9 @@

    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

    -

    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

    +

    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_udp/index.html b/pr/451/docs/noq_udp/index.html index 14b244b89..f6ff6cefa 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 542a6029f..fa27d5986 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 cf2508455..730bcf09a 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 48148268b..80c6f6e94 100644
    --- a/pr/451/docs/noq_udp/struct.UdpSockRef.html
    +++ b/pr/451/docs/noq_udp/struct.UdpSockRef.html
    @@ -1,7 +1,7 @@
    -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 +

    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 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_udp/struct.UdpSocketState.html b/pr/451/docs/noq_udp/struct.UdpSocketState.html index 865b979f0..5a45e8fb2 100644 --- a/pr/451/docs/noq_udp/struct.UdpSocketState.html +++ b/pr/451/docs/noq_udp/struct.UdpSocketState.html @@ -1,7 +1,7 @@ -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( +

    Implementations§

    Source§

    impl UdpSocketState

    Source

    pub fn new(sock: UdpSockRef<'_>) -> Result<Self>

    Source

    pub fn send( &self, socket: UdpSockRef<'_>, transmit: &Transmit<'_>, @@ -13,38 +13,38 @@ employ retransmits and timeouts anyway in order to deal with UDP’s unreliable Thus, logging is most likely the only thing you can do with these errors.

    If you would like to handle these errors yourself, use UdpSocketState::try_send instead.

    -

    Source

    pub fn try_send( +

    Source

    pub fn try_send( &self, socket: UdpSockRef<'_>, transmit: &Transmit<'_>, ) -> Result<()>

    Sends a Transmit on the given socket without any additional error handling.

    -
    Source

    pub fn recv( +

    Source

    pub fn recv( &self, socket: UdpSockRef<'_>, bufs: &mut [IoSliceMut<'_>], meta: &mut [RecvMeta], -) -> Result<usize>

    Source

    pub fn max_gso_segments(&self) -> NonZeroUsize

    The maximum amount of segments which can be transmitted if a platform +) -> Result<usize>

    Source

    pub fn max_gso_segments(&self) -> NonZeroUsize

    The maximum amount of segments which can be transmitted if a platform supports Generic Send Offload (GSO).

    This is 1 if the platform doesn’t support GSO. Subject to change if errors are detected while using GSO.

    -
    Source

    pub fn gro_segments(&self) -> NonZeroUsize

    The number of segments to read when GRO is enabled. Used as a factor to +

    Source

    pub fn gro_segments(&self) -> NonZeroUsize

    The number of segments to read when GRO is enabled. Used as a factor to compute the receive buffer size.

    Returns 1 if the platform doesn’t support GRO.

    -
    Source

    pub fn set_send_buffer_size( +

    Source

    pub fn set_send_buffer_size( &self, socket: UdpSockRef<'_>, bytes: usize, ) -> Result<()>

    Resize the send buffer of socket to bytes

    -
    Source

    pub fn set_recv_buffer_size( +

    Source

    pub fn set_recv_buffer_size( &self, socket: UdpSockRef<'_>, bytes: usize, ) -> Result<()>

    Resize the receive buffer of socket to bytes

    -
    Source

    pub fn send_buffer_size(&self, socket: UdpSockRef<'_>) -> Result<usize>

    Get the size of the socket send buffer

    -
    Source

    pub fn recv_buffer_size(&self, socket: UdpSockRef<'_>) -> Result<usize>

    Get the size of the socket receive buffer

    -
    Source

    pub fn may_fragment(&self) -> bool

    Whether transmitted datagrams might get fragmented by the IP layer

    +
    Source

    pub fn send_buffer_size(&self, socket: UdpSockRef<'_>) -> Result<usize>

    Get the size of the socket send buffer

    +
    Source

    pub fn recv_buffer_size(&self, socket: UdpSockRef<'_>) -> Result<usize>

    Get the size of the socket receive buffer

    +
    Source

    pub fn may_fragment(&self) -> bool

    Whether transmitted datagrams might get fragmented by the IP layer

    Returns false on targets which employ e.g. the IPV6_DONTFRAG socket option.

    -

    Trait Implementations§

    Source§

    impl Debug for UdpSocketState

    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 UdpSocketState

    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/perf/client/fn.run.html b/pr/451/docs/perf/client/fn.run.html index a919a3a96..d78e6dd08 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/client/index.html b/pr/451/docs/perf/client/index.html index 5e45b5396..c731a56b9 100644 --- a/pr/451/docs/perf/client/index.html +++ b/pr/451/docs/perf/client/index.html @@ -1 +1 @@ -perf::client - Rust

    Module client

    Module client 

    Source

    Structs§

    Opt
    Connects to a QUIC perf server and maintains a specified pattern of requests until interrupted

    Functions§

    run
    \ No newline at end of file +perf::client - Rust

    Module client

    Module client 

    Source

    Structs§

    Opt
    Connects to a QUIC perf server and maintains a specified pattern of requests until interrupted

    Functions§

    run
    \ No newline at end of file diff --git a/pr/451/docs/perf/enum.CongestionAlgorithm.html b/pr/451/docs/perf/enum.CongestionAlgorithm.html index b04632029..5624f4695 100644 --- a/pr/451/docs/perf/enum.CongestionAlgorithm.html +++ b/pr/451/docs/perf/enum.CongestionAlgorithm.html @@ -1,8 +1,8 @@ -CongestionAlgorithm in perf - Rust

    CongestionAlgorithm

    Enum CongestionAlgorithm 

    Source
    pub enum CongestionAlgorithm {
    +CongestionAlgorithm in perf - Rust

    CongestionAlgorithm

    Enum CongestionAlgorithm 

    Source
    pub enum CongestionAlgorithm {
         Cubic,
    -    Bbr,
    +    Bbr3,
         NewReno,
    -}

    Variants§

    §

    Cubic

    §

    Bbr

    §

    NewReno

    Implementations§

    Source§

    impl CongestionAlgorithm

    Source

    pub fn build(self) -> Arc<dyn ControllerFactory + Send + Sync + 'static>

    Trait Implementations§

    Source§

    impl Clone for CongestionAlgorithm

    Source§

    fn clone(&self) -> CongestionAlgorithm

    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 ValueEnum for CongestionAlgorithm

    Source§

    fn value_variants<'a>() -> &'a [Self]

    All possible argument values, in display order.
    Source§

    fn to_possible_value<'a>(&self) -> Option<PossibleValue>

    The canonical argument value. Read more
    §

    fn from_str(input: &str, ignore_case: bool) -> Result<Self, String>

    Parse an argument into Self.
    Source§

    impl Copy for CongestionAlgorithm

    Auto Trait Implementations§

    Blanket Implementations§

    Source§

    impl<T> Any for T
    where +}

    Variants§

    §

    Cubic

    §

    Bbr3

    §

    NewReno

    Implementations§

    Source§

    impl CongestionAlgorithm

    Source

    pub fn build(self) -> Arc<dyn ControllerFactory + Send + Sync + 'static>

    Trait Implementations§

    Source§

    impl Clone for CongestionAlgorithm

    Source§

    fn clone(&self) -> CongestionAlgorithm

    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 ValueEnum for CongestionAlgorithm

    Source§

    fn value_variants<'a>() -> &'a [Self]

    All possible argument values, in display order.
    Source§

    fn to_possible_value<'a>(&self) -> Option<PossibleValue>

    The canonical argument value. Read more
    §

    fn from_str(input: &str, ignore_case: bool) -> Result<Self, String>

    Parse an argument into Self.
    Source§

    impl Copy for CongestionAlgorithm

    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/perf/server/index.html b/pr/451/docs/perf/server/index.html index de30e6257..4aa8e181b 100644 --- a/pr/451/docs/perf/server/index.html +++ b/pr/451/docs/perf/server/index.html @@ -1 +1 @@ -perf::server - Rust

    Module server

    Module server 

    Source

    Structs§

    Opt

    Functions§

    run
    \ No newline at end of file +perf::server - Rust

    Module server

    Module server 

    Source

    Structs§

    Opt

    Functions§

    run
    \ No newline at end of file diff --git a/pr/451/docs/perf/stats/index.html b/pr/451/docs/perf/stats/index.html index 023e188b0..d51e70a3b 100644 --- a/pr/451/docs/perf/stats/index.html +++ b/pr/451/docs/perf/stats/index.html @@ -1 +1 @@ -perf::stats - Rust

    Module stats

    Module stats 

    Source

    Structs§

    OpenStreamStats
    Statistics for the currently open streams
    Stats
    StreamStats
    \ No newline at end of file +perf::stats - Rust

    Module stats

    Module stats 

    Source

    Structs§

    OpenStreamStats
    Statistics for the currently open streams
    Stats
    StreamStats
    \ No newline at end of file diff --git a/pr/451/docs/perf/stats/struct.OpenStreamStats.html b/pr/451/docs/perf/stats/struct.OpenStreamStats.html index 108fd546a..d22b55612 100644 --- a/pr/451/docs/perf/stats/struct.OpenStreamStats.html +++ b/pr/451/docs/perf/stats/struct.OpenStreamStats.html @@ -1,13 +1,13 @@ -OpenStreamStats in perf::stats - Rust

    OpenStreamStats

    Struct OpenStreamStats 

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

    Statistics for the currently open streams

    -

    Implementations§

    Source§

    impl OpenStreamStats

    Source

    pub fn new_sender( +OpenStreamStats in perf::stats - Rust

    OpenStreamStats

    Struct OpenStreamStats 

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

    Statistics for the currently open streams

    +

    Implementations§

    Source§

    impl OpenStreamStats

    Source

    pub fn new_sender( &self, stream: &SendStream, upload_size: u64, -) -> Arc<StreamStats>

    Source

    pub fn new_receiver( +) -> Arc<StreamStats>

    Source

    pub fn new_receiver( &self, stream: &RecvStream, download_size: u64, -) -> Arc<StreamStats>

    Trait Implementations§

    Source§

    impl Clone for OpenStreamStats

    Source§

    fn clone(&self) -> OpenStreamStats

    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 Default for OpenStreamStats

    Source§

    fn default() -> OpenStreamStats

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

    Auto Trait Implementations§

    Blanket Implementations§

    Source§

    impl<T> Any for T
    where +) -> Arc<StreamStats>

    Trait Implementations§

    Source§

    impl Clone for OpenStreamStats

    Source§

    fn clone(&self) -> OpenStreamStats

    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 Default for OpenStreamStats

    Source§

    fn default() -> OpenStreamStats

    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/perf/stats/struct.Stats.html b/pr/451/docs/perf/stats/struct.Stats.html index a38f1750d..44ce4452d 100644 --- a/pr/451/docs/perf/stats/struct.Stats.html +++ b/pr/451/docs/perf/stats/struct.Stats.html @@ -1,4 +1,4 @@ -Stats in perf::stats - Rust

    Stats

    Struct Stats 

    Source
    pub struct Stats { /* private fields */ }

    Implementations§

    Source§

    impl Stats

    Source

    pub fn on_interval(&mut self, start: Instant, stream_stats: &OpenStreamStats)

    Source

    pub fn print(&self)

    Source

    pub fn print_json(&self, path: &Path) -> Result<()>

    Trait Implementations§

    Source§

    impl Default for Stats

    Source§

    fn default() -> Self

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

    Auto Trait Implementations§

    §

    impl Freeze for Stats

    §

    impl RefUnwindSafe for Stats

    §

    impl Send for Stats

    §

    impl Sync for Stats

    §

    impl Unpin for Stats

    §

    impl UnwindSafe for Stats

    Blanket Implementations§

    Source§

    impl<T> Any for T
    where +Stats in perf::stats - Rust

    Stats

    Struct Stats 

    Source
    pub struct Stats { /* private fields */ }

    Implementations§

    Source§

    impl Stats

    Source

    pub fn on_interval(&mut self, start: Instant, stream_stats: &OpenStreamStats)

    Source

    pub fn print(&self)

    Source

    pub fn print_json(&self, path: &Path) -> Result<()>

    Trait Implementations§

    Source§

    impl Default for Stats

    Source§

    fn default() -> Self

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

    Auto Trait Implementations§

    §

    impl Freeze for Stats

    §

    impl RefUnwindSafe for Stats

    §

    impl Send for Stats

    §

    impl Sync for Stats

    §

    impl Unpin for Stats

    §

    impl UnwindSafe for Stats

    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/perf/stats/struct.StreamStats.html b/pr/451/docs/perf/stats/struct.StreamStats.html index 0a619517a..d344307d6 100644 --- a/pr/451/docs/perf/stats/struct.StreamStats.html +++ b/pr/451/docs/perf/stats/struct.StreamStats.html @@ -1,4 +1,4 @@ -StreamStats in perf::stats - Rust

    StreamStats

    Struct StreamStats 

    Source
    pub struct StreamStats { /* private fields */ }

    Implementations§

    Source§

    impl StreamStats

    Source

    pub fn on_first_byte(&self, latency: Duration)

    Source

    pub fn on_bytes(&self, bytes: usize)

    Source

    pub fn finish(&self, duration: Duration)

    Auto Trait Implementations§

    Blanket Implementations§

    Source§

    impl<T> Any for T
    where +StreamStats in perf::stats - Rust

    StreamStats

    Struct StreamStats 

    Source
    pub struct StreamStats { /* private fields */ }

    Implementations§

    Source§

    impl StreamStats

    Source

    pub fn on_first_byte(&self, latency: Duration)

    Source

    pub fn on_bytes(&self, bytes: usize)

    Source

    pub fn finish(&self, duration: Duration)

    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/search.index/0033f3568d59.js b/pr/451/docs/search.index/0033f3568d59.js deleted file mode 100644 index bb8d36b85..000000000 --- a/pr/451/docs/search.index/0033f3568d59.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQgBoIAAABQSZgEAEwAAE4ACoOAAABPaYnABAwGwABOWAExz9m0PAAABAL8AKQABAOkCAYACsEASKwABoBAAABCTcHIBgQOgAAAAEQWwAA9YAR6gEAAAD1Blc3XzgANhb3IBAgD1WRAAAAEAAQBLAgEAAQQAOzAAAAEAAAkAAQDKEAkAAYECoJAAABNZsFASEwABYnejgISwUBN1AAGwkBKeAAFAIANXAwAAhqBwAAATUKBAAAATvzBBDDowAAABAAAAAAA7ABAAAACkBKUEYwVkBWUFZgVnBWgFaQVqBWsFbAVRB/UI9gj7COAL4QsDDAQMBQyQDJEMngw2DkMPRA9FD1gPdw94D70PNhBEEEUQYxBkEHYQmxCcEAkRVhFfEWARZBFcEpQSlRJEE0UTSxN1E3YTlBOVE6wTDxQQFBoUGxQ=") \ No newline at end of file diff --git a/pr/451/docs/search.index/005f416926ad.js b/pr/451/docs/search.index/005f416926ad.js new file mode 100644 index 000000000..b563f9018 --- /dev/null +++ b/pr/451/docs/search.index/005f416926ad.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..0f001500d --- /dev/null +++ b/pr/451/docs/search.index/00c4f658f46f.js @@ -0,0 +1 @@ +rn_("AQABsIATzgABdAEHCQAAAQkA87ESAAABAAEA84ACY2cBgwKgkAAAFBugkAAAFBwyMwGAAqBQAAAUR6CAAAATZmNl8wACc3TyNAcAAAEAAQABoLAAABOxYvM7EwAALgEBACOAAqAAAAATOmRyI4ACoFAAABQNY2XzgAJpeQEGAPUkEQAAAQABAAEAAQABAQGwQBRLAAFl9Q8UAAABAAEAAQABAPcAAICFoBAAAAv+KWAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/0125cbd5bba3.js b/pr/451/docs/search.index/0125cbd5bba3.js new file mode 100644 index 000000000..834381469 --- /dev/null +++ b/pr/451/docs/search.index/0125cbd5bba3.js @@ -0,0 +1 @@ +rn_("AYACsFAT+wABoAAAAAj9Y3QBAQD0dxMAAAEAAQABAAEDAPTcDAAA1QagAAEAAYACoBAAAAxloFAAABKGY2QBAgDz8QgAAEsDAQDzAAFt854EAADFAAEAAYECoBAAABMqoHAAABNyYWPzAAFy804HAABBCiAAZw8AgIiwMA+4ANGgMAAAFHspMCM=") \ No newline at end of file diff --git a/pr/451/docs/search.index/016287c739af.js b/pr/451/docs/search.index/016287c739af.js deleted file mode 100644 index e38ce7f6e..000000000 --- a/pr/451/docs/search.index/016287c739af.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQACoDAAABCXoJAAABL7MGj1dxEAAIwBAQABAAEAE4ACoDAAAA9VZHIjAISwEAjGAySwIAu7AP+gMAAAD3oSABj1dgUAAC4AAQABAAEAAQIA9A8UAAABAAoAAQABAgDz9A8AAAEAAQDzgAJpcgEBArBAEN0AAaBQAAAS4WVz9DEOAADCAgEAfgIBCwD2ChMAAAEAowBUABYAAQDzgAJsdNOAhKCQAAASuQFEBA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/01818b9a5dfd.js b/pr/451/docs/search.index/01818b9a5dfd.js deleted file mode 100644 index 7d2cf82a1..000000000 --- a/pr/451/docs/search.index/01818b9a5dfd.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9DkSAAABAKQAAQABCAD08xIAAAEAhQABADMBA7BAEsIAAWFiZTowAAABAAAAAAAJABAAAAAEBwUHnwygDFAPvg8KEAsQEhLhEgEAADswAAABAAAfAA4ApAQBAGMFCQD1CAEA4AsBAJAMAQA2DgAAQw8CAFgPAAB3DwEAYxABAHYQAAAJEQAAXxEBAGQRAAABgQKwABD6AAGwABD8AAE0NiOAArCAE+sAAW9wAQABsEASvgABZfMFEQAASwEBALcBAICFoGAAABChEAEL") \ No newline at end of file diff --git a/pr/451/docs/search.index/01dabba3dd0f.js b/pr/451/docs/search.index/01dabba3dd0f.js deleted file mode 100644 index c604ac4dd..000000000 --- a/pr/451/docs/search.index/01dabba3dd0f.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACoGAAABNyoKAAABNkYWkBCwD0AxMAAAEAAQABAAEEAPQpEAAAAQABAAEAI4ACsEAQgQABbHLzAANjZHMBdxAAAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/0258058d4a15.js b/pr/451/docs/search.index/0258058d4a15.js deleted file mode 100644 index 3098d5aaf..000000000 --- a/pr/451/docs/search.index/0258058d4a15.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEA9DkHAAAtCNwAAQABBQD0pBIAAAEAkQABAPMCAWP12w8AAAEAvAABAAEAYwAEoDAAAAvpoHAAABFWZW5zdvP5DwAAlQATAg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/02dd940163a0.js b/pr/451/docs/search.index/02dd940163a0.js new file mode 100644 index 000000000..04ef38f41 --- /dev/null +++ b/pr/451/docs/search.index/02dd940163a0.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..58a2551b4 --- /dev/null +++ b/pr/451/docs/search.index/0302cc930c05.js @@ -0,0 +1 @@ +rn_("AYECsDAQJAABsBATnwABc3QBAwDzsRIAAAEAAQAjgAKwABGpAAFoaQEAADswAAABAAATAAEAeAcTACOAAqAwAAAMymFl84ACcnTzAAJhbDswAAABAAAoAAMAmQATAKsBAAALEBMA") \ No newline at end of file diff --git a/pr/451/docs/search.index/03099968b016.js b/pr/451/docs/search.index/03099968b016.js deleted file mode 100644 index 6b3fa5854..000000000 --- a/pr/451/docs/search.index/03099968b016.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYkCsCAT0gABoDAAABPcaXATAAOgIAAAEpiggAAAEqtlaXX1yQsAADQAAQABAAEAAYYCsJAT9wABoAAAABIoYmQBgAKwMBKMAAGgMAAAEHRydtcAAACFoDAAAA/xoDAAABLiEAETOjAAAAEAAAAAAAkAEAAAAMoIywjMCPwI/QgJEA4QxBDFEGkS") \ No newline at end of file diff --git a/pr/451/docs/search.index/03aae57c1bdc.js b/pr/451/docs/search.index/03aae57c1bdc.js new file mode 100644 index 000000000..180d12205 --- /dev/null +++ b/pr/451/docs/search.index/03aae57c1bdc.js @@ -0,0 +1 @@ +rn_("AQQBoEAAABQJZfJ/FAAAAQABAQA7MAAAAQAABgABAKYUBgDzBgFl9FQUAAABADMAAQAzBQOwYBNqAIphcnXzYRAAAHcCAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/03d8571d8861.js b/pr/451/docs/search.index/03d8571d8861.js deleted file mode 100644 index 8c8df5e4f..000000000 --- a/pr/451/docs/search.index/03d8571d8861.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIBsEAT3QABdDswAAABAAANAAYAahIDANQSAQBBEwEAWBMBANITAQD3EwEAE4ACsDASnQE/aXABBQDzGBMAAAEAAQATgAKgMAAAE3FleVMBA7BwE34AAWJlcwHFDwAAE4ADsDAT6wABsCAG0QABZWxz") \ No newline at end of file diff --git a/pr/451/docs/search.index/04067d1944a2.js b/pr/451/docs/search.index/04067d1944a2.js deleted file mode 100644 index e4352dbc5..000000000 --- a/pr/451/docs/search.index/04067d1944a2.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAAOjAAAAEAAAAAAAoAEAAAAGsPbA9UEFUQ3xBpEWoRfhJ/EggUCRTzAQF58nMTAAABACMAA6BAAAAQ/qBQAAATv2Vvc/JPDwAAwAI=") \ No newline at end of file diff --git a/pr/451/docs/search.index/040989688a54.js b/pr/451/docs/search.index/040989688a54.js new file mode 100644 index 000000000..f55da29dc --- /dev/null +++ b/pr/451/docs/search.index/040989688a54.js @@ -0,0 +1 @@ +rn_("AQYA9KETAAABAAEAAQABDwD0jBQAAAEAAQABAAEAAbDwFJYAAWs7MAAAAQAACAADAP0TAAACFAYALRQAAAECAPRbEAAAAQCyAAEA9wAAAIWxABR/AAGJMADyvhIAAAEAE4ACsBAMQQABZW8BBQA7MAAAAQAADQABAIERDQABAgDz7g8AAAEAAQABAgD06g8AAAEAAQABAAEDADswAAABAAAIAAEAnxAIAAGAAqAAAAAMFLAwFEsAAXFyAYACoEAAABD1sMAUQQABZXMBAQA7MAAAAQAAEwABAAsQEwAjgAKwkBOpAAFpbXc2AACKoFAAABQMsHAT6gABoCAAAAzdnFUROzAAAAEAAAsABABKBwAAzxADAJ4SAQAPFAQAAQIBsDARXAABcAGFEgAAs4CEoCAAABHGAigE") \ No newline at end of file diff --git a/pr/451/docs/search.index/0435c5f11e89.js b/pr/451/docs/search.index/0435c5f11e89.js deleted file mode 100644 index 49cddb36f..000000000 --- a/pr/451/docs/search.index/0435c5f11e89.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsHARUwABsCAPbwDpaHkBBQD0sQwAAAEAGwMBAAEAAPQTDAAAAQABAGoAAQIAOjAAAAEAAAAAAAkAEAAAAGQPZQ9TEA4RXRJeElcT2RMKFAsUAQMA8+oIAAABAAEA1wMAAIagQAAAEW4YQAc6MAAAAQAAAAAAFwAQAAAAOgc7B8kH9wgrCSwJsAxRD1IPcg8YEB8QIBAhECIQXRBeEJQQlRAEElUTVhOBE4IT") \ No newline at end of file diff --git a/pr/451/docs/search.index/04cd208f0148.js b/pr/451/docs/search.index/04cd208f0148.js deleted file mode 100644 index 3b98ad075..000000000 --- a/pr/451/docs/search.index/04cd208f0148.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMA8y8SAAABAAEAI4ECsSAUFgABY2sBBAGgQAAAEW5l87AMAADkAwEAAQAA9W8QAAABAAEAAQCDAnOAhKAwAAATnwgwAQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/0527185c0cd7.js b/pr/451/docs/search.index/0527185c0cd7.js new file mode 100644 index 000000000..535bd4b78 --- /dev/null +++ b/pr/451/docs/search.index/0527185c0cd7.js @@ -0,0 +1 @@ +rn_("AYACoFAAABEqoFAAABJ5YXUBgwKxABSiAAGw0BSSAAFrbQELADswAAABAAAGAAEAAhQGACOAAqCAAAAULWVvIwMDoGAAABPAsEAUnQABZW5wAREMAAAjgAKgQAAAELttcAELAPPkEwAAAQB3APcAAACFoQAAABReBIEDOzAAAAEAADsABACuBQUAQwcAAJQHMwBqEQAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/0530b4c13347.js b/pr/451/docs/search.index/0530b4c13347.js deleted file mode 100644 index b7fecce4d..000000000 --- a/pr/451/docs/search.index/0530b4c13347.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBoEAAABOfZfLJEwAAAQABAAD0ehIAAAEAAQABAPMEAWT0IBIAAAEAAQABAKcAAAKFsKATtwABsEASeAABsHATZQABFYgAAY0TAAABAAGwgBOhAAFw8p0TAAABAEMAA6AgAAAQ3KBAAAASJGRlc/N+DwAAMwMBAPOAAmVp") \ No newline at end of file diff --git a/pr/451/docs/search.index/058523a6e4f8.js b/pr/451/docs/search.index/058523a6e4f8.js deleted file mode 100644 index 1e553e4bc..000000000 --- a/pr/451/docs/search.index/058523a6e4f8.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQsA9NUTAAAMAB4ADwABCgA6MAAAAQAAAAAABwAQAAAAuhK7ElMTVBOKE4sT1hPXE/OBAm90EwCEsCASrgBZsDAQxgABoFAAABCtEDACOzAAAAEAAAgABAB9AAEAqAADACoHAQA9CQAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/05daabf53ac2.js b/pr/451/docs/search.index/05daabf53ac2.js new file mode 100644 index 000000000..ed95e45d1 --- /dev/null +++ b/pr/451/docs/search.index/05daabf53ac2.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..8d58588c1 --- /dev/null +++ b/pr/451/docs/search.index/05ff51e69bf3.js @@ -0,0 +1 @@ +rn_("AQAAOjAAAAEAAAAAAAoAEAAAAM4Pzw+8EL0QSBHZEdoR9hL3EpAUkRQBBADzyQ8AAN8AAQDzAAFx9/cMAAABADgDAQDMAGEAAQDzAAJleTowAAABAAAAAAAOABAAAAD8CEEMQgxdEHIQcxAHEQgRCRHfE+AT8BPxE2sUcBQ=") \ No newline at end of file diff --git a/pr/451/docs/search.index/0697183800b7.js b/pr/451/docs/search.index/0697183800b7.js deleted file mode 100644 index 03a11cb16..000000000 --- a/pr/451/docs/search.index/0697183800b7.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYQCoCAAABCioDAAABEJaXQBAQA7MAAAAQAAhwACAEUHAABDCYYA8wACZWz1lwQAALICdwlBAgEAAQIAOzAAAAEAAOcAAgDFCwAAwwzmAMcAAACFsLASrAABoBAAABMwsDAMwQABFDACOzAAAAEAABMBCwDSAQYAfAUAAKgF5gAUBwcAyA8CABgRDQA/EQIAExIBAJYSAQDnEgEAexMCAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/06c25d8359bd.js b/pr/451/docs/search.index/06c25d8359bd.js deleted file mode 100644 index df1605b7a..000000000 --- a/pr/451/docs/search.index/06c25d8359bd.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIAOzAAAAEAAAYAAgCTBQUAAREAAAEEAbAAEpQAAXM7MAAAAQAACwACAA4TBwD7EwMARwAAAYaggAAAEu+xABQIAAGwQBJQAAGwgBQWAAGwUBKWAAEVABb0Mg4AAAEAAQABAAEFAPOEDwAAAQABAGOAA7BAEAwAAW1ubwGBArAADJAAAbAQEAoAAXN18wACYWk6MAAAAQAAAAAABwAQAAAAOQdmD0IQQxD8Ev0S/hL/EgGAA7AAE0wAAbAAE04AAbBwE8AAATQ2YQEEAPQPFAAAAQAKAAEAI4ECoEAAABDabXABAgA7MAAAAQAADgAEADwHAQCzDAEAyhAJAAgRAAABAQA7MAAAAQAACAADAEQSAAC1EgAAHBQGAAEBADswAAABAAAKAAMA7hACACYTAwAEFAMAI4ACoDAAAA/dZGfXAwAAxqCAAAASH0BGgAg7MAAAAQAAGAAJAHsPAQCREAEAxhABANYQAwBFEQYAKhMCAHgTAADQEwEAABQBAAEDAPbnCAAAVwDHBgEAsAABABOAArBwE6EAAW9yAQQBsLAT6AABbPSxDAAAAQAbAwEAAQIAOzAAAAEAAAwAAwDHCAAAOhMFAO8TBQABAQDzww8AAAEASgIjgAKgsAAAE6BnegECADowAAABAAAAAAAJABAAAABTEVQRZxJoElETUhOlE6YTFhQXFPMAAW/yQQkAAAEAAQUA9G0PAAABAOgAAQCDAISgQAAADKawcBLaAAGwIAwrAAEQBQI7MAAAAQAACQADAJsEAwBaBQIAQhECAAGAAqAgAAAQoLAgE6EAAWR0AYQCsLAT6wABsAATRAABYXPTgISgsAAAE9RAKAQBgAKgMAAAEW2wYA+CAAFockf/AQDNoCAAAAV5oCAAAAV6sHATOAABA2c0BzswAAABAAA4ABkAfgQAALUEBQA3CQAAfgwAAD4PAQAuEAMA6RAAAEQSAABWEgUAtRIBANgSAADgEgAA8BIAAPsSAAAMEwEAFhMAABgTAgAcEwEAcRMAAHcTAACDEwEAnRMBAK8TBQDDEwAAHBQGAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/06c6b77500fe.js b/pr/451/docs/search.index/06c6b77500fe.js deleted file mode 100644 index 416fb8a5a..000000000 --- a/pr/451/docs/search.index/06c6b77500fe.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIBoKAAABLYZPLIBgAAAQABgQKgMAAAE9SgUAAAE2RjcwEEA7AgEMgAAaBQAAARbaBAAAAToGxzdvKbEwAAAQDzAANhZW87MAAAAQAACAADAGsEAQC8CwAAuwwFAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/070f6c0257e1.js b/pr/451/docs/search.index/070f6c0257e1.js new file mode 100644 index 000000000..49306476a --- /dev/null +++ b/pr/451/docs/search.index/070f6c0257e1.js @@ -0,0 +1 @@ +rn_("AQUAOzAAAAEAAAcAAwCwEQIAYRMBAPgTAgABBADzsxEAAAEAAQAjAAKgYAAAEuFhcvPUEAAAAQDIAQGAAqEQAAAUh7CAFH0AAWl1NwQAgoexUBSkAAGgAAAAELSwcBPBAAGhMAAAFJgKQBsTgAKwEBLtAAFpcg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/0717d66ebc8c.js b/pr/451/docs/search.index/0717d66ebc8c.js deleted file mode 100644 index 3d5ceee14..000000000 --- a/pr/451/docs/search.index/0717d66ebc8c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQBoEAAABOJZfL5EwAAAQABAQA7MAAAAQAABgABABwUBgDzBgFl9NATAAABAC8AAQBTAISgIAAACSqwYBLwAIcBAQnz/w8AAGICAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/0740e388ca16.js b/pr/451/docs/search.index/0740e388ca16.js deleted file mode 100644 index b4815041d..000000000 --- a/pr/451/docs/search.index/0740e388ca16.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACoCAAAA9YoDAAABNAYXLzAAF0AVEHAAABBAGgQAAAE8Zl8uYTAAABAAEFADswAAABAAALAAUA3g8FAD4SAQBnEwEAuRMAANgTAADjAISgMAAAD1aFAAI7MAAAAQAACwAFAH8FAgCSBgQACgwBAAcRAABPEgAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/0777c1b794b3.js b/pr/451/docs/search.index/0777c1b794b3.js new file mode 100644 index 000000000..94c875648 --- /dev/null +++ b/pr/451/docs/search.index/0777c1b794b3.js @@ -0,0 +1 @@ +rn_("AQAEoAAAABB/sCARMQABoFAAABHdoEAAABQgZGxzdjswAAABAAATAAYApAUAACAMAADVDAAA8Q8NAFURAAAbFAEAAQIA9LwRAAABAAEAAQAThAKwIBHAAAFhc/OAAmFl") \ No newline at end of file diff --git a/pr/451/docs/search.index/080595ed9c0d.js b/pr/451/docs/search.index/080595ed9c0d.js deleted file mode 100644 index 8d6e23374..000000000 --- a/pr/451/docs/search.index/080595ed9c0d.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9JASAAABAI8AAQABAwD0xBIAAAEAlQABAPMFAmJ1OjAAAAEAAAAAAAcAEAAAAAcQCBC4ELkQZRFmEZsSnBIBAgD08RAAAAEAbQEBAAEFAPP3EAAAAQABAAEEAPQ5EgAAAQCkAAEA84CEEQkA") \ No newline at end of file diff --git a/pr/451/docs/search.index/082363d032b8.js b/pr/451/docs/search.index/082363d032b8.js new file mode 100644 index 000000000..92fb27686 --- /dev/null +++ b/pr/451/docs/search.index/082363d032b8.js @@ -0,0 +1 @@ +rn_("AQYA9M4TAAABAFYAAQAjgAKhEAAAFItjchOAAqAgAAAI/mh5AQEBoGAAABR8c/LAEQAAAQAjgAKwIAljAAFldQGAArAgEAkAAaCQAAAULGFlAQcBsEAUSwABZfUPFAAAAQABAAEAAQABAAD0DRMAAAEAWQEBAPMEAXM7MAAAAQAADQACAIkTBwCBFAUARwAABIaggAAAE2mxABSQAAGwQBLHAAGwgBSgAAGwUBMPAAEVABb0gQ4AAAEAAQABAIMAhLBAFJYAAbAwEv4AAbAwE50AAcAAAzowAAABAAAAAAAJABAAAACxBPYQ9xCZEZoRpxGoEewT7RNfFAEDAbBAFGEAAXTyvhMAAAEAI4QCoEAAABPuYnMTgAKwwBSUAAF1eQGAAqAAAAAJHrAgEOgAAWdwAQEA9LwRAAABAAEAAQDzgAJkbLfoAACMoNAAABN1oFAAABFVoJAAABORsFAS7QABoDAAAAxZt5gHOzAAAAEAAGUAEwD/AQEABQIBAF8EAwC7BQAAtwYBAFUHAQBoBwAA+ggBAAcJAAARCQAAQwkAAHIMQgDLDAAA1g8BAK4QAQDLEAEAEBEBAJARBACgEgEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/08d9a5c3e851.js b/pr/451/docs/search.index/08d9a5c3e851.js deleted file mode 100644 index 0137afb5a..000000000 --- a/pr/451/docs/search.index/08d9a5c3e851.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMA9N8IAAABADYDAQABAwDzEwwAAAEAAQDzgAJpbwEBAbBQEIgAAWP0LwcAAAEAAQAEAiOBAqBQAAAQTGNuAQQAOzAAAAEAAAcAAwA/EQIA5xIBAHsTAgABAQA6MAAAAQAAAAAABwAQAAAAYhFjEZkSmhLxEvISmROaE/MJAWkBhxAAANcCAACHsMAS3AABoFAAAA9AoRAAABO/FAEXOzAAAAEAACQAAQCqASQA") \ No newline at end of file diff --git a/pr/451/docs/search.index/09031ecd90af.js b/pr/451/docs/search.index/09031ecd90af.js deleted file mode 100644 index 1e7250d2e..000000000 --- a/pr/451/docs/search.index/09031ecd90af.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBsEAQ3QABZQExDgAAAQUAOzAAAAEAAAYAAQDkDwYAAQMA9IMMAAABAAEAAQDzAAJwczowAAABAAAAAAAMABAAAACCBZcG4QgMDBkMgRCCECUSJhJ6EnsSfBJ9EjMAA7BQD1kAymRwcjswAAABAAAPAAYAggQAAEMFAwBGDwAAVxEDACASAwBSEgEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/090c885d9b86.js b/pr/451/docs/search.index/090c885d9b86.js new file mode 100644 index 000000000..02dec59dd --- /dev/null +++ b/pr/451/docs/search.index/090c885d9b86.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..0aaa02067 --- /dev/null +++ b/pr/451/docs/search.index/09d1502998bb.js @@ -0,0 +1 @@ +rn_("AQUBsEASpwABZfLLEgAAAQABAAA7MAAAAQAAEQAEAP0BAQCfEAgAmxEDAKQSAgABCgGwQBRLAAFl9Q8UAAABAAEAAQABAEcGAACHsCAQ+QI6sMAUlgABoCAAAAbvsDAHTAABGIkKOjAAAAEAAAAAAAwAEAAAAEgHSQcyCQsMDAwSDBMMZgxnDLUMtgz6EvsS") \ No newline at end of file diff --git a/pr/451/docs/search.index/0a8ad4b154a4.js b/pr/451/docs/search.index/0a8ad4b154a4.js deleted file mode 100644 index 35f8d54f8..000000000 --- a/pr/451/docs/search.index/0a8ad4b154a4.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsFAQgQABoLAAABNkY3MBBADzSQ8AAAEAAQABgAKgUAAAEFOgYAAAEMFpcgGAAqAgAAAMJ6BgAAAQwGVoAQEBoCAAABC/YvMjDAAAAQABABOBAqAQAAAMJmFjAYACoBAAABMwsDAMwQABZXMjAAKgQAAAE8Zld/LmEwAAAQABAwA7MAAAAQAABQACAB8MAwDUEAEAAQYA9H4SAAABAIkBAQATAAKwYBKAAAFmbfKMDAAAAQABAQD0jBAAAAEAqQABACOAAqCwAAATS2RnAYICoKAAABKwoEAAAA/5b3bzgANnbnIBgAKgQAAAEnegMAAAD0ZobLc/AACKsEAPRwABNGBXOzAAAAEAABoABAD6AQAATAQOAKoLCQB9DAAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/0a98c9fc8112.js b/pr/451/docs/search.index/0a98c9fc8112.js new file mode 100644 index 000000000..8c172d512 --- /dev/null +++ b/pr/451/docs/search.index/0a98c9fc8112.js @@ -0,0 +1 @@ +rn_("AQAA9O8QAAABAJEBAQAjgQKgYAAAEyViaQEAAPTaDwAAAQDyAAcBI4ICsFATOAABZXMBiQKwIBRWAAGgMAAAFGBpcAECAPQJEwAAAQCRAAEAAQMA9D4TAAABAJgAAQDzhQJidQECAPRaEQAAAQB7AQEA84ACaWy3AQAAhaAgAAATERIBDDowAAABAAAAAAAUABAAAABmB+8H8AfxB/IH/ws4DDkMOgw7DAEQxhCSExYUYxRkFGYUcxR0FJsUnxQBBQDz+BMAAAEAAQBDhgOwkBR9AAGgAAAAEp1iZHcBgAKwAAkMAAGwIAzwA2JkbgEDAPPYDwAAAQAEAQECAPYIDAAAAQCIAwEA4QMBAAEAAPQqCQAAAQABAAEAAQAAOzAAAAEAAAYAAgBxEwAA2RMFACOBArBAFFEAAWVz84ADZXh5AQAA9CYJAAABAAEAAQBDgAOgQAAAFHuwIBMFAAFlcngHGgAAyaCQAAATU7BQEAIAAbDAE50AAaAQAAARVqAwAAAT77BADNMAAQBHoAs6MAAAAQAAAAAACQAQAAAAZgVnBdkM2gzuDNQPLBAtEPwS/RIBBgD0BxMAAAEAkQABAPMDAWL07RAAAAEAkQEBALcMAACJoHAAABKLsJATNgABsGAQJAABoDAAABNcOIkTOjAAAAEAAAAAAC8AEAAAALcFuAW5BboFKAcpB+4I7wjwCCAJIQkNDCEM5QzmDOcM6wzsDO0M0A/RDyIQIxAnECgQRRBGEEcQSBBJEEoQSxBtEHQQjhCTEL4QvxAtES4R4RJyE5YTvRNZFGUUhxSYFA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/0ab5109df87a.js b/pr/451/docs/search.index/0ab5109df87a.js new file mode 100644 index 000000000..38fec4e4d --- /dev/null +++ b/pr/451/docs/search.index/0ab5109df87a.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..a3abca6f6 --- /dev/null +++ b/pr/451/docs/search.index/0add2bce1a52.js @@ -0,0 +1 @@ +rn_("AYACoCAAAA+3oDAAABO9YXLzAAF0AXEHAAABBAGgQAAAFEpl8mwUAAABAAEFADswAAABAAALAAUAPxAFALQSAQDkEwEAPRQAAFwUAADjAISgMAAAD7WFAAI7MAAAAQAACwAFAJwFAgCxBgQARQwBAHARAADGEgAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/0bac143bcb50.js b/pr/451/docs/search.index/0bac143bcb50.js deleted file mode 100644 index 169f369b7..000000000 --- a/pr/451/docs/search.index/0bac143bcb50.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQkA898TAAABADUAAYACoEAAABLGsCATgQABaGkBDAGgoAAAFCN38hMUAAABAAEBAPN/DwAAAQABAPOAAnR1AYECsIAT7QABoGAAABQRZHM3AwCDhqBAAAATwrBgE5YATAKQDgESAPT7EwAAAQABAAEAAQDGoFAAABJPoAAAABAZsCAQyAABoFAAABFtoQAAABP2oEAAABOgAAMCCzswAAABAAAVAAcAhgUAAOULAACSDAAAjw8NAOwQAABlEgEAmxMBAAEBAbCwE+gAAWw7MAAAAQAADAADAPgGCACxDAEAzQ8BAAEBAaAwAAAQo2jz0AgAAC4AVwgBAgD0TBEAAAEAAQABABOBArAgEVAAAWFz84ICbm/3AQCAh6AgAAAFhaBAAAALzxUBBw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/0bca21001963.js b/pr/451/docs/search.index/0bca21001963.js deleted file mode 100644 index a4edb28c8..000000000 --- a/pr/451/docs/search.index/0bca21001963.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBoAAAABK2c/RXDwAA1gMBAAMAAQECsGATowABsEAT3QABZHQ7MAAAAQAADQAGAGoSAwDUEgEAQRMBAFgTAQDSEwEA9xMBAAEBA7BwEXUAAbCAEkcAAYAS6QAQ8G1uczswAAABAAALAAIAwAgFAMkSBQCjgISgsAAAEzCxcBQWAAEIQIIBAISgcAAAEg6gcAAAEIqxABOjAAGwABKqAVkCIQHyUwUAAAEAZwQAAIc7EUdIZJ2gEAAABYWwMBKdAT+gQAAAEK0RwQw7MAAAAQAABgACAPYSAABcEwUA") \ No newline at end of file diff --git a/pr/451/docs/search.index/0bd42362d0ac.js b/pr/451/docs/search.index/0bd42362d0ac.js deleted file mode 100644 index 502a52f11..000000000 --- a/pr/451/docs/search.index/0bd42362d0ac.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQcA9HgRAAABAAEAAQAjAQKgcAAAEXdhcgFMBwAAAQMAOzAAAAEAAAgAAgAvDwYAxxIBAPMAAmN0OzAAAAEAAFYAJwCIBAsASgUAAN0IAQDdCwAA4gsCAJoMAACmDAIAuAwBAFUPAABaDwEAXQ8BAGMPAADrDwIA8g8BAPcPAQAXEAAAihAAAJAQAADdEAEA6hABAP4QAQACEQIAWxEBAG4RAgB0EQAAJBIAAC0SAQAyEgEAeBIBAIgSAQCMEgEArBIBALwSAwDCEgEAIhMBADQTAACfEwAAxBMEAM0TAgA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/0bdf527b5d3f.js b/pr/451/docs/search.index/0bdf527b5d3f.js new file mode 100644 index 000000000..c3312182d --- /dev/null +++ b/pr/451/docs/search.index/0bdf527b5d3f.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..1aabec6b9 --- /dev/null +++ b/pr/451/docs/search.index/0c258f5e1776.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..1c751afaf --- /dev/null +++ b/pr/451/docs/search.index/0c57b4f8c6b4.js @@ -0,0 +1 @@ +rn_("AQAA9F0TAAABAAEAAQABBADzqA8AAAEAAQABgAKgUAAAELugYAAAESppcgGAAqAgAAAMYqBgAAARKWVoAQEA814MAAABAAEAE4ECoBAAAAxhYWMBAwA7MAAAAQAABQACAFoMAwA9EQEAAQYA9PYSAAABAJkBAQATAAKwYBL4AAFmbfLPDAAAAQAjgAKgwAAAE8hucqcPAACIoEAAAA+lsEAPpgABNCAXOzAAAAEAABkAAwAHAgAAYwQOAN8LCQABgAOwcBN8AAGwMBMmAAGwgBO1AAFjcnMBAAGwIBN8AAFvOjAAAAEAAAAAAAkAEAAAAMMRxBHeEt8SzhPPEyUUJhSgFKEUAYACsCAJFAABsDATMQABcnT3BAAAyKAgAAAHaKBgAAAP1bCAEzEAAUAGkgn2EwIAAHQC2AQBAIIKAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/0ca93a7be19c.js b/pr/451/docs/search.index/0ca93a7be19c.js deleted file mode 100644 index 9f0b27b33..000000000 --- a/pr/451/docs/search.index/0ca93a7be19c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMA8+sPAAABAAEAAYACoLAAABOMsDAS3AABbnRDAAOgIAAAEJawUBFnAAFlaGk7MAAAAQAABgADAMcLAAB4EQMAOBMBAPOAAmRy") \ No newline at end of file diff --git a/pr/451/docs/search.index/0d6bfa8c2af1.js b/pr/451/docs/search.index/0d6bfa8c2af1.js deleted file mode 100644 index e16e6edf4..000000000 --- a/pr/451/docs/search.index/0d6bfa8c2af1.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEA9XsFAAC+DAEApAABAAEAADswAAABAAAHAAMAAgIBABwMAQCiDwMAAYMCsHATAQABsIATOAABY3MBBQD0pBIAAAEAkQABAPMIAWP12w8AAAEAvAABAAEAAQABoDAAABEFdPJEEAAAAQABgAKgAAAAEBWgAAAAEBY0NvMDAXYBrQwAAJcAAACFsKASiAABsCAL2gABsIASZwABCQQGOjAAAAEAAAAAAAkAEAAAANsG3AbdBugLfQ/bEBgSGRIaEr8TAQIA9JASAAABAI8AAQABAwD0xBIAAAEAlQABAPMAAmJ1OjAAAAEAAAAAABUAEAAAABwHHQceB2EPYg8HEAgQJhAnELgQuRBlEWYRjhKPEpsSnBKrEtYS1xIeEx8TAQEA9HcFAAABACQHAQC3AACAhbEAE6UAAbAAByYAAQwIRAEIADswAAABAAAFAAEASRIFANcNAACIsEAQqAABoCAAAAy3uQALOzAAAAEAAA8ABQByAAEA0wYHADUHAQD6DwEApBABAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/0d6fbddc62d7.js b/pr/451/docs/search.index/0d6fbddc62d7.js new file mode 100644 index 000000000..29056ac93 --- /dev/null +++ b/pr/451/docs/search.index/0d6fbddc62d7.js @@ -0,0 +1 @@ +rn_("AYACoGAAABPvoKAAABPhYWkBCwD0fhMAAAEAAQABAAEEAPSPEAAAAQABAAEAI4ACsEAQ6wABbHLzAANjZHMB4BAAAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/0e2aac44d870.js b/pr/451/docs/search.index/0e2aac44d870.js deleted file mode 100644 index e0ea83a05..000000000 --- a/pr/451/docs/search.index/0e2aac44d870.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9AwJAAABALUHAQABCQGggAAAFBJmAQATAAABAgD1WRAAAAEAAQBLAgEA8wACaXU6MAAAAQAAAAAABwAQAAAANhBcEkQTRRN1E3YTlBOVEwEIAPWnEwAAAQABAAEAAQDzAgFm9RoQAAABAAEAAQABABOAArCgE+sAAXJ08wACYXPzSBAAAAEAAQDzgAJ0dg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/0e4d0f710b2b.js b/pr/451/docs/search.index/0e4d0f710b2b.js deleted file mode 100644 index df57f1ea5..000000000 --- a/pr/451/docs/search.index/0e4d0f710b2b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMA9qwMAADFAloAAQC4AgEAAQQAOzAAAAEAAAoABAAPEAUAQBIBAJ4SAQC6EwAAAYYCoAAAABLAoAAAABLBZHLzAgF2AewSAADzgAJlbycBAADHoDAAAA9woHAAABIRoIAAABONoNAAABMwoLAAABONQEUICTswAAABAAAJAAMAmQUCAPIGBAANDAEAI4ACU5KIn+d+ZHIBAAA6MAAAAQAAAAAACgAQAAAAIAkhCaYP2hLbEgoTCxOuEwIUGBQZFBOAAqAwAAAQJWx084ACYWU=") \ No newline at end of file diff --git a/pr/451/docs/search.index/0f30d894e39f.js b/pr/451/docs/search.index/0f30d894e39f.js deleted file mode 100644 index 48b803886..000000000 --- a/pr/451/docs/search.index/0f30d894e39f.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYYCoAAAABLAoAAAABLBZHIBgAKgcAAAE1CgMAAAEJNmdAELAaBAAAATn2XyyRMAAAEAI4ECsFAQawABY3YBAAGwgBOhAAFw8p0TAAABAEMGA6AgAAAQ3KBAAAASJGRlc/N+DwAAMwMBAPMABGlyc3Y7MAAAAQAADwAFAMoGBQBJEgUAdRIBAOwSAADUEwAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/100cd8e013cf.js b/pr/451/docs/search.index/100cd8e013cf.js deleted file mode 100644 index 638ecb111..000000000 --- a/pr/451/docs/search.index/100cd8e013cf.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQBoEAAABOJZfL5EwAAAQABAQA7MAAAAQAABgABABwUBgDzBgFl9NATAAABAC8AAQAzBQOwYBLwAIdhcnXz/w8AAGICAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/1074492a6ce1.js b/pr/451/docs/search.index/1074492a6ce1.js deleted file mode 100644 index 303d4f5fe..000000000 --- a/pr/451/docs/search.index/1074492a6ce1.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMAOzAAAAEAAD8AAgDtCAAANww+AAGAArBQEEQAAbCwEwwAAWFsYwAEsEAPPgABoBAAAA/9YnBydjswAAABAAAGAAEA4BAGAAGAArCAEI4CE7DgE3MAAWlyAQMBsEAQ3QABZQExDgAAAQUAOzAAAAEAAAYAAQDkDwYAAQMA9IMMAAABAAEAAQDzAAJwczowAAABAAAAAAAMABAAAACCBZcG4QgMDBkMgRCCECUSJhJ6EnsSfBJ9ElcAAACFsFAQNgImsFAPWQDKoEAAABEISIADOzAAAAEAAA8ABgCCBAAAQwUDAEYPAABXEQMAIBIDAFISAQDTAISgYAAADJUQCAbzoAEAAAEA1wo=") \ No newline at end of file diff --git a/pr/451/docs/search.index/10ae14a3503e.js b/pr/451/docs/search.index/10ae14a3503e.js new file mode 100644 index 000000000..1f7e121fe --- /dev/null +++ b/pr/451/docs/search.index/10ae14a3503e.js @@ -0,0 +1 @@ +rn_("AQIAOjAAAAEAAAAAAA4AEAAAAPoM+wz6EN4R3xHgEZkSohKjEqcSqBIAEwETOBM5EwEJAPNgEQAAAQABAAEKAPOxEgAAAQABAAEGAPUkEQAAAQABAAEAAQDzgAJkbnMAhLEgFCMAAQARA/ILEwAAAQABDQD2hRMAAAEArABYABgAAQABgAKwoBOdAAGgEAAADCRjdQEBAPSvEwAAAQBLAAEA8wABZfJjCQAAAQD3AACAxbAwEJ0AAQACqAg=") \ No newline at end of file diff --git a/pr/451/docs/search.index/116465ed6584.js b/pr/451/docs/search.index/116465ed6584.js new file mode 100644 index 000000000..2a7a8321b --- /dev/null +++ b/pr/451/docs/search.index/116465ed6584.js @@ -0,0 +1 @@ +rn_("AQcA9HcTAAABAAEAAQDzBQF0OzAAAAEAAAgAAgD0CwAAJgwHAAEFAPOTEwAAAQABADcAAICFsDAQMwABoKAAABOxoDAAABPuEEFI") \ No newline at end of file diff --git a/pr/451/docs/search.index/117405adffd2.js b/pr/451/docs/search.index/117405adffd2.js deleted file mode 100644 index d38754ec4..000000000 --- a/pr/451/docs/search.index/117405adffd2.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQ0A9UYTAAABAAEAcwABAAECAPSFEAAAAQCGAQEAAQMA9PEQAAABAG0BAQDzAAJidQGfDwAAAQMA9IMQAAABAIYBAQDzhAJkcwECAPPbCAAAAQBYAPOAAml0ARgA8yAUAAABAAEAI4ECsMATRAABY20BBQGgYAAAEjRy9NcLAAABAMUDAQABCQD2XRIAAAEA+QCCADEAAQATgAKwYBBGAAFkbgEDAaBwAAASmGf0LQcAAAEABAIBAAEJAPPfEwAAAQA1AAGBArCAE+0AAaBgAAAUEWRzFwEAiYWw0BQTAAGgQAAAE8KwYBOWAEwCkApnXgAAi7BwEKgAAbCwEqYAAaCgAAASXLDwE3UAAW2BD/SAAAAAAQABAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/120419dec60e.js b/pr/451/docs/search.index/120419dec60e.js deleted file mode 100644 index e087520fa..000000000 --- a/pr/451/docs/search.index/120419dec60e.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABsIAToQABcPKdEwAAAQBDBQOgIAAAENygQAAAEiRkZXPzfg8AADMDAQABAgA7MAAAAQAAEwABAKkPEwATgYSgUAAAEIugEAAAC9ygIAAADJscQAAzgAOwUBLtAAFhZm4=") \ No newline at end of file diff --git a/pr/451/docs/search.index/120ac1721451.js b/pr/451/docs/search.index/120ac1721451.js new file mode 100644 index 000000000..bfe023233 --- /dev/null +++ b/pr/451/docs/search.index/120ac1721451.js @@ -0,0 +1 @@ +rn_("AYMCsQAUogABsNAUkgABa20BCwA7MAAAAQAABgABAAIUBgAjgAKggAAAFC1lbyMEA6BgAAATwLBAFJ0AAWVucAERDAAAAQsA8+QTAAABAHcAdwAAAIWgYAAAEnmhAAAAFF4EgQM7MAAAAQAAOwAEAK4FBQBDBwAAlAczAGoRAABDgAOwgBFpASGgAAAAAhRka3Q=") \ No newline at end of file diff --git a/pr/451/docs/search.index/12ff91055174.js b/pr/451/docs/search.index/12ff91055174.js new file mode 100644 index 000000000..a54e219a0 --- /dev/null +++ b/pr/451/docs/search.index/12ff91055174.js @@ -0,0 +1 @@ +rn_("AYADsAATyQABsAATywABsHAURAABNDZhAQQA9JkUAAABAAoAAQAjgQKgQAAAEUNtcAECADswAAABAAAOAAQAXgcBAPUMAQAzEQkAcREAAAEBADswAAABAAAIAAMAuhIAAC8TAACmFAYAAQEAOzAAAAEAAAoAAwBXEQIAoRMDAIwUAwAjgAKgMAAAED5kZ9cDAADGoIAAABKUQEaACDswAAABAAAYAAkA3Q8BAPsQAQAvEQEAPxEDALYRBQClEwMA9RMAAFQUAQCIFAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/133a2aaf2619.js b/pr/451/docs/search.index/133a2aaf2619.js deleted file mode 100644 index 84f8e1255..000000000 --- a/pr/451/docs/search.index/133a2aaf2619.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUAOzAAAAEAAAcAAwA/EQIA5xIBAHsTAgABBADzQhEAAAEAAQAjAAKgYAAAEmlhcvNrEAAAAQC8AQGAAqEQAAAT/7CAE/cAAWl1NwQAgYexUBQaAAGgAAAAEEywcBNEAAGhMAAAFA4KQBsjgAKgMAAAEgludg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/1363513dee0c.js b/pr/451/docs/search.index/1363513dee0c.js new file mode 100644 index 000000000..d3875e06d --- /dev/null +++ b/pr/451/docs/search.index/1363513dee0c.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..7149e6204 --- /dev/null +++ b/pr/451/docs/search.index/139d49b47758.js @@ -0,0 +1 @@ +rn_("AQMA80wQAAABAAEAAYACoLAAABQMsDATVgABbnRDAAOgIAAAEQCwUBHXAAFlaGk7MAAAAQAABgADAP0LAADoEQMAtRMBAPOAAmRy") \ No newline at end of file diff --git a/pr/451/docs/search.index/13baa5793c64.js b/pr/451/docs/search.index/13baa5793c64.js deleted file mode 100644 index 2b775e4bb..000000000 --- a/pr/451/docs/search.index/13baa5793c64.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABsKASQAABZTswAAABAAAJAAIAtwcHAIASAQABAAGgIAAADJVtAV8FAAABgAKwoBI+AAGwQA9fAAFsbwEAAPR6EgAAAQABAAEA8wQBZPQgEgAAAQABAAEAIwKEsKATtwABsEASeAABsHATZQABFYAAAY0TAAABAAGwgBOhAAFw8p0TAAABAEMAA6AgAAAQ3KBAAAASJGRlc/N+DwAAMwMBAPOBAmVp1wIAAIawIBIFAAGw0BOlAAEXAQL0gwQAAAEAAQDCAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/1507fd45a0d0.js b/pr/451/docs/search.index/1507fd45a0d0.js deleted file mode 100644 index ebf3252a2..000000000 --- a/pr/451/docs/search.index/1507fd45a0d0.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQsBoEAAABOfZfLJEwAAAQABBQD0eBEAAAEAAQABAAGDArAAEGsAAbBQEucAAWR3E4ACoKAAABNYZXYBAgD0hRAAAAEAhgEBAAEAAPR3DwAAAQDrAAEBI4ICsFASvgABZXMBAgD0kBIAAAEAjwABAAEDAPTEEgAAAQCVAAEA84UCYnUBAgD08RAAAAEAbQEBAPOAAmls8wADYnR1OjAAAAEAAAAAAA8AEAAAAEQH0QfSB9MH1AefD1wQFxOWE98T4BPiE+0T7hMRFBUUAQwA83sTAAABAAEAAYACsAAI6AABoCAAAAyuZG4BBgD0jhIAAAEAjwABAPMDAWL0gxAAAAEAhgEBAJcGAACHsEAPdQABoHAAABIWsJASvAABOAkDOjAAAAEAAAAAACQAEAAAAJwFnQWeBZ8FBgcHB+YLowykDKUMqQyqDKsMbQ9uD8EPwg/GD8cP5A/lD+YP5w/oD+kP6g8oEC0QVhBXEPcSGxNAE9UT4RP/Ew4UAQMA9KISAAABAEUBAQDzgAJjbXcBAIDFsYAUHgABQIEECA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/15883d436710.js b/pr/451/docs/search.index/15883d436710.js deleted file mode 100644 index 184d98bca..000000000 --- a/pr/451/docs/search.index/15883d436710.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYECoBAAABKwoHAAABL3YWMBhwKwABNMAAGwABNOAAE0NlMAhKCQAAAS2LCwE3MAAYEACfX3BgAANQDtCA0BGAA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/1593ad1b0859.js b/pr/451/docs/search.index/1593ad1b0859.js deleted file mode 100644 index 5c0f977d5..000000000 --- a/pr/451/docs/search.index/1593ad1b0859.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACoFAAABDBoFAAABIEYXUBgwKxABQYAAGw0BQKAAFrbQEEAaCgAAAUI3fyExQAAAEAAQsA9IUTAAABAAEAAQAjgAKggAAAE61lb2MDA6BgAAATQ2VucAHZCwAAI4ACoEAAABBTbXABCgDzZxMAAAEAcAATgAKiEAAAFCNsb/cAAACFoQAAABPaBIEDOzAAAAEAADsABACTBQUAIQcAAHQHMwABEQAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/15d820844df2.js b/pr/451/docs/search.index/15d820844df2.js deleted file mode 100644 index af028a449..000000000 --- a/pr/451/docs/search.index/15d820844df2.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABsIATUQABdAHjCAAAAQkA8zsSAAABAAEA84ACY2cBgwKgkAAAE5ugkAAAE5wyMwGAAqBQAAATw6CAAAAS7GNl8wACc3TyEgcAAAEAAQABoLAAABM0YvPBEgAAIgEBACOAAqAAAAASwGRyI4ACoFAAABONY2XzgAJpeQEGAPW6EAAAAQABAAEAAQABAQGwQBPHAAFl9Y8TAAABAAEAAQABAPcAAICFoBAAAAvIKWAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/162fe2003634.js b/pr/451/docs/search.index/162fe2003634.js new file mode 100644 index 000000000..cb0e8a47d --- /dev/null +++ b/pr/451/docs/search.index/162fe2003634.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..351619b2e --- /dev/null +++ b/pr/451/docs/search.index/16850befe1eb.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..ae4c7e0c3 --- /dev/null +++ b/pr/451/docs/search.index/169efb3fe210.js @@ -0,0 +1 @@ +rn_("AQIBoDAAABBdYQFEDAAAAYECoCAAABDfsFASoAABYW8BAwGgAAAAE8hzAd4QAAAjgAKgAAAAB+ZydmMAA7BQEVAAAWNlczswAAABAAAIAAQA9QgCACMJAQBVEAIAhhIAAAGAArDQFJQAAaCQAAATkWJoJwEAgIWwUBEQAAGw8BOpAAGwIA+TAAFEIQTzgAJudA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/16ea9b1933a4.js b/pr/451/docs/search.index/16ea9b1933a4.js deleted file mode 100644 index dd302a4db..000000000 --- a/pr/451/docs/search.index/16ea9b1933a4.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQYA9H4SAAABAIkBAQATAgKwYBKAAAFmbfKMDAAAAQAjggKwQBKsAAFlbAEFAPRrEwAAAQBJAAEA8wMBY/JzEgAAAQABgQKgAAAACPugMAAAD1xzdBOAAqAQAAAHIG5404CEsHAUFgABkEAB") \ No newline at end of file diff --git a/pr/451/docs/search.index/17a463b91cd9.js b/pr/451/docs/search.index/17a463b91cd9.js deleted file mode 100644 index d93bc1b09..000000000 --- a/pr/451/docs/search.index/17a463b91cd9.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYMCoJAAABOboJAAABOcMjMBgAKgUAAAE8OggAAAEuxjZfMAAnN08hIHAAABAAEAAaCwAAATNGLzwRIAACIBAQAjgAKwAA95A0dkcgEAAbCAE6EAAXDynRMAAAEAQwADoCAAABDcoEAAABIkZGVz834PAAAzAwEAE4ICoCAAABONZWnzgAJjZQEEADswAAABAAAJAAQAKwkBAB8QAwBdEAEAVRMBALOAhKCQAAAT9RABQg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/17ac3cf6f003.js b/pr/451/docs/search.index/17ac3cf6f003.js new file mode 100644 index 000000000..05164e3b5 --- /dev/null +++ b/pr/451/docs/search.index/17ac3cf6f003.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..70b88d1dc --- /dev/null +++ b/pr/451/docs/search.index/17b7a87bec39.js @@ -0,0 +1 @@ +rn_("AQDKoRAAABRfgAjyABrNoEAAABD6sGAUTwABoBAAAAxxsFAT5gABsDARXAABoAAAAAwvsSAUcQABsEAQ+AIiQE6wCTowAAABAAAAAAAUABAAAAAICQkJHAkdCfcL+Av5C/oLAAwFDMoMhRKxErISsxJtE24TgxOEE/YT9xM=") \ No newline at end of file diff --git a/pr/451/docs/search.index/18eec5319d0e.js b/pr/451/docs/search.index/18eec5319d0e.js deleted file mode 100644 index 15b31f5ef..000000000 --- a/pr/451/docs/search.index/18eec5319d0e.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQkA898TAAABADUAAQ0BoKAAABQjd/ITFAAAAQABgQKwgBPtAAGgYAAAFBFkczcBAIaFoEAAABPCsGATlgBMApAK") \ No newline at end of file diff --git a/pr/451/docs/search.index/190a93e1a007.js b/pr/451/docs/search.index/190a93e1a007.js new file mode 100644 index 000000000..c53373144 --- /dev/null +++ b/pr/451/docs/search.index/190a93e1a007.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..85c4db13b --- /dev/null +++ b/pr/451/docs/search.index/19705dd8c3fb.js @@ -0,0 +1 @@ +rn_("AQMBoAAAABMwc/S2DwAA8wMBAAQAAQECsGAUIwABsEAUYQABZHQ7MAAAAQAADQAGAOISAwBOEwEAvhMBANUTAQBWFAEAfRQBAAEBA7BwEeUAAbCAEr4AAYATYwAQ+m1uczswAAABAAALAAIA5AgFAEMTBQCjgISwsBOsAAGxcBSgAAEIQIIBgAKwMBSUAAGxABQjAAFjbmcBAACFYI9wTDUWsDATFgFKEYEIOzAAAAEAAAYAAgBxEwAA2RMFAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/19baf510d15e.js b/pr/451/docs/search.index/19baf510d15e.js deleted file mode 100644 index 20caee6c1..000000000 --- a/pr/451/docs/search.index/19baf510d15e.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABoEAAABAtdPWeDAAAHwPXAgEAtgDzAAFzOzAAAAEAABgACACUBAIA3gsBAHUPAQA3EAEAdBABAF0RAQAOEwcA+xMDACOAArEQFAAAAWF0") \ No newline at end of file diff --git a/pr/451/docs/search.index/19d0359d569b.js b/pr/451/docs/search.index/19d0359d569b.js deleted file mode 100644 index 94eb07afc..000000000 --- a/pr/451/docs/search.index/19d0359d569b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEBoEAAABPGZfLmEwAAAQABgAOgMAAAEhCw4BOlAAGwkBPoAAFhY22HAQCAhrAQEKgAAbAQEMgAAaCgAAAT1KDwAAAT9TFJAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/1a66cd07757a.js b/pr/451/docs/search.index/1a66cd07757a.js deleted file mode 100644 index bde846ae8..000000000 --- a/pr/451/docs/search.index/1a66cd07757a.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQBoEAAABD/ZQHADwAAAQgA9DoRAAABAAEAAQABAwGgQAAAEP5lAU8PAAABAgDz4gsAAAEAAQAjAAKwIAvTAAFrcvbGBgAAAQC5CecAAQAkAvOAAnJ4AQMBsIATwAABdjswAAABAAALAAMAkRABAEURBgAqEwIAI4ACsGAQCQC7YXIBAwGgAAAAD79z8k0PAAABACOAAqCwAAATUGVoAQMBoEAAABCQZfLkCAAAAQDXCwAAirAgBzgJh7CwEqIAAaCAAAARPqAAAAASr58ITDowAAABAAAAAAAKABAAAAD7AVwEXQReBL4LvwsjDCQMJQywEsYS") \ No newline at end of file diff --git a/pr/451/docs/search.index/1b2b113ba497.js b/pr/451/docs/search.index/1b2b113ba497.js deleted file mode 100644 index 4c86a3eb8..000000000 --- a/pr/451/docs/search.index/1b2b113ba497.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQYAOzAAAAEAAAkAAQDKEAkAAQIAOzAAAAEAABMAAQCpDxMAE4GEoFAAABCLoBAAAAvcoCAAAAybHEAAE4ACsFAS7QABYW4zgAOwMA9RAAFpc3Q=") \ No newline at end of file diff --git a/pr/451/docs/search.index/1b7cc07f1f34.js b/pr/451/docs/search.index/1b7cc07f1f34.js deleted file mode 100644 index 8031a5eef..000000000 --- a/pr/451/docs/search.index/1b7cc07f1f34.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYkCsCAT0gABoDAAABPcaXATAAOgIAAAEpiggAAAEqtlaXX1yQsAADQAAQABAAEAAYYCsJAT9wABoAAAABIoYmQBgAKwMBKMAAGgMAAAEHRydtcAAAKFoDAAAA/xoDAAABLiEAETOjAAAAEAAAAAAAkAEAAAAMoIywjMCPwI/QgJEA4QxBDFEGkSAQMBsDASpgABc/NIEAAAAQABAAEAADswAAABAAAKAAMA7hACACYTAwAEFAMAlwEAAIWgIAAABYmwIAfVAAFQGAH1LQwAAAEAAQABAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/1bf00b4f0c44.js b/pr/451/docs/search.index/1bf00b4f0c44.js deleted file mode 100644 index be881b667..000000000 --- a/pr/451/docs/search.index/1bf00b4f0c44.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYMCsQAUGAABsNAUCgABa20BCgDzZxMAAAEAcAATgAKiEAAAFCNsbzMChKBgAAASBKEAAAAT2gSAAzswAAABAAA0AAIAIQcAAHQHMwA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/1c262f510d82.js b/pr/451/docs/search.index/1c262f510d82.js deleted file mode 100644 index a323c4011..000000000 --- a/pr/451/docs/search.index/1c262f510d82.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEBoGAAABP2c/JQEQAAAQABCAD1pxMAAAEAAQABAAEAowCEsCAJPwABoIAAABJcMEAIOzAAAAEAAAUAAgDkAQAAGhAEAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/1c28e9635eec.js b/pr/451/docs/search.index/1c28e9635eec.js new file mode 100644 index 000000000..64f4ac0e2 --- /dev/null +++ b/pr/451/docs/search.index/1c28e9635eec.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..9b685d998 --- /dev/null +++ b/pr/451/docs/search.index/1c36524cf913.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..d7c72fd39 --- /dev/null +++ b/pr/451/docs/search.index/1c43b144cda4.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..d15bae0da --- /dev/null +++ b/pr/451/docs/search.index/1cbd4fd00132.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..7da1ec9cb --- /dev/null +++ b/pr/451/docs/search.index/1cd53c18f140.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..f67c21cf5 --- /dev/null +++ b/pr/451/docs/search.index/1d05d97e46ea.js @@ -0,0 +1 @@ +rn_("AQQA9JQQAAABAAEAAQABAAA6MAAAAQAAAAAACgAQAAAAzg/PD7wQvRBIEdkR2hH2EvcSkBSRFPMBAXny8BMAAAEAAQADoKAAABQ+oCAAABN1sDARzwABZWl08toSAAABAAEDADswAAABAAAGAAEASREGAAEIAPTfEwAAAQCLAAUAEwEDsJAUSAABsFATJAFnZ2xwOzAAAAEAABIACQBPEAIA0hEBABITAQA0EwEAaxMBANATAQAKFAEAGRQBAFoUAQABgAKwMBHLAAGwMBHNAAFydgECAbAAEpoAAWQ7MAAAAQAABQACAMcRAwDJEgEAI4ECoJAAABQ9aW8BBwD0WRQAAAwAIgARAHcWAAGJsFAT8AABsFASnAEWsGATIgABtQkDOjAAAAEAAAAAAA4AEAAAAMwMzQzODIYOhw61ELYQ8RAUEXMRdBF1EXYRGRNTE0OAA6AgAAAMvqAQAAAMy21ubwEDAPRMEAAAAQABABkBAYACoLAAABQMsDATVgABbnRDAAOgIAAAEQCwUBHXAAFlaGk7MAAAAQAABgADAP0LAADoEQMAtRMBAPOAAmRyhwsAAAqAAfQAEAGAAfcAEAGgUAAAEXCgUAAAFEOgMAAAEH+gMAAAEIowMWNlaW9zdHV39YwHAAABACEI1gJdAQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/1d3f2d3bd8f6.js b/pr/451/docs/search.index/1d3f2d3bd8f6.js deleted file mode 100644 index af8545e4e..000000000 --- a/pr/451/docs/search.index/1d3f2d3bd8f6.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBoAAAABK2c/RXDwAA1gMBAAMAAQECsGATowABsEAT3QABZHQ7MAAAAQAADQAGAGoSAwDUEgEAQRMBAFgTAQDSEwEA9xMBAAEBA7BwEXUAAbCAEkcAAYAS6QAQ8G1uczswAAABAAALAAIAwAgFAMkSBQCjgISgsAAAEzCxcBQWAAEIQIJHAQAAhTsRR0hknbEQE6MAAbAwEp0BPxGBCDswAAABAAAGAAIA9hIAAFwTBQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/1d8c8f6f3340.js b/pr/451/docs/search.index/1d8c8f6f3340.js deleted file mode 100644 index 0aad66da1..000000000 --- a/pr/451/docs/search.index/1d8c8f6f3340.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBsDASxwABY/MCEQAAAQABAAEDAYAS6QAQ8HM7MAAAAQAABQABAMkSBQAjggKgkAAAE8NpcwcBAIDFsFASkgABoKAAABMxsHAS+AABoNAAABPPgAEYCPOAAmRy") \ No newline at end of file diff --git a/pr/451/docs/search.index/1d98360e7dff.js b/pr/451/docs/search.index/1d98360e7dff.js new file mode 100644 index 000000000..6ad9fe823 --- /dev/null +++ b/pr/451/docs/search.index/1d98360e7dff.js @@ -0,0 +1 @@ +rn_("AQIA9ZARAAABAAEAAQABAAGAArBgEvoAAaAgAAAQ5W95AQABoMAAABQgafRSEwAACAAcABsAAQMA9HcTAAABAAEAAQABBgD0jBQAAAEAAQABAAECAPTjEAAAAQCaAAEAAQIBoEAAABMpcjswAAABAAAHAAIA4RABAHgRBQDzgAJhb2cHAACHsIATKABasKATnwABDDAHOzAAAAEAAA4ABACMBAUACgwAAKsRAwCQEgMApwAAgIWg8AAAE7KwUBA1AAGgkAAAEW+UgQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/1dbcf54491ae.js b/pr/451/docs/search.index/1dbcf54491ae.js deleted file mode 100644 index 032eb5f2c..000000000 --- a/pr/451/docs/search.index/1dbcf54491ae.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEA9A0HAAABAAEAAQABAQGgQAAAEQVz9egLAADzBD0BAQABABOCAqAgAAAMt2VyI4ADoEAAAAvnsDAI9QABZW90AQAA9AkHAAABAAEAAQABAgD0DBMAAAEADwABABOAArBAE70AAWJ284ACb3gBCgD1RhMAAAEAAQBzAAEAAYECoCAAABB2sFASKwABYW8BBAGgAAAAE0tzAXUQAABjAwOwUBDnAAFjZXP20QgAAAEAAQAsAAEAEQkBAQD0gwwAAAEAAQABACOAArAwEqoBWXJ08wCGAcAH8uMBAADeBQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/1ddac7964cac.js b/pr/451/docs/search.index/1ddac7964cac.js new file mode 100644 index 000000000..1ea25fb9f --- /dev/null +++ b/pr/451/docs/search.index/1ddac7964cac.js @@ -0,0 +1 @@ +rn_("AQEBsFAPvgABY/SaBAAAAQABAMYAAQABoIAAABScZgF7EwAA8wYBc/LxDAAAjwQzAAOgYAAAFHxjaXPywBEAAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/1de07b6bd5ed.js b/pr/451/docs/search.index/1de07b6bd5ed.js deleted file mode 100644 index 0b2e4a6f0..000000000 --- a/pr/451/docs/search.index/1de07b6bd5ed.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsAAPpwABsCAMEQABZWkBiQKwIBPSAAGgMAAAE9xpcBMAA6AgAAASmKCAAAASq2VpdfXJCwAANAABAAEAAQABhgKwkBP3AAGgAAAAEihiZAGAArAwEowAAaAwAAAQdHJ21wAAAIWgMAAAD/GgMAAAEuIQARM6MAAAAQAAAAAACgAQAAAAygjLCMwI/Aj9CNULCRAOEMQQxRBpEmOAA7AQCL4AAWRtdA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/1dff5e6ecf7f.js b/pr/451/docs/search.index/1dff5e6ecf7f.js deleted file mode 100644 index b0cc7a764..000000000 --- a/pr/451/docs/search.index/1dff5e6ecf7f.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAAOjAAAAEAAAAAAAoAEAAAAGsPbA9UEFUQ3xBpEWoRfhJ/EggUCRQBBADzZg8AANwAAQDzAAFx97UMAAABABkDAQDDAGIAAQDzAAJleTowAAABAAAAAAAOABAAAADYCAYMBwz8DwwQDRCdEJ4QnxBiE2MTcxN0E+UT6hM=") \ No newline at end of file diff --git a/pr/451/docs/search.index/1e2e1d96899e.js b/pr/451/docs/search.index/1e2e1d96899e.js new file mode 100644 index 000000000..90d7390bf --- /dev/null +++ b/pr/451/docs/search.index/1e2e1d96899e.js @@ -0,0 +1 @@ +rn_("AYACsHASfAABsCAUlgABaW8BAAGw8BSWAAFrOzAAAAEAAAgAAwD9EwAAAhQGAC0UAADzBQFk8r4SAAABAAECAPPBEAAAAQABAPMAAWk6MAAAAQAAAAAABwAQAAAAnBDTEsETwhPyE/MTFBQVFCODArCwFHEAAWFzAYECoKAAABMqoEAAABBab3bzgIQBUAE=") \ No newline at end of file diff --git a/pr/451/docs/search.index/1e93e59e94d9.js b/pr/451/docs/search.index/1e93e59e94d9.js deleted file mode 100644 index 1e17c6d75..000000000 --- a/pr/451/docs/search.index/1e93e59e94d9.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIBsEAT3QABdDswAAABAAANAAYAahIDANQSAQBBEwEAWBMBANITAQD3EwEAE4ACsDASnQE/aXABBQDzGBMAAAEAAQATgAKgMAAAE3FleVMAA7BwE34AAWJlcwHFDwAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/1ebad890e62e.js b/pr/451/docs/search.index/1ebad890e62e.js deleted file mode 100644 index 39b6ba28a..000000000 --- a/pr/451/docs/search.index/1ebad890e62e.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEAOjAAAAEAAAAAAAgAEAAAANgI/A+dEJ4QnxBiE2MT5RPqEwECAPQyEAAAAQABAAEAE4ACoNAAABPUY3NTgAOgcAAAE89laW+DgISwIBMIAAGgIAAAD3CwAAckAAEKCAQ=") \ No newline at end of file diff --git a/pr/451/docs/search.index/1ec93e54aaec.js b/pr/451/docs/search.index/1ec93e54aaec.js new file mode 100644 index 000000000..abbaa876a --- /dev/null +++ b/pr/451/docs/search.index/1ec93e54aaec.js @@ -0,0 +1 @@ +rn_("AQIBsEAUYQABdDswAAABAAANAAYA4hIDAE4TAQC+EwEA1RMBAFYUAQB9FAEAE4ACsDATFgFKaXABBQDzkxMAAAEAAQATgAKgMAAAE+5leVMCA7BwE/sAAWJlcwEmEAAAEwACoCAAAAWaZm31gQAAANgGAQDUAQEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/1ee39b692949.js b/pr/451/docs/search.index/1ee39b692949.js deleted file mode 100644 index 0b61e66b9..000000000 --- a/pr/451/docs/search.index/1ee39b692949.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUAOzAAAAEAAAYAAQDkDwYAAQMA9IMMAAABAAEAAQBjAAOgkAAAEuBocHM6MAAAAQAAAAAADgAQAAAAggWXBuEIDAwZDIEQghAlEiYSehJ7EnwSfRKoEqkS") \ No newline at end of file diff --git a/pr/451/docs/search.index/1f0b55d3dbc2.js b/pr/451/docs/search.index/1f0b55d3dbc2.js new file mode 100644 index 000000000..c2c8ca01c --- /dev/null +++ b/pr/451/docs/search.index/1f0b55d3dbc2.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..de79ced3a --- /dev/null +++ b/pr/451/docs/search.index/1f242d58f36b.js @@ -0,0 +1 @@ +rn_("AQACoDAAABEBoJAAABN2MGj15xEAAJcBAQABAAEAE4ACoDAAAA+0ZHIjAISwEAjqAzuwIAvyAQqgMAAAD9wSABj1kgUAAC0AAQABAAEAAQIA9JkUAAABAAoAAQABAgDzVRAAAAEAAQDzgAJpcgEBArBAEUYAAaBQAAATW2Vz9IAOAADcAgEAkgIBCwD2hRMAAAEArABYABgAAQDzgAJsdNOAhKCQAAATMwFEBA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/1f4450a92511.js b/pr/451/docs/search.index/1f4450a92511.js deleted file mode 100644 index e10c427e1..000000000 --- a/pr/451/docs/search.index/1f4450a92511.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMAOzAAAAEAAIcAAgBzEAAAfRGGAAEAAbBwEzIAAWXyZw8AAAEAIwACoEAAABOJZXP00g8AAAEAJgQBADOAA6BgAAARJmRpbw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/1f54ccf79fde.js b/pr/451/docs/search.index/1f54ccf79fde.js new file mode 100644 index 000000000..f8889d6a0 --- /dev/null +++ b/pr/451/docs/search.index/1f54ccf79fde.js @@ -0,0 +1 @@ +rn_("AQIA9JURAAABAAEAAQABAQDzsxEAAAEAAQATgAIhgW3VhgNlcwEKAPSlEwAAAQABAAEAAYACsCAUIwABoGAAABNyb3YBgAKwMBKpAAGgAAAAEypjZPOAhQUQBg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/1f8374f280ae.js b/pr/451/docs/search.index/1f8374f280ae.js deleted file mode 100644 index 8248652c2..000000000 --- a/pr/451/docs/search.index/1f8374f280ae.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQYA9CYTAAABAAEAAQABDwD0BBQAAAEAAQABAAEAAbDwFAwAAWv2gBMAAAUAAQABAAEAJQABAgD0+g8AAAEAqQABAPcAAACFsQAT+QABiTAA8kcSAAABABOAArAQDAYAAWVvAQUAOzAAAAEAAA0AAQAYEQ0AAQIA84wPAAABAAEAAQIA9IgPAAABAAEAAQABAwA7MAAAAQAACAABADkQCAABgAKgAAAAC9ywMBPHAAFxcgGAAqBAAAAQi7DAE70AAWVzAQEAOzAAAAEAABMAAQCpDxMAI4ACsJATLQABaW13NgAAiqBQAAATjLBwE20AAaAgAAAMm5xVETswAAABAAALAAQAKAcAAGUQAwApEgEAjxMEAAECAbAwEPMAAXABEBIAALOAhKAgAAARVgIoBA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/1f9a8e3654ea.js b/pr/451/docs/search.index/1f9a8e3654ea.js new file mode 100644 index 000000000..dc966596a --- /dev/null +++ b/pr/451/docs/search.index/1f9a8e3654ea.js @@ -0,0 +1 @@ +rn_("AQEBsIATUAABdfI3CQAAAQAjgAKwEAk1AAEzNgEGAPPWEAAAAQAVAROAArAAD54AAW91AQIA80UHAADuAQEAAQMA87ESAAABAAEAI4ACsAARqQABaGkBAAA7MAAAAQAAEwABAHgHEwAjgAKgMAAADMphZfOAAnJ0E4ACsDATnwABYWwBAAGggAAAFJxm8u0IAACOCgEHAPVJEwAAAQABAAEAAQAjgAKgUAAAEUhhb/MCAmZzOzAAAAEAAAgABABBBwAAcgcFAPEMAACAEQAAE4ACoEAAAAv8YW8BAwGwQBRhAAF08r4TAAABACOFAqBAAAAT7mJzIwADsEAQ6AABoFAAABFVYWJm9/oIAAABAEgAawcBAGEAAQATgQKgUAAAEeRndAGAAqBAAAAR0aAgAAATzWFpAQIA9DAJAAABAPoHAQABAgDzPgwAAAEAAQATgAOwEAkuAAGwMBQAAAFpdXkBAgA7MAAAAQAACwACALcTBQB1FAUA8wgBbTswAAABAAAMAAMAUhEAAM0SBQAzFAUAAYECoIAAABK6oHAAABQUYXPzgAJucrfbAQCNoUAAABRwoCAAAAkyoFAAAA/pN5UPOzAAAAEAADECBQD6AQAAIAIvAf0GAABpBwAA8wn+AA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/1fac5249737c.js b/pr/451/docs/search.index/1fac5249737c.js deleted file mode 100644 index cd94d35ad..000000000 --- a/pr/451/docs/search.index/1fac5249737c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEA950QAAABAAEAwwIBAIIABQABAgD0MhAAAAEAAQABABOAAqDQAAAT1GNz84ACZW8jgQKwAAckAAFsdAEAAPSLBQAAAQABAAEAU4CEoFAAABQjsKATbwABCCAF") \ No newline at end of file diff --git a/pr/451/docs/search.index/201011f148a9.js b/pr/451/docs/search.index/201011f148a9.js deleted file mode 100644 index 7f0281bf0..000000000 --- a/pr/451/docs/search.index/201011f148a9.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQADsCAQyAABoFAAABFtoEAAABOgbHN28psTAAABAAECAPRMEQAAAQABAAEAE4QCsCARUAABYXPzgAJhZROAAiqOSw6JD3JzAQQBoEAAABOJZfL5EwAAAQABAQA7MAAAAQAABgABABwUBgDzBgFl9NATAAABAC8AAQAzBwOwYBLwAIdhcnXz/w8AAGICAQAjgAKgEAAAC85lcgEAADswAAABAAAfAA4ApAQBAGMFCQD1CAEA4AsBAJAMAQA2DgAAQw8CAFgPAAB3DwEAYxABAHYQAAAJEQAAXxEBAGQRAAABAQA6MAAAAQAAAAAADAAQAAAA2AgGDAcM/A8MEA0QnRCeEJ8QYhNjE+UT6hMBgQKwABD6AAGwABD8AAE0NgEFAPRrEwAAAQBJAAEA8wQBY/JzEgAAAQABAwGgQAAAE59l8skTAAABAAEEAPN4EgAAAQBWAQEAAPR6EgAAAQABAAEA8wQBZPQgEgAAAQABAAEAZxIAAYmwoBO3AAGwUBJFAAGgMAAAEKOwYBPrAAGwcBNlAAG1yAQ6MAAAAQAAAAAACgAQAAAA0Aj+CBEMEgyED4UPhg9VEbcSuBKNExGBAhKwIBFQAAFhcwECAPM7EgAAAQABAPcAAIDFoBAAABAlgAA4CAEFAPQ5EgAAAQCkAAEAAQgA9PMSAAABAIUAAQAzAQOwQBLCAAFhYmX1nwwAAAEAHgNUAs8AAQAA9eALAAABAGIDAQABAPOCAnN1AQYA9AcQAAABALAAAQAzAAOgoAAAFCNidHf1VQUAAAEAbA5RAAEAAQ8A9IoTAAABAEsAAQABBAD0BRAAAAEAsAABACOAArEAE+gAAW9wY4EDsIATCAABY3N0AQQBoKAAABQjd/ITFAAAAQABCwD0hRMAAAEAAQABAAEMAPTxEgAAAQCnAAEAI4ACoHAAABOtZ3fzgAJlbwENAPPZEwAAMQABAAEQAPMCFAAAFgABAAEBAaBgAAASNHL3kQUAAAEANQIQBAEAxQMBAAEDAbAADJ4DH3Py3gsAAAEAAQMA890LAAAABQEAAYACoEAAABNDsDASZQABYXPzgANucnYBCQD0ZREAAAEANQEBAAEHAPSMEAAAAQCpAAEA84ACaGkBAQKwIAycAAGwAAwcAAFlc/I1BwAAAQABBQGwUBKKAAFy9GkPAAABAOcAAQDzgAJiY9feAQCOoCAAAAc3oFAAABEGoOAAABN4sNATaQABl/ULOjAAAAEAAAAAABkAEAAAAPUB9gH3AUUERgTiCNkLGgwbDB4MgwyEDIUMhgwxDv4PSxDzEPQQOBE5EXITtxO4E8sTzBMBgAKgsAAAE9SgMAAAEKBsbhMAArCAE6EAAWVw9PASAACHACYAAQABAwGwQBPHAAFl9Y8TAAABAAEAAQABAAEGAPQmEwAAAQABAAEAAQ8A9AQUAAABAAEAAQABAgA7MAAAAQAABQABAO8TBQDzDQFtOzAAAAEAAAUAAQCvEwUAAQQA9SQSAAAOAAEAiwABAAEAAqAQAAAQ3LDwFAwAAWVrOjAAAAEAAAAAAAcAEAAAAIAThROGE4cTiBOtE8ATwRMBAgD0MhAAAAEAAQABAAECAPT6DwAAAQCpAAEA95MBAI2gUAAAEI+hAAAAE/WxABP5AAGgoAAAErm9vAM7MAAAAQAAIgAMALULAwB+DwAApw8BAK4QBwAFEQAARxIBAFASAQBUEgEAsRIBAM8SBACnEwQAEhQAAAECADswAAABAAALAAIAOhMFAO8TBQDzAQFtOzAAAAEAAA0ABAB+DAAA6RAAAFYSBQCvEwUAEwECoDAAAAyCZXPydREAAAEAAYkCsCAT0gABoDAAABPcaXATAAOgIAAAEpiggAAAEqtlaXX1yQsAADQAAQABAAEAAYYCsJAT9wABoAAAABIoYmQBgAKwMBKMAAGgMAAAEHRydtcAAAOFoDAAAA/xoDAAABLiEAETOjAAAAEAAAAAAAkAEAAAAMoIywjMCPwI/QgJEA4QxBDFEGkSAQYAOjAAAAEAAAAAAAcAEAAAAGEPYg8mECcQjhKPEh4THxMBBwD0AxMAAAEAAQABAHcAAACGoHAAABMboEAAABKvoHAAABChQ0AFOzAAAAEAABAABwALAgAALQcBADIJAQB4EAEAgxABAA8RBQAKEgEAd3cAgIuwsBPjAAGgYAAAEKG1QQ8=") \ No newline at end of file diff --git a/pr/451/docs/search.index/20a41ecff08f.js b/pr/451/docs/search.index/20a41ecff08f.js deleted file mode 100644 index 5ef389029..000000000 --- a/pr/451/docs/search.index/20a41ecff08f.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9WwSAAABAOwAeQABABOAArAwEp0BP2lwAQYA8xgTAAABAAEA8wICYnMBxQ8AAPMBAWbyOAcAAIcJAQIA9MQSAAABAJUAAQAzgAOwQBKSAAFpdXk=") \ No newline at end of file diff --git a/pr/451/docs/search.index/20afefeb7ec4.js b/pr/451/docs/search.index/20afefeb7ec4.js deleted file mode 100644 index bb1e97cef..000000000 --- a/pr/451/docs/search.index/20afefeb7ec4.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIBoQAAABPhZPIyBwAABgIBBgGgMAAAEWR0ARcTAAABAwGwUBJlAAFl9OIIAAA4AwEA4wMBAQD0iwUAAAEAAQABAAEBAaEAAAAT1WTyigUAADwClwcAAIewoBJjAAGg4AAAE6wegArzQgQAAAEAAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/20da1b8e8e24.js b/pr/451/docs/search.index/20da1b8e8e24.js deleted file mode 100644 index 926b71b4d..000000000 --- a/pr/451/docs/search.index/20da1b8e8e24.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEA9boQAAABAAEAAQABACOBArCQE20AAWF0Q4ADsRAT5gABoIAAABMwY290EwACsAATvQABbnL2/QEAAAEAawIBAJ4C8gs=") \ No newline at end of file diff --git a/pr/451/docs/search.index/20fbab33fc2f.js b/pr/451/docs/search.index/20fbab33fc2f.js deleted file mode 100644 index 592d56661..000000000 --- a/pr/451/docs/search.index/20fbab33fc2f.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQBoFAAABO/cwEPEgAAAYICsEASNQABsEASNwABNDYBCwDzLxMAAGgAAQABBgD0DxQAAAEACgABAPMCAXIBeBMAAPMDAWX0AxAAAAEApwABAPOEA2FlaWOAA6BQAAAT9mNldA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/2160978e1be3.js b/pr/451/docs/search.index/2160978e1be3.js new file mode 100644 index 000000000..0b7a63e19 --- /dev/null +++ b/pr/451/docs/search.index/2160978e1be3.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..3a75737c1 --- /dev/null +++ b/pr/451/docs/search.index/21816dd58603.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..2ce56784d --- /dev/null +++ b/pr/451/docs/search.index/21a316596b65.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..16af45473 --- /dev/null +++ b/pr/451/docs/search.index/21a6ad5e522a.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..7cca81298 --- /dev/null +++ b/pr/451/docs/search.index/21f47a3ec65b.js @@ -0,0 +1 @@ +rn_("AQIA9ZARAAABAAEAAQABAAGAArBgEvoAAaAgAAAQ5W95AQABoMAAABQgafRSEwAACAAcABsAAQYA9IwUAAABAAEAAQABAgD04xAAAAEAmgABAAECAaBAAAATKXI7MAAAAQAABwACAOEQAQB4EQUA84ACYW9nAwAAhrCAEygAWrCgE58AAQwwBjswAAABAAAIAAMACgwAAKsRAwCQEgMA84ACZWk=") \ No newline at end of file diff --git a/pr/451/docs/search.index/221b8a1321dc.js b/pr/451/docs/search.index/221b8a1321dc.js new file mode 100644 index 000000000..c862eecb1 --- /dev/null +++ b/pr/451/docs/search.index/221b8a1321dc.js @@ -0,0 +1 @@ +rn_("AQIBsEAUYQABdDswAAABAAANAAYA4hIDAE4TAQC+EwEA1RMBAFYUAQB9FAEAE4ACsDATFgFKaXABBQDzkxMAAAEAAQATgAKgMAAAE+5leVMAA7BwE/sAAWJlcwEmEAAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/227000c17c1b.js b/pr/451/docs/search.index/227000c17c1b.js deleted file mode 100644 index 669a76959..000000000 --- a/pr/451/docs/search.index/227000c17c1b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUBsEASMgABZfJUEgAAAQABAAA7MAAAAQAAEQAEAPABAQA5EAgAMhEDAC8SAgABCgGwQBPHAAFl9Y8TAAABAAEAAQABAEcGAACHsCAQjwIqsMAUDAABoCAAAAbQsDAHKgABGIkKOjAAAAEAAAAAAAwAEAAAACYHJwcOCdML1AvaC9sLKwwsDHYMdwyCEoMS") \ No newline at end of file diff --git a/pr/451/docs/search.index/22b02790a247.js b/pr/451/docs/search.index/22b02790a247.js new file mode 100644 index 000000000..19f7ae705 --- /dev/null +++ b/pr/451/docs/search.index/22b02790a247.js @@ -0,0 +1 @@ +rn_("AQcAOzAAAAEAAIwAAgDcEAAA7RGLAAEDADswAAABAACLAAEA9A2LAAECADswAAABAAAHAAEAMAwHAPMAA2JpdDswAAABAACMAAIAmAQAANIEiwA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/22f66fe81db4.js b/pr/451/docs/search.index/22f66fe81db4.js deleted file mode 100644 index e0bef0582..000000000 --- a/pr/451/docs/search.index/22f66fe81db4.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMAOzAAAAEAAIcAAgBFBwAAQwmGAAGEArBwE50AAaBAAAAQk2lzAQcA820QAAABAA4BI4ACoLAAABJEYXQBAAOwMAjwAAGwMBMBAAGgIAAAB0ZhZG8BBgIAADcDAICGoMAAABOVsAATaQABAJgV") \ No newline at end of file diff --git a/pr/451/docs/search.index/230d9ae0255e.js b/pr/451/docs/search.index/230d9ae0255e.js deleted file mode 100644 index 0d0dbd079..000000000 --- a/pr/451/docs/search.index/230d9ae0255e.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBgBLpABDwczswAAABAAAFAAEAyRIFACOCAqCQAAATw2lzAQEA9JASAAABAI8AAQAjgAKwoBNzAAFjbgGEArCgE4EAAbCgE4MAAWNyAQIAOzAAAAEAAAYAAQDkDwYAE4ICoCAAABNkZXUBAgDzQhEAAAEAAQABgAKwIBOjAAGgYAAAEvdvdmOBA6AQAAASsGFjcwEDAPSDDAAAAQABAAEAAQEA8+oIAAABAAEAAQEA9HoQAAABAJoAAQBTgAOgwAAAE89ucHUBgAKwMA+gAAGhYAAAFANhZQGBArBAEjUAAbBAEjcAATQ2AQUBoFAAABOMaPIpEgAAAQABBQD0+g8AAAEAqQABAHOAhLAwEtwAAQGgBAEIAPPYEgAACAAbABOAArBAEWcAAWFlAQoA8y8TAABoAAEAAQABoCAAABAXcgHcEAAAAQAA9DITAAABAEsAAQCnAQCAhaAwAAAQd6AQAAAQlgkgIQEJAaBAAAATxmXy5hMAAAEAQ4CEoCAAABPUoJAAABMxoTAAABPlEEkAAQIA9MQSAAABAJUAAQATgAKwQBKSAAFpeQEBAqAwAAASaaBAAAASr2dyOzAAAAEAAAcAAgB4EAEADxEFAAGAArAAE0wAAbAAE04AATQ2AQYA9A8UAAABAAoAAQDzAgJydgF4EwAA8wIBZfQDEAAAAQCnAAEA84ACZHTnrT8AEqAwAAAQl6BAAAAMmLDwFAwAAbBwEvgAATBhYmNlZ2hpa2xub3Byc3R1djswAAABAAAHAUYAowQAAIIFAACJBQAAiwUDAJcGAAD3BgAAHAcCACwHAAA+BwUARQcAAKgHAADPBwEAzggBANQIAQDhCAAAPwkBAEMJhgC5CwEAxwsAAMsLAQDWCwAADAwAABkMAAA6DwEAYQ8BAHkPAADdDwAABxABABkQAAAmEAEAaxABAG8QAwCBEAEAuBABANoQAAD3EAIAJhEAAD4RAABhEQAAZREBAGsRAQB3EQQAFhIAACUSAQAoEgAANBIAAEUSAQB6EgMAjhIBAJsSAQCiEgEAqBIBAKsSAACuEgAAwBIAANYSAQD1EgAAAxMEAB4TAQAwEwAAMhMBADgTAQBDEwAAfhMCAIUTBAChEwEArRMAAMATAQDoEwEAIxQAAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/235b929c3e77.js b/pr/451/docs/search.index/235b929c3e77.js new file mode 100644 index 000000000..467a2045e --- /dev/null +++ b/pr/451/docs/search.index/235b929c3e77.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..179227459 --- /dev/null +++ b/pr/451/docs/search.index/238fff737ae7.js @@ -0,0 +1 @@ +rn_("AQMBsDATQQABY/NrEQAAAQABAAEDAYATYwAQ+nM7MAAAAQAABQABAEMTBQAjggKgkAAAFEdpcwcBAIDFsFATCwABoKAAABOusHATcwABoNAAABRTgAEYCPOAAmRy") \ No newline at end of file diff --git a/pr/451/docs/search.index/23daddc85a2a.js b/pr/451/docs/search.index/23daddc85a2a.js deleted file mode 100644 index 2fb4f07a7..000000000 --- a/pr/451/docs/search.index/23daddc85a2a.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABoDAAAAx8aQHiEgAAAQABsDAQxgABbvIqBwAAAQABAQDzqAcAAJIHAQATAAKgUAAAEChlc/U7BAAAAQABAAEAAQABhAKwoBOBAAGwoBODAAFjcvMBAXTzYREAAOQAAQABAwGwYBPLAAFmOzAAAAEAAAYAAwDACwIA8xIBAHkTAQDzgAJhZfOAhAAoBQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/240436689105.js b/pr/451/docs/search.index/240436689105.js deleted file mode 100644 index a4e34eeb2..000000000 --- a/pr/451/docs/search.index/240436689105.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEBsIAS1gABdfITCQAAAQAjgAKwEAkRAAEzNgECAPMjBwAA7AEBAAECAPQMCQAAAQC1BwEAAQIA8wMMAAABAAEAE4ACsBAJCgABaXUBAgA7MAAAAQAACwACADoTBQDvEwUA8wkBbTswAAABAAAMAAMA6RAAAFYSBQCvEwUAdzAAAIqgMAAAE1CgcAAAEXSgIAAACQ6wMBE4AAGgUAAAD4czhQs7MAAAAQAAJgIEAO0BAAATAisBRwcAAMoJ+AA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/243924e3924e.js b/pr/451/docs/search.index/243924e3924e.js new file mode 100644 index 000000000..f27b68899 --- /dev/null +++ b/pr/451/docs/search.index/243924e3924e.js @@ -0,0 +1 @@ +rn_("AQAA9FsQAAABALIAAQABhAKwABFjAAGwABFlAAE0NvMCAWHyYwwAAAEAAQEA9qkSAAABAHIAAQBRAQEA84ACZW/zgAJoaw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/247d5e509be4.js b/pr/451/docs/search.index/247d5e509be4.js new file mode 100644 index 000000000..8c75d2636 --- /dev/null +++ b/pr/451/docs/search.index/247d5e509be4.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..c5e3f5edb --- /dev/null +++ b/pr/451/docs/search.index/24b98fc4a43b.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..7255a9f81 --- /dev/null +++ b/pr/451/docs/search.index/24fd72fc1f6c.js @@ -0,0 +1 @@ +rn_("AQQA9vcMAAABADgDAQAtAQEAAQoA87gSAAABAKwAAQEA9xIJAAABAFAHAQByAAEAFQEjgAKwYBPSAAFpbwECAPS1DAAAAQBEBgEAAQUA9OgTAAABAFAAAQD3CAAAiLCQE58AAaAgAAAMt7CQEd8AAQTcAzowAAABAAAAAAAIABAAAACLBQMJBAlRDFIM5hDnEOsS7BI=") \ No newline at end of file diff --git a/pr/451/docs/search.index/25143dd2528f.js b/pr/451/docs/search.index/25143dd2528f.js deleted file mode 100644 index 380d928c0..000000000 --- a/pr/451/docs/search.index/25143dd2528f.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYADsAATTAABsAATTgABsHATwAABNDZhAQQA9A8UAAABAAoAAQAjgQKgQAAAENptcAEBADswAAABAAAIAAMARBIAALUSAAAcFAYAxwEAAMWgQAAAD92ggAAAEh9ABoAIOzAAAAEAABYACAB7DwEAkRABANYQAwBFEQYAKhMCAHgTAADQEwEAABQBAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/25a2359dd30d.js b/pr/451/docs/search.index/25a2359dd30d.js deleted file mode 100644 index 63dc01182..000000000 --- a/pr/451/docs/search.index/25a2359dd30d.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9GsTAAABAEkAAQDzAwFj8nMSAAABACOAArBwFBYAAWhvAYICsAASUAABsAARXwABZXNDAAOgoAAAE7qgIAAAEvplaXTyYxIAAAEAAQIAOzAAAAEAAAYAAQDgEAYAAQIBoEAAABOfZfLJEwAAAQDzgAJpbwEIAPRiEwAAAQCCAAUAEwEDsJATxAABsFASqgFZZ2xwOzAAAAEAABIACQDuDwIAYhEBAJkSAQC6EgEA8RIBAFMTAQCKEwEAmRMBANYTAQABgAKwQBInAQ6gIAAAEKNhaQGBArAwE3MAAbAwEkUAAWNpAQMBsAASlAABczswAAABAAANAAMAXREBAA4TBwD7EwMAAQMA9VsRAAABABwBAQBWAfOAAnJ2AQAAOzAAAAEAAAUAAgAlEgEAehIDAPMCAWQ7MAAAAQAACQADAFcRAwAgEgMAUhIBACOAAqCQAAATuWlvE4ACsPAUCAABbG8BBQD01RMAAAwAHgAPACOAAqBgAAAS72dzI4ACsJATtwABYmz3xwAAjbBgE+sAAbBwE2UAAbBgEqgAAbBQEpYAAbXJFzowAAABAAAAAAAgABAAAACPBdAI/ggRDBIMiQyKDIsMMg4zDjQONQ43DjgOgg+DD4QPhQ+GD00QThCHEKoQChELEQwRDRFVEaAStxK4EtkSjRM=") \ No newline at end of file diff --git a/pr/451/docs/search.index/25fce273e5ba.js b/pr/451/docs/search.index/25fce273e5ba.js new file mode 100644 index 000000000..9c0528358 --- /dev/null +++ b/pr/451/docs/search.index/25fce273e5ba.js @@ -0,0 +1 @@ +rn_("AQcA9OgRAAABAAEAAQAjAQKgcAAAEedhcgFsBwAAAQMAOzAAAAEAAAgAAgCKDwYAQRMBAPMAAmN0OzAAAAEAAFcAJwCfBAsAZQUAAAEJAQAVDAEAHQwCANwMAADoDAIA+gwBALQPAAC5DwEAvA8BAMYPAABMEAIAUxABAFgQAQB9EAAA9BAAAPoQAABGEQEAUxEBAGcRAQBrEQIAyxEBAN4RAgDkEQAAmRIAAKISAQCnEgEA8BIBAAATAQAFEwEAJhMBADYTAwA8EwEAnRMBALETAAAfFAAASBQEAFEUAgA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/2609108e1ba5.js b/pr/451/docs/search.index/2609108e1ba5.js new file mode 100644 index 000000000..2195bbc42 --- /dev/null +++ b/pr/451/docs/search.index/2609108e1ba5.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..a6a30261b --- /dev/null +++ b/pr/451/docs/search.index/260cd4c5a9b2.js @@ -0,0 +1 @@ +rn_("AQkBoIAAABScZgF7EwAAAQIA9cEQAAABAAEAXQIBAPMAAml1OjAAAAEAAAAAAAcAEAAAAJwQ0xLBE8IT8hPzExQUFRQBCAD1JxQAAAEAAQABAAEA8wIBZvWAEAAAAQABAAEAAQATgAKwoBRxAAFydPMCAmFz87AQAAABAAEAAQQBsEAUSwABZfUPFAAAAQABAAEAAQABAgA7MAAAAQAACwACALcTBQB1FAUA8wMBbTswAAABAAANAAQAvQwAAFIRAADNEgUAMxQFAPOBAmNl84ACYWU=") \ No newline at end of file diff --git a/pr/451/docs/search.index/2657ace38ed9.js b/pr/451/docs/search.index/2657ace38ed9.js deleted file mode 100644 index c0f52d63d..000000000 --- a/pr/451/docs/search.index/2657ace38ed9.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQkA898TAAABADUAAYADoEAAABLGsCATgQABoCAAAA9caGl5AQkBoIAAABQSZgEAEwAAAQMBsAATlgBMc/ItEAAAEwMjgAKgIAAAD1hhcgECAPVZEAAAAQABAEsCAQDzAANpdHU6MAAAAQAAAAAADgAQAAAAUQf7CJ4MvQ82EFwSlBKVEkQTRRNLE3UTdhOUE5UTAYACoDAAABPCsLATHAABYWwBDAGgoAAAFCN38hMUAAABAAEBAPN/DwAAAQABAPOAAnR1AQMA8y8SAAABAAEAAQEA9boQAAABAAEAAQABACOAArCQE20AAWF0AQQBoEAAABPGZfLmEwAAAQABAgA7MAAAAQAACwAFAN4PBQA+EgEAZxMBALkTAADYEwAAE4ICsPAUFgABZmcBgQKwgBPtAAGgYAAAFBFkcwEIAPWnEwAAAQABAAEAAQDzAgFm9RoQAAABAAEAAQABACOAA6AgAAAPVrCgE+sAAWRydHfvAQCNsBAToQABsOATowABl9UOOzAAAAEAACUBEgBnBAEAlAQCAH8FAgCSBgQAwgcDAN4LAQAKDAEAjgwBADkO9QB1DwEANxABAEgQAgB0EAEABxEAAF0RAQBPEgAADhMHAPsTAwA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/26625634ad63.js b/pr/451/docs/search.index/26625634ad63.js deleted file mode 100644 index 207639749..000000000 --- a/pr/451/docs/search.index/26625634ad63.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBoAAAABNLcwF1EAAAAYACsDAQCgABsFAQDAABYWkBgAKgsAAAE9SgMAAAEKBsbhcBAACFoCAAABIOoCAAAAjHsIAToQABEJEEOjAAAAEAAAAAABEAEAAAAH4EtQS2BLcEuAS5BLoENwk+Dz8PthLwEnETdxODE4QTnROeEwECADowAAABAAAAAAAKABAAAAAgCSEJpg/aEtsSChMLE64TAhQYFBkUAQAAOzAAAAEAAAYAAgD2EgAAXBMFAAECAbAwEscAAWM6MAAAAQAAAAAAEwAQAAAApgxVD+sP7A/tD/IP8w/3D/gPihD+EAIRAxEEEXQRvBK9Ep8TxBPFEwEDAPMvEwAAaAABABOBArBAEOoAAWFlE4ACoLAAABPbYWUBAQGwQBPHAAFl9Y8TAAABAAEAAQABABOAAqAQAAALyGFvhw8AAMiwMAjYBySwQBPNAAGggAAAEglATIgJ88gHAAAGBDsAAYACoIAAABLhoJAAABK5YWsBAwDzLxIAAAEAAQAjgISxIBQWAAGgAAAACPugMAAAD1wEBAYBCAD1HhQAAAEAAQABAAEAE4oCsEAT+QABYXKTAISgwAAAErWhAAAAE4ABQAbySgcAAAEAAQMBoEAAABFuZfOwDAAA5AMBAAEFAPQmEwAAAQABAAEAU4ADsAAS3AABZXN0AQYA9AQUAAABAAEAAQDzCAFt9BsSAAABAAEAAQABAgA7MAAAAQAABQABAO8TBQDzDQFtOzAAAAEAAAUAAQCvEwUAAQQA97gMAAABAGsFDgABAIsAAQABAwGAEukAEPBzOzAAAAEAAAUAAQDJEgUAI4ICoJAAABPDaXMBBQGgUAAAE4xo8ikSAAABABOAArAwEtwAAW50AYACoIAAABL7sEARZwABYWUBgAKgEAAAEJagAAAAENxucgEJAaBAAAATxmXy5hMAAAEAI4ACoJAAABMxaWzHIwAACqAwAAAQl7BQEpIAAbDwFAwAAbBwEvgAAaDQAAATzzBiY2VoaWtub3Y7MAAAAQAAGgALAMcLAABvEAMAdxEEAPUSAAADEwMAOBMBAIATAACFEwMAoRMBAK0TAADAEwEAAQICoLAAABO5oLAAABO6Y3PzTRAAAAEAXAABAQKwUBFnAAGgwAAAE4xoafI4EwAAAQDzgAJlaQEBAaCQAAAS9XP0MhAAAAEAAQABAAEDAbAAEGkAAXMB2g8AAPOAA2hpdAEBAaBQAAASd2MBfQwAAAEFAbCAE8AAAXY7MAAAAQAABwACAEURBAAqEwIAAQEA9PoPAAABAKkAAQDjgISwcBFrAAEOCAD3ZQ8A0bCQEgcAAaBQAAAQj6EAAAAT9aCgAAATF6AQAAAHIEA/vyk7MAAAAQAAOAAYAKYEAACoBAAAcwUCAOkGAgC1CwMAgAwBAEkPAgBXDwAAfg8AAIcPAACnDwEArRAIAAURAABHEgEAUBIBAFQSAQCxEgEAzxIEACQTAQAtEwEAMRMAAKcTBAD1EwAAEhQAAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/2685e428a65d.js b/pr/451/docs/search.index/2685e428a65d.js new file mode 100644 index 000000000..2da8ace07 --- /dev/null +++ b/pr/451/docs/search.index/2685e428a65d.js @@ -0,0 +1 @@ +rn_("AQMBoEAAABHeZfPyDAAADAQBAAEGAPT7EAAAAQC+AAEAE4EDoAAAABFDsSAUiAABYWRuAQUA9G4QAAABAAEAAQABFAD1qBQAAAEAAQABAAEAAQUBsIAURAABdjswAAABAAAHAAIAthEDAKUTAwAjgAKwcBHbAAFiZFcCAAGGsMATbwABoKAAABOSoJAAABNbBeAC9QoHAAABAAEA3QiSBAEGAPO4EgAAAQCsAPOAA3J0dgEDADswAAABAAAHAAIAoRMDAIwUAwDzAAFzAUcUAADzAAFzAd0RAADHAgCAh6CAAAATG6AwAAAML7AAE1YAAaAQAAAM2BNADg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/26eaff1ecc2b.js b/pr/451/docs/search.index/26eaff1ecc2b.js deleted file mode 100644 index a55b2e9ef..000000000 --- a/pr/451/docs/search.index/26eaff1ecc2b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAAOzAAAAEAAAYAAgD2EgAAXBMFACOIArBAE80AAWVzAQQBsDASxwABY/MCEQAAAQABAAEFADswAAABAAAIAAMAcRECAG4SBACOEwAA8wMDY2Vw83kMAAABAAEAAQABsGATdQABZQGsEwAA8wIBc/byCAAAAQABAM0HPwAVAQECAaBQAAARAXIBoRAAAPOAA2ZtcA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/280b57a12fe8.js b/pr/451/docs/search.index/280b57a12fe8.js deleted file mode 100644 index c3a679506..000000000 --- a/pr/451/docs/search.index/280b57a12fe8.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYMCsQAUGAABsNAUCgABa20BCgDzZxMAAAEAcAATgAKiEAAAFCNsbzMBhKBgAAASBKEAAAAT2gSAAzswAAABAAA0AAIAIQcAAHQHMwA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/28125c7f5b42.js b/pr/451/docs/search.index/28125c7f5b42.js new file mode 100644 index 000000000..767ef40b0 --- /dev/null +++ b/pr/451/docs/search.index/28125c7f5b42.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..8a212e154 --- /dev/null +++ b/pr/451/docs/search.index/28183bdd4ff4.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..fbe65b30c --- /dev/null +++ b/pr/451/docs/search.index/285de43c88ed.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..4a8a38fe6 --- /dev/null +++ b/pr/451/docs/search.index/2888725fe937.js @@ -0,0 +1 @@ +rn_("AQQBoEAAABQJZfJ/FAAAAQABAQA7MAAAAQAABgABAKYUBgDzBgFl9FQUAAABADMAAQAzAwOwYBNqAIphcnXzYRAAAHcCAQAjgAKAEWAAEAFkcg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/2892218972b3.js b/pr/451/docs/search.index/2892218972b3.js deleted file mode 100644 index 7be5a9a3f..000000000 --- a/pr/451/docs/search.index/2892218972b3.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUAOzAAAAEAAAcAAwA/EQIA5xIBAHsTAgABBADzQhEAAAEAAQAjAAKgYAAAEmlhcvNrEAAAAQC8AQGAAqEQAAAT/7CAE/cAAWl1NwQAgYexUBQaAAGgAAAAEEywcBNEAAGhMAAAFA4KQBsBAAA7MAAAAQAAhgABAKoNhgABAgD2ChMAAAEAowBUABYAAQAjgQKgIAAAEIpydvOAAmVvI4ACoCAAABIJZHQBAAA7MAAAAQAA6QAEAKEFAADXB+YANgwAAO0QAADzgANkbnY=") \ No newline at end of file diff --git a/pr/451/docs/search.index/28d7b5f5abc8.js b/pr/451/docs/search.index/28d7b5f5abc8.js new file mode 100644 index 000000000..b5b094de1 --- /dev/null +++ b/pr/451/docs/search.index/28d7b5f5abc8.js @@ -0,0 +1 @@ +rn_("AQMA9u4MAADmAlgAAQDPAgEAAQQAOzAAAAEAAAoABAB1EAUAthIBABcTAQA+FAAAAYYCoAAAABM6oAAAABM7ZHLzAgF2AWYTAADzgAJlbycBAADHoDAAAA/ToHAAABKGoIAAABQNsNATrAABoLAAABQNQEUICTswAAABAAAJAAMAtAUCABMHBABIDAEAI4ACKBg73U/0ZHIBAAA6MAAAAQAAAAAACgAQAAAARAlFCQgQVBNVE4UThhMyFIoUohSjFBOAAqAwAAAQi2x0w4AEoAAAABB7oAAAABB8NDZhZQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/29ac0c736fc1.js b/pr/451/docs/search.index/29ac0c736fc1.js new file mode 100644 index 000000000..bd4892c24 --- /dev/null +++ b/pr/451/docs/search.index/29ac0c736fc1.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..4a87c955c --- /dev/null +++ b/pr/451/docs/search.index/29d270c7a752.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..7785ff938 --- /dev/null +++ b/pr/451/docs/search.index/29dd87e5e6be.js @@ -0,0 +1 @@ +rn_("AQMBsAAM4AM/c/QXDAAAAQABAAEAAQIA9BUMAAABADAFAQAjAAKgQAAADNxlcvKvBAAAAQABgAKgQAAAE8CwMBLcAAFhcwECAaBAAAAUSmXybBQAAAEA84AEbG5ydg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/2a552ae8044e.js b/pr/451/docs/search.index/2a552ae8044e.js deleted file mode 100644 index 6c2e37553..000000000 --- a/pr/451/docs/search.index/2a552ae8044e.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEAOjAAAAEAAAAAAAkAEAAAAGQPZQ9TEA4RXRJeElcT2RMKFAsUAYACsOATUQABsDATaQABZHDDgISxQBQaAAGgkAAAExcCgAk=") \ No newline at end of file diff --git a/pr/451/docs/search.index/2a56df498b28.js b/pr/451/docs/search.index/2a56df498b28.js new file mode 100644 index 000000000..47009c09b --- /dev/null +++ b/pr/451/docs/search.index/2a56df498b28.js @@ -0,0 +1 @@ +rn_("AQQBoFAAABRDcwGEEgAAAYICsEASqwABsEASrQABNDYBCwDzqxMAAGwAAQABBgD0mRQAAAEACgABAPMCAXIB9RMAAPMDAWU7MAAAAQAABQACAGUQAwAVEQEA84QDYWVpY4ADoFAAABR8Y2V0") \ No newline at end of file diff --git a/pr/451/docs/search.index/2a67720851d1.js b/pr/451/docs/search.index/2a67720851d1.js deleted file mode 100644 index 89ca98e84..000000000 --- a/pr/451/docs/search.index/2a67720851d1.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQgA9FETAAABAFMAAQABAgGgYAAAE/Zz8lARAAABAAGAArAgD6cAAaCQAAATrGFlAQcBsEATxwABZfWPEwAAAQABAAEAAQABAwGwQBPdAAF08kETAAABACOFAqBAAAATcWJzAQEA9EwRAAABAAEAAQATgAKgMAAAEH9kbDd0AACLsEATIgABoFAAABDsoJAAABMWsFASdQABoDAAAAwes5gHOzAAAAEAAF4AEQD4AQEASAQDAKAFAACYBgEAMwcBANYIAQDjCAAA7QgAAB8JAAA3DD4AiAwAAHMPAQBGEAEAYRABAKYQAQAnEQQAKxIBAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/2a6d66bb35a1.js b/pr/451/docs/search.index/2a6d66bb35a1.js new file mode 100644 index 000000000..b85fd694e --- /dev/null +++ b/pr/451/docs/search.index/2a6d66bb35a1.js @@ -0,0 +1 @@ +rn_("AQkA82MUAAABADsAAYADoEAAABNAsCAT/gABoCAAAA+7aGl5AQkBoIAAABScZgF7EwAAAQMBsAAUFgBQc/KTEAAAKgMjgAKgIAAAD7dhcgECAPXBEAAAAQABAF0CAQDzAANpdHU6MAAAAQAAAAAAEAAQAAAAcQcfCeAMHxCcENMSDRMOE8ETwhPIE/IT8xMUFBUUZxRoFAGAAqAwAAAURrCwE5cAAWFsAQEA8+EPAAABAAEAE4ACsMAUnQABdHUBAwDzpBIAAAEAAQABAQD1JBEAAAEAAQABAAEAI4ACsJAT6gABYXQBBAGgQAAAFEpl8mwUAAABAAECADswAAABAAALAAUAPxAFALQSAQDkEwEAPRQAAFwUAAATggKw8BSgAAFmZwGBArCAFHMAAaBgAAAUm2RzAQgA9ScUAAABAAEAAQABAPMCAWb1gBAAAAEAAQABAAEAI4ADoCAAAA+1sKAUcQABZHJ0d+8BAI2wEBQhAAGw4BQjAAGX1Q47MAAAAQAANwETAH4EAQCrBAIAnAUCALEGBADiBwEAFwwDAEUMAQDRDAEAiA4BAdgPAQCdEAEAsBACAN0QAQBwEQAAoxEDAM0RAQDGEgAAiRMHAIEUBQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/2a8e4b0e890f.js b/pr/451/docs/search.index/2a8e4b0e890f.js deleted file mode 100644 index 6ec846f98..000000000 --- a/pr/451/docs/search.index/2a8e4b0e890f.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9DkSAAABAKQAAQABCAD08xIAAAEAhQABADMAA7BAEsIAAWFiZTowAAABAAAAAAAJABAAAAAEBwUHnwygDFAPvg8KEAsQEhLhEg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/2bbea166f1f8.js b/pr/451/docs/search.index/2bbea166f1f8.js deleted file mode 100644 index abe4b0a17..000000000 --- a/pr/451/docs/search.index/2bbea166f1f8.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABoCAAAAyVbQFfBQAAAQAA9HoSAAABAAEAAQDzBAFk9CASAAABAAEAAQAjhISwoBO3AAGwQBJ4AAGwcBNlAAEVgAAzgAOw0BOlAAFhZWk=") \ No newline at end of file diff --git a/pr/451/docs/search.index/2caeb0254b5a.js b/pr/451/docs/search.index/2caeb0254b5a.js new file mode 100644 index 000000000..c9663972c --- /dev/null +++ b/pr/451/docs/search.index/2caeb0254b5a.js @@ -0,0 +1 @@ +rn_("AQkA82MUAAABADsAAYACoEAAABNAsCAT/gABaGkBAQDz4Q8AAAEAAQATgAKwwBSdAAF0dQGBArCAFHMAAaBgAAAUm2RzNwMAgIagQAAAFEawYBQWAFACkA4BAAGwUBDyAAFj9FEHAAABAAEABgLDgISgEAAADGWgUAAAEoYMQAQ=") \ No newline at end of file diff --git a/pr/451/docs/search.index/2d7629068747.js b/pr/451/docs/search.index/2d7629068747.js new file mode 100644 index 000000000..0be69c5d7 --- /dev/null +++ b/pr/451/docs/search.index/2d7629068747.js @@ -0,0 +1 @@ +rn_("AQQA9mMUAAABAA8AAQAnAAQAAQMA86QSAAABAAEAI4ECsSAUoAABY2sTgAKgMAAAFB9uclMAA6BAAAAMDWVvcvQNEQAAfwHBAQEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/2d92cd1657e9.js b/pr/451/docs/search.index/2d92cd1657e9.js deleted file mode 100644 index 1a0e5a44c..000000000 --- a/pr/451/docs/search.index/2d92cd1657e9.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIBsEAT3QABdDswAAABAAANAAYAahIDANQSAQBBEwEAWBMBANITAQD3EwEAE4ACsDASnQE/aXABBQDzGBMAAAEAAQATgAKgMAAAE3FleVMCA7BwE34AAWJlcwHFDwAAEwACoCAAAAV9Zm32ewAAALwGAQDSAQEAtAc=") \ No newline at end of file diff --git a/pr/451/docs/search.index/2e327fd2bb24.js b/pr/451/docs/search.index/2e327fd2bb24.js new file mode 100644 index 000000000..5102fe636 --- /dev/null +++ b/pr/451/docs/search.index/2e327fd2bb24.js @@ -0,0 +1 @@ +rn_("AQQBoEAAABQJZfJ/FAAAAQABAQA7MAAAAQAABgABAKYUBgDzBgFl9FQUAAABADMAAQBTAISgIAAACU6wYBNqAIoBAQnzYRAAAHcCAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/2eaf4ab981c3.js b/pr/451/docs/search.index/2eaf4ab981c3.js deleted file mode 100644 index 570d27443..000000000 --- a/pr/451/docs/search.index/2eaf4ab981c3.js +++ /dev/null @@ -1 +0,0 @@ -rn_("ARMA9R4UAAABAAEAAQABABOAAqDAAAAStWFvAQICoLAAABO5oLAAABO6Y3PzTRAAAAEAXAABgQKwUBFnAAGgwAAAE4xoafOAAmVpI4ACoLAAABL1aXQBBQGwgBPAAAF2OzAAAAEAAAcAAgBFEQQAKhMCACOAArBwEWsAAWJkdwAAAIagoAAAExegkAAAEuGwUBAKAAEF4AL16QYAAAEAAQCcCG4E") \ No newline at end of file diff --git a/pr/451/docs/search.index/2eeacf2c8ee0.js b/pr/451/docs/search.index/2eeacf2c8ee0.js deleted file mode 100644 index 439deff84..000000000 --- a/pr/451/docs/search.index/2eeacf2c8ee0.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYQCoCAAABCioDAAABEJaXQBAQA7MAAAAQAAhwACAEUHAABDCYYAYwADsAACCQABZGVsOjAAAAEAAAAAAAcAEAAAAJcEYAVhBWIFSQfAEAETAhM=") \ No newline at end of file diff --git a/pr/451/docs/search.index/2ef1f9ee4b1b.js b/pr/451/docs/search.index/2ef1f9ee4b1b.js new file mode 100644 index 000000000..67b37aa40 --- /dev/null +++ b/pr/451/docs/search.index/2ef1f9ee4b1b.js @@ -0,0 +1 @@ +rn_("AQAA9A0TAAABAFkBAQDzBAFzOzAAAAEAAA0AAgCJEwcAgRQFAEcAAAGGoIAAABNpsQAUkAABsEASxwABsIAUoAABsFATDwABFQAW9IEOAAABAAEAAQABBQDz5g8AAAEAAQBjgAOwQBByAAFtbm8=") \ No newline at end of file diff --git a/pr/451/docs/search.index/2f7ecc5dbb1a.js b/pr/451/docs/search.index/2f7ecc5dbb1a.js deleted file mode 100644 index 37e6348f3..000000000 --- a/pr/451/docs/search.index/2f7ecc5dbb1a.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9cgPAAABAAEASQIBAAEEAaAAAAAQXHP1qQwAAAEAAQAbAwEAAQIA9ScRAAABAAEAAQABAAGAArBgEoIAAaAgAAAQfG95AQABoMAAABOgafTYEgAACAAbABsAAQYA9AQUAAABAAEAAQABAgD0ehAAAAEAmgABAAECAaBAAAASr3I7MAAAAQAABwACAHgQAQAPEQUA84ACYW9nAwAAhrCAEq4AWbCgEyQAAQwwBjswAAABAAAIAAMA0gsAADoRAwAbEgMA84ICZWlHAQAAhbBwEzIAAaCAAAAUEqBAAAAPfbBABPTJCAAAngYBAJgDAYECoCAAABB2sFASKwABYW8BBAGgAAAAE0tzAXUQAABjBAOwUBDnAAFjZXP20QgAAAEAAQAsAAEAEQkBAAA7MAAAAQAAJAAIABQHBwDIDwIAGBENAD8RAgATEgEAlhIBAOcSAQB7EwIAAQEA9LkLAAABALEFAQATAAOwMBO3AAGwUBKKAAFlcHL0aQ8AAAEA5wABAAEAA7BAE4MAAaFAAAAT6qBQAAARYWJjZgHeBgAA44CEsUAUDwABAjAQAQGEoLAAABNAsEAQNwABsMATbwABoGAAABEOFCAE9sUGAAAPCQEAMQEhAQ4BAQMAOzAAAAEAAAUAAQBJEgUAAQABoKAAABOscvRzDwAAAQDtAAEA84ECZ3MBBwD1zxIAAAEAAQABAAEAAYACoEAAABDfsFAQxgABY2nzgAJhbwEAAaAgAAAQ/3Lz5wsAAFkDYAFDgISgAAAAE4mgMAAAE4mABU0AHLsIICEBAwKwQBKMAAGgMAAAEuJld/IOEAAAtwATgwKgcAAAExtnbwECAPVsEgAAAQDsAHkAAQATgAKwMBKdAT9pcAEGAPMYEwAAAQABAPMFAmJzAcUPAAABAgD0og8AAAEAAQABACOAAqBgAAASD2Jj978AAIygkAAAEhA7yRo6MAAAAQAAAAAAFwAQAAAA/AEJAgoCZARlBGYE5gYfB1IHUwdUB1UHVgdXB7QLJwyvDMAP0g/TD5YQFxH5E/oT") \ No newline at end of file diff --git a/pr/451/docs/search.index/2f88dcc3b5f8.js b/pr/451/docs/search.index/2f88dcc3b5f8.js new file mode 100644 index 000000000..31366a83d --- /dev/null +++ b/pr/451/docs/search.index/2f88dcc3b5f8.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..b70717c04 --- /dev/null +++ b/pr/451/docs/search.index/2fd38d8465d2.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..82c62ff82 --- /dev/null +++ b/pr/451/docs/search.index/2febb6ceaf92.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..552eb521f --- /dev/null +++ b/pr/451/docs/search.index/2ff30af08e21.js @@ -0,0 +1 @@ +rn_("AQEA9wcRAAABAAEA1gIBAIsABQABAgD0mBAAAAEAAQABABOAAqDQAAAUWGNz84ACZW8jgQKwAAdGAAFsdCOAA7AABakAAbCgE+wAAWRydA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/3036a9249b54.js b/pr/451/docs/search.index/3036a9249b54.js deleted file mode 100644 index 8f9354bf5..000000000 --- a/pr/451/docs/search.index/3036a9249b54.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEAOzAAAAEAAAYAAQDgEAYAAQAA9+4IAAABABIHAQBrAAEADgEBggKgIAAAEKKgMAAAEQlpdPOAA2FncwEBAaBAAAATn2XyyRMAAAEAI4ACsFATVQABZnPzgAJpbw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/306dd97f0179.js b/pr/451/docs/search.index/306dd97f0179.js new file mode 100644 index 000000000..244e8078a --- /dev/null +++ b/pr/451/docs/search.index/306dd97f0179.js @@ -0,0 +1 @@ +rn_("AQMBsEARRgABZQGADgAAAQUAOzAAAAEAAAYAAQBFEAYAAQMAOzAAAAEAAAUAAQDEDAUA8wACcHM6MAAAAQAAAAAADQAQAAAAnwWgBbYGBQlHDFQM6xDsEJoSmxLyEvMS9BL1EjMAA7BQD7gA0WRwcjswAAABAAAPAAYAmQQAAF4FAwClDwAAxxEDAJUSAwDJEgEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/31b45cf6ecff.js b/pr/451/docs/search.index/31b45cf6ecff.js deleted file mode 100644 index f81d2dbe0..000000000 --- a/pr/451/docs/search.index/31b45cf6ecff.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEA9coHAAABAAEAAQABAAEAAqAwAAAQl6CQAAAS+zBo9XcRAACMAQEAAQABABOAAqAwAAAPVWRyIwKEsBAIxgMksCALuwD/oDAAAA96EgAY9XYFAAAuAAEAAQABADOAhLCgEj4AAbBAD18AARBJAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/320b0f4af278.js b/pr/451/docs/search.index/320b0f4af278.js deleted file mode 100644 index 439a4a533..000000000 --- a/pr/451/docs/search.index/320b0f4af278.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQA9rUMAAABABkDAQAlAQEAAQoA80ISAAABAKgAAQEA9+4IAAABABIHAQBrAAEADgEjgAKwYBNVAAFpbwECAPR2DAAAAQALBgEAAQUA9GsTAAABAEkAAQD3CAAAiLCQEyQAAaAgAAAMeLCQEW8AAQTcAzowAAABAAAAAAAIABAAAABvBd8I4AgWDBcMfRB+EHMSdBI=") \ No newline at end of file diff --git a/pr/451/docs/search.index/321190b83abb.js b/pr/451/docs/search.index/321190b83abb.js deleted file mode 100644 index 9d8fdf7f6..000000000 --- a/pr/451/docs/search.index/321190b83abb.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9IUQAAABAIYBAQAjgQKgYAAAEqtiaQEAAPR3DwAAAQDrAAEBI4ICsFASvgABZXMBiQKwIBPSAAGgMAAAE9xpcAECAPSQEgAAAQCPAAEAAQMA9MQSAAABAJUAAQDzhQJidQECAPTxEAAAAQBtAQEA84ACaWy3AQAAhaAgAAASmBIBDDowAAABAAAAAAAUABAAAABEB9EH0gfTB9QHyQv9C/4L/wsADJ8PXBAXE5YT3xPgE+IT7RPuExEUFRQBBQDzexMAAAEAAQBDhgOwkBP3AAGgAAAAEihiZHcBgAKwAAjoAAGwIAyuA0NkbgEDAPN1DwAAAQD+AAECAPbQCwAAAQBlAwEAwQMBAAEAAPQGCQAAAQABAAEAAQAAOzAAAAEAAAYAAgD2EgAAXBMFACOBArBAE80AAWVz84ADZXh5AQAA9AIJAAABAAEAAQBDgAOgQAAAE/WwIBKMAAFlcngBgAKgQAAADJiwQA+gAAFnbCcaAADJoJAAABLZsMATIgABoBAAABDtoDAAABNysEAMkAABAEegCzowAAABAAAAAAAJABAAAABLBUwFlgyXDKwMcQ/LD8wPhBKFEgEGAPSOEgAAAQCPAAEA8wMBYvSDEAAAAQCGAQEAtwwAAImgcAAAEhawkBK8AAGwYA/DAAGgMAAAEuI4iRM6MAAAAQAAAAAALwAQAAAAnAWdBZ4FnwUGBwcHygjLCMwI/Aj9CNUL5gujDKQMpQypDKoMqwxtD24PwQ/CD8YPxw/kD+UP5g/nD+gP6Q/qDwkQDhAoEC0QVhBXEMQQxRBpEvcSGxNAE9UT4RP/Ew4U") \ No newline at end of file diff --git a/pr/451/docs/search.index/3257c10e66d5.js b/pr/451/docs/search.index/3257c10e66d5.js new file mode 100644 index 000000000..49fd9bc66 --- /dev/null +++ b/pr/451/docs/search.index/3257c10e66d5.js @@ -0,0 +1 @@ +rn_("AYMCsCARaQEhoJAAABQObXABggKwoBP+AAGwoBQAAAFjcgECAaBAAAARaGUBIRAAADOAhLAgEOYAAbAgD54AAVAUAPOAAm9y") \ No newline at end of file diff --git a/pr/451/docs/search.index/3289f09b047b.js b/pr/451/docs/search.index/3289f09b047b.js deleted file mode 100644 index 286cebe8e..000000000 --- a/pr/451/docs/search.index/3289f09b047b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYICoFAAABJPoQAAABP2Y3QjAAKwQA9aAAFldTswAAABAAA3ABYAmgYBAMMLAQChDAEAXw8BAN4PBQAPEAUAiBABAOcQAQBxEQIAPhIDAG4SBACeEgEApBIBALMSAQA2EwEASRMBAGcTAQBrEwEAjhMAALUTAQC5EwEA2BMAAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/32b740d07761.js b/pr/451/docs/search.index/32b740d07761.js new file mode 100644 index 000000000..2742c6809 --- /dev/null +++ b/pr/451/docs/search.index/32b740d07761.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..d23a9d2fc --- /dev/null +++ b/pr/451/docs/search.index/32d5e2081a15.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..105868666 --- /dev/null +++ b/pr/451/docs/search.index/32d9da1d0315.js @@ -0,0 +1 @@ +rn_("AYQCoCAAABEMoDAAABFyaXQBAQA7MAAAAQAAjAACAGcHAABnCYsA8wACZWz1rgQAAL0CvglTAgEAAQIAOzAAAAEAAO0AAgD7CwAABQ3sAMcAAACFsLATJgABsBATrAABsDANAwABFDACOzAAAAEAABkBCwDcAQYAmQUAAMMF7AA2BwcAKRACAIERDQCwEQIAiBIBAA8TAQBhEwEA+BMCAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/337d979fd6b5.js b/pr/451/docs/search.index/337d979fd6b5.js new file mode 100644 index 000000000..ae93f00b5 --- /dev/null +++ b/pr/451/docs/search.index/337d979fd6b5.js @@ -0,0 +1 @@ +rn_("AQYA9HMRAAABAAEAAQABAgA7MAAAAQAACgAEAHUQBQC2EgEAFxMBAD4UAADzgAJmbgEBADswAAABAAAIAAIAig8GAEETAQDzgAJkbgEBAPMiBwAAAQABAAGGAqAAAAATOqAAAAATO2Ry8wEBdgFmEwAAQwADsIASywABsLAUbgABa2xyOzAAAAEAAAwAAwAZBwgA8wwBAC4QAQABAgD16AcAAAEAAQABAAEA8wCEFEEAAX8AAAA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/3381b6452880.js b/pr/451/docs/search.index/3381b6452880.js deleted file mode 100644 index 041ac6cbe..000000000 --- a/pr/451/docs/search.index/3381b6452880.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIBsEATIgABZfKYBgAAAQABBADz9A8AAAEAAQBDAAOwQBKIAAGxABNnAAFlZnA7MAAAAQAAJAAGAAgCAACnBgwAtQYMAC8PBgCLEAAAxxIBAGOAA7AwC8oAA2FlaQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/34191c056afb.js b/pr/451/docs/search.index/34191c056afb.js new file mode 100644 index 000000000..a1ce7a515 --- /dev/null +++ b/pr/451/docs/search.index/34191c056afb.js @@ -0,0 +1 @@ +rn_("AQUA9HcTAAABAAEAAQABAAD1aAwAAAEAAQABAAEA84ACZ2sTgAKwsBPSAAFhbwGAAqAgAAAL6aBAAAAMIWV0AYUCsHAT+wABsDATTgABZXMTAAOwQA+8AAGgYAAAEXFiZXM7MAAAAQAAEgAEAKEFAQC7BgoANxABAI8QAwABAwD0UxAAAAEABAABAAEKAaBAAAAUSmXybBQAAAEAE4ACsGASngABY2nzgAJkcvMAAmVvOzAAAAEAAAkAAgCOBwUA0QcDAAECAbAwET0AAW47MAAAAQAACQABAN8LCQDzgAJvdAEDADswAAABAADtAAIAagcAAPIK7AABAgGgcAAAEeRpOzAAAAEAAP8AAgBpBwAA8wn+ABOAAqBAAAAMIHJ1AQQA9C0TAAABAJgAAQABAwDzuBIAAAEArADzgAJhb9cHAACIsEAM6QABsTAUaQABNIEWOzAAAAEAAB4ADQC2BAEAeAUBAPQLAAAmDAcASgwBAM4PAQC8EAEASBEAAJURAwDZEQEAlBIAAPYSAQCQFAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/342a647c77a6.js b/pr/451/docs/search.index/342a647c77a6.js deleted file mode 100644 index 7baa88421..000000000 --- a/pr/451/docs/search.index/342a647c77a6.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9psQAAABAHMDAQAKAAEAAQAAOzAAAAEAAAYAAgD2EgAAXBMFACOIArBAE80AAWVzAQ0BsEATxwABZfWPEwAAAQABAAEAAQABBQA7MAAAAQAACAADAHERAgBuEgQAjhMAANcBAACFHYyPbzNAFIQC83kMAAABAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/355e9ccc3e7d.js b/pr/451/docs/search.index/355e9ccc3e7d.js deleted file mode 100644 index bc1b1f5c1..000000000 --- a/pr/451/docs/search.index/355e9ccc3e7d.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQcA9goTAAABAKMAVAAWAAEA8wEBaQEqCQAAAQAA9S0JAAABAAEAAQABAEcBAIDGP4ZFwA18oKAAABP1sGATbQABoBAAAAkBAAogig==") \ No newline at end of file diff --git a/pr/451/docs/search.index/3580644ae6d8.js b/pr/451/docs/search.index/3580644ae6d8.js deleted file mode 100644 index d9bc29d48..000000000 --- a/pr/451/docs/search.index/3580644ae6d8.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIBoDAAAA/8YQEJDAAAAYECoCAAABB2sFASKwABYW8BAwGgAAAAE0tzAXUQAAAjgAKgAAAAB8hydmMAA7BQEOcAAWNlczswAAABAAAIAAQA0QgCAP8IAQD0DwIAERIAAAcBAICFsFAQpgABoKAAABMWsPATLQABsCAPOAABRCEE84ACbnQ=") \ No newline at end of file diff --git a/pr/451/docs/search.index/35dbfa9e409c.js b/pr/451/docs/search.index/35dbfa9e409c.js new file mode 100644 index 000000000..46a6f00a4 --- /dev/null +++ b/pr/451/docs/search.index/35dbfa9e409c.js @@ -0,0 +1 @@ +rn_("AYQCsAARYwABsAARZQABNDbzAAFh8mMMAAABACOAArCQEWkBIWx0AYACsHATfAABsIATtQABY3MBAAA6MAAAAQAAAAAACQAQAAAAwxHEEd4S3xLOE88TJRQmFKAUoRTzgAJkZScBAICFoNAAABP1sBAEuAABoDAAABBeAggL") \ No newline at end of file diff --git a/pr/451/docs/search.index/3650ebdc9bae.js b/pr/451/docs/search.index/3650ebdc9bae.js deleted file mode 100644 index 5f74a8cc9..000000000 --- a/pr/451/docs/search.index/3650ebdc9bae.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEAOjAAAAEAAAAAAAcAEAAAALoSuxJTE1QTihOLE9YT1xPzBgFpAdkSAAAjgAKwUBKuAFlmbgEAAbBAEogAAWU7MAAAAQAADQACAKcGDACLEAAAI4ACoDAAAAvKYWU3AACAxbBQEwEAAbAQE6MAAbBgEwgAAQACmAg=") \ No newline at end of file diff --git a/pr/451/docs/search.index/3654024d46ce.js b/pr/451/docs/search.index/3654024d46ce.js new file mode 100644 index 000000000..bb7188101 --- /dev/null +++ b/pr/451/docs/search.index/3654024d46ce.js @@ -0,0 +1 @@ +rn_("AQACoDAAABEBoJAAABN2MGj15xEAAJcBAQABAAEAE4ACoDAAAA+0ZHIjAYSwEAjqAzuwIAvyAQqgMAAAD9wSABj1kgUAAC0AAQABAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/36590b411474.js b/pr/451/docs/search.index/36590b411474.js deleted file mode 100644 index 9e676b0c4..000000000 --- a/pr/451/docs/search.index/36590b411474.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYQCsCARAAEVoJAAABOObXABgAKgMAAAEIugUAAAEzRjcpcAAACFsCATAQABsMATvQABoGAAABFVEEAOOzAAAAEAABIABgCsBAgAUxEBAGcSAQBREwEApRMBABYUAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/368640dd3a84.js b/pr/451/docs/search.index/368640dd3a84.js deleted file mode 100644 index adaf64363..000000000 --- a/pr/451/docs/search.index/368640dd3a84.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9C4QAAABAAEAAQDzAgFz9RUJAAABAAEAAQBEBgEGAPR4EQAAAQABAAEAAQQBoEAAABOJZfL5EwAAAQABAQA7MAAAAQAABgABABwUBgDzBgFl9NATAAABAC8AAQAzBgOwYBLwAIdhcnXz/w8AAGICAQDzgAJhZQEDAPQyEQAAAQABAAEAAQIA9FEPAAABAC8EAQABggKgEAAAEsawQBJnAAFhcwGAA7AAB0oAAaAAAAALzrBwEAwAAW5yc+cHAACHoHAAABF3kQVBOjAAAAEAAAAAAAwAEAAAANkB2gHbAdwBTAe1B7YHHgkIDIwPjQ+OD5cQ") \ No newline at end of file diff --git a/pr/451/docs/search.index/36e021190be2.js b/pr/451/docs/search.index/36e021190be2.js new file mode 100644 index 000000000..73ec2d075 --- /dev/null +++ b/pr/451/docs/search.index/36e021190be2.js @@ -0,0 +1 @@ +rn_("AYACsHAUfQABoAAAAASdZmcBAQA7MAAAAQAABwABADAMBwABAQGgcAAAEoRhAQcHAAAjgAKhAAAAFIdkbnOAhKBgAAASgwBBSA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/37ae2f2cc265.js b/pr/451/docs/search.index/37ae2f2cc265.js deleted file mode 100644 index 7e096b382..000000000 --- a/pr/451/docs/search.index/37ae2f2cc265.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYECsDAPwwABsBATJAABc3QBAwDzOxIAAAEAAQAjgAKwABE4AAFoaQEAADswAAABAAATAAEAWAcTACOAAqAwAAAMh2Fl84ACcnTzAAJhbDswAAABAAAoAAMAlAATAJ8BAACpDxMA") \ No newline at end of file diff --git a/pr/451/docs/search.index/381301744cd9.js b/pr/451/docs/search.index/381301744cd9.js new file mode 100644 index 000000000..c765ac80b --- /dev/null +++ b/pr/451/docs/search.index/381301744cd9.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..5aeddedf9 --- /dev/null +++ b/pr/451/docs/search.index/384f556a9613.js @@ -0,0 +1 @@ +rn_("AQAAOjAAAAEAAAAAAAoAEAAAAM4Pzw+8EL0QSBHZEdoR9hL3EpAUkRTzAQF58vATAAABACMAA6BAAAARZ6BQAAAUQ2Vvc/KuDwAA1gI=") \ No newline at end of file diff --git a/pr/451/docs/search.index/3874f15e7771.js b/pr/451/docs/search.index/3874f15e7771.js deleted file mode 100644 index 2592ec0a1..000000000 --- a/pr/451/docs/search.index/3874f15e7771.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMA9qwMAADFAloAAQC4AgEAAQQAOzAAAAEAAAoABAAPEAUAQBIBAJ4SAQC6EwAAAYYCoAAAABLAoAAAABLBZHLzAgF2AewSAADzgAJlbycBAADHoDAAAA9woHAAABIRoIAAABONoNAAABMwoLAAABONQEUICTswAAABAAAJAAMAmQUCAPIGBAANDAEAI4ACU5KIn+d+ZHIBAAA6MAAAAQAAAAAACgAQAAAAIAkhCaYP2hLbEgoTCxOuEwIUGBQZFBOAAqAwAAAQJWx0w4AEoAAAABAVoAAAABAWNDZhZQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/38b236771a65.js b/pr/451/docs/search.index/38b236771a65.js deleted file mode 100644 index 9354b1f19..000000000 --- a/pr/451/docs/search.index/38b236771a65.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQA9NUTAAAMAB4ADwABAgGwQBItAAFlAVQPAAABgAKgkAAAE5ugkAAAE5wyM5cCAADGoCAAAAwnoGAAABDAsGAMswABAWQAAzswAAABAAAKAAMA7AYFAPMLAADjEgMA") \ No newline at end of file diff --git a/pr/451/docs/search.index/38e853561a8a.js b/pr/451/docs/search.index/38e853561a8a.js deleted file mode 100644 index a52df7590..000000000 --- a/pr/451/docs/search.index/38e853561a8a.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQBsEATxwABZfWPEwAAAQABAAEAAQABAgA7MAAAAQAACwACADoTBQDvEwUA8wEBbTswAAABAAANAAQAfgwAAOkQAABWEgUArxMFAPMBAWXydREAAAEAM4CEsBAQDAABoCAAAAvOFEAE") \ No newline at end of file diff --git a/pr/451/docs/search.index/39a52dda198c.js b/pr/451/docs/search.index/39a52dda198c.js deleted file mode 100644 index 9ec0123fc..000000000 --- a/pr/451/docs/search.index/39a52dda198c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQBsEATxwABZfWPEwAAAQABAAEAAQABAgA7MAAAAQAACwACADoTBQDvEwUA8wMBbTswAAABAAANAAQAfgwAAOkQAABWEgUArxMFAPOAAmNl") \ No newline at end of file diff --git a/pr/451/docs/search.index/39e247c24669.js b/pr/451/docs/search.index/39e247c24669.js new file mode 100644 index 000000000..56dc85f46 --- /dev/null +++ b/pr/451/docs/search.index/39e247c24669.js @@ -0,0 +1 @@ +rn_("AQYA9PsQAAABAL4AAQATgQOgAAAAEUOxIBSIAAFhZG4BBQD0bhAAAAEAAQABAAEUAPWoFAAAAQABAAEAAQABBQGwgBREAAF2OzAAAAEAAAcAAgC2EQMApRMDACOAArBwEdsAAWJkVwIAAYawwBNvAAGgoAAAE5KgkAAAE1sF4AL1CgcAAAEAAQDdCJIEAQYA87gSAAABAKwAAQABoGAAAA/VcgGHBAAA1wEAgMWgMAAAC/wAAIoK") \ No newline at end of file diff --git a/pr/451/docs/search.index/3a0723146c92.js b/pr/451/docs/search.index/3a0723146c92.js deleted file mode 100644 index 62b7bb9f1..000000000 --- a/pr/451/docs/search.index/3a0723146c92.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYMCsCARAAEVoJAAABOObXABggKwoBOBAAGwoBODAAFjcgECAaBAAAAQ/2UBwA8AADOAhLAgEH0AAbAgD0EAAVAUAPOAAm9y") \ No newline at end of file diff --git a/pr/451/docs/search.index/3a20dad21292.js b/pr/451/docs/search.index/3a20dad21292.js new file mode 100644 index 000000000..4ad6195a0 --- /dev/null +++ b/pr/451/docs/search.index/3a20dad21292.js @@ -0,0 +1 @@ +rn_("AQIA9KMRAAABAAEAAQATAAKgMAAAEW5ldPSqEAAAAQABAAEAAQMA9J8RAAABAAEAAQABgAKgAAAAEHugAAAAEHw0NvMDAXYB7wwAAJcCAACIsKATAAABsCAMEgABsGAS+gABsIAS3gABoCAAABDlCUxGOjAAAAEAAAAAAAkAEAAAAPoG+wb8BiMM3w9EEY0SjhKPEkMU") \ No newline at end of file diff --git a/pr/451/docs/search.index/3b114748649d.js b/pr/451/docs/search.index/3b114748649d.js deleted file mode 100644 index f8c1badf1..000000000 --- a/pr/451/docs/search.index/3b114748649d.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQBoEAAABOJZfL5EwAAAQABAQA7MAAAAQAABgABABwUBgDzBgFl9NATAAABAC8AAQAzAQOwYBLwAIdhcnXz/w8AAGICAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/3b4e28ed9312.js b/pr/451/docs/search.index/3b4e28ed9312.js deleted file mode 100644 index fbfe67c34..000000000 --- a/pr/451/docs/search.index/3b4e28ed9312.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsIASdQABsKAS7QABaXQBgAKw4BPjAAGwQA/YAAFscgEGAPRlEAAAAQABAAEARwUAAIeg0AAAE7+wUA/WAAGgAAAAEu+w8BOdAAEFkQPyeg8AAKoA") \ No newline at end of file diff --git a/pr/451/docs/search.index/3b71704824be.js b/pr/451/docs/search.index/3b71704824be.js new file mode 100644 index 000000000..ffb19b6b4 --- /dev/null +++ b/pr/451/docs/search.index/3b71704824be.js @@ -0,0 +1 @@ +rn_("AQUA9OgTAAABAFAAAQDzBAFj8usSAAABAAEFAPO7EgAAAQABAPOCAmZ0AQABsAAURAABZPTqCwAAAQABAAEA84ACZWkTgAKw4BRPAAF0dg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/3c0f5abd09f3.js b/pr/451/docs/search.index/3c0f5abd09f3.js deleted file mode 100644 index dc41dcf6f..000000000 --- a/pr/451/docs/search.index/3c0f5abd09f3.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9A0HAAABAAEAAQABhQKwcBN+AAGwMBLUAAFlcxMAA7BAD10AAaBgAAARCGJlczswAAABAAASAAQAgwUBAJwGCgDWDwEAKRADAAECAPOPBQAA8wkBAAEDAPVVDwAAnQABAAQAAQABCgGgQAAAE8Zl8uYTAAABACMABKAwAAAQl6CQAAAS+7BgEikAATBjaGn1dxEAAIwBAQABAAEA84ACZHLnBAAAh7AQCMYDJLAgC7sA/6AwAAAPehJBODswAAABAAASAAYAhQACAHYFAACkBQMAbgcFALEHAwDGCwAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/3c791ceb8da1.js b/pr/451/docs/search.index/3c791ceb8da1.js new file mode 100644 index 000000000..4fe4e1166 --- /dev/null +++ b/pr/451/docs/search.index/3c791ceb8da1.js @@ -0,0 +1 @@ +rn_("AQUAOzAAAAEAAAYAAQBFEAYAAQMAOzAAAAEAAAUAAQDEDAUAYwADoJAAABNaaHBzOjAAAAEAAAAAAA8AEAAAAJ8FoAW2BgUJRwxUDOsQ7BCaEpsS8hLzEvQS9RIiEyMT") \ No newline at end of file diff --git a/pr/451/docs/search.index/3c8f419d6519.js b/pr/451/docs/search.index/3c8f419d6519.js deleted file mode 100644 index 69ea2bae2..000000000 --- a/pr/451/docs/search.index/3c8f419d6519.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQACoCAAABL6sDARXwABaXTyYxIAAAEAAQMAOzAAAAEAAAYAAQDgEAYAAQgA9GITAAABAIIABQATAQOwkBPEAAGwUBKqAVlnbHA7MAAAAQAAEgAJAO4PAgBiEQEAmRIBALoSAQDxEgEAUxMBAIoTAQCZEwEA1hMBAAGAArAwEVsAAbAwEV0AAXJ2AQQBsAASJQABZDswAAABAAAFAAIAVxEDAFISAQC3BQABh7BQEicBDrBgEqgAAZQJAzowAAABAAAAAAALABAAAACJDIoMiww3DjgOhxAKEQsRDBENEaAS2RIjgAKgEAAADIhubwEDAPPrDwAAAQABACOAAqAAAAALx2RyYwGEoFAAABEHoDAAABAkFAEQ8mwHAAABABOAA7BgEkcAAbAADzgAAWNnchOAA6CwAAATgKBAAAAONmVvcw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/3ccd0c7a2788.js b/pr/451/docs/search.index/3ccd0c7a2788.js deleted file mode 100644 index 37bd94be5..000000000 --- a/pr/451/docs/search.index/3ccd0c7a2788.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQAOjAAAAEAAAAAAAcAEAAAAGEPYg8mECcQjhKPEh4THxMBAAD0zggAAAEAdArgACcAAACIoRAAABPboEAAABCQsGATywABoBAAAAw2sFATaQABsDAQ8wABsSAT6wABOcECOjAAAAEAAAAAABAAEAAAAOQI5Qj4CPkIwAvBC8ILygvNC4cMEBLzEvQSCBMJE3kTehMzgAOggAAAE3Jlb3k=") \ No newline at end of file diff --git a/pr/451/docs/search.index/3d12e183783e.js b/pr/451/docs/search.index/3d12e183783e.js new file mode 100644 index 000000000..999bc861c --- /dev/null +++ b/pr/451/docs/search.index/3d12e183783e.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..5e915d2a8 --- /dev/null +++ b/pr/451/docs/search.index/3d1419c1550f.js @@ -0,0 +1 @@ +rn_("AQoBoIAAABScZgF7EwAAAQgA9ScUAAABAAEAAQABAPMDAWb1gBAAAAEAAQABAAEA84MCYXMBAgKgIAAAEcKgMAAAEoxiaAGyDwAAI4ACoIAAABMvYXDzAAJobQGdBAAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/3d5f153390be.js b/pr/451/docs/search.index/3d5f153390be.js deleted file mode 100644 index 81a24667c..000000000 --- a/pr/451/docs/search.index/3d5f153390be.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUAOzAAAAEAAAcAAwA/EQIA5xIBAHsTAgABBwD2ChMAAAEAowBUABYAAQABBADzQhEAAAEAAQAjAAKgYAAAEmlhcvNrEAAAAQC8AQGAAqEQAAAT/7CAE/cAAWl1dwgAAIixUBQaAAGgAAAAEEywcBNEAAGhMAAAFA4KQRvyKgkAAPsGAYECoFAAABCPoKAAABK5a3ABAwA7MAAAAQAA9wACAI4MAQA5DvUA84ACbHTzgAJhZQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/3d8d9ef3a31c.js b/pr/451/docs/search.index/3d8d9ef3a31c.js new file mode 100644 index 000000000..1718191fa --- /dev/null +++ b/pr/451/docs/search.index/3d8d9ef3a31c.js @@ -0,0 +1 @@ +rn_("AQIAOzAAAAEAAAUAAQB1FAUA8w0BbTswAAABAAAFAAEAMxQFAEcAAACFsAAURAABsEASpwABoQAAABR7sAATagCKOAgCOzAAAAEAABwABwDqCwMAGBEHAMsSAQBJEwQAJxQEAC4UAwCcFAAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/3d998da360d9.js b/pr/451/docs/search.index/3d998da360d9.js new file mode 100644 index 000000000..83a8575ab --- /dev/null +++ b/pr/451/docs/search.index/3d998da360d9.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..dc64ce70f --- /dev/null +++ b/pr/451/docs/search.index/3dc6d08edc6b.js @@ -0,0 +1 @@ +rn_("AQ8A9AoUAAABAE8AAQABBAD0aRAAAAEAtgABAAECADowAAABAAAAAAAJABAAAADDEcQR3hLfEs4TzxMlFCYUoBShFPMAAW/yZQkAAAEAU4ADsQAUbgABaG9ww4CEoBAAABNAsIATgwABBQAG") \ No newline at end of file diff --git a/pr/451/docs/search.index/3e10313c5965.js b/pr/451/docs/search.index/3e10313c5965.js deleted file mode 100644 index 044978936..000000000 --- a/pr/451/docs/search.index/3e10313c5965.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQYA9FETAAABAFMAAQAjgAKhEAAAFANjchOAAqAgAAAI2mh5AQEBoGAAABP2c/JQEQAAAQAjgAKwIAk/AAFldQGAArAgD6cAAaCQAAATrGFlAQcBsEATxwABZfWPEwAAAQABAAEAAQABBAGwABKUAAFzOzAAAAEAAAsAAgAOEwcA+xMDAEcAAASGoIAAABLvsQAUCAABsEASUAABsIAUFgABsFASlgABFQAW9DIOAAABAAEAAQCDAISwQBQMAAGwMBKGAAGwMBMiAAHAAAM6MAAAAQAAAAAACQAQAAAAmgSMEI0QMBExETYRNxFvE3AT2xMBAwGwQBPdAAF08kETAAABACOFAqBAAAATcWJzAYACoAAAAAj6oCAAABB/Z3ABAQD0TBEAAAEAAQABAPOAAmRst+gAAIyg0AAAEvqgUAAAEOygkAAAExawUBJ1AAGgMAAADB63mAc7MAAAAQAAYQATAPIBAQD4AQEASAQDAKAFAACYBgEAMwcBAEYHAADWCAEA4wgAAO0IAAAfCQAANww+AIgMAABzDwEARhABAGEQAQCmEAEAJxEEACsSAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/3e3cbb04f471.js b/pr/451/docs/search.index/3e3cbb04f471.js deleted file mode 100644 index 27f235dfb..000000000 --- a/pr/451/docs/search.index/3e3cbb04f471.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9KQSAAABAJEAAQDzAAFj9dsPAAABALwAAQABAAEJAPPfEwAAAQA1AAENAaCgAAAUI3fyExQAAAEAAYECsIAT7QABoGAAABQRZHM3AQCEhaBAAAATwrBgE5YATAKQCiOAAqBwAAAQ22RyAQsBoEAAABPGZfLmEwAAAQATgAKwMA/3AAFkcvMCAWX0sQcAAAEAAQABACOAArAgC74AAWV5AQoA8y8SAAABAAEAAQsAOzAAAAEAAAcAAQAOEwcAAQIA9GsTAAABAEkAAQC3AACAhbBwEmMAAbDQFAgAAWAgBgEGAPM7EgAAAQABAAEDAbBAE90AAXTyQRMAAAEAI4UCoEAAABNxYnMjAgOgQAAAEH+gUAAAEOxhYmb31ggAAAEASAAnBwEAXwABAPOAAmlwAQQA8/QPAAABAAEA8wABcDswAAABAAAMAAEAtQYMACOAA6AwAAALzbBwEyIAAWFlafOAA2RtbgEAADowAAABAAAAAAAHABAAAADhCBkMJRImEnoSexJ8En0S8wIBZDswAAABAAAJAAMAVxEDACASAwBSEgEAI4ACoJAAABO5aW8BgAOwMBLWAAGAExgAEAGwUBCbAAFjc3jzAAFlAasEAADnFQAAiaAAAAAMKqBAAAAMf7BADIAAAZBIXQH/AQAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/3e3f10d5e550.js b/pr/451/docs/search.index/3e3f10d5e550.js new file mode 100644 index 000000000..683d4a6a6 --- /dev/null +++ b/pr/451/docs/search.index/3e3f10d5e550.js @@ -0,0 +1 @@ +rn_("AQMAOzAAAAEAAIwAAgDcEAAA7RGLAAEAAbBwE68AAWXyyg8AAAEAIwACoEAAABQJZXP0MxAAAAEASwQBADOAA6BgAAARj2Rpbw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/3e98557ba4a3.js b/pr/451/docs/search.index/3e98557ba4a3.js deleted file mode 100644 index 49f684ba0..000000000 --- a/pr/451/docs/search.index/3e98557ba4a3.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQkA898TAAABADUAAYACoEAAABLGsCATgQABaGkBDAGgoAAAFCN38hMUAAABAAEBAPN/DwAAAQABAPOAAnR1AYECsIAT7QABoGAAABQRZHM3AwCAhqBAAAATwrBgE5YATAKQDgEAAbBQEIgAAWP0LwcAAAEAAQAEAsOAhKAQAAAMKqBQAAASEQxABA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/3eddab2797bd.js b/pr/451/docs/search.index/3eddab2797bd.js new file mode 100644 index 000000000..17e4958e4 --- /dev/null +++ b/pr/451/docs/search.index/3eddab2797bd.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..ca8211b72 --- /dev/null +++ b/pr/451/docs/search.index/3f128b5def95.js @@ -0,0 +1 @@ +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/3f243484e57a.js b/pr/451/docs/search.index/3f243484e57a.js deleted file mode 100644 index 51d7cfa7a..000000000 --- a/pr/451/docs/search.index/3f243484e57a.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsHAT9wABoAAAAASGZmcBAQA7MAAAAQAABwABAPULBwABAQGgcAAAEg9hAeYGAAAjgAKhAAAAE/9kbnOAhKBgAAASDgBBSA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/3f24cd5aa4fa.js b/pr/451/docs/search.index/3f24cd5aa4fa.js deleted file mode 100644 index ad2bb5df2..000000000 --- a/pr/451/docs/search.index/3f24cd5aa4fa.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQYAOzAAAAEAAAUAAQDJEgUAg4YEsFASkgABsHAS+AABoNAAABPPYm5vdhOAArAQEmUAAXByAQABoDAAABFkdAEXEwAA8wIBc/IoEAAA8wITgAKgYAAAEBlldQECAPa1DAAAAQAZAwEAJQEBAAGAArAAElAAAYAONjKQAWVzAQEA8ykHAADECwEAY4ADsIATaQABYm5084ADYW9yARQA9PsTAAABAAEAAQABAAA7MAAAAQAABgACAPYSAABcEwUAI4gCsEATzQABZXMBBAGwMBLHAAFj8wIRAAABAAEAAQUAOzAAAAEAAAgAAwBxEQIAbhIEAI4TAADzAgNjZXDzeQwAAAEAAQBjgAOgYAAAD3llb3UBAAGwYBN1AAFlAawTAADzAgFz9vIIAAABAAEAzQc/ABUBAQgA8y8SAAABAAEAQ4ADoBAAABL6oOAAABOAZG9zAQIBoFAAABEBcgGhEAAAAYACoEAAABAloHAAABO6YWUBAAA7MAAAAQAACwACAMoGBQBJEgUA84ACdHYBAQD0wgcAAAEAAQABAPMAAm5y9ucIAABXAMcGAQCwAAEA8wCHMJEM96kEAADWC+QBAQCFAAEA8AA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/3f8645c00d7c.js b/pr/451/docs/search.index/3f8645c00d7c.js deleted file mode 100644 index f1fe4a621..000000000 --- a/pr/451/docs/search.index/3f8645c00d7c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9CwRAAABAAEAAQABCgDzKhMAAAEAAQDzgQJtdAEGAPPuEAAAAQABAAGCArBgErEAAbDQE8kAAWlyE4ACsBAMkwABZWkBBQD0axMAAAEASQABAPMEAWPycxIAAAEAI4ICsFASRQABZnQBAAGwABPAAAFk9LULAAABAAEAAQDzgQJlaTcGAACJsTAT5gABoGAAABDtoFAAABCAsJAShAABsEAQfQABtRAHAaATAAA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/3fb5642e59ae.js b/pr/451/docs/search.index/3fb5642e59ae.js new file mode 100644 index 000000000..c54201544 --- /dev/null +++ b/pr/451/docs/search.index/3fb5642e59ae.js @@ -0,0 +1 @@ +rn_("AQUAOzAAAAEAAAcAAwCwEQIAYRMBAPgTAgABBwD2hRMAAAEArABYABgAAQABBADzsxEAAAEAAQAjAAKgYAAAEuFhcvPUEAAAAQDIAQGAAqEQAAAUh7CAFH0AAWl1dwgAAIixUBSkAAGgAAAAELSwcBPBAAGhMAAAFJgKQRvyTgkAAD0HAYECoFAAABD5oKAAABMza3ABAwA7MAAAAQAAAwECANEMAQCIDgEB84ACbHTzgAJhZQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/4031cc647b10.js b/pr/451/docs/search.index/4031cc647b10.js deleted file mode 100644 index 940904594..000000000 --- a/pr/451/docs/search.index/4031cc647b10.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUAOzAAAAEAAPcAAgCODAEAOQ71AAEBAaAAAAAStnP1pgQAALEK1gMBAAMAAQECoGAAABM0oOAAABOOZXQ7MAAAAQAACAABAKwECAABgAOwIBIFAAGwsBI+AAGwsBJAAAFiY3PzgARjbHR2") \ No newline at end of file diff --git a/pr/451/docs/search.index/404afcce0ada.js b/pr/451/docs/search.index/404afcce0ada.js deleted file mode 100644 index c3c579fe4..000000000 --- a/pr/451/docs/search.index/404afcce0ada.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQgA9PoPAAABAKkAAQABAAGggAAAFBJm8skIAAA3CgEHAPXPEgAAAQABAAEAAQAjgAKgUAAAEN9hb/MCAmZzOzAAAAEAAAgABAAfBwAAUgcFAK8MAAAXEQAAM4ADoEAAAAvGYWVv") \ No newline at end of file diff --git a/pr/451/docs/search.index/4100782a5d31.js b/pr/451/docs/search.index/4100782a5d31.js deleted file mode 100644 index 9bb908fa5..000000000 --- a/pr/451/docs/search.index/4100782a5d31.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIAOzAAAAEAAAUAAQDvEwUA8w0BbTswAAABAAAFAAEArxMFAEcAAACFsAATwAABsEASMgABoQAAABP1sAAS8ACHOAgCOzAAAAEAABgABgC1CwMArhAHAFQSAQDPEgQApxMEABIUAAA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/4145ba7520f0.js b/pr/451/docs/search.index/4145ba7520f0.js new file mode 100644 index 000000000..ee4aa4ae1 --- /dev/null +++ b/pr/451/docs/search.index/4145ba7520f0.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..561c9a685 --- /dev/null +++ b/pr/451/docs/search.index/41556e3d4595.js @@ -0,0 +1 @@ +rn_("AYECoBAAABMqoHAAABNyYWMBhwKwABPJAAGwABPLAAE0NlMAhKCQAAATUrCwE/AAAYEACfUYBwAANgAxCRABIAA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/4159f6c35981.js b/pr/451/docs/search.index/4159f6c35981.js deleted file mode 100644 index 9ee2a28d9..000000000 --- a/pr/451/docs/search.index/4159f6c35981.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYICsCAQwgABsDAROAABZXABBQD0aw8AAAEA6AABAFOAA7CQEWkAAWFpcgGBArCgE4EAAbCgE4MAAWNyxwAAAIWwUAyhAAGgIAAACT2gIAAAC9U8AAE6MAAAAQAAAAAACgAQAAAApgGnAVsE0AbuCO8IARACEG0QbhB8EQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/41c9d94a3575.js b/pr/451/docs/search.index/41c9d94a3575.js new file mode 100644 index 000000000..cd1eef90d --- /dev/null +++ b/pr/451/docs/search.index/41c9d94a3575.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..a2b23d90e --- /dev/null +++ b/pr/451/docs/search.index/42b98052bb96.js @@ -0,0 +1 @@ +rn_("AQIAOzAAAAEAAO0AAgD7CwAABQ3sAPMAAW07MAAAAQAA7QACAJkFAADDBewAAQMAOzAAAAEAAAgAAgCKDwYAQRMBAPMAAWM7MAAAAQAAVwAnAJ8ECwBlBQAAAQkBABUMAQAdDAIA3AwAAOgMAgD6DAEAtA8AALkPAQC8DwEAxg8AAEwQAgBTEAEAWBABAH0QAAD0EAAA+hAAAEYRAQBTEQEAZxEBAGsRAgDLEQEA3hECAOQRAACZEgAAohIBAKcSAQDwEgEAABMBAAUTAQAmEwEANhMDADwTAQCdEwEAsRMAAB8UAABIFAQAURQCAPOAAnJ3AQkA82MUAAABADsAAYACoEAAABNAsCAT/gABaGkBAQDz4Q8AAAEAAQATgAKwwBSdAAF0dQGBArCAFHMAAaBgAAAUm2RzNwMAgYagQAAAFEawYBQWAFACkA4BiAKwABPJAAGwABPLAAE0NjOAA6AwAAARamRuc2MAA6AQAAAEwmFlbwGFAAAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/4314e4d51001.js b/pr/451/docs/search.index/4314e4d51001.js new file mode 100644 index 000000000..ab7d5d50f --- /dev/null +++ b/pr/451/docs/search.index/4314e4d51001.js @@ -0,0 +1 @@ +rn_("AQEA9FsHAABuCN8AAQABBQD0LhQAAAEAAQABAAEFAPQeEwAAAQCUAAEA8wICY3X1PBAAAAEAxQABAAEA84ACbnM=") \ No newline at end of file diff --git a/pr/451/docs/search.index/4351feb790fe.js b/pr/451/docs/search.index/4351feb790fe.js deleted file mode 100644 index 683f19891..000000000 --- a/pr/451/docs/search.index/4351feb790fe.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQYA9JEQAAABALgAAQATgQOgAAAAENqxIBQAAAFhZG4BFAD1HhQAAAEAAQABAAEAAQUBsIATwAABdjswAAABAAAHAAIARREEACoTAgAjgAKwcBFrAAFiZFcAAAGGoMAAABL1oKAAABMXoJAAABLhsFAQCgABBeAC9ekGAAABAAEAnAhuBAEGAPNCEgAAAQCoAAEAAaBgAAAPcnIBcAQAANcBAIDFoDAAAAvGAACKCg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/436e158b126a.js b/pr/451/docs/search.index/436e158b126a.js deleted file mode 100644 index fb740fc36..000000000 --- a/pr/451/docs/search.index/436e158b126a.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsHASBwABsCAUDAABaW8BAAGw8BQMAAFr9oATAAAFAAEAAQABACUA8wUBZPJHEgAAAQABAgDzWRAAAAEAAQDzAAFpOjAAAAEAAAAAAAcAEAAAADYQXBJEE0UTdRN2E5QTlRMjgwKwsBPrAAFhcwGBAqCgAAASsKBAAAAP+W9284CEAVAB") \ No newline at end of file diff --git a/pr/451/docs/search.index/43bef58741e0.js b/pr/451/docs/search.index/43bef58741e0.js new file mode 100644 index 000000000..00a2ca981 --- /dev/null +++ b/pr/451/docs/search.index/43bef58741e0.js @@ -0,0 +1 @@ +rn_("AYMCoFAAABD5oKAAABMza3ABAAGwQBJ8AAFs9F0JAAABAGsHAQABCAD0kBIAAAEAAQABAAEEAaAAAAAQ03MBOxAAAAECAaBQAAAS72MBvAwAALcAAACGsEAM+gABoJAAABMboDAAAAv+lRABOzAAAAEAAAgABAC/BAAAjwUCAL8MAQCoDwIAo4CEsDAJHAABoBAAABNpkQBAARABsAAUZwABczswAAABAAAFAAEAgRQFAAEDAPRdCQAAAQBrBwEAY4ADoAAAAAv+bnJ1AQACsNATrwABoDAAAA+rYmr2cgUAAAEAAQBJAAEAdQEBAAGwgBPOAAF0OzAAAAEAAEUABAC7BQAABwkAABEJAAByDEIAAQkA87ESAAABAAEA84ADY2d0AYMCoJAAABQboJAAABQcMjMBgAKgUAAAFEeggAAAE2ZjZfMAAnN08jQHAAABAAEAAaCwAAATsWLzOxMAAC4BAQAjgAKgAAAAEzpkciOAAqBQAAAUDWNl84ACaXkBBAD1JBEAAAEAAQABAAEA8wEBbgEqBwAAAQEAOzAAAAEAAAkAAgDXBwcA+BIBAAEGAPShEwAAAQABAAEAAQ8A9IwUAAABAAEAAQDzgQJobQEAAbBAFEsAAWX1DxQAAAEAAQABAAEAU4ADoCAAABHGbG50120AgMugIAAAFHywYBC3AAGwQA/kAAHAGzgK") \ No newline at end of file diff --git a/pr/451/docs/search.index/441e425635ce.js b/pr/451/docs/search.index/441e425635ce.js deleted file mode 100644 index 78b84ee21..000000000 --- a/pr/451/docs/search.index/441e425635ce.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsRAT+QABsBAMBgABZW8BBQA7MAAAAQAADQABABgRDQABAgDzjA8AAAEAAQABAgD0iA8AAAEAAQABAAEDADswAAABAAAIAAEAORAIAIcHAACHsKATLQABsNATvQABoFAAABOMjBQROzAAAAEAAAYAAwAoBwAAZRADACkSAQDzgAJsdBOAA6AgAAAMfLCgE6UAAWFkbg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/444d4497d47d.js b/pr/451/docs/search.index/444d4497d47d.js new file mode 100644 index 000000000..3d1ace2f5 --- /dev/null +++ b/pr/451/docs/search.index/444d4497d47d.js @@ -0,0 +1 @@ +rn_("AQcA9cMTAAABAAEAegABAAEHAPOwEAAAAQABAAGAAqAAAAAHTqAQAAAI/WR48wEBZfICEAAAAQDnAAAAhbBgDNkAAbBADEoAATIIAfTCBAAAggLOCQEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/450719784301.js b/pr/451/docs/search.index/450719784301.js deleted file mode 100644 index 6b700b79a..000000000 --- a/pr/451/docs/search.index/450719784301.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUAOzAAAAEAAAcAAwA/EQIA5xIBAHsTAgABBADzQhEAAAEAAQAjAAKgYAAAEmlhcvNrEAAAAQC8AQGAAqEQAAAT/7CAE/cAAWl1NwQAgoexUBQaAAGgAAAAEEywcBNEAAGhMAAAFA4KQBsTgAKwEBJ1AAFpcg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/4513c73d7beb.js b/pr/451/docs/search.index/4513c73d7beb.js new file mode 100644 index 000000000..99c832462 --- /dev/null +++ b/pr/451/docs/search.index/4513c73d7beb.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..1b84f65f8 --- /dev/null +++ b/pr/451/docs/search.index/451831b31f8f.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..16c277acb --- /dev/null +++ b/pr/451/docs/search.index/4539bd8aa823.js @@ -0,0 +1 @@ +rn_("AQMA9AMJAAABAE0DAQABAwDzTgwAAAEAAQDzgAJpbwEBAbBQEPIAAWP0UQcAAAEAAQAGAiOBAqBQAAAQtGNuAQQAOzAAAAEAAAcAAwCwEQIAYRMBAPgTAgABAQA6MAAAAQAAAAAABwAQAAAA0hHTERITExNrE2wTGRQaFPMJAWkB8RAAANcCAACHsMATVgABoFAAAA+doRAAABRDFAEXOzAAAAEAACIAAQC2ASIA") \ No newline at end of file diff --git a/pr/451/docs/search.index/4571532c4b5c.js b/pr/451/docs/search.index/4571532c4b5c.js new file mode 100644 index 000000000..37c3f2110 --- /dev/null +++ b/pr/451/docs/search.index/4571532c4b5c.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..f33b5d984 --- /dev/null +++ b/pr/451/docs/search.index/4581b8194ca6.js @@ -0,0 +1 @@ +rn_("AYACsIAS7QABsKATZwABaXQBgAKw4BRpAAGwQBA5AAFscgEGAPTPEAAAAQABAAEARwUAAIeg0AAAFEOwUBA3AAGgAAAAE2mw8BQdAAEFkQPy3A8AAK4A") \ No newline at end of file diff --git a/pr/451/docs/search.index/45c7f13213d0.js b/pr/451/docs/search.index/45c7f13213d0.js deleted file mode 100644 index d211fd540..000000000 --- a/pr/451/docs/search.index/45c7f13213d0.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQcAOzAAAAEAAIcAAgBzEAAAfRGGAAEDADswAAABAACGAAEAqg2GAAECADswAAABAAAHAAEA9QsHAPMAA2JpdDswAAABAACHAAIAgQQAALwEhgA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/45cefb4b1fe7.js b/pr/451/docs/search.index/45cefb4b1fe7.js deleted file mode 100644 index 337471401..000000000 --- a/pr/451/docs/search.index/45cefb4b1fe7.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9OMSAAABAAEAAQATAAKwgBK3AAFzdvQ7CQAAAQApCgEAE4ACsDAQiAABbG4BgAKgAAAAEBWgAAAAEBY0NvMDAXYBrQwAABMAAqBAAAARBWFz9+gLAACVA14BPQEBAAEApQEBAgD0kBIAAAEAjwABAAEDAPTEEgAAAQCVAAEA8wACYnU6MAAAAQAAAAAAEwAQAAAAHAcdBx4HYQ9iDwcQCBAmECcQuBC5EGURZhGOEo8SmxKcEqsSHhMfE/OAAmR0E4ACoCAAAAy3ZXIBBwGgQAAAE59l8skTAAABAAEAAPUtCQAAAQABAAEAAQAjgAKgoAAAE/VmcEcAAACFsDAPPAABoFAAAA/9sHAQgQABsUAUHAABgSkA8lEFAAABAPOAAmx0lwEAgIaxMBOhAAGxEBPLAAGgsAAAEsYRRQg=") \ No newline at end of file diff --git a/pr/451/docs/search.index/46615455c43f.js b/pr/451/docs/search.index/46615455c43f.js deleted file mode 100644 index 10648baa7..000000000 --- a/pr/451/docs/search.index/46615455c43f.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMAOzAAAAEAAAkABAArCQEAHxADAF0QAQBVEwEAAQIBsEAT3QABdDswAAABAAANAAYAahIDANQSAQBBEwEAWBMBANITAQD3EwEAE4ACsDASnQE/aXABiAKwABNMAAGwABNOAAE0NhOBAqAwAAARAWRzAYACoCAAAAwqsGATfgABbngBBQDzGBMAAAEAAQATgAKgMAAAE3FlefMAhBIAA/LFDwAA3ADzgAJydA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/4663a4e119e7.js b/pr/451/docs/search.index/4663a4e119e7.js deleted file mode 100644 index 07b00ae8e..000000000 --- a/pr/451/docs/search.index/4663a4e119e7.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUBsEASMgABZfJUEgAAAQABggKgkAAAE5ugkAAAE5wyMwEAAqAgAAASDqAgAAAIx2ltOzAAAAEAAAwABgB+BAAAtQQFADcJAAA+DwEAcRMAAIMTAQDzAAFzAa0QAAABBQD0/BIAAAEAAQABAAEAAPUtDAAAAQABAAEAAQDzgAJna3OAhLCwE1UAARFIAAEFAaBAAAARbmXzsAwAAOQDAQATgAKgMAAAC+ZlcgGAA7BQE5QAAaAQAAALtKBwAAAS7GduckcBAACFoGAAABNyoFAAABPDoKAAABNkFQEEOzAAAAEAABEABADwAQEAORAIADIRAwAvEgIAAYUCsHATfgABsDAS1AABZXMTAAOwQA9dAAGgYAAAEQhiZXM7MAAAAQAAEgAEAIMFAQCcBgoA1g8BACkQAwABAwD08g8AAAEABAABAAEKAaBAAAATxmXy5hMAAAEAE4ACsGASKQABY2nzgAJkcvMAAmVvOzAAAAEAAAkAAgBuBwUAsQcDAAECAbAwENQAAW47MAAAAQAACQABAKoLCQABAAD0LhAAAAEAAQABAAEBADswAAABAADnAAIAoQUAANcH5gBjAAOwEBKSAAFlaXM6MAAAAQAAAAAADgAQAAAA2ggVCRYJFwkYCdAL0Qs2DzcPXA9vD1gQfBD4EvkS44CEsCAHKgABEUAEAQGEsDALvgABoEAAAAx/oIAAABDbsEAMgAABAAAdAf8BAAABBQD1RhMAAAEAAQBzAAEAE4ACoAAAAAbQYWcBAwD0BxIAAAEAZQEBAPOAAmlvAQEAOzAAAAEAAOcAAgBIBwAAwwrmAAEIAbBAE8cAAWX1jxMAAAEAAQABAAEA84ECZ3QBAQGgcAAAEXRpOzAAAAEAAPkAAgBHBwAAygn4AAEFAPNIEAAAAQABAPOAAmFvE4ACoEAAAAvlcnUBgAKgAAAAByygEAAACNlkeBMAArAgDKcAAWVy8qAPAAABABOAArCwFAwAAXJ4AQoA9AMTAAABAAEAAQATgAKwEBCPAiphZQEEAPQpEAAAAQABAAEAAQQAOjAAAAEAAAAAAAcAEAAAAKQSpRKzErQSNhM3E0kTShMBAwDzQhIAAAEAqADTgISwQBCBAAEBSAHHvwMAj6CAAAASRLBgDJYAAbBADA8AAbEwE+MAAT+pHzswAAABAABLACIAcAAAAHUAAACfBAEAqgQAAF0FAQASBwEAIgcAACYHAQAOCQAAIgkHADkJAQC9CwAA0wsBANoLAQDrCwcADwwBACsMAQB2DAEAaw8BANsPAQBUEAEAXxABAHcQAACYEAIAqBABALoQBADfEAAALBEDAGkRAQAfEgAAfhIBAIISAQCvEgAACBQBAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/4683fef789b0.js b/pr/451/docs/search.index/4683fef789b0.js new file mode 100644 index 000000000..92b00555f --- /dev/null +++ b/pr/451/docs/search.index/4683fef789b0.js @@ -0,0 +1 @@ +rn_("AQAA9JQQAAABAAEAAQDzAgFz9TkJAAABAAEAAQB/BgEGAPToEQAAAQABAAEAAQQBoEAAABQJZfJ/FAAAAQABAQA7MAAAAQAABgABAKYUBgDzBgFl9FQUAAABADMAAQAzBgOwYBNqAIphcnXzYRAAAHcCAQDzgAJhZQEDAPSbEQAAAQABAAEAAQIA9LAPAAABAE0EAQABggKgEAAAE0CwQBLeAAFhcwGAAqAAAAAMBrBwEHIAAXJz5wcAAIegcAAAEeeRBUE6MAAAAQAAAAAADAAQAAAA4wHkAeUB5gFsB9UH1gdCCUMM7g/vD/APARE=") \ No newline at end of file diff --git a/pr/451/docs/search.index/46d02c2cf6f3.js b/pr/451/docs/search.index/46d02c2cf6f3.js deleted file mode 100644 index a4bf8ae1c..000000000 --- a/pr/451/docs/search.index/46d02c2cf6f3.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9ScRAAABAAEAAQABAAGAArBgEoIAAaAgAAAQfG95AQABoMAAABOgafTYEgAACAAbABsAAQYA9AQUAAABAAEAAQABAgD0ehAAAAEAmgABAAECAaBAAAASr3I7MAAAAQAABwACAHgQAQAPEQUA84ACYW9nAwAAhrCAEq4AWbCgEyQAAQwwBjswAAABAAAIAAMA0gsAADoRAwAbEgMA84ACZWk=") \ No newline at end of file diff --git a/pr/451/docs/search.index/4701aa0c1499.js b/pr/451/docs/search.index/4701aa0c1499.js new file mode 100644 index 000000000..c9d3b1562 --- /dev/null +++ b/pr/451/docs/search.index/4701aa0c1499.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..8a0d05dc5 --- /dev/null +++ b/pr/451/docs/search.index/4702bdda8368.js @@ -0,0 +1 @@ +rn_("AQ0A9cMTAAABAAEAegABAAECAPTvEAAAAQCRAQEAAQMA9FoRAAABAHsBAQDzAAJidQEBEAAAAQMA9O0QAAABAJEBAQDzhAJkcwECAPP/CAAAAQBYAPOAAml0ARgA86oUAAABAAEAI4ECsMATwQABY20BBQGwYBKpAAFy9A8MAAABAO8DAQABCQD21BIAAAEA/wCJADUAAQATgAKwYBCuAAFkbgEDAaBwAAATEWf0TwcAAAEABgIBAAEJAPNjFAAAAQA7AAGBArCAFHMAAaBgAAAUm2RzFwEAiYWw0BSdAAGgQAAAFEawYBQWAFACkApnugAAjLBwERIAAbCwEyAAAaCgAAAS07FQFJQAAbDwE/IAAW3BD/SGAAAAAQABAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/470732838eeb.js b/pr/451/docs/search.index/470732838eeb.js deleted file mode 100644 index 95e7818d5..000000000 --- a/pr/451/docs/search.index/470732838eeb.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQkA898TAAABADUAAQ0BoKAAABQjd/ITFAAAAQABgQKwgBPtAAGgYAAAFBFkczcBAIWFoEAAABPCsGATlgBMApAK") \ No newline at end of file diff --git a/pr/451/docs/search.index/474b31e6c524.js b/pr/451/docs/search.index/474b31e6c524.js deleted file mode 100644 index 5f815b2ce..000000000 --- a/pr/451/docs/search.index/474b31e6c524.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQwA8yoTAAABAAEAAQYA8+4QAAABAAEAAYICsGASsQABsNATyQABaXITgAKwEAyTAAFlaRcDAIOIsTAT5gABoGAAABDtoFAAABCAsJAShAABsEAQfQABtBAH8wABYfQMCQAAAQC1BwEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/4805b324248d.js b/pr/451/docs/search.index/4805b324248d.js deleted file mode 100644 index 50ce1fac8..000000000 --- a/pr/451/docs/search.index/4805b324248d.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEA9RkJAAABAAEAAQABAPMAAWnzmAwAAHQHAQABAQGgQAAAE4ll8vkTAAABAAEEAqAgAAARUqAwAAASF2JoAVMPAADnAACAxaAQAAAMmaAwAAAQAAQQKAE=") \ No newline at end of file diff --git a/pr/451/docs/search.index/48769e729a3a.js b/pr/451/docs/search.index/48769e729a3a.js new file mode 100644 index 000000000..72a2b33d3 --- /dev/null +++ b/pr/451/docs/search.index/48769e729a3a.js @@ -0,0 +1 @@ +rn_("AQIBoKAAABNSZPLnBgAAAQABgQKgMAAAFFigUAAAE+FjcwEEA7AgETEAAaBQAAAR3aBAAAAUIGxzdvIbFAAAAQDzAANhZW87MAAAAQAACAADAIIEAQDzCwAA/QwFAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/4aa7b4743afe.js b/pr/451/docs/search.index/4aa7b4743afe.js deleted file mode 100644 index 9f79cee80..000000000 --- a/pr/451/docs/search.index/4aa7b4743afe.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQA9t8TAAABAA0AAQAjAAQAAQMA8y8SAAABAAEAI4ECsSAUFgABY2sTgAKgMAAAE59uclMAA6BAAAAL1WVvcvSjEAAAdAGyAQEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/4bd0dc5c36db.js b/pr/451/docs/search.index/4bd0dc5c36db.js new file mode 100644 index 000000000..b41bd2c91 --- /dev/null +++ b/pr/451/docs/search.index/4bd0dc5c36db.js @@ -0,0 +1 @@ +rn_("AQEA8+EPAAABAAEAAYmEoIAAABSboEAAABRGoGAAABRmoJAAABSfAoAKE4EDoKAAABPUsNAUPwABY2l1M4ADsMAUnQABYXR1") \ No newline at end of file diff --git a/pr/451/docs/search.index/4c05ce7be64f.js b/pr/451/docs/search.index/4c05ce7be64f.js deleted file mode 100644 index 9b545b5fc..000000000 --- a/pr/451/docs/search.index/4c05ce7be64f.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQoA9AwTAAABAA8AAQABgwKgIAAAD9GgMAAAEGRpdAEAAPR6EgAAAQABAAEA8wQBZPQgEgAAAQABAAEAI4aEsKATtwABsEASeAABsHATZQABFYAAAQACsDAPwwABsBATJAABc3TycQUAAF4GAYACsMAT4wABoAAAAAcidHnzgIURwQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/4c89ae5762cb.js b/pr/451/docs/search.index/4c89ae5762cb.js deleted file mode 100644 index f5adf7143..000000000 --- a/pr/451/docs/search.index/4c89ae5762cb.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIAOzAAAAEAABEABQCGBQAA5QsAAJIMAACPDw0A7BAAAAGEArAAEPoAAbAAEPwAATQ2FwgAAIigIAAAEVKgMAAAEMCg4AAAExagMAAAEhewcA9nAAGgIAAABZDLwAg7MAAAAQAACwEVAKkBAAB9BQAAkAUAAMcIAADNCAAA6ggCAMULAAABDAEAKAwBAJUMAADDDOYAUw8AAGQPAQBTEAAADhEAAF0SAQA6EwUAVxMAANkTAADvEwUAChQBAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/4d134b527dd8.js b/pr/451/docs/search.index/4d134b527dd8.js new file mode 100644 index 000000000..0e3005109 --- /dev/null +++ b/pr/451/docs/search.index/4d134b527dd8.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..4a784b7f7 --- /dev/null +++ b/pr/451/docs/search.index/4d420ba30216.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..0b55a2ee4 --- /dev/null +++ b/pr/451/docs/search.index/4d60442fb3dc.js @@ -0,0 +1 @@ +rn_("AQAA9A0TAAABAFkBAQDzBAFzOzAAAAEAAA0AAgCJEwcAgRQFAEcAAAGGoIAAABNpsQAUkAABsEASxwABsIAUoAABsFATDwABFQAW9IEOAAABAAEAAQABBQDz5g8AAAEAAQBjgISwQBByAAGgAAAABtMA8AA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/4d88a08a2ae4.js b/pr/451/docs/search.index/4d88a08a2ae4.js deleted file mode 100644 index 1f3be335a..000000000 --- a/pr/451/docs/search.index/4d88a08a2ae4.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQA80IRAAABAAEAAYICsEASNQABsEASNwABNDYBCwDzLxMAAGgAAQABBgD0DxQAAAEACgABAPMCAXIBeBMAAPMCAWX0AxAAAAEApwABABOAAqBQAAASaWR08wCEEQEB92sQAAABAG4AHQABAAEALwE=") \ No newline at end of file diff --git a/pr/451/docs/search.index/4e0fe03427ba.js b/pr/451/docs/search.index/4e0fe03427ba.js deleted file mode 100644 index bc31630d9..000000000 --- a/pr/451/docs/search.index/4e0fe03427ba.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9OMSAAABAAEAAQATAgKwgBK3AAFzdvQ7CQAAAQApCgEA8wABYwEYDAAAAQICoLAAABO5oLAAABO6Y3PzTRAAAAEAXAABgQKwUBFnAAGgwAAAE4xoafOCAmVp84ACZW8=") \ No newline at end of file diff --git a/pr/451/docs/search.index/4e2f866a26d7.js b/pr/451/docs/search.index/4e2f866a26d7.js new file mode 100644 index 000000000..9407e2269 --- /dev/null +++ b/pr/451/docs/search.index/4e2f866a26d7.js @@ -0,0 +1 @@ +rn_("AQABsIAUIQABcPIdFAAAAQBDBQOgIAAAEUWgQAAAEplkZXPz4A8AAEsDAQABAgA7MAAAAQAAEwABAAsQEwATgYSgUAAAEPWgEAAADBSgIAAADN0cQAAzgAOwUBNnAAFhZm4=") \ No newline at end of file diff --git a/pr/451/docs/search.index/4eac59928f18.js b/pr/451/docs/search.index/4eac59928f18.js deleted file mode 100644 index bc6c14aed..000000000 --- a/pr/451/docs/search.index/4eac59928f18.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBgBLpABDwczswAAABAAAFAAEAyRIFACOCAqCQAAATw2lzBwEAhMWwUBKSAAGgoAAAEzGwcBL4AAGg0AAAE8+AARgI") \ No newline at end of file diff --git a/pr/451/docs/search.index/4ec9d96b60ba.js b/pr/451/docs/search.index/4ec9d96b60ba.js new file mode 100644 index 000000000..a9c08babc --- /dev/null +++ b/pr/451/docs/search.index/4ec9d96b60ba.js @@ -0,0 +1 @@ +rn_("AQEAOjAAAAEAAAAAAAgAEAAAAPwIXRAHEQgRCRHfE+ATaxRwFAEBAqBAAAAUU6AwAAARDWVo9fQIAAAuACoDAQB4BQECAPS8EQAAAQABAAEAE4ECsCARwAABYXPzgAJubwECAPSYEAAAAQABAAEAEwACoNAAABRYY3PyCRAAAAEA84ADZWlv") \ No newline at end of file diff --git a/pr/451/docs/search.index/4eed4a0cfe1e.js b/pr/451/docs/search.index/4eed4a0cfe1e.js deleted file mode 100644 index 1d0c38cc8..000000000 --- a/pr/451/docs/search.index/4eed4a0cfe1e.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsHAQ8wABsCATvQABYWQBBQD0sxIAAAEAlQABAPMAAWMBHxIAAEOAA7AwEwgAAbCgE6MAAWFzeQEGAPSREAAAAQC4AAEAk4CEoAAAABDasSAUAAABCSAB84ACZWk=") \ No newline at end of file diff --git a/pr/451/docs/search.index/4f8c49c523f4.js b/pr/451/docs/search.index/4f8c49c523f4.js new file mode 100644 index 000000000..83eb054cf --- /dev/null +++ b/pr/451/docs/search.index/4f8c49c523f4.js @@ -0,0 +1 @@ +rn_("AQcA9OgRAAABAAEAAQAjAAKgcAAAEedhcgFsBwAAAQAA9gURAAABAJMDAQAKAAEAAQAAOzAAAAEAAAYAAgBxEwAA2RMFACOIArBAFFEAAWVzAQ0BsEAUSwABZfUPFAAAAQABAAEAAQABBQA7MAAAAQAACAADAOERAgDmEgQADhQAANcDAACGI4//c3rnFIQG9bgEAAABAP8HAQABAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/4fae9a79a161.js b/pr/451/docs/search.index/4fae9a79a161.js deleted file mode 100644 index b16d153ce..000000000 --- a/pr/451/docs/search.index/4fae9a79a161.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9GsTAAABAEkAAQDzAwFj8nMSAAABACOAArBwFBYAAWhvAYICsAASUAABsAARXwABZXNDAAOgoAAAE7qgIAAAEvplaXT1YxIAAAEAhQABAPAAAYACsHASRwABoLAAABOAZW8BAgA7MAAAAQAABgABAOAQBgABAgGgQAAAE59l8skTAAABAPOAAmlvAQgA9GITAAABAIIABQATAQOwkBPEAAGwUBKqAVlnbHA7MAAAAQAAEgAJAO4PAgBiEQEAmRIBALoSAQDxEgEAUxMBAIoTAQCZEwEA1hMBAAGAArBAEicBDqAgAAAQo2FpAYECsDATcwABsDASRQABY2kBAwGwABKUAAFzOzAAAAEAAA0AAwBdEQEADhMHAPsTAwABAwD1WxEAAAEAHAEBAFYB84ACcnYBAAA7MAAAAQAABQACACUSAQB6EgMA8wIBZDswAAABAAAJAAMAVxEDACASAwBSEgEAI4ACoJAAABO5aW8TgAKw8BQIAAFsbwEFAPTVEwAADAAeAA8AI4ACoGAAABLvZ3MjgAKwkBO3AAFibPcXAwCPsHARdQABsGAT6wABsHATZQABsGASqAABsFASlgABtfkXOzAAAAEAADwAGACPBQAAPAcBAMAIBQDQCAAA/ggAAMgLAAARDAEAiQwCALMMAQAyDgMANw4BAIIPBABNEAEAhxAAAKoQAADKEAkACBEAAAoRAwBVEQAAoBIAALcSAQDJEgUA2RIAAI0TAAA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/4fc7afbad082.js b/pr/451/docs/search.index/4fc7afbad082.js new file mode 100644 index 000000000..9d81af04f --- /dev/null +++ b/pr/451/docs/search.index/4fc7afbad082.js @@ -0,0 +1 @@ +rn_("AQoA9IcTAAABAA8AAQABgwKgIAAAEDKgMAAAEM5pdAEAAPTyEgAAAQABAAEA8wQBZPSVEgAAAQABAAEAI4aEsKAUOwABsEAS8AABsHAT4gABFYAAAQACsDAQJAABsBATnwABc3TyjQUAAHoGAYACsMAUaQABoAAAAAdEdHnzgIURwQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/5043dc5e7d82.js b/pr/451/docs/search.index/5043dc5e7d82.js new file mode 100644 index 000000000..ef209609b --- /dev/null +++ b/pr/451/docs/search.index/5043dc5e7d82.js @@ -0,0 +1 @@ +rn_("AYACsFAQ6wABoLAAABPhY3MBBADzqA8AAAEAAQABgAKgUAAAELugYAAAESppcgGAAqAgAAAMYqBgAAARKWVoAQEA814MAAABAAEAE4ECoBAAAAxhYWMBgAKwEBOsAAGwMA0DAAFlcyMAAqBAAAAUSmV38mwUAAABAAEDADswAAABAAAFAAIAWgwDAD0RAQABBgD09hIAAAEAmQEBABMAArBgEvgAAWZt8s8MAAABAAEBAPT2EAAAAQCwAAEAI4ACoLAAABPIZGcBggKgoAAAEyqgQAAAEFpvdvOAA2ducgGAAqBAAAAS76AwAAAPpWhstz8AAIqwQA+mAAE0YFc7MAAAAQAAGgAEAAcCAABjBA4A3wsJALwMAAA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/50abe47e037f.js b/pr/451/docs/search.index/50abe47e037f.js deleted file mode 100644 index 29ecb46be..000000000 --- a/pr/451/docs/search.index/50abe47e037f.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEAOjAAAAEAAAAAAAgAEAAAANgI/A+dEJ4QnxBiE2MT5RPqEwEBAqBAAAATz6AwAAAQo2Vo9dAIAAAuABMDAQBDBQECAPRMEQAAAQABAAEAE4ECsCARUAABYXPzgAJubwECAPQyEAAAAQABAAEAEwACoNAAABPUY3Pypw8AAAEA84ADZWlv") \ No newline at end of file diff --git a/pr/451/docs/search.index/5132048f5f5c.js b/pr/451/docs/search.index/5132048f5f5c.js new file mode 100644 index 000000000..69535aff6 --- /dev/null +++ b/pr/451/docs/search.index/5132048f5f5c.js @@ -0,0 +1 @@ +rn_("AQQAOjAAAAEAAAAAAAcAEAAAAMAPwQ+MEI0QBxMIE5kTmhMBBAD0wg8AAAEAAQABAAEAAqAQAAAMcbBQE+YAAWlv8hwJAAABAPOAA2Vpbw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/51f65056eec1.js b/pr/451/docs/search.index/51f65056eec1.js new file mode 100644 index 000000000..9244a06d9 --- /dev/null +++ b/pr/451/docs/search.index/51f65056eec1.js @@ -0,0 +1 @@ +rn_("AYACsCATKABaoFAAABEXbXNDgAOw4BPqAAEL31J7XT9kZ3IBAwDzuxIAAAEAAQAjgAKwMBPwAAFjaTOAA7AADPADYmFpbw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/521c1cdfffb6.js b/pr/451/docs/search.index/521c1cdfffb6.js deleted file mode 100644 index e6a406d0a..000000000 --- a/pr/451/docs/search.index/521c1cdfffb6.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsAAQ+gABsAAQ/AABNDYBBAGgoAAAFCN38hMUAAABAAELAPSFEwAAAQABAAEAI4ACoIAAABOtZW9jAgOgYAAAE0NlbnAB2QsAAPMAAmF2AbQGAAA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/523d09888781.js b/pr/451/docs/search.index/523d09888781.js new file mode 100644 index 000000000..661af410b --- /dev/null +++ b/pr/451/docs/search.index/523d09888781.js @@ -0,0 +1 @@ +rn_("AQMAOzAAAAEAAEMAAgARCQAAcgxCAAEFAPSqEAAAAQABAAEAE4ACsLAThwABYWwBBAD0mQ8AAAEAAQABAHMABKAQAAAQXmJwcnY7MAAAAQAACgACAEkRBgCfEQMAAYACsIAQ+AIisOAT8AABaXIBAwGwQBFGAAFlAYAOAAABBQA7MAAAAQAABgABAEUQBgABAwA7MAAAAQAABQABAMQMBQDzAAJwczowAAABAAAAAAANABAAAACfBaAFtgYFCUcMVAzrEOwQmhKbEvIS8xL0EvUSVwAAAIWwUBCcAjewUA+4ANGgQAAAEXFIgAM7MAAAAQAADwAGAJkEAABeBQMApQ8AAMcRAwCVEgMAyRIBANMAhKBgAAAM2BAIBvOsAQAAAQAKCw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/528006bc24f9.js b/pr/451/docs/search.index/528006bc24f9.js new file mode 100644 index 000000000..2c7fd63d9 --- /dev/null +++ b/pr/451/docs/search.index/528006bc24f9.js @@ -0,0 +1 @@ +rn_("AQgA82MUAAABADsAI4ACsAAQxAABbW4BgAOgQAAAE0CwIBP+AAGgIAAAD7toaXkBCQGggAAAFJxmAXsTAAABAwGwABQWAFBz8pMQAAAqA0OAA7AgD7cBKLBQEqAAAWFvcgECAPXBEAAAAQABAF0CAQDzAANpdHU6MAAAAQAAAAAAEQAQAAAAcQcfCeAMHxCcEMYR0xINEw4TwRPCE8gT8hPzExQUFRRnFGgUAQYA9PYSAAABAJkBAQATAgKwYBL4AAFmbfLPDAAAAQAjggKwQBMmAAFlbAGAAqAwAAAURrCwE5cAAWFsAQYA88kPAADfAAEAAQUA9OgTAAABAFAAAQDzAgFj8usSAAABAAECAaAwAAAQXWEBRAwAAAGAArBQERAAAbAgD5MAAWNu84ACbnQBBQA7MAAAAQAADgACANUMAADxDw0AAQIAOzAAAAEAAAcAAQBGCQcA5wUAAAmgMAAADEOgsAAAFBSggAAAEY+w0BQjAAEwYWZpa21ydXY7MAAAAQAAZwIFAPwBAABlA+wAagcAAPIK7AD0DYsAAQEA8+EPAAABAAEAE4ACsMAUnQABdHUBAwDzpBIAAAEAAQABAQD1JBEAAAEAAQABAAEAAQEBsDAP0gDudPZcBwAAAQCKAO4HqQD7AWOAA7CQE+oAAWFtdAEDAaBAAAAUSmXybBQAAAEAE4ACsGAUoAABZXIBAwGgAAAAE8hzAd4QAAABAgA6MAAAAQAAAAAACgAQAAAARAlFCQgQVBNVE4UThhMyFIoUohSjFIMABKCAAAASfrBwEVMAAbAgEPQA8GZucnYB5gcAAAGBAqAAAAAJH6AwAAAPu3N0xwEAgMawABQhAAGwkBJ8AAGgEAAAB0IAEogoAQIAOzAAAAEAAA0ABgA/EAUAUBEBALQSAQDkEwEAPRQAAFwUAAATggKw8BSgAAFmZwGAArCAFHMAAaBgAAAUm2RzE4ACsMATrwABaW8BCAD1JxQAAAEAAQABAAEA8wIBZvWAEAAAAQABAAEAAQAjgAOgIAAAD7WwoBRxAAFkcnT3+wcAj6AwAAAPq5fXDzswAAABAABPAR4AfgQBAKsEAgByBQIAnAUCAL0FAQCxBgQAMwcAAEsHAADiBwEA9QgCACMJAQAXDAMARQwBAG0MAwDRDAEAiA4BAdgPAQBVEAIAnRABALAQAgDdEAEAcBEAAKMRAwDNEQEAhhIAAMYSAAD+EgEAZxMBAIkTBwCBFAUA") \ No newline at end of file diff --git a/pr/451/docs/search.index/52b7dbce0a40.js b/pr/451/docs/search.index/52b7dbce0a40.js deleted file mode 100644 index 934c019b8..000000000 --- a/pr/451/docs/search.index/52b7dbce0a40.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQYA9CYTAAABAAEAAQABDwD0BBQAAAEAAQABAPOHAmhtAQAA9HoSAAABAAEAAQDzBAFk9CASAAABAAEAAQAjh4SwoBO3AAGwQBJ4AAGwcBNlAAEVgAABAAGgQAAAEBdlOzAAAAEAAAcAAgA+BwUAzwcBABOAArBQD9IAAWRyAQkA9UYTAAABAAEAcwABALcCAICGsCASKwABTqxZko8YEUkB") \ No newline at end of file diff --git a/pr/451/docs/search.index/52ccda238ef2.js b/pr/451/docs/search.index/52ccda238ef2.js new file mode 100644 index 000000000..e7afe6d90 --- /dev/null +++ b/pr/451/docs/search.index/52ccda238ef2.js @@ -0,0 +1 @@ +rn_("AQgA9N8TAAABAIsABQATAAOwkBRIAAGwUBMkAWdnbHA7MAAAAQAAEgAJAE8QAgDSEQEAEhMBADQTAQBrEwEA0BMBAAoUAQAZFAEAWhQBAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/536f57712388.js b/pr/451/docs/search.index/536f57712388.js deleted file mode 100644 index e2510eeb0..000000000 --- a/pr/451/docs/search.index/536f57712388.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9ScRAAABAAEAAQABAAGAArBgEoIAAaAgAAAQfG95AQABoMAAABOgafTYEgAACAAbABsAAQYA9AQUAAABAAEAAQABAgD0ehAAAAEAmgABAAECAaBAAAASr3I7MAAAAQAABwACAHgQAQAPEQUA84ACYW9nAwAAhrCAEq4AWbCgEyQAAQwwBjswAAABAAAIAAMA0gsAADoRAwAbEgMA84ICZWkjAAKggAAAFBJmaPLJCAAANwoBAQD0uQsAAAEAsQUBABMAA7AwE7cAAbBQEooAAWVwcvRpDwAAAQDnAAEAAQADsEATgwABoUAAABPqoFAAABFhYmNmAd4GAADzgAJtbgEBhKCwAAATQLBAEDcAAbDAE28AAaBgAAARDhQgBPbFBgAADwkBADEBIQEOAQEFADswAAABAAAFAAEASRIFAAEHAPXPEgAAAQABAAEAAQAjgAKgUAAAEN9hb/MAhSBJAjswAAABAAAIAAQAHwcAAFIHBQCvDAAAFxEAAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/5376c77b4a8d.js b/pr/451/docs/search.index/5376c77b4a8d.js new file mode 100644 index 000000000..96e540421 --- /dev/null +++ b/pr/451/docs/search.index/5376c77b4a8d.js @@ -0,0 +1 @@ +rn_("AYICsEAUSAABsGATHAABZXIBBQD0hRAAAAEAAQABAPOAAmZtAQYAOjAAAAEAAAAAAAgAEAAAAMcPyA93EdQS1RLUE10UkhSTFAEAArCwE+wAAaFAAAAUi2Vn9G0HAAABAAEAAQDDAISgsAAAE72wQBCdAAEUIAT35AYAANcFegMBADkBLQEWAWOAA7AwEAIAAWFlaQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/538b5fc4e1ed.js b/pr/451/docs/search.index/538b5fc4e1ed.js deleted file mode 100644 index 5ba20b562..000000000 --- a/pr/451/docs/search.index/538b5fc4e1ed.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9CwRAAABAAEAAQABAQDzQhEAAAEAAQATgAIlFD3SUo9lcwEKAPMqEwAAAQABAAGAArAgE6MAAaBgAAAS9292AYACoDAAABI0oAAAABKwY2TzgIUFEAY=") \ No newline at end of file diff --git a/pr/451/docs/search.index/5392889fe77e.js b/pr/451/docs/search.index/5392889fe77e.js deleted file mode 100644 index b851be4e1..000000000 --- a/pr/451/docs/search.index/5392889fe77e.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYICsEASNQABsEASNwABNDYBCwDzLxMAAGgAAQABBgD0DxQAAAEACgABAPMCAXIBeBMAAPMDAWX0AxAAAAEApwABAPOAA2FlaQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/53dbf4b2839c.js b/pr/451/docs/search.index/53dbf4b2839c.js new file mode 100644 index 000000000..772927ea0 --- /dev/null +++ b/pr/451/docs/search.index/53dbf4b2839c.js @@ -0,0 +1 @@ +rn_("AQIAOzAAAAEAAAUAAgBtDAMA/hIBAAECAPNgEQAAAQABAAEGAPSMFAAAAQABAAEA8wYBbfSQEgAAAQABAAEA84ACbnbzgAJhaQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/53ffa8e8252a.js b/pr/451/docs/search.index/53ffa8e8252a.js deleted file mode 100644 index 12a588128..000000000 --- a/pr/451/docs/search.index/53ffa8e8252a.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9M4IAAABAHQK4AAnAAAAyqEQAAAT26BAAAAQkLBgE8sAAaAQAAAMNrBQE2kAAbAwEPMAAaAAAAAL9LEgE+sAAbBAEI4CE0BOsAk6MAAAAQAAAAAAEwAQAAAA5AjlCPgI+QjAC8ELwgvKC80LhwwQEjsSPBI9EvMS9BIIEwkTeRN6Ew==") \ No newline at end of file diff --git a/pr/451/docs/search.index/54278f1995a0.js b/pr/451/docs/search.index/54278f1995a0.js new file mode 100644 index 000000000..f1fc533bc --- /dev/null +++ b/pr/451/docs/search.index/54278f1995a0.js @@ -0,0 +1 @@ +rn_("AQkBoEAAABRKZfJsFAAAAQABgAKwAAzWAAGggAAAE65kcAECA7BgEysAAbDQFE0AAaCAAAATzWlycwFYFAAA1wAAgIWwMA+sAAGhMAAAFGuQSQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/5478b18519c4.js b/pr/451/docs/search.index/5478b18519c4.js deleted file mode 100644 index b22d92500..000000000 --- a/pr/451/docs/search.index/5478b18519c4.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIAOjAAAAEAAAAAAA4AEAAAALgMuQyQEG4RbxFwESQSLRIuEjISMxKIEokSvhK/EgEJAPP3EAAAAQABAAEKAPM7EgAAAQABAAEGAPW6EAAAAQABAAEAAQDzgAJkbnMAhLEgE6MAAQARA/KSEgAAAQABDQD2ChMAAAEAowBUABYAAQABgAKwoBMiAAGgEAAAC+ljdQEBAPQyEwAAAQBLAAEA8wABZfI/CQAAAQD3AACAxbAwEDcAAQACqAg=") \ No newline at end of file diff --git a/pr/451/docs/search.index/548eac6e4034.js b/pr/451/docs/search.index/548eac6e4034.js deleted file mode 100644 index 611bccd33..000000000 --- a/pr/451/docs/search.index/548eac6e4034.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQ0A9UYTAAABAAEAcwABAAEHAPMvEwAAaAABACOAAqEQAAATgG5yAQcA9DIRAAABAAEAAQAzAASgIAAACPew4BN1AAExZWlyOjAAAAEAAAAAAAkAEAAAAEAEQQQgB9kIHwwgDCEMIgzUENUQAQIA9IUQAAABAIYBAQABAwD08RAAAAEAbQEBAPMAAmJ1AZ8PAAABAwD0gxAAAAEAhgEBAPOEAmRzAQIA89sIAAABAFgA84ACaXQBGADzIBQAAAEAAQAjgQKwwBNEAAFjbQENAbBAE8cAAWX1jxMAAAEAAQABAAEAE4ICsAAQmwABa3MBBAD0LhAAAAEAAQABAAGBAqAgAAAQlrBAEtwAAWVp84ICZXQBBAGgYAAAEjRy9NcLAAABAMUDAQDzgANhZW8BCQD2XRIAAAEA+QCCADEAAQATgAKwYBBGAAFkbgEGAPVvEAAAAQABAAEAgwIBAwGgcAAAEphn9C0HAAABAAQCAQABCQDz3xMAAAEANQABgQKwgBPtAAGgYAAAFBFkcxcBAIiFsNAUEwABoEAAABPCsGATlgBMApAKAQQA9DITAAABAEsAAQBTgAOwkBQMAAFlaG8BAAGgQAAAEP5lAU8PAAAjgQKwUBCoAAFrdHf8AQCNoZAAABQDsLASpgABoKAAABJc/YEPOzAAAAEAACEACAB6AAAAgAADAE0FAQAJBwcAAgkHAPULBwDYDwEACRIAAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/549a18f8219c.js b/pr/451/docs/search.index/549a18f8219c.js deleted file mode 100644 index cef0ef2de..000000000 --- a/pr/451/docs/search.index/549a18f8219c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIBsIATUQABdAHjCAAAAYECoBAAABKwoHAAABL3YWPzAQFy8iYRAAAYAPOAAmVpI4ACsAALywABZHLzAAFl9IgAAAATDAgHAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/54a3a24e98f0.js b/pr/451/docs/search.index/54a3a24e98f0.js new file mode 100644 index 000000000..5dcde6e96 --- /dev/null +++ b/pr/451/docs/search.index/54a3a24e98f0.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..dca6f636c --- /dev/null +++ b/pr/451/docs/search.index/54aec76a13de.js @@ -0,0 +1 @@ +rn_("AQgA9FsQAAABALIAAQABAAGggAAAFJxm8u0IAACOCgEHAPVJEwAAAQABAAEAAQAjgAKgUAAAEUhhb/MCAmZzOzAAAAEAAAgABABBBwAAcgcFAPEMAACAEQAAM4ADoEAAAAv8YWVv") \ No newline at end of file diff --git a/pr/451/docs/search.index/5535b627edd5.js b/pr/451/docs/search.index/5535b627edd5.js deleted file mode 100644 index ee1fe0d0e..000000000 --- a/pr/451/docs/search.index/5535b627edd5.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQYA9AoRAAABAAEAAQABAgA7MAAAAQAADAAFAMMLAQAPEAUAQBIBAJ4SAQC6EwAA84ACZm4BAQA7MAAAAQAACAACAC8PBgDHEgEA84ACZG4BAQDzAQcAAAEAAQABhgKgAAAAEsCgAAAAEsFkcvMBAXYB7BIAAEMAA7CAElQAAbCwE+gAAWtscjswAAABAAAMAAMA+AYIALEMAQDNDwEAAQIA9coHAAABAAEAAQABAPMAhBRBAAF2AAAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/559b896a9166.js b/pr/451/docs/search.index/559b896a9166.js new file mode 100644 index 000000000..d093d9394 --- /dev/null +++ b/pr/451/docs/search.index/559b896a9166.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..a401a807d --- /dev/null +++ b/pr/451/docs/search.index/56a96cba0f24.js @@ -0,0 +1 @@ +rn_("AQIA9eQSAAABAPEAgAABABOAArAwExYBSmlwAQYA85MTAAABAAEA8wICYnMBJhAAAPMBAWYBWgcAAAECAPQ+EwAAAQCYAAEAM4ADsEATCwABaXV5") \ No newline at end of file diff --git a/pr/451/docs/search.index/571482a0f60a.js b/pr/451/docs/search.index/571482a0f60a.js deleted file mode 100644 index fa4510055..000000000 --- a/pr/451/docs/search.index/571482a0f60a.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQcA9PwSAAABAAEAAQDzBQF0OzAAAAEAAAgAAgC9CwAA6wsHAAEFAPMYEwAAAQABADcAAICFsDAP0gABoKAAABM0oDAAABNxEEFI") \ No newline at end of file diff --git a/pr/451/docs/search.index/571c0d56c455.js b/pr/451/docs/search.index/571c0d56c455.js new file mode 100644 index 000000000..e704c89db --- /dev/null +++ b/pr/451/docs/search.index/571c0d56c455.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..05d4d3cb1 --- /dev/null +++ b/pr/451/docs/search.index/573b61c91606.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..16404ba84 --- /dev/null +++ b/pr/451/docs/search.index/5775f8d2dd1c.js @@ -0,0 +1 @@ +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/579677a41519.js b/pr/451/docs/search.index/579677a41519.js deleted file mode 100644 index e7b677928..000000000 --- a/pr/451/docs/search.index/579677a41519.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9DkSAAABAKQAAQABCAD08xIAAAEAhQABADMBA7BAEsIAAWFiZfWfDAAAAQAeA1QCzwABAAD14AsAAAEAYgMBAAEA84ACc3UjgQKwgBLtAAFydAGAArBwEnUAAaAQAAAMgm56AQYA9AcQAAABALAAAQBzAISgoAAAFCMCART1VQUAAAEAbA5RAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/579724b12c01.js b/pr/451/docs/search.index/579724b12c01.js new file mode 100644 index 000000000..edb5a1e22 --- /dev/null +++ b/pr/451/docs/search.index/579724b12c01.js @@ -0,0 +1 @@ +rn_("AQYAOzAAAAEAAAkAAQAzEQkAI4ADsGATZwABsDAPsAABaXN0AQgA9ScUAAABAAEAAQABAPMBAWb1gBAAAAEAAQABAAEAY4ADoDAAABMpZWd0") \ No newline at end of file diff --git a/pr/451/docs/search.index/57d12a4129f3.js b/pr/451/docs/search.index/57d12a4129f3.js new file mode 100644 index 000000000..e2d3bdabf --- /dev/null +++ b/pr/451/docs/search.index/57d12a4129f3.js @@ -0,0 +1 @@ +rn_("AYACsEATVAABoMAAABQUZW8BAgD0vBEAAAEAAQABABMAA7AgEcAAAbCAFCUAAWFzdAHLDAAAAQQA8+YPAAABAAEAs4CEsEATZwABQQAG") \ No newline at end of file diff --git a/pr/451/docs/search.index/5802da10e9df.js b/pr/451/docs/search.index/5802da10e9df.js deleted file mode 100644 index 2eef7d2b0..000000000 --- a/pr/451/docs/search.index/5802da10e9df.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEAOzAAAAEAAAcAAQAiCQcAAQIA99EPAADRAKQCAQABAHMAAQATgAKwMBBkAKVpdPMAAXPy6xMAAAEAM4ADsRATZQABZG5w") \ No newline at end of file diff --git a/pr/451/docs/search.index/58045aae0759.js b/pr/451/docs/search.index/58045aae0759.js deleted file mode 100644 index 402d9900b..000000000 --- a/pr/451/docs/search.index/58045aae0759.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYECoCAAABP2sEAPggABYnABBQD0sxIAAAEAlQABAPMDAWMBHxIAAAEMAPT7EwAAAQABAAEA84ACbnYBgAKwMAwKAAGgMAAADAxpbwEDAaAAAAATw3MBbREAAMcDAICGsEALywABsAAFTwABkUgB") \ No newline at end of file diff --git a/pr/451/docs/search.index/58b912fa1a43.js b/pr/451/docs/search.index/58b912fa1a43.js new file mode 100644 index 000000000..f997c7198 --- /dev/null +++ b/pr/451/docs/search.index/58b912fa1a43.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..f90da6a4c --- /dev/null +++ b/pr/451/docs/search.index/58fc7e75991d.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..7bebba27a --- /dev/null +++ b/pr/451/docs/search.index/596040a74c77.js @@ -0,0 +1 @@ +rn_("AQMBgBNjABD6czswAAABAAAFAAEAQxMFACOCAqCQAAAUR2lzAQEA9AkTAAABAJEAAQAjgAKwoBPwAAFjbgGEArCgE/4AAbCgFAAAAWNyAQIAOzAAAAEAAAYAAQBFEAYAE4ICoCAAABPhZXUBAgDzsxEAAAEAAQABgAKwIBQjAAGgYAAAE3JvdmOBA6AQAAATKmFjcwEDADswAAABAAAFAAEAxAwFAAEBAPMOCQAAAQABAAEBAPTjEAAAAQCaAAEAU4ADoMAAABRTbnB1AYACsDAQAgABoWAAABSLYWUBgQKwQBKrAAGwQBKtAAE0NgEFAaBQAAAUDGjynhIAAAEAAQUA9FsQAAABALIAAQBzgISwMBNWAAEBoAQBCADzUhMAAAgAHAATgAKwQBHXAAFhZQEKAPOrEwAAbAABAAEAAaAgAAAQfXIBRREAAAEAAPSvEwAAAQBLAAEApwEAgIWgMAAAEOCgEAAAEQAJICEBCQGgQAAAFEpl8mwUAAABAEOAhKAgAAAUWKCQAAATrqEwAAAUaxBJAAECAPQ+EwAAAQCYAAEAE4ACsEATCwABaXkBAQKgMAAAEuGgQAAAEylncjswAAABAAAHAAIA4RABAHgRBQABgAKwABPJAAGwABPLAAE0NgEGAPSZFAAAAQAKAAEA8wICcnYB9RMAAPMCAWU7MAAAAQAABQACAGUQAwAVEQEA84ACZHTn1x8AEaAwAAARAbDwFJYAAbBwE3MAATBhYmNlaGlrbG5vcHJzdHV2OzAAAAEAABgBRAC6BAAAnwUBAKcFAACpBQEAtgYAABgHAAA+BwIATgcAAGAHBQBnBwAAyAcAAO0HAQDyCAEA+AgBAAUJAABjCQEAZwmLAO4LAwD9CwAAAQwDAA4MAABHDAAAVAwAAJUPAQDADwUAPhAAAGsQAQB/EAAAjBABANQQAQDYEAMA6xABACIRAQBDEQAAYBECAI8RAACvEQAA0REAANURAQDbEQEA5xEEAIsSAACaEgEAnRIAAKkSAQC7EgIA8hIDAAcTAQAUEwEAHBMBACITAQAlEwAAKBMAADoTAABQEwEAbxMBAH4TBACZEwEArBMBAK8TAQC1EwEAwBMAAPsTAgACFAcAIRQBAC0UAABEFAEAbhQBAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/598001827816.js b/pr/451/docs/search.index/598001827816.js new file mode 100644 index 000000000..6424cb58a --- /dev/null +++ b/pr/451/docs/search.index/598001827816.js @@ -0,0 +1 @@ +rn_("AQEA9T0JAAABAAEAAQABAPMAAWnylhQAAAEAAQEBoEAAABQJZfJ/FAAAAQABBAKgIAAAEcKgMAAAEoxiaAGyDwAA5wAAgMWgEAAADNugMAAAEGIEECgB") \ No newline at end of file diff --git a/pr/451/docs/search.index/59b3fb7f8021.js b/pr/451/docs/search.index/59b3fb7f8021.js new file mode 100644 index 000000000..2bc88f58f --- /dev/null +++ b/pr/451/docs/search.index/59b3fb7f8021.js @@ -0,0 +1 @@ +rn_("AQACoCAAABN1sDARzwABaXTy2hIAAAEAAQMAOzAAAAEAAAYAAQBJEQYAAQgA9N8TAAABAIsABQATAQOwkBRIAAGwUBMkAWdnbHA7MAAAAQAAEgAJAE8QAgDSEQEAEhMBADQTAQBrEwEA0BMBAAoUAQAZFAEAWhQBAAGAArAwEcsAAbAwEc0AAXJ2AQQBsAASmgABZDswAAABAAAFAAIAxxEDAMkSAQC3BQABh7BQEpwBFrBgEyIAAZQJAzowAAABAAAAAAALABAAAADMDM0MzgyGDocO8RBzEXQRdRF2ERkTUxMjgAKgEAAADMtubwEDAPNMEAAAAQABACOAAqAAAAAL/WRyYwGEoFAAABFwoDAAABCKFAEQ8owHAAABABOAA7BgEr4AAbAAD5MAAWNnchOAA6CwAAAT/aBAAAAOhWVvcw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/59be03fa81eb.js b/pr/451/docs/search.index/59be03fa81eb.js deleted file mode 100644 index 012493ef2..000000000 --- a/pr/451/docs/search.index/59be03fa81eb.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQsA9AwTAAABAA8AAQABBAGwQBLCAAFlARISAADzAAF0AcITAAABDQDz2RMAADEAAQABEADzAhQAABYAAQDzgQNrbXMBAAA7MAAAAQAABgACAPYSAABcEwUAI4YCsEATzQABZXNjgAOwQBBEAAFjcnQzgISwYBBfAAGgEAAABZABCAk=") \ No newline at end of file diff --git a/pr/451/docs/search.index/59c5b0e1f8ad.js b/pr/451/docs/search.index/59c5b0e1f8ad.js deleted file mode 100644 index a10b55957..000000000 --- a/pr/451/docs/search.index/59c5b0e1f8ad.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYICsEATxAABsGASogABZXIBBQD0HxAAAAEAAQABAPOAAmZtAQYAOjAAAAEAAAAAAAgAEAAAAGQPZQ8OEV0SXhJXE9kTChQLFAEAArCwE28AAaFAAAAUA2Vn9E0HAAABAAEAAQDDAISgsAAAE0CwQBA3AAEUIAT3xQYAALcFWAMBADEBIQEOAWOAA7AwD6AAAWFlaQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/59fb890d29b8.js b/pr/451/docs/search.index/59fb890d29b8.js deleted file mode 100644 index a1fae09c6..000000000 --- a/pr/451/docs/search.index/59fb890d29b8.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABsPAUDAABa/aAEwAABQABAAEAAQAlABMAArEAE/kAAWRu8kcSAAABABOAArAQDAYAAWVvAQUAOzAAAAEAAA0AAQAYEQ0AAQIA84wPAAABAAEAAQIA9IgPAAABAAEAAQABAwA7MAAAAQAACAABADkQCAABgAKgQAAAEIuwwBO9AAFlcwEBADswAAABAAATAAEAqQ8TACOAArCQEy0AAWltNzYAAIqgEAAAC9ygUAAAE4ywcBNtAAGgIAAADJucVRE7MAAAAQAABgADACgHAABlEAMAKRIBAPOAAmx0") \ No newline at end of file diff --git a/pr/451/docs/search.index/5a22fd2fcd2b.js b/pr/451/docs/search.index/5a22fd2fcd2b.js deleted file mode 100644 index fa1d07e65..000000000 --- a/pr/451/docs/search.index/5a22fd2fcd2b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYQCsAAQ+gABsAAQ/AABNDbzAAFh8igMAAABACOAArCQEQABFWx0AYACsHATAQABsIATOAABY3MBAAA6MAAAAQAAAAAACQAQAAAAUxFUEWcSaBJRE1ITpROmExYUFxTzgAJkZScBAICFoNAAABN4sBAEoQABoDAAAA/9AggL") \ No newline at end of file diff --git a/pr/451/docs/search.index/5aaa90b55fc2.js b/pr/451/docs/search.index/5aaa90b55fc2.js new file mode 100644 index 000000000..6c2422dc8 --- /dev/null +++ b/pr/451/docs/search.index/5aaa90b55fc2.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..5f64db57d --- /dev/null +++ b/pr/451/docs/search.index/5ad143d05f59.js @@ -0,0 +1 @@ +rn_("AYACsAAQCQABsCAMTAABZWkBiQKwIBRWAAGgMAAAFGBpcBMAA6AgAAATEaCAAAATJWVpdfX/CwAAOQABAAEAAQABhgKwkBR9AAGgAAAAEp1iZAGAArAwEwUAAaAwAAAQ3XJ21wAAAIWgMAAAEFKgMAAAE1wQARM6MAAAAQAAAAAACgAQAAAA7gjvCPAIIAkhCQ0MbRB0EC0RLhHhEmOAA7AQCOIAAWRtdA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/5b4386429020.js b/pr/451/docs/search.index/5b4386429020.js new file mode 100644 index 000000000..73c0fd837 --- /dev/null +++ b/pr/451/docs/search.index/5b4386429020.js @@ -0,0 +1 @@ +rn_("AQQBoEAAABFoZQEhEAAAAQgA9KsRAAABAAEAAQABAwGgQAAAEWdlAa4PAAABAgDzHQwAAAEAAQAjAAKwIAwLAAFrcvblBgAAAQAECu0AAQA0AvOAAnJ4AQMBsIAURAABdjswAAABAAALAAMA+xABALYRBQClEwMAI4ACsGAQbQDAYXIBgAKgsAAAE82wMA+sAAFlaAEDAaBAAAAQ+mXyCAkAAAEA1xMAAIugIAAAB1qwsBMcAAGgkAAAEuCggAAAEa+gAAAAEymfGEw6MAAAAQAAAAAADAAQAAAACAJzBHQEdQT1C/YLXgxfDGAM8g3zDSoTQBM=") \ No newline at end of file diff --git a/pr/451/docs/search.index/5b62c7da471c.js b/pr/451/docs/search.index/5b62c7da471c.js deleted file mode 100644 index 95a617a99..000000000 --- a/pr/451/docs/search.index/5b62c7da471c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBoEAAABFuZfOwDAAA5AMBAAEGAPSREAAAAQC4AAEAE4EDoAAAABDasSAUAAABYWRuARQA9R4UAAABAAEAAQABAAEFAbCAE8AAAXY7MAAAAQAABwACAEURBAAqEwIAI4ACsHARawABYmRXAAABhqDAAAAS9aCgAAATF6CQAAAS4bBQEAoAAQXgAvXpBgAAAQABAJwIbgQBBgDzQhIAAAEAqADzgANydHYBAwA7MAAAAQAABwACACYTAwAEFAMA8wABcwHDEwAA8wABcwFtEQAAZwEAgIagMAAAC/SwABLcAAGgEAAADJUSQA4=") \ No newline at end of file diff --git a/pr/451/docs/search.index/5bbccf0d1c5c.js b/pr/451/docs/search.index/5bbccf0d1c5c.js new file mode 100644 index 000000000..4a6125429 --- /dev/null +++ b/pr/451/docs/search.index/5bbccf0d1c5c.js @@ -0,0 +1 @@ +rn_("AYECoCAAABR8sEAP5AABYnABBQD0LRMAAAEAmAABAPMDAWMBlBIAAAEMAbAAFGcAAXM7MAAAAQAABQABAIEUBQDzgAJudgGAArAwDEUAAaAwAAAMR2lvAQMBoAAAABRHcwHdEQAAAQQA9AEMAAABAAEAAQDXAwCAhrAABWoAAZFIAQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/5bd4900427a6.js b/pr/451/docs/search.index/5bd4900427a6.js deleted file mode 100644 index e5b4a6710..000000000 --- a/pr/451/docs/search.index/5bd4900427a6.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQkA898TAAABADUAAYADoEAAABLGsCATgQABoCAAAA9caGl5AQkBoIAAABQSZgEAEwAAAQMBsAATlgBMc/ItEAAAEwNDgAOwIA9YAR6wUBIrAAFhb3IBAgD1WRAAAAEAAQBLAgEA8wADaXR1OjAAAAEAAAAAAA8AEAAAAFEH+wieDL0PNhBWEVwSlBKVEkQTRRNLE3UTdhOUE5UTAQYA9H4SAAABAIkBAQATAgKwYBKAAAFmbfKMDAAAAQAjggKwQBKsAAFlbAGAAqAwAAATwrCwExwAAWFsAQYA82YPAADcAAEAAQUA9GsTAAABAEkAAQDzAgFj8nMSAAABAAECAaAwAAAP/GEBCQwAAAGAArBQEKYAAbAgDzgAAWNu84ACbnQBBQA7MAAAAQAADgACAJIMAACPDw0AAQIAOzAAAAEAAAcAAQAiCQcA5wUAAAmgMAAADAigsAAAE5SggAAAESaw0BOjAAEwYWZpa21ydXY7MAAAAQAAVgIFAO8BAABUA+YASAcAAMMK5gCqDYYAAQwBoKAAABQjd/ITFAAAAQABAQDzfw8AAAEAAQDzgAJ0dQEDAPMvEgAAAQABAAEBAPW6EAAAAQABAAEAAQABAQGwMA9vAOl09joHAAABAI4AqQemAOwBY4ADsJATbQABYW10AQMBoEAAABPGZfLmEwAAAQATgAKwYBQWAAFlcgEDAaAAAAATS3MBdRAAAAECADowAAABAAAAAAAKABAAAAAgCSEJpg/aEtsSChMLE64TAhQYFBkUgwAEoIAAABIJsHAQ6gABsCAQigDqZm5ydgHIBwAAAYECoAAAAAj7oDAAAA9cc3THAQCAxrAAE6EAAbCQEgcAAaAQAAAHIAASiCgBAgA7MAAAAQAADQAGAN4PBQDnEAEAPhIBAGcTAQC5EwAA2BMAABOCArDwFBYAAWZnAYACsIAT7QABoGAAABQRZHMTgAKwwBMyAAFpbwEIAPWnEwAAAQABAAEAAQDzAgFm9RoQAAABAAEAAQABACOAA6AgAAAPVrCgE+sAAWRydPf7BwCPoDAAAA9Ml9cPOzAAAAEAAD0BHQBnBAEAlAQCAFcFAgB/BQIAogUBAJIGBAARBwAAKQcAAMIHAwDRCAIA/wgBAN4LAQAKDAEAMgwDAI4MAQA5DvUAdQ8BAPQPAgA3EAEASBACAHQQAQAHEQAAXREBABESAABPEgAAhhIBAO0SAQAOEwcA+xMDAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/5bf126ddcd97.js b/pr/451/docs/search.index/5bf126ddcd97.js deleted file mode 100644 index bfe32641a..000000000 --- a/pr/451/docs/search.index/5bf126ddcd97.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQBsAASlAABczswAAABAAALAAIADhMHAPsTAwBHAAAEhqCAAAAS77EAFAgAAbBAElAAAbCAFBYAAbBQEpYAARUAFvQyDgAAAQABAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/5c04ad06faf3.js b/pr/451/docs/search.index/5c04ad06faf3.js new file mode 100644 index 000000000..2b79ce6fd --- /dev/null +++ b/pr/451/docs/search.index/5c04ad06faf3.js @@ -0,0 +1 @@ +rn_("AYMCoJAAABQboJAAABQcMjMBgAKgUAAAFEeggAAAE2ZjZfMAAnN08jQHAAABAAEAAaCwAAATsWLzOxMAAC4BAQAjgAKgAAAAEzpkcgEAAbCAFCEAAXDyHRQAAAEAQwADoCAAABFFoEAAABKZZGVz8+APAABLAwEAE4ICoCAAABQNZWnzgAJjZQEEADswAAABAAAJAAQATwkBAIUQAwDHEAEA0hMBALOAhKCQAAAUexABQg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/5c174cbd368c.js b/pr/451/docs/search.index/5c174cbd368c.js new file mode 100644 index 000000000..4ef9427ba --- /dev/null +++ b/pr/451/docs/search.index/5c174cbd368c.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..b8553c6c9 --- /dev/null +++ b/pr/451/docs/search.index/5c2ba014f923.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..29953d66b --- /dev/null +++ b/pr/451/docs/search.index/5c53e8bde1cc.js @@ -0,0 +1 @@ +rn_("AYkCsCAUVgABoDAAABRgaXATAAOgIAAAExGggAAAEyVlaXX1/wsAADkAAQABAAEAAYYCsJAUfQABoAAAABKdYmQBgAKwMBMFAAGgMAAAEN1ydtcAAAKFoDAAABBSoDAAABNcEAETOjAAAAEAAAAAAAkAEAAAAO4I7wjwCCAJIQltEHQQLREuEeESAQMBsDATIAABc/OwEAAAAQABAAEAADswAAABAAAKAAMAVxECAKETAwCMFAMAlwEAAIWgIAAABaewIAfzAAFQGAH1aAwAAAEAAQABAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/5ccc63e2df25.js b/pr/451/docs/search.index/5ccc63e2df25.js deleted file mode 100644 index d3483aeff..000000000 --- a/pr/451/docs/search.index/5ccc63e2df25.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIAOzAAAAEAAOcAAgDFCwAAwwzmAPMAAW07MAAAAQAA5wACAHwFAACoBeYAAQMAOzAAAAEAAAgAAgAvDwYAxxIBAPMAAWM7MAAAAQAAVgAnAIgECwBKBQAA3QgBAN0LAADiCwIAmgwAAKYMAgC4DAEAVQ8AAFoPAQBdDwEAYw8AAOsPAgDyDwEA9w8BABcQAACKEAAAkBAAAN0QAQDqEAEA/hABAAIRAgBbEQEAbhECAHQRAAAkEgAALRIBADISAQB4EgEAiBIBAIwSAQCsEgEAvBIDAMISAQAiEwEANBMAAJ8TAADEEwQAzRMCAPOAAnJ3AQkA898TAAABADUAAYACoEAAABLGsCATgQABaGkBDAGgoAAAFCN38hMUAAABAAEBAPN/DwAAAQABAPOAAnR1AYECsIAT7QABoGAAABQRZHM3AwCBhqBAAAATwrBgE5YATAKQDgGIArAAE0wAAbAAE04AATQ2M4ADoDAAABEBZG5zYwADoBAAAASqYWVvAX8AAAA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/5d45dfdef8cc.js b/pr/451/docs/search.index/5d45dfdef8cc.js deleted file mode 100644 index cec8a307d..000000000 --- a/pr/451/docs/search.index/5d45dfdef8cc.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQACoCAAABL6sDARXwABaXTyYxIAAAEAAQMAOzAAAAEAAAYAAQDgEAYAAQgA9GITAAABAIIABQATAQOwkBPEAAGwUBKqAVlnbHA7MAAAAQAAEgAJAO4PAgBiEQEAmRIBALoSAQDxEgEAUxMBAIoTAQCZEwEA1hMBAAGAArAwEVsAAbAwEV0AAXJ2AQQBsAASJQABZDswAAABAAAFAAIAVxEDAFISAQC3BQABh7BQEicBDrBgEqgAAZQJAzowAAABAAAAAAALABAAAACJDIoMiww3DjgOhxAKEQsRDBENEaAS2RIjgAKgEAAADIhubwEDAPPrDwAAAQABACOAAqAAAAALx2RyYwCEoFAAABEHoDAAABAkFAEQ8mwHAAABAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/5d4cba66f76d.js b/pr/451/docs/search.index/5d4cba66f76d.js new file mode 100644 index 000000000..7d776b212 --- /dev/null +++ b/pr/451/docs/search.index/5d4cba66f76d.js @@ -0,0 +1 @@ +rn_("AQQA9vcMAAABADgDAQAtAQEAAQoA87gSAAABAKwAAQEA9xIJAAABAFAHAQByAAEAFQEjgAKwYBPSAAFpbwECAPS1DAAAAQBEBgEAAQUA9OgTAAABAFAAAQD3BAAAh6AgAAAMt7CQEd8AAQScAzowAAABAAAAAAAIABAAAACLBQMJBAlRDFIM5hDnEOsS7BI=") \ No newline at end of file diff --git a/pr/451/docs/search.index/5d5b1b8d153b.js b/pr/451/docs/search.index/5d5b1b8d153b.js deleted file mode 100644 index 4dc4d979a..000000000 --- a/pr/451/docs/search.index/5d5b1b8d153b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIAOzAAAAEAAAUAAgAyDAMAhhIBAAECAPP3EAAAAQABAAEGAPQEFAAAAQABAAEA8wYBbfQbEgAAAQABAAEA84ACbnbzgAJhaQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/5dbdf61761a4.js b/pr/451/docs/search.index/5dbdf61761a4.js deleted file mode 100644 index 26687ad77..000000000 --- a/pr/451/docs/search.index/5dbdf61761a4.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQgA9GITAAABAIIABQATAAOwkBPEAAGwUBKqAVlnbHA7MAAAAQAAEgAJAO4PAgBiEQEAmRIBALoSAQDxEgEAUxMBAIoTAQCZEwEA1hMBAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/5dd78db990e0.js b/pr/451/docs/search.index/5dd78db990e0.js new file mode 100644 index 000000000..2f6f58861 --- /dev/null +++ b/pr/451/docs/search.index/5dd78db990e0.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..a0ba7dfa9 --- /dev/null +++ b/pr/451/docs/search.index/5e2391be14db.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..8239434d0 --- /dev/null +++ b/pr/451/docs/search.index/5e3e1ae9d4f1.js @@ -0,0 +1 @@ +rn_("AQUAOzAAAAEAAAMBAgDRDAEAiA4BAQEBAaAAAAATMHP1vQQAAPkK8wMBAAQAAQECoGAAABOxoOAAABQOZXQ7MAAAAQAACAABAMQECAABgAOwIBJ6AAGwsBK0AAGwsBK2AAFiY3PzgARjbHR2") \ No newline at end of file diff --git a/pr/451/docs/search.index/5e60a22686e2.js b/pr/451/docs/search.index/5e60a22686e2.js new file mode 100644 index 000000000..f4747a0a8 --- /dev/null +++ b/pr/451/docs/search.index/5e60a22686e2.js @@ -0,0 +1 @@ +rn_("AQMA9u4MAADmAlgAAQDPAgEAAQQAOzAAAAEAAAoABAB1EAUAthIBABcTAQA+FAAAAYYCoAAAABM6oAAAABM7ZHLzAgF2AWYTAADzgAJlbycBAADHoDAAAA/ToHAAABKGoIAAABQNsNATrAABoLAAABQNQEUICTswAAABAAAJAAMAtAUCABMHBABIDAEAI4ACKBg73U/0ZHIBAAA6MAAAAQAAAAAACgAQAAAARAlFCQgQVBNVE4UThhMyFIoUohSjFBOAAqAwAAAQi2x084ACYWU=") \ No newline at end of file diff --git a/pr/451/docs/search.index/5ed9ecced0b1.js b/pr/451/docs/search.index/5ed9ecced0b1.js deleted file mode 100644 index 3e89ec63f..000000000 --- a/pr/451/docs/search.index/5ed9ecced0b1.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYQCsAAQ+gABsAAQ/AABNDbzAgFh8igMAAABAAEAAPU0EgAAbgABAEUBAQDzAAFk80ISAAABAKgAY4ADoCAAAAjIYWVv") \ No newline at end of file diff --git a/pr/451/docs/search.index/5f1f66ca795b.js b/pr/451/docs/search.index/5f1f66ca795b.js new file mode 100644 index 000000000..c00b987f1 --- /dev/null +++ b/pr/451/docs/search.index/5f1f66ca795b.js @@ -0,0 +1 @@ +rn_("AQAA9gURAAABAJMDAQAKAAEAAQAAOzAAAAEAAAYAAgBxEwAA2RMFACOIArBAFFEAAWVzAQ0BsEAUSwABZfUPFAAAAQABAAEAAQABBQA7MAAAAQAACAADAOERAgDmEgQADhQAANcBAACFI4//c3rnFIQC87gMAAABAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/5f940ff8b47f.js b/pr/451/docs/search.index/5f940ff8b47f.js deleted file mode 100644 index 10b11a5de..000000000 --- a/pr/451/docs/search.index/5f940ff8b47f.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYMCsQAUGAABsNAUCgABa20BBAGgoAAAFCN38hMUAAABAAELAPSFEwAAAQABAAEAI4ACoIAAABOtZW9jBAOgYAAAE0NlbnAB2QsAAAEKAPNnEwAAAQBwABOAAqIQAAAUI2xvdwAAAIWgYAAAEgShAAAAE9oEgQM7MAAAAQAAOwAEAJMFBQAhBwAAdAczAAERAABDgAOwgBEAARWgAAAAAgdka3Q=") \ No newline at end of file diff --git a/pr/451/docs/search.index/6025c125c951.js b/pr/451/docs/search.index/6025c125c951.js new file mode 100644 index 000000000..5579a9018 --- /dev/null +++ b/pr/451/docs/search.index/6025c125c951.js @@ -0,0 +1 @@ +rn_("AQQBsEAUSwABZfUPFAAAAQABAAEAAQABAgA7MAAAAQAACwACALcTBQB1FAUA8wEBbTswAAABAAANAAQAvQwAAFIRAADNEgUAMxQFAPMBAWXy5REAAAEAM4CEsBAQcgABoCAAAAwGFEAE") \ No newline at end of file diff --git a/pr/451/docs/search.index/604e947796a4.js b/pr/451/docs/search.index/604e947796a4.js new file mode 100644 index 000000000..0ab4b8dc9 --- /dev/null +++ b/pr/451/docs/search.index/604e947796a4.js @@ -0,0 +1 @@ +rn_("AQAA9O8QAAABAJEBAQAjgQKgYAAAEyViaQEAAPTaDwAAAQDyAAcBI4ECsFATOAABZXMTgAKAB0Ue4AFhcgGJArAgFFYAAaAwAAAUYGlwAQIA9AkTAAABAJEAAQABAwD0PhMAAAEAmAABAPOFAmJ1AQIA9FoRAAABAHsBAQDzgAJpbLcBAACFoCAAABMREgEMOjAAAAEAAAAAABYAEAAAAGYH7wfwB/EH8gf/CzgMOQw6DDsMARDGEFYTVxOSExYUYxRkFGYUcxR0FJsUnxQ=") \ No newline at end of file diff --git a/pr/451/docs/search.index/608f704c3516.js b/pr/451/docs/search.index/608f704c3516.js new file mode 100644 index 000000000..bfd262ed8 --- /dev/null +++ b/pr/451/docs/search.index/608f704c3516.js @@ -0,0 +1 @@ +rn_("AQQBoEAAABQJZfJ/FAAAAQABAQA7MAAAAQAABgABAKYUBgDzBgFl9FQUAAABADMAAQAzAQOwYBNqAIphcnXzYRAAAHcCAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/60fbf2ca92b7.js b/pr/451/docs/search.index/60fbf2ca92b7.js new file mode 100644 index 000000000..5dcc5de1d --- /dev/null +++ b/pr/451/docs/search.index/60fbf2ca92b7.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..accf5acf0 --- /dev/null +++ b/pr/451/docs/search.index/6116929fe6af.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..1f223cf09 --- /dev/null +++ b/pr/451/docs/search.index/61405852662c.js @@ -0,0 +1 @@ +rn_("AQgA9ScUAAABAAEAAQABAPMAAWY7MAAAAQAABQACAO4BAACAEAQAAQIAOzAAAAEAAAsAAgC3EwUAdRQFAPMGAW07MAAAAQAADQAEAL0MAABSEQAAzRIFADMUBQBjgAOwAAkUAAFlbXM=") \ No newline at end of file diff --git a/pr/451/docs/search.index/618145397e6f.js b/pr/451/docs/search.index/618145397e6f.js new file mode 100644 index 000000000..bae3a90ad --- /dev/null +++ b/pr/451/docs/search.index/618145397e6f.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..2b04d427b --- /dev/null +++ b/pr/451/docs/search.index/6186d9628089.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..008588a98 --- /dev/null +++ b/pr/451/docs/search.index/61fa9fe339d6.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..b9d7e2f3f --- /dev/null +++ b/pr/451/docs/search.index/6243561514b9.js @@ -0,0 +1 @@ +rn_("AQEAOjAAAAEAAAAAAAcAEAAAADQTNRPQE9ETChQLFFoUWxTzBgFpAVMTAABDgAOwABSUAAGwUBMoAFpkZm4BAAGwQBMAAAFlOzAAAAEAAA0AAgDGBgwA9RAAACOAAqAwAAAMAGFlNwAAgMWwUBN8AAGwEBQjAAGwYBODAAEAApgI") \ No newline at end of file diff --git a/pr/451/docs/search.index/624a2471dcc3.js b/pr/451/docs/search.index/624a2471dcc3.js deleted file mode 100644 index 4cfcd8c61..000000000 --- a/pr/451/docs/search.index/624a2471dcc3.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9PwSAAABAAEAAQABAAD1LQwAAAEAAQABAAEA84ACZ2sTgAKwsBNVAAFhbwGAAqAgAAALtKBAAAAL5mV0AYUCsHATfgABsDAS1AABZXMTAAOwQA9dAAGgYAAAEQhiZXM7MAAAAQAAEgAEAIMFAQCcBgoA1g8BACkQAwABAwD08g8AAAEABAABAAEKAaBAAAATxmXy5hMAAAEAE4ACsGASKQABY2nzgAJkcvMAAmVvOzAAAAEAAAkAAgBuBwUAsQcDAAECAbAwENQAAW47MAAAAQAACQABAKoLCQDzgAJvdAEDADswAAABAADnAAIASAcAAMMK5gABAgGgcAAAEXRpOzAAAAEAAPkAAgBHBwAAygn4ABOAAqBAAAAL5XJ1AQQA9LMSAAABAJUAAQABAwDzQhIAAAEAqADzgAJhb9cHAACIsEAMpwABsTAT4wABNIEWOzAAAAEAAB4ADQCfBAEAXQUBAL0LAADrCwcADwwBAGsPAQBUEAEA3xAAACwRAwBpEQEAHxIAAH4SAQAIFAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/62bb0fe614ca.js b/pr/451/docs/search.index/62bb0fe614ca.js deleted file mode 100644 index 9392d5001..000000000 --- a/pr/451/docs/search.index/62bb0fe614ca.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQA9C4QAAABAAEAAQABAAA6MAAAAQAAAAAACgAQAAAAaw9sD1QQVRDfEGkRahF+En8SCBQJFPMBAXnycxMAAAEAAQADoKAAABO6oCAAABL6sDARXwABZWl08mMSAAABAAEDADswAAABAAAGAAEA4BAGAAEIAPRiEwAAAQCCAAUAEwEDsJATxAABsFASqgFZZ2xwOzAAAAEAABIACQDuDwIAYhEBAJkSAQC6EgEA8RIBAFMTAQCKEwEAmRMBANYTAQABgAKwMBFbAAGwMBFdAAFydgECAbAAEiUAAWQ7MAAAAQAABQACAFcRAwBSEgEAI4ECoJAAABO5aW8BBwD01RMAAAwAHgAPAHcWAAGJsFATcwABsFASJwEOsGASqAABtQkDOjAAAAEAAAAAAA4AEAAAAIkMigyLDDcOOA5NEE4QhxCqEAoRCxEMEQ0RoBLZEkOAA6AgAAAMf6AQAAAMiG1ubwEDAPTrDwAAAQABABEBAYACoLAAABOMsDAS3AABbnRDAAOgIAAAEJawUBFnAAFlaGk7MAAAAQAABgADAMcLAAB4EQMAOBMBAPOAAmRyhwsAAAqAAecAEAGAAeoAEAGgUAAAEQegUAAAE7+gMAAAEBmgMAAAECQwMWNlaW9zdHV39WwHAAABAOIHwAJVAQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/62ce08268925.js b/pr/451/docs/search.index/62ce08268925.js deleted file mode 100644 index 86a152354..000000000 --- a/pr/451/docs/search.index/62ce08268925.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQwBoKAAABQjd/ITFAAAAQABAQDzfw8AAAEAAQABiYSggAAAFBGgQAAAE8KgYAAAE+KgkAAAFBUCgAoTgYSgoAAAE1exoBQhAAGw0BO7AAEEAQnzgANhdHU=") \ No newline at end of file diff --git a/pr/451/docs/search.index/635558f41241.js b/pr/451/docs/search.index/635558f41241.js deleted file mode 100644 index 25d15c547..000000000 --- a/pr/451/docs/search.index/635558f41241.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABoJAAABLgaPKoEgAAAQDzAgFk8ucGAAABAAECADswAAABAAAFAAEA7xMFAPMLAW07MAAAAQAABQABAK8TBQAjgAKwIAyWAAFhbwECAPPNCAAANAMBAAGKArAAE0wAAbAAE04AATQ2I4ECoDAAAAwqZXIBgAKwQBFpAAEb8AtPDERjdbcGAACHsBAP2AABsFATywABEVEJ9X0EAAAKAMEAAQBuBw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/639489df947c.js b/pr/451/docs/search.index/639489df947c.js new file mode 100644 index 000000000..8eb1924bc --- /dev/null +++ b/pr/451/docs/search.index/639489df947c.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..0cc514e16 --- /dev/null +++ b/pr/451/docs/search.index/639fc837a5d5.js @@ -0,0 +1 @@ +rn_("AYACsRAUfwABsBAMQQABZW8BBQA7MAAAAQAADQABAIERDQABAgDz7g8AAAEAAQABAgD06g8AAAEAAQABAAEDADswAAABAAAIAAEAnxAIAIcHAACHsKATqQABsNAUQQABoFAAABQMjBQROzAAAAEAAAYAAwBKBwAAzxADAJ4SAQDzgAJsdBOAA6AgAAAMu7CgFCUAAWFkbg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/63d5d6c31ff0.js b/pr/451/docs/search.index/63d5d6c31ff0.js deleted file mode 100644 index e7bb838f0..000000000 --- a/pr/451/docs/search.index/63d5d6c31ff0.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQ8A9IoTAAABAEsAAQABBAD0BRAAAAEAsAABAAECADowAAABAAAAAAAJABAAAABTEVQRZxJoElETUhOlE6YTFhQXFPMAAW/yQQkAAAEAU4ADsQAT6AABaG9ww4CEoBAAABLGsIATCAABBQAG") \ No newline at end of file diff --git a/pr/451/docs/search.index/6441524172dd.js b/pr/451/docs/search.index/6441524172dd.js new file mode 100644 index 000000000..c787160fe --- /dev/null +++ b/pr/451/docs/search.index/6441524172dd.js @@ -0,0 +1 @@ +rn_("AQAA9JkPAAABAAEAAQDzAgFzARcRAAABAAKwgBRzAAGgYAAAFJtkc/LqCAAAOwOTgISwwBOvAAGgEAAAEcIAwUA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/64f401d0105c.js b/pr/451/docs/search.index/64f401d0105c.js deleted file mode 100644 index c44133ce1..000000000 --- a/pr/451/docs/search.index/64f401d0105c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9IUQAAABAIYBAQAjgQKgYAAAEqtiaQEAAPR3DwAAAQDrAAEBI4ECsFASvgABZXMTgAKAByMewAFhcgGJArAgE9IAAaAwAAAT3GlwAQIA9JASAAABAI8AAQABAwD0xBIAAAEAlQABAPOFAmJ1AQIA9PEQAAABAG0BAQDzgAJpbLcBAACFoCAAABKYEgEMOjAAAAEAAAAAABYAEAAAAEQH0QfSB9MH1AfJC/0L/gv/CwAMnw9cENwS3RIXE5YT3xPgE+IT7RPuExEUFRQ=") \ No newline at end of file diff --git a/pr/451/docs/search.index/655cb440fbba.js b/pr/451/docs/search.index/655cb440fbba.js deleted file mode 100644 index 01baf5ae0..000000000 --- a/pr/451/docs/search.index/655cb440fbba.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEBsIAS1gABdfITCQAAAQAjgAKwEAkRAAEzNgEGAPNtEAAAAQAOAROAArAAD0EAAW91AQIA8yMHAADsAQEAAQMA8zsSAAABAAEAI4ACsAAROAABaGkBAAA7MAAAAQAAEwABAFgHEwAjgAKgMAAADIdhZfOAAnJ0E4ACsDATJAABYWwBAAGggAAAFBJm8skIAAA3CgEHAPXPEgAAAQABAAEAAQAjgAKgUAAAEN9hb/MCAmZzOzAAAAEAAAgABAAfBwAAUgcFAK8MAAAXEQAAE4ACoEAAAAvGYW8BAwGwQBPdAAF08kETAAABACOFAqBAAAATcWJzIwADoEAAABB/oFAAABDsYWJm99YIAAABAEgAJwcBAF8AAQATgQKgUAAAEXRndAGAAqBAAAARYaAgAAATUGFpAQIA9AwJAAABALUHAQABAgDzAwwAAAEAAQATgAOwEAkKAAGwMBODAAFpdXkBAgA7MAAAAQAACwACADoTBQDvEwUA8wgBbTswAAABAAAMAAMA6RAAAFYSBQCvEwUAAYECoIAAABJEoHAAABOUYXPzgAJucrfbAQCNoUAAABPqoCAAAAkOoFAAAA+HN5UPOzAAAAEAACcCBQDtAQAAEwIrAd4GAABHBwAAygn4AA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/657959d9dfd0.js b/pr/451/docs/search.index/657959d9dfd0.js deleted file mode 100644 index 505d334ea..000000000 --- a/pr/451/docs/search.index/657959d9dfd0.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBsAAMngMfc/LeCwAAAQABAgDz3QsAAAAFAQAjAAKgQAAADJplcvKYBAAAAQABgAKgQAAAE0OwMBJlAAFhcwECAaBAAAATxmXy5hMAAAEA84AEbG5ydg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/667f942da920.js b/pr/451/docs/search.index/667f942da920.js deleted file mode 100644 index a4d3e2664..000000000 --- a/pr/451/docs/search.index/667f942da920.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABsGATdQABZQGsEwAAAQYA9KISAAABAEUBAQABBAD0vBIAAAEABwEBAPMAA2VyczowAAABAAAAAAAJABAAAADyCPMI9AjBEAARBxIIEhUSbRNuEw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/6693e2a7afc5.js b/pr/451/docs/search.index/6693e2a7afc5.js new file mode 100644 index 000000000..72e8ca49d --- /dev/null +++ b/pr/451/docs/search.index/6693e2a7afc5.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..341be0fc8 --- /dev/null +++ b/pr/451/docs/search.index/66dcaa05b5bb.js @@ -0,0 +1 @@ +rn_("AQABoCAAAAzYbQF6BQAAAQAA9PISAAABAAEAAQDzBAFk9JUSAAABAAEAAQAjhISwoBQ7AAGwQBLwAAGwcBPiAAEVgAAzgAOw0BQlAAFhZWk=") \ No newline at end of file diff --git a/pr/451/docs/search.index/670f0a9091b9.js b/pr/451/docs/search.index/670f0a9091b9.js new file mode 100644 index 000000000..bdbd50565 --- /dev/null +++ b/pr/451/docs/search.index/670f0a9091b9.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..b6acec73e --- /dev/null +++ b/pr/451/docs/search.index/6718ecfbb765.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..9ee4cc0e5 --- /dev/null +++ b/pr/451/docs/search.index/67a942abe161.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..9af061301 --- /dev/null +++ b/pr/451/docs/search.index/67bd888b1183.js @@ -0,0 +1 @@ +rn_("AQ0A9cMTAAABAAEAegABAAEHAPOrEwAAbAABACOAAqEQAAAT/W5yAQcA9JsRAAABAAEAAQAzAASgIAAACRuw4BPyAAExZWlyOjAAAAEAAAAAAAkAEAAAAFcEWARCB/0IWgxbDFwMXQw9ET4RAQIA9O8QAAABAJEBAQABAwD0WhEAAAEAewEBAPMAAmJ1AQEQAAABAwD07RAAAAEAkQEBAPOEAmRzAQIA8/8IAAABAFgA84ACaXQBGADzqhQAAAEAAQAjgQKwwBPBAAFjbQENAbBAFEsAAWX1DxQAAAEAAQABAAEAE4ICsAARBQABa3MBBAD0lBAAAAEAAQABAAGBAqAgAAARALBAE1YAAWVp84ICZXQBBAGwYBKpAAFy9A8MAAABAO8DAQDzgANhZW8BCQD21BIAAAEA/wCJADUAAQATgAKwYBCuAAFkbgEGADswAAABAAAFAAIA2BADAG8TAQABAwGgcAAAExFn9E8HAAABAAYCAQABCQDzYxQAAAEAOwABgQKwgBRzAAGgYAAAFJtkcxcBAIiFsNAUnQABoEAAABRGsGAUFgBQApAKAQQA9K8TAAABAEsAAQBTgAOwkBSWAAFlaG8BAAGgQAAAEWdlAa4PAAAjgQKwUBESAAFrdHfsAwCOoZAAABSLsLATIAABoKAAABLTsVAUlAAB/cEPOzAAAAEAACEACACAAAAAhgADAGgFAQArBwcAJgkHADAMBwA5EAEAfhIAAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/680921c7e0dc.js b/pr/451/docs/search.index/680921c7e0dc.js deleted file mode 100644 index 102a5159b..000000000 --- a/pr/451/docs/search.index/680921c7e0dc.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsEAS2gABoMAAABOUZW8BAgD0TBEAAAEAAQABABMAA7AgEVAAAbCAE6UAAWFzdAGIDAAAAQQA84QPAAABAAEAs4CEsEAS7QABQQAG") \ No newline at end of file diff --git a/pr/451/docs/search.index/688c1e1d0f3f.js b/pr/451/docs/search.index/688c1e1d0f3f.js new file mode 100644 index 000000000..d8ff702fb --- /dev/null +++ b/pr/451/docs/search.index/688c1e1d0f3f.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..b853d5ea1 --- /dev/null +++ b/pr/451/docs/search.index/6902fbabc407.js @@ -0,0 +1 @@ +rn_("AQIAOzAAAAEAAAUAAQB1FAUA8w0BbTswAAABAAAFAAEAMxQFACMDhLBAEqcAAaEAAAAUe7AAE2oAijAIAjswAAABAAAYAAYAGBEHAMsSAQBJEwQAJxQEAC4UAwCcFAAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/690e03933450.js b/pr/451/docs/search.index/690e03933450.js deleted file mode 100644 index ceac190b6..000000000 --- a/pr/451/docs/search.index/690e03933450.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAEoAAAABAZsCAQyAABoFAAABFtoEAAABOgZGxzdjswAAABAAATAAYAhgUAAOULAACSDAAAjw8NAOwQAACbEwEAAQIA9EwRAAABAAEAAQAThAKwIBFQAAFhc/OAAmFl") \ No newline at end of file diff --git a/pr/451/docs/search.index/694efa293f2d.js b/pr/451/docs/search.index/694efa293f2d.js deleted file mode 100644 index 57f6b6772..000000000 --- a/pr/451/docs/search.index/694efa293f2d.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9DkSAAABAKQAAQABCAD08xIAAAEAhQABADMBA7BAEsIAAWFiZfWfDAAAAQAeA1QCzwABAAD14AsAAAEAYgMBAAEA84ACc3UjgQKwgBLtAAFydAEGAPQHEAAAAQCwAAEAUwCEsIASdQABoKAAABQjAgEU9VUFAAABAGwOUQABAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/69efc07ee8fe.js b/pr/451/docs/search.index/69efc07ee8fe.js new file mode 100644 index 000000000..d3902e756 --- /dev/null +++ b/pr/451/docs/search.index/69efc07ee8fe.js @@ -0,0 +1 @@ +rn_("AYACsHASvgABoLAAABP9ZW8jAQOwcBHlAAGAE2MAEPptbnM7MAAAAQAAGgAGAF4HAQDkCAUA9QwBADMRCQBxEQAAQxMFAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/6a152aadbb4b.js b/pr/451/docs/search.index/6a152aadbb4b.js deleted file mode 100644 index 2b346bbd2..000000000 --- a/pr/451/docs/search.index/6a152aadbb4b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9JASAAABAI8AAQABAwD0xBIAAAEAlQABAPMCAmJ1OjAAAAEAAAAAABMAEAAAABwHHQceB2EPYg8HEAgQJhAnELgQuRBlEWYRjhKPEpsSnBKrEh4THxM=") \ No newline at end of file diff --git a/pr/451/docs/search.index/6a4e9b07d877.js b/pr/451/docs/search.index/6a4e9b07d877.js deleted file mode 100644 index 5ec50ab37..000000000 --- a/pr/451/docs/search.index/6a4e9b07d877.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsHASRwABoLAAABOAZW8jAQOwcBF1AAGAEukAEPBtbnM7MAAAAQAAGgAGADwHAQDACAUAswwBAMoQCQAIEQAAyRIFAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/6a81a15ea6e9.js b/pr/451/docs/search.index/6a81a15ea6e9.js deleted file mode 100644 index a35fc8dc3..000000000 --- a/pr/451/docs/search.index/6a81a15ea6e9.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAAOzAAAAEAAAYAAgAyEQMALxICAAEKAbBAE8cAAWX1jxMAAAEAAQABAAEA8wACaXP02gsAAAEAUAABACOBAqDgAAATrmV5") \ No newline at end of file diff --git a/pr/451/docs/search.index/6ab71af3e20b.js b/pr/451/docs/search.index/6ab71af3e20b.js deleted file mode 100644 index d796e6255..000000000 --- a/pr/451/docs/search.index/6ab71af3e20b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9VsRAAABABwBAQBWAQEBAPedEAAAAQABAMMCAQCCAAUAAQIA9DIQAAABAAEAAQATgAKg0AAAE9Rjc/OAAmVvI4ACsAAHJAABbHTzAAJhcvI4DwAAAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/6b3565993718.js b/pr/451/docs/search.index/6b3565993718.js new file mode 100644 index 000000000..6ebeb8c90 --- /dev/null +++ b/pr/451/docs/search.index/6b3565993718.js @@ -0,0 +1 @@ +rn_("AQYA9PYSAAABAJkBAQATAgKwYBL4AAFmbfLPDAAAAQAjggKwQBMmAAFlbAEFAPToEwAAAQBQAAEA8wMBY/LrEgAAAQABgQKgAAAACR+gMAAAD7tzdBOAAqAQAAAHQm5404CEsHAUoAABkEAB") \ No newline at end of file diff --git a/pr/451/docs/search.index/6b804baa315c.js b/pr/451/docs/search.index/6b804baa315c.js new file mode 100644 index 000000000..5a9186a25 --- /dev/null +++ b/pr/451/docs/search.index/6b804baa315c.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..5bd2f49f7 --- /dev/null +++ b/pr/451/docs/search.index/6b91b3b9e417.js @@ -0,0 +1 @@ +rn_("AYACsHARwwABsCAP0gDuaHkBBQD08wwAAAEAOgMBAAEAAPRODAAAAQABAG4AAQIAOjAAAAEAAAAAAAkAEAAAAMcPyA+7EHcR1BLVEtQTXRSSFJMUAQMA8w4JAAABAAEA1wMAAIagQAAAEd4YQAc6MAAAAQAAAAAAFwAQAAAAXAddB+cHGwlPCVAJ8gywD7EP1Q9+EIUQhhCHEIgQxxDIEP4Q/xB5EtIT0xP+E/8T") \ No newline at end of file diff --git a/pr/451/docs/search.index/6c530d02ab41.js b/pr/451/docs/search.index/6c530d02ab41.js deleted file mode 100644 index c2f73879e..000000000 --- a/pr/451/docs/search.index/6c530d02ab41.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsFATfgABoAAAAAjZY3QBAQD0/BIAAAEAAQABAAEDAPSaDAAAmgaZAAEAAYACoBAAAAwqoFAAABIRY2QBAgDzzQgAADQDAQDzAAFt84cEAADBAAEAAYECoBAAABKwoHAAABL3YWPzAAFy8ywHAAD6CRgAZw8AgIiwMA9ZAMqgMAAAE/UpMCM=") \ No newline at end of file diff --git a/pr/451/docs/search.index/6c8b565dd978.js b/pr/451/docs/search.index/6c8b565dd978.js deleted file mode 100644 index 8047c7b30..000000000 --- a/pr/451/docs/search.index/6c8b565dd978.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9S0JAAABAAEAAQABACOAAqCgAAAT9WZwRwAAAIWwMA88AAGgUAAAD/2wcBCBAAGxQBQcAAGBKQDyUQUAAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/6d6ee039d782.js b/pr/451/docs/search.index/6d6ee039d782.js new file mode 100644 index 000000000..3b7fc27db --- /dev/null +++ b/pr/451/docs/search.index/6d6ee039d782.js @@ -0,0 +1 @@ +rn_("AQsBoEAAABQfZfJNFAAAAQABBQD06BEAAAEAAQABAAGDArAAENQAAbBQE2EAAWR3E4ACoKAAABPVZXYBAgD07xAAAAEAkQEBAAEAAPTaDwAAAQDyAAcBI4ICsFATOAABZXMBAgD0CRMAAAEAkQABAAEDAPQ+EwAAAQCYAAEA84UCYnUBAgD0WhEAAAEAewEBAPOAAmls8wADYnR1OjAAAAEAAAAAAA8AEAAAAGYH7wfwB/EH8gcBEMYQkhMWFGMUZBRmFHMUdBSbFJ8UAQwA8/gTAAABAAEAAYACsAAJDAABoCAAAAzwZG4BBgD0BxMAAAEAkQABAPMDAWL07RAAAAEAkQEBAJcGAACHsEAP2AABoHAAABKLsJATNgABOAkDOjAAAAEAAAAAACQAEAAAALcFuAW5BboFKAcpByEM5QzmDOcM6wzsDO0M0A/RDyIQIxAnECgQRRBGEEcQSBBJEEoQSxCOEJMQvhC/EHITlhO9E1kUZRSHFJgUAQMA9BwTAAABAFEBAQDzgAJjbXcBAIDFsYAUqAABQIEECA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/6d8cc2ed1313.js b/pr/451/docs/search.index/6d8cc2ed1313.js deleted file mode 100644 index cfef3db39..000000000 --- a/pr/451/docs/search.index/6d8cc2ed1313.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQADoKAAABO6oCAAABL6sDARXwABZWl08mMSAAABAAEDADswAAABAAAGAAEA4BAGAAEIAPRiEwAAAQCCAAUAEwEDsJATxAABsFASqgFZZ2xwOzAAAAEAABIACQDuDwIAYhEBAJkSAQC6EgEA8RIBAFMTAQCKEwEAmRMBANYTAQABgAKwMBFbAAGwMBFdAAFydgECAbAAEiUAAWQ7MAAAAQAABQACAFcRAwBSEgEAI4ECoJAAABO5aW8BBwD01RMAAAwAHgAPAHcLAAGIsFASJwEOsGASqAABlQkDOjAAAAEAAAAAAA4AEAAAAIkMigyLDDcOOA5NEE4QhxCqEAoRCxEMEQ0RoBLZEiOAAqAQAAAMiG5v") \ No newline at end of file diff --git a/pr/451/docs/search.index/6d8f629c95e4.js b/pr/451/docs/search.index/6d8f629c95e4.js new file mode 100644 index 000000000..c704aee91 --- /dev/null +++ b/pr/451/docs/search.index/6d8f629c95e4.js @@ -0,0 +1 @@ +rn_("AYACsAARYwABsAARZQABNDYBCwA7MAAAAQAABgABAAIUBgAjgAKggAAAFC1lbyMCA6BgAAATwLBAFJ0AAWVucAERDAAA8wACYXYB0wYAAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/6dfbbd7cf8df.js b/pr/451/docs/search.index/6dfbbd7cf8df.js deleted file mode 100644 index f6531ae59..000000000 --- a/pr/451/docs/search.index/6dfbbd7cf8df.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABoDAAABEFdPJEEAAAAQABgAKgAAAAEBWgAAAAEBY0NvMDAXYBrQwAABcBAACHsKASiAABsCAL2gABsGASggABsIASZwABoCAAABB8CURGOjAAAAEAAAAAAAkAEAAAANsG3AbdBugLfQ/bEBgSGRIaEr8T") \ No newline at end of file diff --git a/pr/451/docs/search.index/6e41df2f5abc.js b/pr/451/docs/search.index/6e41df2f5abc.js new file mode 100644 index 000000000..7081ad294 --- /dev/null +++ b/pr/451/docs/search.index/6e41df2f5abc.js @@ -0,0 +1 @@ +rn_("AQMBoAAAABMwc/S2DwAA8wMBAAQAAQECsGAUIwABsEAUYQABZHQ7MAAAAQAADQAGAOISAwBOEwEAvhMBANUTAQBWFAEAfRQBAAEBA7BwEeUAAbCAEr4AAYATYwAQ+m1uczswAAABAAALAAIA5AgFAEMTBQCjgISwsBOsAAGxcBSgAAEIQIIBAIWgcAAAEoOwMBSUAAGgcAAAEPSxABQjAAGwABMkAWcGIQHybgUAAAEAZwQAAIdgj3BMNRagEAAABaOwMBMWAUqgQAAAERcRwQw7MAAAAQAABgACAHETAADZEwUA") \ No newline at end of file diff --git a/pr/451/docs/search.index/6e4c3193eddd.js b/pr/451/docs/search.index/6e4c3193eddd.js deleted file mode 100644 index f33a6610b..000000000 --- a/pr/451/docs/search.index/6e4c3193eddd.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIAOjAAAAEAAAAAAAgAEAAAANAL0Qs2DzcPbw9YEHwQ+BL5EgEAAPQGCQAAAQABAAEAAQUBsDAShgABaPNvEwAAAQBrABODAqAwAAAQS2NwAQMA9G0PAAABAOgAAQABgQKgAAAAEQWgEAAAD1BldfOAAmFyAYECoJAAABNZsFASEwABYncTgAKwkBKeAAFucvMAAmV08kQQAAABAAEBAPUtDAAAAQABAAEAAQATgAKwsBNVAAFhbyMAArBADKcAAWV0OzAAAAEAAAkABACfBAEAXQUBAA8MAQAsEQMAAQIAOzAAAAEAAAkAAgC3BwcAgBIBAAGAArCQEucAAaCgAAATWGV2E4ACsYAUHgABY20BggKwQBODAAGgUAAAEWFiZhOAAqBAAAATrGRzI4ACoNAAABP2Y27zgIQRAUABBQGwQBLCAAFlARISAABDggOhAAAAFAKg0AAAE9lrbXMTgAKwYBBfAAFhcgEFADswAAABAACHAAIAcxAAAH0RhgABggOwIBKCAAGwIBABAAGwQBD1AAFrbHNTgAOwIBPdAAFrdHUBiYSggAAAFBGgQAAAE8KgYAAAE+KgkAAAFBUCgAoTgoSgoAAAE1exoBQhAAGw0BO7AAEEAQkBgAKxcBQcAAGg0AAAE5VjbyOAArBgEF0AAWlvAQAA9NoLAAABAFAAAQAjgQKg4AAAE65leQENADowAAABAAAAAAAHABAAAAC6ErsSUxNUE4oTixPWE9cTAYECsEAS2gABoMAAABOUZW8BAgA6MAAAAQAAAAAACAAQAAAAZA9lDw4RXRJeElcT2RMKFAsU8wEBbzswAAABAAAJAAQAKwkBAB8QAwBdEAEAVRMBAPOAA21ucgEAADswAAABAAAGAAIA9hIAAFwTBQBDgAOwQBPNAAGgIAAADKZlcnMjAAKwMBM4AAFjcvIkEwAAAQABgQKwgBKIAAGhAAAAE9hjZgELAPTjEgAAAQABAAEAAYACoAAAABAVoAAAABAWNDbzAgF2Aa0MAADzAAJkbAHmCAAA1/45AJKwoBOBAAGwcBFTAAGwgBJnAAGgsAAAEqCd3X87MAAAAQAAFwALAJwBAQCbBAMAWgUCANEGAQDbBgIA6AsAAH0PAADbEAAAQhECABgSAgC/EwAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/6e7e72e7f0eb.js b/pr/451/docs/search.index/6e7e72e7f0eb.js deleted file mode 100644 index 3848409ec..000000000 --- a/pr/451/docs/search.index/6e7e72e7f0eb.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9PoPAAABAKkAAQABhAKwABD6AAGwABD8AAE0NvMCAWHyKAwAAAEAAQEA9TQSAABuAAEARQEBAPOAAmVv84ACaGs=") \ No newline at end of file diff --git a/pr/451/docs/search.index/6e98d18aefa1.js b/pr/451/docs/search.index/6e98d18aefa1.js deleted file mode 100644 index 59bed4121..000000000 --- a/pr/451/docs/search.index/6e98d18aefa1.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYkCsCAT0gABoDAAABPcaXATAAOgIAAAEpiggAAAEqtlaXX1yQsAADQAAQABAAEAAYYCsJAT9wABoAAAABIoYmQBAgD00AsAAAEAZQMBAAEAAPQGCQAAAQABAAEAAQAAOzAAAAEAAAYAAgD2EgAAXBMFACOBArBAE80AAWVz84ADZXh5AQAA9AIJAAABAAEAAQAjgAKwIBKMAAFyeEcBAADGsMATIgABoBAAABDtsEAMkAABoDAAABB0AESAC/RLBQAAAQBKBwEA1wAAAIWgMAAAD/GgMAAAEuIQARM6MAAAAQAAAAAACgAQAAAAygjLCMwI/Aj9CNULCRAOEMQQxRBpEg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/6e9cbfa350f1.js b/pr/451/docs/search.index/6e9cbfa350f1.js new file mode 100644 index 000000000..f27ca878c --- /dev/null +++ b/pr/451/docs/search.index/6e9cbfa350f1.js @@ -0,0 +1 @@ +rn_("AQAAOzAAAAEAAAYAAgBxEwAA2RMFACOIArBAFFEAAWVzAQQBsDATQQABY/NrEQAAAQABAAEFADswAAABAAAIAAMA4RECAOYSBAAOFAAA8wADY2Vw87gMAAABAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/6ede7ccd0b48.js b/pr/451/docs/search.index/6ede7ccd0b48.js deleted file mode 100644 index 62f2e1e94..000000000 --- a/pr/451/docs/search.index/6ede7ccd0b48.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMA9DIMAAABAAEAAQABAAGwUBOjAAFyAaMEAABTAYSwQBItAAGwQBPdAAGYAAQ6MAAAAQAAAAAAKAAQAAAAbQRuBG8EcgXbCNwIAQk0CYIMVA/DD8QP0Q8AEFkQWhBbEKIQDhJqEmsSbBJtEpgSphKnEtQS1RJBE0ITRhNHE0gTWBNZE7sTvBPSE9MT9xP4Ew==") \ No newline at end of file diff --git a/pr/451/docs/search.index/6f1ace4f0243.js b/pr/451/docs/search.index/6f1ace4f0243.js deleted file mode 100644 index ff2209179..000000000 --- a/pr/451/docs/search.index/6f1ace4f0243.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQACoCAAABL6sDARXwABaXTyYxIAAAEAAQMAOzAAAAEAAAYAAQDgEAYAAQgA9GITAAABAIIABQATAQOwkBPEAAGwUBKqAVlnbHA7MAAAAQAAEgAJAO4PAgBiEQEAmRIBALoSAQDxEgEAUxMBAIoTAQCZEwEA1hMBAAGAArAwEVsAAbAwEV0AAXJ2AQQBsAASJQABZDswAAABAAAFAAIAVxEDAFISAQC3BQABh7BQEicBDrBgEqgAAZQJAzowAAABAAAAAAALABAAAACJDIoMiww3DjgOhxAKEQsRDBENEaAS2RIjgAKgEAAADIhubwEDAPPrDwAAAQABACOAAqAAAAALx2RyYwGEoFAAABEHoDAAABAkFAEQ8mwHAAABABOAArBgEkcAAWNnE4ADoLAAABOAoEAAAA42ZW9z") \ No newline at end of file diff --git a/pr/451/docs/search.index/6f3b4b773035.js b/pr/451/docs/search.index/6f3b4b773035.js deleted file mode 100644 index 513a9dc5a..000000000 --- a/pr/451/docs/search.index/6f3b4b773035.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQkBoIAAABQSZgEAEwAAAQIA9VkQAAABAAEASwIBAPMAAml1OjAAAAEAAAAAAAcAEAAAADYQXBJEE0UTdRN2E5QTlRMBCAD1pxMAAAEAAQABAAEA8wIBZvUaEAAAAQABAAEAAQATgAKwoBPrAAFydPMCAmFz80gQAAABAAEAAYABOaUt2hmMdPOAAmFl") \ No newline at end of file diff --git a/pr/451/docs/search.index/6f920086d797.js b/pr/451/docs/search.index/6f920086d797.js new file mode 100644 index 000000000..4faccd03b --- /dev/null +++ b/pr/451/docs/search.index/6f920086d797.js @@ -0,0 +1 @@ +rn_("AQUA9SkQAAABAAEAXQIBAAEEAaAAAAAQxnP16wwAAAEAAQA6AwEAAQIA9ZARAAABAAEAAQABAAGAArBgEvoAAaAgAAAQ5W95AQABoMAAABQgafRSEwAACAAcABsAAQYA9IwUAAABAAEAAQABAgD04xAAAAEAmgABAAECAaBAAAATKXI7MAAAAQAABwACAOEQAQB4EQUA84ACYW9nAwAAhrCAEygAWrCgE58AAQwwBjswAAABAAAIAAMACgwAAKsRAwCQEgMA84ICZWlHAQAAhbBwE68AAaCAAAAUnKBAAAAP37BABPTtCAAA3QYBALADAYECoCAAABDfsFASoAABYW8BBAGgAAAAE8hzAd4QAABjBAOwUBFQAAFjZXP29QgAAAEAAQAsAAEAYgkBAAA7MAAAAQAAJAAIADYHBwApEAIAgRENALARAgCIEgEADxMBAGETAQD4EwIAAQUA8wITAAABAAEAAQEAOzAAAAEAAAUAAgDuCwMA2xEBAFMAA7AwFDsAAWVwcvTMDwAAAQDsAAEAAQADsEAUAAABoUAAABRwoFAAABHRYmNmAf0GAADjgISxQBSZAAECMBABAYSgsAAAE72wQBCdAAGwwBPsAAGgYAAAEXcUIAT25AYAAFEJAQA5AS0BFgEBAwA7MAAAAQAABQABAMASBQABAAGgoAAAFCxy9NYPAAABAPQAAQDzgQJncwEHAPVJEwAAAQABAAEAAQABgAKgQAAAEUiwUBEvAAFjafOAAmFvAQABoCAAABFocvMiDAAAewNtAUOAhKAAAAAUCaAwAAAUCYAFaAAdFQggIQEDArBAEwUAAaAwAAATXGV38nQQAAC6ABODAqBwAAATlmdvAQIA9eQSAAABAPEAgAABABOAArAwExYBSmlwAQYA85MTAAABAAEA8wUCYnMBJhAAAAECAPQEEAAAAQABAAEAI4ACoGAAABKEYmP3XwEAzbBgEMQAAaCQAAAShcBOchU6MAAAAQAAAAAAGAAQAAAAFgIXAnsEfAR9BAcHQQdyB3MHdAd1B3YHdwfpC2IM8QwhEDMQNBAAEYARfxSAFJQUlRQ=") \ No newline at end of file diff --git a/pr/451/docs/search.index/6fa3f569eb52.js b/pr/451/docs/search.index/6fa3f569eb52.js deleted file mode 100644 index f93017e86..000000000 --- a/pr/451/docs/search.index/6fa3f569eb52.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQoBoIAAABQSZgEAEwAAAQgA9acTAAABAAEAAQABAPMDAWb1GhAAAAEAAQABAAEA84MCYXMBAgKgIAAAEVKgMAAAEhdiaAFTDwAAI4ACoIAAABK1YXDzAAJobQGGBAAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/6ffa824fa9c9.js b/pr/451/docs/search.index/6ffa824fa9c9.js deleted file mode 100644 index 3cd8948ba..000000000 --- a/pr/451/docs/search.index/6ffa824fa9c9.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIAOzAAAAEAABYACQDeDwUA5xABAHERAgA+EgEAbhIEAGcTAQCOEwAAuRMAANgTAAAjgAOh8AAAFCOw8BQWAAFjZmcjgAKwUBMkAAFtbgEFAPNnEwAAAQBwAPMEAWMBBxEAAAEBAbBwE70AAXA7MAAAAQAACQACAKkHBwBPEAEAAQIAOzAAAAEAAAsAAgA6EwUA7xMFAPMBAW07MAAAAQAADQAEAH4MAADpEAAAVhIFAK8TBQATAAKgMAAADIJlc/J1EQAAAQABggKwcBOdAAGgQAAAEJNpc/cBAICGsJATgQABkEkI") \ No newline at end of file diff --git a/pr/451/docs/search.index/7032cf56e781.js b/pr/451/docs/search.index/7032cf56e781.js new file mode 100644 index 000000000..974a63f38 --- /dev/null +++ b/pr/451/docs/search.index/7032cf56e781.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..5aaa47e91 --- /dev/null +++ b/pr/451/docs/search.index/7106b5f1eec5.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..407f2b4e6 --- /dev/null +++ b/pr/451/docs/search.index/710e3a47aed6.js @@ -0,0 +1 @@ +rn_("AQAA9F0TAAABAAEAAQDzBgFz8uITAAABAAEBAbBQEtwAAWX3BgkAAE8DAQAJBAEA2wMBAAEDAPSjEQAAAQABAAEAAQMA9J8RAAABAAEAAQDzgwJsc/OAAmN0U4ADoBAAAAy3YW9y") \ No newline at end of file diff --git a/pr/451/docs/search.index/711105b55fc6.js b/pr/451/docs/search.index/711105b55fc6.js deleted file mode 100644 index a4d5bf986..000000000 --- a/pr/451/docs/search.index/711105b55fc6.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQACoDAAABCXoJAAABL7MGj1dxEAAIwBAQABAAEAE4ACoDAAAA9VZHIjAYSwEAjGAySwIAu7AP+gMAAAD3oSABj1dgUAAC4AAQABAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/715f6cda5699.js b/pr/451/docs/search.index/715f6cda5699.js deleted file mode 100644 index cc481f37a..000000000 --- a/pr/451/docs/search.index/715f6cda5699.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQYAOzAAAAEAAAkAAQDKEAkAI4ADsGAS7QABsDAPUQABaXN0AQgA9acTAAABAAEAAQABAPMBAWb1GhAAAAEAAQABAAEAY4ADoDAAABKvZWd0") \ No newline at end of file diff --git a/pr/451/docs/search.index/7163b34c9425.js b/pr/451/docs/search.index/7163b34c9425.js new file mode 100644 index 000000000..6ca564156 --- /dev/null +++ b/pr/451/docs/search.index/7163b34c9425.js @@ -0,0 +1 @@ +rn_("AQgA9M4TAAABAFYAAQABAgGgYAAAFHxz8sARAAABAAGAArAgEAkAAaCQAAAULGFlAQcBsEAUSwABZfUPFAAAAQABAAEAAQABAwGwQBRhAAF08r4TAAABACOEAqBAAAAT7mJzE4ACsMAUlAABdXkBAQD0vBEAAAEAAQABABOAArAwEOgAAWRsN3QAAIuwQBOdAAGgUAAAEVWgkAAAE5GwUBLtAAGgMAAADFmzmAc7MAAAAQAAYgARAAUCAQBfBAMAuwUAALcGAQBVBwEA+ggBAAcJAAARCQAAQwkAAHIMQgDLDAAA1g8BAK4QAQDLEAEAEBEBAJARBACgEgEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/71f423a61c5f.js b/pr/451/docs/search.index/71f423a61c5f.js deleted file mode 100644 index 576e1236a..000000000 --- a/pr/451/docs/search.index/71f423a61c5f.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQgA9acTAAABAAEAAQABAPMAAWY7MAAAAQAABQACAOQBAAAaEAQAAQIAOzAAAAEAAAsAAgA6EwUA7xMFAPMGAW07MAAAAQAADQAEAH4MAADpEAAAVhIFAK8TBQBjgAOwAAjwAAFlbXM=") \ No newline at end of file diff --git a/pr/451/docs/search.index/731cfc3d033b.js b/pr/451/docs/search.index/731cfc3d033b.js deleted file mode 100644 index a6c4c5cd1..000000000 --- a/pr/451/docs/search.index/731cfc3d033b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACoAAAABAVoAAAABAWNDYBgAOwABNMAAGwABNOAAGwcBPAAAE0NmEBBAD0DxQAAAEACgABACOBAqBAAAAQ2m1wAQEAOzAAAAEAAAgAAwBEEgAAtRIAABwUBgDHAQACxaBAAAAP3aCAAAASH0AGgAg7MAAAAQAAFgAIAHsPAQCREAEA1hADAEURBgAqEwIAeBMAANATAQAAFAEA8wACZXY6MAAAAQAAAAAAEgAQAAAABAIFAogFrQw8Dz0PVg9wDwMQBBCrEKwQNRI2EjcSOBIvE5cTmBMBgQKwcBDWAAGwcBDYAAE0NiOAArAQBzMAAW5wAQMCsEASjAABoDAAABLiZXf0CRAAAAUAtgABABOBAqBwAAATG2dvAQQAOzAAAAEAAAYAAgC8CwAAuwwFAGOAA7BQD3sAAWRzdFcBAACFoBAAAA/dsSAT0AABESEBOzAAAAEAAAoAAwB0AAAAjQAFAEwRAwA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/732444e4dd16.js b/pr/451/docs/search.index/732444e4dd16.js new file mode 100644 index 000000000..eee2c4cad --- /dev/null +++ b/pr/451/docs/search.index/732444e4dd16.js @@ -0,0 +1 @@ +rn_("AYACsRAUfwABsBAMQQABZW8BBQA7MAAAAQAADQABAIERDQABAgDz7g8AAAEAAQABAgD06g8AAAEAAQABAAEDADswAAABAAAIAAEAnxAIAIcHAACHsKATqQABsNAUQQABoFAAABQMjBQROzAAAAEAAAYAAwBKBwAAzxADAJ4SAQDzgQJsdPMAAWk7MAAAAQAAjQADAJMEAADcEAAA7RGLAAEFAPNjFAAAAQA7AAEAAaBAAAAQ4GXz+AgAAAEAkgkTgAKgMAAAD8ZkcvMAAWXz4QYAAAEAAQDzgAJodAEBADswAAABAACMAAIA3BAAAO0RiwATgAKgUAAAEAhpdAEIAPTfEwAAAQCLAAUAEwCEsPAUZQAzsJAUSAABsFATJAFnQIkAOzAAAAEAABIACQBPEAIA0hEBABITAQA0EwEAaxMBANATAQAKFAEAGRQBAFoUAQDXAQAAhrCAEZkAAaBAAAAJGwgwJjowAAABAAAAAAANABAAAACQAJEAVAdcCfIL/AzvEPAQgRKCEgkTChObE5wT") \ No newline at end of file diff --git a/pr/451/docs/search.index/735698ad2cbf.js b/pr/451/docs/search.index/735698ad2cbf.js deleted file mode 100644 index 3b0c9a7d9..000000000 --- a/pr/451/docs/search.index/735698ad2cbf.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABoLAAABM0YvPBEgAAIgEBAAGAAqAgAAAPWKAwAAATQGFy8wABdAFRBwAAAQQBoEAAABPGZfLmEwAAAQABBQA7MAAAAQAACwAFAN4PBQA+EgEAZxMBALkTAADYEwAA4wGEoDAAAA9WhQACOzAAAAEAAAsABQB/BQIAkgYEAAoMAQAHEQAATxIAAGOAA7AAD3kDR2Rucg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/7359d03f24c8.js b/pr/451/docs/search.index/7359d03f24c8.js deleted file mode 100644 index 39bb80bb4..000000000 --- a/pr/451/docs/search.index/7359d03f24c8.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMAOzAAAAEAAAUAAQBJEgUAAQQBoFAAABOMaPIpEgAAAQABAAKwQBMiAAGgoAAAE6xlcjowAAABAAAAAAAHABAAAACYBpkGMwc0B3MPdA9hEGIQ84ADZ2lz") \ No newline at end of file diff --git a/pr/451/docs/search.index/736cd7e92e7f.js b/pr/451/docs/search.index/736cd7e92e7f.js new file mode 100644 index 000000000..e777d9f89 --- /dev/null +++ b/pr/451/docs/search.index/736cd7e92e7f.js @@ -0,0 +1 @@ +rn_("AYkCsCAUVgABoDAAABRgaXATAAOgIAAAExGggAAAEyVlaXX1/wsAADkAAQABAAEAAYYCsJAUfQABoAAAABKdYmQBgAKwMBMFAAGgMAAAEN1ydtcAAACFoDAAABBSoDAAABNcEAETOjAAAAEAAAAAAAkAEAAAAO4I7wjwCCAJIQltEHQQLREuEeES") \ No newline at end of file diff --git a/pr/451/docs/search.index/73772e5a75b7.js b/pr/451/docs/search.index/73772e5a75b7.js deleted file mode 100644 index 42b149371..000000000 --- a/pr/451/docs/search.index/73772e5a75b7.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQBsAASlAABczswAAABAAALAAIADhMHAPsTAwBHAAABhqCAAAAS77EAFAgAAbBAElAAAbCAFBYAAbBQEpYAARUAFvQyDgAAAQABAAEAAQUA84QPAAABAAEAY4CEsEAQDAABoAAAAAa0APAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/738edcf2da0b.js b/pr/451/docs/search.index/738edcf2da0b.js new file mode 100644 index 000000000..5342e1141 --- /dev/null +++ b/pr/451/docs/search.index/738edcf2da0b.js @@ -0,0 +1 @@ +rn_("AQUA9OgTAAABAFAAAQDzAwFj8usSAAABACOAArBwFKAAAWhvAYICsAASxwABsAARzwABZXNDAAOgoAAAFD6gIAAAE3VlaXT12hIAAAEAiAABAPoAAYACsHASvgABoLAAABP9ZW8BAgA7MAAAAQAABgABAEkRBgABAgGgQAAAFB9l8k0UAAABAPOAAmlvAQgA9N8TAAABAIsABQATAQOwkBRIAAGwUBMkAWdnbHA7MAAAAQAAEgAJAE8QAgDSEQEAEhMBADQTAQBrEwEA0BMBAAoUAQAZFAEAWhQBAAGAArBAEpwBFqAgAAARDWFpAQMA87sSAAABAAEAI4ECsDAT8AABY2kBAAD0DRMAAAEAWQEBAPMDAXM7MAAAAQAADwADAM0RAQCJEwcAgRQFAAEDAPXLEQAAAQAkAQEAYgHzgAJydgEAADswAAABAAAFAAIAmhIBAPISAwDzAgFkOzAAAAEAAAkAAwDHEQMAlRIDAMkSAQAjgAKgkAAAFD1pbxOAArDwFJAAAWxvAQUA9FkUAAAMACIAEQAjgAKgYAAAE2lncyOAArCQFDsAAWJs9xcDAI+wcBHlAAGwYBRxAAGwcBPiAAGwYBMiAAGwUBMPAAG1+Rc7MAAAAQAAPAAYAKsFAABeBwEA5AgFAPQIAAAiCQAA/gsAAEwMAQDMDAIA9QwBAIEOAwCGDgEA5A8EALUQAQDxEAAAFBEAADMRCQBxEQAAcxEDAMURAAAZEwAAMRMBAEMTBQBTEwAADRQAAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/73e05ea17e51.js b/pr/451/docs/search.index/73e05ea17e51.js deleted file mode 100644 index 31fa802f0..000000000 --- a/pr/451/docs/search.index/73e05ea17e51.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIAOzAAAAEAAAUAAQDvEwUA8w0BbTswAAABAAAFAAEArxMFACMDhLBAEjIAAaEAAAAT9bAAEvAAhzAIAjswAAABAAAUAAUArhAHAFQSAQDPEgQApxMEABIUAAA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/74220e663fcd.js b/pr/451/docs/search.index/74220e663fcd.js new file mode 100644 index 000000000..7ee76d2dc --- /dev/null +++ b/pr/451/docs/search.index/74220e663fcd.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..91e5b372b --- /dev/null +++ b/pr/451/docs/search.index/74b289fd1fbb.js @@ -0,0 +1 @@ +rn_("AYQCsCARaQEhoJAAABQObXABgAKgMAAAEPWgUAAAE7FjcpcAAACFsCATfAABsMAUQQABoGAAABHFEEAOOzAAAAEAABIABgDEBAgAwxEBAN4SAQDOEwEAJRQBAKAUAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/74cc3e63f115.js b/pr/451/docs/search.index/74cc3e63f115.js deleted file mode 100644 index 15577ccb5..000000000 --- a/pr/451/docs/search.index/74cc3e63f115.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9IUQAAABAIYBAQABAAD0dw8AAAEA6wABASOCArBQEr4AAWVzAQIA9JASAAABAI8AAQABAwD0xBIAAAEAlQABAPOFAmJ1AQIA9PEQAAABAG0BAQDzgAJpbPMAA2J0dTowAAABAAAAAAAPABAAAABEB9EH0gfTB9QHnw9cEBcTlhPfE+AT4hPtE+4TERQVFAEMAPN7EwAAAQABAAGAArAACOgAAaAgAAAMrmRuAQYA9I4SAAABAI8AAQDzAwFi9IMQAAABAIYBAQCXBgAAh7BAD3UAAaBwAAASFrCQErwAATgJAzowAAABAAAAAAAkABAAAACcBZ0FngWfBQYHBwfmC6MMpAylDKkMqgyrDG0Pbg/BD8IPxg/HD+QP5Q/mD+cP6A/pD+oPKBAtEFYQVxD3EhsTQBPVE+ET/xMOFA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/765cffa6a271.js b/pr/451/docs/search.index/765cffa6a271.js deleted file mode 100644 index 13f37077f..000000000 --- a/pr/451/docs/search.index/765cffa6a271.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9OMSAAABAAEAAQABBADzSQ8AAAEAAQABgAKgUAAAEFOgYAAAEMFpcgGAAqAgAAAMJ6BgAAAQwGVoAQEBoCAAABC/YvMjDAAAAQABABOBAqAQAAAMJmFjAQMAOzAAAAEAAAUAAgAfDAMA1BABAAEGAPR+EgAAAQCJAQEAEwACsGASgAABZm3yjAwAAAEAI4ACoMAAABNLbnKnDwAAiKBAAAAPRrBAD0cAATQgFzswAAABAAAZAAMA+gEAAEwEDgCqCwkAAYADsHATAQABsDASrAABsIATOAABY3JzAQABsCATAQABbzowAAABAAAAAAAJABAAAABTEVQRZxJoElETUhOlE6YTFhQXFAGAArAgCPAAAbAwErcAAXJ09wQAAMigIAAAB0agYAAAD3KwgBK3AAFABpIJ9gYCAABqAssEAQApCgEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/766d90f1d785.js b/pr/451/docs/search.index/766d90f1d785.js deleted file mode 100644 index 699962e9e..000000000 --- a/pr/451/docs/search.index/766d90f1d785.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9JASAAABAI8AAQABAgD0ehAAAAEAmgABAAEDAPTEEgAAAQCVAAEAAQIBoEAAABKvcjswAAABAAAHAAIAeBABAA8RBQDzAIQDQAg6MAAAAQAAAAAAFQAQAAAAHAcdBx4HYQ9iDwcQCBAmECcQuBC5EGURZhGOEo8SmxKcEqsS1hLXEh4THxM=") \ No newline at end of file diff --git a/pr/451/docs/search.index/76f703a56322.js b/pr/451/docs/search.index/76f703a56322.js deleted file mode 100644 index ab6bd2ee0..000000000 --- a/pr/451/docs/search.index/76f703a56322.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACoIAAABJEsHATlAABYXMBAAGggAAAFBJm8skIAAA3CgEHAPXPEgAAAQABAAEAAQAjgAKgUAAAEN9hb/MAAmZzOzAAAAEAAAgABAAfBwAAUgcFAK8MAAAXEQAA84ACZHk=") \ No newline at end of file diff --git a/pr/451/docs/search.index/771f0ba905a1.js b/pr/451/docs/search.index/771f0ba905a1.js deleted file mode 100644 index be1e870ad..000000000 --- a/pr/451/docs/search.index/771f0ba905a1.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQcA9HgRAAABAAEAAQAjAAKgcAAAEXdhcgFMBwAAAQAA9psQAAABAHMDAQAKAAEAAQAAOzAAAAEAAAYAAgD2EgAAXBMFACOIArBAE80AAWVzAQ0BsEATxwABZfWPEwAAAQABAAEAAQABBQA7MAAAAQAACAADAHERAgBuEgQAjhMAANcDAACGHYyPbzNAFIQG9aEEAAABANcHAQABAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/77798d930b56.js b/pr/451/docs/search.index/77798d930b56.js new file mode 100644 index 000000000..d90c9033c --- /dev/null +++ b/pr/451/docs/search.index/77798d930b56.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..c3005db0a --- /dev/null +++ b/pr/451/docs/search.index/77822d716af7.js @@ -0,0 +1 @@ +rn_("AQIA9O8QAAABAJEBAQABAAD02g8AAAEA8gAHASOCArBQEzgAAWVzAQIA9AkTAAABAJEAAQABAwD0PhMAAAEAmAABAPOFAmJ1AQIA9FoRAAABAHsBAQDzgAJpbPMAA2J0dTowAAABAAAAAAAPABAAAABmB+8H8AfxB/IHARDGEJITFhRjFGQUZhRzFHQUmxSfFAEMAPP4EwAAAQABAAGAArAACQwAAaAgAAAM8GRuAQYA9AcTAAABAJEAAQDzAwFi9O0QAAABAJEBAQCXBgAAh7BAD9gAAaBwAAASi7CQEzYAATgJAzowAAABAAAAAAAkABAAAAC3BbgFuQW6BSgHKQchDOUM5gznDOsM7AztDNAP0Q8iECMQJxAoEEUQRhBHEEgQSRBKEEsQjhCTEL4QvxByE5YTvRNZFGUUhxSYFA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/77be044378b5.js b/pr/451/docs/search.index/77be044378b5.js deleted file mode 100644 index 070edfc34..000000000 --- a/pr/451/docs/search.index/77be044378b5.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEA84wPAAABAAEAAQMBoEAAABOJZfL5EwAAAQABAQGgIAAAEL9i8yMMAAABAAEAE4ACoBAAAAwmYWNzgISgUAAAD3IQQAw=") \ No newline at end of file diff --git a/pr/451/docs/search.index/77bf703f157b.js b/pr/451/docs/search.index/77bf703f157b.js deleted file mode 100644 index 20adbccd7..000000000 --- a/pr/451/docs/search.index/77bf703f157b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQBoEAAABOJZfL5EwAAAQABAQA7MAAAAQAABgABABwUBgDzBgFl9NATAAABAC8AAQAzAwOwYBLwAIdhcnXz/w8AAGICAQAjgAKAEPcAEAFkcg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/77e679070a1d.js b/pr/451/docs/search.index/77e679070a1d.js new file mode 100644 index 000000000..5e3d2714a --- /dev/null +++ b/pr/451/docs/search.index/77e679070a1d.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..47571b9ec --- /dev/null +++ b/pr/451/docs/search.index/780398a1ffe5.js @@ -0,0 +1 @@ +rn_("AYACsHARXAABsCAUQQABYWQBBQD0LRMAAAEAmAABAPMAAWMBlBIAAEOAA7AwE4MAAbCgFCMAAWFzeQEGAPT7EAAAAQC+AAEAk4CEoAAAABFDsSAUiAABCSAB84ACZWk=") \ No newline at end of file diff --git a/pr/451/docs/search.index/781f99b9d65a.js b/pr/451/docs/search.index/781f99b9d65a.js deleted file mode 100644 index bb96d917e..000000000 --- a/pr/451/docs/search.index/781f99b9d65a.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEBsFAPXwABY/SDBAAAAQABAMIAAQABoIAAABQSZgEAEwAA8wYBc/KvDAAAaAQzAAOgYAAAE/ZjaXPyUBEAAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/7844f0d84b02.js b/pr/451/docs/search.index/7844f0d84b02.js new file mode 100644 index 000000000..f113fc4aa --- /dev/null +++ b/pr/451/docs/search.index/7844f0d84b02.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..23d1e65f2 --- /dev/null +++ b/pr/451/docs/search.index/78878d5dabd9.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..af034525c --- /dev/null +++ b/pr/451/docs/search.index/78cce7048e1e.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..b2a999e54 --- /dev/null +++ b/pr/451/docs/search.index/78f59f62da93.js @@ -0,0 +1 @@ +rn_("AQACoCAAABN1sDARzwABaXTy2hIAAAEAAQMAOzAAAAEAAAYAAQBJEQYAAQgA9N8TAAABAIsABQATAQOwkBRIAAGwUBMkAWdnbHA7MAAAAQAAEgAJAE8QAgDSEQEAEhMBADQTAQBrEwEA0BMBAAoUAQAZFAEAWhQBAAGAArAwEcsAAbAwEc0AAXJ2AQQBsAASmgABZDswAAABAAAFAAIAxxEDAMkSAQC3BQABh7BQEpwBFrBgEyIAAZQJAzowAAABAAAAAAALABAAAADMDM0MzgyGDocO8RBzEXQRdRF2ERkTUxMjgAKgEAAADMtubwEDAPNMEAAAAQABACOAAqAAAAAL/WRyYwCEoFAAABFwoDAAABCKFAEQ8owHAAABAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/7918be142114.js b/pr/451/docs/search.index/7918be142114.js deleted file mode 100644 index 263426016..000000000 --- a/pr/451/docs/search.index/7918be142114.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9ScRAAABAAEAAQABAAGAArBgEoIAAaAgAAAQfG95AQABoMAAABOgafTYEgAACAAbABsAAQMA9PwSAAABAAEAAQABBgD0BBQAAAEAAQABAAECAPR6EAAAAQCaAAEAAQIBoEAAABKvcjswAAABAAAHAAIAeBABAA8RBQDzgAJhb2cHAACHsIASrgBZsKATJAABDDAHOzAAAAEAAA4ABAB1BAUA0gsAADoRAwAbEgMApwAAgIWg8AAAEzWwUA/UAAGgkAAAEQaUgQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/7969e190031a.js b/pr/451/docs/search.index/7969e190031a.js deleted file mode 100644 index 47aca549c..000000000 --- a/pr/451/docs/search.index/7969e190031a.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsRAT+QABsBAMBgABZW8BBQA7MAAAAQAADQABABgRDQABAgDzjA8AAAEAAQABAgD0iA8AAAEAAQABAAEDADswAAABAAAIAAEAORAIAIcHAACHsKATLQABsNATvQABoFAAABOMjBQROzAAAAEAAAYAAwAoBwAAZRADACkSAQDzgQJsdPMAAWk7MAAAAQAAiAADAHwEAABzEAAAfRGGAAEFAPPfEwAAAQA1AAEAAaBAAAAQd2Xz1AgAAAEAQQkTgAKgMAAAD2NkcvMAAWXzwgYAAAEAAQDzgAJodAEBADswAAABAACHAAIAcxAAAH0RhgATgAKgUAAAD6ZpdAEIAPRiEwAAAQCCAAUAEwCEsPAT4QAtsJATxAABsFASqgFZQIkAOzAAAAEAABIACQDuDwIAYhEBAJkSAQC6EgEA8RIBAFMTAQCKEwEAmRMBANYTAQDXAQAAhrCAETAAAaBAAAAI9wgwJjowAAABAAAAAAANABAAAACKAIsAMgc4CbsLugyFEIYQDBINEpASkRIgEyET") \ No newline at end of file diff --git a/pr/451/docs/search.index/799a0178fd53.js b/pr/451/docs/search.index/799a0178fd53.js new file mode 100644 index 000000000..56104a10f --- /dev/null +++ b/pr/451/docs/search.index/799a0178fd53.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..57aa75990 --- /dev/null +++ b/pr/451/docs/search.index/79de9416e93c.js @@ -0,0 +1 @@ +rn_("AQEBoEAAABRKZfJsFAAAAQABgAOgMAAAEoWw4BQlAAGwkBRuAAFhY22HAQCAhrAQERIAAbAQETEAAaCgAAAUWKDwAAAUezFJAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/7a61dea0bfb9.js b/pr/451/docs/search.index/7a61dea0bfb9.js deleted file mode 100644 index 82d8961cd..000000000 --- a/pr/451/docs/search.index/7a61dea0bfb9.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBoEAAABD+ZQFPDwAAAQIA8+ILAAABAAEAIwACsCAL0wABa3L2xgYAAAEAuQnnAAEAJALzAAJyePK+CAAAAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/7b1673ee6360.js b/pr/451/docs/search.index/7b1673ee6360.js deleted file mode 100644 index 261ad288b..000000000 --- a/pr/451/docs/search.index/7b1673ee6360.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQBsAASlAABczswAAABAAALAAIADhMHAPsTAwBHAAAFhqCAAAAS77EAFAgAAbBAElAAAbCAFBYAAbBQEpYAARUAFvQyDgAAAQABAAEAQwCEoCAAAAj6oNAAABL6oDAAAAjaFQAE8/IBAAABAFMF") \ No newline at end of file diff --git a/pr/451/docs/search.index/7bb203f6c904.js b/pr/451/docs/search.index/7bb203f6c904.js new file mode 100644 index 000000000..fe919ea72 --- /dev/null +++ b/pr/451/docs/search.index/7bb203f6c904.js @@ -0,0 +1 @@ +rn_("AQEAOzAAAAEAAAYAAQBJEQYAAQAA9xIJAAABAFAHAQByAAEAFQEBggKgIAAAEQygMAAAEXJpdPOAA2FncwEBAaBAAAAUH2XyTRQAAAEAI4ACsFAT0gABZnPzgAJpbw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/7bd7bca0a11f.js b/pr/451/docs/search.index/7bd7bca0a11f.js deleted file mode 100644 index 87559b73f..000000000 --- a/pr/451/docs/search.index/7bd7bca0a11f.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAAOzAAAAEAAAYAAgD2EgAAXBMFACOIArBAE80AAWVzAQQBsDASxwABY/MCEQAAAQABAAEFADswAAABAAAIAAMAcRECAG4SBACOEwAA8wEDY2Vw83kMAAABAAEAAQIA9ucIAABXAMcGAQCwAAEA84ACbnI=") \ No newline at end of file diff --git a/pr/451/docs/search.index/7be830c7f373.js b/pr/451/docs/search.index/7be830c7f373.js new file mode 100644 index 000000000..891d9d43f --- /dev/null +++ b/pr/451/docs/search.index/7be830c7f373.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..6d73ecf1e --- /dev/null +++ b/pr/451/docs/search.index/7cc498cd5f4e.js @@ -0,0 +1 @@ +rn_("AQIAOzAAAAEAABEABQCkBQAAIAwAANUMAADxDw0AVREAAAGEArDQFJQAAaCQAAATkWJoAYQCsAARYwABsAARZQABNDaXBAAAh6AgAAARwqAwAAARKaAwAAASjLBwD8oAActACDswAAABAAAQARQAtQEAAJoFAADrCAAA8QgAAA4JAgD7CwAAPAwBAGMMAQDYDAAABQ3sALIPAADHDwEAuxAAAHcRAADUEgEAtxMFANQTAABdFAAAdRQFAJIUAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/7ccfc824e9f7.js b/pr/451/docs/search.index/7ccfc824e9f7.js deleted file mode 100644 index 5efc2f249..000000000 --- a/pr/451/docs/search.index/7ccfc824e9f7.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYMCoFAAABCPoKAAABK5a3ABAAGwQBIHAAFs9DkJAAABACUHAQABCAD0GxIAAAEAAQABAAEEAbAAEGkAAXMB2g8AAAECAaBQAAASd2MBfQwAALcAAACFsEAMuAABoDAAAAvIlQABOzAAAAEAAAgABACoBAAAcwUCAIAMAQBJDwIAJwEAgIWwMAj4AAGgEAAAEu+wIAdKAAGRAEQBEAD0+xMAAAEAAQABAAEDAPQ5CQAAAQAlBwEAY4ADoAAAAAvIbnJ1AQACsNATMgABoDAAAA9MYmr2VwUAAAEAAQBJAAEAbgEBAAGwgBNRAAF0OzAAAAEAAEEABACgBQAA4wgAAO0IAAA3DD4AAQkA8zsSAAABAAEA84ADY2d0AYMCoJAAABOboJAAABOcMjMBgAKgUAAAE8OggAAAEuxjZfMAAnN08hIHAAABAAEAAaCwAAATNGLzwRIAACIBAQAjgAKgAAAAEsBkciOAAqBQAAATjWNl84ACaXkBBAD1uhAAAAEAAQABAAEA8wEBbgEIBwAAAQEAOzAAAAEAAAkAAgC3BwcAgBIBAAEGAPQmEwAAAQABAAEAAQ8A9AQUAAABAAEAAQDzgQJobQEAAbBAE8cAAWX1jxMAAAEAAQABAAEAU4ADoCAAABFWbG50120AgMugIAAAE/awYBBPAAGwQA+CAAHAGzgK") \ No newline at end of file diff --git a/pr/451/docs/search.index/7d0b131779fc.js b/pr/451/docs/search.index/7d0b131779fc.js new file mode 100644 index 000000000..0368f3cd0 --- /dev/null +++ b/pr/451/docs/search.index/7d0b131779fc.js @@ -0,0 +1 @@ +rn_("AQIA9JURAAABAAEAAQABCgD0pRMAAAEAAQABAPOBAm10AQYA81cRAAABAAEAAYICsGATKwABsNAUTQABaXITgAKwEAzWAAFlaQEFAPToEwAAAQBQAAEA8wQBY/LrEgAAAQABBQDzuxIAAAEAAQDzggJmdAEAAbAAFEQAAWT06gsAAAEAAQABAPOBAmVpNwYAAImxMBRsAAGgYAAAEVagUAAAEOqwkBL8AAGwQBDmAAG1EAcBIBQAAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/7da5ade5b83b.js b/pr/451/docs/search.index/7da5ade5b83b.js new file mode 100644 index 000000000..2ed751913 --- /dev/null +++ b/pr/451/docs/search.index/7da5ade5b83b.js @@ -0,0 +1 @@ +rn_("AQMA9G0MAAABAAEAAQABAAGwUBQjAAFyAboEAABTAYSwQBKiAAGwQBRhAAGYAAQ6MAAAAQAAAAAAKAAQAAAAhASFBIYEjgX/CAAJJQlYCcEMsw8kECUQMhBiEMEQwhDDEAwRgxLiEuMS5BLlEhETIBMhE04TTxO+E78TwxPEE8UT1RPWEz8UQBRWFFcUfRR+FA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/7dc6410c642a.js b/pr/451/docs/search.index/7dc6410c642a.js deleted file mode 100644 index 781db7b6c..000000000 --- a/pr/451/docs/search.index/7dc6410c642a.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQcA9UYTAAABAAEAcwABAAEHAPNIEAAAAQABAAGAAqAAAAAHLKAQAAAI2WR48wEBZfKgDwAAAQDnAAAAhbBgDJYAAbBADA8AATIIAfSqBAAAeAKGCQEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/7dca73af94f0.js b/pr/451/docs/search.index/7dca73af94f0.js new file mode 100644 index 000000000..82406dc3c --- /dev/null +++ b/pr/451/docs/search.index/7dca73af94f0.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..0b38ce78e --- /dev/null +++ b/pr/451/docs/search.index/7dfde7047aaf.js @@ -0,0 +1 @@ +rn_("AQABsKAStgABZTswAAABAAAJAAIA1wcHAPgSAQABAAGgIAAADNhtAXoFAAABgAKwoBK0AAGwQA++AAFsbwEAAPTyEgAAAQABAAEA8wQBZPSVEgAAAQABAAEAIwKEsKAUOwABsEAS8AABsHAT4gABFYAAAQ0UAAABAAGwgBQhAAFw8h0UAAABAEMAA6AgAAARRaBAAAASmWRlc/PgDwAASwMBAPOBAmVp1wIAAIawIBJ6AAGw0BQlAAEXAQL0mgQAAAEAAQDGAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/7e3b19027434.js b/pr/451/docs/search.index/7e3b19027434.js deleted file mode 100644 index 59c6ec1f5..000000000 --- a/pr/451/docs/search.index/7e3b19027434.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEBoEAAABEFc/XoCwAA8wQ9AQEAAQATggKgIAAADLdlcgECAPRzDwAAAQDtAAEAI4ECoBAAAAvnZWkTgAKg8AAAE41ucjMAA7AwCPUAAWVvdAFSEQAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/7e508d623850.js b/pr/451/docs/search.index/7e508d623850.js new file mode 100644 index 000000000..e21b3c17e --- /dev/null +++ b/pr/451/docs/search.index/7e508d623850.js @@ -0,0 +1 @@ +rn_("AQAAOzAAAAEAAAYAAgBxEwAA2RMFACOIArBAFFEAAWVzAQQBsDATQQABY/NrEQAAAQABAAEFADswAAABAAAIAAMA4RECAOYSBAAOFAAA8wMDY2Vw87gMAAABAAEAAQABsGAT8gABZQEsFAAA8wIBc/YWCQAAAQABABIIPwAhAQECAaBQAAARanIBCxEAAPOAA2ZtcA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/7e644c6ffb35.js b/pr/451/docs/search.index/7e644c6ffb35.js deleted file mode 100644 index 4ba1fce49..000000000 --- a/pr/451/docs/search.index/7e644c6ffb35.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQA9rUMAAABABkDAQAlAQEAAQoA80ISAAABAKgAAQEA9+4IAAABABIHAQBrAAEADgEjgAKwYBNVAAFpbwECAPR2DAAAAQALBgEAAQUA9GsTAAABAEkAAQD3BAAAh6AgAAAMeLCQEW8AAQScAzowAAABAAAAAAAIABAAAABvBd8I4AgWDBcMfRB+EHMSdBI=") \ No newline at end of file diff --git a/pr/451/docs/search.index/7e7ee16d46d1.js b/pr/451/docs/search.index/7e7ee16d46d1.js new file mode 100644 index 000000000..fcf15af48 --- /dev/null +++ b/pr/451/docs/search.index/7e7ee16d46d1.js @@ -0,0 +1 @@ +rn_("AQIBoQAAABRlZPJUBwAACAIBBgGgMAAAEdR0AZITAAABAwGwUBLcAAFl9QYJAABPAwEACQQBAAEBAaEAAAAUWWTyqAUAADwCFwcAAIewoBLaAAGg4AAAFCywEAWpAAEegArzWQQAAAEAAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/7ebdb25ba28a.js b/pr/451/docs/search.index/7ebdb25ba28a.js deleted file mode 100644 index 0ceddae56..000000000 --- a/pr/451/docs/search.index/7ebdb25ba28a.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9GsTAAABAEkAAQDzBAFj8nMSAAABACOCArBQEkUAAWZ0AQABsAATwAABZPS1CwAAAQABAAEA84ACZWkTgAKw4BPLAAF0dg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/7f6612368049.js b/pr/451/docs/search.index/7f6612368049.js deleted file mode 100644 index 4be9dcc7f..000000000 --- a/pr/451/docs/search.index/7f6612368049.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQkBoEAAABPGZfLmEwAAAQABgAKwAAyTAAGggAAAEzFkcAEDAaAAAAAPv3PyTQ8AAAEAAQIDsGASsQABsNATyQABoIAAABNQaXJzAdQTAAD3AACAhaEwAAAT5ZBJAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/7f9dc1f44330.js b/pr/451/docs/search.index/7f9dc1f44330.js new file mode 100644 index 000000000..dd31b70ed --- /dev/null +++ b/pr/451/docs/search.index/7f9dc1f44330.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..3c8b4d1a1 --- /dev/null +++ b/pr/451/docs/search.index/7f9fb0e71342.js @@ -0,0 +1 @@ +rn_("AQIAOjAAAAEAAAAAAAgAEAAAAAgMCQyRD5IP0g/AEOUQcxN0EwEAAPQqCQAAAQABAAEAAQUBsDAS/gABaPPsEwAAAQByABODAqAwAAAQs2NwAQMA9NAPAAABAO0AAQABgQKgAAAAEW6gEAAAD69ldfOAAmFyAQAA9KMRAAABAAEAAQABgAKgkAAAE9awUBKIAAFid/OAAmR0E4ACsJATFwABbnLzAAJldPSqEAAAAQABAAEAAQEA9WgMAAABAAEAAQABABOAArCwE9IAAWFvIwACsEAM6QABZXQ7MAAAAQAACQAEALYEAQB4BQEASgwBAJURAwABAgA7MAAAAQAACQACANcHBwD4EgEAAYACsJATYQABoKAAABPVZXYTgAKxgBSoAAFjbQGCArBAFAAAAaBQAAAR0WJmE4ACoEAAABQsZHMjgAKg0AAAFHxjbvOAhBEBQAEFAbBAEzwAAWUBhxIAAEOCA6EAAAAUiqDQAAAUXWttcxOAArBgEMkAAWFyAQUAOzAAAAEAAIwAAgDcEAAA7RGLAAGCA7AgEvoAAbAgEGMAAbBAEV4AAWtsc1OAA7AgFGEAAWt0dQGJhKCAAAAUm6BAAAAURqBgAAAUZqCQAAAUnwKAChOCA6CgAAAT1LDQFD8AAWNpdQEBAPSfEQAAAQABAAEAQ4ADsXAUpgABoNAAABQVY29zI4ACsGAQxwABaW8BAAD0EgwAAAEAUwABACOBAqDgAAAUMmV5AQ0AOjAAAAEAAAAAAAcAEAAAADQTNRPQE9ETChQLFFoUWxQBgQKwQBNUAAGgwAAAFBRlbwECADowAAABAAAAAAAIABAAAADHD8gPdxHUEtUS1BNdFJIUkxTzAQFvOzAAAAEAAAkABABPCQEAhRADAMcQAQDSEwEA84ADbW5yAQAAOzAAAAEAAAYAAgBxEwAA2RMFAEOAA7BAFFEAAaAgAAAM6GVycyMAArAwE7UAAWNy8p8TAAABAAGBArCAEwAAAaEAAAAUXGNmAQsA9F0TAAABAAEAAQABgAKgAAAAEHugAAAAEHw0NvMCAXYB7wwAAPMAAmRsAQoJAADX/jkAkrCgE/4AAbBwEcMAAbCAEt4AAaCwAAATGZ3dfzswAAABAAAXAAsAqAEBALIEAwB1BQIA8AYBAPoGAgAjDAAA3w8AAEQRAACzEQIAjRICAEMUAAA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/desc/59e6f7c5fb07.js b/pr/451/docs/search.index/desc/59e6f7c5fb07.js deleted file mode 100644 index 499a4a444..000000000 --- a/pr/451/docs/search.index/desc/59e6f7c5fb07.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 stream00AfWhich path is now openAhWith path was abandoned.B`Which path had its state droppedAmPath which has changed statusCfPath over which the observed address was reported, \xe2\x80\xa6AlWhich stream is now readableAlWhich stream is now writableAnWhich stream has been finishedAmWhich stream has been stoppedAnType of the Long header packetBcExperimental! Use at your own risk.0ClWhether 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\xa600: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.777BfQUIC transport protocol implementationCkGet SystemTime::now() or the mocked equivalentAhLook up the current time01ChCurrent best estimate of this connection\xe2\x80\x99s latency \xe2\x80\xa6000ChThe 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 pathCjThe source address of the datagram(s) contained in the \xe2\x80\xa6mType of errorBlCurrent congestion window of the connection.0gPayloadAoReturns the argument unchanged.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000BaCalls U::from(self).00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dNextCoMethod 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\xa6BiConstruct a fresh Controller0AiThe contents of the chunkCiThe total amount of bytes which have been transferred \xe2\x80\xa6BjThe amount of bytes which had been written210BnUnderlying 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.2BcTitle 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\xa6CeChunks returned from RecvStream::read().AmThe initiator of a connectionAdRun as a perf client1CgAttempted to initiate NAT traversal on a closed, or \xe2\x80\xa6BcThe receiver lagged too far behind.CcFuture produced by Connection::open_biAjA new path has been openedChOne 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\xa61C`The amount of full chunks which had been written0BlAllows 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 valueBcHuman-readable reason for the close000BhHuman-readable explanation of the reasonBeReason that the connection was closedBcReason why this path was abandoned.AjSwitch 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 remoteCfThe size of a single datagram in the associated bufferCkStatistics about datagrams and bytes received on this path.ChStatistics about UDP datagrams received on a connection.10CgStatistics about datagrams and bytes sent on this path.CkStatistics about UDP datagrams transmitted on a connection.10AnMethod 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\xa6ClIndicates how many bytes and chunks had been transferred \xe2\x80\xa60l0-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.CaStatistics about frames received on a connection.10CaStatistics about frames transmitted on this path.CdStatistics about frames transmitted on a 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\xa6000BnReturns 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.CfFuture produced by Connection::accept_uniCdPaths marked with as available will be used when \xe2\x80\xa6CnAt least one new stream of a certain directionality may be \xe2\x80\xa6ClConfiguration for the Bbr congestion controller0BkThe connection was successfully establishedAoAPI to control datagram trafficAlDecoding of QUIC primitives.CkA path was discarded and all remaining state for it has \xe2\x80\xa6BgInfallible 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 closedAnApplication events about pathsBjStatistics 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\xa6A`Opens a new pathBmPing 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 timeCkError indicating that a path has not been opened or has \xe2\x80\xa6BnThe path is already closed or was never opened1BeIn-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 statusBeConfiguration 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\xa60CiCloses 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 amount of bytes lost on this path.0CjPad UDP datagrams carrying application data to current \xe2\x80\xa60AgReturns path statistics0CjThe final path stats, they are no longer available via \xe2\x80\xa6C`Attempt to write bytes from buf into the stream.AmRead the next segment of dataAoReads 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 controller0CdReasons 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\xa60AdPacing rate (bits/s)0DeA stream of PathEvents for all paths in this connection.DfGets the local PathStatus for a known PathIdAnRead the next segments of dataCkConvenience 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.0DbWrite a single Bytes into this stream in its entiretyBbAn 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\xa6Akn0\xe2\x80\x99s nat traversal eventsDhNull implementation of TokenLog, which never accepts tokens0ClReceived an observation of our external address from the \xe2\x80\xa6CiFuture produced by Connection::read_datagramBiThe remote changed the status of the pathBlRTT 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 IDs0BlIf the connection is closed, the reason 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 amount of packets lost on this path.0DcMaximum 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\xa600ChThe 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\xa6000DgWrite a slice of Bytes into this stream, returning how \xe2\x80\xa6AmSend 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\xa610CnAttempts to read from the stream into the provided buffer, \xe2\x80\xa6AkReturns 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 pathAgThe 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 update0BkConfigures 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 address0BgThe 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 packetCaThe 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 pathsClThe 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\xa6CmWe didn\xe2\x80\x99t receive a path response in time after opening \xe2\x80\xa6CaPath could not be validated and will be abandonedBcA 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\xa60CfGet 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\xa60DdWrite a slice of Bytes into this stream in its entiretyCfthe application or application protocol caused the \xe2\x80\xa60AnThe peer closed the connectionBoThe path was closed locally by the application.1B`Constructs controllers on demand0BdCommon congestion controller metrics0ClThe 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\xa60Cmthe 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 statusAjTransport-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 pathCkTransport 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\xa60CiThe remote SocketAddr supplied was malformedCkThe remote address for the path is not supported by the \xe2\x80\xa61ChThe 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-sideDmError 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 negotiatedCnError 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.C`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\xa6CnCreate 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 lost0CnRemoves 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 timeout0BoThe key exchange group negotiated with the peer0CbWhether 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\xa60AcNat traversal draftCkSets the maximum number of nat traversal addresses this \xe2\x80\xa60CjWhen multipath is required and has not been explicitly \xe2\x80\xa6") \ 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 new file mode 100644 index 000000000..da822e76c --- /dev/null +++ b/pr/451/docs/search.index/desc/791dbc1ca79a.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.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/7780e033398e.js b/pr/451/docs/search.index/entry/7780e033398e.js deleted file mode 100644 index 99bf12d66..000000000 --- a/pr/451/docs/search.index/entry/7780e033398e.js +++ /dev/null @@ -1 +0,0 @@ -rd_("Af[463,15,464,0,126,0,0]Ak[3096,15,3097,3097,127,0,0]Ag[463,15,464,0,4085,0,0]Al[3096,15,3097,1784,4086,0,0]Al[1985,15,1986,1986,4087,0,0]Al[3096,15,1689,1689,1433,0,0]Ao[1195,13,1196,1196,3928,3037,0]Am[463,13,464,3640,4749,3037,0]Am[463,13,464,4142,1418,3037,0]Al[463,13,464,1094,503,3037,0]Am[463,13,464,1094,4359,3037,0]Am[463,13,464,4034,4352,3037,0]Am[463,13,464,4034,3044,3037,0]Am[463,13,464,4034,3239,3037,0]Am[463,13,464,4034,4351,3037,0]Am[463,13,464,4039,3257,3037,0]Am[463,13,464,4039,4120,3037,0]Aj[463,13,464,0,1443,3037,0]Aj[463,13,464,0,4915,3037,0]Aj[463,13,464,0,1152,3037,0]Aj[463,13,464,0,1426,3037,0]Aj[463,13,464,0,2294,3037,0]Aj[463,13,464,0,3217,3037,0]Aj[463,13,464,0,3041,3037,0]Aj[463,13,464,0,1867,3037,0]Aj[463,13,464,0,4071,3037,0]Aj[463,13,464,0,4444,3037,0]Aj[463,13,464,0,3941,3037,0]Aj[463,13,464,0,3931,3037,0]Aj[463,13,464,0,3025,3037,0]Aj[463,13,464,0,4441,3037,0]Aj[463,13,464,0,4642,3037,0]Aj[463,13,464,0,4077,3037,0]Aj[463,13,464,0,4080,3037,0]Aj[463,13,464,0,4085,3037,0]Aj[463,13,464,0,4808,3037,0]Ai[463,13,464,0,510,3037,0]Ai[463,13,464,0,126,3037,0]Aj[463,13,464,0,2281,3037,0]Ak[463,13,1693,0,5064,3037,0]Ak[463,13,1773,0,1169,3037,0]Ao[3096,13,3097,1810,1444,3037,0]Ao[3096,13,3097,1810,4916,3037,0]Ao[3096,13,3097,3061,1153,3037,0]Ao[3096,13,3097,3070,4750,3037,0]Ao[3096,13,3097,1366,1427,3037,0]Ao[3096,13,3097,1366,3233,3037,0]Ao[3096,13,3097,1366,3040,3037,0]Ao[3096,13,3097,1366,4748,3037,0]Ao[3096,13,3097,1366,4804,3037,0]Ao[3096,13,3097,1366,3207,3037,0]Ao[3096,13,3097,1381,2295,3037,0]Ao[3096,13,3097,1381,3218,3037,0]Ao[3096,13,3097,1381,3042,3037,0]Ao[3096,13,3097,1120,3045,3037,0]Ao[3096,13,3097,1120,4241,3037,0]Ao[3096,13,3097,1125,1868,3037,0]Ao[3096,13,3097,1125,3258,3037,0]Ao[3096,13,3097,1125,3940,3037,0]Ao[3096,13,3097,2002,3959,3037,0]Ao[3096,13,3097,2002,4353,3037,0]Ao[3096,13,3097,2002,4075,3037,0]Ao[3096,13,3097,3641,4445,3037,0]Ao[3096,13,3097,3641,3038,3037,0]Ao[3096,13,3097,3641,4319,3037,0]Ao[3096,13,3097,3194,3942,3037,0]Ao[3096,13,3097,1691,3932,3037,0]Ao[3096,13,3097,1356,3026,3037,0]Ao[3096,13,3097,1356,4334,3037,0]Ao[3096,13,3097,1356,3224,3037,0]Ao[3096,13,3097,1356,3247,3037,0]Ao[3096,13,3097,1356,4082,3037,0]Ao[3096,13,3097,1356,4443,3037,0]Ao[3096,13,3097,1356,4644,3037,0]Ao[3096,13,3097,2305,4648,3037,0]Ao[3096,13,3097,2305,4078,3037,0]Ao[3096,13,3097,1756,2267,3037,0]Ao[3096,13,3097,1756,4746,3037,0]Ao[3096,13,3097,1784,4081,3037,0]Ao[3096,13,3097,1784,4086,3037,0]Al[3096,13,3097,0,4357,3037,0]Al[3096,13,3097,0,4809,3037,0]An[3096,13,3097,3097,511,3037,0]An[3096,13,3097,3097,127,3037,0]Ao[3096,13,3097,3097,2282,3037,0]Ao[3096,13,3097,3097,3024,3037,0]Ao[3096,13,1689,1689,4247,3037,0]Ao[3096,13,4957,4957,4855,3037,0]Ao[3096,13,4957,4957,1171,3037,0]Ao[3096,13,1694,1694,5065,3037,0]Al[3096,13,1774,0,1170,3037,0]Ao[1985,13,1986,1986,4087,3037,0]Ai[463,13,464,1094,503,0,0]Aj[463,13,464,1094,4359,0,0]Aj[463,13,464,4034,3237,0,0]Aj[463,13,464,4034,4856,0,0]Aj[463,13,464,4039,3243,0,0]Ai[3096,14,3039,0,1420,0,0]Ai[3096,14,3039,0,3002,0,0]Ai[3096,14,3039,0,3020,0,0]Ai[3096,14,3039,0,4107,0,0]Ai[3096,14,3039,0,4100,0,0]Ai[3096,14,3958,0,2277,0,0]Ai[3096,14,3958,0,2297,0,0]Ai[3096,14,3958,0,2261,0,0]Ai[3096,14,3958,0,1855,0,0]Ae[1195,7,1196,0,0,0,0]Ah[3096,14,4456,0,499,0,0]Ag[463,15,464,0,3025,0,0]Al[3096,15,3097,1356,3026,0,0]Af[463,5,3635,148,0,0,0]Ag[3096,5,3636,148,0,0,0]Al[1095,15,1096,1096,4848,0,0]Ae[2358,5,2359,0,0,0,0]Af[463,6,464,3097,0,0,0]Ae[3096,6,3097,0,0,0,0]Al[3096,15,1689,1689,1433,0,0]Ag[463,19,464,0,1443,0,0]Ag[463,19,464,0,1426,0,0]Al[3096,19,3097,1810,1444,0,0]Al[3096,19,3097,1366,1427,0,0]Ae[1195,5,1196,0,0,0,0]Ae[1095,5,1683,0,0,0,0]Ae[1095,5,1779,0,0,0,0]Af[463,15,464,0,126,0,0]Ak[3096,15,3097,3097,127,0,0]Ai[463,13,464,0,2294,117,0]Ai[463,13,464,0,3217,117,0]Ai[463,13,464,0,4448,117,0]An[3096,13,3097,1381,2295,117,0]An[3096,13,3097,1381,3218,117,0]An[3096,13,3097,1381,4449,117,0]Ai[463,13,464,0,1443,137,0]Ai[463,13,464,0,1426,137,0]Ai[463,13,464,0,3941,137,0]Ai[463,13,464,0,4080,137,0]Ah[463,13,464,0,510,137,0]Ah[463,13,464,0,126,137,0]Ai[463,13,464,0,2281,137,0]An[3096,13,3097,1810,1444,137,0]An[3096,13,3097,3070,4750,137,0]An[3096,13,3097,1366,1427,137,0]An[3096,13,3097,1120,3045,137,0]An[3096,13,3097,1120,4241,137,0]An[3096,13,3097,1125,3258,137,0]An[3096,13,3097,3194,3942,137,0]An[3096,13,3097,2305,4648,137,0]An[3096,13,3097,1756,4746,137,0]An[3096,13,3097,1784,4081,137,0]Am[3096,13,3097,3097,511,137,0]Am[3096,13,3097,3097,127,137,0]An[3096,13,3097,3097,2282,137,0]Ag[463,13,464,0,2281,0,0]Al[3096,13,3097,3097,2282,0,0]Ai[3096,14,3958,0,1421,0,0]Ai[3096,14,3958,0,3009,0,0]Ag[463,14,464,0,2291,0,0]Al[3096,14,3097,3097,2292,0,0]Al[1985,14,1986,1986,2279,0,0]Al[1985,14,1986,1986,2293,0,0]An[1195,13,1196,1196,134,1159,0]Ao[1195,13,1196,1196,3952,1159,0]Ao[1195,13,1196,1196,3928,1159,0]Ao[1195,13,1380,1380,4354,1159,0]Ao[3966,13,3967,3967,5006,1159,0]Am[463,13,464,4142,4912,1159,0]Am[463,13,464,3640,3209,1159,0]Am[463,13,464,3640,3211,1159,0]Am[463,13,464,3640,4918,1159,0]D`[463,13,464,3640,4749,1159,0,\"impl-Debug-for-SendDatagramError\"]Db[463,13,464,3640,4749,1827,0,\"impl-Display-for-SendDatagramError\"]Am[463,13,464,2304,2259,1159,0]Am[463,13,464,2304,4215,1159,0]Cg[463,13,464,4142,1418,1827,0,\"impl-Display-for-Lagged\"]Ce[463,13,464,4142,1418,1159,0,\"impl-Debug-for-Lagged\"]Am[463,13,464,4142,3231,1159,0]Am[463,13,464,4142,4849,1159,0]Am[463,13,464,2336,2263,1159,0]Ck[463,13,464,2336,3240,1827,0,\"impl-Display-for-RetryError\"]Ci[463,13,464,2336,3240,1159,0,\"impl-Debug-for-RetryError\"]Am[463,13,464,2336,4333,1159,0]Al[463,13,464,1094,503,1159,0]Am[463,13,464,1094,4359,1159,0]Am[463,13,464,4034,3237,1159,0]Am[463,13,464,4034,4856,1159,0]Co[463,13,464,4034,4352,1827,0,\"impl-Display-for-ReadToEndError\"]Cm[463,13,464,4034,4352,1159,0,\"impl-Debug-for-ReadToEndError\"]Cj[463,13,464,4034,3044,1827,0,\"impl-Display-for-ReadError\"]Ch[463,13,464,4034,3044,1159,0,\"impl-Debug-for-ReadError\"]Ci[463,13,464,4034,3239,1159,0,\"impl-Debug-for-ResetError\"]Ck[463,13,464,4034,3239,1827,0,\"impl-Display-for-ResetError\"]Cm[463,13,464,4034,4351,1159,0,\"impl-Debug-for-ReadExactError\"]Co[463,13,464,4034,4351,1827,0,\"impl-Display-for-ReadExactError\"]Am[463,13,464,1393,4121,1159,0]Am[463,13,464,1137,3955,1159,0]Am[463,13,464,4039,3243,1159,0]Ci[463,13,464,4039,3257,1159,0,\"impl-Debug-for-WriteError\"]Ck[463,13,464,4039,3257,1827,0,\"impl-Display-for-WriteError\"]Cm[463,13,464,4039,4120,1827,0,\"impl-Display-for-StoppedError\"]Ck[463,13,464,4039,4120,1159,0,\"impl-Debug-for-StoppedError\"]Am[463,13,464,4039,1857,1159,0]Cd[463,13,464,0,1443,1827,0,\"impl-Display-for-VarInt\"]Cb[463,13,464,0,1443,1159,0,\"impl-Debug-for-VarInt\"]D`[463,13,464,0,4915,1159,0,\"impl-Debug-for-VarIntBoundsExceeded\"]Db[463,13,464,0,4915,1827,0,\"impl-Display-for-VarIntBoundsExceeded\"]Aj[463,13,464,0,1152,1159,0]Cb[463,13,464,0,1426,1159,0,\"impl-Debug-for-PathId\"]Cd[463,13,464,0,1426,1827,0,\"impl-Display-for-PathId\"]Aj[463,13,464,0,2294,1159,0]Aj[463,13,464,0,3217,1159,0]Aj[463,13,464,0,3041,1159,0]Aj[463,13,464,0,4448,1159,0]Aj[463,13,464,0,1867,1159,0]Cj[463,13,464,0,4071,1827,0,\"impl-Display-for-ClosedStream\"]Ch[463,13,464,0,4071,1159,0,\"impl-Debug-for-ClosedStream\"]Ck[463,13,464,0,4444,1159,0,\"impl-Debug-for-ConnectionError\"]Cm[463,13,464,0,4444,1827,0,\"impl-Display-for-ConnectionError\"]Aj[463,13,464,0,4458,1159,0]Aj[463,13,464,0,4467,1159,0]Aj[463,13,464,0,4773,1159,0]Aj[463,13,464,0,4788,1159,0]Aj[463,13,464,0,3941,1159,0]Aj[463,13,464,0,4328,1159,0]Aj[463,13,464,0,4114,1159,0]Aj[463,13,464,0,4972,1159,0]Aj[463,13,464,0,4065,1159,0]Cg[463,13,464,0,3931,1159,0,\"impl-Debug-for-ConfigError\"]Ci[463,13,464,0,3931,1827,0,\"impl-Display-for-ConfigError\"]Cg[463,13,464,0,3025,1827,0,\"impl-Display-for-FrameType\"]Ce[463,13,464,0,3025,1159,0,\"impl-Debug-for-FrameType\"]Cm[463,13,464,0,4441,1827,0,\"impl-Display-for-ConnectionClose\"]Ck[463,13,464,0,4441,1159,0,\"impl-Debug-for-ConnectionClose\"]Cn[463,13,464,0,4642,1827,0,\"impl-Display-for-ApplicationClose\"]Cl[463,13,464,0,4642,1159,0,\"impl-Debug-for-ApplicationClose\"]Ch[463,13,464,0,4077,1159,0,\"impl-Debug-for-ConnectError\"]Cj[463,13,464,0,4077,1827,0,\"impl-Display-for-ConnectError\"]Cj[463,13,464,0,4080,1827,0,\"impl-Display-for-ConnectionId\"]Ch[463,13,464,0,4080,1159,0,\"impl-Debug-for-ConnectionId\"]Aj[463,13,464,0,4085,1159,0]Cn[463,13,464,0,4808,1159,0,\"impl-Debug-for-TransportErrorCode\"]D`[463,13,464,0,4808,1827,0,\"impl-Display-for-TransportErrorCode\"]Aj[463,13,464,0,3220,1159,0]Aj[463,13,464,0,4675,1159,0]Ai[463,13,464,0,510,1159,0]C`[463,13,464,0,126,1827,0,\"impl-Display-for-Dir\"]Bn[463,13,464,0,126,1159,0,\"impl-Debug-for-Dir\"]Cf[463,13,464,0,2281,1827,0,\"impl-Display-for-StreamId\"]Cd[463,13,464,0,2281,1159,0,\"impl-Debug-for-StreamId\"]Aj[463,13,464,0,2291,1159,0]Aj[463,13,3635,0,120,1159,0]Ak[463,13,3635,0,3012,1159,0]Ak[463,13,3635,0,1156,1159,0]Ak[463,13,3635,0,3936,1159,0]Ak[463,13,3635,0,1840,1159,0]Ak[463,13,3635,0,4233,1159,0]Ak[463,13,3635,0,4737,1159,0]Ak[463,13,1693,0,5064,1159,0]Ak[463,13,1693,0,3934,1159,0]Ak[463,13,1693,0,4812,1159,0]Cb[463,13,1773,0,1169,1159,0,\"impl-Debug-for-Error\"]Cd[463,13,1773,0,1169,1827,0,\"impl-Display-for-Error\"]Da[463,13,1773,0,4910,1159,0,\"impl-Debug-for-NoInitialCipherSuite\"]Dc[463,13,1773,0,4910,1827,0,\"impl-Display-for-NoInitialCipherSuite\"]Ao[3096,13,3097,3641,3212,1159,0]Ao[3096,13,3097,2305,2260,1159,0]Ci[3096,13,3097,1810,1444,1827,0,\"impl-Display-for-VarInt\"]Cg[3096,13,3097,1810,1444,1159,0,\"impl-Debug-for-VarInt\"]De[3096,13,3097,1810,4916,1159,0,\"impl-Debug-for-VarIntBoundsExceeded\"]Dg[3096,13,3097,1810,4916,1827,0,\"impl-Display-for-VarIntBoundsExceeded\"]Ao[3096,13,3097,3061,1153,1159,0]Db[3096,13,3097,3070,4750,1159,0,\"impl-Debug-for-SendDatagramError\"]Dd[3096,13,3097,3070,4750,1827,0,\"impl-Display-for-SendDatagramError\"]Ci[3096,13,3097,1366,1427,1827,0,\"impl-Display-for-PathId\"]Cg[3096,13,3097,1366,1427,1159,0,\"impl-Debug-for-PathId\"]Ao[3096,13,3097,1366,4110,1159,0]Ao[3096,13,3097,1366,3233,1159,0]Ao[3096,13,3097,1366,3040,1159,0]Ao[3096,13,3097,1366,4748,1159,0]Dc[3096,13,3097,1366,4804,1159,0,\"impl-Debug-for-SetPathStatusError\"]De[3096,13,3097,1366,4804,1827,0,\"impl-Display-for-SetPathStatusError\"]Cm[3096,13,3097,1366,3207,1827,0,\"impl-Display-for-ClosedPath\"]Ck[3096,13,3097,1366,3207,1159,0,\"impl-Debug-for-ClosedPath\"]Ao[3096,13,3097,1381,2295,1159,0]Ao[3096,13,3097,1381,3218,1159,0]Ao[3096,13,3097,1381,3042,1159,0]Ao[3096,13,3097,1381,4449,1159,0]Cj[3096,13,3097,1120,3045,1159,0,\"impl-Debug-for-ReadError\"]Cl[3096,13,3097,1120,3045,1827,0,\"impl-Display-for-ReadError\"]D`[3096,13,3097,1120,4241,1827,0,\"impl-Display-for-ReadableError\"]Cn[3096,13,3097,1120,4241,1159,0,\"impl-Debug-for-ReadableError\"]Ao[3096,13,3097,1125,1868,1159,0]Cm[3096,13,3097,1125,3258,1827,0,\"impl-Display-for-WriteError\"]Ck[3096,13,3097,1125,3258,1159,0,\"impl-Debug-for-WriteError\"]Cn[3096,13,3097,1125,3940,1827,0,\"impl-Display-for-FinishError\"]Cl[3096,13,3097,1125,3940,1159,0,\"impl-Debug-for-FinishError\"]Ao[3096,13,3097,2002,3959,1159,0]Ao[3096,13,3097,2002,4353,1159,0]Co[3096,13,3097,2002,4075,1827,0,\"impl-Display-for-ClosedStream\"]Cm[3096,13,3097,2002,4075,1159,0,\"impl-Debug-for-ClosedStream\"]Db[3096,13,3097,3641,4445,1827,0,\"impl-Display-for-ConnectionError\"]D`[3096,13,3097,3641,4445,1159,0,\"impl-Debug-for-ConnectionError\"]Cl[3096,13,3097,3641,3038,1827,0,\"impl-Display-for-PathError\"]Cj[3096,13,3097,3641,3038,1159,0,\"impl-Debug-for-PathError\"]Da[3096,13,3097,3641,4319,1827,0,\"impl-Display-for-ClosePathError\"]Co[3096,13,3097,3641,4319,1159,0,\"impl-Debug-for-ClosePathError\"]Dg[3096,13,3097,3641,5001,1159,0,\"impl-Debug-for-MultipathNotNegotiated\"]Di[3096,13,3097,3641,5001,1827,0,\"impl-Display-for-MultipathNotNegotiated\"]Ao[3096,13,3097,3641,1174,1159,0]Ao[3096,13,3097,1116,4459,1159,0]Ao[3096,13,3097,3194,4468,1159,0]Ao[3096,13,3097,3194,4774,1159,0]Ao[3096,13,3097,3194,4789,1159,0]Ao[3096,13,3097,3194,3942,1159,0]Ao[3096,13,3097,1691,4329,1159,0]Ao[3096,13,3097,1691,4116,1159,0]Ao[3096,13,3097,1691,4973,1159,0]Ao[3096,13,3097,1691,4067,1159,0]Cl[3096,13,3097,1691,3932,1159,0,\"impl-Debug-for-ConfigError\"]Cn[3096,13,3097,1691,3932,1827,0,\"impl-Display-for-ConfigError\"]Cl[3096,13,3097,1356,3026,1827,0,\"impl-Display-for-FrameType\"]Cj[3096,13,3097,1356,3026,1159,0,\"impl-Debug-for-FrameType\"]Da[3096,13,3097,1356,4334,1827,0,\"impl-Display-for-InvalidFrameId\"]Co[3096,13,3097,1356,4334,1159,0,\"impl-Debug-for-InvalidFrameId\"]Cm[3096,13,3097,1356,3224,1827,0,\"impl-Display-for-MaybeFrame\"]Ck[3096,13,3097,1356,3224,1159,0,\"impl-Debug-for-MaybeFrame\"]Cm[3096,13,3097,1356,3247,1827,0,\"impl-Display-for-StreamInfo\"]Ck[3096,13,3097,1356,3247,1159,0,\"impl-Debug-for-StreamInfo\"]Cm[3096,13,3097,1356,4082,1159,0,\"impl-Debug-for-DatagramInfo\"]Co[3096,13,3097,1356,4082,1827,0,\"impl-Display-for-DatagramInfo\"]Db[3096,13,3097,1356,4443,1827,0,\"impl-Display-for-ConnectionClose\"]D`[3096,13,3097,1356,4443,1159,0,\"impl-Debug-for-ConnectionClose\"]Dc[3096,13,3097,1356,4644,1827,0,\"impl-Display-for-ApplicationClose\"]Da[3096,13,3097,1356,4644,1159,0,\"impl-Debug-for-ApplicationClose\"]Ci[3096,13,3097,1356,2253,1159,0,\"impl-Debug-for-Datagram\"]Ck[3096,13,3097,1356,2253,1827,0,\"impl-Display-for-Datagram\"]Ao[3096,13,3097,2305,4648,1159,0]Ao[3096,13,3097,2305,2264,1159,0]Co[3096,13,3097,2305,4078,1827,0,\"impl-Display-for-ConnectError\"]Cm[3096,13,3097,2305,4078,1159,0,\"impl-Debug-for-ConnectError\"]Ao[3096,13,3097,2305,3926,1159,0]Cm[3096,13,3097,2305,3241,1827,0,\"impl-Display-for-RetryError\"]Ck[3096,13,3097,2305,3241,1159,0,\"impl-Debug-for-RetryError\"]Ao[3096,13,3097,1756,4236,1159,0]Ao[3096,13,3097,1756,4457,1159,0]Ao[3096,13,3097,1756,5005,1159,0]Ao[3096,13,3097,1756,2267,1159,0]Dd[3096,13,3097,1756,4746,1827,0,\"impl-Display-for-PacketDecodeError\"]Db[3096,13,3097,1756,4746,1159,0,\"impl-Debug-for-PacketDecodeError\"]Ao[3096,13,3097,1784,4447,1159,0]Ao[3096,13,3097,1784,4214,1159,0]Co[3096,13,3097,1784,4081,1827,0,\"impl-Display-for-ConnectionId\"]Cm[3096,13,3097,1784,4081,1159,0,\"impl-Debug-for-ConnectionId\"]Ao[3096,13,3097,1784,4086,1159,0]Ce[3096,13,3097,0,4357,1827,0,\"impl-Display-for-Error\"]Cc[3096,13,3097,0,4357,1159,0,\"impl-Debug-for-Error\"]Cd[3096,13,3097,0,4809,1827,0,\"impl-Display-for-Code\"]Cb[3096,13,3097,0,4809,1159,0,\"impl-Debug-for-Code\"]Ao[3096,13,3097,4254,3221,1159,0]Ao[3096,13,3097,4254,5099,1159,0]Ao[3096,13,3097,4844,4676,1159,0]An[3096,13,3097,3097,511,1159,0]Cc[3096,13,3097,3097,127,1159,0,\"impl-Debug-for-Dir\"]Ce[3096,13,3097,3097,127,1827,0,\"impl-Display-for-Dir\"]Ci[3096,13,3097,3097,2282,1159,0,\"impl-Debug-for-StreamId\"]Ck[3096,13,3097,3097,2282,1827,0,\"impl-Display-for-StreamId\"]Ao[3096,13,3097,3097,2292,1159,0]Cl[3096,13,3097,3097,3024,1827,0,\"impl-Display-for-FourTuple\"]Cj[3096,13,3097,3097,3024,1159,0,\"impl-Debug-for-FourTuple\"]Cn[3096,13,1689,1689,4247,1159,0,\"impl-Debug-for-UnexpectedEnd\"]D`[3096,13,1689,1689,4247,1827,0,\"impl-Display-for-UnexpectedEnd\"]Ao[3096,13,4957,4957,4855,1159,0]Ch[3096,13,4957,4957,1171,1827,0,\"impl-Display-for-Error\"]Cf[3096,13,4957,4957,1171,1159,0,\"impl-Debug-for-Error\"]Ao[3096,13,1694,1694,5065,1159,0]Ao[3096,13,1694,1694,3935,1159,0]Ao[3096,13,1694,1694,4815,1159,0]Cc[3096,13,1774,0,1170,1159,0,\"impl-Debug-for-Error\"]Ce[3096,13,1774,0,1170,1827,0,\"impl-Display-for-Error\"]Dg[3096,13,1774,1774,4911,1827,0,\"impl-Display-for-NoInitialCipherSuite\"]De[3096,13,1774,1774,4911,1159,0,\"impl-Debug-for-NoInitialCipherSuite\"]Am[3096,13,3636,148,121,1159,0]An[3096,13,3636,148,3013,1159,0]Ao[3096,13,3636,1352,1157,1159,0]Ao[3096,13,3636,1352,3937,1159,0]Ao[3096,13,3636,2358,1841,1159,0]Ao[3096,13,3636,2358,4234,1159,0]Ao[3096,13,3636,3636,4738,1159,0]Ch[3096,13,4706,4706,1172,1827,0,\"impl-Display-for-Error\"]Cf[3096,13,4706,4706,1172,1159,0,\"impl-Debug-for-Error\"]Ao[3096,13,4706,4706,1175,1159,0]An[1985,13,1986,416,4358,1159,0]Ao[1985,13,1986,1986,2279,1159,0]Ao[1985,13,1986,1986,2293,1159,0]Ao[1985,13,1986,1986,4087,1159,0]Aj[463,13,464,4142,4912,0,0]Al[3096,13,3097,1366,4110,0,0]Ag[463,14,464,0,2294,0,0]Al[3096,14,3097,1381,2295,0,0]Al[3096,13,3097,1756,4236,0,0]Al[3096,14,3097,1756,5005,0,0]Ah[3096,14,4456,0,499,0,0]Al[1985,14,1986,1986,2279,0,0]Ag[463,13,464,0,4972,0,0]Al[3096,13,3097,1691,4973,0,0]8Al[1195,13,1380,1380,4354,0,0]Al[3966,13,3967,3967,5006,0,0]Ag[463,13,464,0,4206,0,0]Aj[463,13,464,2304,2259,0,0]Ag[463,13,464,0,3234,0,0]Ag[463,13,464,0,4458,0,0]Ag[463,13,464,0,4328,0,0]Ag[463,13,464,0,4114,0,0]Ag[463,13,464,0,4065,0,0]Ag[463,13,464,0,4080,0,0]Ag[463,13,464,0,4675,0,0]Ag[463,13,464,0,2281,0,0]Ag[463,13,3635,0,120,0,0]Ah[463,13,3635,0,1156,0,0]Ah[463,13,3635,0,1840,0,0]Al[3096,13,3097,4477,4207,0,0]Al[3096,13,3097,1116,3235,0,0]Al[3096,13,3097,2305,2260,0,0]Al[3096,13,3097,1756,5124,0,0]Al[3096,13,3097,4254,5094,0,0]Al[3096,13,3097,1116,4459,0,0]Al[3096,13,3097,1691,4329,0,0]Al[3096,13,3097,1691,4116,0,0]Al[3096,13,3097,1691,4067,0,0]Al[3096,13,3097,1756,4236,0,0]Al[3096,13,3097,1784,4081,0,0]Ai[3096,13,3097,0,4357,0,0]Al[3096,13,3097,4254,5099,0,0]Al[3096,13,3097,4844,4676,0,0]Al[3096,13,3097,3097,2282,0,0]Al[3096,13,3097,3097,3024,0,0]Aj[3096,13,3636,148,121,0,0]Al[3096,13,3636,1352,1157,0,0]Al[3096,13,3636,2358,1841,0,0]Ak[1985,13,1986,416,4358,0,0]Al[1095,13,4174,4174,5050,0,0]Al[1095,13,4174,4174,5051,0,0]Aa[463,3,0,0,0,0,0]Ah[463,13,464,0,510,133,0]Am[3096,13,3097,3097,511,133,0]Ag[463,12,464,0,3250,0,0]Aj[463,13,464,1994,1852,0,0]0Am[463,13,464,1393,4121,1852,0]Aj[463,13,464,0,4245,3250,0]Al[3096,12,3097,1691,3251,0,0]Ao[3096,13,3097,1691,4246,3251,0]Aj[463,13,464,3640,3211,0,0]Ag[463,14,464,0,3041,0,0]Al[3096,13,3097,3641,3212,0,0]Al[3096,14,3097,1381,3042,0,0]Ae[1095,7,1683,0,0,0,0]Ae[1095,7,1779,0,0,0,0]Ac[463,4,464,0,0,0,0]Ag[463,15,464,0,4085,0,0]Al[3096,15,3097,1784,4086,0,0]Al[1985,15,1986,1986,4087,0,0]210Ag[463,5,1693,1694,0,0,0]Ae[3096,5,1694,0,0,0,0]Al[3096,15,3097,1756,4457,0,0]Al[3096,15,3097,1356,3224,0,0]Af[463,5,464,1094,0,0,0]Al[3096,15,3097,3641,1174,0,0]Ag[463,15,464,0,3025,0,0]Al[3096,15,3097,1356,3026,0,0]Aj[463,15,464,4034,4352,0,0]Af[463,6,464,3097,0,0,0]Ae[3096,6,3097,0,0,0,0]Ag[463,19,464,0,1426,0,0]Al[3096,19,3097,1366,1427,0,0]Ag[463,14,464,0,3217,0,0]Al[3096,14,3097,1381,3218,0,0]Ai[3096,14,3039,0,4100,0,0]Al[1985,14,1986,1986,2279,0,0]Aa[519,3,0,0,0,0,0]Ai[3096,14,3097,0,4357,0,0]Ag[463,14,464,0,3041,0,0]Al[3096,14,3097,1381,3042,0,0]Al[3096,14,3097,1356,2253,0,0]Al[463,13,464,3640,2274,487,0]Al[463,13,464,2336,2263,487,0]Ak[463,13,464,1094,503,487,0]Al[463,13,464,1094,4359,487,0]Al[463,13,464,4034,3237,487,0]Al[463,13,464,4039,3243,487,0]An[3096,13,3097,1120,1407,487,0]Am[1195,13,1196,1196,134,494,0]An[1195,13,1196,1196,3952,494,0]An[1195,13,1196,1196,3928,494,0]An[1195,13,1380,1380,3960,494,0]An[1195,13,1380,1380,1189,494,0]An[1195,13,1380,1380,4354,494,0]Ak[519,13,520,520,3929,494,0]An[3966,13,3967,3967,5006,494,0]Al[463,13,464,3640,4472,494,0]Al[463,13,464,3640,2274,494,0]Ai[463,13,464,0,4206,494,0]Al[463,13,464,4142,4912,494,0]Al[463,13,464,1094,2275,494,0]Al[463,13,464,1094,4640,494,0]Al[463,13,464,3640,3209,494,0]Al[463,13,464,3640,3211,494,0]Al[463,13,464,3640,1843,494,0]Al[463,13,464,3640,1419,494,0]Al[463,13,464,3640,3004,494,0]Al[463,13,464,3640,2247,494,0]Al[463,13,464,3640,4106,494,0]Al[463,13,464,3640,4111,494,0]Al[463,13,464,3640,4918,494,0]Al[463,13,464,3640,4749,494,0]0Al[463,13,464,2304,2259,494,0]Al[463,13,464,2304,4215,494,0]Al[463,13,464,2304,1399,494,0]Al[463,13,464,4142,1418,494,0]Al[463,13,464,4142,3231,494,0]Al[463,13,464,4142,4849,494,0]Al[463,13,464,2336,2263,494,0]Al[463,13,464,2336,3240,494,0]Al[463,13,464,2336,4333,494,0]Ak[463,13,464,1094,503,494,0]Al[463,13,464,1094,4359,494,0]Al[463,13,464,4034,3237,494,0]Al[463,13,464,4034,4856,494,0]Al[463,13,464,4034,4352,494,0]0Al[463,13,464,4034,3044,494,0]Df[463,13,464,4034,3044,494,0,\"impl-From%3CResetError%3E-for-ReadError\"]Dk[463,13,464,4034,3044,494,0,\"impl-From%3CConnectionError%3E-for-ReadError\"]Al[463,13,464,4034,3239,494,0]0Al[463,13,464,4034,4351,494,0]0Al[463,13,464,1393,4121,494,0]Al[463,13,464,1137,3955,494,0]Al[463,13,464,4039,3243,494,0]Di[463,13,464,4039,3257,494,0,\"impl-From%3CStoppedError%3E-for-WriteError\"]Di[463,13,464,4039,3257,494,0,\"impl-From%3CClosedStream%3E-for-WriteError\"]Dl[463,13,464,4039,3257,494,0,\"impl-From%3CConnectionError%3E-for-WriteError\"]Al[463,13,464,4039,3257,494,0]Al[463,13,464,4039,4120,494,0]0Al[463,13,464,4039,1857,494,0]Ai[463,13,464,0,3234,494,0]Ai[463,13,464,0,4245,494,0]Ai[463,13,464,0,4464,494,0]Ai[463,13,464,0,4098,494,0]Ai[463,13,464,0,4342,494,0]Dh[463,13,464,0,1443,494,0,\"impl-From%3CTransportErrorCode%3E-for-VarInt\"]Ch[463,13,464,0,1443,494,0,\"impl-From%3Cu8%3E-for-VarInt\"]Ci[463,13,464,0,1443,494,0,\"impl-From%3Cu16%3E-for-VarInt\"]Cn[463,13,464,0,1443,494,0,\"impl-From%3CStreamId%3E-for-VarInt\"]Ci[463,13,464,0,1443,494,0,\"impl-From%3Cu32%3E-for-VarInt\"]Ai[463,13,464,0,1443,494,0]Ai[463,13,464,0,4915,494,0]Ai[463,13,464,0,1152,494,0]Ai[463,13,464,0,1426,494,0]0Ai[463,13,464,0,2294,494,0]Ai[463,13,464,0,3217,494,0]Ai[463,13,464,0,3041,494,0]Ai[463,13,464,0,4448,494,0]Ai[463,13,464,0,1867,494,0]Ai[463,13,464,0,4071,494,0]Dm[463,13,464,0,4444,494,0,\"impl-From%3CTransportError%3E-for-ConnectionError\"]Dd[463,13,464,0,4444,494,0,\"impl-From%3CClose%3E-for-ConnectionError\"]Ai[463,13,464,0,4444,494,0]Dj[463,13,464,0,4444,494,0,\"impl-From%3CCloseReason%3E-for-ConnectionError\"]Ai[463,13,464,0,4458,494,0]Ai[463,13,464,0,4467,494,0]Ai[463,13,464,0,4773,494,0]Ai[463,13,464,0,4788,494,0]Ai[463,13,464,0,3941,494,0]0Ai[463,13,464,0,4328,494,0]Ai[463,13,464,0,4114,494,0]Ai[463,13,464,0,4972,494,0]Ai[463,13,464,0,4065,494,0]Ai[463,13,464,0,3931,494,0]Do[463,13,464,0,3931,494,0,\"impl-From%3CVarIntBoundsExceeded%3E-for-ConfigError\"]Dj[463,13,464,0,3931,494,0,\"impl-From%3CTryFromIntError%3E-for-ConfigError\"]Ai[463,13,464,0,3025,494,0]Ai[463,13,464,0,4441,494,0]0Ai[463,13,464,0,4642,494,0]Ai[463,13,464,0,4077,494,0]0Ai[463,13,464,0,4080,494,0]Ai[463,13,464,0,4085,494,0]Ai[463,13,464,0,4808,494,0]0Ai[463,13,464,0,3220,494,0]Ai[463,13,464,0,4675,494,0]Ah[463,13,464,0,510,494,0]Ah[463,13,464,0,126,494,0]Ai[463,13,464,0,2281,494,0]0Ai[463,13,464,0,2291,494,0]Ai[463,13,3635,0,120,494,0]Aj[463,13,3635,0,3012,494,0]Aj[463,13,3635,0,1156,494,0]Aj[463,13,3635,0,3936,494,0]Aj[463,13,3635,0,1840,494,0]Aj[463,13,3635,0,4233,494,0]Aj[463,13,3635,0,4737,494,0]Aj[463,13,1693,0,1835,494,0]Ai[463,13,1693,0,497,494,0]Aj[463,13,1693,0,5064,494,0]Aj[463,13,1693,0,3934,494,0]0Aj[463,13,1693,0,4812,494,0]Aj[463,13,1773,0,3252,494,0]Aj[463,13,1773,0,4217,494,0]Df[463,13,1773,0,1169,494,0,\"impl-From%3CInconsistentKeys%3E-for-Error\"]Df[463,13,1773,0,1169,494,0,\"impl-From%3CPeerIncompatible%3E-for-Error\"]De[463,13,1773,0,1169,494,0,\"impl-From%3CGetRandomFailed%3E-for-Error\"]Dd[463,13,1773,0,1169,494,0,\"impl-From%3CInvalidMessage%3E-for-Error\"]Dd[463,13,1773,0,1169,494,0,\"impl-From%3CPeerMisbehaved%3E-for-Error\"]De[463,13,1773,0,1169,494,0,\"impl-From%3CSystemTimeError%3E-for-Error\"]Dm[463,13,1773,0,1169,494,0,\"impl-From%3CCertRevocationListError%3E-for-Error\"]Df[463,13,1773,0,1169,494,0,\"impl-From%3CCertificateError%3E-for-Error\"]D`[463,13,1773,0,1169,494,0,\"impl-From%3COtherError%3E-for-Error\"]Do[463,13,1773,0,1169,494,0,\"impl-From%3CUnsupportedOperationError%3E-for-Error\"]Do[463,13,1773,0,1169,494,0,\"impl-From%3CEncryptedClientHelloError%3E-for-Error\"]Aj[463,13,1773,0,1169,494,0]Aj[463,13,1773,0,4671,494,0]Aj[463,13,1773,0,4910,494,0]Aj[463,13,1773,0,4673,494,0]Am[2358,13,2359,2359,125,494,0]An[2358,13,2359,2359,2250,494,0]An[3096,13,3097,4477,4207,494,0]An[3096,13,3097,3070,3018,494,0]An[3096,13,3097,1120,1407,494,0]An[3096,13,3097,2002,1861,494,0]An[3096,13,3097,2002,3238,494,0]An[3096,13,3097,2002,3244,494,0]An[3096,13,3097,3641,3212,494,0]An[3096,13,3097,1116,3235,494,0]An[3096,13,3097,1691,4246,494,0]An[3096,13,3097,2305,2260,494,0]An[3096,13,3097,2305,4213,494,0]An[3096,13,3097,2305,4651,494,0]An[3096,13,3097,2305,4224,494,0]An[3096,13,3097,1756,5124,494,0]An[3096,13,3097,4254,5094,494,0]An[3096,13,3097,1392,4465,494,0]An[3096,13,3097,1392,4099,494,0]An[3096,13,3097,1392,4343,494,0]Cn[3096,13,3097,1810,1444,494,0,\"impl-From%3Cu32%3E-for-VarInt\"]Dc[3096,13,3097,1810,1444,494,0,\"impl-From%3CStreamId%3E-for-VarInt\"]Cn[3096,13,3097,1810,1444,494,0,\"impl-From%3Cu16%3E-for-VarInt\"]Cm[3096,13,3097,1810,1444,494,0,\"impl-From%3Cu8%3E-for-VarInt\"]An[3096,13,3097,1810,1444,494,0]Co[3096,13,3097,1810,1444,494,0,\"impl-From%3CCode%3E-for-VarInt\"]An[3096,13,3097,1810,4916,494,0]An[3096,13,3097,3061,1153,494,0]An[3096,13,3097,3070,4750,494,0]An[3096,13,3097,1366,1427,494,0]0An[3096,13,3097,1366,4110,494,0]An[3096,13,3097,1366,3233,494,0]An[3096,13,3097,1366,3040,494,0]An[3096,13,3097,1366,4748,494,0]An[3096,13,3097,1366,4804,494,0]An[3096,13,3097,1366,3207,494,0]An[3096,13,3097,1381,2295,494,0]An[3096,13,3097,1381,3218,494,0]An[3096,13,3097,1381,3042,494,0]An[3096,13,3097,1381,4449,494,0]An[3096,13,3097,1120,3045,494,0]An[3096,13,3097,1120,4241,494,0]An[3096,13,3097,1125,1868,494,0]An[3096,13,3097,1125,3258,494,0]An[3096,13,3097,1125,3940,494,0]An[3096,13,3097,2002,3959,494,0]An[3096,13,3097,2002,4353,494,0]An[3096,13,3097,2002,4075,494,0]An[3096,13,3097,3641,4445,494,0]0An[3096,13,3097,3641,3038,494,0]An[3096,13,3097,3641,4319,494,0]An[3096,13,3097,3641,5001,494,0]An[3096,13,3097,3641,1174,494,0]0An[3096,13,3097,1116,4459,494,0]An[3096,13,3097,3194,4468,494,0]An[3096,13,3097,3194,4774,494,0]An[3096,13,3097,3194,4789,494,0]An[3096,13,3097,3194,3942,494,0]0An[3096,13,3097,1691,4329,494,0]An[3096,13,3097,1691,4116,494,0]An[3096,13,3097,1691,4973,494,0]An[3096,13,3097,1691,4067,494,0]An[3096,13,3097,1691,3932,494,0]Do[3096,13,3097,1691,3932,494,0,\"impl-From%3CTryFromIntError%3E-for-ConfigError\"]Ed[3096,13,3097,1691,3932,494,0,\"impl-From%3CVarIntBoundsExceeded%3E-for-ConfigError\"]An[3096,13,3097,1356,3026,494,0]An[3096,13,3097,1356,4334,494,0]An[3096,13,3097,1356,3224,494,0]An[3096,13,3097,1356,3247,494,0]An[3096,13,3097,1356,4082,494,0]An[3096,13,3097,1356,4443,494,0]0An[3096,13,3097,1356,4644,494,0]An[3096,13,3097,1356,2253,494,0]An[3096,13,3097,2305,4648,494,0]An[3096,13,3097,2305,2264,494,0]An[3096,13,3097,2305,4078,494,0]0An[3096,13,3097,2305,3926,494,0]An[3096,13,3097,2305,3241,494,0]An[3096,13,3097,1756,4236,494,0]An[3096,13,3097,1756,4457,494,0]An[3096,13,3097,1756,5005,494,0]An[3096,13,3097,1756,2267,494,0]An[3096,13,3097,1756,4746,494,0]0An[3096,13,3097,1784,4447,494,0]An[3096,13,3097,1784,4214,494,0]An[3096,13,3097,1784,4081,494,0]An[3096,13,3097,1784,4086,494,0]Ak[3096,13,3097,0,4357,494,0]0Ak[3096,13,3097,0,4809,494,0]0An[3096,13,3097,4254,3221,494,0]An[3096,13,3097,4254,5099,494,0]An[3096,13,3097,4844,4676,494,0]Am[3096,13,3097,3097,511,494,0]Am[3096,13,3097,3097,127,494,0]An[3096,13,3097,3097,2282,494,0]0An[3096,13,3097,3097,2292,494,0]An[3096,13,3097,3097,3024,494,0]An[3096,13,1689,1689,4247,494,0]An[3096,13,4957,4957,4855,494,0]An[3096,13,4957,4957,1171,494,0]0An[3096,13,1694,1694,1836,494,0]Am[3096,13,1694,1694,498,494,0]An[3096,13,1694,1694,5065,494,0]An[3096,13,1694,1694,3935,494,0]0An[3096,13,1694,1694,4815,494,0]An[3096,13,1774,1774,3253,494,0]An[3096,13,1774,1774,4218,494,0]E`[3096,13,1774,0,1170,494,0,\"impl-From%3CUnsupportedOperationError%3E-for-Error\"]Dg[3096,13,1774,0,1170,494,0,\"impl-From%3CInconsistentKeys%3E-for-Error\"]Da[3096,13,1774,0,1170,494,0,\"impl-From%3COtherError%3E-for-Error\"]Dg[3096,13,1774,0,1170,494,0,\"impl-From%3CPeerIncompatible%3E-for-Error\"]Ak[3096,13,1774,0,1170,494,0]Df[3096,13,1774,0,1170,494,0,\"impl-From%3CGetRandomFailed%3E-for-Error\"]De[3096,13,1774,0,1170,494,0,\"impl-From%3CPeerMisbehaved%3E-for-Error\"]Df[3096,13,1774,0,1170,494,0,\"impl-From%3CSystemTimeError%3E-for-Error\"]E`[3096,13,1774,0,1170,494,0,\"impl-From%3CEncryptedClientHelloError%3E-for-Error\"]Dg[3096,13,1774,0,1170,494,0,\"impl-From%3CCertificateError%3E-for-Error\"]De[3096,13,1774,0,1170,494,0,\"impl-From%3CInvalidMessage%3E-for-Error\"]Dn[3096,13,1774,0,1170,494,0,\"impl-From%3CCertRevocationListError%3E-for-Error\"]An[3096,13,1774,1774,4672,494,0]An[3096,13,1774,1774,4911,494,0]An[3096,13,1774,1774,4674,494,0]Al[3096,13,3636,148,121,494,0]Am[3096,13,3636,148,3013,494,0]An[3096,13,3636,1352,1157,494,0]An[3096,13,3636,1352,3937,494,0]An[3096,13,3636,2358,1841,494,0]An[3096,13,3636,2358,4234,494,0]An[3096,13,3636,3636,4738,494,0]An[3096,13,4706,4706,1172,494,0]An[3096,13,4706,4706,1175,494,0]An[1985,13,1986,1986,3256,494,0]0Am[1985,13,1986,416,4358,494,0]An[1985,13,1986,1986,2279,494,0]An[1985,13,1986,1986,2293,494,0]An[1985,13,1986,1986,4087,494,0]An[1095,13,1096,1096,3015,494,0]An[1095,13,1096,1096,4848,494,0]Am[1095,13,1683,1683,135,494,0]An[1095,13,1382,1382,1190,494,0]An[1095,13,1382,1382,3961,494,0]An[1095,13,1382,1382,4453,494,0]An[1095,13,4174,4174,5050,494,0]An[1095,13,4174,4174,5051,494,0]Am[1095,13,1779,1779,136,494,0]Ai[463,13,464,0,1443,495,0]Ai[463,13,464,0,1426,495,0]Ai[463,13,464,0,3941,495,0]Ai[463,13,464,0,4080,495,0]Ah[463,13,464,0,510,495,0]Ah[463,13,464,0,126,495,0]Ai[463,13,464,0,2281,495,0]An[3096,13,3097,1810,1444,495,0]An[3096,13,3097,3070,4750,495,0]An[3096,13,3097,1366,1427,495,0]An[3096,13,3097,1120,3045,495,0]An[3096,13,3097,1120,4241,495,0]An[3096,13,3097,1125,3258,495,0]An[3096,13,3097,3194,3942,495,0]An[3096,13,3097,2305,4648,495,0]An[3096,13,3097,1756,4746,495,0]An[3096,13,3097,1784,4081,495,0]Am[3096,13,3097,3097,511,495,0]Am[3096,13,3097,3097,127,495,0]An[3096,13,3097,3097,2282,495,0]An[3096,13,3097,3097,3024,495,0]Am[1195,13,1196,1196,134,496,0]An[1195,13,1196,1196,3952,496,0]An[1195,13,1196,1196,3928,496,0]An[1195,13,1380,1380,3960,496,0]An[1195,13,1380,1380,1189,496,0]An[1195,13,1380,1380,4354,496,0]Ak[519,13,520,520,3929,496,0]An[3966,13,3967,3967,5006,496,0]Al[463,13,464,3640,4472,496,0]Al[463,13,464,3640,2274,496,0]Ai[463,13,464,0,4206,496,0]Al[463,13,464,4142,4912,496,0]Al[463,13,464,1094,2275,496,0]Al[463,13,464,1094,4640,496,0]Al[463,13,464,3640,3209,496,0]Al[463,13,464,3640,3211,496,0]Al[463,13,464,3640,1843,496,0]Al[463,13,464,3640,1419,496,0]Al[463,13,464,3640,3004,496,0]Al[463,13,464,3640,2247,496,0]Al[463,13,464,3640,4106,496,0]Al[463,13,464,3640,4111,496,0]Al[463,13,464,3640,4918,496,0]Al[463,13,464,3640,4749,496,0]Al[463,13,464,2304,2259,496,0]Al[463,13,464,2304,4215,496,0]Al[463,13,464,2304,1399,496,0]Al[463,13,464,4142,1418,496,0]Al[463,13,464,4142,3231,496,0]Al[463,13,464,4142,4849,496,0]Al[463,13,464,2336,2263,496,0]Al[463,13,464,2336,3240,496,0]Al[463,13,464,2336,4333,496,0]Ak[463,13,464,1094,503,496,0]Al[463,13,464,1094,4359,496,0]Al[463,13,464,4034,3237,496,0]Al[463,13,464,4034,4856,496,0]Al[463,13,464,4034,4352,496,0]Al[463,13,464,4034,3044,496,0]Al[463,13,464,4034,3239,496,0]Al[463,13,464,4034,4351,496,0]Al[463,13,464,1393,4121,496,0]Al[463,13,464,1137,3955,496,0]Al[463,13,464,4039,3243,496,0]Al[463,13,464,4039,3257,496,0]Al[463,13,464,4039,4120,496,0]Al[463,13,464,4039,1857,496,0]Ai[463,13,464,0,3234,496,0]Ai[463,13,464,0,4245,496,0]Ai[463,13,464,0,4464,496,0]Ai[463,13,464,0,4098,496,0]Ai[463,13,464,0,4342,496,0]Ai[463,13,464,0,1443,496,0]Ai[463,13,464,0,4915,496,0]Ai[463,13,464,0,1152,496,0]Ai[463,13,464,0,1426,496,0]Ai[463,13,464,0,2294,496,0]Ai[463,13,464,0,3217,496,0]Ai[463,13,464,0,3041,496,0]Ai[463,13,464,0,4448,496,0]Ai[463,13,464,0,1867,496,0]Ai[463,13,464,0,4071,496,0]Ai[463,13,464,0,4444,496,0]Ai[463,13,464,0,4458,496,0]Ai[463,13,464,0,4467,496,0]Ai[463,13,464,0,4773,496,0]Ai[463,13,464,0,4788,496,0]Ai[463,13,464,0,3941,496,0]Ai[463,13,464,0,4328,496,0]Ai[463,13,464,0,4114,496,0]Ai[463,13,464,0,4972,496,0]Ai[463,13,464,0,4065,496,0]Ai[463,13,464,0,3931,496,0]Ai[463,13,464,0,3025,496,0]Ai[463,13,464,0,4441,496,0]Ai[463,13,464,0,4642,496,0]Ai[463,13,464,0,4077,496,0]Ai[463,13,464,0,4080,496,0]Ai[463,13,464,0,4085,496,0]Ai[463,13,464,0,4808,496,0]Ai[463,13,464,0,3220,496,0]Ai[463,13,464,0,4675,496,0]Ah[463,13,464,0,510,496,0]Ah[463,13,464,0,126,496,0]Ai[463,13,464,0,2281,496,0]Ai[463,13,464,0,2291,496,0]Ai[463,13,3635,0,120,496,0]Aj[463,13,3635,0,3012,496,0]Aj[463,13,3635,0,1156,496,0]Aj[463,13,3635,0,3936,496,0]Aj[463,13,3635,0,1840,496,0]Aj[463,13,3635,0,4233,496,0]Aj[463,13,3635,0,4737,496,0]Aj[463,13,1693,0,1835,496,0]Ai[463,13,1693,0,497,496,0]Aj[463,13,1693,0,5064,496,0]Aj[463,13,1693,0,3934,496,0]Aj[463,13,1693,0,4812,496,0]Aj[463,13,1773,0,3252,496,0]Aj[463,13,1773,0,4217,496,0]Aj[463,13,1773,0,1169,496,0]Aj[463,13,1773,0,4671,496,0]Aj[463,13,1773,0,4910,496,0]Aj[463,13,1773,0,4673,496,0]Am[2358,13,2359,2359,125,496,0]An[2358,13,2359,2359,2250,496,0]An[3096,13,3097,4477,4207,496,0]An[3096,13,3097,3070,3018,496,0]An[3096,13,3097,1120,1407,496,0]An[3096,13,3097,2002,1861,496,0]An[3096,13,3097,2002,3238,496,0]An[3096,13,3097,2002,3244,496,0]An[3096,13,3097,3641,3212,496,0]An[3096,13,3097,1116,3235,496,0]An[3096,13,3097,1691,4246,496,0]An[3096,13,3097,2305,2260,496,0]An[3096,13,3097,2305,4213,496,0]An[3096,13,3097,2305,4651,496,0]An[3096,13,3097,2305,4224,496,0]An[3096,13,3097,1756,5124,496,0]An[3096,13,3097,4254,5094,496,0]An[3096,13,3097,1392,4465,496,0]An[3096,13,3097,1392,4099,496,0]An[3096,13,3097,1392,4343,496,0]An[3096,13,3097,1810,1444,496,0]An[3096,13,3097,1810,4916,496,0]An[3096,13,3097,3061,1153,496,0]An[3096,13,3097,3070,4750,496,0]An[3096,13,3097,1366,1427,496,0]An[3096,13,3097,1366,4110,496,0]An[3096,13,3097,1366,3233,496,0]An[3096,13,3097,1366,3040,496,0]An[3096,13,3097,1366,4748,496,0]An[3096,13,3097,1366,4804,496,0]An[3096,13,3097,1366,3207,496,0]An[3096,13,3097,1381,2295,496,0]An[3096,13,3097,1381,3218,496,0]An[3096,13,3097,1381,3042,496,0]An[3096,13,3097,1381,4449,496,0]An[3096,13,3097,1120,3045,496,0]An[3096,13,3097,1120,4241,496,0]An[3096,13,3097,1125,1868,496,0]An[3096,13,3097,1125,3258,496,0]An[3096,13,3097,1125,3940,496,0]An[3096,13,3097,2002,3959,496,0]An[3096,13,3097,2002,4353,496,0]An[3096,13,3097,2002,4075,496,0]An[3096,13,3097,3641,4445,496,0]An[3096,13,3097,3641,3038,496,0]An[3096,13,3097,3641,4319,496,0]An[3096,13,3097,3641,5001,496,0]An[3096,13,3097,3641,1174,496,0]An[3096,13,3097,1116,4459,496,0]An[3096,13,3097,3194,4468,496,0]An[3096,13,3097,3194,4774,496,0]An[3096,13,3097,3194,4789,496,0]An[3096,13,3097,3194,3942,496,0]An[3096,13,3097,1691,4329,496,0]An[3096,13,3097,1691,4116,496,0]An[3096,13,3097,1691,4973,496,0]An[3096,13,3097,1691,4067,496,0]An[3096,13,3097,1691,3932,496,0]An[3096,13,3097,1356,3026,496,0]An[3096,13,3097,1356,4334,496,0]An[3096,13,3097,1356,3224,496,0]An[3096,13,3097,1356,3247,496,0]An[3096,13,3097,1356,4082,496,0]An[3096,13,3097,1356,4443,496,0]An[3096,13,3097,1356,4644,496,0]An[3096,13,3097,1356,2253,496,0]An[3096,13,3097,2305,4648,496,0]An[3096,13,3097,2305,2264,496,0]An[3096,13,3097,2305,4078,496,0]An[3096,13,3097,2305,3926,496,0]An[3096,13,3097,2305,3241,496,0]An[3096,13,3097,1756,4236,496,0]An[3096,13,3097,1756,4457,496,0]An[3096,13,3097,1756,5005,496,0]An[3096,13,3097,1756,2267,496,0]An[3096,13,3097,1756,4746,496,0]An[3096,13,3097,1784,4447,496,0]An[3096,13,3097,1784,4214,496,0]An[3096,13,3097,1784,4081,496,0]An[3096,13,3097,1784,4086,496,0]Ak[3096,13,3097,0,4357,496,0]Ak[3096,13,3097,0,4809,496,0]An[3096,13,3097,4254,3221,496,0]An[3096,13,3097,4254,5099,496,0]An[3096,13,3097,4844,4676,496,0]Am[3096,13,3097,3097,511,496,0]Am[3096,13,3097,3097,127,496,0]An[3096,13,3097,3097,2282,496,0]An[3096,13,3097,3097,2292,496,0]An[3096,13,3097,3097,3024,496,0]An[3096,13,1689,1689,4247,496,0]An[3096,13,4957,4957,4855,496,0]An[3096,13,4957,4957,1171,496,0]An[3096,13,1694,1694,1836,496,0]Am[3096,13,1694,1694,498,496,0]An[3096,13,1694,1694,5065,496,0]An[3096,13,1694,1694,3935,496,0]An[3096,13,1694,1694,4815,496,0]An[3096,13,1774,1774,3253,496,0]An[3096,13,1774,1774,4218,496,0]Ak[3096,13,1774,0,1170,496,0]An[3096,13,1774,1774,4672,496,0]An[3096,13,1774,1774,4911,496,0]An[3096,13,1774,1774,4674,496,0]Al[3096,13,3636,148,121,496,0]Am[3096,13,3636,148,3013,496,0]An[3096,13,3636,1352,1157,496,0]An[3096,13,3636,1352,3937,496,0]An[3096,13,3636,2358,1841,496,0]An[3096,13,3636,2358,4234,496,0]An[3096,13,3636,3636,4738,496,0]An[3096,13,4706,4706,1172,496,0]An[3096,13,4706,4706,1175,496,0]An[1985,13,1986,1986,3256,496,0]Am[1985,13,1986,416,4358,496,0]An[1985,13,1986,1986,2279,496,0]An[1985,13,1986,1986,2293,496,0]An[1985,13,1986,1986,4087,496,0]An[1095,13,1096,1096,3015,496,0]An[1095,13,1096,1096,4848,496,0]Am[1095,13,1683,1683,135,496,0]An[1095,13,1382,1382,1190,496,0]An[1095,13,1382,1382,3961,496,0]An[1095,13,1382,1382,4453,496,0]An[1095,13,4174,4174,5050,496,0]An[1095,13,4174,4174,5051,496,0]Am[1095,13,1779,1779,136,496,0]Ac[519,7,520,0,0,0,0]Ae[3966,7,3967,0,0,0,0]Ae[4257,7,4258,0,0,0,0]Ae[2358,7,2359,0,0,0,0]Ae[4768,7,4769,0,0,0,0]Al[3096,13,3097,1120,1407,0,0]Ao[3096,13,3097,2305,4224,2265,0]Ah[463,12,1693,0,4739,0,0]Al[3096,13,3097,2002,1861,0,0]Al[3096,12,1694,1694,4740,0,0]Aj[463,13,464,3640,3211,0,0]Ab[1095,3,0,0,0,0,0]Ai[463,13,464,1094,503,0,0]Ag[463,14,464,0,3217,0,0]Al[3096,13,3097,3641,3212,0,0]Al[3096,14,3097,1381,3218,0,0]Aj[463,12,464,1994,3200,0,0]Am[463,13,464,3640,4472,1415,0]Am[463,13,464,3640,2274,1415,0]Am[463,13,464,1094,2275,1415,0]Am[463,13,464,3640,3209,1415,0]Am[463,13,464,3640,1843,1415,0]Am[463,13,464,3640,1419,1415,0]Am[463,13,464,3640,3004,1415,0]Am[463,13,464,3640,2247,1415,0]Am[463,13,464,3640,4106,1415,0]Am[463,13,464,3640,4111,1415,0]Am[463,13,464,2304,1399,1415,0]Am[463,13,464,2336,4333,1415,0]Am[463,13,464,4039,1857,1415,0]?Aj[463,13,464,4034,3237,0,0]Al[3096,13,3097,2002,3238,0,0]Al[3096,13,4957,4957,4855,0,0]Al[3096,13,3097,3070,3018,0,0]Ak[1985,13,1986,416,4358,0,0]Ah[463,12,1693,0,4739,0,0]Al[3096,12,1694,1694,4740,0,0]32Ag[463,13,464,0,4972,0,0]Al[3096,13,3097,1691,4973,0,0]Aj[463,13,464,3640,3211,0,0]Al[3096,13,3097,3641,3212,0,0]Ah[463,12,1693,0,1831,0,0]Al[3096,12,1694,1694,1832,0,0]Al[1195,14,1380,1380,4354,0,0]Ag[463,14,464,0,2291,0,0]Al[3096,14,3097,3097,2292,0,0]Ai[3096,14,4456,0,1186,0,0]Aj[463,13,464,4034,3237,0,0]Aj[463,13,464,4034,4856,0,0]Al[3096,13,3097,2002,3238,0,0]Ag[463,12,464,0,3254,0,0]Aj[463,13,464,0,4342,3254,0]Aj[463,13,464,0,4675,3254,0]Al[3096,12,3097,1392,3255,0,0]Ao[3096,13,3097,1392,4343,3255,0]Ao[3096,13,3097,4844,4676,3255,0]Af[463,5,464,3061,0,0,0]Ah[3096,5,3097,3061,0,0,0]Ag[463,5,3635,1352,0,0,0]Ah[3096,5,3636,1352,0,0,0]Al[1095,15,1096,1096,4848,0,0]Ag[463,6,1773,1355,0,0,0]Ah[3096,6,1774,1355,0,0,0]Ae[3096,6,4957,0,0,0,0]Ae[3096,6,4706,0,0,0,0]Ah[3096,6,3097,3641,0,0,0]1Al[3096,15,3097,1356,3224,0,0]Ah[463,15,1773,0,1169,0,0]Ai[3096,15,1774,0,1170,0,0]Aj[463,15,464,4034,3044,0,0]Ag[463,15,464,0,4444,0,0]Al[3096,15,3097,1120,3045,0,0]Al[3096,15,3097,3641,4445,0,0]Al[3096,15,3097,1756,4457,0,0]0Ae[1195,5,1380,0,0,0,0]Ae[1095,5,1382,0,0,0,0]Al[1195,15,1196,1196,3952,0,0]Al[3096,13,3097,2305,4651,0,0]Ab[1195,3,0,0,0,0,0]Ah[463,12,3635,0,4735,0,0]Ak[463,13,3635,0,3012,4735,0]Ak[463,13,3635,0,3936,4735,0]Ak[463,13,3635,0,4233,4735,0]Al[3096,12,3636,3636,4736,0,0]An[3096,13,3636,148,3013,4736,0]Ao[3096,13,3636,1352,3937,4736,0]Ao[3096,13,3636,2358,4234,4736,0]Al[1095,13,1096,1096,4848,0,0]Ag[463,14,464,0,1152,0,0]Ag[463,14,464,0,2294,0,0]Ag[463,14,464,0,1867,0,0]Al[3096,14,3097,3061,1153,0,0]Al[3096,14,3097,1381,2295,0,0]Al[3096,14,3097,1125,1868,0,0]Al[3096,14,3097,2305,3926,0,0]An[1195,13,1196,1196,134,1154,0]Ao[1195,13,1196,1196,3952,1154,0]Ao[1195,13,1196,1196,3928,1154,0]Am[463,13,464,3640,3211,1154,0]Am[463,13,464,3640,4918,1154,0]Am[463,13,464,3640,4749,1154,0]Am[463,13,464,2304,2259,1154,0]Am[463,13,464,2304,4215,1154,0]Am[463,13,464,4142,1418,1154,0]Al[463,13,464,1094,503,1154,0]Am[463,13,464,1094,4359,1154,0]Am[463,13,464,4034,4352,1154,0]Am[463,13,464,4034,3044,1154,0]Am[463,13,464,4034,3239,1154,0]Am[463,13,464,4034,4351,1154,0]Am[463,13,464,4039,3257,1154,0]Am[463,13,464,4039,4120,1154,0]Aj[463,13,464,0,1443,1154,0]Aj[463,13,464,0,4915,1154,0]Aj[463,13,464,0,1426,1154,0]Aj[463,13,464,0,2294,1154,0]Aj[463,13,464,0,3217,1154,0]Aj[463,13,464,0,3041,1154,0]Aj[463,13,464,0,4448,1154,0]Aj[463,13,464,0,1867,1154,0]Aj[463,13,464,0,4071,1154,0]Aj[463,13,464,0,4444,1154,0]Aj[463,13,464,0,4467,1154,0]Aj[463,13,464,0,4773,1154,0]Aj[463,13,464,0,4788,1154,0]Aj[463,13,464,0,3941,1154,0]Aj[463,13,464,0,4328,1154,0]Aj[463,13,464,0,4114,1154,0]Aj[463,13,464,0,4972,1154,0]Aj[463,13,464,0,4065,1154,0]Aj[463,13,464,0,3931,1154,0]Aj[463,13,464,0,3025,1154,0]Aj[463,13,464,0,4441,1154,0]Aj[463,13,464,0,4642,1154,0]Aj[463,13,464,0,4077,1154,0]Aj[463,13,464,0,4080,1154,0]Aj[463,13,464,0,4085,1154,0]Aj[463,13,464,0,4808,1154,0]Aj[463,13,464,0,3220,1154,0]Ai[463,13,464,0,510,1154,0]Ai[463,13,464,0,126,1154,0]Aj[463,13,464,0,2281,1154,0]Aj[463,13,3635,0,120,1154,0]Ak[463,13,3635,0,3012,1154,0]Ak[463,13,3635,0,1156,1154,0]Ak[463,13,3635,0,3936,1154,0]Ak[463,13,3635,0,1840,1154,0]Ak[463,13,3635,0,4233,1154,0]Ak[463,13,1773,0,1169,1154,0]Ak[463,13,1773,0,4671,1154,0]Ak[463,13,1773,0,4910,1154,0]Ak[463,13,1773,0,4673,1154,0]Ao[3096,13,3097,1810,1444,1154,0]Ao[3096,13,3097,1810,4916,1154,0]Ao[3096,13,3097,3070,4750,1154,0]Ao[3096,13,3097,1366,1427,1154,0]Ao[3096,13,3097,1366,4110,1154,0]Ao[3096,13,3097,1366,3233,1154,0]Ao[3096,13,3097,1366,3040,1154,0]Ao[3096,13,3097,1366,4748,1154,0]Ao[3096,13,3097,1366,4804,1154,0]Ao[3096,13,3097,1366,3207,1154,0]Ao[3096,13,3097,1381,2295,1154,0]Ao[3096,13,3097,1381,3218,1154,0]Ao[3096,13,3097,1381,3042,1154,0]Ao[3096,13,3097,1381,4449,1154,0]Ao[3096,13,3097,1120,3045,1154,0]Ao[3096,13,3097,1120,4241,1154,0]Ao[3096,13,3097,1125,1868,1154,0]Ao[3096,13,3097,1125,3258,1154,0]Ao[3096,13,3097,1125,3940,1154,0]Ao[3096,13,3097,2002,4353,1154,0]Ao[3096,13,3097,2002,4075,1154,0]Ao[3096,13,3097,3641,4445,1154,0]Ao[3096,13,3097,3641,3038,1154,0]Ao[3096,13,3097,3641,4319,1154,0]Ao[3096,13,3097,3641,5001,1154,0]Ao[3096,13,3097,3194,4468,1154,0]Ao[3096,13,3097,3194,4774,1154,0]Ao[3096,13,3097,3194,4789,1154,0]Ao[3096,13,3097,3194,3942,1154,0]Ao[3096,13,3097,1691,4329,1154,0]Ao[3096,13,3097,1691,4116,1154,0]Ao[3096,13,3097,1691,4973,1154,0]Ao[3096,13,3097,1691,4067,1154,0]Ao[3096,13,3097,1691,3932,1154,0]Ao[3096,13,3097,1356,3026,1154,0]Ao[3096,13,3097,1356,3224,1154,0]Ao[3096,13,3097,1356,3247,1154,0]Ao[3096,13,3097,1356,4082,1154,0]Ao[3096,13,3097,1356,4443,1154,0]Ao[3096,13,3097,1356,4644,1154,0]Ao[3096,13,3097,1356,2253,1154,0]Ao[3096,13,3097,2305,4648,1154,0]Ao[3096,13,3097,2305,4078,1154,0]Ao[3096,13,3097,1756,4457,1154,0]Ao[3096,13,3097,1756,5005,1154,0]Ao[3096,13,3097,1756,2267,1154,0]Ao[3096,13,3097,1756,4746,1154,0]Ao[3096,13,3097,1784,4081,1154,0]Ao[3096,13,3097,1784,4086,1154,0]Al[3096,13,3097,0,4357,1154,0]Al[3096,13,3097,0,4809,1154,0]Ao[3096,13,3097,4254,3221,1154,0]Ao[3096,13,3097,4254,5099,1154,0]An[3096,13,3097,3097,511,1154,0]An[3096,13,3097,3097,127,1154,0]Ao[3096,13,3097,3097,2282,1154,0]Ao[3096,13,3097,3097,3024,1154,0]Ao[3096,13,1689,1689,4247,1154,0]Ao[3096,13,4957,4957,4855,1154,0]Ao[3096,13,4957,4957,1171,1154,0]Al[3096,13,1774,0,1170,1154,0]Ao[3096,13,1774,1774,4672,1154,0]Ao[3096,13,1774,1774,4911,1154,0]Ao[3096,13,1774,1774,4674,1154,0]Am[3096,13,3636,148,121,1154,0]An[3096,13,3636,148,3013,1154,0]Ao[3096,13,3636,1352,1157,1154,0]Ao[3096,13,3636,1352,3937,1154,0]Ao[3096,13,3636,2358,1841,1154,0]Ao[3096,13,3636,2358,4234,1154,0]Ao[3096,13,4706,4706,1175,1154,0]Ao[1985,13,1986,1986,2279,1154,0]Ao[1985,13,1986,1986,2293,1154,0]Ao[1985,13,1986,1986,4087,1154,0]Ao[1095,13,1096,1096,4848,1154,0]Ao[1095,13,1382,1382,4453,1154,0]Ao[1095,13,4174,4174,5050,1154,0]Ao[1095,13,4174,4174,5051,1154,0]Aj[463,13,464,3640,3211,0,0]Aj[463,13,464,2304,2259,0,0]Ai[463,13,464,1094,503,0,0]Al[3096,13,3097,3641,3212,0,0]Aj[463,13,464,0,4080,1160,0]Ao[3096,13,3097,1784,4081,1160,0]Ai[3096,14,3097,0,4357,0,0]Ag[463,13,464,0,2281,0,0]Al[3096,13,3097,3097,2282,0,0]Ag[463,13,464,0,4085,0,0]Al[3096,13,3097,1784,4086,0,0]Ah[463,14,1693,0,1835,0,0]Al[3096,14,1694,1694,1836,0,0]Al[3096,12,3097,1756,4779,0,0]Ao[3096,13,3097,1756,5124,4779,0];Al[1195,13,1380,1380,1189,0,0]Ai[519,13,520,520,3929,0,0]Al[1095,13,1382,1382,1190,0,0]Aj[463,12,464,1994,3200,0,0]Aj[463,13,464,4039,3243,0,0]Al[3096,13,3097,2002,3244,0,0]Aj[463,13,464,2336,2263,0,0]Al[3096,13,3097,2305,2260,0,0]Aj[463,12,464,1994,1852,0,0]Am[463,13,464,1393,4121,1852,0]Am[463,13,464,1137,3955,1852,0]Ae[1195,2,1196,0,0,0,0]Ak[1195,14,1196,1196,134,0,0]Aj[463,13,464,3640,3211,0,0]Aj[463,13,464,2304,2259,0,0]Ai[463,13,464,1094,503,0,0]Al[3096,13,3097,3641,3212,0,0]Ae[1095,2,1096,0,0,0,0]Ag[463,13,464,0,3234,0,0]Al[3096,13,3097,1116,3235,0,0]?>Al[3096,13,4957,4957,4855,0,0]Af[463,5,464,2304,0,0,0]Ag[463,15,464,0,3025,0,0]Al[3096,15,3097,1356,3026,0,0]Al[1195,15,1196,1196,3928,0,0]0Al[3096,15,3097,1366,3233,0,0]Ah[3096,5,3097,1120,0,0,0]Af[463,15,464,0,510,0,0]Al[2358,15,2359,2359,2250,0,0]Ak[3096,15,3097,3097,511,0,0]Al[3096,15,4706,4706,1172,0,0]87Af[463,5,464,4142,0,0,0]Af[463,5,464,3640,0,0,0]Al[3096,15,3097,1366,3040,0,0]Al[3096,15,3097,2002,3959,0,0]Af[463,5,464,1366,0,0,0]Ah[3096,5,3097,1366,0,0,0]Ae[3096,8,1689,0,0,0,0]:98?Al[3096,15,3097,3641,1174,0,0]?Af[463,5,464,1810,0,0,0]Ah[3096,5,3097,1810,0,0,0]Aj[463,13,464,2304,2259,0,0]Aj[463,13,464,2336,2263,0,0]Al[3096,13,3097,2002,1861,0,0]Al[3096,13,3097,2305,2260,0,0]An[1195,13,1196,1196,134,1405,0]Ao[1195,13,1196,1196,3952,1405,0]Ao[1195,13,1196,1196,3928,1405,0]Ao[1195,13,1380,1380,3960,1405,0]Ao[1195,13,1380,1380,1189,1405,0]Ao[1195,13,1380,1380,4354,1405,0]Al[519,13,520,520,3929,1405,0]Ao[3966,13,3967,3967,5006,1405,0]Am[463,13,464,3640,4472,1405,0]Am[463,13,464,3640,2274,1405,0]Aj[463,13,464,0,4206,1405,0]Am[463,13,464,4142,4912,1405,0]Am[463,13,464,1094,2275,1405,0]Am[463,13,464,1094,4640,1405,0]Am[463,13,464,3640,3209,1405,0]Am[463,13,464,3640,3211,1405,0]Am[463,13,464,3640,1843,1405,0]Am[463,13,464,3640,1419,1405,0]Am[463,13,464,3640,3004,1405,0]Am[463,13,464,3640,2247,1405,0]Am[463,13,464,3640,4106,1405,0]Am[463,13,464,3640,4111,1405,0]Am[463,13,464,3640,4918,1405,0]Am[463,13,464,3640,4749,1405,0]Am[463,13,464,2304,2259,1405,0]Am[463,13,464,2304,4215,1405,0]Am[463,13,464,2304,1399,1405,0]Am[463,13,464,4142,1418,1405,0]Am[463,13,464,4142,3231,1405,0]Am[463,13,464,4142,4849,1405,0]Am[463,13,464,2336,2263,1405,0]Am[463,13,464,2336,3240,1405,0]Am[463,13,464,2336,4333,1405,0]Al[463,13,464,1094,503,1405,0]Am[463,13,464,1094,4359,1405,0]Am[463,13,464,4034,3237,1405,0]Am[463,13,464,4034,4856,1405,0]Am[463,13,464,4034,4352,1405,0]Am[463,13,464,4034,3044,1405,0]Am[463,13,464,4034,3239,1405,0]Am[463,13,464,4034,4351,1405,0]Am[463,13,464,1393,4121,1405,0]Am[463,13,464,1137,3955,1405,0]Am[463,13,464,4039,3243,1405,0]Am[463,13,464,4039,3257,1405,0]Am[463,13,464,4039,4120,1405,0]Am[463,13,464,4039,1857,1405,0]Aj[463,13,464,0,3234,1405,0]Aj[463,13,464,0,4245,1405,0]Aj[463,13,464,0,4464,1405,0]Aj[463,13,464,0,4098,1405,0]Aj[463,13,464,0,4342,1405,0]Aj[463,13,464,0,1443,1405,0]Aj[463,13,464,0,4915,1405,0]Aj[463,13,464,0,1152,1405,0]Aj[463,13,464,0,1426,1405,0]Aj[463,13,464,0,2294,1405,0]Aj[463,13,464,0,3217,1405,0]Aj[463,13,464,0,3041,1405,0]Aj[463,13,464,0,4448,1405,0]Aj[463,13,464,0,1867,1405,0]Aj[463,13,464,0,4071,1405,0]Aj[463,13,464,0,4444,1405,0]Aj[463,13,464,0,4458,1405,0]Aj[463,13,464,0,4467,1405,0]Aj[463,13,464,0,4773,1405,0]Aj[463,13,464,0,4788,1405,0]Aj[463,13,464,0,3941,1405,0]Aj[463,13,464,0,4328,1405,0]Aj[463,13,464,0,4114,1405,0]Aj[463,13,464,0,4972,1405,0]Aj[463,13,464,0,4065,1405,0]Aj[463,13,464,0,3931,1405,0]Aj[463,13,464,0,3025,1405,0]Aj[463,13,464,0,4441,1405,0]Aj[463,13,464,0,4642,1405,0]Aj[463,13,464,0,4077,1405,0]Aj[463,13,464,0,4080,1405,0]Aj[463,13,464,0,4085,1405,0]Aj[463,13,464,0,4808,1405,0]Aj[463,13,464,0,3220,1405,0]Aj[463,13,464,0,4675,1405,0]Ai[463,13,464,0,510,1405,0]Ai[463,13,464,0,126,1405,0]Aj[463,13,464,0,2281,1405,0]Aj[463,13,464,0,2291,1405,0]Aj[463,13,3635,0,120,1405,0]Ak[463,13,3635,0,3012,1405,0]Ak[463,13,3635,0,1156,1405,0]Ak[463,13,3635,0,3936,1405,0]Ak[463,13,3635,0,1840,1405,0]Ak[463,13,3635,0,4233,1405,0]Ak[463,13,3635,0,4737,1405,0]Ak[463,13,1693,0,1835,1405,0]Aj[463,13,1693,0,497,1405,0]Ak[463,13,1693,0,5064,1405,0]Ak[463,13,1693,0,3934,1405,0]Ak[463,13,1693,0,4812,1405,0]Ak[463,13,1773,0,3252,1405,0]Ak[463,13,1773,0,4217,1405,0]Ak[463,13,1773,0,1169,1405,0]Ak[463,13,1773,0,4671,1405,0]Ak[463,13,1773,0,4910,1405,0]Ak[463,13,1773,0,4673,1405,0]An[2358,13,2359,2359,125,1405,0]Ao[2358,13,2359,2359,2250,1405,0]Ao[3096,13,3097,4477,4207,1405,0]Ao[3096,13,3097,3070,3018,1405,0]Ao[3096,13,3097,1120,1407,1405,0]Ao[3096,13,3097,2002,1861,1405,0]Ao[3096,13,3097,2002,3238,1405,0]Ao[3096,13,3097,2002,3244,1405,0]Ao[3096,13,3097,3641,3212,1405,0]Ao[3096,13,3097,1116,3235,1405,0]Ao[3096,13,3097,1691,4246,1405,0]Ao[3096,13,3097,2305,2260,1405,0]Ao[3096,13,3097,2305,4213,1405,0]Ao[3096,13,3097,2305,4651,1405,0]Ao[3096,13,3097,2305,4224,1405,0]Ao[3096,13,3097,1756,5124,1405,0]Ao[3096,13,3097,4254,5094,1405,0]Ao[3096,13,3097,1392,4465,1405,0]Ao[3096,13,3097,1392,4099,1405,0]Ao[3096,13,3097,1392,4343,1405,0]Ao[3096,13,3097,1810,1444,1405,0]Ao[3096,13,3097,1810,4916,1405,0]Ao[3096,13,3097,3061,1153,1405,0]Ao[3096,13,3097,3070,4750,1405,0]Ao[3096,13,3097,1366,1427,1405,0]Ao[3096,13,3097,1366,4110,1405,0]Ao[3096,13,3097,1366,3233,1405,0]Ao[3096,13,3097,1366,3040,1405,0]Ao[3096,13,3097,1366,4748,1405,0]Ao[3096,13,3097,1366,4804,1405,0]Ao[3096,13,3097,1366,3207,1405,0]Ao[3096,13,3097,1381,2295,1405,0]Ao[3096,13,3097,1381,3218,1405,0]Ao[3096,13,3097,1381,3042,1405,0]Ao[3096,13,3097,1381,4449,1405,0]Ao[3096,13,3097,1120,3045,1405,0]Ao[3096,13,3097,1120,4241,1405,0]Ao[3096,13,3097,1125,1868,1405,0]Ao[3096,13,3097,1125,3258,1405,0]Ao[3096,13,3097,1125,3940,1405,0]Ao[3096,13,3097,2002,3959,1405,0]Ao[3096,13,3097,2002,4353,1405,0]Ao[3096,13,3097,2002,4075,1405,0]Ao[3096,13,3097,3641,4445,1405,0]Ao[3096,13,3097,3641,3038,1405,0]Ao[3096,13,3097,3641,4319,1405,0]Ao[3096,13,3097,3641,5001,1405,0]Ao[3096,13,3097,3641,1174,1405,0]Ao[3096,13,3097,1116,4459,1405,0]Ao[3096,13,3097,3194,4468,1405,0]Ao[3096,13,3097,3194,4774,1405,0]Ao[3096,13,3097,3194,4789,1405,0]Ao[3096,13,3097,3194,3942,1405,0]Ao[3096,13,3097,1691,4329,1405,0]Ao[3096,13,3097,1691,4116,1405,0]Ao[3096,13,3097,1691,4973,1405,0]Ao[3096,13,3097,1691,4067,1405,0]Ao[3096,13,3097,1691,3932,1405,0]Ao[3096,13,3097,1356,3026,1405,0]Ao[3096,13,3097,1356,4334,1405,0]Ao[3096,13,3097,1356,3224,1405,0]Ao[3096,13,3097,1356,3247,1405,0]Ao[3096,13,3097,1356,4082,1405,0]Ao[3096,13,3097,1356,4443,1405,0]Ao[3096,13,3097,1356,4644,1405,0]Ao[3096,13,3097,1356,2253,1405,0]Ao[3096,13,3097,2305,4648,1405,0]Ao[3096,13,3097,2305,2264,1405,0]Ao[3096,13,3097,2305,4078,1405,0]Ao[3096,13,3097,2305,3926,1405,0]Ao[3096,13,3097,2305,3241,1405,0]Ao[3096,13,3097,1756,4236,1405,0]Ao[3096,13,3097,1756,4457,1405,0]Ao[3096,13,3097,1756,5005,1405,0]Ao[3096,13,3097,1756,2267,1405,0]Ao[3096,13,3097,1756,4746,1405,0]Ao[3096,13,3097,1784,4447,1405,0]Ao[3096,13,3097,1784,4214,1405,0]Ao[3096,13,3097,1784,4081,1405,0]Ao[3096,13,3097,1784,4086,1405,0]Al[3096,13,3097,0,4357,1405,0]Al[3096,13,3097,0,4809,1405,0]Ao[3096,13,3097,4254,3221,1405,0]Ao[3096,13,3097,4254,5099,1405,0]Ao[3096,13,3097,4844,4676,1405,0]An[3096,13,3097,3097,511,1405,0]An[3096,13,3097,3097,127,1405,0]Ao[3096,13,3097,3097,2282,1405,0]Ao[3096,13,3097,3097,2292,1405,0]Ao[3096,13,3097,3097,3024,1405,0]Ao[3096,13,1689,1689,4247,1405,0]Ao[3096,13,4957,4957,4855,1405,0]Ao[3096,13,4957,4957,1171,1405,0]Ao[3096,13,1694,1694,1836,1405,0]An[3096,13,1694,1694,498,1405,0]Ao[3096,13,1694,1694,5065,1405,0]Ao[3096,13,1694,1694,3935,1405,0]Ao[3096,13,1694,1694,4815,1405,0]Ao[3096,13,1774,1774,3253,1405,0]Ao[3096,13,1774,1774,4218,1405,0]Al[3096,13,1774,0,1170,1405,0]Ao[3096,13,1774,1774,4672,1405,0]Ao[3096,13,1774,1774,4911,1405,0]Ao[3096,13,1774,1774,4674,1405,0]Am[3096,13,3636,148,121,1405,0]An[3096,13,3636,148,3013,1405,0]Ao[3096,13,3636,1352,1157,1405,0]Ao[3096,13,3636,1352,3937,1405,0]Ao[3096,13,3636,2358,1841,1405,0]Ao[3096,13,3636,2358,4234,1405,0]Ao[3096,13,3636,3636,4738,1405,0]Ao[3096,13,4706,4706,1172,1405,0]Ao[3096,13,4706,4706,1175,1405,0]Ao[1985,13,1986,1986,3256,1405,0]An[1985,13,1986,416,4358,1405,0]Ao[1985,13,1986,1986,2279,1405,0]Ao[1985,13,1986,1986,2293,1405,0]Ao[1985,13,1986,1986,4087,1405,0]Ao[1095,13,1096,1096,3015,1405,0]Ao[1095,13,1096,1096,4848,1405,0]An[1095,13,1683,1683,135,1405,0]Ao[1095,13,1382,1382,1190,1405,0]Ao[1095,13,1382,1382,3961,1405,0]Ao[1095,13,1382,1382,4453,1405,0]Ao[1095,13,4174,4174,5050,1405,0]Ao[1095,13,4174,4174,5051,1405,0]An[1095,13,1779,1779,136,1405,0]Ag[463,14,464,0,1867,0,0]Al[3096,14,3097,1125,1868,0,0]Ak[1195,14,1196,1196,134,0,0]Ac[519,7,520,0,0,0,0]Aj[463,13,464,2304,2259,0,0]Ae[1095,2,1096,0,0,0,0]Ae[4768,7,4769,0,0,0,0]Aj[463,13,464,3640,3211,0,0]Ae[3096,2,3097,0,0,0,0]Al[3096,13,3097,2305,2260,0,0]Af[463,2,464,3097,0,0,0]Ag[463,13,464,0,4808,0,0]Ag[463,14,464,0,3217,0,0]Ag[463,14,464,0,4114,0,0]5Ai[3096,13,3097,0,4809,0,0]Al[3096,14,3097,1381,3218,0,0]Al[3096,14,3097,1691,4116,0,0]Ai[3096,14,3097,0,4357,0,0]Aj[463,13,464,0,1443,3019,0]Aj[463,13,464,0,1426,3019,0]Aj[463,13,464,0,3025,3019,0]Aj[463,13,464,0,4808,3019,0]Aj[463,13,464,0,2281,3019,0]Ao[3096,13,3097,1810,1444,3019,0]Ao[3096,13,3097,1366,1427,3019,0]Ao[3096,13,3097,1356,3026,3019,0]Ao[3096,13,3097,1356,3224,3019,0]Al[3096,13,3097,1756,4457,0,0]Al[3096,13,3097,0,4809,3019,0]Ao[3096,13,3097,3097,2282,3019,0]Al[3096,12,1689,1689,3019,0,0]Al[1985,14,1986,1986,2279,0,0]Aj[463,13,464,0,1443,3022,0]Aj[463,13,464,0,1426,3022,0]Aj[463,13,464,0,3025,3022,0]Aj[463,13,464,0,4808,3022,0]Aj[463,13,464,0,2281,3022,0]Ao[3096,13,3097,1810,1444,3022,0]Ao[3096,13,3097,1366,1427,3022,0]Ao[3096,13,3097,1356,3026,3022,0]Ao[3096,13,3097,1356,3224,3022,0]Ao[3096,13,3097,1356,2253,3022,0]Al[3096,13,3097,0,4809,3022,0]Ao[3096,13,3097,3097,2282,3022,0]Al[3096,12,1689,1689,3022,0,0]Aj[463,13,464,4039,3243,0,0]Al[3096,13,3097,2002,3244,0,0]Al[1095,13,1382,1382,3961,0,0]Al[3096,13,3097,2305,2260,0,0]Ag[463,14,1693,0,497,0,0]Ak[3096,14,1694,1694,498,0,0]Aj[463,13,464,2336,2263,0,0]3Ag[463,12,464,0,3254,0,0]Aj[463,13,464,0,4342,3254,0]Aj[463,13,464,0,4675,3254,0]Al[3096,12,3097,1392,3255,0,0]Ao[3096,13,3097,1392,4343,3255,0]Ao[3096,13,3097,4844,4676,3255,0]Al[1095,14,1096,1096,3015,0,0]Ag[463,14,464,0,1152,0,0]Al[3096,14,3097,3061,1153,0,0]Ah[463,13,3635,0,3213,0,0]Aj[463,13,3635,0,120,3213,0]Ak[463,13,3635,0,1156,3213,0]Ak[463,13,3635,0,1840,3213,0]Al[3096,13,3636,3636,3214,0,0]Am[3096,13,3636,148,121,3214,0]Ao[3096,13,3636,1352,1157,3214,0]Ao[3096,13,3636,2358,1841,3214,0]Ag[463,14,1693,0,497,0,0]Ak[3096,14,1694,1694,498,0,0]Ai[3096,14,4456,0,4680,0,0]Ag[463,14,464,0,4441,0,0]Ag[463,14,464,0,4642,0,0]Al[3096,14,3097,1356,4443,0,0]Al[3096,14,3097,1356,4644,0,0]Ai[3096,14,3097,0,4357,0,0]Ai[3096,14,1173,0,4321,0,0]Ai[3096,14,3039,0,3002,0,0]Aj[463,13,464,2304,2259,0,0]Aj[463,13,464,2336,2263,0,0]Al[3096,13,3097,2305,2260,0,0]Ah[463,14,1693,0,1835,0,0]Al[3096,13,3097,3097,3024,0,0]Al[3096,14,1694,1694,1836,0,0]Ac[463,4,464,0,0,0,0]Ag[463,2,1693,1694,0,0,0]Ae[3096,4,3097,0,0,0,0]Ae[3096,2,1694,0,0,0,0]Ac[519,7,520,0,0,0,0]:Ae[1095,2,1096,0,0,0,0]Ae[4768,7,4769,0,0,0,0]Am[463,13,464,3640,4749,1162,0]Am[463,13,464,4034,4352,1162,0]Am[463,13,464,4034,3044,1162,0]Am[463,13,464,4034,3239,1162,0]Am[463,13,464,4034,4351,1162,0]Am[463,13,464,4039,3257,1162,0]Am[463,13,464,4039,4120,1162,0]Aj[463,13,464,0,4444,1162,0]Ao[3096,13,3097,3641,4445,1162,0]Ag[463,14,464,0,2291,0,0]Al[3096,14,3097,3097,2292,0,0]Al[1985,14,1986,1986,2293,0,0]Ai[463,13,464,1094,503,0,0]Ai[3096,14,3039,0,4107,0,0]Ag[463,14,464,0,3217,0,0]Al[3096,14,3097,1381,3218,0,0]Al[1985,14,1986,1986,2279,0,0]Ag[463,14,464,0,3041,0,0]Ag[463,14,464,0,4448,0,0]Al[3096,14,3097,1381,3042,0,0]Al[3096,14,3097,1381,4449,0,0]3210Ah[463,12,1693,0,1831,0,0]Al[3096,12,1694,1694,1832,0,0]Ah[463,12,3635,0,3213,0,0]Aj[463,13,3635,0,120,3213,0]Ak[463,13,3635,0,1156,3213,0]Ak[463,13,3635,0,1840,3213,0]Al[3096,12,3636,3636,3214,0,0]Am[3096,13,3636,148,121,3214,0]Ao[3096,13,3636,1352,1157,3214,0]Ao[3096,13,3636,2358,1841,3214,0]Al[3096,15,3097,3070,4750,0,0]Al[3096,15,3097,1120,3045,0,0]Al[3096,15,3097,1125,3258,0,0]Ah[463,15,1773,0,1169,0,0]Ai[3096,15,1774,0,1170,0,0]Ah[463,10,1693,1694,0,0,0]Af[3096,10,1694,0,0,0,0]Al[3096,15,3097,1756,4457,0,0]Ag[463,5,1693,1694,0,0,0]Ae[3096,5,1694,0,0,0,0]Ag[463,15,464,0,3025,0,0]Al[3096,15,3097,1356,3026,0,0]Ag[463,5,3635,2358,0,0,0]Ah[3096,5,3636,2358,0,0,0]Al[1095,15,1096,1096,4848,0,0]Af[463,5,464,3640,0,0,0]5454Ag[463,10,464,1994,0,0,0];:Aj[463,15,464,4039,3257,0,0]Af[463,5,464,4039,0,0,0]Al[3096,15,3097,1125,3258,0,0]Al[3096,15,3097,1125,3940,0,0]Al[3096,15,3097,2002,3959,0,0]Ah[3096,5,3097,2002,0,0,0]Aj[463,15,464,4034,4352,0,0]Al[3096,15,3097,1356,3224,0,0]Af[463,5,464,1125,0,0,0]Ah[3096,5,3097,1125,0,0,0]Al[3096,15,3097,1756,2267,0,0]Ag[463,12,464,0,4963,0,0]Al[3096,12,3097,4254,4964,0,0]Ao[3096,13,3097,4254,5094,4964,0]Ao[3096,13,3097,4254,5099,4964,0]Ak[1195,14,1196,1196,134,0,0]An[1195,13,1196,1196,134,4320,0]An[2358,13,2359,2359,125,4320,0]Ak[2358,14,2359,2359,125,0,0]Ao[1095,13,1096,1096,3015,4320,0]An[1095,13,1683,1683,135,4320,0]An[1095,13,1779,1779,136,4320,0]Aj[463,13,464,0,1443,3208,0]Aj[463,13,464,0,1426,3208,0]Aj[463,13,464,0,3941,3208,0]Aj[463,13,464,0,4080,3208,0]Ai[463,13,464,0,510,3208,0]Ai[463,13,464,0,126,3208,0]Aj[463,13,464,0,2281,3208,0]Ao[3096,13,3097,1810,1444,3208,0]Ao[3096,13,3097,3070,4750,3208,0]Ao[3096,13,3097,1366,1427,3208,0]Ao[3096,13,3097,1120,3045,3208,0]Ao[3096,13,3097,1120,4241,3208,0]Ao[3096,13,3097,1125,3258,3208,0]Ao[3096,13,3097,3194,3942,3208,0]Ao[3096,13,3097,2305,4648,3208,0]Ao[3096,13,3097,1756,4746,3208,0]Ao[3096,13,3097,1784,4081,3208,0]An[3096,13,3097,3097,511,3208,0]An[3096,13,3097,3097,127,3208,0]Ao[3096,13,3097,3097,2282,3208,0]Aj[463,13,464,2304,2259,0,0]Al[3096,13,3097,2305,2260,0,0]Aj[463,13,464,2336,2263,0,0]Ah[463,12,1693,0,3035,0,0]Ah[463,12,1693,0,3028,0,0]Al[3096,13,3097,2305,2264,0,0]Al[3096,12,1694,1694,3036,0,0]Al[3096,12,1694,1694,3029,0,0]Ao[1195,13,1380,1380,3960,1826,0]Ao[1195,13,1380,1380,1189,1826,0]Al[519,13,520,520,3929,1826,0]Aj[463,13,464,0,4206,1826,0]Am[463,13,464,2304,4215,1826,0]Aj[463,13,464,0,1443,1826,0]Aj[463,13,464,0,1426,1826,0]Aj[463,13,464,0,2294,1826,0]Aj[463,13,464,0,3217,1826,0]Aj[463,13,464,0,3041,1826,0]Aj[463,13,464,0,4448,1826,0]Aj[463,13,464,0,1867,1826,0]Aj[463,13,464,0,4071,1826,0]Aj[463,13,464,0,4467,1826,0]Aj[463,13,464,0,4773,1826,0]Aj[463,13,464,0,4788,1826,0]Aj[463,13,464,0,3941,1826,0]Aj[463,13,464,0,4328,1826,0]Aj[463,13,464,0,4972,1826,0]Aj[463,13,464,0,4675,1826,0]Ak[463,13,3635,0,3012,1826,0]Ak[463,13,3635,0,3936,1826,0]Ak[463,13,3635,0,4233,1826,0]Ak[463,13,3635,0,4737,1826,0]Ao[3096,13,3097,4477,4207,1826,0]Ao[3096,13,3097,4254,5094,1826,0]Ao[3096,13,3097,1810,1444,1826,0]Ao[3096,13,3097,1366,1427,1826,0]Ao[3096,13,3097,1366,3233,1826,0]Ao[3096,13,3097,1366,3207,1826,0]Ao[3096,13,3097,1381,2295,1826,0]Ao[3096,13,3097,1381,3218,1826,0]Ao[3096,13,3097,1381,3042,1826,0]Ao[3096,13,3097,1381,4449,1826,0]Ao[3096,13,3097,1125,1868,1826,0]Ao[3096,13,3097,2002,4353,1826,0]Ao[3096,13,3097,2002,4075,1826,0]Ao[3096,13,3097,3194,4468,1826,0]Ao[3096,13,3097,3194,4774,1826,0]Ao[3096,13,3097,3194,4789,1826,0]Ao[3096,13,3097,3194,3942,1826,0]Ao[3096,13,3097,1691,4329,1826,0]Ao[3096,13,3097,1691,4973,1826,0]Ao[3096,13,3097,4254,5099,1826,0]Ao[3096,13,3097,4844,4676,1826,0]An[3096,13,3636,148,3013,1826,0]Ao[3096,13,3636,1352,3937,1826,0]Ao[3096,13,3636,2358,4234,1826,0]Ao[3096,13,3636,3636,4738,1826,0]Ao[1985,13,1986,1986,2279,1826,0]Ao[1095,13,1382,1382,1190,1826,0]Ao[1095,13,1382,1382,4453,1826,0]Al[3096,13,3097,1784,4214,0,0]Al[3096,13,3097,1756,4236,0,0]Al[3096,13,3097,1756,4457,0,0]Al[3096,14,3097,1756,5005,0,0]Ai[3096,14,4745,0,4810,0,0]Ah[3096,14,4456,0,499,0,0]Ai[3096,14,4456,0,1184,0,0]Ai[3096,14,4456,0,1186,0,0]Ai[3096,14,4456,0,4680,0,0]Ah[463,12,1693,0,3035,0,0]Ah[463,12,1693,0,3028,0,0]Al[3096,12,1694,1694,3036,0,0]Al[3096,12,1694,1694,3029,0,0]Aj[463,13,464,4034,3237,0,0]Aj[463,13,464,4034,4856,0,0]Ah[463,13,3635,0,3213,0,0]Aj[463,13,3635,0,120,3213,0]Ak[463,13,3635,0,1156,3213,0]Ak[463,13,3635,0,1840,3213,0]Al[3096,13,3636,3636,3214,0,0]Am[3096,13,3636,148,121,3214,0]Ao[3096,13,3636,1352,1157,3214,0]Ao[3096,13,3636,2358,1841,3214,0]Ag[463,13,464,0,4467,0,0]Al[3096,13,3097,3194,4468,0,0]Ab[1985,3,0,0,0,0,0]:965Aj[463,13,464,3640,3211,0,0]Aj[463,13,464,1094,2275,0,0]Al[3096,14,3097,1756,5005,0,0]Ai[3096,14,4745,0,4810,0,0]Ah[3096,14,4456,0,499,0,0]Ai[3096,14,4456,0,1184,0,0]Ai[3096,14,4456,0,4680,0,0]Aj[463,13,464,4039,3243,0,0]Al[3096,13,3097,2002,3244,0,0]Ak[1195,14,1196,1196,134,0,0]Al[1195,14,1380,1380,1189,0,0]Al[3096,13,3097,3641,3212,0,0]Ah[463,12,1693,0,3035,0,0]Al[3096,12,1694,1694,3036,0,0]Am[1195,13,1196,1196,134,118,0]An[1195,13,1196,1196,3952,118,0]An[1195,13,1196,1196,3928,118,0]An[1195,13,1380,1380,3960,118,0]An[1195,13,1380,1380,1189,118,0]An[1195,13,1380,1380,4354,118,0]Ak[519,13,520,520,3929,118,0]An[3966,13,3967,3967,5006,118,0]Al[463,13,464,3640,4472,118,0]Al[463,13,464,3640,2274,118,0]Ai[463,13,464,0,4206,118,0]Al[463,13,464,4142,4912,118,0]Al[463,13,464,1094,2275,118,0]Al[463,13,464,1094,4640,118,0]Al[463,13,464,3640,3209,118,0]Al[463,13,464,3640,3211,118,0]Al[463,13,464,3640,1843,118,0]Al[463,13,464,3640,1419,118,0]Al[463,13,464,3640,3004,118,0]Al[463,13,464,3640,2247,118,0]Al[463,13,464,3640,4106,118,0]Al[463,13,464,3640,4111,118,0]Al[463,13,464,3640,4918,118,0]Al[463,13,464,3640,4749,118,0]Al[463,13,464,2304,2259,118,0]Al[463,13,464,2304,4215,118,0]Al[463,13,464,2304,1399,118,0]Al[463,13,464,4142,1418,118,0]Al[463,13,464,4142,3231,118,0]Al[463,13,464,4142,4849,118,0]Al[463,13,464,2336,2263,118,0]Al[463,13,464,2336,3240,118,0]Al[463,13,464,2336,4333,118,0]Ak[463,13,464,1094,503,118,0]Al[463,13,464,1094,4359,118,0]Al[463,13,464,4034,3237,118,0]Al[463,13,464,4034,4856,118,0]Al[463,13,464,4034,4352,118,0]Al[463,13,464,4034,3044,118,0]Al[463,13,464,4034,3239,118,0]Al[463,13,464,4034,4351,118,0]Al[463,13,464,1393,4121,118,0]Al[463,13,464,1137,3955,118,0]Al[463,13,464,4039,3243,118,0]Al[463,13,464,4039,3257,118,0]Al[463,13,464,4039,4120,118,0]Al[463,13,464,4039,1857,118,0]Ai[463,13,464,0,3234,118,0]Ai[463,13,464,0,4245,118,0]Ai[463,13,464,0,4464,118,0]Ai[463,13,464,0,4098,118,0]Ai[463,13,464,0,4342,118,0]Ai[463,13,464,0,1443,118,0]Ai[463,13,464,0,4915,118,0]Ai[463,13,464,0,1152,118,0]Ai[463,13,464,0,1426,118,0]Ai[463,13,464,0,2294,118,0]Ai[463,13,464,0,3217,118,0]Ai[463,13,464,0,3041,118,0]Ai[463,13,464,0,4448,118,0]Ai[463,13,464,0,1867,118,0]Ai[463,13,464,0,4071,118,0]Ai[463,13,464,0,4444,118,0]Ai[463,13,464,0,4458,118,0]Ai[463,13,464,0,4467,118,0]Ai[463,13,464,0,4773,118,0]Ai[463,13,464,0,4788,118,0]Ai[463,13,464,0,3941,118,0]Ai[463,13,464,0,4328,118,0]Ai[463,13,464,0,4114,118,0]Ai[463,13,464,0,4972,118,0]Ai[463,13,464,0,4065,118,0]Ai[463,13,464,0,3931,118,0]Ai[463,13,464,0,3025,118,0]Ai[463,13,464,0,4441,118,0]Ai[463,13,464,0,4642,118,0]Ai[463,13,464,0,4077,118,0]Ai[463,13,464,0,4080,118,0]Ai[463,13,464,0,4085,118,0]Ai[463,13,464,0,4808,118,0]Ai[463,13,464,0,3220,118,0]Ai[463,13,464,0,4675,118,0]Ah[463,13,464,0,510,118,0]Ah[463,13,464,0,126,118,0]Ai[463,13,464,0,2281,118,0]Ai[463,13,464,0,2291,118,0]Ai[463,13,3635,0,120,118,0]Aj[463,13,3635,0,3012,118,0]Aj[463,13,3635,0,1156,118,0]Aj[463,13,3635,0,3936,118,0]Aj[463,13,3635,0,1840,118,0]Aj[463,13,3635,0,4233,118,0]Aj[463,13,3635,0,4737,118,0]Aj[463,13,1693,0,1835,118,0]Ai[463,13,1693,0,497,118,0]Aj[463,13,1693,0,5064,118,0]Aj[463,13,1693,0,3934,118,0]Aj[463,13,1693,0,4812,118,0]Aj[463,13,1773,0,3252,118,0]Aj[463,13,1773,0,4217,118,0]Aj[463,13,1773,0,1169,118,0]Aj[463,13,1773,0,4671,118,0]Aj[463,13,1773,0,4910,118,0]Aj[463,13,1773,0,4673,118,0]Am[2358,13,2359,2359,125,118,0]An[2358,13,2359,2359,2250,118,0]An[3096,13,3097,4477,4207,118,0]An[3096,13,3097,3070,3018,118,0]An[3096,13,3097,1120,1407,118,0]An[3096,13,3097,2002,1861,118,0]An[3096,13,3097,2002,3238,118,0]An[3096,13,3097,2002,3244,118,0]An[3096,13,3097,3641,3212,118,0]An[3096,13,3097,1116,3235,118,0]An[3096,13,3097,1691,4246,118,0]An[3096,13,3097,2305,2260,118,0]An[3096,13,3097,2305,4213,118,0]An[3096,13,3097,2305,4651,118,0]An[3096,13,3097,2305,4224,118,0]An[3096,13,3097,1756,5124,118,0]An[3096,13,3097,4254,5094,118,0]An[3096,13,3097,1392,4465,118,0]An[3096,13,3097,1392,4099,118,0]An[3096,13,3097,1392,4343,118,0]An[3096,13,3097,1810,1444,118,0]An[3096,13,3097,1810,4916,118,0]An[3096,13,3097,3061,1153,118,0]An[3096,13,3097,3070,4750,118,0]An[3096,13,3097,1366,1427,118,0]An[3096,13,3097,1366,4110,118,0]An[3096,13,3097,1366,3233,118,0]An[3096,13,3097,1366,3040,118,0]An[3096,13,3097,1366,4748,118,0]An[3096,13,3097,1366,4804,118,0]An[3096,13,3097,1366,3207,118,0]An[3096,13,3097,1381,2295,118,0]An[3096,13,3097,1381,3218,118,0]An[3096,13,3097,1381,3042,118,0]An[3096,13,3097,1381,4449,118,0]An[3096,13,3097,1120,3045,118,0]An[3096,13,3097,1120,4241,118,0]An[3096,13,3097,1125,1868,118,0]An[3096,13,3097,1125,3258,118,0]An[3096,13,3097,1125,3940,118,0]An[3096,13,3097,2002,3959,118,0]An[3096,13,3097,2002,4353,118,0]An[3096,13,3097,2002,4075,118,0]An[3096,13,3097,3641,4445,118,0]An[3096,13,3097,3641,3038,118,0]An[3096,13,3097,3641,4319,118,0]An[3096,13,3097,3641,5001,118,0]An[3096,13,3097,3641,1174,118,0]An[3096,13,3097,1116,4459,118,0]An[3096,13,3097,3194,4468,118,0]An[3096,13,3097,3194,4774,118,0]An[3096,13,3097,3194,4789,118,0]An[3096,13,3097,3194,3942,118,0]An[3096,13,3097,1691,4329,118,0]An[3096,13,3097,1691,4116,118,0]An[3096,13,3097,1691,4973,118,0]An[3096,13,3097,1691,4067,118,0]An[3096,13,3097,1691,3932,118,0]An[3096,13,3097,1356,3026,118,0]An[3096,13,3097,1356,4334,118,0]An[3096,13,3097,1356,3224,118,0]An[3096,13,3097,1356,3247,118,0]An[3096,13,3097,1356,4082,118,0]An[3096,13,3097,1356,4443,118,0]An[3096,13,3097,1356,4644,118,0]An[3096,13,3097,1356,2253,118,0]An[3096,13,3097,2305,4648,118,0]An[3096,13,3097,2305,2264,118,0]An[3096,13,3097,2305,4078,118,0]An[3096,13,3097,2305,3926,118,0]An[3096,13,3097,2305,3241,118,0]An[3096,13,3097,1756,4236,118,0]An[3096,13,3097,1756,4457,118,0]An[3096,13,3097,1756,5005,118,0]An[3096,13,3097,1756,2267,118,0]An[3096,13,3097,1756,4746,118,0]An[3096,13,3097,1784,4447,118,0]An[3096,13,3097,1784,4214,118,0]An[3096,13,3097,1784,4081,118,0]An[3096,13,3097,1784,4086,118,0]Ak[3096,13,3097,0,4357,118,0]Ak[3096,13,3097,0,4809,118,0]An[3096,13,3097,4254,3221,118,0]An[3096,13,3097,4254,5099,118,0]An[3096,13,3097,4844,4676,118,0]Am[3096,13,3097,3097,511,118,0]Am[3096,13,3097,3097,127,118,0]An[3096,13,3097,3097,2282,118,0]An[3096,13,3097,3097,2292,118,0]An[3096,13,3097,3097,3024,118,0]An[3096,13,1689,1689,4247,118,0]An[3096,13,4957,4957,4855,118,0]An[3096,13,4957,4957,1171,118,0]An[3096,13,1694,1694,1836,118,0]Am[3096,13,1694,1694,498,118,0]An[3096,13,1694,1694,5065,118,0]An[3096,13,1694,1694,3935,118,0]An[3096,13,1694,1694,4815,118,0]An[3096,13,1774,1774,3253,118,0]An[3096,13,1774,1774,4218,118,0]Ak[3096,13,1774,0,1170,118,0]An[3096,13,1774,1774,4672,118,0]An[3096,13,1774,1774,4911,118,0]An[3096,13,1774,1774,4674,118,0]Al[3096,13,3636,148,121,118,0]Am[3096,13,3636,148,3013,118,0]An[3096,13,3636,1352,1157,118,0]An[3096,13,3636,1352,3937,118,0]An[3096,13,3636,2358,1841,118,0]An[3096,13,3636,2358,4234,118,0]An[3096,13,3636,3636,4738,118,0]An[3096,13,4706,4706,1172,118,0]An[3096,13,4706,4706,1175,118,0]An[1985,13,1986,1986,3256,118,0]Am[1985,13,1986,416,4358,118,0]An[1985,13,1986,1986,2279,118,0]An[1985,13,1986,1986,2293,118,0]An[1985,13,1986,1986,4087,118,0]An[1095,13,1096,1096,3015,118,0]An[1095,13,1096,1096,4848,118,0]Am[1095,13,1683,1683,135,118,0]An[1095,13,1382,1382,1190,118,0]An[1095,13,1382,1382,3961,118,0]An[1095,13,1382,1382,4453,118,0]An[1095,13,4174,4174,5050,118,0]An[1095,13,4174,4174,5051,118,0]Am[1095,13,1779,1779,136,118,0]Aj[463,13,464,3640,4918,0,0]Aj[463,13,464,1094,4359,0,0]Ag[463,13,464,0,4065,0,0]Al[3096,13,3097,1691,4067,0,0]Al[3096,14,3097,1756,5005,0,0]Ai[3096,14,4745,0,4810,0,0]Ah[3096,14,4456,0,499,0,0]Ai[3096,14,4456,0,1184,0,0]Af[463,5,464,3640,0,0,0]Al[1195,15,1196,1196,3928,0,0]Ae[2358,6,2359,0,0,0,0]Ag[463,15,464,0,3025,0,0]Al[3096,15,3097,1356,3026,0,0]Ah[3096,5,3097,1356,0,0,0]Aj[463,15,464,3640,4749,0,0]Al[3096,15,3097,3070,4750,0,0]Af[463,5,464,2304,0,0,0]Ah[3096,5,3097,2305,0,0,0]Al[3096,15,3097,2002,3959,0,0]Af[463,5,464,2336,0,0,0]2Ah[3096,6,3097,1756,0,0,0]Ag[463,19,464,0,1443,0,0]Al[3096,19,3097,1810,1444,0,0]Ag[463,19,464,0,4808,0,0]Ai[3096,19,3097,0,4809,0,0]=Ah[463,12,1693,0,4115,0,0]>Ak[463,13,1773,0,4673,4115,0]>Al[3096,12,1694,1694,4117,0,0]>Ao[3096,13,1774,1774,4674,4117,0]Ao[1095,13,4174,4174,5051,4117,0]An[2358,13,2359,2359,125,3943,0]Ao[2358,13,2359,2359,2250,3943,0]Ag[463,14,464,0,3041,0,0]Al[3096,14,3097,1381,3042,0,0]Ag[463,13,464,0,4114,0,0]Al[3096,13,3097,1691,4116,0,0]Aj[463,13,464,1994,4316,0,0]0>=Al[1095,13,1382,1382,4453,0,0]Ae[1095,2,1096,0,0,0,0]Aj[463,13,464,2336,2263,0,0]Al[3096,13,3097,2305,2264,0,0]Ag[463,14,464,0,3217,0,0]Al[3096,14,3097,1381,3218,0,0]Al[3096,13,3097,3641,3212,0,0]Ag[463,13,464,0,4467,0,0]Al[3096,13,3097,3194,4468,0,0]43Ak[1195,14,1196,1196,134,0,0]Ag[463,14,464,0,2291,0,0]Al[3096,14,3097,3097,2292,0,0]Al[1985,14,1986,1986,2293,0,0]Al[3096,13,3097,2002,1861,0,0]Al[3096,13,3097,4254,5094,0,0]Al[3096,13,3097,4254,5099,0,0]Aj[463,13,464,4039,3243,0,0]Al[3096,13,3097,2002,3244,0,0]=Al[1195,14,1380,1380,1189,0,0]Ao[1095,13,1096,1096,4848,3050,0]Ag[463,15,464,0,3025,0,0]Af[463,5,464,1356,0,0,0]Al[3096,15,3097,1356,3026,0,0]Ah[3096,5,3097,1356,0,0,0]Af[463,6,464,3641,0,0,0]Ah[3096,6,3097,3641,0,0,0]Al[3096,15,3097,2305,4213,0,0]Ah[3096,5,3097,1784,0,0,0]Af[463,5,464,1381,0,0,0]Ah[3096,5,3097,1381,0,0,0]97Ae[1095,5,1382,0,0,0,0]:8Ah[3096,6,3097,1756,0,0,0]Af[463,5,464,1116,0,0,0]Ah[3096,5,3097,1116,0,0,0]Al[3096,15,3097,1366,4748,0,0]Af[463,5,464,1392,0,0,0]Ah[3096,5,3097,1392,0,0,0]Af[463,5,464,3194,0,0,0]Ah[3096,5,3097,3194,0,0,0]Ag[463,15,464,0,4444,0,0]Al[3096,15,3097,3641,4445,0,0]Af[463,5,464,3640,0,0,0]Aj[463,15,464,4034,3044,0,0]Aj[463,15,464,4034,3239,0,0]Aj[463,15,464,4039,3257,0,0]Aj[463,15,464,4039,4120,0,0]An[1195,13,1196,1196,134,4212,0]Ao[1195,13,1196,1196,3952,4212,0]Ao[1195,13,1196,1196,3928,4212,0]Am[463,13,464,3640,3211,4212,0]Am[463,13,464,3640,4918,4212,0]Am[463,13,464,3640,4749,4212,0]Am[463,13,464,2304,2259,4212,0]Am[463,13,464,2304,4215,4212,0]Am[463,13,464,4142,1418,4212,0]Al[463,13,464,1094,503,4212,0]Am[463,13,464,1094,4359,4212,0]Am[463,13,464,4034,4352,4212,0]Am[463,13,464,4034,3044,4212,0]Am[463,13,464,4034,3239,4212,0]Am[463,13,464,4034,4351,4212,0]Am[463,13,464,4039,3257,4212,0]Am[463,13,464,4039,4120,4212,0]Aj[463,13,464,0,1443,4212,0]Aj[463,13,464,0,4915,4212,0]Aj[463,13,464,0,1426,4212,0]Aj[463,13,464,0,2294,4212,0]Aj[463,13,464,0,3217,4212,0]Aj[463,13,464,0,3041,4212,0]Aj[463,13,464,0,4448,4212,0]Aj[463,13,464,0,1867,4212,0]Aj[463,13,464,0,4071,4212,0]Aj[463,13,464,0,4444,4212,0]Aj[463,13,464,0,4467,4212,0]Aj[463,13,464,0,4773,4212,0]Aj[463,13,464,0,4788,4212,0]Aj[463,13,464,0,3941,4212,0]Aj[463,13,464,0,4328,4212,0]Aj[463,13,464,0,4114,4212,0]Aj[463,13,464,0,4972,4212,0]Aj[463,13,464,0,4065,4212,0]Aj[463,13,464,0,3931,4212,0]Aj[463,13,464,0,3025,4212,0]Aj[463,13,464,0,4441,4212,0]Aj[463,13,464,0,4642,4212,0]Aj[463,13,464,0,4077,4212,0]Aj[463,13,464,0,4080,4212,0]Aj[463,13,464,0,4085,4212,0]Aj[463,13,464,0,4808,4212,0]Aj[463,13,464,0,3220,4212,0]Ai[463,13,464,0,510,4212,0]Ai[463,13,464,0,126,4212,0]Aj[463,13,464,0,2281,4212,0]Aj[463,13,3635,0,120,4212,0]Ak[463,13,3635,0,3012,4212,0]Ak[463,13,3635,0,1156,4212,0]Ak[463,13,3635,0,3936,4212,0]Ak[463,13,3635,0,1840,4212,0]Ak[463,13,3635,0,4233,4212,0]Ak[463,13,1773,0,1169,4212,0]Ak[463,13,1773,0,4671,4212,0]Ak[463,13,1773,0,4910,4212,0]Ak[463,13,1773,0,4673,4212,0]Ao[3096,13,3097,1810,1444,4212,0]Ao[3096,13,3097,1810,4916,4212,0]Ao[3096,13,3097,3070,4750,4212,0]Ao[3096,13,3097,1366,1427,4212,0]Ao[3096,13,3097,1366,4110,4212,0]Ao[3096,13,3097,1366,3233,4212,0]Ao[3096,13,3097,1366,3040,4212,0]Ao[3096,13,3097,1366,4748,4212,0]Ao[3096,13,3097,1366,4804,4212,0]Ao[3096,13,3097,1366,3207,4212,0]Ao[3096,13,3097,1381,2295,4212,0]Ao[3096,13,3097,1381,3218,4212,0]Ao[3096,13,3097,1381,3042,4212,0]Ao[3096,13,3097,1381,4449,4212,0]Ao[3096,13,3097,1120,3045,4212,0]Ao[3096,13,3097,1120,4241,4212,0]Ao[3096,13,3097,1125,1868,4212,0]Ao[3096,13,3097,1125,3258,4212,0]Ao[3096,13,3097,1125,3940,4212,0]Ao[3096,13,3097,2002,4353,4212,0]Ao[3096,13,3097,2002,4075,4212,0]Ao[3096,13,3097,3641,4445,4212,0]Ao[3096,13,3097,3641,3038,4212,0]Ao[3096,13,3097,3641,4319,4212,0]Ao[3096,13,3097,3641,5001,4212,0]Ao[3096,13,3097,3194,4468,4212,0]Ao[3096,13,3097,3194,4774,4212,0]Ao[3096,13,3097,3194,4789,4212,0]Ao[3096,13,3097,3194,3942,4212,0]Ao[3096,13,3097,1691,4329,4212,0]Ao[3096,13,3097,1691,4116,4212,0]Ao[3096,13,3097,1691,4973,4212,0]Ao[3096,13,3097,1691,4067,4212,0]Ao[3096,13,3097,1691,3932,4212,0]Ao[3096,13,3097,1356,3026,4212,0]Ao[3096,13,3097,1356,3224,4212,0]Ao[3096,13,3097,1356,3247,4212,0]Ao[3096,13,3097,1356,4082,4212,0]Ao[3096,13,3097,1356,4443,4212,0]Ao[3096,13,3097,1356,4644,4212,0]Ao[3096,13,3097,1356,2253,4212,0]Ao[3096,13,3097,2305,4648,4212,0]Ao[3096,13,3097,2305,4078,4212,0]Ao[3096,13,3097,1756,4457,4212,0]Ao[3096,13,3097,1756,5005,4212,0]Ao[3096,13,3097,1756,2267,4212,0]Ao[3096,13,3097,1756,4746,4212,0]Ao[3096,13,3097,1784,4081,4212,0]Ao[3096,13,3097,1784,4086,4212,0]Al[3096,13,3097,0,4357,4212,0]Al[3096,13,3097,0,4809,4212,0]Ao[3096,13,3097,4254,3221,4212,0]Ao[3096,13,3097,4254,5099,4212,0]An[3096,13,3097,3097,511,4212,0]An[3096,13,3097,3097,127,4212,0]Ao[3096,13,3097,3097,2282,4212,0]Ao[3096,13,3097,3097,3024,4212,0]Ao[3096,13,1689,1689,4247,4212,0]Ao[3096,13,4957,4957,4855,4212,0]Ao[3096,13,4957,4957,1171,4212,0]Al[3096,13,1774,0,1170,4212,0]Ao[3096,13,1774,1774,4672,4212,0]Ao[3096,13,1774,1774,4911,4212,0]Ao[3096,13,1774,1774,4674,4212,0]Am[3096,13,3636,148,121,4212,0]An[3096,13,3636,148,3013,4212,0]Ao[3096,13,3636,1352,1157,4212,0]Ao[3096,13,3636,1352,3937,4212,0]Ao[3096,13,3636,2358,1841,4212,0]Ao[3096,13,3636,2358,4234,4212,0]Ao[3096,13,4706,4706,1175,4212,0]Ao[1985,13,1986,1986,2279,4212,0]Ao[1985,13,1986,1986,2293,4212,0]Ao[1985,13,1986,1986,4087,4212,0]Ao[1095,13,1096,1096,4848,4212,0]Ao[1095,13,1382,1382,4453,4212,0]Ao[1095,13,4174,4174,5050,4212,0]Ao[1095,13,4174,4174,5051,4212,0]Af[463,7,464,1994,0,0,0]Ag[463,13,464,0,4328,0,0]Al[3096,13,3097,1691,4329,0,0]Ah[463,12,1693,0,3035,0,0]Al[3096,12,1694,1694,3036,0,0]Al[1985,14,1986,1986,2279,0,0]Ag[463,14,464,0,3217,0,0]Al[3096,14,3097,1381,3218,0,0]10Ae[1095,7,1096,0,0,0,0]Aj[463,13,464,2304,2259,0,0]Ae[4257,7,4258,0,0,0,0]Ae[1195,7,1196,0,0,0,0]Al[3096,13,3097,3641,3212,0,0]Aj[463,13,464,3640,3211,0,0]1Al[3096,13,3097,2002,4353,0,0]Al[1195,13,1380,1380,1189,0,0]Al[1195,14,1380,1380,3960,0,0]Aj[463,12,464,1994,1852,0,0]Am[463,13,464,1393,4121,1852,0]Am[463,13,464,1137,3955,1852,0]Ah[463,12,1693,0,1853,0,0]Ak[463,13,1773,0,3252,1853,0]Al[3096,12,1694,1694,1854,0,0]Ao[3096,13,1774,1774,3253,1854,0]Af[463,5,464,1094,0,0,0]Ag[463,15,464,0,3025,0,0]Af[463,5,464,1356,0,0,0]Al[3096,15,3097,1356,3026,0,0]Ah[3096,5,3097,1356,0,0,0]Ag[463,15,464,0,4444,0,0]Al[3096,15,3097,3641,4445,0,0]Ah[3096,5,3097,2305,0,0,0]Al[3096,15,3097,3641,1174,0,0]1Ag[463,15,464,0,4077,0,0]Al[3096,15,3097,2305,4078,0,0]Ag[463,19,464,0,4808,0,0]Ai[3096,19,3097,0,4809,0,0]Ah[463,15,1773,0,1169,0,0]Ai[3096,15,1774,0,1170,0,0]32Al[3096,15,3097,3641,3038,0,0]><><><21Ag[463,5,1773,1774,0,0,0]Ae[3096,5,1774,0,0,0,0]10Af[463,5,464,4844,0,0,0]Ah[3096,5,3097,4844,0,0,0]Al[3096,15,4706,4706,1172,0,0]Al[3096,15,3097,1366,4748,0,0]6Al[3096,15,3097,1756,4457,0,0]Ag[463,12,464,0,2287,0,0]Aj[463,13,464,0,4206,2287,0]Aj[463,13,464,0,4098,2287,0]Al[3096,12,3097,1392,2288,0,0]Ao[3096,13,3097,4477,4207,2288,0]Ao[3096,13,3097,1392,4099,2288,0]Ae[3966,7,3967,0,0,0,0]Aj[463,13,464,3640,3211,0,0]Al[3096,13,3097,3641,3212,0,0]Ag[463,14,464,0,3217,0,0]Al[3096,14,3097,1381,3218,0,0]32An[1195,13,1196,1196,134,4330,0]An[2358,13,2359,2359,125,4330,0]Ao[2358,13,2359,2359,2250,4330,0]Ao[1095,13,1096,1096,3015,4330,0]An[1095,13,1683,1683,135,4330,0]An[1095,13,1779,1779,136,4330,0]Ak[1985,13,1986,416,4358,0,0]Ag[463,13,464,0,4467,0,0]Al[3096,13,3097,3194,4468,0,0]:9Ae[3096,2,3097,0,0,0,0]Aj[463,13,464,2304,2259,0,0]Al[3096,13,3097,2305,2260,0,0]?>43Ae[4257,7,4258,0,0,0,0]6Al[1095,14,1096,1096,3015,0,0]70Ae[1195,7,1196,0,0,0,0]Ag[463,13,464,0,4114,0,0]Ag[463,13,464,0,4065,0,0]Al[3096,13,3097,1691,4116,0,0]Al[3096,13,3097,1691,4067,0,0]Ag[463,14,464,0,4114,0,0]Al[3096,14,3097,1691,4116,0,0]53Aj[463,13,464,4039,3243,0,0]Ag[463,19,464,0,4808,0,0]Ai[3096,19,3097,0,4809,0,0]Ag[463,15,464,0,4444,0,0]Al[3096,15,3097,1366,4748,0,0]Al[3096,15,3097,3641,4445,0,0]Ah[463,10,3635,3636,0,0,0]Af[3096,10,3636,0,0,0,0]Ag[463,5,3635,3636,0,0,0]Ae[3096,5,3636,0,0,0,0]Ah[463,10,1693,1694,0,0,0]Af[3096,10,1694,0,0,0,0]Ag[463,15,464,0,4077,0,0]Al[3096,15,3097,2305,4078,0,0]Ag[463,10,464,3641,0,0,0]Ai[3096,10,3097,3641,0,0,0]Ah[3096,6,3097,1756,0,0,0]Ah[3096,6,3097,1366,0,0,0]Af[463,6,464,3640,0,0,0]Ah[3096,6,3097,3070,0,0,0]Ag[463,15,464,0,3025,0,0]Al[3096,15,3097,1356,3026,0,0]10Aj[463,15,464,3640,4749,0,0]Al[3096,15,3097,3070,4750,0,0]Ag[463,14,464,0,3041,0,0]Al[3096,14,3097,1381,3042,0,0]Ah[463,14,3635,0,4737,0,0]Al[3096,14,3636,3636,4738,0,0]Aj[463,13,464,3640,3211,0,0]Ag[463,14,464,0,3217,0,0]Al[3096,14,3097,1381,3218,0,0]10Al[3096,13,3097,3070,3018,0,0]Aj[463,13,464,2304,2259,0,0]Al[3096,13,3097,2305,2260,0,0]Ab[4768,3,0,0,0,0,0]Ao[1095,13,1096,1096,4848,3050,0]Ag[463,19,464,0,4808,0,0]Ai[3096,19,3097,0,4809,0,0]Af[463,5,464,3194,0,0,0]Ah[3096,5,3097,3194,0,0,0]Ah[463,15,1773,0,1169,0,0]Ai[3096,15,1774,0,1170,0,0]54Ai[3096,10,3097,1756,0,0,0]Al[3096,15,3097,3641,1174,0,0]7600Al[3096,15,3097,1120,4241,0,0]4365Al[3096,15,4706,4706,1172,0,0]Ae[1095,9,1096,0,0,0,0]:9Ag[463,15,464,0,3025,0,0]Al[3096,15,3097,1356,3026,0,0]<;<;Ah[3096,6,3097,1366,0,0,0]21Al[1195,15,1196,1196,3952,0,0]Ac[463,5,464,0,0,0,0]Ae[3096,5,3097,0,0,0,0]Ag[463,15,464,0,4077,0,0]Ag[463,5,1693,1694,0,0,0]Al[3096,15,3097,2305,4078,0,0]Al[3096,15,3097,1756,4746,0,0]Ae[3096,5,1694,0,0,0,0]An[1195,13,1196,1196,134,4320,0]An[2358,13,2359,2359,125,4320,0]Ao[1095,13,1096,1096,3015,4320,0]An[1095,13,1683,1683,135,4320,0]An[1095,13,1779,1779,136,4320,0]Ag[463,13,464,0,4467,0,0]Al[3096,13,3097,3194,4468,0,0]Ag[463,13,464,0,1443,0,0]Al[3096,13,3097,1810,1444,0,0]Aj[463,14,464,2304,4215,0,0]Aj[463,13,464,3640,4918,0,0]Ag[463,13,464,0,4328,0,0]Al[3096,13,3097,1691,4329,0,0]Ag[463,13,464,0,4206,0,0]Al[3096,13,3097,4477,4207,0,0]Ag[463,14,464,0,3217,0,0]Al[3096,14,3097,1381,3218,0,0]7Al[3096,13,3097,3641,3212,0,0]Aj[463,13,464,3640,3211,0,0]Ah[463,13,1773,0,4671,0,0]Ah[463,13,1773,0,4673,0,0]Al[3096,13,1774,1774,4672,0,0]Al[3096,13,1774,1774,4674,0,0]45;:Ao[3966,13,3967,3967,5006,4802,0]Ag[463,13,464,0,4458,0,0]Al[3096,13,3097,1116,4459,0,0]Ae[1095,6,1096,0,0,0,0]Af[463,5,464,4142,0,0,0]Ag[463,15,464,0,3025,0,0]Al[3096,15,3097,1356,3026,0,0]10Al[3096,15,3097,3641,3038,0,0]Ae[3096,5,4957,0,0,0,0]Af[463,5,464,4034,0,0,0]Ah[463,15,1773,0,1169,0,0]Ai[3096,15,1774,0,1170,0,0]Al[3096,15,4706,4706,1172,0,0]Aj[463,14,464,2304,4215,0,0]Ai[463,13,464,0,510,3006,0]Ai[463,13,464,0,126,3006,0]An[3096,13,3097,3097,511,3006,0]An[3096,13,3097,3097,127,3006,0]Ao[2358,13,2359,2359,2250,3248,0]Ag[463,13,464,0,4788,0,0]Al[3096,13,3097,3194,4789,0,0]Ah[463,12,1693,0,1853,0,0]Ak[463,13,1773,0,3252,1853,0]Al[3096,12,1694,1694,1854,0,0]Ao[3096,13,1774,1774,3253,1854,0]Aj[463,13,464,3640,3211,0,0]Ag[463,12,464,0,4743,0,0]Al[3096,12,3097,3641,4744,0,0]Ag[463,13,464,0,4467,0,0]Al[3096,13,3097,3194,4468,0,0]Ag[463,14,464,0,3041,0,0]Al[3096,14,3097,1381,3042,0,0]Ah[463,12,3635,0,3213,0,0]Aj[463,13,3635,0,120,3213,0]Ak[463,13,3635,0,1156,3213,0]Ak[463,13,3635,0,1840,3213,0]Al[3096,12,3636,3636,3214,0,0]Am[3096,13,3636,148,121,3214,0]Ao[3096,13,3636,1352,1157,3214,0]Ao[3096,13,3636,2358,1841,3214,0]Aj[463,14,464,2304,4215,0,0]Al[3096,13,3097,2002,1861,0,0]Ah[3096,2,4252,4038,0,0,0]Ae[1195,7,1196,0,0,0,0]==Ai[463,13,464,1094,503,0,0]Ak[1985,13,1986,416,4358,0,0]032Ah[463,12,1693,0,1853,0,0]Ak[463,13,1773,0,3252,1853,0]Ae[3096,2,3097,0,0,0,0]Al[3096,12,1694,1694,1854,0,0]Ao[3096,13,1774,1774,3253,1854,0]Ag[463,10,464,4254,0,0,0]Ai[3096,10,3097,4254,0,0,0]Ah[463,15,1773,0,1169,0,0]Ai[3096,15,1774,0,1170,0,0]Ag[463,15,464,0,4077,0,0]Al[3096,15,3097,2305,4078,0,0]Ag[463,19,464,0,4808,0,0]Ai[3096,19,3097,0,4809,0,0]Af[463,5,464,1691,0,0,0]Ah[3096,5,3097,1691,0,0,0]Ah[463,12,1693,0,3035,0,0]Al[3096,12,1694,1694,3036,0,0]Aj[463,13,464,2304,2259,0,0]Al[3096,13,3097,3641,3212,0,0]Al[3096,13,3097,2305,2260,0,0]Al[3096,13,3097,3097,3024,0,0]Ah[463,13,3635,0,4233,0,0]Al[3096,13,3636,2358,4234,0,0]Aj[463,13,464,1994,3048,0,0]0Aj[463,13,464,3640,3211,0,0]6Ag[463,14,464,0,3217,0,0]Al[3096,14,3097,1381,3218,0,0]Ag[463,13,464,0,4467,0,0]Al[3096,13,3097,3194,4468,0,0]Al[1095,14,1096,1096,3015,0,0]Ag[463,19,464,0,4808,0,0]Ai[3096,19,3097,0,4809,0,0]Al[3096,15,4706,4706,1172,0,0]Ah[463,15,1773,0,1169,0,0]Ai[3096,15,1774,0,1170,0,0]10Al[3096,15,3097,1366,4804,0,0]Al[3096,15,3097,3641,3038,0,0]Al[3096,15,3097,3641,4319,0,0]Ah[3096,5,3097,3641,0,0,0]Al[3096,15,3097,1366,4748,0,0]Ag[463,15,464,0,3025,0,0]Al[3096,15,3097,1356,3026,0,0]Ah[3096,5,3097,1756,0,0,0]Ae[3966,5,3967,0,0,0,0]Al[1095,13,1096,1096,3015,0,0]Aj[463,13,464,3640,3211,0,0]Ah[463,12,1693,0,1853,0,0]Ak[463,13,1773,0,3252,1853,0]Al[3096,12,1694,1694,1854,0,0]Ao[3096,13,1774,1774,3253,1854,0]Ah[3096,7,4890,4889,0,0,0]0Al[3096,13,3097,3641,3212,0,0]6Ai[463,13,464,1094,503,0,0]Ag[463,14,464,0,3217,0,0]Al[3096,14,3097,1381,3218,0,0]Ao[3966,13,3967,3967,5006,4802,0]0Ag[463,13,464,0,4065,0,0]Al[3096,13,3097,1691,4067,0,0]Ah[463,15,1773,0,1169,0,0]Ai[3096,15,1774,0,1170,0,0]10Ag[463,13,464,0,4773,0,0]Al[3096,13,3097,3194,4774,0,0]Am[1195,13,1196,1196,134,485,0]Am[2358,13,2359,2359,125,485,0]An[1095,13,1096,1096,3015,485,0]Am[1095,13,1683,1683,135,485,0]Am[1095,13,1779,1779,136,485,0]??>An[1195,13,1196,1196,134,4330,0]An[2358,13,2359,2359,125,4330,0]Ao[2358,13,2359,2359,2250,4330,0]Ao[1095,13,1096,1096,3015,4330,0]An[1095,13,1683,1683,135,4330,0]An[1095,13,1779,1779,136,4330,0]Ag[463,13,464,0,4114,0,0]Al[3096,13,3097,1691,4116,0,0]Ag[463,19,464,0,4808,0,0]Ai[3096,19,3097,0,4809,0,0]Ae[1095,5,4174,0,0,0,0]0Ag[463,13,464,0,4328,0,0]Al[3096,13,3097,1691,4329,0,0]Ag[463,13,464,0,4065,0,0]Al[3096,13,3097,1691,4067,0,0]Aj[463,13,464,2304,2259,0,0]Aj[463,13,464,2336,2263,0,0]Al[3096,13,3097,2305,2264,0,0]Al[3096,13,3097,3641,3212,0,0]Ao[3966,13,3967,3967,5006,4802,0];:Ag[463,5,1693,1694,0,0,0]Ae[3096,5,1694,0,0,0,0]Ah[463,15,1773,0,1169,0,0]Ai[3096,15,1774,0,1170,0,0]?>?>Aj[463,13,464,3640,3211,0,0]6Ag[463,13,464,0,4467,0,0]Al[3096,13,3097,3194,4468,0,0]Ae[3966,7,3967,0,0,0,0]Ae[4257,7,4258,0,0,0,0]Ag[463,14,464,0,3217,0,0]Al[3096,14,3097,1381,3218,0,0]?434343>=>=Al[3096,14,4957,4957,4855,0,0]54Ai[3096,18,3097,3194,0,0,0]") \ 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 new file mode 100644 index 000000000..d6f75a4cb --- /dev/null +++ b/pr/451/docs/search.index/entry/8a78b0eadc5e.js @@ -0,0 +1 @@ +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]?>>>>>A`[\"{IBfBB@j}\",[]]A`[\"{KDhBEIb}\",[]]A`[\"{cc{}}\",[\"T\"]]00000A`[\"{KDhAAMd}\",[]]111111A`[\"{BABnIBf}\",[]]222Aa[\"{ANOdANKn}\",[]]3333A`[\"{BGJ`IBd}\",[]]A`[\"{BDF`IBd}\",[]]A`[\"{AICfIBd}\",[]]A`[\"{BDGhIBd}\",[]]7A`[\"{BBLdIBd}\",[]]A`[\"{BAO`IBd}\",[]]A`[\"{BBMnIBd}\",[]]A`[\"{BGHnIBd}\",[]]A`[\"{BDDjIBd}\",[]]A`[\"{BAMnIBd}\",[]]A`[\"{BGD`IBd}\",[]]>>>>>>>>>>>>An[\"{{{AHEf{c}}}AIG`CLl}\",[\"S\"]]??????????????Bm[\"{{{AHEf{KDf}}{AHEf{CEdc}}}HOjKA`}\",[\"__H\"]]Bk[\"{{{AHEf{KBd}}{AHEf{CEdc}}}HOjKA`}\",[\"H\"]]Bn[\"{{{AHEf{ANLj}}{AHEf{CEdc}}}HOjKA`}\",[\"__H\"]]Bn[\"{{{AHEf{AON`}}{AHEf{CEdc}}}HOjKA`}\",[\"__H\"]]Bm[\"{{{AHEf{COl}}{AHEf{CEdc}}}HOjKA`}\",[\"__H\"]]Bl[\"{{{AHEf{Ol}}{AHEf{CEdc}}}HOjKA`}\",[\"__H\"]]Bn[\"{{{AHEf{AAMb}}{AHEf{CEdc}}}HOjKA`}\",[\"__H\"]]Bm[\"{{{AHEf{KDh}}{AHEf{CEdc}}}HOjKA`}\",[\"__H\"]]Bn[\"{{{AHEf{BEAl}}{AHEf{CEdc}}}HOjKA`}\",[\"__H\"]]Bk[\"{{{AHEf{KBf}}{AHEf{CEdc}}}HOjKA`}\",[\"H\"]]Bn[\"{{{AHEf{AGLj}}{AHEf{CEdc}}}HOjKA`}\",[\"__H\"]]Bn[\"{{{AHEf{BABb}}{AHEf{CEdc}}}HOjKA`}\",[\"__H\"]]Bn[\"{{{AHEf{AIGd}}{AHEf{CEdc}}}HOjKA`}\",[\"__H\"]]Bn[\"{{{AHEf{ANLl}}{AHEf{CEdc}}}HOjKA`}\",[\"__H\"]]Bn[\"{{{AHEf{BDE`}}{AHEf{CEdc}}}HOjKA`}\",[\"__H\"]]Bn[\"{{{AHEf{BEAd}}{AHEf{CEdc}}}HOjKA`}\",[\"__H\"]]Bn[\"{{{AHEf{AONb}}{AHEf{CEdc}}}HOjKA`}\",[\"__H\"]]Bm[\"{{{AHEf{COn}}{AHEf{CEdc}}}HOjKA`}\",[\"__H\"]]Bl[\"{{{AHEf{On}}{AHEf{CEdc}}}HOjKA`}\",[\"__H\"]]Bn[\"{{{AHEf{AAMd}}{AHEf{CEdc}}}HOjKA`}\",[\"__H\"]]Bn[\"{{{AHEf{AGJ`}}{AHEf{CEdc}}}HOjKA`}\",[\"__H\"]]Aa[\"{{}c{}}\",[\"U\"]]00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000n[\"{{}HOj}\",[]]0000Cc[\"{{{AHEf{CEdJOn}}JNf}{{KBj{{KB`{I@b}}AGLj}}}}\",[]]Cf[\"{{{AHEf{CEdBA@`}}}{{KB`{c}}}{}}\",[\"Iterator::Item\"]]Dl[\"{{{AHEf{BE@f}}HOh{AHEf{CEd{JL`{Nd}}}}}{{KBj{{AHEf{{JL`{Nd}}}}ANKl}}}}\",[]]Bh[\"{{{AHEf{CEdNHj}}On}{{KB`{AAMd}}}}\",[]]Dl[\"{{{AHEf{BE@h}}HOh{AHEf{CEd{JL`{Nd}}}}}{{KBj{{AHEf{{JL`{Nd}}}}ANKn}}}}\",[]]Bf[\"{{{AHEf{AIAf}}KBd}{{KB`{CNn}}}}\",[]]Bf[\"{{{AHEf{CNn}}}{{KBj{HOjAI@h}}}}\",[]]A`[\"{AIBbCLf}\",[]]Am[\"{{{AHEf{CEdAIAh}}}HOj}\",[]]A`[\"{AIBdCLf}\",[]]Ck[\"{{{AAd{{AHEf{CEdAI@`}}}}{AHEf{CEdNDb}}}{{COf{HOj}}}}\",[]]Dk[\"{{{AAd{{AHEf{CEdBBO`}}}}{AHEf{CEdNDb}}}{{COf{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{AAd{{AHEf{CEdAALd}}}}{AHEf{CEdNDb}}}{{COf{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{AAd{{AHEf{CEdAALf}}}}{AHEf{CEdNDb}}}{{COf{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{AAd{{AHEf{CEdAIAb}}}}{AHEf{CEdNDb}}}{{COf{c}}}{}}\",[\"Future::Output\"]]Dj[\"{{{AAd{{AHEf{CEdNFf}}}}{AHEf{CEdNDb}}}{{COf{c}}}{}}\",[\"Future::Output\"]]Dj[\"{{{AAd{{AHEf{CEdKAf}}}}{AHEf{CEdNDb}}}{{COf{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{AAd{{AHEf{CEdAGGh}}}}{AHEf{CEdNDb}}}{{COf{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{AAd{{AHEf{CEdAAHn}}}}{AHEf{CEdNDb}}}{{COf{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{AAd{{AHEf{CEdB@Ad}}}}{AHEf{CEdNDb}}}{{COf{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{AAd{{AHEf{CEdB@An}}}}{AHEf{CEdNDb}}}{{COf{c}}}{}}\",[\"Future::Output\"]]Dj[\"{{{AAd{{AHEf{CEdJNn}}}}{AHEf{CEdNDb}}}{{COf{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{AAd{{AHEf{CEdBAMj}}}}{AHEf{CEdNDb}}}{{COf{c}}}{}}\",[\"Future::Output\"]]Dj[\"{{{AAd{{AHEf{CEdNHb}}}}{AHEf{CEdNDb}}}{{COf{c}}}{}}\",[\"Future::Output\"]]Bf[\"{{{AHEf{CEdAIAh}}}{{KB`{IBl}}}}\",[]]De[\"{{{AHEf{CEdAIDj}}{AHEf{CEd{JL`{Nd}}}}}{{KBj{{KB`{JNf}}AGLh}}}}\",[]]Bm[\"{{{AHEf{CEdAIDl}}D@n}{{KBj{JOnBABb}}}}\",[]]Bo[\"{{COn{AHEf{CEdc}}}{{KBj{BENnIBf}}}Oh}\",[\"R\"]]Bf[\"{{{AHEf{CEdAGId}}}{{KB`{HOn}}}}\",[]]Ec[\"{{{AHEf{BB@l}}AIG`{AHEf{CEd{JL`{AIBl}}}}{AHEf{CEd{JL`{AALn}}}}}{{KBl{JNf}}}}\",[]]Cn[\"{{{AHEf{BE@f}}HOh{AHEf{CEd{AAj{Nd}}}}}{{KBj{HOjANKl}}}}\",[]]Cn[\"{{{AHEf{BE@h}}HOh{AHEf{CEd{AAj{Nd}}}}}{{KBj{HOjANKn}}}}\",[]]C`[\"{{{AHEf{CEdAGId}}HOnD@n}{{KBj{HOjBEAl}}}}\",[]]Cc[\"{{{AHEf{BB@l}}AIG`{AHEf{AANj}}}{{KBl{HOj}}}}\",[]]Bn[\"{{{AHEf{CEdBFMh}}CLd}{{AHEf{CEdBFMh}}}}\",[]]Bn[\"{{{AHEf{CEdBFMj}}CLd}{{AHEf{CEdBFMj}}}}\",[]]Aj[\"{{{AHEf{AIAf}}}COl}\",[]]Aj[\"{{{AHEf{AIAh}}}COn}\",[]]Cn[\"{{{AHEf{NDn}}{AHEf{{JL`{Nd}}}}{AHEf{CEd{JL`{Nd}}}}}HOj}\",[]]Cn[\"{{{AHEf{NE`}}{AHEf{{JL`{Nd}}}}{AHEf{CEd{JL`{Nd}}}}}HOj}\",[]]A`[\"{BB@dCLf}\",[]]A`[\"{AANfJNf}\",[]]A`[\"{AANhJNf}\",[]]o[\"{IDdD@n}\",[]]Bm[\"{{{AHEf{CEdAIDj}}KDf}{{KBj{HOjAOLn}}}}\",[]]Bm[\"{{{AHEf{CEdBEO`}}KDf}{{KBj{HOjAOLn}}}}\",[]]Bm[\"{{{AHEf{CEdAIDl}}KDh}{{KBj{HOjAOMf}}}}\",[]]Bn[\"{{{AHEf{AIFl}}{AHEf{CL`}}}{{KB`{HOn}}}}\",[]]Bn[\"{{{AHEf{BANl}}{AHEf{CL`}}}{{KB`{HOn}}}}\",[]]Bn[\"{{{AHEf{BDHf}}{AHEf{CL`}}}{{KB`{HOn}}}}\",[]]Bn[\"{{{AHEf{AIFn}}{AHEf{CL`}}}{{KB`{HOn}}}}\",[]]Bn[\"{{{AHEf{BANn}}{AHEf{CL`}}}{{KB`{HOn}}}}\",[]]Bn[\"{{{AHEf{BDHh}}{AHEf{CL`}}}{{KB`{HOn}}}}\",[]]Bd[\"{{{AHEf{BDEf}}}{{KB`{BA@`}}}}\",[]]Bg[\"{{{Nn{BDOn}}NEdCLb}{{Of{AIAj}}}}\",[]]Bg[\"{{{Nn{AGHh}}NEdCLb}{{Of{AIAj}}}}\",[]]Bg[\"{{{Nn{ANL`}}NEdCLb}{{Of{AIAj}}}}\",[]]Bg[\"{{{Nn{BAAb}}NEdCLb}{{Of{AIAj}}}}\",[]]Bg[\"{{{Nn{BE@`}}NEdCLb}{{Of{AIAl}}}}\",[]]Bg[\"{{{Nn{AGHj}}NEdCLb}{{Of{AIAl}}}}\",[]]Bg[\"{{{Nn{ANLb}}NEdCLb}{{Of{AIAl}}}}\",[]]Bg[\"{{{Nn{BAAd}}NEdCLb}{{Of{AIAl}}}}\",[]]Ai[\"{BEN`{{Nn{BE@`}}}}\",[]]o[\"{I@`HOn}\",[]]A`[\"{AANlCLf}\",[]]o[\"{NIfJNf}\",[]]o[\"{I@bHOn}\",[]]A`[\"{AANnCLf}\",[]]o[\"{NIhJNf}\",[]]Aa[\"{ANJlBBKj}\",[]]Ai[\"{{{AHEf{A@l}}}A@l}\",[]]Ak[\"{{{AHEf{ANN`}}}ANN`}\",[]]Ak[\"{{{AHEf{ANK`}}}ANK`}\",[]]Ak[\"{{{AHEf{AIAf}}}AIAf}\",[]]Ak[\"{{{AHEf{BFFl}}}BFFl}\",[]]Ak[\"{{{AHEf{BEAj}}}BEAj}\",[]]Ak[\"{{{AHEf{AAJf}}}AAJf}\",[]]Ak[\"{{{AHEf{B@Nn}}}B@Nn}\",[]]Ai[\"{{{AHEf{KAd}}}KAd}\",[]]Ai[\"{{{AHEf{CNn}}}CNn}\",[]]Ak[\"{{{AHEf{BB@n}}}BB@n}\",[]]Ak[\"{{{AHEf{BB@`}}}BB@`}\",[]]Ak[\"{{{AHEf{AGLh}}}AGLh}\",[]]Ak[\"{{{AHEf{AIDn}}}AIDn}\",[]]Ak[\"{{{AHEf{BAOn}}}BAOn}\",[]]Ak[\"{{{AHEf{AIGb}}}AIGb}\",[]]Ak[\"{{{AHEf{B@C`}}}B@C`}\",[]]Ai[\"{{{AHEf{KDf}}}KDf}\",[]]Ak[\"{{{AHEf{BFFf}}}BFFf}\",[]]Ai[\"{{{AHEf{KBd}}}KBd}\",[]]Ak[\"{{{AHEf{AANl}}}AANl}\",[]]Ak[\"{{{AHEf{AIBb}}}AIBb}\",[]]Ak[\"{{{AHEf{AGLb}}}AGLb}\",[]]Ak[\"{{{AHEf{BBL`}}}BBL`}\",[]]Ai[\"{{{AHEf{NIf}}}NIf}\",[]]Ak[\"{{{AHEf{AOLn}}}AOLn}\",[]]Ak[\"{{{AHEf{BBKh}}}BBKh}\",[]]Ak[\"{{{AHEf{BBNf}}}BBNf}\",[]]Ak[\"{{{AHEf{BEDj}}}BEDj}\",[]]Ak[\"{{{AHEf{BEFh}}}BEFh}\",[]]Ak[\"{{{AHEf{ANLj}}}ANLj}\",[]]Ak[\"{{{AHEf{BAM`}}}BAM`}\",[]]Ak[\"{{{AHEf{B@Bd}}}B@Bd}\",[]]Ak[\"{{{AHEf{BFMh}}}BFMh}\",[]]Ak[\"{{{AHEf{AOLb}}}AOLb}\",[]]Ak[\"{{{AHEf{ANKf}}}ANKf}\",[]]Ak[\"{{{AHEf{AGJb}}}AGJb}\",[]]Ak[\"{{{AHEf{BBKb}}}BBKb}\",[]]Ak[\"{{{AHEf{BDDd}}}BDDd}\",[]]Ak[\"{{{AHEf{AOMj}}}AOMj}\",[]]Ak[\"{{{AHEf{AON`}}}AON`}\",[]]Ak[\"{{{AHEf{AONj}}}AONj}\",[]]Ak[\"{{{AHEf{BEI`}}}BEI`}\",[]]Ak[\"{{{AHEf{AIBh}}}AIBh}\",[]]Ai[\"{{{AHEf{COl}}}COl}\",[]]Ag[\"{{{AHEf{Ol}}}Ol}\",[]]Ak[\"{{{AHEf{AAMb}}}AAMb}\",[]]Ag[\"{{{AHEf{O`}}}O`}\",[]]Ak[\"{{{AHEf{AGHh}}}AGHh}\",[]]Ai[\"{{{AHEf{I@h}}}I@h}\",[]]Ak[\"{{{AHEf{ANL`}}}ANL`}\",[]]Ai[\"{{{AHEf{NF`}}}NF`}\",[]]Ak[\"{{{AHEf{BAAb}}}BAAb}\",[]]Ai[\"{{{AHEf{IBb}}}IBb}\",[]]Ak[\"{{{AHEf{BDGn}}}BDGn}\",[]]Ak[\"{{{AHEf{BFEl}}}BFEl}\",[]]Ak[\"{{{AHEf{BDHb}}}BDHb}\",[]]Ai[\"{{{AHEf{KDh}}}KDh}\",[]]Ak[\"{{{AHEf{BFFh}}}BFFh}\",[]]Ak[\"{{{AHEf{BEAl}}}BEAl}\",[]]Ai[\"{{{AHEf{KBf}}}KBf}\",[]]Ak[\"{{{AHEf{B@Al}}}B@Al}\",[]]Ak[\"{{{AHEf{AIDb}}}AIDb}\",[]]Ak[\"{{{AHEf{AGL`}}}AGL`}\",[]]Ak[\"{{{AHEf{BEAh}}}BEAh}\",[]]Ak[\"{{{AHEf{BEHh}}}BEHh}\",[]]Ak[\"{{{AHEf{AI@n}}}AI@n}\",[]]Ak[\"{{{AHEf{AANn}}}AANn}\",[]]Ak[\"{{{AHEf{AIBd}}}AIBd}\",[]]Ak[\"{{{AHEf{AGLd}}}AGLd}\",[]]Ak[\"{{{AHEf{BBLb}}}BBLb}\",[]]Ak[\"{{{AHEf{AGLj}}}AGLj}\",[]]Ak[\"{{{AHEf{BABb}}}BABb}\",[]]Ai[\"{{{AHEf{NIh}}}NIh}\",[]]Ak[\"{{{AHEf{AIGd}}}AIGd}\",[]]Ak[\"{{{AHEf{ANLh}}}ANLh}\",[]]Ak[\"{{{AHEf{BB@b}}}BB@b}\",[]]Ak[\"{{{AHEf{AOMf}}}AOMf}\",[]]Ak[\"{{{AHEf{BBKj}}}BBKj}\",[]]Ak[\"{{{AHEf{AGKl}}}AGKl}\",[]]Ak[\"{{{AHEf{BAKn}}}BAKn}\",[]]Ak[\"{{{AHEf{BGAb}}}BGAb}\",[]]Ak[\"{{{AHEf{BBNh}}}BBNh}\",[]]Ak[\"{{{AHEf{BEDl}}}BEDl}\",[]]Ak[\"{{{AHEf{BEFj}}}BEFj}\",[]]Ak[\"{{{AHEf{ANLl}}}ANLl}\",[]]Ak[\"{{{AHEf{BAMb}}}BAMb}\",[]]Ak[\"{{{AHEf{B@Bh}}}B@Bh}\",[]]Ak[\"{{{AHEf{BFMj}}}BFMj}\",[]]Ak[\"{{{AHEf{AOLf}}}AOLf}\",[]]Ak[\"{{{AHEf{ANKh}}}ANKh}\",[]]Ak[\"{{{AHEf{AGJd}}}AGJd}\",[]]Ak[\"{{{AHEf{AIC`}}}AIC`}\",[]]Ak[\"{{{AHEf{AIEn}}}AIEn}\",[]]Ak[\"{{{AHEf{AONd}}}AONd}\",[]]Ak[\"{{{AHEf{BBKf}}}BBKf}\",[]]Ak[\"{{{AHEf{BDDh}}}BDDh}\",[]]Ak[\"{{{AHEf{AAIj}}}AAIj}\",[]]Ak[\"{{{AHEf{BDE`}}}BDE`}\",[]]Ak[\"{{{AHEf{AOMl}}}AOMl}\",[]]Ak[\"{{{AHEf{BBMb}}}BBMb}\",[]]Ak[\"{{{AHEf{BGAj}}}BGAj}\",[]]Ak[\"{{{AHEf{AAKf}}}AAKf}\",[]]Ak[\"{{{AHEf{BEAd}}}BEAd}\",[]]Ak[\"{{{AHEf{AONb}}}AONb}\",[]]Ak[\"{{{AHEf{AONl}}}AONl}\",[]]Ak[\"{{{AHEf{BB@j}}}BB@j}\",[]]Ak[\"{{{AHEf{BEIb}}}BEIb}\",[]]Ak[\"{{{AHEf{AIBj}}}AIBj}\",[]]Ak[\"{{{AHEf{BGMf}}}BGMf}\",[]]Ai[\"{{{AHEf{COn}}}COn}\",[]]Ag[\"{{{AHEf{On}}}On}\",[]]Ak[\"{{{AHEf{AAMd}}}AAMd}\",[]]Ak[\"{{{AHEf{AGJ`}}}AGJ`}\",[]]Ak[\"{{{AHEf{BABn}}}BABn}\",[]]Ak[\"{{{AHEf{BENn}}}BENn}\",[]]Ai[\"{{{AHEf{IBf}}}IBf}\",[]]Ai[\"{{{AHEf{IBd}}}IBd}\",[]]Ak[\"{{{AHEf{BDH`}}}BDH`}\",[]]Ak[\"{{{AHEf{BFEn}}}BFEn}\",[]]Ak[\"{{{AHEf{BDHd}}}BDHd}\",[]]Ag[\"{{{AHEf{Ob}}}Ob}\",[]]Ak[\"{{{AHEf{AGHj}}}AGHj}\",[]]Ai[\"{{{AHEf{I@j}}}I@j}\",[]]Ak[\"{{{AHEf{ANLb}}}ANLb}\",[]]Ai[\"{{{AHEf{NFb}}}NFb}\",[]]Ak[\"{{{AHEf{BAAd}}}BAAd}\",[]]Ai[\"{{{AHEf{IBn}}}IBn}\",[]]Ak[\"{{{AHEf{AALn}}}AALn}\",[]]Ak[\"{{{AHEf{AANj}}}AANj}\",[]]Ak[\"{{{AHEf{AONn}}}AONn}\",[]]Ak[\"{{{AHEf{BEN`}}}BEN`}\",[]]Ak[\"{{{AHEf{BBLj}}}BBLj}\",[]]Ak[\"{{{AHEf{BGGd}}}BGGd}\",[]]Ak[\"{{{AHEf{BGGf}}}BGGf}\",[]]Bn[\"{{{AHEf{AIAf}}KDf{AHEf{{JL`{Nd}}}}}HOj}\",[]]Bn[\"{{{AHEf{AAJf}}KDf{AHEf{{JL`{Nd}}}}}HOj}\",[]]Bf[\"{{{AHEf{CNn}}}{{KBj{HOjBAKl}}}}\",[]]Bf[\"{{{AHEf{CEdAIAh}}NEdKDhHOn}HOj}\",[]]Bj[\"{{{AHEf{AON`}}}{{AHEf{{JL`{Nd}}}}}}\",[]]Bj[\"{{{AHEf{AONb}}}{{AHEf{{JL`{Nd}}}}}}\",[]]Aa[\"{BB@jAIC`}\",[]]A`[\"{AAMbCLf}\",[]]A`[\"{AAMdCLf}\",[]]A`[\"{AONjD@n}\",[]]A`[\"{AONlD@n}\",[]]l[\"{NEf}\",[]]l[\"{NEh}\",[]]Ce[\"{{{AHEf{BEEf}}{AHEf{CEdOh}}}{{KBj{AONbBEAd}}}}\",[]]Ce[\"{{{AHEf{BH@h}}{AHEf{CEdOh}}}{{KBj{AONbBEAd}}}}\",[]]Bc[\"{{{AHEf{AIAh}}}{{AAj{KBf}}}}\",[]]Bd[\"{{{AHEf{IDj}}{AHEf{CL`}}}HOj}\",[]]Am[\"{{{AHEf{ANKb}}JNf}HOj}\",[]]Ai[\"{{{AHEf{IDl}}}HOj}\",[]]Bg[\"{{{AAd{{AHEf{CEdAI@`}}}}NEd}HOj}\",[]]Bm[\"{{{AHEf{CEdAIEf}}KDf}{{KBj{HOjAOLn}}}}\",[]]Bm[\"{{{AHEf{CEdAIEh}}KDh}{{KBj{HOjAOMf}}}}\",[]]Am[\"{AAJn{{KBj{HOjAIE`}}}}\",[]]Dc[\"{{{AHEf{CEdAAJh}}AAK`{AHEf{CEd{AAj{Nd}}}}}{{KBj{AANhAIEb}}}}\",[]]Bi[\"{{{AHEf{NGh}}{AAd{{Of{K@n}}}}}HOj}\",[]]Bj[\"{{{AHEf{B@Cb}}{AAd{{Of{K@n}}}}}HOj}\",[]]Bj[\"{{{AHEf{ANNf}}{AAd{{Of{K@n}}}}}HOj}\",[]]o[\"{A@lD@n}\",[]]Ak[\"{{{AHEf{AIAf}}}BBL`}\",[]]Ak[\"{{{AHEf{AAJf}}}B@Nn}\",[]]Aj[\"{{{AHEf{CNn}}}AGLb}\",[]]An[\"{{{AHEf{CEdAIAh}}}BBLb}\",[]]Ce[\"{{{AHEf{CEdAIDd}}{KB`{KDb}}}{{AHEf{CEdAIDd}}}}\",[]]Ce[\"{{{AHEf{CEdAIDf}}{KB`{KDb}}}{{AHEf{CEdAIDf}}}}\",[]]Ck[\"{{{AHEf{CEdAIEf}}{AHEf{{JL`{Nd}}}}}{{KBj{JNfAIGb}}}}\",[]]Ck[\"{{{AHEf{CEdAIEh}}{AHEf{{JL`{Nd}}}}}{{KBj{JNfAIGd}}}}\",[]]Bl[\"{{{AHEf{BENn}}{AHEf{CEdc}}}HOjJOl}\",[\"W\"]]Aj[\"{{{AHEf{AAJf}}}JNn}\",[]]An[\"{AAJn{{KBj{AIAbBBKh}}}}\",[]]Bh[\"{{{AHEf{CEdNHj}}On}{{KB`{AAMd}}}}\",[]]Fh[\"{{{AHEf{CEdAAJh}}AAK`NEd{AHEf{CEd{AAj{Nd}}}}{KB`{{Nn{B@Bh}}}}}{{KBj{{JNd{BDE`AIAh}}{Of{ANJl}}}}}}\",[]]Am[\"{AHEf{{AHEf{c}}}{}}\",[\"T\"]]00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000o[\"{NIfJNf}\",[]]o[\"{NIhJNf}\",[]]A`[\"{A@lANK`}\",[]]Bc[\"{{AIElBAKjA@l}{{KBn{ANKb}}}}\",[]]Aj[\"{AIEl{{KBl{AAJf}}}}\",[]]B`[\"{{}{{KBj{HOj{Of{IAd}}}}}}\",[]]Ak[\"{{{AHEf{AIAf}}}BBKh}\",[]]Be[\"{{{AHEf{AAJh}}}{{AHEf{BAMb}}}}\",[]]o[\"{NdBEI`}\",[]]A`[\"{AIBbCLf}\",[]]o[\"{B@BdNn}\",[]]o[\"{NdBEIb}\",[]]A`[\"{AIBdCLf}\",[]]o[\"{B@BhNn}\",[]]A`[\"{BB@jKB`}\",[]]Bl[\"{{{AHEf{CEdc}}}{{KBj{KDfBABn}}}Oh}\",[\"B\"]]Bl[\"{{{AHEf{CEdc}}}{{KBj{KBdBABn}}}Oh}\",[\"B\"]]Bm[\"{{{AHEf{CEdc}}}{{KBj{AGJbBABn}}}Oh}\",[\"B\"]]Bm[\"{{{AHEf{CEdc}}}{{KBj{BEI`BABn}}}Oh}\",[\"B\"]]Bm[\"{{{AHEf{CEdc}}}{{KBj{AAMbBABn}}}Oh}\",[\"B\"]]Bh[\"{{{AHEf{CEdc}}}{{KCb{KDh}}}Oh}\",[\"B\"]]Bh[\"{{{AHEf{CEdc}}}{{KCb{KBf}}}Oh}\",[\"B\"]]Bi[\"{{{AHEf{CEdc}}}{{KCb{AGJd}}}Oh}\",[\"B\"]]Bi[\"{{{AHEf{CEdc}}}{{KCb{AIC`}}}Oh}\",[\"B\"]]Ek[\"{{{AHEf{CEd{K@l{AAI`}}}}{AHEf{c}}{AHEf{{JL`{CLd}}}}D@n}{{KBj{BBMbBEAd}}}{BEEfIDh}}\",[\"\"]]Bi[\"{{{AHEf{CEdc}}}{{KCb{BEIb}}}Oh}\",[\"B\"]]Bi[\"{{{AHEf{CEdc}}}{{KCb{AAMd}}}Oh}\",[\"B\"]]Bi[\"{{{AHEf{CEdc}}}{{KCb{AGIf}}}Oh}\",[\"B\"]]A`[\"{AALnKB`}\",[]]Bk[\"{{{AHEf{KDf}}{AHEf{CEdc}}}HOjJOl}\",[\"B\"]]Bk[\"{{{AHEf{KBd}}{AHEf{CEdc}}}HOjJOl}\",[\"B\"]]Bl[\"{{{AHEf{AGJb}}{AHEf{CEdc}}}HOjJOl}\",[\"B\"]]Bl[\"{{{AHEf{BEI`}}{AHEf{CEdc}}}HOjJOl}\",[\"B\"]]Bl[\"{{{AHEf{AAMb}}{AHEf{CEdc}}}HOjJOl}\",[\"B\"]]Bk[\"{{{AHEf{KDh}}{AHEf{CEdc}}}HOjJOl}\",[\"B\"]]Bk[\"{{{AHEf{KBf}}{AHEf{CEdc}}}HOjJOl}\",[\"B\"]]Bl[\"{{{AHEf{AGJd}}{AHEf{CEdc}}}HOjJOl}\",[\"B\"]]Bl[\"{{{AHEf{AIC`}}{AHEf{CEdc}}}HOjJOl}\",[\"B\"]]Bl[\"{{{AHEf{AAIj}}{AHEf{CEdc}}}HOjJOl}\",[\"B\"]]Bl[\"{{{AHEf{BEIb}}{AHEf{CEdc}}}HOjJOl}\",[\"B\"]]Bl[\"{{{AHEf{AAMd}}{AHEf{CEdc}}}HOjJOl}\",[\"B\"]]Bl[\"{{{AHEf{AGIl}}{AHEf{CEdc}}}HOjJOl}\",[\"B\"]]Bj[\"{{{AHEf{CEdAIEf}}}{{KBj{HOjAOLn}}}}\",[]]Bj[\"{{{AHEf{CEdAIEh}}}{{KBj{HOjANLh}}}}\",[]]An[\"{{{AHEf{ANOb}}AAJb}HOj}\",[]]Ea[\"{{{AHEf{CEdAAJh}}NEdAGJ`{KB`{AONl}}AAI`{AHEf{CEd{AAj{Nd}}}}}{{KB`{B@Nj}}}}\",[]]o[\"{CNbNEf}\",[]]o[\"{CNdNEh}\",[]]A`[\"{AAJnHOj}\",[]]Ba[\"{{{AHEf{CEdAAJh}}AAK`}HOj}\",[]]Bh[\"{{{AHEf{AIFl}}{AHEf{CL`}}HOn}HOj}\",[]]Bh[\"{{{AHEf{BANl}}{AHEf{CL`}}HOn}HOj}\",[]]Bh[\"{{{AHEf{BDHf}}{AHEf{CL`}}HOn}HOj}\",[]]Bh[\"{{{AHEf{AIFn}}{AHEf{CL`}}HOn}HOj}\",[]]Bh[\"{{{AHEf{BANn}}{AHEf{CL`}}HOn}HOj}\",[]]Bh[\"{{{AHEf{BDHh}}{AHEf{CL`}}HOn}HOj}\",[]]A`[\"{AGHnD@n}\",[]]o[\"{I@`CLf}\",[]]o[\"{I@bCLf}\",[]]Ce[\"{{{AHEf{CEdAIAj}}NEdNEdCLfD@n{AHEf{B@Aj}}}HOj}\",[]]Cc[\"{{{AHEf{CEdO`}}NEdNEdCLfD@n{AHEf{B@Aj}}}HOj}\",[]]Cd[\"{{{AHEf{CEdI@h}}NEdNEdCLfD@n{AHEf{B@Aj}}}HOj}\",[]]Cd[\"{{{AHEf{CEdNF`}}NEdNEdCLfD@n{AHEf{B@Aj}}}HOj}\",[]]Ce[\"{{{AHEf{CEdAIAl}}NEdNEdCLfD@n{AHEf{B@Al}}}HOj}\",[]]Cc[\"{{{AHEf{CEdOb}}NEdNEdCLfD@n{AHEf{B@Al}}}HOj}\",[]]Cd[\"{{{AHEf{CEdI@j}}NEdNEdCLfD@n{AHEf{B@Al}}}HOj}\",[]]Cd[\"{{{AHEf{CEdNFb}}NEdNEdCLfD@n{AHEf{B@Al}}}HOj}\",[]]o[\"{CNbNEf}\",[]]o[\"{CNdNEh}\",[]]o[\"{BDI`Nd}\",[]]A`[\"{BBKbHOn}\",[]]A`[\"{BDDdHOn}\",[]]A`[\"{BBKfHOn}\",[]]A`[\"{BDDhHOn}\",[]]A`[\"{BB@jKDb}\",[]]Aa[\"{BALbBBKj}\",[]]Aa[\"{AGGdBEAh}\",[]]Bg[\"{{{AHEf{AAJf}}AGMb}{{KBl{HOj}}}}\",[]]A`[\"{AAJnHOj}\",[]]Cf[\"{{{AHEf{CEdAAJh}}AAK`{AHEf{CEd{AAj{Nd}}}}}AANh}\",[]]l[\"{NEf}\",[]]Ak[\"{{{AHEf{AGJ`}}}AIEl}\",[]]l[\"{NEh}\",[]]An[\"{{AAJdA@l}{{KBn{HOj}}}}\",[]]B`[\"{{B@BdAIEl}{{KBl{AAJf}}}}\",[]]Bb[\"{B@Bb{{KBj{HOj{Of{IAd}}}}}}\",[]]Bk[\"{{{AHEf{BEAj}}}{{KB`{{AHEf{IAd}}}}}}\",[]]Bk[\"{{{AHEf{BB@`}}}{{KB`{{AHEf{IAd}}}}}}\",[]]Bk[\"{{{AHEf{AGLh}}}{{KB`{{AHEf{IAd}}}}}}\",[]]Bk[\"{{{AHEf{AIDn}}}{{KB`{{AHEf{IAd}}}}}}\",[]]Bk[\"{{{AHEf{BAOn}}}{{KB`{{AHEf{IAd}}}}}}\",[]]Bk[\"{{{AHEf{AIGb}}}{{KB`{{AHEf{IAd}}}}}}\",[]]Bk[\"{{{AHEf{B@C`}}}{{KB`{{AHEf{IAd}}}}}}\",[]]Bk[\"{{{AHEf{BBKh}}}{{KB`{{AHEf{IAd}}}}}}\",[]]Bk[\"{{{AHEf{BBKj}}}{{KB`{{AHEf{IAd}}}}}}\",[]]A`[\"{AANfKB`}\",[]]A`[\"{AANhKB`}\",[]]A`[\"{AANjKB`}\",[]]Bg[\"{{{AHEf{CNn}}}{{KBj{AID`AI@h}}}}\",[]]Aa[\"{B@AfAIDb}\",[]]A`[\"{AIBbCLf}\",[]]A`[\"{AIBdCLf}\",[]]A`[\"{AALnJNf}\",[]]Aa[\"{AGLbAANl}\",[]]Aa[\"{BBL`AANl}\",[]]Aa[\"{AGLdAANn}\",[]]Aa[\"{BBLbAANn}\",[]]3210Dh[\"{{{AHEf{NDn}}{AHEf{{JL`{Nd}}}}{AHEf{{JL`{Nd}}}}}{{KBj{HOjANKl}}}}\",[]]Dh[\"{{{AHEf{NE`}}{AHEf{{JL`{Nd}}}}{AHEf{{JL`{Nd}}}}}{{KBj{HOjANKn}}}}\",[]]Aj[\"{{{AHEf{AIAj}}}CLf}\",[]]Ah[\"{{{AHEf{O`}}}CLf}\",[]]Ai[\"{{{AHEf{I@h}}}CLf}\",[]]Ai[\"{{{AHEf{NF`}}}CLf}\",[]]Aj[\"{{{AHEf{AIAl}}}CLf}\",[]]Ah[\"{{{AHEf{Ob}}}CLf}\",[]]Ai[\"{{{AHEf{I@j}}}CLf}\",[]]Ai[\"{{{AHEf{NFb}}}CLf}\",[]]Aj[\"{{{AHEf{BFLf}}}JNf}\",[]]Aj[\"{{{AHEf{BFLh}}}JNf}\",[]]Aj[\"{{{AHEf{BGLl}}}JNf}\",[]]Aj[\"{{{AHEf{BGMf}}}JNf}\",[]]o[\"{A@lJNf}\",[]]n[\"{{}ND`}\",[]]0o[\"{OjAAId}\",[]]111Ba[\"{{AHEf{AHEf{c}}}AALh{}}\",[\"K\"]]0000000000000000000Cg[\"{{{AHEf{AAJf}}AIEl{AHEf{CL`}}}{{KBj{AIAbAOMj}}}}\",[]]Dl[\"{{{AHEf{CEdAAJh}}NEdAOLfAIEl{AHEf{CL`}}}{{KBj{{JNd{BDE`AIAh}}AOMl}}}}\",[]]Bd[\"{{{AHEf{AAJn}}}{{KB`{BDEd}}}}\",[]]Dm[\"{{{AHEf{AGKf}}KBdCLf{AHEf{{JL`{Nd}}}}{AHEf{CEdAAI`}}}{{KBj{HOjANKl}}}}\",[]]Ca[\"{{{AHEf{AGJh}}JNf{AHEf{CEd{JL`{Nd}}}}}HOj}\",[]]Bd[\"{{{AHEf{AAK`}}}{{KB`{BDEf}}}}\",[]]Dm[\"{{{AHEf{AGKh}}KBfCLf{AHEf{{JL`{Nd}}}}{AHEf{CEdAAI`}}}{{KBj{HOjANKn}}}}\",[]]Ca[\"{{{AHEf{AGJj}}JNf{AHEf{CEd{JL`{Nd}}}}}HOj}\",[]]o[\"{{}ANO`}\",[]]n[\"{{}IDj}\",[]]o[\"{{}ANKb}\",[]]o[\"{{}B@Ml}\",[]]o[\"{{}B@Nn}\",[]]n[\"{{}KDf}\",[]]n[\"{{}KBd}\",[]]o[\"{{}AANl}\",[]]o[\"{{}AIBb}\",[]]o[\"{{}AGLb}\",[]]o[\"{{}BBL`}\",[]]n[\"{{}NIf}\",[]]o[\"{{}AOLn}\",[]]o[\"{{}BBNf}\",[]]o[\"{{}BEDj}\",[]]o[\"{{}BEFh}\",[]]o[\"{{}ANLj}\",[]]o[\"{{}BAM`}\",[]]o[\"{{}BFMh}\",[]]o[\"{{}BDHf}\",[]]o[\"{{}AGHh}\",[]]o[\"{{}ANL`}\",[]]o[\"{{}BAAb}\",[]]o[\"{{}BE@b}\",[]]o[\"{{}B@Mn}\",[]]o[\"{{}BGLl}\",[]]n[\"{{}KDh}\",[]]n[\"{{}KBf}\",[]]o[\"{{}AIDb}\",[]]o[\"{{}AI@n}\",[]]o[\"{{}AANn}\",[]]o[\"{{}AIBd}\",[]]o[\"{{}AGLd}\",[]]o[\"{{}BBLb}\",[]]n[\"{{}NIh}\",[]]o[\"{{}BB@b}\",[]]o[\"{{}AOMf}\",[]]o[\"{{}BBNh}\",[]]o[\"{{}BEDl}\",[]]o[\"{{}BEFj}\",[]]o[\"{{}ANLl}\",[]]o[\"{{}BAMb}\",[]]o[\"{{}BFMj}\",[]]o[\"{{}BGMf}\",[]]o[\"{{}BDHh}\",[]]o[\"{{}AGHj}\",[]]o[\"{{}ANLb}\",[]]o[\"{{}BAAd}\",[]]o[\"{{}BE@d}\",[]]o[\"{{}AALn}\",[]]n[\"{{}IDl}\",[]]o[\"{{}BBLj}\",[]]o[\"{{}B@Nl}\",[]]Ak[\"{{{AHEf{BAAh}}}AONb}\",[]]Ak[\"{{{AHEf{BBMb}}}AONb}\",[]]Aa[\"{BGAjAONb}\",[]]Aa[\"{BEIdAONb}\",[]]A`[\"{CNfAONb}\",[]]A`[\"{ID`AONb}\",[]]A`[\"{IDdAONb}\",[]]Aa[\"{BDI`AONb}\",[]]Cg[\"{{{AHEf{AGKf}}KBdCLf{AHEf{CEd{JL`{Nd}}}}JNf}HOj}\",[]]Ca[\"{{{AHEf{AGJh}}JNf{AHEf{CEd{JL`{Nd}}}}}HOj}\",[]]Cg[\"{{{AHEf{AGKh}}KBfCLf{AHEf{CEd{JL`{Nd}}}}JNf}HOj}\",[]]Ca[\"{{{AHEf{AGJj}}JNf{AHEf{CEd{JL`{Nd}}}}}HOj}\",[]]Aj[\"{{{AHEf{AIDj}}}D@n}\",[]]Aj[\"{{{AHEf{BEO`}}}D@n}\",[]]Ak[\"{{{AHEf{AIAj}}}BE@b}\",[]]Ai[\"{{{AHEf{O`}}}BE@b}\",[]]Aj[\"{{{AHEf{I@h}}}BE@b}\",[]]Aj[\"{{{AHEf{NF`}}}BE@b}\",[]]Ak[\"{{{AHEf{AIAl}}}BE@d}\",[]]Ai[\"{{{AHEf{Ob}}}BE@d}\",[]]Aj[\"{{{AHEf{I@j}}}BE@d}\",[]]Aj[\"{{{AHEf{NFb}}}BE@d}\",[]]Bn[\"{{{AHEf{CEdBBNf}}CLb}{{AHEf{CEdBBNf}}}}\",[]]Bn[\"{{{AHEf{CEdBBNh}}CLb}{{AHEf{CEdBBNh}}}}\",[]]Bf[\"{{{AHEf{CEdAIAj}}NEdCLfCLf}HOj}\",[]]Bd[\"{{{AHEf{CEdO`}}NEdCLfCLf}HOj}\",[]]Bf[\"{{{AHEf{CEdAIAl}}NEdCLfCLf}HOj}\",[]]Bd[\"{{{AHEf{CEdOb}}NEdCLfCLf}HOj}\",[]]Aj[\"{{{AHEf{AIAf}}}KAf}\",[]]Bc[\"{{{AHEf{AALf}}}{{KB`{KBd}}}}\",[]]Aa[\"{BGAjAONb}\",[]]Aa[\"{BEIdAONb}\",[]]A`[\"{CNfAONb}\",[]]A`[\"{ID`AONb}\",[]]Aa[\"{BDI`AONb}\",[]]Aj[\"{{{AHEf{AIEf}}}NHb}\",[]]Bn[\"{{{AHEf{AIEh}}}{{KBj{{KB`{KDh}}AOMf}}}}\",[]]o[\"{A@lJNf}\",[]]o[\"{IDjJNf}\",[]]Am[\"{{{AHEf{CEdAIAh}}}NHj}\",[]]Aj[\"{{{AHEf{AGKf}}}JNf}\",[]]Aj[\"{{{AHEf{AGKh}}}JNf}\",[]]A`[\"{AHEfKDd}\",[]]00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Bd[\"{{{AHEf{BFFl}}}{{KB`{AIAf}}}}\",[]]Bc[\"{{{AHEf{BB@n}}}{{KB`{CNn}}}}\",[]]Bn[\"{{{AHEf{CEdAOLb}}CLd}{{AHEf{CEdAOLb}}}}\",[]]Bn[\"{{{AHEf{CEdAOLf}}CLd}{{AHEf{CEdAOLf}}}}\",[]]A`[\"{BGAjCLd}\",[]]A`[\"{BEIdCLd}\",[]]o[\"{CNfCLd}\",[]]o[\"{ID`CLd}\",[]]A`[\"{AGHnKB`}\",[]]Aa[\"{AANjAHEf}\",[]]A`[\"{AIBbCLf}\",[]]A`[\"{AIBdCLf}\",[]]Aa[\"{BB@dAAJb}\",[]]A`[\"{JOnBB@b}\",[]]Aa[\"{AGLbAIBb}\",[]]Aa[\"{BBL`AIBb}\",[]]Aa[\"{AGLdAIBd}\",[]]Aa[\"{BBLbAIBd}\",[]]3210Bg[\"{{{AHEf{CEdc}}JNf}AON`{OhIDh}}\",[\"\"]]Bg[\"{{{AHEf{CEdc}}JNf}AONb{OhIDh}}\",[\"\"]]o[\"{{}BBMd}\",[]]o[\"{{}BBMf}\",[]]A`[\"{CLfBGLl}\",[]]Cd[\"{{{AHEf{CL`}}}{{KBn{ANN`c}}}{}}\",[\"FromStr::Err\"]]Cd[\"{{{AHEf{CL`}}}{{KBn{ANK`c}}}{}}\",[\"FromStr::Err\"]]o[\"{CLdKDf}\",[]]o[\"{CLdKDh}\",[]]Al[\"{CLf{{KBj{KDfBFFf}}}}\",[]]Al[\"{CLf{{KBj{KDhBFFh}}}}\",[]]Aa[\"{BFDnANKj}\",[]]Aa[\"{BH@jB@Oj}\",[]]Aa[\"{BFE`ANKj}\",[]]Aa[\"{BH@lB@Oj}\",[]]Af[\"{{}{{KB`{Ab}}}}\",[]]0000Aj[\"{{{AHEf{AIAh}}}D@n}\",[]]Bo[\"{{{AHEf{CEdBEFh}}AAJb}{{AHEf{CEdBEFh}}}}\",[]]Bo[\"{{{AHEf{CEdBEFj}}AAJb}{{AHEf{CEdBEFj}}}}\",[]]Ao[\"{{{Of{AIAj}}}{{Of{Nl}}}}\",[]]Am[\"{{{Of{O`}}}{{Of{Nl}}}}\",[]]An[\"{{{Of{I@h}}}{{Of{Nl}}}}\",[]]An[\"{{{Of{NF`}}}{{Of{Nl}}}}\",[]]Ao[\"{{{Of{AIAl}}}{{Of{Nl}}}}\",[]]Am[\"{{{Of{Ob}}}{{Of{Nl}}}}\",[]]An[\"{{{Of{I@j}}}{{Of{Nl}}}}\",[]]An[\"{{{Of{NFb}}}{{Of{Nl}}}}\",[]]Aj[\"{{{AHEf{BFFl}}}D@n}\",[]]Bo[\"{{{AHEf{CEdBFMh}}AAJb}{{AHEf{CEdBFMh}}}}\",[]]Bo[\"{{{AHEf{CEdBFMj}}AAJb}{{AHEf{CEdBFMj}}}}\",[]]Bc[\"{{{AHEf{AIAb}}}{{KB`{KAb}}}}\",[]]Bc[\"{{{AHEf{AIAf}}}{{KB`{KAb}}}}\",[]]Bc[\"{{{AHEf{AAJn}}}{{KB`{KAb}}}}\",[]]Bc[\"{{{AHEf{AAK`}}}{{KB`{KAb}}}}\",[]]Bc[\"{{{AHEf{AGJ`}}}{{KB`{KAb}}}}\",[]]A`[\"{AIBbCLf}\",[]]A`[\"{AIBdCLf}\",[]]Bc[\"{{{AHEf{AGId}}}{{KB`{JNf}}}}\",[]]Am[\"{{{AHEf{ANOb}}JNf}HOj}\",[]]Aj[\"{{{AHEf{AIAf}}}NFf}\",[]]Bg[\"{{{AHEf{AIEf}}}{{KBj{CCnAOLn}}}}\",[]]Bg[\"{{{AHEf{AIEh}}}{{KBj{CCnAOMf}}}}\",[]]A`[\"{B@ObKB`}\",[]]A`[\"{B@OdKB`}\",[]]A`[\"{AGHnKB`}\",[]]A`[\"{ANJlKB`}\",[]]Ck[\"{{{AHEf{CEdBAM`}}{KB`{{IEf{Nd}}}}}{{AHEf{CEdBAM`}}}}\",[]]Ck[\"{{{AHEf{CEdBAMb}}{KB`{{IEf{Nd}}}}}{{AHEf{CEdBAMb}}}}\",[]]A`[\"{BE@bKB`}\",[]]A`[\"{BE@dKB`}\",[]]Ac[\"{AHEfc{}}\",[\"T\"]]00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Ao[\"{c{{KBj{e}}}{}{}}\",[\"U\",\"T\"]]000000000000000000000000000000000000000000000000000Al[\"{HOh{{KBj{KDfBFFf}}}}\",[]]Al[\"{JNf{{KBj{KDfBFFf}}}}\",[]]2Al[\"{CLf{{KBj{KDfBFFf}}}}\",[]]333333333333333Aj[\"{AAJb{{KBj{ANLj}}}}\",[]]44444Ai[\"{CLf{{KBj{AGJb}}}}\",[]]5555555555555555555555555555Aj[\"{AOKn{{KBj{BDGn}}}}\",[]]Bb[\"{{{Nn{AOKn}}}{{KBj{BDGn}}}}\",[]]777Bb[\"{{{Nn{B@B`}}}{{KBj{BDHb}}}}\",[]]Aj[\"{B@B`{{KBj{BDHb}}}}\",[]]999999999999999999999Al[\"{JNf{{KBj{KDhBFFh}}}}\",[]]Al[\"{HOh{{KBj{KDhBFFh}}}}\",[]]Al[\"{CLf{{KBj{KDhBFFh}}}}\",[]]<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Bm[\"{AOKn{{KBj{BDH`c}}}{}}\",[\"TryFrom::Error\"]]Ce[\"{{{Nn{AOKn}}}{{KBj{BDH`c}}}{}}\",[\"TryFrom::Error\"]]Ao[\"{c{{KBj{e}}}{}{}}\",[\"U\",\"T\"]]0Ce[\"{{{Nn{B@B`}}}{{KBj{BDHdc}}}{}}\",[\"TryFrom::Error\"]]Bm[\"{B@B`{{KBj{BDHdc}}}{}}\",[\"TryFrom::Error\"]]22222222222222222222222Aj[\"{{}{{KBj{c}}}{}}\",[\"U\"]]00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Cd[\"{{{AAd{{AHEf{CEdc}}}}{AHEf{CEdNDb}}}COf{}}\",[\"F\"]]000000000Cc[\"{{{AHEf{BB@l}}AIG`{AHEf{AANj}}}{{KBl{HOj}}}}\",[]]Bk[\"{{{AHEf{BFLf}}AON`}{{KBj{HOjAIBh}}}}\",[]]Bk[\"{{{AHEf{BFLh}}AONb}{{KBj{HOjAIBj}}}}\",[]]0Bk[\"{{{AHEf{BGLl}}AONb}{{KBj{HOjAIBj}}}}\",[]]Ak[\"{{{AHEf{AIAf}}}AAHn}\",[]]Bi[\"{{{AHEf{CEdB@Cd}}}{{KBj{KDfIB`}}}}\",[]]Bi[\"{{{AHEf{CEdB@Cd}}}{{KBj{COlIB`}}}}\",[]]Bh[\"{{{AHEf{CEdB@Cd}}}{{KBj{OlIB`}}}}\",[]]Bj[\"{{{AHEf{CEdB@Cd}}}{{KBj{AAMbIB`}}}}\",[]]Bf[\"{{{AHEf{CEdB@Cd}}}{{KC`{KDh}}}}\",[]]Bf[\"{{{AHEf{CEdB@Cd}}}{{KC`{COn}}}}\",[]]Be[\"{{{AHEf{CEdB@Cd}}}{{KC`{On}}}}\",[]]Bg[\"{{{AHEf{CEdB@Cd}}}{{KC`{AAMd}}}}\",[]]Aa[\"{ANK`BFFd}\",[]]Bc[\"{{{AHEf{AIAj}}}{{Of{AIAj}}}}\",[]]Ba[\"{{{AHEf{O`}}}{{Of{AIAj}}}}\",[]]Bb[\"{{{AHEf{I@h}}}{{Of{AIAj}}}}\",[]]Bb[\"{{{AHEf{NF`}}}{{Of{AIAj}}}}\",[]]Bc[\"{{{AHEf{AIAl}}}{{Of{AIAl}}}}\",[]]Ba[\"{{{AHEf{Ob}}}{{Of{AIAl}}}}\",[]]Bb[\"{{{AHEf{I@j}}}{{Of{AIAl}}}}\",[]]Bb[\"{{{AHEf{NFb}}}{{Of{AIAl}}}}\",[]]A`[\"{AANlCLf}\",[]]An[\"{{{AHEf{CEdAIAh}}}AGId}\",[]]A`[\"{AANnCLf}\",[]]C`[\"{{{AHEf{CEdAON`}}}{{AHEf{CEd{JL`{Nd}}}}}}\",[]]C`[\"{{{AHEf{CEdAONb}}}{{AHEf{CEd{JL`{Nd}}}}}}\",[]]Ah[\"{Nd{{KB`{AONj}}}}\",[]]Ah[\"{Nd{{KB`{AONl}}}}\",[]]Ah[\"{Nd{{KB`{AONn}}}}\",[]]A`[\"{AAMbCOl}\",[]]A`[\"{AAMdCOn}\",[]]Bi[\"{AIAb{{KBj{{JNd{AIAfBBO`}}AIAb}}}}\",[]]Aa[\"{{}c{}}\",[\"I\"]]o[\"{COlD@n}\",[]]o[\"{COnD@n}\",[]]Aj[\"{{{AHEf{AIAh}}}D@n}\",[]]21Aj[\"{{{AHEf{AAJn}}}D@n}\",[]]Aj[\"{{{AHEf{AAK`}}}D@n}\",[]]Bn[\"{{{AHEf{CEdB@Bd}}D@n}{{AHEf{CEdB@Bd}}}}\",[]]Bn[\"{{{AHEf{CEdB@Bh}}D@n}{{AHEf{CEdB@Bh}}}}\",[]]Bl[\"{{{AHEf{NGh}}NEd}{{AAd{{Of{AI@`}}}}}}\",[]]Bm[\"{{{AHEf{B@Cb}}NEd}{{AAd{{Of{AI@`}}}}}}\",[]]Bm[\"{{{AHEf{ANNf}}NEd}{{AAd{{Of{AI@`}}}}}}\",[]]A`[\"{AIBbCLf}\",[]]A`[\"{AIBdCLf}\",[]]Ak[\"{{{AHEf{AIAf}}}AALd}\",[]]Bc[\"{{{AHEf{AIAf}}AIElAID`}AALf}\",[]]Ce[\"{{{AHEf{CEdAIAh}}AGJ`AIDbNEd}{{KBj{KBfAGKl}}}}\",[]]43Bm[\"{{{AHEf{CEdAIAh}}KBf}{{KBj{HOjAI@n}}}}\",[]]E`[\"{{{AAd{{AHEf{CEdBFF`}}}}{AHEf{CEdNDb}}}{{COf{{KB`{c}}}}}{}}\",[\"Stream::Item\"]]E`[\"{{{AAd{{AHEf{CEdBDD`}}}}{AHEf{CEdNDb}}}{{COf{{KB`{c}}}}}{}}\",[\"Stream::Item\"]]E`[\"{{{AAd{{AHEf{CEdAICn}}}}{AHEf{CEdNDb}}}{{COf{{KB`{c}}}}}{}}\",[\"Stream::Item\"]]E`[\"{{{AAd{{AHEf{CEdBENb}}}}{AHEf{CEdNDb}}}{{COf{{KB`{c}}}}}{}}\",[\"Stream::Item\"]]Ec[\"{{{AHEf{CEdAIDj}}{AHEf{CEdNDb}}{AHEf{CEd{JL`{Nd}}}}}{{COf{{KBj{JNfAGLh}}}}}}\",[]]E`[\"{{{AAd{{AHEf{CEdAIDj}}}}{AHEf{CEdNDb}}{AHEf{CEdNGb}}}{{COf{{KBl{HOj}}}}}}\",[]]Ef[\"{{{AAd{{AHEf{CEdAIDj}}}}{AHEf{CEdNDb}}{AHEf{CEd{JL`{Nd}}}}}{{COf{{KBl{JNf}}}}}}\",[]]Fg[\"{{{AHEf{CEdBAKh}}{AHEf{CEdNDb}}{AHEf{CEd{JL`{AIBl}}}}{AHEf{CEd{JL`{AALn}}}}}{{COf{{KBl{JNf}}}}}}\",[]]Dn[\"{{{AAd{{AHEf{CEdAGM`}}}}{AHEf{AANj}}{AHEf{CEdNDb}}}{{COf{{KBl{HOj}}}}}}\",[]]>=Cd[\"{{{AHEf{CEdBAM`}}{Nn{NDn}}}{{AHEf{CEdBAM`}}}}\",[]]Cd[\"{{{AHEf{CEdBAMb}}{Nn{NE`}}}{{AHEf{CEdBAMb}}}}\",[]]Cj[\"{{{AHEf{B@Bf}}CLdAON`{AHEf{{JL`{Nd}}}}}{{IEf{Nd}}}}\",[]]Cj[\"{{{AHEf{BDHb}}CLdAON`{AHEf{{JL`{Nd}}}}}{{IEf{Nd}}}}\",[]]Cj[\"{{{AHEf{B@Bj}}CLdAONb{AHEf{{JL`{Nd}}}}}{{IEf{Nd}}}}\",[]]Cj[\"{{{AHEf{BDHd}}CLdAONb{AHEf{{JL`{Nd}}}}}{{IEf{Nd}}}}\",[]]Cj[\"{{{AHEf{BGGf}}CLdAOMn{AHEf{{JL`{Nd}}}}}{{IEf{Nd}}}}\",[]]Bb[\"{JNf{{JNd{JNf{KB`{JNf}}}}}}\",[]]000Aj[\"{{{AHEf{AIAf}}}JNf}\",[]]A`[\"{AHEfKDb}\",[]]00000000000000000000000000000000000000000000000000000000000000Ce[\"{{{AHEf{CEdB@Bd}}{Nn{BE@f}}}{{AHEf{CEdB@Bd}}}}\",[]]Ce[\"{{{AHEf{CEdB@Bh}}{Nn{BE@h}}}{{AHEf{CEdB@Bh}}}}\",[]]A`[\"{BGAjICf}\",[]]o[\"{B@BdNn}\",[]]o[\"{B@BhNn}\",[]]Aj[\"{{{AHEf{AAJf}}}HOj}\",[]]Ck[\"{{{AHEf{CEdAIEf}}{AHEf{{JL`{Nd}}}}}{{KBj{HOjAIGb}}}}\",[]]n[\"{{}JNf}\",[]]Ak[\"{{{AHEf{AIAf}}}AGGh}\",[]]Ba[\"{{{AHEf{CEdAANl}}AANl}HOj}\",[]]Ba[\"{{{AHEf{CEdAIBb}}AIBb}HOj}\",[]]Ba[\"{{{AHEf{CEdBBL`}}AGLb}HOj}\",[]]Ba[\"{{{AHEf{CEdAANn}}AANn}HOj}\",[]]Ba[\"{{{AHEf{CEdAIBd}}AIBd}HOj}\",[]]Ba[\"{{{AHEf{CEdBBLb}}AGLd}HOj}\",[]]Bn[\"{{{AHEf{CEdBBNf}}D@n}{{AHEf{CEdBBNf}}}}\",[]]Bn[\"{{{AHEf{CEdBBNh}}D@n}{{AHEf{CEdBBNh}}}}\",[]]Bi[\"{{{AHEf{CEd}}}{{AHEf{CEdc}}}{}}\",[\"T\"]]00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Bc[\"{{AHEf{AHEf{CEdc}}}HOj{}}\",[\"T\"]]00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Cc[\"{{{AHEf{CEdAIAh}}NEdKBfKDh}{{KBj{HOjBAKn}}}}\",[]]A`[\"{AGHnD@n}\",[]]B`[\"{{AHEf{AHEf{c}}}D@n{}}\",[\"K\"]]00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Aa[\"{BBKbBEI`}\",[]]A`[\"{BDDdKDf}\",[]]Aa[\"{BBKfBEIb}\",[]]A`[\"{BDDhKDh}\",[]]A`[\"{BDOfKDh}\",[]]A`[\"{BBMhKDh}\",[]]o[\"{NGnKDh}\",[]]Aa[\"{BBKbAIBn}\",[]]Aa[\"{BBKfAIC`}\",[]]o[\"{KDfCLf}\",[]]o[\"{KDhCLf}\",[]]Aj[\"{{{AHEf{AIAh}}}D@n}\",[]]Aj[\"{{{AHEf{B@Nl}}}D@n}\",[]]Bd[\"{{{AHEf{BAKh}}}{{KBl{AIEl}}}}\",[]]Bd[\"{{{AHEf{AAJf}}}{{KBl{AIEl}}}}\",[]]A`[\"{AGLbCLf}\",[]]A`[\"{AGLdCLf}\",[]]Cb[\"{{{AHEf{BBLj}}{AHEf{AIEd}}CLf}{{Nn{ANOb}}}}\",[]]Bn[\"{{{AHEf{CEdBBNf}}D@n}{{AHEf{CEdBBNf}}}}\",[]]Bn[\"{{{AHEf{CEdBBNh}}D@n}{{AHEf{CEdBBNh}}}}\",[]]Bg[\"{{{AHEf{AIAf}}KBd}{{KB`{AGLb}}}}\",[]]Bj[\"{{{AHEf{CEdAIAh}}KBf}{{KB`{AGLd}}}}\",[]]o[\"{AGIhOf}\",[]]Db[\"{{{AAd{{AHEf{CEdAIEf}}}}{AHEf{CEdNDb}}}{{COf{{KBl{HOj}}}}}}\",[]]00Eg[\"{{{AAd{{AHEf{CEdAIEf}}}}{AHEf{CEdNDb}}{AHEf{{JL`{Nd}}}}}{{COf{{KBj{JNfAIGb}}}}}}\",[]]Ec[\"{{{AAd{{AHEf{CEdAIEf}}}}{AHEf{CEdNDb}}{AHEf{{JL`{Nd}}}}}{{COf{{KBl{JNf}}}}}}\",[]]0Bm[\"{{{AHEf{IDl}}{AHEf{CNl}}}{{KBl{HOj}}}}\",[]]Cd[\"{{{AHEf{CEdAIDj}}JNf}{{KBj{{KB`{I@`}}AGLh}}}}\",[]]Cd[\"{{{AHEf{CEdBEO`}}JNf}{{KBj{{KB`{I@`}}AGLh}}}}\",[]]Cn[\"{{{AHEf{CEdAIDj}}{AHEf{CEd{JL`{Nd}}}}}{{KBj{HOjBAOn}}}}\",[]]Bj[\"{{{AHEf{CNn}}AID`}{{KBj{HOjBEHf}}}}\",[]]Bn[\"{{{AHEf{CEdAIDd}}NEd}{{AHEf{CEdAIDd}}}}\",[]]Bn[\"{{{AHEf{CEdAIDf}}NEd}{{AHEf{CEdAIDf}}}}\",[]]A`[\"{BB@dAFd}\",[]]o[\"{IDjCLf}\",[]]o[\"{{}AIEl}\",[]]0o[\"{{}AHEf}\",[]]Bj[\"{{AAJn{Nn{B@Bd}}}{{KBj{AIAbBBKh}}}}\",[]]A`[\"{AIBbCLf}\",[]]A`[\"{AIBdCLf}\",[]]Bh[\"{{{AHEf{AGHn}}AIEl}{{KBn{AGMb}}}}\",[]]A`[\"{AGLbCLb}\",[]]Aj[\"{{{AHEf{AIAh}}}CLb}\",[]]A`[\"{AGLdCLb}\",[]]Ce[\"{{{AHEf{CEdAIDd}}{KB`{KDb}}}{{AHEf{CEdAIDd}}}}\",[]]Ce[\"{{{AHEf{CEdAIDf}}{KB`{KDb}}}{{AHEf{CEdAIDf}}}}\",[]]Aa[\"{AANfAIEl}\",[]]Aa[\"{AANhAIEl}\",[]]Aa[\"{AANjAIEl}\",[]]Aa[\"{AIElAGJ`}\",[]]o[\"{A@lCLb}\",[]]Bn[\"{{{AHEf{CEdBBNf}}CLb}{{AHEf{CEdBBNf}}}}\",[]]Bn[\"{{{AHEf{CEdBBNh}}CLb}{{AHEf{CEdBBNh}}}}\",[]]A`[\"{AGHnCLb}\",[]]Bo[\"{{{AHEf{CEdBBNf}}AAJb}{{AHEf{CEdBBNf}}}}\",[]]Bo[\"{{{AHEf{CEdBBNh}}AAJb}{{AHEf{CEdBBNh}}}}\",[]]A`[\"{AGHnKB`}\",[]]k[\"{{}}\",[]]0000000000Bh[\"{AAJnc{}}\",[\"IntoFuture::IntoFuture\"]]11A`[\"{AIBbCLf}\",[]]A`[\"{AIBdCLf}\",[]]o[\"{A@lJNf}\",[]]Ao[\"{{AHEfc}{{NEj{c}}}{}}\",[\"S\"]]0C`[\"{{{AHEf{CEdAIAj}}NEdCLfD@n{KB`{CLf}}}HOj}\",[]]Bn[\"{{{AHEf{CEdO`}}NEdCLfD@n{KB`{CLf}}}HOj}\",[]]C`[\"{{{AHEf{CEdAIAl}}NEdCLfD@n{KB`{CLf}}}HOj}\",[]]Bn[\"{{{AHEf{CEdOb}}NEdCLfD@n{KB`{CLf}}}HOj}\",[]]Bk[\"{{{AHEf{CEdIDl}}NEd{AHEf{BBLj}}}HOj}\",[]]A`[\"{BE@bKB`}\",[]]A`[\"{BE@dKB`}\",[]]Bn[\"{{{AHEf{KDf}}{AHEf{KDf}}}{{KB`{AALh}}}}\",[]]Bn[\"{{{AHEf{KBd}}{AHEf{KBd}}}{{KB`{AALh}}}}\",[]]C`[\"{{{AHEf{ANLj}}{AHEf{ANLj}}}{{KB`{AALh}}}}\",[]]C`[\"{{{AHEf{AON`}}{AHEf{AON`}}}{{KB`{AALh}}}}\",[]]Bn[\"{{{AHEf{COl}}{AHEf{COl}}}{{KB`{AALh}}}}\",[]]Bl[\"{{{AHEf{Ol}}{AHEf{Ol}}}{{KB`{AALh}}}}\",[]]C`[\"{{{AHEf{AAMb}}{AHEf{AAMb}}}{{KB`{AALh}}}}\",[]]Bn[\"{{{AHEf{KDh}}{AHEf{KDh}}}{{KB`{AALh}}}}\",[]]C`[\"{{{AHEf{BEAl}}{AHEf{BEAl}}}{{KB`{AALh}}}}\",[]]Bn[\"{{{AHEf{KBf}}{AHEf{KBf}}}{{KB`{AALh}}}}\",[]]C`[\"{{{AHEf{AGLj}}{AHEf{AGLj}}}{{KB`{AALh}}}}\",[]]C`[\"{{{AHEf{BABb}}{AHEf{BABb}}}{{KB`{AALh}}}}\",[]]C`[\"{{{AHEf{AIGd}}{AHEf{AIGd}}}{{KB`{AALh}}}}\",[]]C`[\"{{{AHEf{ANLl}}{AHEf{ANLl}}}{{KB`{AALh}}}}\",[]]C`[\"{{{AHEf{BDE`}}{AHEf{BDE`}}}{{KB`{AALh}}}}\",[]]C`[\"{{{AHEf{BEAd}}{AHEf{BEAd}}}{{KB`{AALh}}}}\",[]]C`[\"{{{AHEf{AONb}}{AHEf{AONb}}}{{KB`{AALh}}}}\",[]]Bn[\"{{{AHEf{COn}}{AHEf{COn}}}{{KB`{AALh}}}}\",[]]Bl[\"{{{AHEf{On}}{AHEf{On}}}{{KB`{AALh}}}}\",[]]C`[\"{{{AHEf{AAMd}}{AHEf{AAMd}}}{{KB`{AALh}}}}\",[]]Ak[\"{{{AHEf{AIAf}}}AICn}\",[]]Bk[\"{{{AHEf{AIAh}}KBf}{{KBj{AIDbAI@n}}}}\",[]]Df[\"{{{AHEf{CEdAIDj}}{AHEf{CEd{JL`{HOn}}}}}{{KBj{{KB`{JNf}}AGLh}}}}\",[]]Cc[\"{{{AHEf{CEdAIDj}}JNf}{{KBj{{AAj{Nd}}BB@`}}}}\",[]]Bb[\"{{{AHEf{CEdAIAh}}AAMd}AIDl}\",[]]Aj[\"{{{AHEf{AGJh}}}JNf}\",[]]Aj[\"{{{AHEf{AGJj}}}JNf}\",[]]Bb[\"{{{AHEf{CEdAIAh}}AAMd}AIEh}\",[]]Bn[\"{{{AHEf{CEdBBNf}}CLf}{{AHEf{CEdBBNf}}}}\",[]]Bn[\"{{{AHEf{CEdBBNh}}CLf}{{AHEf{CEdBBNh}}}}\",[]]A`[\"{AGHnKB`}\",[]]A`[\"{B@ObKB`}\",[]]A`[\"{B@OdKB`}\",[]]Ce[\"{{{AHEf{CEdB@Bd}}{Nn{AIFd}}}{{AHEf{CEdB@Bd}}}}\",[]]Ce[\"{{{AHEf{CEdB@Bh}}{Nn{AIFf}}}{{AHEf{CEdB@Bh}}}}\",[]]Ce[\"{{{AHEf{CEdAOLb}}{Nn{AIFl}}}{{AHEf{CEdAOLb}}}}\",[]]Ce[\"{{{AHEf{CEdAOLf}}{Nn{AIFn}}}{{AHEf{CEdAOLf}}}}\",[]]o[\"{A@lCLf}\",[]]Bn[\"{{{AHEf{CEdBEFh}}CLb}{{AHEf{CEdBEFh}}}}\",[]]Bn[\"{{{AHEf{CEdBEFj}}CLb}{{AHEf{CEdBEFj}}}}\",[]]Ak[\"{{{AHEf{AIAf}}}BFFl}\",[]]Aj[\"{{{AHEf{CNn}}}BB@n}\",[]]Ai[\"{{{Nn{B@Bf}}}B@Bd}\",[]]Ai[\"{{{Nn{B@Bj}}}B@Bh}\",[]]Aj[\"{{BBMdc}BBMdAAMl}\",[\"\"]]Aj[\"{{BBMfc}BBMfAAMl}\",[\"\"]]Bm[\"{{{AHEf{CEdAIEf}}HOn}{{KBj{HOjAIGb}}}}\",[]]n[\"{{}KAb}\",[]]o[\"{ND`ND`}\",[]]0000Bd[\"{{{AHEf{BFLf}}}{{KB`{AAJb}}}}\",[]]Bd[\"{{{AHEf{BFLh}}}{{KB`{AAJb}}}}\",[]]Bd[\"{{{AHEf{BGLl}}}{{KB`{AAJb}}}}\",[]]Bd[\"{{{AHEf{BGMf}}}{{KB`{AAJb}}}}\",[]]Bd[\"{{{AHEf{AIAf}}}{{KB`{BBKh}}}}\",[]]Ck[\"{{{AHEf{AAJf}}AOLbAIEl{AHEf{CL`}}}{{KBj{AIAbAOMj}}}}\",[]]Ak[\"{{{AHEf{B@Al}}}AAJb}\",[]]A`[\"{AIBbCLf}\",[]]A`[\"{AIBdCLf}\",[]]An[\"{{AIDhD@n}{{KBn{JNf}}}}\",[]]Cj[\"{{{AHEf{NGj}}}{{KB`{{JNd{{Of{AGJh}}{Of{AGKf}}}}}}}}\",[]]Ck[\"{{{AHEf{AIFh}}}{{KB`{{JNd{{Of{AGJh}}{Of{AGKf}}}}}}}}\",[]]Cj[\"{{{AHEf{NGl}}}{{KB`{{JNd{{Of{AGJj}}{Of{AGKh}}}}}}}}\",[]]Ck[\"{{{AHEf{AIFj}}}{{KB`{{JNd{{Of{AGJj}}{Of{AGKh}}}}}}}}\",[]]A`[\"{BFDnAAj}\",[]]A`[\"{BH@jAAj}\",[]]A`[\"{BFE`AAj}\",[]]A`[\"{BH@lAAj}\",[]]An[\"{{{AHEf{CEdBFLf}}}AON`}\",[]]An[\"{{{AHEf{CEdBFLh}}}AONb}\",[]]An[\"{{{AHEf{CEdBGLl}}}AONb}\",[]]An[\"{{{AHEf{CEdBGMf}}}AONb}\",[]]Ak[\"{{{AHEf{BB@l}}}B@@b}\",[]]Ba[\"{{{AHEf{CEdAIAh}}BBKn}HOj}\",[]]Bo[\"{{{AHEf{CEdAAJh}}BDE`B@Nl}{{KB`{BBKn}}}}\",[]]B`[\"{{{AHEf{NGj}}AON`COl}CNb}\",[]]Bn[\"{{{AHEf{B@Bf}}CLdAON`}{{KBj{CNbBEIh}}}}\",[]]Ba[\"{{{AHEf{AIFh}}AON`COl}CNb}\",[]]Bn[\"{{{AHEf{BDHb}}CLdAON`}{{KBj{CNbBEIh}}}}\",[]]B`[\"{{{AHEf{NGl}}AONbCOn}CNd}\",[]]Bn[\"{{{AHEf{B@Bj}}CLdAONb}{{KBj{CNdBEIn}}}}\",[]]Ba[\"{{{AHEf{AIFj}}AONbCOn}CNd}\",[]]Bn[\"{{{AHEf{BDHd}}CLdAONb}{{KBj{CNdBEIn}}}}\",[]]Bn[\"{{{AHEf{BGGf}}CLdAOMn}{{KBj{CNdBEIn}}}}\",[]]Aj[\"{{}{{NGd{c}}}{}}\",[\"T\"]]0A`[\"{AGLbCLf}\",[]]A`[\"{AGLdCLf}\",[]]Bn[\"{{{AHEf{CEdB@Bd}}JNf}{{AHEf{CEdB@Bd}}}}\",[]]Bn[\"{{{AHEf{CEdB@Bh}}JNf}{{AHEf{CEdB@Bh}}}}\",[]]Aj[\"{{{AHEf{BAKh}}}D@n}\",[]]0Aj[\"{{{AHEf{BB@l}}}D@n}\",[]]Bk[\"{{{AHEf{AIAh}}KBf}{{KBj{AGJ`AI@n}}}}\",[]]Cb[\"{{{AHEf{BBLj}}{AHEf{AIDh}}CLf}{{Nn{ANOb}}}}\",[]]Ak[\"{{{AHEf{AAJn}}}AON`}\",[]]Ak[\"{{{AHEf{AAK`}}}AONb}\",[]]A`[\"{AIBbCLf}\",[]]A`[\"{AIBdCLf}\",[]]Bf[\"{{{AHEf{CEdAIAh}}}{{KB`{NEd}}}}\",[]]Ce[\"{{{AHEf{CEdBBNf}}{Nn{ANMh}}}{{AHEf{CEdBBNf}}}}\",[]]Ce[\"{{{AHEf{CEdBBNh}}{Nn{ANMj}}}{{AHEf{CEdBBNh}}}}\",[]]43A`[\"{A@lANN`}\",[]]A`[\"{AANfKB`}\",[]]A`[\"{AANhKB`}\",[]]A`[\"{AANjKB`}\",[]]Ai[\"{{{AHEf{NHj}}}JNf}\",[]]Bo[\"{{{AHEf{CEdBGLl}}AAJb}{{AHEf{CEdBGLl}}}}\",[]]Bo[\"{{{AHEf{CEdBGMf}}AAJb}{{AHEf{CEdBGMf}}}}\",[]]Bj[\"{{{AHEf{AIEf}}CCn}{{KBj{HOjAOLn}}}}\",[]]Bm[\"{{{AHEf{CEdAIEh}}CCn}{{KBj{HOjAOMf}}}}\",[]]==E`[\"{{{AHEf{BGAl}}{AHEf{{JL`{Nd}}}}{AHEf{BAKj}}{AHEf{BFLj}}}{{KBj{BGDbIAh}}}}\",[]]0Bf[\"{{{Nn{BABh}}}{{KBj{AOLbBFFj}}}}\",[]]Bf[\"{{{Nn{BABh}}}{{KBj{AOLfBFFj}}}}\",[]]Bn[\"{{{AHEf{CEdBEDj}}KDf}{{AHEf{CEdBEDj}}}}\",[]]Bn[\"{{{AHEf{CEdBEDl}}KDh}{{AHEf{CEdBEDl}}}}\",[]]o[\"{ND`ND`}\",[]]0000Aj[\"{{{AHEf{AIAh}}}D@n}\",[]]0Ci[\"{{{AHEf{CNn}}{KB`{AAJb}}}{{KBj{{KB`{AAJb}}AI@h}}}}\",[]]Cd[\"{{{AHEf{CEdA@l}}{AHEf{AHOn}}}{{KBj{HOjIAn}}}}\",[]]Cc[\"{{{AHEf{CEdOj}}{AHEf{AHOn}}}{{KBj{HOjIAn}}}}\",[]]Ce[\"{{{AHEf{CEdAAId}}{AHEf{AHOn}}}{{KBj{HOjIAn}}}}\",[]]Ce[\"{{{AHEf{CEdAGHn}}{AHEf{AHOn}}}{{KBj{HOjIAn}}}}\",[]]Cd[\"{{{AHEf{CEdA@n}}{AHEf{AHOn}}}{{KBj{HOjIAn}}}}\",[]]Cd[\"{{{AHEf{CEdAA`}}{AHEf{AHOn}}}{{KBj{HOjIAn}}}}\",[]]Bo[\"{{{AHEf{CEdB@Bd}}BFMh}{{AHEf{CEdB@Bd}}}}\",[]]Bo[\"{{{AHEf{CEdB@Bh}}BFMj}{{AHEf{CEdB@Bh}}}}\",[]]Aj[\"{{{AHEf{BAM`}}}CLf}\",[]]Aj[\"{{{AHEf{BAMb}}}CLf}\",[]]Cc[\"{{{AHEf{CEdAOLb}}{Nn{A`}}}{{AHEf{CEdAOLb}}}}\",[]]Cc[\"{{{AHEf{CEdAOLf}}{Nn{A`}}}{{AHEf{CEdAOLf}}}}\",[]]Cj[\"{{BAM`{KB`{B@Bd}}{Of{BAKh}}{Nn{NGh}}}{{KBl{AAJf}}}}\",[]]Aj[\"{{{AHEf{AAJn}}}D@n}\",[]]Aj[\"{{{AHEf{AAK`}}}D@n}\",[]]Ca[\"{{{AHEf{CEdAIAh}}NEdAICd}{{KBj{HOjBGAb}}}}\",[]]Bd[\"{{{AHEf{BGAl}}}{{AAj{BBMl}}}}\",[]]Bj[\"{{{AHEf{AIAf}}AIEl}{{KBj{HOjIBh}}}}\",[]]Bm[\"{{{AHEf{CEdAIAh}}AIEl}{{KBj{HOjIBh}}}}\",[]]Bn[\"{{{AHEf{CEdBBNf}}JNf}{{AHEf{CEdBBNf}}}}\",[]]Bn[\"{{{AHEf{CEdBBNh}}JNf}{{AHEf{CEdBBNh}}}}\",[]]Bl[\"{{}{{KBj{{JNd{BAKjBEGd}}{Of{IAd}}}}}}\",[]]Ai[\"{AIAd{{KBn{HOj}}}}\",[]]A`[\"{AIBbCLf}\",[]]A`[\"{AIBdCLf}\",[]]An[\"{{{AHEf{AAJf}}AOLb}HOj}\",[]]Dc[\"{{{AHEf{CEdAIAh}}NEdKBf{KB`{AAJb}}}{{KBj{{KB`{AAJb}}AI@n}}}}\",[]]o[\"{{}AHEf}\",[]]Aj[\"{{{AHEf{AIAf}}}JNf}\",[]]Bn[\"{{{AHEf{CEdB@Bd}}CLf}{{AHEf{CEdB@Bd}}}}\",[]]Bn[\"{{{AHEf{CEdB@Bh}}CLf}{{AHEf{CEdB@Bh}}}}\",[]]Bn[\"{{{AHEf{CEdBBNf}}KDf}{{AHEf{CEdBBNf}}}}\",[]]Bn[\"{{{AHEf{CEdBBNh}}KDh}{{AHEf{CEdBBNh}}}}\",[]]:Bb[\"{{{AHEf{CEdAIAh}}OnKDh}HOj}\",[]]Ak[\"{{}{{KBj{AOLbIBb}}}}\",[]]Ak[\"{{}{{KBj{AOLfIBd}}}}\",[]]Bn[\"{{{AHEf{CEdBBNf}}D@n}{{AHEf{CEdBBNf}}}}\",[]]Bn[\"{{{AHEf{CEdBBNh}}D@n}{{AHEf{CEdBBNh}}}}\",[]]65Cg[\"{{{AHEf{CEdA@l}}{AHEf{CEdAHOn}}}{{KBj{HOjIAn}}}}\",[]]Cf[\"{{{AHEf{CEdOj}}{AHEf{CEdAHOn}}}{{KBj{HOjIAn}}}}\",[]]Ch[\"{{{AHEf{CEdAAId}}{AHEf{CEdAHOn}}}{{KBj{HOjIAn}}}}\",[]]Ch[\"{{{AHEf{CEdAGHn}}{AHEf{CEdAHOn}}}{{KBj{HOjIAn}}}}\",[]]Cg[\"{{{AHEf{CEdA@n}}{AHEf{CEdAHOn}}}{{KBj{HOjIAn}}}}\",[]]Cg[\"{{{AHEf{CEdAA`}}{AHEf{CEdAHOn}}}{{KBj{HOjIAn}}}}\",[]]Bi[\"{{{AHEf{CEdAGJ`}}{AHEf{AGJ`}}}D@n}\",[]]n[\"{{}HOj}\",[]]Ce[\"{{{AHEf{CEdBBNf}}{KB`{JNf}}}{{AHEf{CEdBBNf}}}}\",[]]Ce[\"{{{AHEf{CEdBBNh}}{KB`{JNf}}}{{AHEf{CEdBBNh}}}}\",[]]Bn[\"{{{AHEf{AIAf}}}{{KBj{{AAj{AIEl}}IBh}}}}\",[]]Cd[\"{{{AHEf{CEdAIAh}}NEd}{{KBj{{AAj{AIEl}}IBh}}}}\",[]]Am[\"{{{AHEf{CEdAIAj}}}HOj}\",[]]Al[\"{{{AHEf{CEdI@h}}}HOj}\",[]]Am[\"{{{AHEf{CEdAIAl}}}HOj}\",[]]Al[\"{{{AHEf{CEdI@j}}}HOj}\",[]]Ai[\"{AIAd{{KBn{HOj}}}}\",[]]Bj[\"{{{AHEf{AIAf}}AIEl}{{KBj{HOjIBh}}}}\",[]]Bm[\"{{{AHEf{CEdAIAh}}AIEl}{{KBj{HOjIBh}}}}\",[]]D`[\"{{{AHEf{CEdAIAh}}KBf{KB`{AAJb}}}{{KBj{{KB`{AAJb}}AI@n}}}}\",[]]Ce[\"{{{AHEf{CEdBBNf}}{Nn{BDOn}}}{{AHEf{CEdBBNf}}}}\",[]]Ce[\"{{{AHEf{CEdBBNh}}{Nn{BE@`}}}{{AHEf{CEdBBNh}}}}\",[]]Cf[\"{{{AHEf{CEdBBNf}}{KB`{AAJb}}}{{AHEf{CEdBBNf}}}}\",[]]Cf[\"{{{AHEf{CEdBBNh}}{KB`{AAJb}}}{{AHEf{CEdBBNh}}}}\",[]]Aa[\"{B@ObAICb}\",[]]Aa[\"{B@OdAICb}\",[]]9Bn[\"{{{AHEf{CEdBBNf}}D@n}{{AHEf{CEdBBNf}}}}\",[]]Bn[\"{{{AHEf{CEdBBNh}}D@n}{{AHEf{CEdBBNh}}}}\",[]]Am[\"{{{AHEf{AIAf}}KDf}HOj}\",[]]o[\"{ND`ND`}\",[]]Bn[\"{{{AHEf{CEdBBNf}}CLd}{{AHEf{CEdBBNf}}}}\",[]]Bn[\"{{{AHEf{CEdBBNh}}CLd}{{AHEf{CEdBBNh}}}}\",[]]3109854Bn[\"{{{AHEf{AIAf}}}{{KBj{{AAj{AIEl}}IBh}}}}\",[]]Bn[\"{{{AHEf{AIAh}}}{{KBj{{AAj{AIEl}}IBh}}}}\",[]]10A`[\"{BENnKB`}\",[]]Bm[\"{{{AHEf{CEdBBNf}}Nd}{{AHEf{CEdBBNf}}}}\",[]]Bm[\"{{{AHEf{CEdBBNh}}Nd}{{AHEf{CEdBBNh}}}}\",[]]o[\"{{}AICd}\",[]]") \ 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 new file mode 100644 index 000000000..00c74febe --- /dev/null +++ b/pr/451/docs/search.index/function/bcf4aac8ded5.js @@ -0,0 +1 @@ +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/af48d3aa5b7a.js b/pr/451/docs/search.index/generic_inverted_index/af48d3aa5b7a.js deleted file mode 100644 index 814955c52..000000000 --- a/pr/451/docs/search.index/generic_inverted_index/af48d3aa5b7a.js +++ /dev/null @@ -1 +0,0 @@ -rb_("QU5gADswAAABAADnAAIAVAPmAAkMAAA7MAAAAQAAVwIoABMCFgArAg4AOwIAAD4CAABBAgMASAIAAEoCBgBWAgMAWwIFAGMCAABlAgMAagIFAHICAAB0AgIAeAIBAHsCBACBAgsAjgIDAJ0CFwC5AgAAuwIDAMACEQDTAgQA2QIEAN8CBADmAgQA7AIEAPICBgD6AgUAAgMFAAkDBQAQAwIAFAMDABwDAAAkAwsAMQMNAEMJhgDDCuYAmg8AAEIQAQA7MAAAAQAA0AEOAJAAAgCoBeYAygkzAAAKAAACCg4AEgoEABgKGwA2CgIAOwoUAFMKHgBzCgYAewoqAKgKAQCsChYAOzAAAAEAABQBCwBaAgAAvwIAADADAABYBxMAcgoAAHoKAACmCgAAqwoAADkO9QCgDwEA2A8BADswAAABAAB4AQYAQQQAAA8JAQCnCgAAqgoAAMMMbQFMEQUAOzAAAAEAAAYAAgCsBgMAsQYCADswAAABAAApAAUAPwMUAHUFAACnBgQAtQYMAAoJAQA7MAAAAQAACgACAF4EAACqCwkAOzAAAAEAAAwAAQBNBAwAOzAAAAEAAAUAAgAfDAMA1BABAAI4EQAAOREAAAACnhAAAJ8QAAAAAsIBAACwBgAAQ24AAAA7MAAAAQAA5gAMAMoJMwAACgAAAgoOABIKBAAYChsANgoCADsKFABTCh4AcwoGAHsKKgCoCgEArAoWAA==") \ 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 new file mode 100644 index 000000000..6e93adf41 --- /dev/null +++ b/pr/451/docs/search.index/generic_inverted_index/e9848908a123.js @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..09541b27b --- /dev/null +++ b/pr/451/docs/search.index/name/14e0528539da.js @@ -0,0 +1 @@ +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/edb16650d3a7.js b/pr/451/docs/search.index/name/edb16650d3a7.js deleted file mode 100644 index 5baaa2535..000000000 --- a/pr/451/docs/search.index/name/edb16650d3a7.js +++ /dev/null @@ -1 +0,0 @@ -rd_("b()b->bBi0bCe00bFnbIdbOkb[]beq0000000000000000000000000000000000000000000000000000000000000000000000000000000000000bid0000000000000brtbtybu8cAck0cAddcAnycArccBbr00cBoxcBufcClicDir0cErrcMAX000cNotcOpt00cOrdcPincUni0cVeccadd00000cbbrccmp0000000000000000000cdir000cecn000cf32cf64cfmt00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cget0ci32cimpcios0clen000clog0cmincmutcnew000000000000000000000000000000000000cnoqcnot0cnow000000crtt000crun0cstrcu16cu32cu64cudpdArgsdAsFddDropdEct000dEct100dFromdHashdIntodKeys0dLong0dNonedPath00dPing0dPolldReaddSenddSide0dSyncdZERO0dacks0daddr0dbooldbulkdcodedcwnd0ddataddrop000000dfrom00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dhash00000000000000000000dinto00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dmain0000dnext0dopen00dpath0dperfdping000dpoll00000000000000dqlogdread00drecv00dseal0dsend00dsent0dside0dsign0dsize00dsmoldspindstop00dtake00000du128duniteAsRefeByteseChunk0eCloneeCloseeCubic00eDebugeDerefeError00000000000eEvent00eKnowneOther0eRangeeReset000eRetry0eShort0eSizedeStats0eSuiteeTokioeWriteealpnsearrayebenchebuild00000000ebytes00000ecauseeclone00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000eclose000ecubicederef0eerroreframe0eindex0eis_ce0elocal0eparse0epaths0eprint00ereset00eretry0esliceespawn00estats000000000etitle0etokenetokioetupleeusizeewrite00fAcceptfAckEcn0fAes128fAes256fBackupfBorrowfBufMutfChunksfClient00fClosedfCrypto0fCursorfFuturefHasherfIpAddrfLaggedfOpenBifOpened000fOptionfOutputfPathId0fResult00000fServer00fStream000fStringfTypeIdfVarInt0faccept000fborrow00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fchunks0fcipherfclient0000fclosedfcoding0fconfig0fcrypto0000000000fdecode000000000000fdst_ipfencode000000000000ffinish00fhandlefheader0fignore0finsert00000fkeylogfoffset0fon_ack0000000fpacket00frandomfreason000000frebindfrefuse0fremote00frustls00000fserver0000fsharedfsource00000000fsrc_ip00fstatus0fstream0fstridefudp_rx000fudp_tx000fvarintfverify0fwindow0000000gBlocked00gCommandgContextgDefaultgDisplaygFromStrgGeneral0gHmacKey0gInitialgInstantgKeyPair0gLayeredgMaxData0gNewReno00gOpenUnigPadding0gPathAck0gPathBufgReadBufgRequestgRuntime0gSession0gStopped00000gStreamsgToOwnedgTooLonggTryFromgTryIntogUnknowngWritten0gZeroRttgcid_len000gclientsgcommand00000gcompare0000000000000000000gconnect0gdecrypt00000gdefault000000000000000000000000000000000000000000000000000gdrainedgdst_cid0000000gencrypt000gis_0rtt0gmetrics0000000gmin_mtu0gnoq_udpgon_sent000gopen_bigpath_idgpointergruntimegsrc_cid0000gstopped0gstreams000gtag_len0gtype_id00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000gupgrade0gversion00000hAcceptBihBytesMuthChacha20hCommandshDatagram00hDerefMuthDisabled0hDurationhEndpoint00hFinished0hIncoming0hIteratorhLayerExthLongTypehMAX_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_owned00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000htry_from00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000htry_into00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000htry_poll000000000htry_sendhvalidate000iAbandoned0iAcceptUniiAddAssigniArbitraryiAsyncRead0iAvailable00iBbrConfig0iBorrowMutiCommonOptiConnectediCriterioniDatagramsiDecodableiDiscarded0iEncodableiFormatteriFourTupleiFrameType0iHandshakeiHeaderKey0iHistogramiMalformediMaxPathId0iMultipathiPacketKey0iPartialEqiPathErroriPathEvent0iPathStats0iReadError00iTryFutureiTryStreamiUdpSenderiUdpSocketiValueEnumiaccept_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_string00000000000000000000000000000000000000000000000000000000000000itoken_key0itoken_positransport00iwait_idleiwrite_alljArgMatchesjAsyncTimerjAsyncWrite0jBATCH_SIZEjClosedPath000jComparablejConnectingjConnection00jController0jEquivalent0jFrameStats0jIntoFuturejInvalidCid0jIoSliceMutjMaybeFrame0jNamedGroupjNonZeroU32jOtherErrorjPartialOrdjPathAckEcn0jPathEventsjPathStatus0jQlogConfig0jRecvStream00jResetErrorjRetryError0jSendStream00jServerNamejSocketAddrjStreamInfojSubcommandjSystemTimejTimeSource0jTlsSession0jTokenStore0jUdpSockRefjWriteError0jaccept_unijadd_assign00000jallow_spin0jborrow_mut00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000jclone_into00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000jclose_pathjcongestion000jconn_statsjconnection0jequivalent00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000jerror_code000000jframe_type0jinto_inner0jis_drained0jlocal_addr0jlost_bytes0jnew_senderjpad_to_mtu0jpath_stats00jpoll_closejpoll_flush0jpoll_write00jprint_jsonjread_chunk0jread_exactjset_statusjstart_time0jthroughputjtotal_sizekAcceptErrorkCLIENT_ADDRkCipherSuitekClientStatskCloseReasonkConfigError0kContentTypekCryptoError0kCubicConfig0kDataBlocked0kFinishErrorkIdleTimeout0kIntoRequestkOutOfBounds0kPathAbandon0kQlogFactory0kResetStream0kRuntimeTypekSERVER_ADDRkSERVER_NAMEkSmolRuntimekStopSending0kStreamEvent0kStreamStats0kUnspecifiedkaccept_withkadd_address0kbind_socketkcertificatekcurrent_mtu00kdescription0kdestination00kfrom_remotekinitial_mtu000kinitial_rtt00kinto_future0000000000000kmax_path_id0kmax_streamsknamed_layer0kon_end_acks000kon_intervalkpacing_rate0kpartial_cmp0000000000000000000kpath_eventskpath_statuskread_chunkskread_to_endkrecv_stream0ksample_size0ksend_bufferksend_stream0ksend_window00kserver_name0ktime_source0ktoken_store0kupload_sizekupper_bound0kweak_handle0kwith_crypto0kwith_prefix0kwrite_chunklAckFrequency0lAddressAddedlClientConfig00000lClosedStream000000lConnectError00lConnectionId00lDatagramInfolDecryptError0lEcnCodepoint00lEncryptError0lIllegalValuelImmediateAck0lIntoIteratorlLOCALHOST_V4lLastOpenPathlNatTraversallNonZeroUsizelNoneTokenLog0lObservedAddr0lPathResponse0lPathsBlocked0lReadDatagramlRemoteStatus0lRttEstimator0lSendDatagramlServerConfig00000lSocketAddrV4lSocketAddrV6lStoppedErrorlTokioRuntimelUnstructuredlaugment_args0000lcid_lifetime000lclose_reasonlconnect_withlconservativeldata_blocked0ldrain_streamlearly_crypto000levent_streamlexpect_types000lgenerate_cid000lgro_segmentslhandle_event0linitial_keys00000000linto_request0llost_packets0lmax_incoming0lmay_fragment00lnetwork_pathlnew_receiverlnoprotection0lorig_dst_cid0lpath_abandon0lpoll_timeoutlqlog_factory0lreset_stream0lruntime_typelsegment_size00lsend_streamslset_lifetime0lset_priority0lstop_sending0lstream_statslupload_statslwith_initial000lwrite_chunks0mAlertReceived0mBloomTokenLog0mCidsExhausted000mCloneToUninitmDatagramEventmEndpointEventmEndpointStatsmFinishedEarlymHandshakeData0mHandshakeDone0mHandshakeTypemINVALID_TOKEN0mIncomingAlpnsmInvalidHeadermLocallyClosed0mMaxStreamData0mMaxStreamsUni0mNewConnectionmNewRenoConfig0mParseIntErrormPartialDecodemPathChallenge0mPossibleValuemPrivateKeyDermReadableErrormRemoveAddress0mRootCertStoremStdSystemTime0mUnexpectedEndmaccepted_0rttmack_frequency00mbench_exports0mcid_generator00mcreate_sendermdata_transfermdownload_sizemduration_histmimmediate_ack0minto_incoming0mmax_ack_delay0mno_protectionmobserved_addr0mon_first_bytemon_mtu_update0000000mpath_response0mpaths_blocked0mpeer_identity0000mpoll_read_bufmpoll_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_keys000npath_challenge0nqlog_from_path0nread_handshake000nread_unorderednreceive_window00nreceived_reset00nremote_address0000nremove_address0nsaturating_add000nsaturating_sub0nthroughput_bpsntime_threshold0ntotal_durationnvalue_variantsoConnectionClose000oConnectionError0oConnectionEvent0oConnectionStats0oGetRandomFailedoNewConnectionId0oOpenStreamStatsoPathCidsBlocked0oProtectedHeader0oQlogFileFactory0oRemoteAbandoned0oSignatureSchemeoSystemTimeErroroTokenReuseError0oTransportConfig00oTryFromIntErroroVersionMismatch0oZeroRttAcceptedoZeroRttRejected000obloom_token_logoclone_to_uninit00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000odefault_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`MaxPathIdReachedA`ObservedIpv4Addr0A`ObservedIpv6Addr0A`PathStatusBackup0A`PeerIncompatible00A`QuicClientConfig0A`QuicServerConfig0A`TokenMemoryCache0A`TooManyAddressesA`ValidationFailed0A`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`receive_datagramA`recv_buffer_size0A`send_buffer_size0A`transport_config0000A`validation_token0A`with_single_cert0A`write_all_chunksAaAPPLICATION_ERROR0AaApplicationClosed000AaControllerFactory0AaControllerMetrics0AaHandshakeTokenKey0AaInvalidServerName0AaNetworkChangeHint0AaPacketDecodeError0AaPathAbandonReason0AaSendDatagramError0AaStreamDataBlocked0AaStreamsBlockedUni0AaUnsupportedByPeer0Aacongestion_events0Aacongestion_window0Aamax_datagram_sizeAanew_connection_id0Aapath_cids_blocked0Aasend_buffer_spaceAaset_server_config0Aaset_up_connectionAato_possible_valueAbAEAD_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_instant0AcCongestionAlgorithmAcNatTraversalUpdatesAcPathNewConnectionId0AcPathStatusAvailable0AcRemoteCidsExhaustedAcTransportParametersAcUnorderedRecvStreamAcUnsupportedNameType0AcWrongConnectionSideAcaccepted_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_ERROR0AdHandshakeNotComplete0AdInappropriateMessage000AdInvalidRemoteAddress00AdNoInitialCipherSuite0AdObservedExternalAddrAdServerSideNotAllowedAdSupportedCipherSuiteAdVarIntBoundsExceeded0AdVerifierBuilderErrorAdWeakConnectionHandleAdack_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_EXCEEDED0AfExtensionNotNegotiatedAfFailedToGetCurrentTime0AfFailedToGetRandomBytes0AfMultipathNotNegotiated000AfNatTraversalRoundEndedAfPathRetireConnectionId0AfProtectedInitialHeaderAfSkipServerVerificationAfbuild_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_negotiatedAgset_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_streamsAjtry_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_event000Alreceive_bidirectional_streamAlremove_nat_traversal_address0Alset_path_keep_alive_intervalAmFixedLengthConnectionIdParserAmInappropriateHandshakeMessage000Amcongestion_controller_factory0Amdefault_path_max_idle_timeout0Amnegotiated_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_addressesBfset_max_remote_nat_traversal_addresses0BoDEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED") \ No newline at end of file diff --git a/pr/451/docs/search.index/normalizedName/de3a57d53d3b.js b/pr/451/docs/search.index/normalizedName/de3a57d53d3b.js deleted file mode 100644 index 730c73655..000000000 --- a/pr/451/docs/search.index/normalizedName/de3a57d53d3b.js +++ /dev/null @@ -1 +0,0 @@ -rd_("b()b->bbi0bce00bfnbidbokb[]beq000000000000000000000000000000000000000000000000000000000000000000000000000000000000033333333333333brtbtybu8cack0caddcanycarccbbr00cboxcbufcclicdir0cerrcmax000cnotcopt00cordcpincuni0cvec??????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/934c7b66edd3.js b/pr/451/docs/search.index/path/934c7b66edd3.js new file mode 100644 index 000000000..1b1528f10 --- /dev/null +++ b/pr/451/docs/search.index/path/934c7b66edd3.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\"]Bc[6,\"rustls::error\",\"rustls::error\"]Ao[2,\"noq_proto::crypto::rustls\"]Cm[5,\"noq_proto::transport_error\",\"noq_proto::transport_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\"]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\"]i[2,\"noq\"]o[2,\"noq_proto\"]1Ak[2,\"noq_proto::congestion\"]f[3,\"\"]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\"]Ae[5,\"noq::congestion\"]Ch[5,\"noq_proto::congestion\",\"noq_proto::congestion::bbr\"]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\"]Bg[6,\"noq_proto\",\"noq_proto::connection\"]o[2,\"noq_proto\"]Bn[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\"]Da[5,\"noq_proto::connection::paths\",\"noq_proto::connection::paths\"]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\"]6i[5,\"noq\"]Bn[5,\"noq_proto\",\"noq_proto::connection::stats\"]B`[10,\"core::future::into_future\"]2Bj[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\"]Bo[6,\"rustls::msgs::enums\",\"rustls::msgs::enums\"]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\"]Da[6,\"noq_proto::connection::paths\",\"noq_proto::connection::paths\"]Bn[6,\"noq_proto\",\"noq_proto::connection::paths\"]=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\"]=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\"]>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\"]Ci[5,\"ring::error::unspecified\",\"ring::error::unspecified\"]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\"]Ak[15,\"noq_proto::PathEvent\"]Ak[5,\"noq\",\"noq::connection\"]1Da[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\"]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\"]Cc[6,\"noq_proto::connection\",\"noq_proto::connection\"]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\"]9i[6,\"noq\"]>Bc[5,\"noq_proto\",\"noq_proto::shared\"]2Bn[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\"]7Bi[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\"]=Bn[5,\"noq_proto\",\"noq_proto::config::transport\"]Bi[5,\"core::num::error\",\"core::num::error\"]Ak[5,\"noq\",\"noq::connection\"]:Ae[5,\"noq\",\"noq::path\"]i[5,\"noq\"]Bb[5,\"noq_proto\",\"noq_proto::frame\"]Bc[6,\"rustls::error\",\"rustls::error\"]Be[5,\"noq_proto\",\"noq_proto::endpoint\"]Bo[5,\"noq_proto::endpoint\",\"noq_proto::endpoint\"]122Ai[5,\"noq::crypto::rustls\"]Ck[5,\"noq_proto::crypto::rustls\",\"noq_proto::crypto::rustls\"]106Bo[5,\"noq_proto\",\"noq_proto::token_memory_cache\"]Ba[15,\"noq_proto::ProtectedHeader\"]o[2,\"noq_proto\"]Bc[15,\"noq_proto::PathAbandonReason\"]Af[10,\"noq::congestion\"]Cd[10,\"noq_proto::congestion\",\"noq_proto::congestion\"]Ae[5,\"noq::congestion\"]Cc[5,\"noq_proto::congestion\",\"noq_proto::congestion\"]Ab[10,\"noq::crypto\"]Bl[10,\"noq_proto::crypto\",\"noq_proto::crypto\"]j[10,\"noq\"]Bh[10,\"noq_proto\",\"noq_proto::connection\"]9Bc[6,\"noq_proto\",\"noq_proto::packet\"]:Bn[6,\"noq_proto\",\"noq_proto::connection::paths\"]Ak[6,\"noq\",\"noq::connection\"]Cb[6,\"noq_proto\",\"noq_proto::connection::datagrams\"]f[3,\"\"]i[5,\"noq\"]Bn[5,\"noq_proto\",\"noq_proto::config::transport\"]Bd[10,\"noq_proto\",\"noq_proto::packet\"]21Bi[5,\"rustls_pki_types\",\"rustls_pki_types\"]Be[5,\"rustls::verify\",\"rustls::verify\"]Ae[10,\"rustls::verify\"]Da[6,\"noq_proto::connection::paths\",\"noq_proto::connection::paths\"]:6o[5,\"noq_proto\"]Bc[15,\"noq_proto::PacketDecodeError\"]Aa[5,\"noq::crypto\"]Bk[5,\"noq_proto::crypto\",\"noq_proto::crypto\"]o[2,\"noq_proto\"]Aa[6,\"perf\",\"perf\"]Am[5,\"noq\",\"noq::event_stream\"]Dg[5,\"noq_proto::transport_parameters\",\"noq_proto::transport_parameters\"]Al[5,\"noq\",\"noq::recv_stream\"]Bh[2,\"noq_proto::connection::send_buffer\"]An[2,\"noq_proto::bench_exports\"]Ba[15,\"noq::crypto::rustls::Error\"]Bg[15,\"noq_proto::crypto::rustls::Error\"]Ai[5,\"noq::crypto::rustls\"]Ck[5,\"noq_proto::crypto::rustls\",\"noq_proto::crypto::rustls\"]8Be[6,\"rustls::suites\",\"rustls::suites\"]i[5,\"noq\"]Bc[5,\"noq_proto\",\"noq_proto::varint\"]Be[6,\"rustls::webpki\",\"rustls::webpki\"]Ak[5,\"noq\",\"noq::connection\"]?j[10,\"noq\"]Bk[10,\"noq_proto\",\"noq_proto::cid_generator\"]Be[5,\"rustls::verify\",\"rustls::verify\"]6Bc[5,\"noq_proto\",\"noq_proto::config\"]Bg[5,\"noq_proto\",\"noq_proto::connection\"]Bc[5,\"noq_proto\",\"noq_proto::packet\"]Ao[5,\"certificate\",\"certificate\"]Bc[6,\"rustls::error\",\"rustls::error\"]5Bm[5,\"perf::noprotection\",\"perf::noprotection\"]01Aa[5,\"noq::crypto\"]Bk[5,\"noq_proto::crypto\",\"noq_proto::crypto\"]Ce[5,\"rustls::crypto::cipher\",\"rustls::crypto::cipher\"]Bj[5,\"noq_proto\",\"noq_proto::cid_generator\"]07Ba[15,\"noq::crypto::rustls::Error\"]Bg[15,\"noq_proto::crypto::rustls::Error\"]") \ No newline at end of file diff --git a/pr/451/docs/search.index/root.js b/pr/451/docs/search.index/root.js index 174740d9e..ad4d78992 100644 --- a/pr/451/docs/search.index/root.js +++ b/pr/451/docs/search.index/root.js @@ -1 +1 @@ -rr_('{"normalizedName":{"I":"AQQA9HgRAAABAAEAAQATgAKgIAAAD0xlcwEAAaBQAAASuWvzcQAAAAoE/gABAwGwIBDYAAFl8jcSAAABAAEAAbCAEtYAAXX0sQAAADEBMQcBAPMAAjRh9+ABAACaA5wK5gABAFECAQABAwGwIBDWAAFl8jUSAAABABMAArCAEtYAAWF1OjAAAAEAAAAAAAkAEAAAALEA4gETCRQJ/Q8VEPoQ+xBME00TAQAA9rAAAADuAEMAMAcBAIcDE4ACoIAAABOcMnMBAASgAAAACMigEAAABXqwAAR7AP6ggAAAE5swNThz9rAAAADuAEMAMAcBAIcDAQYA9DIRAAABAAEAAQABgAKwAAR7AP6ggAAAE5s4c5MABKCQAAATnKAAAAAB4DIzNnLz6gEAAAEAAQABAgD1tQcAAAEAaAHqAo8EIwACsLASYQABbnL05wEAAAEAAQDfBgEDAPQyDAAAAQABAAEAAQABsFATowABcgGjBAAAAYACoBAAAAyZoDAAABAAM3MAB2VqZWN0ZWQAAPR4EQAAAQABAAEAAAAHY2NlcHRlZAAAAXcRAAAA8gJ0dAIAYXIBTAcAAADyAW8BAXJ18gACAAABAAC2AAABZQEEsEASLQABsEAT3QABcmRlaHQAOjAAAAEAAAAAACgAEAAAAG0EbgRvBHIF2wjcCAEJNAmCDFQPww/ED9EPABBZEFoQWxCiEA4SahJrEmwSbRKYEqYSpxLUEtUSQRNCE0YTRxNIE1gTWRO7E7wT0hPTE/cT+BMBAwD03wgAAAEANgMBAAEDAPMTDAAAAQABAPOAAmlvAYACoEAAAA9AsCAMwQABZXABBQGwMBKGAAFo828TAAABAGsAE4ICoDAAABBLY3ABAgA7MAAAAQAA5wACAMULAADDDOYAAQEAOjAAAAEAAAAAAAcAEAAAAGIRYxGZEpoS8RLyEpkTmhPzBgFpAYcQAAATggKwgBKsAAFudAEBAbBQEIgAAWP0LwcAAAEAAQAEAiOAAqBQAAAQTGNuABBuZ2Nvbm5lY3Rpb25zaWRlAAAB+hIAAAAAAmVuAADySgcAAAEAAAAIYW5kc2hha2UAAPQbEgAAAQABAAEAAAAEcnJvcgAA8rgMAAABAAAABGh1bmsBANAAEGkAAXMB2g8AAAAABWh1bmtzAAABdxIAAADyAmxsAQBjAX0MAAAA8gCEgJUAAPSoBAAAywABAAEA9YAMAAABAMgCAQABAAADYmxlAADy+AgAAAEAAPKBdAMAYWV0AApwdWRwc29ja2V0AADzGBIAAAEAAQAA8oADAWFpb2UACmFydGluc3RhbnQAAPLtEgAAAQAAAAhuZ2xlY2VydAAA8nUSAAABAADygAIAaXQAD29vdGNlcnRpZmljYXRlcwAA8p0TAAABAAAABGVmaXgAAPLYDwAAAQAAEoABAbDgE+MAAXJsAAZuaXRpYWwAAPRlEAAAAQABAAEAAAAFcnlwdG8AAPLWDwAAAQAA9gEAAWgFAaDQAAATv2NpcHJzYQDyeg8AAKoAAANkb3cAADswAAABAAAHAAEAFAcHADswAAABAAAcAAcAyA8CABgRDQA/EQIAExIBAJYSAQDnEgEAexMCAPKAAgBudAAJYXRoaGFuZGxlAAABBhEAAAAABWFuZGxlAADy1A8AAAEAAAAPb25uZWN0aW9uaGFuZGxlAAABNRMAAADygWsDAGNocBKAAQKgAAAAEzCwsBLcAAFhZHgAA2RsZQAAAXwMAAAA8gJpdAEAaQAB4hIAAPZ1AACEiKCQAAAUIy7qzyyO4KEQAAATvxEBAYRwFgA7MAAAAQAAOAEMAKoBJADSAQYAfAUAAKgF5gAUBwcAyA8CABgRDQA/EQIAExIBAJYSAQDnEgEAexMCAAEFAPajDAAAAQABABwDAQA1AwEKAaBAAAATn2XyyRMAAAEAAYADsHAQ8wABsCATvQABsGAStwABYWRvAQkA82oSAAABAO0A8QABQ2H1FhAAAOYAAQBRAgEA8QABQWH2/Q8AABgA5QABAFECAQABBQA7MAAAAQAABwADAD8RAgDnEgEAexMCAAEHAPYKEwAAAQCjAFQAFgABAAGAAqEQAAAT/7CAE/cAAWl1AQYA9JEQAAABALgAAQABBQD0sxIAAAEAlQABAPMAAWMBHxIAAAEDAPasDAAAxQJaAAEAuAIBAAEEADswAAABAAAKAAQADxAFAEASAQCeEgEAuhMAAAGGAqAAAAASwKAAAAASwWRy8wIBdgHsEgAA84ACZW8BgAKwIBMIAAGgIAAAD3BiZAAIZWdvdGlhdGUAAPJHEgAAAQAAAAdpc21hdGNoAADydREAAAEAADIBbgIBgBLpABDwbW5zOzAAAAEAAAUAAQDACAUAOzAAAAEAAAUAAQDJEgUAEoABAqCwAAATMLCAE6MAAW9kehKAAQE7EUdIZJ1pYQAJc2lnbmF0dXJlAAABnBMAAAAIAAAAAZsTAAAA8oNsczECADIzAAhydmVyY2VydAAAAewSAAAAEoABAaBQAAATw2Vj8gACAHN08hIHAAABAAAAC3VpbGRlcmVycm9yAAABNBMAAADyAAEAYgDzwRIAACIBAQASgAEBoAAAABLAcmQSgAEBoFAAABONZWPygWYCAGl59goAAAIGoHAAABIRoLAAABONaXNhY2VudnkAOzAAAAEAAAoABACZBQIA8gYEAA0MAQBMEAAA5kIAAIKJwAAAAACMTYigiirkGbr1ENFesVAUGgABsHATRAABoTAAABQOBAABC2EaAPIqCQAA+wYBgAKgUAAAEI+goAAAErlrcBOAAqAgAAAQJWVpAQUBsGATywABZjswAAABAAAGAAMAwAsCAPMSAQB5EwEAAA1vdW5kc2V4Y2VlZGVkAADyMhMAAAEAAPoZAQBi86IFAAABAG4BABKBaQEBoDAAABFWbmEBAgA7MAAAAQAA9wACAI4MAQA5DvUAAAdhcmlhbnRzAAABVhEAAAAAA251bQAAAekLAAAA+iYCAGV2APP5DwAAlQATAgECAPQsEQAAAQABAAEAAQoA8yoTAAABAAEA84ECbXQBBgDz7hAAAAEAAQABggKwYBKxAAGw0BPJAAFpchOAArAQDJMAAWVpAAVvbmZpZwAA9GsTAAABAEkAAQAA8gRva2VuAQBj8nMSAAABAAAABWFpbGVkAADyRRIAAAEAAPKCb24CAGZ0AAAAAbAAE8AAAWT0tQsAAAEAAQABAAD6iAIAZWk2BgABZIGIsTAT5gABoGAAABDtoFAAABCAsJAShAABsEAQfQABAQAAtBAHAAGgEwAA8gACAWl1ZQA6MAAAAQAAAAAACgAQAAAAIAkhCaYP2hLbEgoTCxOuEwIUGBQZFPKAAgJscml09hsAAMLHoDAAAAjmQAQAAKhAFAEAOjAAAAEAAAAAAAcAEAAAAF8EYARhBAwJDQkmDMIQwxABBQA7MAAAAQAABwABAK4QBwABAAGwUBEwAAFjAekLAAABAQKwgBNzAAGgMAAAEBlpdQFkEwAAAQEAOzAAAAEAAAYAAgDsBgUA8wsAAAEBAPVvEAAAAQABAAEAgwLzgAJlbAEAAaCQAAAS4GjyqBIAAAEAEwADsEARbwABsDASwgABZGVy87sEAAAsAgEAAQ4A9PsTAAABAAEAAQABBQD0ORIAAAEApAABAAEHAPTzEgAAAQCFAAEAI4ACoUAAABPbYnYAAnplAAABcgUAAAEAEAAA9gUAAIGGsAAPRwABAAEAlwAEADowAAABAAAAAAAJABAAAAAEBwUHnwygDFAPvg8KEAsQEhLhEgEBAPUZCQAAAQABAAEAAQAABnJib3VuZAAA8tIPAAABAAASgAEBTqxZko8YZW8AA2F0cwAAAWQQAAAACAIAAAHRDwAAAPKDYWRzAgBpdBKAAQGwAAVxBl5vZQAEcmFkZQAA8r4IAAABAAAAEG9jYWxpZnNhbWVyZW1vdGUAAAH1EwAAAAACdXQAADswAAABAAAFAAEA7xMFAADyDXJvbWFyZ21hdGNoZXMBAG07MAAAAQAABQABAK8TBQAAMgNhdGUCArBAEjIAAbAAEvAAh2ZsZXMAOzAAAAEAABQABQCuEAcAVBIBAM8SBACnEwQAEhQAAPYCAAAEAqCQAAASoGRnbHBjaQA6MAAAAQAAAAAABwAQAAAAewWYDDkSOhLeEt8SDBQNFAEDAbAwD28A6XT2OgcAAAEAjgCpB6YA7AEBAAGwcBMyAAFl8mcPAAABACMAAqBAAAATiWVz9NIPAAABACYEAQAAF3NhYmxlYWZ0ZXJuZXR3b3JrY2hhbmdlAAAB2xMAAAAABmVyc2lvbgAAOzAAAAEAAAUAAQDJEgUAAAANcGVyYXRpb25lcnJvcgAAAc8TAAAAAAdhbWV0eXBlAADy+BIAAAEAAAAFeXBlZXIAAPKSEgAAAQAA8odwcG9ydGVkBABibm92AAd1Y3R1cmVkAAABGRAAAAASgAEBsJATaQABcmEAB2VjaWZpZWQAAAF5DwAAAPKAAwBwdHUACWVjdnN0cmVhbQAAAfcSAAAA8gZyZGVyZWQBAHIA8iYRAAAYAAEAAPd+BQAAEQEBAC8JqgABAA0CAANvd24AAAFJBwAAAPIAAQFucwD1fwQAAAEAzQoBAIwAAQYA898TAAABADUAAQIAOzAAAAEAAIcAAgBzEAAAfRGGAAAEdGltZQAAAfcIAAAApgEAAAIDwAAAAAR8sQAT4QAtdHhkbnPyigAAAAEAOjAAAAEAAAAAAAsAEAAAADIHOAm7C7oMhRCGEAwSDRKQEpESIBMhEwAKeHBlY3RlZGVuZAAAAZYQAAAA9lMAAIaFoGAAABKrsGAS1gABoHAAABIQEEUKDgAFAPLdAQAAAQABDQD0sxIAAAEAlQABAAEKAPVGEwAAAQABAHMAAQAAAXgAAPQNBwAAAQABAAEAAAgVAADy9QgAAAEAAAACZWYAAAG3DAAAAAAEdGF0ZQAAAQURAAAA+hwBAHMB6AsAAPTbEAAAPQEBAAEA8oJjawIAZXIABG5kZXIAAAHnCwAAAPKAAwBlb3QBAwD0DBMAAAEADwABAAAAAAD0CQcAAAEAAQABAADygAEBeG/yAAMBcnN0cAHjAQAAAcEHAADygAEBcGkAATQAAbCAEtYAAXUB4gEAAPITCQAAAQAAATIAAAHhAQAA8xEJAAABAIcDAIACAOOAAAAEe8AAAAAB4DI2di9IAAgLwAAAAABxeB+ZudZaAlgFjUoVLZLNFlfpb6P1aetSQDHMZHsQX5QP+LR/C6wUO8tQTImuV2LLMTM2OGRucHNiY2VmZ2lsbXJ0dQA7MAAAAQAACgAEAL8HAQBBDwEAfw8CAIgPAwABAAA7MAAAAQAABwACAA0HAwAGCQMAIYACsCAPegCqqmlvAYUCoAAAABLAoAAAABLBZHIjgAKgAAAAD/00ZQEJAPWAEwAABQABAAEAAQABgAKwgBKIAAGhAAAAE9hjZhOAA6AgAAAQwLCQEJQAAWVvcwECAbAADz4AAXMBrRAAAAEAArCAE+0AAaBgAAAUEWRz8sYIAAAkA5OAhLDAEzIAAaAQAAARUgDBQPEAAcVy8+oBAAABAAEAAQQA9AcSAAABAGUBAQABAAD0LhAAAAEAAQABAAggAAA7MAAAAQAA5wACAKEFAADXB+YAAPonAQFpcwA6MAAAAQAAAAAADgAQAAAA2ggVCRYJFwkYCdAL0Qs2DzcPXA9vD1gQfBD4EvkS8gABAXBsAXAAAAA7MAAAAQAACAADADkJAQBfEAEAuhAEAPEBAShz8rsLAAD/AAEFAPQMEwAAAQAPAAEA8YACJABpcAgAAAABcQUAAAHPCwAAEoABAaCQAAASoGxjdAIAAAEFAAIEaQ4DkzRQKo5LDokPWnBkbnJzdQA7MAAAAQAACgAEAL8HAQBBDwEAfw8CAIgPAwABAQGgMAAAEBl1AWQTAAATgAKgQAAAEgRjbgEGAPR+EgAAAQCJAQEAEwICsGASgAABZm3yjAwAAAEAI4ECsEASrAABZWwBAQA7MAAAAQAAQAADAKAFAADtCAAANww+AAEBADswAAABAAAJAAIAtwcHAIASAQBTgAOgEAAABwhjZG4BAQD1LQwAAAEAAQABAAEAE4ACsLATVQABYW8AE2l0aHBsYXRmb3JtdmVyaWZpZXIAAPLjEwAAAQAAAARyZWFtAAAB5gsAAAAAAm5kAAABtAsAAADygAIAZXQAA2V4dAAA8tQQAAABAADyA29sbAEAbjswAAABAAAJAAEAqgsJAAAAA250bwAAOzAAAAEAAOcAAgBIBwAAwwrmAAAABHR1cmUAAAHlCwAAAAAHbnRlcnJvcgAAAXQRAAAA8gJvbQEAaTswAAABAAD5AAIARwcAAMoJ+AAA8oACAHJ19gUAAIWCsEAMpwABIIESEAAEADswAAABAAAJAAQAnwQBAF0FAQAPDAEALBEDAAEHAPT8EgAAAQABAAEA8wEBdDswAAABAAAIAAIAvQsAAOsLBwABAAGgUAAAE79zAQ8SAAAjgAKgwAAAE/ZpdAGCArBAE4MAAaBQAAARYWJmAAAAADswAAABAAAGAAIA9hIAAFwTBQAACFUAAPLNEwAAAQAA8ohhcmFtZXRlcgIAZXMAA29kZQAA8scSAAABAAD6AgEAY/MCEQAAAQABAAAINQAAOzAAAAEAAAcAAgBxEQIAbhIEAAGOEwAA8gNvcnQDAGNlcPN5DAAAAQABAAABAAGwYBN1AAFlAawTAAD6RwABc/PyCAAAAQABAPPBEAAAPwAVAQAFZXN1bHQAAAEBEQAAAPICZXIBAHIAAaEQAADygAMAZm1w8oABAXNkcoABAxAM2OATz25jcna2AwAAAgQVB/1FoNBheWVpb3UA9N8BAABEBewBAQABAwA7MAAAAQAAhwACAHMQAAB9EYYAE4ECoGAAABEmaW8BgAKgoAAAE5SwEA9BAAFhdAECAaAwAAAP/GEBCQwAAAGAArBQEKYAAbAgDzgAAWNu84ACbnQBgQKwoBOBAAGwoBODAAFjchOAAqAgAAAL1WVyAQQAOzAAAAEAAA4AAgCSDAAAjw8NACOAArBQD2cAAWJ1AQEBoEAAABD/ZQHADwAAE4ACsDAPXQABbnIBAAD04xIAAAEAAQABABMCArCAErcAAXN29DsJAAABACkKAQABhQKwcBN+AAGwMBLUAAFlcwECADswAAABAAAHAAEAIgkHAAADaXplAAABVA8AAAAAB3VyYXRpb24AAAFVEQAAAPICYWwCAGRzAPLdEwAAAQAABHJpbmcAADswAAABAAA/AAIA7QgAADcMPgAAEoABAaBQAAARCHRlAQYA9HMPAAABAO0AAQABAQGgQAAAEBdlOzAAAAEAAAcAAgA+BwUAzwcBABOAArAgEisAAWVpAAtzc2libGV2YWx1ZQAAAaESAAAA0gABA6BQAAAP/m9lcHMA83IEAAABAAEAAANuZWQAADswAAABAACHAAIARQcAAEMJhgAAAAxhbnlhZGRyZXNzZXMAAAFEEgAAAAACbmcAAAFGBwAAAAADcmdlAADy8AgAAAEAAPKAAgBhb/KAAwBsbXcABnVudGltZQAAARgQAAAAAAx1cnJlbnR0aHJlYWQAAAHGEgAAAPpuAgBjcvKnBAAAyQAAAQUA9GsTAAABAEkAAQAABHRvcmUAAPS1DAAAAQAZAwEA8vUQAAABAAAJZXVzZWVycm9yAADybxEAAAEAAAACb3MAAAF4DAAAAAAKZW1vcnljYWNoZQAA80ISAAABAKgAAAABZwAA8u4IAAABAPUBEAAAAQBrAAEADgESgAEBsGATVQABb2kAAmV5AADydgwAAAEA8oISAAABAPpZhoEAnAMEAAABbwUAADowAAABAAAAAAAHABAAAADfCOAIFgwXDH0QfhBzEnQS8oACAGVp9v0LAAUMoDAAAAwIHf9ebs9/sNATowABa29wc3QwYWJjZWZnaW1ydXYAOzAAAAEAAGoCCgDvAQAAVAPmAIMFAQCcBgoASAcAAMMK5gAYDAAAqg2GANYPAQApEAMAAYACsXAUHAABoNAAABOVY28BgAKgkAAAE5ugkAAAE5wyMwAGZXNzaW9uAADyswwAAAEAAPIAAQFzMQA7MAAAAQAABgACAOwGBQDzCwAAkoABA7AABW0AAbBgEF0AAXNlaW8BDQA6MAAAAQAAAAAABwAQAAAAuhK7ElMTVBOKE4sT1hPXEwGAASWiNZ3TDW4BAAGwgBOhAAFw8p0TAAABAEMFA6AgAAAQ3KBAAAASJGRlc/N+DwAAMwMBAAECAPM7EgAAAQABAAEAAPW6EAAAAQABAAEAAQAigAEB9lAFbQABbHkBAAD0EwwAAAEAAQBqAAECADowAAABAAAAAAAJABAAAABkD2UPUxAOEV0SXhJXE9kTChQLFAAHcmVzaG9sZAAA8lMRAAABAAASgAEBsCAPbwDpaHkABW91cmNlAAD0sQwAAAEAGwMBAAAAA291dAAA8+oIAAABAAEAAHYDAACDg6BAAAARbggABhBAAQA6MAAAAQAAAAAAFwAQAAAAOgc7B8kH9wgrCSwJsAxRD1IPcg8YEB8QIBAhECIQXRBeEJQQlRAEElUTVhOBE4ITEoABAbAwEAwAAWVhtioAgMLJWfuJDSm4oCAAAAx8aAkhx+DcUhwc3/+2oBAAABAlAAAEAsAKuAgBAwGgoAAAFCN38hMUAAABABOAA7DgE+MAAbBAD9gAAWFscgELAPSFEwAAAQABAAEAAQwA9PESAAABAKcAAQAjgAKgcAAAE61nd/OAAmVvAQwA89kTAAAxAAEA8wABYQHvEgAAARAA8wIUAAAWAAEAAQUA9GUQAAABAAEAAQABAAGgYAAAEjRy95EFAAABADUCEAQBAMUDAQAzgAOgEAAAEhdkbnMBCQD0ZREAAAEANQEBAAEHAPSMEAAAAQCpAAEAM4CEoQAAABQDsEAP1gABgEEBAQECsCAMnAABsAAMHAABZXPyNQcAAAEAAQQBsFASigABcvRpDwAAAQDnAAEAE4ACoLAAABO/YXPzgAJiYwADaXN0AAABFxIAAAAAAnBzAAABUhEAAADyBWdocHV0AgBiaAFTDwAAABKAAQGw0BOdAAF1bxKAAQFj1dbDH/BvZba8AACBjaAgAAAHN2V5Wdnf0KBQAAARBqDgAAATeGlO+ik/LbDQE2kAAQAAAZf1CgA6MAAAAQAAAAAAGwAQAAAA9QH2AfcBRQRGBOII2QsaDBsMHgyDDIQMhQyGDDEOeg/+DyQQSxDzEPQQOBE5EXITtxO4E8sTzBMBAQDzKQcAAMQLAQABCgD04xIAAAEAAQABAAGAAqAwAAATMKDAAAATZG95YwCEoHAAABFVoDAAAA9UCIgC8t0TAAABAAGAAqAAAAAQFaAAAAAQFjQ28wIBdvKtDAAAqQIBBAA6MAAAAQAAAAAABwAQAAAAYQ9iDyYQJxCOEo8SHhMfEwEAAqAQAAAMNrBQE2kAAWlv8vgIAAABAPOBAmVvAQcA9AMTAAABAAEAAQABAwD0/BIAAAEAAQABAPosAAFyOzAAAAEAAAUAAQB1BAUAAAGBAQMJmWiwFm0IewAA8tUHAAABAADyAAEBbHIA9S0MAAABAAEAAQABAPYOAACCiaBgAAARd6BwAAATG3FfbNpWmQGBi5pd/UAEAA9oBQA7MAAAAQAAEQAIAAsCAAAtBwEA5ggAADIJAQB4EAEAgxABAA8RBQAKEgEAAAAIEmLiliapCV8qAIM5mENX+CctnTG3piUNP330zHhR8VMRBGrK3VnZoIZpHxo9oR5gUoAB5wAQASoOamdLqHlogEBj72/6gk+pyVeEosKc7CZiVjStY7CwE+MAAaDgAAATFqAwAAAPTGqBoV6m6WLOCCaJJU1uzV3XhVm+A/qB6wAz81aNWTaGQN06hE7Yn2FfozlJVLlFaA5VNZdoXWFoaWxvcnV5MDFiY2RlZmdqa21ucHN0dnd4ADswAAABAAB4BGUAbwAAAIQAAwCyAOsAqQEAANABAQDZAQMAQAQBAGcEAQB8BAAAlAQCAJsEAwChBAEAVwUFAHYFAAB9BQAAfwUCAJAFAACTBQUAogUFAJIGBADKBgUA0QYBANsGAgARBwAAIAcBACkHAAA5BwAATAcAAGwHOwCxBwUAwgcDAMcIAADNCAAA0QgCANkIAADqCAIA8ggCAP8IAQAeCQAAxQsBAN4LAQDoCwAAAQwBAAgMAAAKDAEAHwwDACgMAQAyDAMAeQwCAI4MAQCVDAAAwwzmADkO9QBPDwAAUw8AAGQPAgB1DwEAfQ8AAIwPAgD0DwIANxABAEIQAQBIEAIAUxAAAHMQAgCXEAAAoxAAAMEQAADUEAEA2xAAAOAQBgAAEQEABxEAAA4RAABCEQIAXREBAH0RhgAFEgMADxIAABESAAAVEgAAFxIDAEkSBgBdEgEAdRIBAIYSAQDiEgAA7BICAPwSAwAOEwcAOhMFAFcTAABkEwAAbRMBAL8TAADJEwEA1BMAANkTAADvEwUA+xMDAAoUAQABBAD0LBEAAAEAAQABACODArAwE8AAAWFyQ4ADsAATTAABsAATTgABNDZhAQcAOzAAAAEAAAUAAgCnEwQAEhQAAAEBAaBQAAARAXIBoRAAAGOAhKAAAAAB5aBgAAATUBhAAQGAA7BgEzgAAaBwAAASH7BQDzoAAWVpcgGAAqAAAAAFeaCAAAATmzhzE4ACoJAAABOcMjMBAwDztQcAAAEAaAEIIQAAAW4RAAAA8gd0ZW10aW1lAQBlAbAMAADylBAAAAEAAAFjgISwMAu+AAGgQAAADH+ggAAAENuwQAyAAAEAAB0B/wEAAADygAIAbnMBAAD0hRAAAAEAhgEBACOAAqBgAAASq2JpAQEAOzAAAAEAAAYAAgCTBQUAAREAAAAAAADy6RIAAAEAAdoTAADyA2lvbgEAcwA7MAAAAQAABQABAMkSBQAACWZ5c2NoZW1lcwAAAcMTAAAA+qECAGlzAAppcGhlcnN1aXRlAAABMRMAAAA2AACGcG9ydGVkAgOwUBKSAAGwcBL4AAGg0AAAE89jdmJubwACdGUAAaAAAAAStnMBpgQAAPRXDwAA1gMBAAMAAQABoIAAABQSZgEAEwAA8gZvbW1hbmQAAXMBrwwAAAEXEQAAEgABAaBgAAAT9mNzAPJQEQAAAQD2AQCAg4OwEBJlAAECgQAAKAEBgAKwUBBEAAGwsBMMAAFhbAECAbBwE70AAXA7MAAAAQAACQACAKkHBwBPEAEAAQMBsAAPPgABcwGtEAAAAQIBoDAAABAZdQFkEwAAAQIBoFAAABO/cwEPEgAACBUAAAGgBQAAOzAAAAEAAD8AAgDtCAAANww+ACKAAgDmUAAABwhkbgECAPSFEAAAAQCGAQEAAAAAAPN3DwAAAQDrAAFkEQAAAAVlcnJvcgAA8r4SAAABAADygmF0AgBlcwECAPTxEAAAAQBtAQEAAAJuaQAA9JASAAABAI8AAQAAAANpZGkAAPTEEgAAAQCVAAEAAPKFb2NrZWQCAGJ18oABAWxp8gACAWJ0dfVEBwAAjQABAAEAAQA6MAAAAQAAAAAACgAQAAAAnw9cEBcTlhPfE+AT4hPtE+4TERQVFAAMZWNlaXZld2luZG93AADzexMAAAEAAQAAAAlpbWl0ZXJyb3IAAPK8EgAAAQAAAAJmbwAAAa4MAAAAIoACANAACOgAAWRuAAdpbmlzaGVkAAABFhIAAAAABHZlbnQAAPJ1DwAAAQAAAAZsb2NrZWQAAPSOEgAAAQCPAAEAAPIDYXRhAQBiAPSDEAAAAQCGAQEA8gJhbYeAOAkDOzAAAAEAAAUAAgCcBQMABgcBADowAAABAAAAAAAeABAAAADmC6MMpAylDKkMqgyrDG0Pbg/BD8IPxg/HD+QP5Q/mD+cP6A/pD+oPKBAtEFYQVxD3EhsTQBPVE+ET/xMOFPIAAgJlaWF1Ad8BAADzIwcAAOwBAQABAQD3tQwAAAEAGQMBAMMAYgABAAAGZW5kaW5nAAD0cw8AAAEA7QABAAAIGAAAARcQAAAA+koBAGU7MAAAAQAABwACAD4HBQDPBwEAABKAAQGwIBIrAAFlaTIAAgGgUAAAD/5wc2XzcgQAAAEAAQAAUoABAqAwAAAL1XBncgAKc3lzdGVtdGltZQAA8pQQAAABAAABAAGwQBK+AAFl8wURAABLAQEAAQgA9PMSAAABAIUAAQASAXMAA0MHb94/Y7BAEsIAAWFiZfIEBwAAAQA6MAAAAQAAAAAABwAQAAAAnwygDFAPvg8KEAsQEhLhEgAAAAA7MAAAAQAACwACAKQEAQBjBQkAOjAAAAEAAAAAABMAEAAAAPUI9gjgC+ELkAyRDDYOQw9ED0UPWA93D3gPYxBkEHYQCRFfEWARZBHygAIBc3VlAANpbWUAAPJRDwAAAQAACCYAADswAAABAAAJAAEAyhAJAAA61AIBsGAS7QABc3RpAIJsZQEB5kAAAAw2sFATaQABaW/wgAMBAAEEWGJydG72QwAAxMgEzSCPAUiwQAlBAAFzdy5adbdwObKwc/SgEAAAD/1AApAAgGUiCAA7MAAAAQAAEgAIADkHAABmDwAAQhABAKMQAADgEAYAFxIAAPwSAwDJEwEAAYADsAATTAABsAATTgABsHATwAABNDZhAQQA9A8UAAABAAoAAQBDgQOwwBNzAAGgQAAAENpkbXABAQA7MAAAAQAADgAEADwHAQCzDAEAyhAJAAgRAAABAQA7MAAAAQAABgACALwLAAC7DAUAY4ADsHAQjgITYmdvAQABsDAMswABczswAAABAAAIAAMARBIAALUSAAAcFAYAQ4ADoEAAAAv0sDAMDQABbXJzAQEAOzAAAAEAAAoAAwDuEAIAJhMDAAQUAwAjgAOgMAAAD92gsAAAEtlkZ20ABWhyZXNoAADyQQkAAAEAALYLAAABB6CAAAASH6CAAAARF3RhZGVpcnV2ADswAAABAAAYAAkAew8BAJEQAQDGEAEA1hADAEURBgAqEwIAeBMAANATAQAAFAEAgYCEsMATcwABoAAAAAR9oEAAABDaCiiQAAABcAAA8wEHAAABAAEAAAACaWQAAPXKBwAAAQABAAEAAQAA8oACAGNpMoABAqBAAAAL82NldQEUAPT7EwAAAQABAAEAAYACsGAToQABoNAAABNyZW8BgAKgUAAAD3mg8AAAE6xjbggtAAABcQQAAPLBDAAAAQAIAAAA82AFAAABAAEAABKAAQKwEBKdAT+wwBMIAAF3Y3R2BgCAgoWgIAAAByJ717ygoR8BAQAQSAkBAAA7MAAAAQAACwAFAN8GBgBMDwAAWQ8AACMQAACKEgEAAANyY2UAAbCwE+gAAWw7MAAAAQAACAABAPgGCAD0sQwAAAEAGwMBAACAAgDAAAAAEBXAAAAAEBY0NvIDZGRyAQB2Aa0MAAAAGggBAaBAAAARBWFzAPfoCwAAlQNeAT0BAQABAKUBEoFrAQGgIAAADLdlcnYAAIACA6AAAAAFhaBwAAASXGN1bnJzAQAAOzAAAAEAAAwAAwDHCAAAOhMFAO8TBQABhgKgYAAAE0OgkAAAE61lbvOAAmx08QEB4HP28ggAAAEAAQDNBz8AFQEIPAAAAXIPAAAA8gFsAQByAXAEAAAA0oABA7AwEXUAAW9haXUAA2ljZQABoCAAAAyVbQFfBQAAAAAUaXBzZXJ2ZXJ2ZXJpZmljYXRpb24AAAGNEwAAAAEDAPMvEgAAAQABACOEArEgFBYAAWNrAANpbnQAAPQyDAAAAQABAAEAAAAAAAGwUBOjAAFyAaMEAAAAOjACArBAEi0AAbBAE90AAWRoZXTzbQQAAAEAAQA6MAAAAQAAAAAAJAAQAAAAcgXbCNwINAmCDFQPww/ED9EPABBZEFoQWxCiEA4SahJrEmwSbRKYEqYSpxLUEtUSQRNCE0YTRxNIE1gTWRO7E7wT0hPTE/cT+BMBgQKgMAAAE9SgUAAAE2RjcwAFY2hlbWUAAAFtEQAAAAgxAADyyBAAAAEAADpZAgGgQAAAE6Bsc3YA8psTAAABAPoNAQFhZfJrBAAAAQA7MAAAAQAABgACALwLAAC7DAUACAQAAaCQAAATMG70/QEAAAEAawIBAAH6EgAAdgQAgIOEsHAQjgITsHASdQABak6bB9h3SACAAmACAQABoCAAAA9jcgGHBQAAAQACoUAAABPloEAAABB3Y2Xz1AgAAAEAQQnzgAJkcgEBADowAAABAAAAAAAJABAAAABTEVQRZxJoElETUhOlE6YTFhQXFAAKbGR0cmFuc21pdAAA8gARAAAVAQBigAIB90AEoQABcnVsAQgA9ScRAAABAAEAAQABAAACZWQAAAH3BgAAAFKAAQJG0Cws9vNya25yAAICoFAAABDAYW9ldQA7MAAAAQAAHAAFAO4BAAA/AxQAwgYCAEEJAQBHDwEAAQYA9W8QAAABAAEAAQCDAiOAArBQEzIAAWNoAQMBsEAQ3QABZQExDgAAAYACsAAJPwABsDARbwABZHIAC3Bjb25uZWN0aW9uAAABoBIAAAABAwD0bQ8AAAEA6AABAAADdHVzAAABUA8AAADygAEBYXIACXZlcmNvbmZpZwAA8p4SAAABAAAABWluZG93AADyExIAAAEAAAAJdWZmZXJzaXplAAABWRMAAAD6oQIAYnfygAIAbnLygAIAZXQACmJ1ZmZlcnNpemUAAAFYEwAAAAAJaXZld2luZG93AADy5xIAAAEAAPKCZWMCAGV2AAZpb3JpdHkAAPJfEAAAAQAACCcAAPLCEgAAAQAA8gV0YXR1cwEAZQESEgAAAAANYXhpZGxldGltZW91dAAAAdkTAAAAABBlZXBhbGl2ZWludGVydmFsAAABAhQAAADygnRoAwBrbXPygAIAYXIAGmVtb3RlbmF0dHJhdmVyc2FsYWRkcmVzc2VzAADyIRQAAAEAAAAKZGxldGltZW91dAAAAVcTAAAAAAluaXN0cmVhbXMAAAEVFAAAAAAGdHJlYW1zAAAB4hMAAAAABGF0aHMAAAHCEwAAAAAIaXN0cmVhbXMAAAERFAAAAPKJb25jdXJyZW50hIACgArygmF4AwBjaXIAB2lmZXRpbWUAAPJdEAAAAQAAAA5wYWxpdmVpbnRlcnZhbAAAAa4TAAAAGpsBAbAADCsAAWV5ABJlZmF1bHRjbGllbnRjb25maWcAAAHYEwAAAAAMbHBucHJvdG9jb2xzAAD04xIAAAEAAQABAAD2HwAAiYKgQAAADKawcBLaAAEJnAsQAQAAOzAAAAEAAAsABACbBAMAWgUCANEGAQBCEQIACE8AAPI8BwAAAQA7MAAAAQAADAADALMMAQDKEAkACBEAAPIAAQBzADswAAABAAAIAAMARBIAALUSAAAcFAYAAQAAOzAAAAEAAAsAAgDKBgUASRIFAAANaWRlbm90YWxsb3dlZAAAATATAAAAAANhbWUAAPSsDAAAxQJaAAEA8oQSAAABAAAHbmRwb2ludAAAARESAAAAAARuZmlnAAA7MAAAAQAABQABAA8QBQD1QBIAAAEAXQABABsBAIZlcmlmaWUCAMAAAAASwMAAAAASwWRy8gJydAEAdgAB7BIAAPKAAgBlbwgyAAABcA8AAAD2AQAABQGgsAAAE41hY2Vuc3Y7MAAAAQAABwACAJkFAgDyBgQA8g0MAAABABKAAQFTkoif535yZBKAAQGgQAAAECVlYZIAAQOwQA9ZAMqwIBLCAAF2ZXJ0APKqEgAAWQEADGxwbXR1ZHByb2JlcwAA8hwTAAABAAASAAECsBAToQABsOATowABcGVv8mcEAAABAPTCBwAAAQABAAEAAQIA9HMPAAABAO0AAQABAQDz5wsAAFkDYAEIIQAA88gPAAABAAEA8hMSAAABAAAJbnJlbGlhYmxlAAABEBIAAAAABXRyZWFtAQDAAAAAEFxz9akMAAABAAEAGwMBAAAAFWJzZXJ2ZWRhZGRyZXNzcmVwb3J0cwAA8g8UAAABAAAAB2Fpcm5lc3MAAPLGEAAAAQAAAAduc3RyZWFtAAABGxMAAAAAA2FpdAAAAeISAAAACCEAAPKMEgAAAQAA8gNyYW0CAGV38g4QAAC3AAD6zgIAZ28AA2FjZQAAAZ0SAAAB3BMAAAACemUAAPJsEgAAAQDzWRMAAHkAAQDygAIAaXAABmVuY2hlcwAA8xgTAAABAAEAAPIFdWZmZXICAGJzAcUPAAAA8gCHgipAGhABAPT8AQAAaAIBAAEA8rQLAABzAPKAAgBkdAgCAADzWRAAAAEAAQAA8gABAGkAOjAAAAEAAAAAAAcAEAAAADYQXBJEE0UTdRN2E5QTlRMShG1lbnQBAbCwE+sAAXNh5hMFAMbJ9sAEYgABoHAAABIOHuObaSlJoHAAABCKoLAAABPUoEAAAAv0sGASBQABQBCIA4BGZiAAOzAAAAEAABoADQCCBAAAuwQAAEMFAwBTBQEA5wYBAOcIAAA+CQAARg8AAAUQAQC2EAEAVxEDACASAwBSEgEAAYICoRAAABPbsAAIzgABYWQAAnViAADyUBEAAAEAAAACZGQAAPRMEQAAAQABAAEAAPKHdXJhdGluZwIAYXMBgAOgkAAAEtmgMAAAE3KgUAAAE/VjcHIABmxlc2l6ZQAA8sMPAAABAADygAEBcGV0AQCAAgUAAwagMAAAD92KBxfWbryMSwG6GD+4bXRiZGdsdgAADgplRfcJKv0O0e+G9PUtuNr7WlozB/+UubF2NIgAc+4UwVXdCmx9XowYm/oE8Mi+ew5I3A6Db6dFNrufF0tzYGO/TItzqlf/nYFwZ7581cUfVkcWN1ckFy1VgnILglrXDgugIAAABXqgYAAAErkIBZXtnA1YBFquB1l92YEiJBNA2NlLZkpQv2I2eq+goAAAFCNhZWhpa2xtb3Byc3R1eTAxMjhiY2RmdncAOzAAAAEAAAUBbACgAQEA5AEAAPABAQACAgEAfgQAAKQEAQCpBAAAtQQFAFUFAQBjBQkAfgUAAI8GAQDsBgUABAcBAEQHAABRBwAAtwcHANEHAwDJCAAA9QgBAPsIAAA3CQAAyQsAAOALAQDzCwAA/QsDAAMMAgAcDAEAeAwAAH4MAACQDAEAngwCADYOAAA+DwEAQw8CAFAPAABYDwAAZw8BAHcPAQB7DwEAnw8AAKIPAwC9DwIAChABABoQBAAuEAMANhAAADkQCABEEAEAXBAAAGMQAQBpEAEAdhAAAH8QAACREAEAmxABAMYQAQDWEAMA6RAAAAkRAAAyEQMARREGAFIRAABWEQAAXxEBAGQRAAASEgAALxICAEQSAABWEgYAYxIBAHcSAACAEgEAlBIBALUSAQDYEgAA3BIBAOASAQDjEgMA6RIBAPASAAD2EgAA+xIAAAATAAAMEwEAFhMEABwTAQAqEwIARBMBAEsTAABcEwUAcRMAAHUTAwCDEwEAlBMCAJ0TAQCsEwAArxMFAMITAQDQEwEA2hMAAN8TAQDiEwAA7RMBAAAUAQAPFAIAExQCABoUCAABAAA7MAAAAQAABwACAAkHAwACCQMAAYACsCATywABsCATaQABYW8BCADz9xAAAAEAAQABgAKAC9ZtgFmwQBKAAAFkdGOABKBAAAALzrBwE+MAAWFlaXYBCwD0AxMAAAEAAQABAAEFAPQpEAAAAQABAAEA8wECY2QBdxAAAAGAA35BWXdMB7AQC9MAAaAwAAAQS2NlcAEDAPR+EgAAAQCJAQEAI4ECoFAAABIJZXQTAAKgsAAAErZhY/JHBAAA7wQBBAA7MAAAAQAACgADAM8SBACnEwQAEhQAAAADdGxzAAA7MAAAAQAABQABAOwGBQAB8wsAAAADeXBlAADybw8AAOkAAPpUAQB08zoHAAABAI4A83IPAACmAOwB8gABAHTy3QEAAAEAALKAAgJUOJzbvs1uc2NwAQUAOzAAAAEAAAkAAQDKEAkAEwADoHAAABNQoDAAABCTZWZ09psQAAABAHMDAQAKAAEAAQsBoEAAABOfZfLJEwAAAQAjgQKwUBBrAAFjdgEAADswAAABAAAGAAIA9hIAAFwTBQAjiAKwQBPNAAFlcwENAbBAE8cAAWX1jxMAAAEAAQABAAEAAQUAOzAAAAEAAAgAAwBxEQIAbhIEAI4TAAABBwD0eBEAAAEAAQABAAEDAPQyEQAAAQABAAEAAAhzdGltYXRvcgAA8gwQAAABAACWAQAAAQSgcAAAEXewIA9RAAFlYWlrcvTZAQAAAQABAAEAOjAAAAEAAAAAAAgAEAAAAEwHtQe2Bx4JCAyMD40Pjg+XEDYPAADByR2Mj28zQBIEGd7GDqAwAAALz0/xjtpSzAAAAAIARaENAW8AAAA7MAAAAQAAFAAHAKEEAQDKBgUAeQwCAEkSBQB1EgEA7BIAANQTAAABAAEr6mdhfqZtOzAAAAEAAOcAAgB8BQAAqAXmAAEGAPQmEwAAAQABAAEAAQ8A9AQUAAABAAEAAQDzhAJobfMAAXI7MAAAAQAABwACAOYBAAAMAgYAAYACsEASrAABFeL8ze8dZWwBAQD0DBMAAAEADwABACOAAqCwAAATcmFlAAR0b3JlAAABkxAAAAAShXRjZXJ0AQGwcBOdAAFzaXaCAADByyQENmiRBaDwAAAS+gvUI2LQrKBwAAAQNk4P4DQnukgFsyQkjbBAE70AAQAAEACAAK4fAPIAAgAAAQABgAKwEBDGAAGgoAAAE9tzdAEBAPasDAAAxQJaAAEAuAIBAAEDAPMvEwAAaAABABOAArBAEOoAAWFl84ACbG0ABHNlZWQAAPI/CQAAAQAA8oABAmdhZQGAArBQE34AAaAAAAAI2WN0AYACoDAAABOgCOXLz4YgYW8BAwD0mgwAAJoGmQABAAGAArAQEMgAAaBAAAASEGVpAQUA9HgRAAABAAEAAQABgAKg0AAAE/agUAAAE6xjbgALa2VubGlmZXRpbWUAAPJVEwAAAQAACG0AAPUtDAAAAQABAAEAAQAA8oACAGFvCBUAAPKnDAAAAQAA8gACAGV09J8EAAABAL0AAQA7MAAAAQAABQACAA8MAQAsEQMA8oABAXlhAA5yZWNvbm5lY3Rpb25pZAAA9LoSAAABAJgAAQD0ihMAAAEASwABAPKAAgBpcgEAAPT8EgAAAQABAAEAAQIAOjAAAAEAAAAAAAkAEAAAAFMRVBFnEmgSURNSE6UTphMWFBcU8wABb/JBCQAAAQAAAmx0AAA7MAAAAQAABQABAJMFBQABAREAAAAEb25zZQAA8ucIAABXAPQFEAAAAQCwAAEACB4AAPRtDwAAAQDoAAEAAAACZXkAAPIrDAAAAQAACAUAAAGmDAAAAHIAAwGwcBLaAAFla3NpOzAAAAEAAAYAAgCbBAMAWgUCAPNCEQAAAQABABKAAQGwMBOhAAF0bnYJAICDhaBAAAARbbEAE+gAASUUPdJSjxCACIRABgEFAPSkEgAAAQCRAAEA8wIBY/XbDwAAAQC8AAEAAQAIVwAAATkHAADzZg8AANwAAQDygnVlAQFzbgAQcmRlcmluZ3RocmVzaG9sZAAA8lETAAABAAAAEmF0dHJhdmVyc2FsYWRkcmVzcwAA8gAUAAABAAAABmRkcmVzcwAA9JEQAAABALgAAQAAOrQCAaAAAAAQ2mFuZAEUAPUeFAAAAQABAAEAAQAIQwAA8goQAAABAAAACWF0aHN0YXR1cwAAAeESAAAAAApwZW5zdHJlYW1zAAABFxMAAAAADGlkc2V4aGF1c3RlZAAAAfUSAAAAAAhhbGlkYXRlZAAA8sATAAABAADyBWRyZXNzAQB29UURAAABAAEAAQABAPMqEwAAAQABAAAHYW5kb25lZAAA8msRAAABAADygAIAYmT6BwUBYWNvcHNu8+kGAAABAAEA8ocPAABuBPKBbwIAdHYBAgDzzQgAADQDAQAACWFuZHNoYWtlcwAAAeASAAAA8gABAGgA8qgSAAABAPICc2UBAGTy5wYAAAEAAAGKArAAE0wAAbAAE04AATQ2AANuY2UAAAEqDAAAAPKBcgEBZXKyAAICsBAP2AABZXVpbQD1fQQAAAoAwQABAG4HAQsA9NUTAAAMAB4ADwABCQA6MAAAAQAAAAAABwAQAAAAuhK7ElMTVBOKE4sT1hPXExOAA7AQE6MAAbBgEwgAAW5ydggVAAD1owwAAAEAAQAcAwEAAfcSAAAAA2V0YQAAAeYIAAAACFkAAPJqEgAAAQABWBMAAPIAAwBibXPzXwQAAAEAAQABJgwAAAg5AADzPxEAAAEAAQD15xIAAAEAkwABAAEAABNuaWRpcmVjdGlvbmFsc3RyZWFtAAABDhQAAAAIOAAA8hoUAAABAAAABGVzZXQAAPNCEQAAAQABAAAABnRhZ3JhbQAAAWkSAAAA8gACAGFyAPNrEAAAAQC8AQARZGlyZWN0aW9uYWxzdHJlYW0AAAH/EwAAABKAAQGwgBP3AAFpdfYBAINpdmWFgqAAAAAQTLBwE0QAAQpAGAAAA7YBAIACA6BAAAAST2V2bG90AAdic3RyYWN0AAABDxIAAADyA2luZAEAYQHmBgAAAAhrAAA7MAAAAQAABgABAN8GBgD0WQ8AAMoAZwIBABKAAQGwcBIFAAFvZQAIbm9yZGVyZWQAAAE+EQAAAAghAAAB/xAAAADyBG9lbmQBAGUBwA8AAAAACGFuZHNoYWtlAAD0OhEAAAEAAQABAAAIAgAAAf4QAAAA8gNhY3QBAGUBTw8AAAAAA3JvcgAA8+ILAAABAAEAAPKAAgByeAAHYXRhZ3JhbQAA8gkQAAC7AAAAA3VuawEAwAAAAA+/c/JNDwAAAQAAAAtydHNmcm9tZmlsZQAAAVATAAAA8oACAGVoAAJ1ZgAAATgHAAABvxAAAAgGAAABkBAAAADyA2JsZQEAZfLkCAAAAQAA9g8AAIiBoAAAABKvnwAMAABA9PsBAABhAgEAAQD3vgsAAAEAZAABAAEAiwYWAAEBAPU0EgAAbgABAEUBAQAAhHRpcHYCANAAEPoAAdAAEPwAATQ28gJ1dAEAYfIoDAAAAQAA8oFoAQFvZXYAAIADAnTMPmPxFaBwAAAQoGNkc2109t80AMnJFYg9Q2cQPphVe6SjIiy18ec+sHARbwABwAlUAwCCqiwAOzAAAAEAADIADQCGBQAAyAYBAFgHEwDlCwAAkgwAALUMAQCPDw0Azw8BAJMQAADsEAAA9RABAGUSAQCbEwEAAQkA8xgSAAABAAEAAQEA86gHAACSBwEAE4ECoFAAABAoZXMBBAGwMBKmAAFz80gQAAABAAEAAYACoBAAAA9wsAAIvgABZGUBAgDzhwwAAIEGAQAAFG9ubmVjdGlvbmlkZ2VuZXJhdG9yAAAB6hMAAAASAm9tAQKwQBODAAGgUAAAEWFjYmYB3gYAAAASgAIB5lAAAASaJur/HswrZGdzthMAgMHMddql7J6ysAAHJAABbpjRiu+hS7zNp9r5UKvkfgN/EAzY4BPPoAAAAASqAAAIAIBTpkoAAMbPK0KlTbf9Un6ZlRaRWQWzaOfzcNWDgh0PesAb6TQEFFylxsWnVxSCoPYKVTW2J+3VVJoY+CGcPSHnXCQrcfQjphxffM/IJOn3Qb86B7zpJP0QwYRCAwSpOEolPr1b2u2JXMxj4t8lC8ohABljOHTxXndxdMse6f8kYkokcdzDQAQYBoBbp2kAOzAAAAEAAMIAYQB3AAIAfQACAJMAAACoAAMA3wEAAAQCAQCIBAsAmAQBAEoFAACFBQAAhwUBAJkFAgCRBgAAxgYBAPIGBAAjBwAAKgcBAMgHAADYCAAA3QgBAA8JAQA9CQAAzgsAAN0LAADiCwIA5wsAAPQLAAAGDAEACQwAAA0MAQATDAIAfwwAAIwMAQCaDAAApgwCAK0MAAC4DAEAOA8BADwPAQBADwAAVQ8BAFoPAQBdDwEAYw8AAHAPAACgDwEAxQ8AAOsPAgDyDwEA9w8BAPwPAAADEAEADBABABcQAABMEAAAgBAAAIoQAACPEAEAnRAEAKsQAQDcEAIA6hABAP4QAQACEQIAWxEBAGcRAQBuEQIAdBEAACQSAAAtEgEAMhIBADUSAwB4EgEAiBIBAIwSAQCSEgEAqhIAAKwSAQC5EgAAvBIDAMESAgAiEwEALxMAADQTAABiEwEAaRMBAHMTAQCMEwAAlxMBAJ8TAAC9EwEAxBMEAM0TAgDjEwIA6hMAAPYTAAADFAAAAYACoCAAAAk2oDAAAAwYZXIBAQD0OQcAAC0I3AABAPGAAlEAbnMBBQA7MAAAAQAA9wACAI4MAQA5DvUAAAtlcnZlcmNvbmZpZwAA8kASAAABAAAAC2xpZW50Y29uZmlnAADyPhIAAAEAADKAAgGwIBIFAAFjc2LygAEBY3ZSgAECoBAAAAfBaWRlAANhdGgAAPI4EQAAAQAAAAJudgAA8sIQAAABAAD6hAIAZXAACWxlZmFjdG9yeQAA8mkRAAABAAAABWN0b3J5AAD0aw8AAAEA6AABAADygAMAYWlyAAJpcgAAAT0JAAAAAAVvbmZpZwAA8qEMAAABAADyAm9nAwBjZGYBWwQAAADyAAIBbHVwADswAAABAABXAAMACwBVAM8BAADcCwAAAQABKfMgvG2fYfL8EAAAAQABAAFuzkY07g5h8voQAAABAPOAAjQ2AQACoCAAABFSoDAAABIXYmjykAUAAMMJARMA9PsTAAABAAEAAQABBwDzGBIAAAEAAQDzgANkcnQBCAD04xIAAAEAAQABABOAArAABKkL1nBzAQgA9AwTAAABAA8AAQAjgAKgoAAAE5VhdAEEAaAAAAAStnP0Vw8AANYDAQADAPMCAXMBkQYAAAEAAPQJBwAAAQABAAEA8YUCmptobQAIaW9sYXRpb24AAPK3EgAAAQAAEgJvbAEBD+/Kb+ZsdnPyOwkAAAEA8mUTAAABAPIAAQBjAAEYDAAAAQICoLAAABO5oLAAABO6Y3PzTRAAAAEAXAAADG5pdGlhbGhlYWRlcgAAAYwTAAAAAAVlYWRlcgAA8mcRAAABAADygWQCAGhp8oJjdAEBZWnygAIAZW9wgAEDAJ8JsEATvQABdGJwdvGCAqWmaG0ACmtjczhrZXlkZXIAAAG5EgAAAAAFZXlkZXIAAAGPEAAAAPKDYXRlAgBrcAAEcml0eQAA8jkJAAABAPJfEAAAAQAAA3NvbgAAAUwPAAAA+kYBAGrzVwUAAAEAAQAA8oADAW5vdmEAjHJyZWRhZGRyZXNzdgIA0AATTAAB0AATTgABNDYSgAEBsBAP2AABZWkSgAEBsFAToQABZnPygAMBZWlveAGAATQqZHx3pnQBAgD25wgAAFcAxwYBALAAAQAACWlibGV2YWx1ZQAAAY4QAAABoRIAAPIAAQBzAAF4DAAAAARyaXRlAADzSQ8AAAEAAQAAAAZhbnNtaXQAAAHBEAAAAAAFbWVvdXQAAAFTEAAAAPKAAgBpcgAGdXRkb3duAAABwBAAAAAAAm5kAAABJwwAAADygAIAZWgIJAAAAb8QAAAA+g8BAGLzIwwAAAEAAQAAGsMCAOdgAAAMJmFjAANleHQAAPQfDAAAAQABAAEA8tQQAAABAAAEbHVzaAAA8kcPAAABAAAADW5kcG9pbnRldmVudHMAAAFLEwAAAAAEbG9zZQAAAUYPAAAA8gFsiIA0IBc7MAAAAQAADwACAPoBAABMBA4AOzAAAAEAAAkAAQCqCwkAAYECoCAAABB2sFASKwABYW8BAwGgAAAAE0tzAXUQAAAigAEBwAAAAAfIcnZSAm50AQKwUBDnAAFlY3MAOzAAAAEAAAgABADRCAIA/wgBAPQPAgAREgAA9gIAgIODsBATaQABAAkCAGABAQAA9RkJAAABAAEAAQABAAgjAAABHgwAAADyAAEAcDswAAABAAAFAAIA+AEBAEgEAwDyKxIAAAEA8gABAGcBiQAAAPNxBAAAUAgBAPKAAQFuZAEBADswAAABAADnAAIAoQUAANcH5gABAAGgQAAAEBdlOzAAAAEAAAcAAgA+BwUAzwcBAAEEAPQuEAAAAQABAAEAAYECoCAAABCWsEAS3AABZWnzgAJldCOAAqBAAAAPeWl0ABlpc3RlbnRjb25nZXN0aW9udGhyZXNob2xkAADyFhQAAAEAAAALaXBoZXJzdWl0ZXMAAAG2EgAAAPIAAQBjAUcEAAABNgkAADKAAgKgkAAAE8+wQA/SAAFmc2FiABJlbnRvdmVyc2l6ZWRyZWNvcmQAAPKjEwAAAQAAAAlpc2JlaGF2ZWQAAPP3EAAAAQABAAAACmNvbXBhdGlibGUAAPM7EgAAAQABAAAABmVudGl0eQAA9boQAAABAAEAAQABAADygAIAZG76TAMAaW1zAPKSEgAAAQD2AQAAgoUgr+/rfsR88KfgkXoQAAEMIQIAOjAAAAEAAAAAAA4AEAAAANoIFQkWCRcJGAnQC9ELNg83D1wPbw9YEHwQ+BL5EgEIAPVGEwAAAQABAHMAAQABAQDzYAUAAAEAAQABCwD2ChMAAAEAowBUABYAAQABAwDzOxIAAAEAAQABBAGgoAAAFCN38hMUAAABAAENAPPZEwAAMQABAAANbnN0YWJsZW9ycG9vcgAA8mkTAAABAAAABWFja3VwAAD0ORIAAAEApAABAAAACHZhaWxhYmxlAAD08xIAAAEAhQABAAAyAXMCAbBAEsIAAWFiZfOfDAAAAQAeA/ISEgAAzwAAAAAA9eALAAABAGIDAQABAADygmF0AgBzdQAGbG9ja2VkAAD0BxAAAAEAsAABAAAyAAIBoKAAABQjYnR38lUFAAABAPPCEwAAUQABAAAPaXJlY29ubmVjdGlvbmlkAAD0ihMAAAEASwABAAAIagAA9AUQAAABALAAAQAAABB1cmNlbGltaXRyZWFjaGVkAADy6BMAAAEAAPKAAgBvcDqXAgGwgBMIAAFzdGMADmJzZXJ2ZWRhZGRyZXNzAAABeBMAAAAADGNvbm5lY3Rpb25pZAAA9PESAAABAKcAAQAAEoABAaBwAAATrXdnEoABATrYf5OJTWVvCBsAAaBgAAASNHLzkQUAAAEANQL01wsAAAEAxQMBAAGAAqBAAAATQ7AwEmUAAWFzAANlbnQBANAADJ4DH3Py3gsAAAEAAAhHAAAB3QsAAPLdEAAAAQDygAIBcnZuAAlkc2Jsb2NrZWQAAPRlEQAAAQA1AQEAAAAHYWxsZW5nZQAA9IwQAAABAKkAAQAA8oACAGhpCCEAAAE3BwAAAAACY24AAPKcDAAAAQAAEgFrAgDQAAwcAAFlc/I1BwAAAQAAAAVlYXNvbgAA8ooSAAABAADyBWFuZG9uAQBy9GkPAAABAOcAAQAA8oACAGJj9j8DAIqEoFAAABEGTmE1d9cxF2ELgJQA9fUBAAABAAEATgIBADowAAABAAAAAAAUABAAAADiCNkLGgwbDB4MgwyEDIUMhgwxDv4PSxDzEPQQOBE5EXITtxO4E8sTzBPygAEBaGkBAAA7MAAAAQAAEwABAFgHEwABhAKwQBPNAAFQh4j0PfplcyOAAqAgAAAMh2JtAAJyZAAAAZsMAAAAAAVlY29kZQAAAYsQAAAAAAJtcAAAOzAAAAEAABMAAQCpDxMAALKDaWFshIDnEAAAC9wcQAAAB250ZXJyb3IAAAGKEAAAAAAHeXRlc2l6ZQAAAQ4SAAAAOhICAbAAEqoBWWJpcvJTBQAAAQAA8oACAnN0YWUABG9tdHUAAPJBDwAAAQAAAAJuZwAA8jMHAAABAAASgAEBoAAAAAWIaXLygAIAZHQACGhyZXNob2xkAADyZxIAAAEAAAACZXkAAPLaCwAAAQAAAAplY29kZWVycm9yAADyiBIAAAEAAHICZXQDAbAAEEQAAWRrdHPz2wYAAAEAAQAAAAZuZ3JhdGUAAPKnDwAAAQAAMoACAbAAEp0BP2lrZfYOAICEhLAQByoAAQwABQAJUPaRMQDFzaCQAAASoHPgXqF+UbBACL4AAaCQAAASuUwFznvmT1K3284KQH47GQJ0NDwPWr0J80BEkAAAMy8PADswAAABAABGABAAlAATAJ8BAADjAQAA5gEAAAwCBgByBAIAewUAALQGAAABBwIAwQcAAC0JBACYDAAAqQ8TADkSAQDeEgEADBQBAAEAADswAAABAAAIAAIAegAAAPULBwARAAKkoCAAAAfBcHUBzwEAAAEDAPXdCAAAAQB/BgEAaAQjgAJhv1oHpuVucgGAAjBYUNtQr6AgAAAQAGNpAQEBsLAT6AABbDswAAABAAAMAAMA+AYIALEMAQDNDwEAE4ACoEAAAAvPY3QBAAEG6LbFTuxp85gMAAB0BwEAAQICoCAAABFSoDAAABIXYmgBUw8AACOBAqCAAAAStWFwAYQCsAAQ+gABsAAQ/AABNDYIVQAAAZAFAAAAAAdmYm91bmRzAADyZw8AAAEAAAAOb2luZ2hhbmRzaGFrZXMAAAEWEwAAAPIAAwFnb3BhADowAAABAAAAAAAOABAAAADqCOsI7AgoDCkMZA9lD1MQDhFdEl4SVxPZEwoUCxRWDgCAAQegEAAADJmwkBEAARUfRFCpJRF0M2dsbnBycwEDAPQVCQAAAQABAAEAAQABXwC3vFiDYwEYDAAAAQABsPAUDAABa/aAEwAABQABAAEAAQAlAPMDAWTyRxIAAAEAAYICsHATnQABoEAAABCTaXMTgAKwYBMkAAFlbwEBA6BwAAARVaBAAAATMKAwAAAPVGRsc/LdEwAAAQAIWwAAAZoMAAAA8gJlcgEAZfKYBAAAAQAAdg0AAIGHLq9KuYHDTW7NXdeFgAAAFWEEAPOEAAAATAEBAAEEAPNCEgAAAQCoAPMAAWM6MAAAAQAAAAAACgAQAAAAaw9sD1QQVRDfEGkRahF+En8SCBQJFAGAAqBAAAAQGGktJx5LB25wAQIBK+pnYX6mbTswAAABAADnAAIAfAUAAKgF5gABgAOgQAAAC86gEAAAC9awcBPjAAFhZXYBgAJ+QVl3TAegMAAAEEtjcAEEAPNmDwAA3AABACMAAqCgAAAS2GRxOjAAAAEAAAAAAAgAEAAAAMgGyQa1DLYMzw/QD5MQ9RD2EAEHAPQKEQAAAQABAAEAAQEAOzAAAAEAAAgAAgAvDwYAxxIBAPOAAmRuI4ACsJASVAABZW8BAAD0OQkAAAEAJQcBACOAAqAQAAAS72h5AAZkc3RjaWQAAPJPEAAAAQAA8oABAWd0AYECoBAAABKwoHAAABL3YWPzAQFy8iYRAAAYAAgLAAGwgBNRAAF0AeMIAAAA+qsBAWll8gABAGUBiAAAAPObDAAACAcBAPZrAACCiT69W9rtiXcfC6kFoQgBABSUTQA7MAAAAQAAaAAyAIgECwBKBQAAhQUAANgIAADdCAEA3QsAAOILAgAGDAEAmgwAAKYMAgC4DAEAVQ8AAFoPAQBdDwEAYw8AAOsPAgDyDwEA9w8BAPwPAAAMEAEAFxAAAIoQAACQEAAAnRACAN0QAQDqEAEA/hABAAIRAgBbEQEAbhECAHQRAAAkEgAALRIBADISAQB4EgEAiBIBAIwSAQCsEgEAvBIDAMISAQAiEwEANBMAAGITAQBpEwEAcxMBAJ8TAADEEwQAzRMCAOUTAADqEwAAAQcCoLAAABO5oLAAABO6Y3PzTRAAAAEAXAABgwIsTV1wV0l1R+cBLvdobfOAAmlvAAJvbgAAAY8FAAAA8gABAGnzhQAAAAEAAQABxgsAAAAQaXJlY3Rpb25hbHN0cmVhbQAAAeETAAAA8gJuaQEAZPIyBwAABgIAAANhdHMAAAFkEQAAAPIGdHJlYW1zAQB0AAEXEwAAAAVuc3VyZQAA8mUSAAABAAD6OQEAZfPiCAAAOAMBAAH+DwAACCYAAPSLBQAAAQABAAEAAAAKb25uZWN0aW9ucwAA8mMSAAABAAAIBwAAAdUTAAAA8gFpAQBk8ooFAAA8AgDyAIaAFoAK80IEAAABAAEAABKAAQGgoAAAE89ucjYCAACChaCwAAATlaCwAAASoS+1jbSlf3FOLsfLwxAABADQAwA7MAAAAQAACgADAOYBAAAMAgYAcgQCAAGEAqAQAAAMmaAwAAAQADNzAYECsDASigABsEASqAABYWYBgAKwIBO3AAGwYBNlAAFhcgGAArBQE+sAAaAQAAALxmZwAQIAOzAAAAEAAAYAAQDgEAYAAQIBoEAAABOfZfLJEwAAAQDzgAJpbwGAArBAEicBDqAgAAAQo2FpAYECsAASUAABsAARXwABZXMTgAKgMAAAExthcgEIAPMvEgAAAQABACOAAqAQAAAS+mRzABV1cmlvdXNjb25nZXN0aW9uZXZlbnQAAPT7EwAAAQABAAEAAAGBAqBAAAAQJaBwAAATumFlCFAAAPTCBwAAAQABAAEAAPIAAQFucgD25wgAAFcAxwYBALAAAQDyAAICZXBpdAD1YxIAAAEAhQABAPAAAAd1dXBkYXRlAAA7MAAAAQAABwABAK4QBwAAEoABAbBgEXUAAXRpAAZ0ZXJ2YWwAAAGmDwAAABKAAQFdvfYXYaRuZAGAArAwE3MAAbAwEkUAAWNpAAV0Ynl0ZQAAAa0QAAAAEoABAbAgEq4AWXNtEoABArDgE20AATKJ8JsEe3JkZ/KAAQFpYQEDAbAAEpQAAXM7MAAAAQAADQADAF0RAQAOEwcA+xMDAAEFADswAAABAACHAAIAcxAAAH0RhgABggKwIBABAAGwQBD1AAFsc/OBAmt1AQMA9VsRAAABABwBAQBWAQEDADswAAABAACGAAEAqg2GAAEAAPS5CwAAAQCxBQEAAQIAOzAAAAEAAAcAAQD1CwcAAAVkYWNrcwAA9KIPAAABAAEAAQAA8gABBm5iZGlydHYAOzAAAAEAAIwABQD0AQAAgQQAALwEhgB6EAEAFREBAAALZXN0aW9uZXZlbnQAADswAAABAAAHAAEADhMHAAAa/gEBsNAUCAABZ3QAAAAA8uEIAAA4AzswAAABAAAFAAIAJRIBAHoSAwDyAnNlAQBkADswAAABAAAJAAMAVxEDACASAwBSEgEAEoABAaCQAAATuW9pMoACAUcHMoOO62xvdQAEeXRlcwAAATcJAAAAAQUA9NUTAAAMAB4ADwAjgAKgYAAAEu9ncwgtAAA7MAAAAQAABwABANMGBwAAUoABArCQE7cAAWNibPZvJwCIinsWc+5jYG8azk8CQxbqmxkzpLBQEpYAATcRAsDolQA7MAAAAQAATAAfAI8FAADfBgYAPAcBAMAIBQDQCAAA/ggAAMgLAAARDAEAiQwCALMMAQAyDgMANw4BAEwPAABZDwAAaQ8BAIIPBAAjEAAATRABAFEQAQCHEAAAqhAAAMoQCQAIEQAAChEDAFURAACKEgEAoBIAALcSAQDJEgUA2RIAAI0TAAABAQKgwAAAEsagYAAAEBhjcvKnBAAAyQABgAF+ZExv+zVu8gAAAmVpAQkAAAAACB8AAPLRBgAAAQAAEoABAbAwE+sAAXNsEoABArBQD2cAAW4PwRW8CmZidQGEArBwE34AAbAwEtQAAWVzAARhZGRyAADyNxIAAAEAAAgAAADyNRIAAAEAAPKCcHYCADQ2AAt4dGVybmFsYWRkcgAA8y8TAABoAAEAAAEGAPQPFAAAAQAKAAEA8wIBcgF4EwAA8gNkZHIAAWX0AxAAAAEApwABAADyhWVydmVkAwBhZWmSgAEDoLAAABNydAVXfAv9c2FldQAACA9EpAoRwsZ1IRUFJhQv9ByMKrYCU7ycAFcWwWq1fJ5dLgPiECpxKt0J0AdnUk4869CgMAAADAhYAtoQ6d9FzvtLH+czgbZFKIAQh9vY7hZBWfbDWYE1gGRK5th2XP+monFlXLRA+7oi9m/oHbRP9PfunjMQdEkqbOFO7UoM/h4Glxg4ALdPe63rcHZiZmtucHJ0dTBhY2RlZ2lsbW9xc3Z3eAA7MAAAAQAAdgISAO8BAAAAAgEAVAPmAKcEAABwBQAAgwUBAJwGCgAvBwIASAcAADUJAADDCuYAGAwAAK4MAACqDYYA1g8BAPEPAAApEAMA5hMBAAEAAqCAAAASRLBwE5QAAWFzOzAAAAEAAAgAAgB1AAAAIgkHAAEBAaEAAAAT4WTyMgcAAAYCI4ADoAAAAAvpoWAAABPbbW5zAYGFsDASigABsEASqAABoFAAABIQsKAS2gABsHARbwABIQgKAQYBD+/Kb+Zsc/JlEwAAAQABAgGwUBJlAAFl9uIIAAA4AwEA4wO5AwEAU4ADoBAAAAx4YW9yAQEBbAAqva7odfK/BwAAAQABCQDzQhIAAAEAqABTgAOwYBF1AAFlaXQBAQD0dgwAAAEACwYBABMAA6AwAAAHSRUXl2fEDmVuc/V/BAAAAQDNCgEAjAABgAKwIBKuAFmgUAAAEK1tc0OAA7DgE20AATKJ8JsEe2RncjGAAzkAsAAMrgNDYWlvAQABoQAAABPVZPKKBQAAPAITgAOgUAAAEqugMAAACTdpbHkBAAGgoAAAEthk8sgGAAABAAEEAaCQAAAS93LyJhEAABgA84ACZGUAAAABsAAElwKybjswAAABAAAGAAEA0gEGAAAACWlhYmxlcGF0aAAA8vMQAAABAAAADm5vdWdoYWRkcmVzc2VzAAABtRIAAAASAAEDoGAAABMwsHATJAABTW7NXdeFZWFjbvOEAAAATAEBAAAAAmRwAAABwQcAAAAAA290bwAAARgMAAAAAAJyZgAAATYJAAAA8oACAGVy8gACAHB1Ac8BAAAACHsAAAG6EwAAAAh7AAABuRMAAADyCG90ZWN0aW9uAgBjc/NNEAAAAQBcAAASgAEBoAAAAAvGcnQAA2l6ZQAAAQAQAAAAIoRlcm91AgDjIAAADJkzcwAEdG9yZQAA8vUQAAABAAAIeAAA8gEQAAABAADyhG9rZW4CAGxz8gABAHQB9AEAAADygAIAZXoAEW5pdGlhbGNpcGhlcnN1aXRlAADyLRMAAAEAAAgvAADy3QgAAAEAAAASZWZhdWx0Y2xpZW50Y29uZmlnAADyZxMAAAEAAAARZGF2YWlsYWJsZWZvcnBhdGgAAPLLEwAAAQAAABNydGlmaWNhdGVzcHJlc2VudGVkAADyoRMAAAEAADKAAgGwQBCIAAFlaW8AEnBwbGljYXRpb25wcm90b2NvbAAA8mUTAAABAAD2fwEAy8OwUBPrAAGgsAAAErVAR2gaAAiABAD0LwcAAAEAAQAEAgEEAPSiDwAAAQABAAEAI4ACoEAAABQjYWQBAAKgMAAAE9SgUAAAE2RjczswAAABAACSAAcAiwUDAEUHAACoBwAAQwmGALkLAQA6DwEAaxEBAAGAAV1F3974zHQAB3J0dGtleXMAAPQyEQAAAQABAAEAAPIAAQAx8kAEAAABADswAAABAAAFAAIAHwwDANQQAQASgAEBoIAAABCWdHAAEWl0aGFic3RyYWN0c29ja2V0AAABvxMAAAAAA2tlbgAA9N8IAAABADYDAQAAAANtZXIAAPMTDAAAAQABAADygAIAaW8ABWVuZGVyAAABQA8AAAAABW9uZmlnAADyiBAAAAEAAPIBbwEAY/QvBwAAAQABAAQCAAAFZWl2ZXIAAAFMEAAAAPrHAgBjbgAMeHBlY3RlZGl0ZW1zAADy3BIAAAEAAAgyAAD0YhEAAAEANgEBAPTxEgAAAQCnAAEA8glvbm5lY3Rpb24BAGkBhxAAAADyAIaAFAAXOzAAAAEAACQAAQCqASQAAPGAAmhpa3UINQAAAUsQAAAAAANpbnQAAPKGEgAAAQAA8gVoYW5nZQEAaADzbxMAAAEAawDyg29yawIAY3DygAEBd28AD2V5ZXhjaGFuZ2Vncm91cAAA8gwUAAABAADyAAEAawD2gBMAAAUAAQABAAEAJQDyBm90aWF0ZQEAZADyRxIAAAEA9J8AAMTIAAMJFmwZGm4barca8+ILsBAQxgABcwAQADKAQ4gJADswAAABAACMAAUA9AEAAIEEAAC8BIYAehABABURAQABiQIsTV1wV0l1R+cBLvdobQEEAPTVEwAADAAeAA8AI4ACsHASLQABaXQHAQCAhXp599zKF7BAEOoAAaBgAAAS76AgAAAJAVEBAgEBADswAAABAAAHAAEA0wYHAAGBArAAE0MA4LEgE+sAAWRzI4ACsIATtwABYWwBAgOwIBDIAAGgUAAAEW2gQAAAE6Bsc3bymxMAAAEAAQIA84QPAAABAAEAAQEAOzAAAAEAAAYAAQAcFAYA8wYBZfTQEwAAAQAvAAEAAAZwZGF0ZXMAAPLwEgAAhwAAAARuZGVkAAABiRMAAADyBG91bmQBAGUA8vkTAAABAPIIcmF2ZXJzYWwCAXJ1YQH/DwAA8mESAAABAPKAAQJ0aXUABGF5ZXIAAPKgDwAAAQAAAARyb3VwAAABmAwAAADygAIAZ2waDwEBsDAS+AABZHQA9qwMAADFAloAAQC4AgEA8oACBG10YmNscAAMbmF0dHJhdmVyc2FsAADyYRIAAAEAAAAABBRVhR0yggNrzJyCIngS1BlfF+IrzFGnsINbHIm8vsQ+PLsE9HEvfsxduxo9YbWKNhU+EDE8WWVwWMjABKp5aeGQAxphbkLKpEkwNqkkm1QzrizyXMs8j0GdZRk/p0yzOtV4MZyURB8/JM1apPpb1JAEJ6ZSoqmafGdHSzHmxSSwUBKWAAEfVfrtL69vEf4HJVYwYWVvYmNkZmdoaWtsbW5wcnN0dXZ3eXoAOzAAAAEAAIwAOQAHAAAAiQAAAKwAAwCiAQMAqAEAAN0BAQA7BAQAQgQCAGsEAQBxBAAAlwQAAGAFAgBvBQAAdwUBAI8FAADfBgYAPAcBAEkHAgBNBwMA1QcBAMAIBQDQCAAA3wgBAP4IAAC8CwAAyAsAABEMAQAWDAEAiQwCAJwMAQCzDAEAuwwHADIOAwA3DgEATA8AAFkPAABpDwEAgg8EACMQAABNEAEAURABAH0QAQCHEAAAqhAAAMAQAADIEAsACBEAAAoRAwBVEQAAcxIBAIoSAQCgEgAAtxIBAMkSBQDZEgAAARMBAI0TAAABBQDzexMAAAEAAQBDhAOwkBP3AAGgAAAAEihiZHcTgQKgMAAAD4djbQGAAqAQAAATUKBQAAASFmxuI4ACoEAAABFhYWkBBgD0jhIAAAEAjwABAPMDAWL0gxAAAAEAhgEBAAGAArBAETAAAaEwAAAT6mhvAQIA8wMMAAABAAEAE4CEoCAAAAv0sBAJCgABsDATgwABAAlIAQEBsIAS1gABdfITCQAAAQAAAAAAAakBAAA7MAAAAQAA+QAJAH0FAADHCAAAzQgAAMULAAABDAEAlQwAAMMM5gA6EwUA7xMFAAALdG5lZ290aWF0ZWQAAPSFEwAAAQABAAEAABKAAQGggAAAE61vZRIGdGlwYXRoAQKgYAAAE0NhhlhUop5uZXAB2QsAAAC2AACAAgOwEAkRAAGwYBEwAAFsdDM2bQEGAPNtEAAAAQAOAQECAaBAAAARbmXzsAwAAOQDAQAADXNjb3Zlcnljb25maWcAAPSzEgAAAQCVAAEAABKAAQFxeebaljNpcBIAAQFsACq9ruhkdQA7MAAAAQAACgAEAL8HAQBBDwEAfw8CAIgPAwDyAAECdWlvADswAAABAADpAAEAsgDpAAEGAPP3EAAAAQABABOAArBAEXUAAWJtAQMBsEAT3QABdPJBEwAAAQAjhQKgQAAAE3FicyMAA6BAAAAQf6BQAAAQ7GFiZvfWCAAAAQBIACcHAQBfAAEAAAxlc2V0aW50ZXJ2YWwAAPLaEgAAAQAAAAJ0dQAA8r8HAAABAAAACW11bWNoYW5nZQAA8jARAAABAAB2AQAAg4OwEAyuA0OgUAAAEXQAEQFgAAQBqAEAAAAABnJhdGlvbgAA8hEMAAABAAC2AACAAgOwAAjoAAFmf5QtqSBnbmRzdAEDAPN1DwAAAQD+AAEDADswAAABAAAKAAMA7hACACYTAwAEFAMAAQQA9LEMAAABABsDAQAzAAOwMAyQAAFvc3QBwxMAAAEAAPQCCQAAAQABAAEAQwADoEAAABP1sCASjAABZXJ49BMMAAABAAEAagABCADzQhIAAAEAqAABgAKwsBMiAAGgMAAAEW5ucgECAPPqCAAAAQABAAEGAPT6DwAAAQCpAAEAowCEoEAAAAyYsEAPoAABQEkA89YLAADYBlkAAQIAOjAAAAEAAAAAAAcAEAAAANAL0Qs2DzcPbw9YEPgS+RIBAAD0BgkAAAEAAQABAAGBArBAE80AAVCHiPQ9+mVzAANpY3MAADswAAABAAAHAAEAtwcHAPKAEgAAAQBWAwCAAQWgAAAACOawcBFTAAFyYWVoeHnW4gAAwcugkAAAEtmgEAAAEO0OKqxE2HBqRXwOXf6gMAAAE3IAAAACAEe8CQA6MAAAAQAAAAAAIgAQAAAASwVMBToHOwfJB/cIKwksCZYMlwysDLAMUQ9SD3EPcg/LD8wPGBAfECAQIRAiEF0QXhCUEJUQbREEEoQShRJVE1YTgROCEwECADswAAABAAALAAIAOhMFAO8TBQDzCAFtOzAAAAEAAAwAAwDpEAAAVhIFAK8TBQAABGV0cnkAAPIPDAAAAQAAAAdyYWdtZW50AADzSBAAAAEAAQAAAAZlZnJhbWUAAPKWDAAAAQAA8oADAGJmcgANZHBwYXlsb2Fkc2l6ZQAA80YTAAABAAEA8rsTAAABAAAPcmFuc21pdHNlZ21lbnRzAADydRMAAAEAAAhUAAD0hRAAAAEAhgEBAAAAA2lkaQAA9PEQAAABAG0BAQAA8gACAGJ1AZ8PAAAAAANhdGEAAPSDEAAAAQCGAQEAAPKEcmVhbQIAZHMAAnplAADz2wgAAAEAWAAA8oACAGl0ABhvdGVuYXR0cmF2ZXJzYWxhZGRyZXNzZXMAAAEgFAAA8iEUAAABAAAMZWl2ZXNlZ21lbnRzAADyRBMAAAEAAPqVAgBjbQAGZWFjaGVkAAABNBIAAADyBWF0aGlkAQBy9NcLAAABAMUDAQAAAAZjb21pbmcAAPJGEAAAAQAAAAlsZXRpbWVvdXQAAPJdEgAAAQD0VxMAAIIAMQABAPKAAgBkbgAKc29zZWdtZW50cwAAAVwSAAAAAAdyYW1zaXplAAABmBIAAAD6CwEAZ/QtBwAAAQAEAgEAAAAJbmlzdHJlYW1zAADy3xMAAAEAARUUAAAIYgAAAZYTAAAB4hMAAAANdWx0aXBhdGhwYXRocwAA8hMUAAABAAAACGlzdHJlYW1zAADy7RMAAAEAABrfAQGgYAAAFBFkc/YAAIlvbmN1cnJlbnQEAaBAAAATwmJtc3VwAAdja2RlbGF5AADyqBAAAAEAAPY/AACKgbCwEqYAAU2BDyAAAPSAAAAAAQABAAEAAAAGZm9ybWVkAAAB1gsAAAAIWwAA9TsEAAABAAEAAQABAAD2BACAhISwMAcmAAF29wOlYyI46FNWGooACWAEIAX2HwQAxc2gIAAACQ6wkBK8AAFASvzOCtpDUf63kP43ri8swmVk9AHQEFywcBPjAAGgMAAAEuJARAAGgAu3GQA7MAAAAQAAWQIhAO0BAAATAisBnAUDAN4GAAAGBwEARwcAAMoIAgD8CAEAygn4ANULAADmCwAA6QsAAKMMAgCpDAIAbQ8BAMEPAQDGDwEA5A8GAAkQAAAOEAAAKBAAAC0QAABWEAEAxBABAGkSAADvEgAA9xIAABsTAABAEwAA1RMAAOETAAD/EwAADhQAAAEDAPNJDwAAAQABAAEEADswAAABAAANAAEAGBENAPOAAmlyAYACoDAAAA/5sHAStwABYWkBAAKgMAAAC+mgcAAAEVZldvP5DwAAlQATAhOAA7BQEvAAh7AQD0cAAWVwcwEJAPQMEwAAAQAPAAEAE4ACZQVDlK24bW4BAgA7MAAAAQAABQACAB8MAwDUEAEAI4ACsTAUHAABYWUBAgD0iA8AAAEAAQABAAECADswAAABAAAIAAEAORAIAPMAAWUBBwIAAAGAAqBAAAATjKBAAAAP/WVvAQABoFAAABLvbwH6CAAAAYADsDAPRwABoEAAAAvWoJAAABPUbG9zAQEAOzAAAAEAABMAAQCpDxMAg4CEoEAAABJ3sJATLQABoDAAAA9GgBkAAYADsLASrAABoBAAABMwsDAMwQABY2VzAQEBoHAAABKwZQGbDAAAAQgA820QAAABAA4BE4ACoMAAABOVbXABAgD30Q8AANEApAIBAAEAcwABABOAArAwEGQApWl08wEBc/LrEwAAAQAAC3BtdHVkcHJvYmVzAADyDBMAAAEAAAAFY2tldHMAAPJEEAAAAQAA8oACAGFsCFwAAPI+DwAAAQAA8gACAGJwADswAAABAAAGAAEA4BAGAAAPcmVkdWN0aW9uZmFjdG9yAADycxMAAAEAADKAAgEJAx7NkK9zdGUAA3lwZQAAAdoIAAAA8gABAHTy8gEAAAEAAUYHAAASgAEBRcfxMhPQZ2UBggKwIBDCAAGwMBE4AAFlcAEFAPRrDwAAAQDoAAEAU4ADsJARaQABYWlyQgAAA7BQDKEAAaAgAAAJPWNkZvKmAQAAAQA6MAAAAQAAAAAACAAQAAAAWwTQBu4I7wgBEAIQbRBuEHwRAAd5Y2xvc2VkAADygRAAAAEAAAAAAAD1LQkAAAEAAQABAAEAABKAAQGgoAAAE/VwZgAFb3N0djQAAAH9DwAAAAhaAADyPA8AAAEAAPYAAAFsBAGxQBQcAAFhaGlsbvJRBQAAAQAAEoABAWoVKq27S2Fr9h0AAISFoEAAABPGRCACEUARAPLmEwAAAQABAQFVEmdd/IVpASoJAAABAQGgQAAAE59l8skTAAABAAEAAPUtCQAAAQABAAEAAQABBgD0ohIAAAEARQEBAAEEAPS8EgAAAQAHAQEA8wICZXL0BxIAAAEAZQEBAAGBAQdA44jKFnQABHRpbWUAAPIrCQAAAQA7MAAAAQAABwADAB8QAwBdEAEAVRMBABKAAQGgkAAAE/VlcxYvAADByqAwAAASECu+oWbx+D+GRcANfLBgE20AAaAQAAAJAQAIAABABySLAAF8AAAAAQUAOjAAAAEAAAAAAAgAEAAAAGQPZQ8OEV0SXhJXE9kTChQLFCOAArAAEyQAAWVpAYADsHATOAABsRAT6wABsCAPwwABZGVpAQIA9uoQAAABAMEBAQAaAQEAQwCEWVOJSq/tsGASgAABsIAQawABIBAF8/QLAACYAAEAAYICoKAAABKwoEAAAA/5b3YBgAKwUBFpAAGwUBPLAAFhbwGAAqAwAAAQkLAwEDcAAXJ2AYQCsKATgQABsKATgwABY3LzAAF0984IAAABAJII5AABAP0A4AABgAOwIBJ1AAGgoAAAE0CwYBMBAAFlbG8BAAA7MAAAAQAA9wACAI4MAQA5DvUAAQAA9IwQAAABAKkAAQATgAKhMAAAFANldMIAgISgsAAAE0uwsBNvAAFYAAT0ogEAAAEAAQABADswAAABAAAHAAMATQcDANUHAQDIEAEA1ocDAMHOoRAAABPboBAAAAw2sFATaQABsDAQ8wABoAAAAAvcsEAQjgITAAAIAEBf8AsAOjAAAAEAAAAAAB8AEAAAAG0FbgVxBcUG5AjlCPgI+QjAC8ELwgvKC80Lzwt8DIcM1A/VDwYREBInEjsSPBI9EvMS9BIIEwkTNRNQE3kTehMBgAKwsBPjAAGwIBK3AAFmafECAa9l9NATAAABAC8AAQDzAgFl9TwPAAABAPIDaAABAAGFArBwEwEAAbCAEzgAAWNzAQIBsGATywABZjswAAABAAAGAAMAwAsCAPMSAQB5EwEAAIACAMAAAAAHLOdAAAAI2WR4+nkBAGUA8qAPAAABAPIAAQBlAPMiBwAAhgkBAAAJdG9wZW5wYXRoAAAB/g8AAAAAA2dlZAAAAYkFAAAA9gsAgIOFsCAI8AABQABCDgAFAADE0Xwy0MrlUkl3EsaPI0wjQpSD5hSGBZCxXyaPNPukpTZZC0EUdF4Ht5MSLFnTW14Tt3BUCWyKDC642M/ULwqK1LFUpEDYw+1RHS5CD4iMfwXC3Cia9Xe+BEN4tTiyNncaZRWTrRsIWVtX/SnUuEUQHCk5+GYOk71NjAHau6PdD0BEEAAAO69fADswAAABAABCABkA+gEAAAYCAABMBA4AYgQBAHAEAABRBQEAJAcBACgHAAAgCQEAOwkBAKoLCQB9DAAApg8AAP8PAABlEAMAKRIBAGESAQDaEgEAChMBAGUTAQCPEwQArhMAAN0TAQACFAAAGBQBAAEBAPV7BQAAvgwBAKQAAQABAAA6MAAAAQAAAAAADgAQAAAAAgIDAn4FjwaQBhwMHQyiD6MPpA+lD78PaRBqEHcSAQMBoFAAABEGaAFLEAAAAQACoMAAABLGoGAAABAYY3LypwQAAMkAAQEA9ScRAAABAAEAAQABADOAA6EgAAATjW5vcAGCArBwEwEAAbCAEzgAAWNzI4ACsEAP1AABYW8BCAEOqASN4Lxj9dsPAAABALwAAQABAAEEAbAwEoYAAWjzbxMAAAEAawATgAOg4AAAEzWgcAAAErlob3MBCAA7MAAAAQAABQABAEkSBQAAA293bgAAAZcEAAABSQcAAAEAAaDAAAAToGn02BIAAAgAGwAbAAEGAPQEFAAAAQABAAEAAQAA9HcFAAABACQHAQATgAKwcBKuAFlubwEKAbBAE8cAAWX1jxMAAAEAAQABAAEACFEAAPIyEgAAAQAA8gVwZGF0ZQEAZQDyVBIAAAEAAAAAAPLwAQAAAQA7MAAAAQAADwADADkQCAAyEQMALxICAAADYWlyAADyKgcAAAEAAAhXAAAB0AYAAAD2BAAAhIOwIBCPAiqwwBQMAAEAiAoYAQAAOjAAAAEAAAAAAAwAEAAAACYHJwcOCdML1AvaC9sLKwwsDHYMdwyCEoMSAA5wYWxpdmVpbnRlcnZhbAAA8goTAAABAPSuEwAAVAAWAAEAdhIAAIKIdm2Q8deFsQATpQABMgsPSvJ4U6cJ/1R1bfu9fPjfEABADDgHADswAAABAAAOAAQAdQQFANILAAA6EQMAGxIDAPbeAACCirBAEKgAAaAgAAAMtxAgAK2BCwA7MAAAAQAAFgAKAAkAAAByAAEABwIAAH8EAQDTBgcANQcBAE0PAQDaDwAA+g8BAKQQAQABAAKhkAAAFAOgMAAACPdldPLYDwAAAQABAQGwQBCOAhN28zsSAAABAAEAI4ACoBAAABP2ZWwBAAEX0O3eVA1sOzAAAAEAAAgAAwA5CQEAXxABALoQBAABAAGwYBN1AAFl9AMMAAABAAEApwcBBQD0ohIAAAEARQEBAAECAWmwNrDeZnQ7MAAAAQAACAACAL0LAADrCwcA84ACYWUBgAKwIAj4AAGg4AAAE2RibAEIAPQbEgAAAQABAAEAAQQBsAAQaQABcwHaDwAAAQIBoFAAABJ3YwF9DAAAAQIA9LwSAAABAAcBAQAAA3RvcgAAAdgIAAAB/A8AAFIAAQKgIAAAC8hhaXIAAQkMAAB2AQAAgYawQAy4AAGwEBLcAAGgAAAAErYAAAGVEAIAOjAAAAEAAAAAAA0AEAAAAKYEqARzBXQFdQWADIEMSQ9KD0sPVw8tEy4TMRM2FgAAgYg7TijtkxJEHkJWNc6wEAVtAAGwIAdKAAEQAACBCUcAOzAAAAEAAJUACgB8BAAA8ggCAHMQAADBEAAAABEAAH0RhgAFEgMAFRIAAOISAABtEwEAAAlhbGlkcmV0cnkAAPQsEQAAAQABAAEAAAAEcnZlcgAA8g0MAAABAAAADG1lY29ubmVjdGlvbgAAAdkSAAAA8oACAGFlAA5vYmFibHlzYW1lcGF0aAAAAXITAAAAAA10aHJlY292ZXJhYmxlAADyCBMAAAEAADKAAgGgIAAAByJhcmwACWVnb3RpYXRlZAAAAa0TAAAAAAZuYWJsZWQAAAFDEwAAAPKHbHRpcGF0aAIAZW4SgAEBsDARdQABdWEBAAGgQAAAEHdl89QIAAABAEEJE4ACoDAAAA9jZHIACW5kc2hha2luZwAA9ScRAAABAAEAAQABAADyAAEBYWUA88IGAAABAAEAAAZyYWluZWQAAPI6DwAAAQAAAQQBBPlAI774YwEfEgAAAANzZWQAAAEMDAAAAAADZW50AADyCgwAAAEAAPKAAgBpb6KAAgLQAAVPAAGwQAvLAAFlbGFvAANpdmUAAAEqCQAAABKAAQGwMAjOAAFsYgADcnR0AADytQcAAAEAAPQ/AIAJAgABAwgJDREUF9pKp7R0Ov4wYWNkaG1wc3ZidAGAAqCAAAATrTrYf5OJTWVvIwEDoGAAABNDYYZYVKKeZW5wAdkLAAAAAmRyAAABiAUAAADygAEBZHQWAAAAAQRXVqf9hfuhEAAAE42wMA+CAAFDfokF52dhaHN0dgA7MAAAAQAACAADALQGAAABBwIALQkEAAEQAPT7EwAAAQABAAEAAQIA9DkJAAABACUHAQATgAKgUAAAEBhpdQAGaWNlbXV0AAABlQwAAADyAAEAbPKgAQAAAQAAFgMAAAEFoMAAABLGsFAStwABT66aeaFhc2NsbnJ1APKnBAAAyQAIaQAA8n0QAAABAAAACWVydmVybmFtZQAA8oQSAAABAAAADGVtb3RlYWRkcmVzcwAA8yoTAAABAAEAAAAGZXNzYWdlAADz7hAAAAEAAQAAAAVlYWRlcgAAAYAQAAAAAAZyYW1laWQAAAHtEAAAAAATbmNyeXB0ZWRjbGllbnRoZWxsbwAA8uYTAAABAAAADWV2b2NhdGlvbmxpc3QAAPLJEwAAAQAAAAZmaWNhdGUAAPKxEgAAAQAA8oJydAIAaXISgAIA9kAMkwABZWnyhGFsaWSIgLQQBwAIbm9yZGVyZWQAAAEmEQAAAAAGZXF1ZXN0AADzZg8AANwAAQAACB0AAAH8DwAAAPICZXIBAGEBCQwAAAAIAAAA8jgPAAABAAAABW9taW5nAADyphAAAAEAAPKAAgBjbvKAAgBudAAFdXR1cmUAADswAAABAAAOAAIAkgwAAI8PDQAAAAJueQAAOzAAAAEAAAcAAQAiCQcAAAgVAAABCAwAAAD2AwAABgGgsAAAE5QwYWZpcnVtOzAAAAEAAOcAAgDvAQAAVAPmADswAAABAABuAQMASAcAAMMK5gCqDYYAAAJhbAAA8iAJAAABADowAAABAAAAAAAIABAAAACmD9oS2xIKEwsTrhMCFBgUGRQAB2FsZXJyb3IAAPLqEAAAAQAAAAhhY2VpbmRleAAAAQkSAAAAcgADAbAgEIoA6mZudnIAAcgHAAAACXJpdHlsaW1pdAAA8gcSAAABAAAygAIBTsz/40NSZ3J2NgAAAAIEsNATMgABsFAQ5wABoDAAAA9MTWzmc0OHZW9iY2pzADswAAABAAAUAAkAVwUCAKIFAQARBwAA0QgCAP8IAQAyDAMA9A8CABESAACGEgEAAAJudAAAASkHAADy7RIAAAEAEoABAaAwAAAQKGFyCA8AADswAAABAAAFAAEAygYFADswAAABAAAFAAEASRIFAPKAAgBldPEBATpl88IGAAABAAEAABFuYXR0cmF2ZXJzYWxyb3VuZAAA8vkTAAABAAASgAIA9yAMBgABZW8ABWluZG93AAA7MAAAAQAADQABABgRDQAAAAJ0dAAA84wPAAABAAEAAAhhAAD0iA8AAAEAAQABAAAAA2V5cwAAOzAAAAEAAAgAAQA5EAgAAAANc3RjaWRwcm92aWRlcgAA8r0TAAABAAD2AQAAhYKwoBMtAAGgUAAAE4wIFBGEAAABKAcAADswAAABAAAFAAIAZRADACkSAQDygWECAGx08gABAGkAOzAAAAEAAIcAAgBzEAAAfRGGAFKAAQKwgBEwAAF0bXMBAQA7MAAAAQAAJAAIABQHBwDIDwIAGBENAD8RAgATEgEAlhIBAOcSAQB7EwIAAAF4AADyTQUAAAEAAQkSAABSAAEDoHAAABIPoFAAAA99ZWFvcwAB5gYAAAALc2lzdGVudGtleXMAAPMvEgAAAQABAAABBgDzOxIAAAEAAQAIEQAAAewQAAAAAARvdGFsAADy3RMAAAEAAPIDaXplAQB08kETAAABAAAIRwAAAXETAAAA8oV1ZmZlcgIAYnMABGxwbnMAAAF/EAAAAPICbmcDAGFiZvPWCAAAAQBIAPRGEAAAAQBfAAEA8oABAWlw8oFvAgBtbgEDAPOEDwAAAQABAAGAAqAgAAAJAbCAEi0AAWlzCCcAAPQmEwAAAQABAAEAAAAPYW5kc2hha2VtZXNzYWdlAAD0BBQAAAEAAQABAADyiXByb3ByaWF0ZQIAaG3ygAECcGx09gcAAMfGPytMmIl3sBAMrgNDKmdyCFHRsCAHvwABsCAPOAABsMAS2gABQEMACwAcjAAAOjAAAAEAAAAAAAkAEAAAAIkAqAE7BDwEPQQ+BD8EcQTBDMIM8QECtbZlcvQHEgAAAQBlAQEAAAllZGlhdGVhY2sAAPT6DwAAAQCpAAEAABYBAIACBMAAAAABn7AwEAwAAQQ1xfEeibBwETAAAW1wYWVpdfEAAap082ERAADkAAEAEwACsGARaQABZGYBUBMAAAEAAqBgAAATNKDgAAATjmV0OzAAAAEAAAgAAQCsBAgAAQMBsGATywABZjswAAABAAAGAAMAwAsCAPMSAQB5EwEAAARhbHVlAAAB+Q8AAAAACnJkZXJlZHJlYWQAAAGwEgAAAPKEZWdhbAIAb3bygAEDbGFkZQGCAqBQAAAST6EAAAAT9mN0AQQDsCAQyAABoFAAABFtoEAAABOgbHN28psTAAABAAAKaGFuZHNoYWtlcwAAAdgSAAAA8gJyZQEAZPLIBgAAAQAAMgABAi6K1OXXUm9hZQA7MAAAAQAACAADAGsEAQC8CwAAuwwFABYCAAABBbBQEE8AAbBAD1oAAaEQAAATZLBQDBEAAW5kZWlydQA7MAAAAQAANwAWAJoGAQDDCwEAoQwBAF8PAQDeDwUADxAFAIgQAQDnEAEAcRECAD4SAwBuEgQAnhIBAKQSAQCzEgEANhMBAEkTAQBnEwEAaxMBAI4TAAC1EwEAuRMBANgTAAABBwD1bxAAAAEAAQABAIMCI4ACsHAShAABcngBBgD0ZREAAAEANQEBAPOAAmJlAYEDoEAAABI0eYUCNIopLc8aI3BSYW10AYACsEASqgFZsGATvQABYXIBDgD01RMAAAwAHgAPABMAArBgE+0AAXJzOjAAAAEAAAAAAAcAEAAAAPEQ8hBfEmASxBLFEloTWxMBCAD3nRAAAAEAAQDDAgEAggAFAAEFAPQfEAAAAQABAAEAE4ACsHATxAABZm0BAAD0TQcAAAEAAQABAAAGaW1lb3V0AADyZA8AAAEA9l0SAAABAPkAggAxAAEA8gABAXRuAAF8DAAA8oABAWVpFuoAAIGMfr2yW6KKCzE+sIcvINobjo4koGAAABDtoFAAABCAW7vNIVGvsEAQfQABAAgA9ZEHOzAAAAEAAA4AAgAIAAAAYQANADswAAABAAAgARkAkQUBAKEFAACpBwcAxwcAAMoHBADXB+YA6AgBABkJBADXCwEANgwAAJMMAQCdDwEA7g8CADIQAwBPEAEA7RAAAGIRAQCZEgEAuhIBAPESAQBTEwEAihMBAJkTAQCgEwAA1hMBAOa/IADKyeMgAAABnh+DdPKArhO6pXk8ZHNWmK0svxekY7kc2QWFI6bk+D1fFTOQvm7efM0LSAQSPgPADYCoADowAAABAAAAAAAcABAAAAACAAMAfACKAIsAigUyB8YHxgg4CbsL6gu6DIUQhhDxEPIQDBINEl8SYBKQEpESxBLFEiATIRNaE1sTAQABFReXZ8QOc/V/BAAAAQDNCgEAjAATgAKwsBNpAAFrcxOAAqBAAAAQwG50AQUCoCAAABFSoDAAABIXYmgBUw8AACOAArDQE50AAW91I4ACY9XWwx/wZW8BAQKgIAAAEVKgMAAAEhdiaAFTDwAAg4CEX2g/czZGsOAT4wABsEAP2AABAQgJAQsA89kTAAAxAAEAAANrZXkAAPImBwAAAQAA8oABAWN48gABAGEAAe8SAAABAAA7MAAAAQAABQACADIMAwCGEgEAAQQA9GUQAAABAAEAAQDzgAJpdAAEZ3JhbQAAAdULAAAA8gF0AQBvAPKjEAAAdAFSgAECQWTjurI8c2RuAQMBsAAMngMfc/LeCwAAAQABAAFQWWymAVVtOzAAAAEAABAABQB+DAAA6RAAAFYSBQAYEwIArxMFAAECAPTdCwAAhgN6AQEAIwADoEAAAAyaGza+0BOAZXJz9JgEAAABAO4ACgEBgAOgQAAAE0OgYAAAFCOwMBJlAAFhZXMBAQGgAAAAE8NzAW0RAAABAgGgQAAAE8Zl8uYTAAABAAEAAqFAAAAT5aBAAAAQd2NlOjAAAAEAAAAAAAcAEAAAANQI1QgWEjQSohKjEugT6RMRgQLEsBAS1gABYWUAAmV5AADy0wsAAAEAAPIDZGVyAQBr8sYGAAABAPSAEAAA5wABACQC9h8AAMHJsFAQmwABQAAAAAADjikA80ISAAABAKgAAQIA8/cQAAABAAEAAQQA9W8QAAABAAEAAQCDAgEFAPSMEAAAAQCpAAEAAQACsCAMnAABsAAMHAABZXPyNQcAAAEAI4ACoDAAAAjIaGsACXViY29tbWFuZAAAARcRAAAAABFlbmRpbmdyZXRyYW5zbWl0cwAAAawTAAAAABVjb25uZWN0aW9uaWRnZW5lcmF0b3IAAAHlEwAAABKAAgDAAAAABYdkcvIAAQBlOzAAAAEAABUAAgDuAQAAPwMUAAAIKQAAAR4JAAAA8oAEADBocHMBAQKwQBQMAAGwMBKGAAFnaPUwEQAAAQA+AgEAawABAgD1JxEAAAEAAQABAAEAAAJwZQAAAXwQAAAAAAZrZW5rZXkAAPKCEgAAAQAA8oACAG95AAxnbmF0dXJldmFsaWQAAAGgEwAAAPIAAQBpAPTYEgAACAAbABsAAApvdGNvbXBsZXRlAADyJBMAAAEAAAACbmUAAPR6EAAAAQCaAAEAAAAEZWFkeQAAAa8SAAAA8gJ0YQEAcjswAAABAAAHAAIAeBABAA8RBQAA8oACAGFvAAhvbmZpcm1lZAAA8q4SAABZAADwAAUBAAEFBgjeY2Ruc3RtAdILAAA7MAAAAQAABwACADoRAwAbEgMA8oNoYWsBAWVpCBIAAAEOEQAAAAAMZXR3b3JrY2hhbmdlAADybxMAAAEAAAAEdmVudAAA8jcQAAABAAAAC2xpZW50c3RyZWFtAAABQBMAAADyAWWEgBQgBAHFBgAA9dQPAAABADEBIQEOAfKAAgBsc/KAAQFkZzZpAIDCyaAQAAAIyEwy4vEwsqBwAAAStUbQLCz286AgAAAG9wAACAGCA4MM9sACAISKoCAAAAc3DFLPC2JpoFAAABEGTmE1d9cxNvsseLOLWiL9L80rV5Z3pBUZEREAhuQLADswAAABAAA7ABgA7gEAAPUBAgA/AxQARQQBAKsEAADCBgIA4ggAAEEJAQDZCwAAGgwBAB4MAACDDAMAMQ4AAEcPAQB6DwAA/g8AACQQAABLEAAA8xABADgRAQB1EQEAchMAALcTAQDLEwEAAQYA9FETAAABAFMAAQAjgAKhEAAAFANjchOAAqAgAAAI2mh5AYECsDASdQABsAAH1QABY24BgAKgcAAAErWgcAAAExZkbhOAAgneIPAjomFwAYCEVe06Mn9msJARaQABbUse0pO9oEAAABDsAQEJAYACoBAAAAk9sEAQTwABaXMBggKwIAyhAAGgoAAAEvpmbgGABKAAAAAI+qCgAAASsKAgAAAQf6BAAAAP+WdvcHYBAQD0TBEAAAEAAQABAPOAAmRsAAFkAAD1GQkAAAEAAQABAAEAAPIBcAEAaQDzmAwAAHQHAQAAB2VnbWVudHMAAAE2EAAAAPKAAgBzdQAJc2VxdWljYml0AADyBRIAAAEAABKAAQGggAAAE6xhdDKAAgId4Htr1e2wcBIHAAFlb2FpCG8AAPQVCQAAAQABAAEAABKAAQExgzaNN2x0aRKAAQKgwAAAExagQAAAEu90aXIBAwD0Wg8AAAEAxwMBAAAZbW90ZW5hdHRyYXZlcnNhbGFkZHJlc3NlcwAA8h4UAAABAAAABWFpbGVkAAABYREAAAAShG5kb20BAbBAE4MAAWZi8oACAGFlABBheHVkcHBheWxvYWRzaXplAADyuxMAAAEAAAANcG1hbnlzZWdtZW50cwAAAZUTAAAAABdhbG5hdHRyYXZlcnNhbGFkZHJlc3NlcwAA8hwUAAABAAD6rAIAY28AD250b21hbnlzZWdtZW50cwAAAZQTAAAA9gAAAAQBsKATgQABaWxtcmPynAEAAAEAAAEBAPedEAAAAQABAMMCAQCCAAUAAA1lbGZzaWduZWRjZXJ0AAAB1BMAAAAAAmlkAAD0MhAAAAEAAQABAADygAIAY3PygAEBZW8ig2VyYQIA0AAHJAABbHQ2AgAAgoWgAAAABYmwQBQMAAGwMBKGAAFb8SbdzZcAIATIAAMAOzAAAAEAABYACgCaBAAA8AgBAIwQAQDuEAIAMBEBADYRAQAmEwMAbxMBANsTAAAEFAMA9K4wAIOPABIVHC/MguS3jx8gIaAgAAAFiSKhEAAAE2QkbztLdzA1Bsa3dQD+oDAAAAweHCYvUQ2CJYAQQAHvuQ4AOzAAAAEAAKsAMQCmAQEA8gEBAPgBAQBIBAMAWwQAAIYEAACgBQAAmAYDANAGAAAzBwEARgcAANYIAQDjCAAA7QgCAPoIAAAfCQAAwwsBAC0MBAA3DD4AiAwAAKEMAQBfDwEAcw8BAN4PBQABEAEADxAFAEYQAQBhEAEAbRABAIgQAQCmEAEA5xABACcRBABxEQIAfBEAACsSAQA+EgMAbhIEAJ4SAQCkEgEAsxIBADYTAQBJEwEAZxMBAGsTAQCOEwAAtRMBALkTAQDYEwAAAYMCoJAAABOboJAAABOcMjMBgAKgUAAAE8OggAAAEuxjZfMAAnN08hIHAAABAAGAA6CAAAAT9bAQBtEAAaCAAAAT1GFlaQEBAXTUoxt7NGTy5wYAAAEAAAN1cmUAAAGGBQAAOzAAAAEAABAABADlCwAAkgwAAI8PDQDsEAAA8oABAXRzAQYBDqgEjeC8Y/XbDwAAAQC8AAEAAQAACG5jaGVja2VkAADy1hIAAAEAAPIBNAEAdfITCQAAAQAAIoACAPMgCREAATM2AAJ0cgAA8yMHAADsAQEAAAAFZW1vdGUAAAGHDwAAAAgoAAABDgkAAAAAAm52AADyDAkAAAEA8sIQAAABAAACdHMAAPMDDAAAAQABAAASgAIA9mAJCgABaXUAAnV0AAA7MAAAAQAABQABADoTBQA7MAAAAQAABQABAO8TBQDyCXJnbWF0Y2hlcwEAbTswAAABAAAGAAIA6RAAAFYSBQA7MAAAAQAABQABAK8TBQD2BwABbYeDoDAAABNQoHAAABF0sDAROAABEwQLIIEAOzAAAAEAACwBAgDtAQAAEwIrATswAAABAAD5AAIARwcAAMoJ+AAIKAAA9NALAAABAGUDAQAAAAAAAPQGCQAAAQABAAEAAPKAAgB4eQAEdGF0cwAA8pAMAAABAAAIRwAA9AIJAAABAAEAAQAAAAxuY29kaW5nZXJyb3IAAPIiEwAAAQAA/gAAHwQBoBAAABDtZXJzdGnySwUAAAEA8pYMAAABABKAAQE5FQvFyORtZ/KAAgFhb2UABnJ0dXBsZQAAAc8LAAAAAQUAOzAAAAEAAAoAAwDPEgQApxMEABIUAAAABHR0ZXIAAAHOCwAAABKAAQKgEAAAC9awcBPjAAFhZXYACG5uZWN0aW9uAAD0ChEAAAEAAQABAAAACWtleXVwZGF0ZQAA8lQSAAABAADygAIAZW+ygAICsDATywABY21wdfIAAgBydQDyrgwAAEMDAQEA9H0FAABQAzQDAQAAAAAAOzAAAAEAAOkAAQCyAOkAAPKAAQF0dQAMY29udHJvbGVycm9yAADyrBIAAAEAABKAAQGwEBPrAAF3YRKAAQGwIA9HAAFvdQEBAbBgEWkAAWYBUBMAAAEAAaCwAAATNGLzwRIAACIBAQAjgAKwAA95A0dkcgEAA6AgAAAQ3KBAAAASJDA/N1e4smRlc/N+DwAAMwMBABOCAqAgAAATjWVpABlkbGVuZ3RoY29ubmVjdGlvbmlkcGFyc2VyAAABAxQAAADyAAEAZQDy2A8AAAEAAANyb3IAAAFjDwAAAAAEYXJseQAAAXcQAAAA8gABAGXy1AgAAAEAARYSAADygAIAZHLyAnNoAQBl88IGAAABAAEAAAAIaXplZXJyb3IAAPItEgAAAQAAAAJ6ZQAAAQEJAAAA8oFsAgBpc/KAAgBhaXYFAICChrDgE20AATKJ8JsEe3oT7nOO6gAgIFwIAQEAADowAAABAAAAAAAKABAAAABrD2wPVBBVEN8QaRFqEX4SfxIIFAkU8wIBefJzEwAAAQAjgAKgUAAAEglldAAKYW5kb21ieXRlcwAA8oMTAAABAAAACnVycmVudHRpbWUAAPKBEwAAAQAA8oRvZ2V0AgBjcvICZWQBAHQA82ERAADkAAEAEoABAbBAEMYAAWxyMoABAhvwC08MRGljdcY7KADKyOMgAAAAsONAAAAAscAAAAAAB7BQD2cAAaCwAAAStqAAAAAB5UZhVFXEPwPYVx2IYaDwAAAT22RAngSADwBDADowAAABAAAAAAAMABAAAAB7AEcEfQSHBEgFSQU3BzgHCgkLCTYJtwy/EAGAAqEgAAAUDrBgEW8AAW5zAYICoCAAABCPsFASVAABZHUBBAD0MhMAAAEASwABABOAArCQFAwAAWVoAQIBoEAAABD+ZQFPDwAAAQcA9DIRAAABAAEAAQABBwDzLxMAAGgAAQAAEXNpb25ub3RuZWdvdGlhdGVkAAABgBMAAADygAEBbnLyAAEBZTEAOjAAAAEAAAAAAAkAEAAAAEAEQQQgB9kIHwwgDCEMIgzUENUQAARycm9yAADyxxMAAAEAAPINZXlpbmdtYXRlcmlhbAEAZfWPEwAAAQABAAEAAQAAGuUBAbAAEJsAAWtzAAR5cGVzAAD0LhAAAAEAAQABAAASgmN0AQENBCWMSKN0ZfKAAgBlb/YAAAACA3nr5SCwjHB0YWNoAPNNBQAAAQC7DAGAAqAQAAAToLAQEI4CE2l1E4ACoFAAABFWbHIABHJlYW0AAAEtEAAAAPIAAQB0APWeDAAAHwPXAgEAtgD6agEAc/OUBAAAAQABADswAAABAAAVAAcA3gsBAHUPAQA3EAEAdBABAF0RAQAOEwcA+xMDADKAAQII5cvPhiBlYW8BAgKgcAAAEXcWjWdZDMFhcgFMBwAAEwACePg1Xo9zcnXyAAIAAAEAAYACZBCW5iMPoLAAABPbYWUBgAKwUBKAAAFlX37ONStlaQEFAPMYEwAAAQABABOAhLAwD9IAAaCgAAATNKAwAAATcRBASAGIArAAE0wAAbAAE04AATQ2E4ACoDAAABEBZHMAA29kZQAAOzAAAAEAAAYAAQAvDwYA8scSAAABAPIBcgEAYzswAAABAAAMAAIAiAQLAEoFAAA6MAAAAQAAAAAASQAQAAAA3QjeCN0L4gvjC+QLmgymDKcMqAy4DLkMVQ9aD1sPXQ9eD2MP6w/sD+0P8g/zD/cP+A8XEIoQkBDdEN4Q6hDrEP4Q/xACEQMRBBFbEVwRbhFvEXARdBEkEi0SLhIyEjMSeBJ5EogSiRKMEo0SrBKtErwSvRK+Er8SwhLDEiITIxM0E58TxBPFE8YTxxPIE80TzhPPE/IAAQFvZQF/AAAAAFZwAADBzx662JDmLjf3a0mN0mxTDQKrQT0h51wkKxXYIIRN8rAgC9MAAR0/LTvY9g8w2JTjnw5ND3ELK3qVE/p16QQvKO6eAgAAgADATR1rADowAAABAAAAAAA0ABAAAACYBJkEhwWZBZoFmwWRBsYGxwbyBvMG9Ab1BvYGyAfOC+cL9AsJDA0MDgwTDBQMFQx/DIwMjQw4DzkPQA+gD6EPxQ9MEIAQjxCgEKEQ3BBnEWgRkhKTEqoSuRLBEowTvRO+E+MT5BP2EwMUAAVhbGVudAAAOzAAAAEAAPcAAgCODAEAOQ71AAASgAEBsDASBQABdmMSgAEBOAWC/WxfaWXyAAEAdTswAAABAABVAAEACwBVAAHcCwAAAQAA9AwJAAABALUHAQABgAKgAAAAC+kszKUQgBZtbgEEAaAwAAARZHQBFxMAAAECAPa1DAAAAQAZAwEAJQEBAPOAAm9yI4ADoPAAABOAsCASZQABaXR1AQIBsFASZQABZfTiCAAAOAMBAOMDE4ACoBAAAAx4YW8BgAKgYAAAEzCwcBMkAAFhYyMAA7BQEIgAAaCwAAAStWN0dfQvBwAAAQABAAQCAQoA80ISAAABAKgAAQEA9+4IAAABABIHAQBrAAEADgEjgAKwYBNVAAFpbwECAPR2DAAAAQALBgEAAQEBoQAAABPVZPKKBQAAPAIBBQD1yA8AAAEAAQBJAgEAAQUBoAAAABBcc/WpDAAAAQABABsDAQABAgGgoAAAE6xy9HMPAAABAO0AAQABAAGgIAAAEP9y8+cLAABZA2ABI4ACoAAAABOJZHIBAwKwQBKMAAGgMAAAEuJld/IOEAAAtwATgwKgcAAAExtnbwECAPVsEgAAAQDsAHkAAQATgAKwMBKdAT9pcAEGAPMYEwAAAQABAPMFAmJzAcUPAAABAwD0og8AAAEAAQABAAAFcHBpbmcAAPIrEgAAAQAACCQAAAF2EAAAAPrRAgBhbwhAAAGgAAAAE0tzAXUQAAAAAAVvbmZpZwAA8ucQAAABAADyBG9pbnQDAGNlc/XRCAAAAQABACwAAQABERIAAPZVAACBirBwEMYAAbFQFA8AAaCQAAASEACAADtBGgA6MAAAAQAAAAAABwAQAAAA/AFkBGUEZgS0CycMwA+WEAEAAQ6oBI3gvGP12w8AAAEAvAABAAEAAYACgBMYABABsFAQmwABc3jzAAFlAasEAAAIGgAA8vcPAAABAAAIEAAAAcYTAAAA8gtjbGllbnRoZWxsbwEAZQDy5hMAAAEA8oACAGRy8gN5cHQBAGX0sQcAAAEAAQABAAABAgD0axMAAAEASQABABOAArBwEmMAAWZuAAAAADswAAABAAAMAAEAtQYMAAAAA2JsZQAAAc0LAAAAMoACAbBwEyIAAWFlafKAAQFkbrYBAIACA6AAAAAMKm9yZWh5AQEA9R4UAAABAAEAAQABAPMGAWXyABQAAAEAE4kCsEAT+QABYXIAEmVnbWVudGF0aW9ub2ZmbG9hZAAA8usTAAABAAASgmxlAQGwABNDAOBzZPKAAQFidPZ8DQDDzR+sUklzfF7VC1lgcrCQEW8AASZXrOOO2UADAACAFLcPADswAAABAAAZAAsAogEDAEIEAgBvBQAASgcBAE0HAwDVBwEA3wgBABYMAQB9EAEAyBABAHMSAQABAAMW4vvnH26gMAAACOZ65zSlfNBibXP0XwQAAAEAAQDFBwGAArBwEikAAbAwD/IAAWRy8wMBZTswAAABAAAFAAEAbgcFAAEEAaBQAAATQHMBTxIAAAGAAlMKy4lFlLAQEtYAAWFlAQABoJAAABL1c/QyEAAAAQABAAEAE4ACoDAAAA95ZGYBAwGwABBpAAFzAdoPAAAAAAAA8+oBAAABAAEAAAAAAADz5wEAAAEAAQAANgAAAAIFoFAAABEHDKk6e+GcbYzC7RMTYUoXNIM+oDAAABAkMDFjZWl0dwDybAcAAAEAAAhvZGVwb2ludAAA8/QPAAABAAEAAPIAAQBj9KwAAAABAAEAAQD0dwUAAAEAJAcBALY3AADCyEUHGXhDATZQ69ybrgAACAIAZJMIAAGMAAAAAQEAOzAAAAEAAAoABADfBgYAWQ8AACMQAACKEgEAI4ACsHASBQABZW8BAAGgUAAAEndjAX0MAADzAAFs8mIEAAABAAGAA6DwAAATNbBQD9QAAaCQAAARBmNocAALYXRhYWNjZXB0ZWQAAPQDEwAAAQABAAEAAAAFcnlwdG8AAPQpEAAAAQABAAEAAPICbHkCAGNkAAF3EAAAFhYAgIGJsHARawABbn5y5/DrGmbNB3V6oQAAABPbdMw+Y/EVoHAAABCgAAABLhwGAADH0hvzvHQaCyUPvt3MqxuCME/eoweyA4AV+jXYV/9iyURwXizuXUVSaC3p+j8kNITleiMNmuAlXlR4sYUZxGNVWPQSQUNuFYsSal1bG40VOyiSIYlys38FoUuoMyMbKUA/+hnQNZ1Wm1tix9pHHCpVKugETgFih8c5rwbCXYNZvW5MMZPt3QA7v5ATEAjZpcPoUSJwAMF8G0AByCiA/jdXADswAAABAAD/AYsABAACAPQBAAD9AQEACAIAAGkEAQBtBAIAdQQFAIEEAQCaBAAApgQAAKgEAAC7BIsASwUBAE8FAQBTBQEAXwUAAG0FAQBxBQQAhgUAAKcGDAC1BgwAxQYAAMgGAQDnBgQA+AYIAAgHAAA6BwEAWAcTAMkHAAC+CAEA2ggCAOQIAQDnCAAA8AgBAPcIAgABCQAAFQkDACoJAgA0CQAAPgkAALULAwDACwIAygsAAM0LAADPCwMA5QsAACoMAAB8DAAAgAwCAIcMAACSDAAAlgwBAKwMAACwDAIAtQwBAC8PCABGDwAASQ8CAFEPAQBUDwAAVw8AAFwPAABvDwAAcQ8BAH4PAACHDwAAjw8NAKcPAQDDDwEAyw8GANQPAQD5DwAAABAAAAUQAQAYEAAAHxADACUQAABYEAMAXRABAHoQAgCLEAMAkxACAKIQAACtEAoA7BAAAO4QAgD1EAEABREBABURAQAwEQEANhEBADoRAwBXEQMAbREAAAQSAAAOEgAAEBIAABsSAwAgEgMAJxIAADsSAgBCEgEARxIBAFASBQBlEgEAahIDAIQSAQCYEgAAnRIAAKESAACmEgEAsRIBAMcSAQDPEgYA6xIAAPMSAQD4EgIACBMBACQTBQAtEwEAMRMAADUTAABBEwEARhMCAFATAABVEwEAWBMBAG8TAQB5EwEAgRMBAJsTAQCnEwQAuxMBANITAQDbEwEA9RMAAPcTAQAEFAMAEhQAAAEDAYAS6QAQ8HM7MAAAAQAABQABAMkSBQAjggKgkAAAE8NpcwGBArBQEvgAAbEAE9AAAW10AQUA8+4QAAABAAEAI4ACsNASpgABYWUBBADzgBAAAOcAAQABBwGAEtgAgBtz9DoRAAABAAEAAQDzgAJhZQEHAPedEAAAAQABAMMCAQCCAAUAE4ACoDAAAAyYZXIBBwD1zxIAAAEAAQABAAEAAYACoEAAABDfsFAQxgABY2kzgAOgUAAAEO1hb3IBAAD0kBIAAAEAjwABABOAA6BgAAARPqBwAAASEGlvcgADaXN0AAABoxAAAADyBWF0aW9uAQBo8tAIAAAuAAFVEQAAUoABArCgE3MAAXJjbgGBAUbQLCz282sBgAKwcBKEAAFs6EAzEgdyeAECAaAAAAAQXHM7MAAAAQAACwADAKkMAgDGDwEA5A8GABOAAqAgAAATZGV1CD8AAbBwE70AAXA7MAAAAQAABwABAKkHBwDyTxAAAAEAkoACAucAAAAGtLAgEGQApWNpYXJWAQAAgYcZThJV5HeggAAAFBI8KeilSjigQAAAD32wgBCUAAEAAASyQUAA9MkIAACeBgEAmAMITQAAOzAAAAEAAAcAAgDmAQAADAIGAAAABXRyZWFtAAABKBAAAAAIUAAAAagHAADyOg8AAAEA8oJpbgIAZXMyAAICU4tfxOHtejl6SV5gYW9ldgA6MAAAAQAAAAAAEgAQAAAABAIFAogFrQw8Dz0PVg9wDwMQBBCrEKwQNRI2EjcSOBIvE5cTmBMBAQDz6ggAAAEAAQABAAEX7x+JCZZk9HoQAAABAJoAAQATAAOwMBO3AAGwUBKKAAFlcHL0aQ8AAAEA5wABAAEAA7BAE4MAAaFAAAAT6qBQAAARYWJjZgHeBgAAAANhdHMAAAEJEQAAAAgpAAABohAAAADyhG9hZHMCAGl08gABAGwA88AQAABBAgEA8gABAG4AOzAAAAEAACQACAAUBwcAyA8CABgRDQA/EQIAExIBAJYSAQDnEgEAexMCANYCAIABBbFAFA8AAaDAAAATz3dibW5wdQEEAbAgENgAAWXyNxIAAAEAAQQBsCAQ1gABZfI1EgAAAQDzgQI0NgEFAPT6DwAAAQCpAAEAAANsYXkAAAEiBwAAAAAEcmRlZAAA8ssLAAABAAASgAEBZAuJYiVdYW8ABGJsZWQAAPLOCAAAAQAAcoADAbBQE+0AAWFjcHQBDQD01RMAAAwAHgAPAPIAAAFlOzAAAAEAAAUAAgB9AAEAqAADAAE9CQAAABJpdGFsbHlzaWduZWRzdHJ1Y3QAAAFkEwAAAPYCAACDhHNZ0D8kyLAwEtwAAUAAAwGgBAA6MAAAAQAAAAAABwAQAAAA8RDyEF8SYBLEEsUSWhNbEwEJAPMvEwAAaAABAPEAAqgAYXTzTQUAAAEAuwwBBADz9A8AAAEAAQABAQD1uhAAAAEAAQABAAEAI4ECsJATbQABYXQBAAGgEAAAE4llAZYQAACjgISxEBPmAAGggAAAEzAMQAQBAAA6MAAAAQAAAAAABwAQAAAAywvMC90PMhMzE34TfxOJEwAHaW5hdGlvbgAA84QPAAABAAEAAAAHcmlwdGlvbgAA8oIPAAABAADygAIAY3QBAgD14gsAAAEAAQAzBOgAAQIBsIATUQABdAHjCAAAAQABEHZI/rdAcvImEQAAGAAIQwAA880IAAA0AwEAAPIAAQBt84cEAADBAAEAADKAAQKgMAAAEzRmZHKyAAEDsCAL0wABZWlrcgA6MAAAAQAAAAAADQAQAAAAxgbHBucLQA+AEI8QoBDcEGcRaBG5EowTvRO+EwAQdXBwb3J0ZWR2ZXJzaW9ucwAAAdoTAAAAAAZ1bnRpbWUAAAEEEgAAAAANYXhpZGxldGltZW91dAAA8goUAAABAAAAEGVlcGFsaXZlaW50ZXJ2YWwAAPIYFAAAAQAA8oNhdGgCAGttAQoA82cTAAABAHAAACFuY3VycmVudG11bHRpcGF0aHBhdGhzd2hlbmVuYWJsZWQAAAEjFAAAAPKAAQFvbPIEYXVsdISABIADOzAAAAEAADQAAgAhBwAAdAczAAAIIgAA8vIPAAABAAAAB2luaXRpYWwAAPIpEgAAAQAA8oACAGRy+iEBAGU7MAAAAQAABQABAG4HBQAAAAAAAbBAEogAAWU7MAAAAQAADAABAKcGDAABixAAAAghAAABygsAAAD6lgIAYWXygAIAb3IAAnVnAAABhgQAAAD2ZQEAhYigMAAAEHewQBKIAAGwIBCoAAGwUBM4AAEmAAMZqCQAOzAAAAEAACwACwD9AQEACAIAAGkEAQCnBgwAtQYMAAgHAAC+CAEALw8GAIsQAADHEgEA+hIAAAEEADswAAABAAAGAAIAvAsAALsMBQABgAKwABNMAAGwABNOAAE0NiMCAlmsAziKGXJ283sPAAABAPwD8wIBZfQDEAAAAQCnAAEAAQIA9KIPAAABAAEAAQABAQGgQAAAEJBl9OQIAAABAOUCAwATgAKgUAAAEg9scwEEATSEeeeTnmPycxIAAAEAI4ICsFASRQABZnQAB3JhbnNmZXIAAAGhEAAAAAAIbmJsb2NrZWQAAAGrEgAAAAADYWNlAAAB3BMAAAAIGAAA8tITAAABAADyiW5kYnVmZmVycwIAaXAyAAIBoCAAABKYZXVp9ckLAAA0AAEAAQABAAAACXVmZmVyc2l6ZQAA8vcTAAABAAAShmVjZWl2ZQIAwAAAABIoYmQAA25mbwAAAfEPAAAAAANlbnQAAAF0EAAAABKAAQGwMBKMAAF2cvYAAANyYW0EAaAwAAAS4mVpcnN39coIAAABAAEAMAABAPUJEAAABQC2AAEApAEABmxvY2tlZAAA9GEPAAABAMQAAQD0jhIAAAEAjwABAHYAAACDg2+gwUeEIqBwAAATG6BAAAASr0IABAFAAQELAgAAOzAAAAEAAA8ABgAtBwEAMgkBAHgQAQCDEAEADxEFAAoSAQBSgAECQQB4Kl0xYWVp9gEAgAEFsOATywABdGJjZHN2AAAHEDFK+YGu/lFRE9mNSxoJcIxPOUF4qfLdp2ftYvP61Va6U+790X1Tz4sIwaAwAAAQlyCkHs/wj39mEjaASXMc/D0DO1LdVJbUpxCGcLZ5oCaqc8FHw7DwFAwAAVnFsOH4rVe6dcl9bx/pkqkn5h289USRrjoHIxRsknzk+J7i/g5XG9hefKAAAAASr2FlaW9yc3UwYmNkZmdoa2xtbnB0dnd5ADswAAABAAB/AncACAAAAGEADQB0AAAAiAAAAI0ABQDlAQAA+wEBAAkCAQBcBAIAZAQCAKMEAACsBAgAggUAAIkFAACLBQMAkQUBAKEFAACXBgAA5gYAAPcGAAAcBwMALAcAAD4HBQBFBwAAUgcFAKgHCADHBwAAygcGANcH5gDOCAEA1AgBAOEIAADoCAEAGQkEAD8JAQBDCYYAtAsAALkLAQC+CwEAxwsAAMsLAQDWCwIADAwAABkMAAAjDAIAJwwAADYMAACTDAEAmwwAAK8MAAA6DwEAYQ8BAHkPAACdDwEAwA8AANIPAQDdDwAA7g8CAAcQAQAZEAAAJhABADIQAwBPEAEAaxABAG8QAwCBEAEAlhAAALgQAQDaEAAA7RAAAPcQAgAXEQAAJhEAAD4RAABMEQMAUxEBAGERAgBlEQEAaxEBAHcRBAAWEgAAJRIBACgSAAA0EgAARRIBAGcSAQB6EgMAjhIBAJkSAwCiEgEAqBIBAKsSAACuEgAAsBIAALoSAQDAEgAAxhIAANYSAQDxEgEA9RIAAAMTBAAeEwEAMBMAADITAQA4EwEAQxMAAFETAwB+EwIAhRMGAJkTAQCgEwYArRMAAMATAQDWEwEA6BMBAOsTAQD5EwEAFhQBACMUAAABAAKgYAAAErmwoBJAAAE4ZTswAAABAAAJAAIAtwcHAIASAQABCADz9A8AAAEAAQDzAAFjOzAAAAEAAAcAAwCsAAMAdwUBAJwMAQAAgAEB9kACCQABsDAMgAABbnIAAm9yAAABhQUAAAABCQDz3xMAAAEANQABgAKgQAAAEsawIBOBAAFoaQGBArCAE+0AAaBgAAAUEWRzAQwBoKAAABQjd/ITFAAAAQAAAXUAAPN/DwAAAQABAADygAEBdHU+AwCEAQWgQAAAE8KwYBOWAExtYnBzdHXygAIAcnMIOAAA8l8PAAABAADyAmljAQBj9IMEAAABAAEAwgAAMoACAaBwAAAQ22JyZAEDAPTyDwAAAQAEAAEAAQoBoEAAABPGZfLmEwAAAQATgAKwYBIpAAFjafOAAmRyAAZlc3Npb24AAAEIEQAAAAg4AADyXQ8AAAEAAAADaXplAADy1BIAAAEAAAAHeGNlZWRlZAAA8n4TAAABAADyhXVmZmVyAgBlc/IAAwBiZXM7MAAAAQAADAACAIMFAQCcBgoAOzAAAAEAAAUAAgDWDwEAKRADAPICcHQBAW9lADswAAABAAAJAAIAbgcFALEHAwAABWVyaW9uAAAByAsAAAASgAECoCAAABP2sEAPggABdGJwAAdlc2VuZGVyAAABoBAAAAASgWEBAbAAC74AAXRk8oADAGVpeQGCArAwEwgAAQQvKO6eAmF5AQACD+/Kb+ZssIAStwABc3b0OwkAAAEAKQoBAAAGZXRyaWNzAADygBIAAAEAAAAGYWN0b3J5AADyfhIAAAEA8ggUAAABAPICZXICAGZt8owMAAABAAASgm9sAQGwQBKsAAFsZQADeXBlAAABXA8AAAAqygIAwAAAAAj7c3QSgAIA50AAAAcgbnjygAIAZXIBCQDzLxIAAAEAAQAAB3J2YXRpdmUAAAElEAAAAPKAAQFlaQhtAAABNg4AAAAAA2l0aAAAASQQAAAACDgAAPJfEQAAAQAAEgABAaAgAAAS+nRpAPJjEgAAAQAABmVmdXNlZAAA8qgSAAABAAAAA29zdAAAOzAAAAEAAAYAAQDgEAYAAAAFYXJzZXIAAAGqEgAAAQMUAAAACWltaXRlcnJvcgAA8sQTAAABAAAACGVuZXJhdG9yAADyYhMAAAEA8uUTAAAFAPopAwBnbHDz7g8AAAEAAQA6MAAAAQAAAAAADwAQAAAAYhFjEZkSmhK6ErsS8RLyElMTVBOKE4sTmROaE9YT1xMABWFuZGxlAAABJxIAAAE1EwAACBwAAPJdEQAAAQAACDAAAPJbEQAAAQAA8oACAHJ2AARsb3NlAQDQABIlAAFkOzAAAAEAAAUAAgBXEQMAUhIBAADyAW6HgJQJA/WJDAAAAQABAKwBAQD3hxAAAIMAAQABAAEAkwE5ACKAAgDmcAAADIhubwgCAADz6w8AAAEAAQAAIoACAMAAAAALx2RyAAVsaWVudAAAAQcRAAAA+mSEgBQBEPJsBwAAAQAA8oACAGVzAAVpbmRvdwAA8pYSAAABAAAABHRhdGUAAPJQEgAAAQAACF4BANAAEpQAAXMAOzAAAAEAAAsAAgAOEwcA+xMDAAAQb250cm9sbGVyZmFjdG9yeQAA8ggUAAABAAAACGxnb3JpdGhtAAAB7xIAAAD2AQAFc3Rpb24FAbCAFBYAAWFjZXN3dPQyDgAAAQABAAEAAAACbGcAAAH6CAAAAPKAAgBhZQAQcmFjaW5nc3Vic2NyaWJlcgAAAfYTAAAACAkAAAFPEgAAAPKCcmUCAGN0CCkAAPJaDwAAAQAA8gACAGV18poGAAABADswAAABAAA1ABUAwwsBAKEMAQBfDwEA3g8FAA8QBQCIEAEA5xABAHERAgA+EgMAbhIEAJ4SAQCkEgEAsxIBADYTAQBJEwEAZxMBAGsTAQCOEwAAtRMBALkTAQDYEwAAAA5lbnRpYWxpdHlsaW1pdAAA8m0TAAABAAAygWkCAbAwEq4AWWRncvYBAIAFARjuxTGdDmZnbnN0YwECA6BAAAAQfy/MguS3j6BQAAAQ7GFiZvfWCAAAAQBIACcHAQBfAAEAAQQA8zsSAAABAAEAAAAAADswAAABAAATAAEAWAcTAAAIQAAAAYcMAAAA8oACAGFl8oABAXJ0EoABAbAwEyQAAWFsAARub3B0AAABxgsAAAAAAAABoIAAABQSZgHJCAAAAQATAAAAB3J1cGRhdGUAAPXPEgAAAQABAAEAAQAAAAVjdG9yeQAAAd8QAAAA8oACAGFv8gJuZAIAZnM7MAAAAQAABgACAB8HAABSBwUA8q8MAABoBPKAAgBhb/KAAgFtcGkAAm5nAAGwQBMiAAFl8pgGAAABAAAgAAACsEASiAABn2VwAQgCAAA7MAAAAQAAIwAFAKcGDAC1BgwALw8GAIsQAADHEgEAMoACAbAwC8oAA2VpYfYEAIADBLBQEwEAAbAQE6MAAWRtbmxvcnYIXgAAOzAAAAEAABMAAQCUABMAOzAAAAEAABMAAQCpDxMAAAVlYXNvbgAA8lkPAADKAAAICgAA8t0QAAABAAD6TwEAZQExDgAAAAhhAAA7MAAAAQAABgABAOQPBgAACAEAAPSDDAAAAQABAAEAAPIAAgBwc/KCBQAAFQE6MAAAAQAAAAAACgAQAAAA4QgMDBkMgRCCECUSJhJ6EnsSfBJ9EvIBZQMAZHBy9YIEAADBAAEAAQABADswAAABAAAKAAQARg8AAFcRAwAgEgMAUhIBAAAHb3VuaW5pdAAAOzAAAAEAAIcAAgBzEAAAfRGGAAAAA250bwAAOzAAAAEAAIYAAQCqDYYAAAACb3gAADswAAABAAAHAAEA9QsHAAD6AwMAYml0OzAAAAEAAIcAAgCBBAAAvASGAADygAIAbnMBBAGgQAAAE8Zl8uYTAAABAAh8AAABWA8AAAASgAEBoDAAABNAYXLyAAEAdAFRBwAAAAhBAAA7MAAAAQAABQABAN4PBQD2PhIAAAEAKAEBAFEAHwAAA2RkcgAAAVYPAAAA8gJudAMBYWNzaDswAAABAAAHAAIAfwUCAJIGBAD0CgwAAAEA/ARIAfIAAQBlAXwAAAAA8oACAGlvAYECsDAPpwABoJAAABP2cnMABHVpdGUAAaAAAAAStnMBVw8AAPMtEwAAAQADAPo4AQBzAZEGAAAA8gABAGgA8wEHAAABAAEAAAh4aGF1c3RlZAAA9G8QAAABAAEAAQAB9RIAABKAAQEZThJV5HdlYgAGZmV0aW1lAAD0HxAAAAEAAQABAAAIKQAA9E0HAAABAAEAAQAA8oACAGVpCDAAAPOdEAAAAQABAAB2AAAAAwKw8BPLAAGwcBO9AAFnbHNhcAA7MAAAAQAAFAAFAKkHBwDKBwQAkwwBADIQAwBPEAEAtgAAgAIDoDAAAA95sMATpQABZHBmbnQBAgFV6fatTpBh8igMAAABAAAAAADzfgUAABEBAQD0vw8AAKoAAQANAvICbmsBAHPyfwQAAAEA800PAAABAIwAAQAA9TQSAABuAAEARQEBAAAIbmRpbnNlcnQAADswAAABAAAFAAEASRIFAAASgWsBAbAQEtYAAWFlNgAAAAEEQ4NIEVEZNZx5+EU+sFAQmwABY2Rtc3gA80ISAAABAKgAAQICsEAUDAABsDAShgABZ2j1MBEAAAEAPgIBAGsAAARoYTIwAAAByAgAAACSgAEDoBAAAAjITse2sw5WYzJsbvYAAAADAqAwAAAMgmFldW9zAPOrBAAAygwBAAEDAPQyEwAAAQBLAAEAAYACoHAAABNQoDAAABCTZnQIFgAAAZ8TAAAA8g1ldm9jYXRpb25saXN0AQBlAPLJEwAAAQAIAQAAASQSAAAACAsAAAHcEAAAADIGZmljYXRlAgEwPzdXuLJkZXMBfg8AAPKxEgAAAQB6QwICT/GO2lLMaXJzdgD0dRIAAAEAdgDoADYAAACBhhM6Kq8mGbCAElQAAbCwE+gAAaAgAAAMlXERBbVfxgAAARCdAPMEAAAAAQABADswAAABAAASAAgATwUBAF8FAAD4BggAKgwAALEMAQDNDwEAnRIAANwTAAABAgDzQhIAAAEAqAAAAnNlAAABuwQAAAA2AACAAQRsi1Zd2XiwMAvLAAEFMLTBM0d1Y2xydPY/BgDKyLAgEgUAATG0XPbs/w1r+owq8WK7D+YUyk2e3LmorT4cvvj+bUBklhSAAQlLAPd2AAAAFgBzAYQCAQABAMIAAQABsFALwwABY/R3AAAAAQABABoAAQYBIxvEYIcNc/KvDAAAaAQIcQAAAccIAAAAEgABAaAgAAASDm1pOzAAAAEAAAYAAgB+BAAAtQQFAPY3CQAABwYBADIEEgABAPoWAQBzAAGtEAAAEoABAbAwEpIAAXRwAA5yYW5zcG9ydGNvbmZpZwAAAY4TAAAAEgJsZAEBoGAAABM0dGU7MAAAAQAACAABAKwECAAAAYEBHpPlnpTZcggDAAABfQUAAADyAAEBbWYBewAAAPU3BwAAAQDSAQEAtAcygAMB5rAAAAIHoAAAAASGZmlsZwECAQSaJUFk0HPy0g8AAAEAAAAAAAF6AAAAOzAAAAEAAAcAAQD1CwcACAAAADswAAABAADnAAIAxQsAAMMM5gAA8gNyb3cBAG07MAAAAQAA5wACAHwFAACoBeYAAOKAAwHmwAAAAgZvcnh1AAltdG9rZW5sb2cAAPNtEAAAAQAOAQABAgD0kBIAAAEAjwABAAEDAPTEEgAAAQCVAAEA8gNrZWQAAmJ18xwHAAABAAEAOjAAAAEAAAAAABAAEAAAAGEPYg8HEAgQJhAnELgQuRBlEWYRjhKPEpsSnBKrEh4THxPygAIAY28ACGRldGVjdGVkAADyOBMAAAEAAAAHb29sZG93bgAA8gETAAABAADyhmNraG9sZQIAY3MygAICU/+o6CUqoIAAABNyYW9leQEDAWmwNrDeZnQ7MAAAAQAACAACAL0LAADrCwcAEwACgAwDABABcnPyBRIAAAEAAQECsOAT1QAqsGAT7QABcnM6MAAAAQAAAAAABwAQAAAA8RDyEF8SYBLEEsUSWhNbEwEAAbBQD18AAWP0gwQAAAEAAQDCAAAFb2NrZXQAAAF9DwAAABpIAQGgcAAAEg9zYQAB5gYAAHYBAAABBKBgAAAUEW5jZHN08gIAAAABAPSKBQAAPAIAASQDAQAA9AwTAAABAA8AAQABBADz9xAAAAEAAQAABXBvcnRzAADymxAAAAEAABKAAQGAExgAEAF4c/ICY2gBAGUBqwQAAABWAQCAAQSwQAyWAAGgAAAAE/ZuZmhycwgjAADywwsAAAEAAPIBcgEAY/R3AAAAAQABABoAAAEBAPS5CwAAAQCxBQEAEwMDsDATtwABsFASigABZXBy9GkPAAABAOcAAQAABmNoc2l6ZQAAAYIMAAAAAA9tYXhmcmFnbWVudHNpemUAAPKmEgAAAQAAAANrdXAAAAF7BQAA9DkSAAABAKQAAQB2AQCAAwKgoAAAE3JjZHRibvZfAACIhKAQAAARUiD7qzP8LxNJSASAAwDyUBEAAAEAAAZpbGFibGUAAbBgE8sAAWbzwAsAAAEAAQD08xIAAAEAhQABABKAAQF3v3A/FXthZQECAPVvEAAAAQABAAEAgwIjgAKgAAAABLtldAAIb3J1cGRhdGUAAAESFAAAAPIKdWJjb21tYW5kcwEAZgEAEwAAAAgBAAD1pxMAAAEAAQABAAEAAPIDcmdzAQBm9RoQAAABAAEAAQABAADyhG1lbnQCAGFzUoABAigLV6Ev6GdscwGAAqFQAAAT5aAAAAAFh2Ry8wABZTswAAABAAAVAAIA7gEAAD8DFAAABHJpdGUAAPKADAAAAQAAAAhkcHNvY2tldAAAAdsQAAAAAARpbWVyAAABfwwAAAAAA2VhZAAA8r4LAAABAADygm5jhIAAAB0BAgA7MAAAAQAABgACALwLAAC7DAUAAAVtYmxlcgAAAfQLAAAAMoABAqCAAAARF2VpdQAEc3RscwAAAfMLAAAAIoACAOZgAAAEfWV15gQAgAQG5kAAAAHloDAAAAkesHASBQABFW0vhGM+oRAAABOsoIAAAA/+ZnJzeTBlaG9wdAEAAWmwNrDeZnQ7MAAAAQAACAACAL0LAADrCwcAAQEDoHAAABIOoHAAABCKsAASqgFZYmly8lMFAAABAAEBAbDQEzIAAWLzogUAAAEAbgEjgAKgMAAAEVZhbgEAAqAgAAASr6AAAAAG92FkOzAAAAEAABMAAQBYBxMAAAJheQAAAaoEAAAAAQgA9acTAAABAAEAAQABAPIAAAFmAeQBAAD1GhAAAAEAAQABAAEAAAZhdGNoZXMAAVBZbKYBVW0BfgwAADswAAABAAAMAAMA6RAAAFYSBQCvEwUAMoACAbAACPAAAW1zZQAHYWtlcmVzdAAA9PwSAAABAAEAAQAA8gZpdHJhcnkBAHQ7MAAAAQAACAACAL0LAADrCwcAANasAICEiMAAAAAAdjB6OPlSGLAgC8sAAST9EMGEQhsrETukl0YAARkJRgEIAPMYEgAAAQABAAGHAixNXXBXSXVH5wEu92htCCoAAPJ4EgAAAQAAAAAAAPR6EgAAAQABAAEAAPpeAQBk9CASAAABAAEAAQAAAApiYW5kb25wYXRoAADytxMAAAEAAHKHaWNhdGlvbgMBsHATZQABYWNlcPKAAQFscvKAAQFwdQEAAaAAAAARVnPzKQcAAMQLAQABAQKwQBQMAAGwMBKGAAFnaPaaBAAAlgwBAD4CAQBrAAAAAAKggAAAEkSwcBOUAAFhcwF1AAAAOzAAAAEAAAcAAQAiCQcAVgEAgAEEU29XcSOIJur/HswreWRnc3QBBQA7MAAAAQAADQABABgRDQABgAIL2Jv15A2wUBLwAIdlcAECAbBAEi0AAWUBVA8AABOAAmlytc83dWl0AQEA84wPAAABAAEAAQMBoEAAABOJZfL5EwAAAQDzgAJvdAECAaBwAAASsGUBmwwAAAEDADswAAABAAAIAAEAORAIAAGAA6BAAAAQi7DAE70AAaBgAAARVWVzdQGAArCQEy0AAV2ThnyHkmltAQMBMjtJrQqkZfU8DwAAAQDyA2gAAQAAAAAAAakEAAABfxAAABq2AQELW+Jab2lzcAGAArBQEIEAAaCwAAATZGNzAQMA9IwQAAABAKkAAQAAA3BpbgAA8sEMAAABAAASgXcBAaAQAAATMHNl0gABA6BQAAASd29jZXkAAX0MAAABAQA7MAAAAQAA9wACAI4MAQA5DvUAAQIA9OoQAAABANwCAQAACHJlY2VpdmVkAADyaxAAAAEAAPKAAQF0cjKAAQKgAAAAC9xybnH2Q14Aw9CgUAAAC9ZZ01teE7dwVAlsigw1XpzMPn1A2MPtUR2xQBQcAAGgQAAAD/kABCIAQHudHQA6MAAAAQAAAAAAIQAQAAAAYgRjBFEFUgUkByUHKAcgCSEJpg//D2UQZhBnEGgQKRIqEmESYhLaEtsSChMLE48TkBORE5ITkxOuE90T3hMCFBgUGRQAAjU2AAABegUAAAAAAjI4AAABeQUAAADygAIAMTIADWRsaW1pdHJlYWNoZWQAAPKiEgAAAQAA8oACAGFzAQIBoEAAABD/ZQHADwAAE4ECsCAPQQABZW0BCAD0OhEAAAEAAQABAAEDAaAAAAAPv3PyTQ8AAAEAI4ACoLAAABNQZWgBAwGgQAAAEJBl8uQIAAABAAEBADswAAABAAAIAAMARBIAALUSAAAcFAYAAARvdmVkAAAB2hAAAAAasAEBIiy18ec+bXAACGlzY292ZXJ5AAABHxIAAAAABGRkZWQAAAHdDwAAAPYAAAJzcwMCVMBtK9HYYWRyZXYAOzAAAAEAABYACAB7DwEAkRABANYQAwBFEQYAKhMCAHgTAADQEwEAABQBABIAAQF6OXpJXmBldvIEAgAAAQA6MAAAAQAAAAAAEAAQAAAAiAWtDDwPPQ9WD3APAxAEEKsQrBA1EjYSNxI4Ei8TlxOYEwASYXR0cmF2ZXJzYWxhZGRyZXNzAADy0BMAAAEAAAAHYWRkcmVzcwAA8tgQAAABAAAIAAAA8tYQAAABAADygXYCADQ2EoABAbAQBzMAAXBuAARzaWduAAA7MAAAAQAABgACALwLAAC7DAUAAAAFZHJlc3MAAPJ7DwAAAQAAMoACAbBQEAkAu2RzdPYAAAAEAaAQAAAP3WFpbnJlOzAAAAEAAAYAAgB0AAAAjQAFAPRMEQAAAQABAAEAtiIAAIGLsCAHOAmHemHeoL+5sLASogABsOASpgABa0YdGwB+oIAAABE+oAAAABKvCAAAlxhOADowAAABAAAAAAAMABAAAAD7AVwEXQReBL4LvwsjDCQMJQywEsYS6xPsEwGCArAwD6cAAaCQAAAT9nJzAQABoEAAABIJafKdEgAAPwEBgwKwcBMBAAGwgBM4AAFjcwAAAADyAgIAAAEAOzAAAAEAAAUAAgAcDAEAog8DAAgrAAD0pBIAAAEAkQABAADyCHJlcXVlbmN5AQBj9dsPAAABALwAAQABAAABAISwoBKIAAGwIAvaAAGwABBEAAGwgBJnAAEIBAbz2wYAAAEAAQAAEGljaXRpbmd0aHJlc2hvbGQAAPKlEwAAAQAACBQAAPJ3BQAAAQDynAwAAAEAcoACArAAByYAAWNsdHl2AQAAg4OwQBCoAAF+1WRXHDwwAAKIAAjycgAAAAEAOzAAAAEAAA0ABADTBgcANQcBAPoPAQCkEAEAAANpdGgAAAF6DwAAAAACbmkAAPK7CwAA/wAACHcAAAFVDwAAAAAJYW5kc2hha2VzAAAB+xIAAAAAA3J0dAAAAZcQAAAA8gACADBoAPV3EQAAjAEBAAEAAQDygAIAZHLiA2VwdISA9pAIxgMkEgAY9XYFAAAuAAEAAQABAAB2AQCAAgRe2ezO0LEEBn0ZRKJja2VoaXQBBQGgUAAAE79zAQ8SAAAIOAAA8rkLAAABAPJrEQAAAQASBG5kb24BArAwE7cAAbBQEooAAWVwcgD0aQ8AAAEA5wABAFKAAQI8zQx6J4hhbHMAAMvMM+fsBE0qOdKf80EgEafbvvXwL1zb4lkpQNfx6gRPIVu0+G+yL50+1pQADwZvytaLRCaHC/KvLJvn3tH7S36gfLxNoBAAAAjIb6DBR4QioQAAABPbG3zAfx80I9rdyFoqeRi+FCEUMhGQuDq7oHAAABMbIBAR8UipGCtPbvZJEXQFrf/SfcZBDGQqgAeqDUJYFXIAOzAAAAEAABEACAALAgAALQcBAOYIAAAyCQEAeBABAIMQAQAPEQUAChIBAACAGg7ikAAAAADj4AAAAAHl0AAAAApdnzhvtcl/nvsMJ4YmIItkYdoN6BlcDexpyNHFNSdxIVSPYmpDFOV8Y6kfWXSGKwczrKIk1ZBsQH5WNyY+WwAqtmgmF6pWTqZDlrlDpmIGJ6fUs8YupL0gv80N7+vt4Y0tQDvjXthXXav9p6Zx+skspyI/gP2sbgFDJ1+K05Z3sPuHm4BGnIJcOYugAAAAAAArHOHWmO8GjvnJ3SBxL+tlsjBOAcxO7C9wOhJvt3KgEAAABXpifF7ubsxZF4nv4J+gAAAAAAGgAAAAAAplQlCoU2JUjqxuQDRGY6ThGecoLVthYmNkZWZnaGlrbG1ub3BxcnN0dXZ3eikwMTIzNDU2OD5danh5","N":"ADBd","E":"OjAAAAAAAAA=","H":"3jpX1T07"},"crateNames":{"N":"j","E":"OjAAAAAAAAA=","H":"JFDJy0NX"},"name":{"N":"ADBd","E":"OjAAAAAAAAA=","H":"7bFmUNOn"},"path":{"N":"BAo","E":"OzAAAAEAAAQSugACAAQACQAAAAsAZQByAAEAeQAAAH8ABACKAAEAjQAFAJQAGwCyAOsAoAEIAKoBJADQAQ4A4wEAAOcBBQDzAQEA9wECAPsBAAAAAgUACAI8AkYEAABIBBIAXAQCAGAEAwBlBAoAcQQJAIUEAACXBAIAmwQDAKAEAACiBAAApwQAAKkEAACsBJoASAUBAEwFCABWBQgAYAUCAGYFCABzBQIAdwUEAH8FBQCNBQEAmQUCAJ0FAgCkBe0AkwYEAJkGAACbBgAAngY8ANwGDwDuBgMA8wYDAPgGGAASBwwAJAcBACgHAAAtBwEAMQcAADMHAwA/BwAAQQcCAEYHAABJBwAATAd0AMIHBQDKBwYA0gfzAMgIAADKCAEAzggBANUIAADbCAUA5QgAAOcIAADqCAIA8AgBAPkIBQABCR0AIAkUADcJgQK6CwAAwQsBAMcLAADMCwAA0gsAANYLAwDiCwAA6gsJAPULBwD+CxkAGQwQACsMTQB6DAMAggwAAIQMAQCcDAEAugx3ATQOAgA5DhsBVg8AAGEPAQBnDwMAbQ8BAHAPAQBzDwEAeg8DAH8PQQDCDwIAxw8WAOQPAQDnDwIA8g8BAPcPBAD9DwIABBAEAAsQAAAaEBIALhAeAE4QHgBvEAMAdxAAAHoQAQB9EAEAgBAHAIwQAQCREAEAlxADAJwQAACeEAIAohA4AOEQBQDqEAEA7xAFAPgQBQACEQEABxFQAFkRAABdEQAAYhEBAGURAQBsEQAAdREBAHgRAwB9EaEAIBIAACISAAAlEgEAKBIAACsSAwAwEgoAPBIBAEQSAgBIEhgAYhIXAHsSAgCEEgEAjhIRAKESAgCmEgMAqxIHALUSAwC6EgUAxBICAMoSAADMEgEAzxIbAOwSAgDxEgQA+BIfABoTCwAoEwQAMBMAADYTJQBdEwQAZRMFAG0TGgCJEwIAjhMQAKETFwC7EwoAyRMFANATFADmEwMA6xMXAAYUHQA=","H":"tf4W8H1m"},"entry":{"N":"ACAl","E":"OjAAAAEAAAAAAAcBEAAAAAAAAQAHAAgACgBxAHQAdQB2AHoAewCEAIgAiQCMAJMAsACxAJ4BnwGpAd8B4AHhAeIB5AHlAeYB7QHuAe8B8gH1AfoB/AH/AQYCRQRbBF8EZARwBHsEfAR9BH4EgQSCBIYEhwSIBIkEigSLBIwEjQSOBI8ElASaBJ8EoQSjBKYEqASqBEcFSgVLBVUFXwVjBWQFZQVvBXAFcQVyBXwFfQWFBYYFhwWIBYsFjAWPBZAFkwWUBZUFlgWXBZwFoAWhBZIGmAaaBpwGnQbbBuwG7QbyBvcGEQcfByAHIQciByMHKQcsBzcHOAc5BzoHPgdFB0cHSAfIB8kH0QfHCM0I0AjRCNQI2AjZCOMI5AjtCPcI+Aj/CAAJHwk1CbkLvAu9C74LvwvAC8ULyAvLC84L1QvcC94L5QvmC+gL6Qv0C/0LKgx5DH4MgAyBDIMMhwyJDI4MjwySDJUMlgyYDJkMmgybDJ8MowypDKwMrQyvDLAMMg4zDjcOOA5ZD1wPZg91D3kPwQ/FD8YP3g/fD+sP7g/8DwAQAxAKEAwQDxAQEBUQFhAZEC0QTRBzEHwQihCOEI8QkxCbEJ0Q3BDdEN8Q4BDpEO4Q9xBhEWcRaxFtEW4RcRF0EXwRJBIpEi8SOxJHEmESehKIEooSuRLAEsESwhLJEusSGBMZEyYTJxMxEzQTXBNkE58ToBPGE88TBBQFFA==","H":"d4DgMzmO"},"desc":{"N":"FNi","E":"OzAAAAEAADoNhAEAAAEABwABAAoAVgBvAAAAcQAFAHkAAwCEAAEAhwACAIwAGwCwAOsAngEBAKkBAgDMAQIA0AEBANUBAQDYAQAA3QEJAO0BAgDyAQAA9QEAAPgBAgD8AQAA/wEAAAICAQAGAgEADAIGACoCAAA6AgAAPAIBAD8CAQBFAgIASQIAAFECBABaAgAAYQIBAGQCAABpAgAAcAIBAHMCAAB3AgAAegIAAIACAACNAgAAkgIKALUCAwC6AgAAvwIAANICAADYAgAA3gIAAOQCAQDrAgAA8QIAAPkCAAAAAwEACAMAAA8DAAATAwAAGAMDAB0DBgAwAwAAPwMUADsEBABBBAAARQQAAEcEAABJBAAASwQAAE0EDABbBAAAXwQAAGEEAABkBAAAbQQAAHAEAAB2BAEAeQQFAIEEAQCFBAoAlAQAAJoEAACfBAAAoQQAAKMEBQCqBAEArQQCALEEAwC8BIYARwUEAFQFAQBXBQIAXwUAAGEFBQBsBQAAbwUDAHcFAwB8BQEAgwUFAIsFAQCPBQEAkwUEAJwFAQCfBQIAqAXmAJIGAQCVBgEAmAYAAJoGAACcBgEAoAYAAKQGAACnBggAsQYBALUGCwDEBgAAywYBAM4GAQDUBgIA2AYDAOwGAgDwBgAA8gYBAPUGCwAGBwEAEQcAABUHAgAZBwIAHwcEACkHAAAsBwIAMQcAADMHBwA+BwAARQcAAEcHAQBPBwAAUgcZAHQHMACmBwEAuAcCALwHAgDDBwAAxQcAAMgHAQDRBwAA0wcAANcH5gDHCAQAzQgAANAIAQDUCAAA2AgBAN8IAQDjCAEA7QgAAPcIAQD8CAQADwkBABkJBAAfCQAAIwkCACcJAgAyCQEANQkCAEMJugAACgAAAgpNAFMKYAG4CwEAvAsEAMULAQDICwAAywsAAM4LAADQCwEA1QsAANcLAQDcCwAA3gsAAOULAQDoCwEA6wsJAPYLAgD6CwMAAQwBAAkMAAAUDAMAHAwBAB8MAwAkDAEAKAwCAC4MAAAwDAUANww+AHkMAAB+DAAAgAwBAIMMAACHDAAAiQwAAI4MAQCSDAAAlQwBAJgMBQCfDAAAowwAAKkMAACsDAQAuwwFAMMMbQEyDgEANw73AEAPAABGDwIASg8CAFMPAQBWDwMAXA8AAGEPAQBmDwAAaQ8BAG0PBABzDwIAdw8CAHsPAwCPDw8AoA8BAKMPAAClDwEAqQ8TAMEPAADFDwEA2w8BAN4PAQDrDwAA7g8AAPEPAAD6DwMAABAAAAMQAAAFEAMAChAAAAwQAAAPEAEAFRABABkQBQAhEAEAJhACACoQAAAsEAEANBABADsQAQA/EAQATBACAFEQAQBWEAEAYRADAHMQAAB6EAIAgxADAIoQAACMEAMAkRACAJgQAQCbEAAAnRAAAKEQAACjEAIAqxACAK8QAgCzEAYAwBAAAMwQAQDQEAkA3BABAN8QAQDpEAAA7hAAAPEQAQD3EAAA+hADAAERAAAJEQAACxEAAA0RAAASEQAAFBEDABkRAAAbEQAAHREAACARAAAiEQAAJBEAACgRAAArEQAALREAAC8RAAAzEQAANRECADsRAAA9EQAAShEBAFIRAABVEQIAWREAAGERAgBlEQIAaxEAAG0RAQBxEQAAdBEAAHwRhwAKEgQAEBIAABYSAQAZEgEAHBIAAB4SAAAgEgAAIhIAACQSAAApEgAALxIAADUSBgBHEgAAShIBAE0SAgBSEgEAVhIFAF8SAgBpEgAAbhIAAHoSAACIEgAAihIAAI4SAwCZEgMAoBIBALYSAAC5EgIAwBICAMQSAgDJEgAAzxIEAN4SAQDrEgEA7xIAAPESAwD8EgQABBMAAAYTAAAPEwIAExMCABgTAQAbEwAAHhMDACYTAQAxEwAANBMAADoTBgBQEwAAUxMBAFoTAgBeEwAAYRMAAGQTAAB5EwEAihMBAI0TAQCREwAAkxMAAJkTAwCfEwEApxMEAK8TBQC5EwEAwxMAAMYTAADPEwAA1BMDAOETAADvEwUA9hMAAPwTAAD+EwEABBQBAA4UAAASFAAA","H":"Web3xfsH"},"function":{"N":"A@J`","E":"OzAAAAEAAIMDWAAAAAoAcQAbAJMAAACwAAEAngEBAKkBAADPAQAA3wEiAAYCAQBFBAAARwQAAFsEAABfBAAAZAQAAHAEAAB7BC0AqgQBAEcFAABKBQEAVQUAAF8FAABjBQMAbAUAAG8FAwB2BS0AkgYAAJUGAACYBgIAnAYCAKIGAADbBgAA7AYGAPUGAAD3BgAAEQcAABwHMADBBwAAyAcBANEHAADGCDMA/wgBAB8JAAA1CQEAuQswAPQLAAD9CwAAGAwAACoMAAB5DAAAfgwDAIMMNgAyDgMANw4BAFUPAABXDxgAcg8HAH4PAADBDwAAxQ8BANsPIQD+DxsALRAAAE0QAQBrECsAmxACAKEQAADWEDAAVxElAB8SKQBhEgEAeBIbAKASAACiEhMAtxIXAOsSAADvEgsAGBMCACITEwBcEwAAXxMAAGITCgB+Ew8AnxMFALcTAwDEEwsA2xMAAOUTBQADFAQA","H":"N4rV6oa0"},"type":{"N":"AJl","E":"OzAAAAEAAHcSnAACAAQACQAAAAsAZQByAAIAeQAAAH8ABQCIAAAAigABAI0AIgCyAOsAnwEJAKoBNADjAQEA5gEIAPMBAQD3AQIA+wEAAAACBQAHAnMCgQQAAIUEAgCKBAAAjAQAAJQEAACXBAIAmwQDAKAEAACiBAAApwQAAKkEAACrBLMAYAUQAHMFAgB3BQUAfwUFAI0FAQCZBQYApAV6ASEHBAAoBwAALQcBADEHAAAzBwMAPwcAAEEHAgBFBwQATAd7AMkH/ADICAAAyggBAM0IAgDVCAAA2AgBANsIBQDlCAAA5wgAAOoIAgDwCAEA+Qi/AroLAAC8CwMAwQsBAMULAADHCwAAzAsAANILAADWCwMA3AsAAN4LAADiCwAA5QsBAOkLQAArDFIAgAwCAIQMAQCHDAAAjgwBAJIMAACbDAIArwwAALoMmgJWDwAAYQ8BAGYPBABtDwEAcA8BAHMPAgB6D2MA5A8BAOcPAgDyDwEA9w8IAAQQBAALEAAAGhBSAG8QBAB3EAAAehABAH0QAQCAEAcAjBABAJEQAQCXEEMA3xAAAOEQBQDpEAIA7xAFAPgQBQACEQEABxFQAFkRAABdEQAAYhEBAGURAgBsEQAAdREBAHgRpgAgEgAAIhIAACUSAQAoEgAAKxIDADASCgA8EgEARBICAEgSMQB7EgIAhBIBAIgSAACKEgAAjhIVAKYSAwCrEgcAtRIDALoSBQDBEgAAxBICAMoSAADMEgEAzxIfAPESBAD4Ei0AKBMEADATAAA2EysAZRMFAG0TGgCJEwIAjhMQAKETFwC7EwoAyRMFANATFADmEwMA6xMXAAYUHQA=","H":"QqzQLfta"},"alias":{"N":"`","E":"OzAAAAEAACMUAQAAACMU","H":"p2IVDFMs"},"generic_inverted_index":{"N":"b","E":"OjAAAAAAAAA=","H":"r0jTqlt6"}}') \ No newline at end of file +rr_('{"normalizedName":{"I":"AQQA9OgRAAABAAEAAQATgAKgIAAAD6tlcwEAAaBQAAATM2vzegAAABgEAwEBAwGwIBFBAAFl8q0SAAABAAEAAbCAE1AAAXX0tgAAADYBSwcBAPMAAjRh9+oBAACsA+YK6QABAGUCAQABAwGwIBE/AAFl8qsSAAABABMAArCAE1AAAWF1OjAAAAEAAAAAAAkAEAAAALYA7AE3CTgJXhB7EGMRZBHJE8oTAQAA9rUAAAD1AEEASgcBAKUDEwADsFAMwgABoIAAABQcMmNz9PABAAABAAEAIAABAASgAAAACOygEAAABZawAASSAQOggAAAFBswNThz9rUAAAD1AEEASgcBAKUDAQYA9JsRAAABAAEAAQABgAKwAASSAQOggAAAFBs4c5MABKCQAAAUHKAAAAAB6jIzNnLz9wEAAAEAAQABAgD11QcAAAEAbAEBA74EIwACsLAS2AABbnL09AEAAAEAAQD2BgEDAPRtDAAAAQABAAEAAQABsFAUIwABcgG6BAAAAYACoBAAAAzboDAAABBiM3MAB2VqZWN0ZWQAAPToEQAAAQABAAEAAAAHY2NlcHRlZAAAAecRAAAA8gJ0dAIAYXIBbAcAAADyAW8BAXJ18gsCAAABAAC2AAABZQEEsEASogABsEAUYQABcmRlaHQAOjAAAAEAAAAAACgAEAAAAIQEhQSGBI4F/wgACSUJWAnBDLMPJBAlEDIQYhDBEMIQwxAMEYMS4hLjEuQS5RIREyATIRNOE08TvhO/E8MTxBPFE9UT1hM/FEAUVhRXFH0UfhQBAwD0AwkAAAEATQMBAAEDAPNODAAAAQABAPOAAmlvAYACoEAAAA+dsCANAwABZXABBQGwMBL+AAFo8+wTAAABAHIAE4ICoDAAABCzY3ABAgA7MAAAAQAA7QACAPsLAAAFDewAAQEAOjAAAAEAAAAAAAcAEAAAANIR0xESExMTaxNsExkUGhTzBgFpAfEQAAATggKwgBMmAAFudAEBAbBQEPIAAWP0UQcAAAEAAQAGAiOAAqBQAAAQtGNuABBuZ2Nvbm5lY3Rpb25zaWRlAAABdRMAAAAACWFueWNodW5rcwAAARsTAAAAAAhhbmRzaGFrZQAA9JASAAABAAEAAQAAAARycm9yAADy+gwAAAEAAAAEaHVuawEAwAAAABDTcwE7EAAAAAAFaHVua3MAAAHvEgAAAPICbGwBAGMBvAwAAADyAIWAlRAA9L8EAADQAAEAAQD1vwwAAAEA6AIBAAEAAANibGUAAPIcCQAAAQAA8oF0AgBhZQAKcHVkcHNvY2tldAAA840SAAABAAEAAPKAAwFhaW9lAAphcnRpbnN0YW50AADyZxMAAAEAAAAIbmdsZWNlcnQAAPLtEgAAAQAA8oACAGl0AA9vb3RjZXJ0aWZpY2F0ZXMAAPIdFAAAAQAAAARlZml4AADyORAAAAEAABKAAQGw4BRpAAFybAAGbml0aWFsAAD0zxAAAAEAAQABAAAABXJ5cHRvAADyNxAAAAEAAPYBAAFoBQGg0AAAFENjaXByc2EA8twPAACuAAADZG93AAA7MAAAAQAABwABADYHBwA7MAAAAQAAHAAHACkQAgCBEQ0AsBECAIgSAQAPEwEAYRMBAPgTAgDygAIAbnQACWF0aGhhbmRsZQAAAW8RAAAAAAVhbmRsZQAA8jUQAAABAAAAD29ubmVjdGlvbmhhbmRsZQAAAbITAAAA8oFrAwBjaHASgAECsAATrAABsLATVgABYWR4AANkbGUAAAG7DAAAAPICaXQBAGkAAVwTAAD2OwAAhIcTY1E97gyhEAAAFEMRAQEEcBYAOzAAAAEAADwBDAC2ASIA3AEGAJkFAADDBewANgcHACkQAgCBEQ0AsBECAIgSAQAPEwEAYRMBAPgTAgABBQD25QwAAAEAAQA7AwEATwMBCgGgQAAAFB9l8k0UAAABAAGAA7BwEVwAAbAgFEEAAbBgEzEAAWFkbwEJAPPiEgAAAQDyAPEAAUNh9XwQAADpAAEAZQIBAPEAAUFh9l4QAAAdAOgAAQBlAgEAAQUAOzAAAAEAAAcAAwCwEQIAYRMBAPgTAgABBwD2hRMAAAEArABYABgAAQABgAKhEAAAFIewgBR9AAFpdQEGAPT7EAAAAQC+AAEAAQUA9C0TAAABAJgAAQDzAAFjAZQSAAABAwD27gwAAOYCWAABAM8CAQABBAA7MAAAAQAACgAEAHUQBQC2EgEAFxMBAD4UAAABhgKgAAAAEzqgAAAAEztkcvMCAXYBZhMAAPOAAmVvAYACsCATgwABoCAAAA/TYmQACGVnb3RpYXRlAADyvhIAAAEAAAAHaXNtYXRjaAAA8uURAAABAAAyAW4CAYATYwAQ+m1uczswAAABAAAFAAEA5AgFADswAAABAAAFAAEAQxMFABKAAQKwsBOsAAGwgBQjAAFvZHoSgAEBYI9wTDUWaWEACXNpZ25hdHVyZQAAARwUAAAACAAAAAEbFAAAAPKDbHMxAgAyMwAIcnZlcmNlcnQAAAFmEwAAABKAAQGgUAAAFEdlY/IAAgBzdPI0BwAAAQAAAAt1aWxkZXJlcnJvcgAAAbETAAAA8gABAGIA8zsTAAAuAQEAEoABAaAAAAATOnJkEoABAaBQAAAUDWVj8oFmAgBpefYKAAACBqBwAAAShqCwAAAUDWlzYWNlbnZ5ADswAAABAAAKAAQAtAUCABMHBABIDAEAtBAAAOZCAACCicAAAAAAklWbiWqRZlwXTL02jLFQFKQAAbBwE8EAAaEwAAAUmAQAAQthGgDyTgkAAD0HAYACoFAAABD5oKAAABMza3ATgAKgIAAAEItlaQEFAbBgFE8AAWY7MAAAAQAABwADAPcLAwBtEwEA9hMBAAANb3VuZHNleGNlZWRlZAAA8q8TAAABAAD6GQEAYvO9BQAAAQB1AQASgWkBAaAwAAARxm5hAQIAOzAAAAEAAAMBAgDRDAEAiA4BAQAHYXJpYW50cwAAAcYRAAAAAANudW0AAAEkDAAAAPomAgBldgDzWhAAAJ4AIgIBAgD0lREAAAEAAQABAAEKAPSlEwAAAQABAAEA84ECbXQBBgDzVxEAAAEAAQABggKwYBMrAAGw0BRNAAFpchOAArAQDNYAAWVpAAVvbmZpZwAA9OgTAAABAFAAAQAA8gRva2VuAQBj8usSAAABAAAABWFpbGVkAADzuxIAAAEAAQAA8oJvbgIAZnQAAAABsAAURAABZPTqCwAAAQABAAEAAPqIAgBlaTYGAAFkgYixMBRsAAGgYAAAEVagUAAAEOqwkBL8AAGwQBDmAAEBAAC0EAcAASAUAADyAAIBaXVlADowAAABAAAAAAAKABAAAABECUUJCBBUE1UThROGEzIUihSiFKMU8oACAmxyaXT2GwAAwsegMAAACQpABAAAqEAUAQA6MAAAAQAAAAAABwAQAAAAdgR3BHgEMAkxCWEMKxEsEQEFADswAAABAAAHAAEAGBEHAAEAAbBQEZkAAWPzJAwAAKAEAQABAQKwgBPwAAGgMAAAEH9pdQHhEwAAAQEAOzAAAAEAAAYAAgANBwUALgwAAAEBADswAAABAAAFAAIA2BADAG8TAQDzgAJlbAEAAaCQAAATWmjyIhMAAAEAEwADsEAR3wABsDATPAABZGVy89EEAAA3AgEAAQ4BsAAUZwABczswAAABAAAFAAEAgRQFAAEFAPSvEgAAAQCoAAEAAQcA9G0TAAABAIgAAQAjgAKhQAAAFF9idgACemUAAAGOBQAAAWIQAAD2BQAAgYawAA+mAAEAAQCXAAQAOjAAAAEAAAAAAAwAEAAAACUHJgcnB+EM4gyvDyAQbhBvEHAQcRCHElsTAQEA9T0JAAABAAEAAQABAAAGcmJvdW5kAADyMxAAAAEAABKAAQFwMs9W54FlbwADYXRzAAABzhAAAAAIAgAAATIQAAAA8oNhZHMCAGl0EoABAbAABY0Gem9lAARyYWRlAADy4ggAAAEAAAAQb2NhbGlmc2FtZXJlbW90ZQAAAXsUAAAAAAJ1dAAAOzAAAAEAAAUAAQB1FAUAAPINcm9tYXJnbWF0Y2hlcwEAbTswAAABAAAFAAEAMxQFAAAyA2F0ZQICsEASpwABsAATagCKZmxlcwA7MAAAAQAAGAAGABgRBwDLEgEASRMEACcUBAAuFAMAnBQAAPYCAAAEAqCQAAATGWRnbHBjaQA6MAAAAQAAAAAABwAQAAAAlwWYBa8SsBJYE1kTlhSXFAEDAbAwD9IA7nT2XAcAAAEAigDuB6kA+wEBAAGwcBOvAAFl8soPAAABACMAAqBAAAAUCWVz9DMQAAABAEsEAQAAF3NhYmxlYWZ0ZXJuZXR3b3JrY2hhbmdlAAABXxQAAAAABmVyc2lvbgAAOzAAAAEAAAUAAQBDEwUAAAANcGVyYXRpb25lcnJvcgAAAVMUAAAAAAdhbWV0eXBlAADycxMAAAEAAAAFeXBlZXIAAPILEwAAAQAA8odwcG9ydGVkBABibm92AAd1Y3R1cmVkAAABfxAAAAASgAEBsJAT5gABcmHygAIAdHUACWVjdnN0cmVhbQAAAXITAAAA8gZyZGVyZWQBAHIA8o8RAAAgAAEAAPWbBQAAOAsNAg8ALAAAA293bgAAAWsHAAAA8gABAW5zAPWWBAAAAQAVCwEAjgABBgDzYxQAAAEAOwABAgA7MAAAAQAAjAACANwQAADtEYsAAAR0aW1lAAABGwkAAACmAQAAAgPAAAAABJOxABRlADN0eGRuc/KQAAAAAQA6MAAAAQAAAAAACwAQAAAAVAdcCfIL/AzvEPAQgRKCEgkTChObE5wTAAp4cGVjdGVkZW5kAAABABEAAAD2UwAAhoWgYAAAEyWwYBNQAAGgcAAAEoUQRQoOAAUA8ucBAAABAAENAPQtEwAAAQCYAAEAAQoA9cMTAAABAAEAegABAAABeAAA9C8HAAABAAEAAQAACBQAAPIZCQAAAQAAAAJlZgAAAfkMAAAAAAR0YXRlAAABbhEAAAD6GwEAcwEjDAAA9EQRAABJAQEAAQDygmNrAgBlcgAEbmRlcgAAASIMAAAA8oADAGVvdAEDAPSHEwAAAQAPAAEAAAAAAPQrBwAAAQABAAEAAPKAAQF4b/IAAwFyc3RwAe0BAAAB4QcAAPKAAQFwaQABNAABsIATUAABdQHsAQAA8jcJAAABAAABMgAAAesBAADzNQkAAAEApQMAgAIA44AAAASSwAAAAAHqMjZ2T5AACAzAAAAAAHqwQBDEAAEd2seWTKwWL+IANjQeyT5Uquw9FBnBVQ9ePhrp1PESCsFyFFFnqUKr4WF8xJjNX04xMzY4ZG5wc2FiY2VmZ2lsbXJ0dQA7MAAAAQAACgAEAN8HAQCeDwEA4Q8CAOoPAwABAAA7MAAAAQAABwACAC8HAwAqCQMAIYACsCAP3ACuqWlvAYUCoAAAABM6oAAAABM7ZHIjgAKgAAAAEF40ZQEJADswAAABAAAHAAIA/RMAAAIUBgABhAKw0BSUAAGgkAAAE5FiaAGAArCAEwAAAaEAAAAUXGNmE4ADoCAAABEpsJAQ/gABZW9z8QABwnLz9wEAAAEAAQABBAD0fBIAAAEAbQEBAAEAAPSUEAAAAQABAAEACB8AADswAAABAADtAAIAvAUAAPUH7AAA+iYBAWlzADowAAABAAAAAAAOABAAAAD+CDkJOgk7CTwJCAwJDJEPkg+7D9IPwBDlEHMTdBPyAAEBcGwBeQAAADswAAABAAAIAAMAXQkBAMkQAQAkEQQA8QEBJnPy8gsAAAoBAQUA9IcTAAABAA8AAQDxgAIiAGlwCAAAAAGNBQAAAQcMAAASgAEBoJAAABMZbGO0BAAAgYYAArAAEMQAAQQHd8G3lLN/ncH0QzBXAIAACDALADswAAABAAAKAAQA3wcBAJ4PAQDhDwIA6g8DAAEBAaAwAAAQf3UB4RMAABOAAqBAAAASeWNuAQYA9PYSAAABAJkBAQATAgKwYBL4AAFmbfLPDAAAAQAjgQKwQBMmAAFlbAEBADswAAABAABEAAMAuwUAABEJAAByDEIAAQEAOzAAAAEAAAkAAgDXBwcA+BIBAFOAA6AQAAAHKmNkbgEBAPVoDAAAAQABAAEAAQATgAKwsBPSAAFhbwATaXRocGxhdGZvcm12ZXJpZmllcgAA8mkUAAABAAAABHJlYW0AAAEhDAAAAAACbmQAAAHpCwAAAPKAAgBldAADZXh0AADyPREAAAEAAPIDb2xsAQBuOzAAAAEAAAkAAQDfCwkAAAADbnRvAAA7MAAAAQAA7QACAGoHAADyCuwAAAAEdHVyZQAAASAMAAAAAAdudGVycm9yAAAB5BEAAADyAm9tAQBpOzAAAAEAAP8AAgBpBwAA8wn+AADygAIAcnX2BQAAhYKwQAzpAAEggRIQAAQAOzAAAAEAAAkABAC2BAEAeAUBAEoMAQCVEQMAAQcA9HcTAAABAAEAAQDzAQF0OzAAAAEAAAgAAgD0CwAAJgwHAAEAAaBQAAAUQ3MBhBIAACOAAqDAAAAUfGl0AYICsEAUAAABoFAAABHRYmYAAAAAOzAAAAEAAAYAAgBxEwAA2RMFAAAIVAAA8lEUAAABAADyiGFyYW1ldGVyAgBlcwADb2RlAADyQRMAAAEAAPoCAQBj82sRAAABAAEAAAg0AAA7MAAAAQAABwACAOERAgDmEgQAAQ4UAADyA29ydAMAY2Vw87gMAAABAAEAAAEAAbBgE/IAAWUBLBQAAPpGAAFz8xYJAAABAAEA8yoRAAA/ACEBAAVlc3VsdAAAAWoRAAAA8gJlcgEAcgABCxEAAPKAAwBmbXDygAEBc2RygAEDA6rlfBvcbmNydrYDAAACBG1u4DnXgmF5ZWlvdQD06QEAAFwF7gEBAAEDADswAAABAACMAAIA3BAAAO0RiwATgQKgYAAAEY9pbwGAAqCgAAAUFLAQD54AAWF0AQIBoDAAABBdYQFEDAAAAYACsFAREAABsCAPkwABY27zgAJudAGBArCgE/4AAbCgFAAAAWNyE4ACoCAAAAwNZXIBBAA7MAAAAQAADgACANUMAADxDw0AI4ACsFAPygABYnUBAQGgQAAAEWhlASEQAAATgAKwMA+8AAFucgEAAPRdEwAAAQABAAEAEwICsIATMQABc3b0XwkAAAEAggoBAAGFArBwE/sAAbAwE04AAWVzAQIAOzAAAAEAAAcAAQBGCQcAAANpemUAAAGzDwAAAAAHdXJhdGlvbgAAAcURAAAA8gJhbAIAZHMA8mEUAAABAAAEcmluZwAAOzAAAAEAAEMAAgARCQAAcgxCAAASgAEBoFAAABFxdGUBBgD01g8AAAEA9AABAAEBAaBAAAAQfWU7MAAAAQAABwACAGAHBQDtBwEAE4ACsCASoAABZWkAC3NzaWJsZXZhbHVlAAABGhMAAADSAAEDsFAQXwABb2VwcwDziQQAAAEAAQAAA25lZAAAOzAAAAEAAIwAAgBnBwAAZwmLAAAADGFueWFkZHJlc3NlcwAAAboSAAAAAAJuZwAAAWgHAAAAAANyZ2UAAPIUCQAAAQAA8oACAGFv8oADAGxtdwAGdW50aW1lAAABfhAAAAAADHVycmVudHRocmVhZAAAAUATAAAA+m0CAGNy8r4EAADOAAABBQD06BMAAAEAUAABAAAEdG9yZQAA9PcMAAABADgDAQDyXhEAAAEAAAlldXNlZXJyb3IAAPLfEQAAAQAAAAJvcwAAAbcMAAAAAAplbW9yeWNhY2hlAADzuBIAAAEArAAAAAFnAADyEgkAAAEA9WMQAAABAHIAAQAVARKAAQGwYBPSAAFvaQACZXkAAPK1DAAAAQDy+hIAAAEA+liGgQCcAwQAAAGLBQAAOjAAAAEAAAAAAAcAEAAAAAMJBAlRDFIM5hDnEOsS7BLygAIAZWn2/QsABQygMAAADEMF/1Hmm/Ow0BQjAAFrb3BzdDBhYmNlZmdpbXJ1dgA7MAAAAQAAewIKAPwBAABlA+wAoQUBALsGCgBqBwAA8grsAFMMAAD0DYsANxABAI8QAwABAQD0nxEAAAEAAQABAEOAA7FwFKYAAaDQAAAUFWNvcwGAAqCQAAAUG6CQAAAUHDIzAAZlc3Npb24AAPL1DAAAAQAA8gABAXMxADswAAABAAAGAAIADQcFAC4MAACSgAEDsAAFiQABsGAQxwABc2VpbwENADowAAABAAAAAAAHABAAAAA0EzUT0BPREwoUCxRaFFsUAYABZpPip6/FbgEAAbCAFCEAAXDyHRQAAAEAQwUDoCAAABFFoEAAABKZZGVz8+APAABLAwEAAQIA87ESAAABAAEAAQAA9SQRAAABAAEAAQABACKAAQH2UAWJAAFseQEAAPRODAAAAQABAG4AAQIAOjAAAAEAAAAAAAkAEAAAAMcPyA+7EHcR1BLVEtQTXRSSFJMUAAdyZXNob2xkAADywxEAAAEAABKAAQGwIA/SAO5oeQAFb3VyY2UAAPTzDAAAAQA6AwEAAAADb3V0AADzDgkAAAEAAQAAdgMAAIODoEAAABHeCAAGEEABADowAAABAAAAAAAXABAAAABcB10H5wcbCU8JUAnyDLAPsQ/VD34QhRCGEIcQiBDHEMgQ/hD/EHkS0hPTE/4T/xMSgAEBsDAQcgABZWG2KgCAwslhFpKf5q+gIAAADLtX0SpBKfNtj2KcleSgEAAAEIsAAAQCwAq4CAGAA7AwFJ0AAbDgFGkAAbBAEDkAAWFscgELADswAAABAAAGAAEAAhQGAAEMAPRrEwAAAQCtAAEAI4ACoHAAABQtZ3fzgAJlbwEMAPNdFAAANQABAPMAAWEBaRMAAAEQAPOKFAAAGAABAAEFAPTPEAAAAQABAAEAAQABsGASqQABcvesBQAAAQA4AioEAQDvAwEAM4ADoBAAABKMZG5zAQkA9NURAAABAD4BAQABBwD09hAAAAEAsAABADOAhKEAAAAUi7BAEDcAAYBBAQADaXN0AAABjBIAAAAAAnBzAAABwhEAAADyBWdocHV0AgBiaAGyDwAAABKAAQGw0BQdAAF1bxKAAQE9xtCO3GtvZZa8AACBjSFgl44b46AgAAAHWSndh+XmvqBQAAARb6DgAAAT9UVxUyxLXLDQE+YAAQAAAZf1CgA6MAAAAQAAAAAAHgAQAAAAAgIDAgQCXARdBAYJEQxVDFYMWQzEDMUMxgzHDMgMyQyADtwPXxBgEIoQsxBcEV0RqRGqEe8TOxQ8FE8UUBQBAQDzSwcAABwMAQABCgD0XRMAAAEAAQABAAGAArAwE6wAAaDAAAAT4W95YwCEoHAAABHFoDAAAA+zCIgC8mEUAAABAAGAAqAAAAAQe6AAAAAQfDQ28wIBdvLvDAAAxgIBgAFRMgSPX1xsAQcA9H4TAAABAAEAAQABAwD0dxMAAAEAAQABAPosAAFyOzAAAAEAAAUAAQCMBAUAAAGBAXNs1+kuf20AAmVuAADy8wcAAAEAAPIAAQFscgD1aAwAAAEAAQABAAEA9g4AAIKJoGAAABHnoHAAABOWV5cksSwBWLkS+hpDQAQAD2gFADswAAABAAARAAgAGAIAAE8HAQAKCQAAVgkBAOEQAQDtEAEAeBEFAH8SAQAAAAgSIJ5DlPt7OkIJ7Yl+STSvkbWVQOZmwW//PiWg1Al2P+P0EIMUKsqjrAKvAKNsLQ3mgAH0ABABYl/4qVFcZEFSQXLdPt2rJ5e9NSm/BauNPxKLXe+VsLAUaQABHfxVzXw7oDAAAA+reETw2EsCS9DcXDbbRq7h6ZxmL9ONhGXSAt2UAWOgRoP+94mwUEU5EZnsXzcMYFZ0SduZ3LyRYWhpbG9ydXkwMWJjZGVmZ2prbW5wc3R2d3gAOzAAAAEAAJ8EZQB4AAAAigADALcA8gC1AQAA2gEBAOMBAwBXBAEAfgQBAJMEAACrBAIAsgQDALgEAQByBQUAkgUAAJoFAACcBQIArgUFAL0FBQCxBgQA6QYFAPAGAQD6BgIAMwcAAEIHAQBLBwAAWwcAAGwHAACMBzsA0QcFAOIHAQDrCAAA8QgAAPUIAgD9CAAADgkCABYJAgAjCQEAQgkAAPsLAQAXDAMAIwwAADwMAQBDDAAARQwBAFoMAwBjDAEAbQwDALgMAgDRDAEA2AwAAAUN7ACIDgEBrg8AALIPAADHDwIA2A8BAN8PAADuDwIAVRACAJ0QAQCoEAEAsBACALsQAADcEAIAAREAAA0RAAAqEQAAPREBAEQRAABJEQYAaREBAHARAAB3EQAAnxEHALMRAgDNEQEA7RGLAHoSAwCEEgAAhhIAAIoSAACMEgMAwBIGANQSAQDtEgEA/hIBAFwTAABmEwIAdxMDAIkTBwC3EwUA1BMAAOETAADqEwEAQxQAAE0UAQBYFAAAXRQAAHUUBQCBFAUAkhQBAAEEAPSVEQAAAQABAAEAI4MCsDAURAABYXJDgAOwABPJAAGwABPLAAE0NmEBBwA7MAAAAQAABQACACcUBACcFAAAAQEBoFAAABFqcgELEQAAY4CEoAAAAAHvoGAAABPNGEABAYADsGATtQABoHAAABKUsFAPlQABZWlyAYACoAAAAAWVoIAAABQbOHMTgAKgkAAAFBwyMwEDAPPVBwAAAQBsAQGBhLAwC/UAAaBAAAAMvqCAAAARRLBADL8AAQAAHQghAAAB3hEAAADyB3RlbXRpbWUBAGUB8gwAAPL+EAAAAQDygAEBc24BAAD07xAAAAEAkQEBACOAAqBgAAATJWJpAQEAOzAAAAEAAAYAAgCuBQUAahEAAAAAAADyYxMAAAEAAV4UAADyA2lvbgEAcwA7MAAAAQAABQABAEMTBQAACWZ5c2NoZW1lcwAAAUcUAAAA+qACAGlzAAppcGhlcnN1aXRlAAABrhMAAAA2AACGcG9ydGVkAgOwUBMLAAGwcBNzAAGg0AAAFFNjdmJubwACdGUAAaAAAAATMHMBvQQAAPS2DwAA8wMBAAQAAQABoIAAABScZgF7EwAA8gZvbW1hbmQAAXMB8QwAAAGAEQAAEgABAaBgAAAUfGNzAPLAEQAAAQD2AQCAg4OwEBLcAAECgQAAKAEBBQD0qhAAAAEAAQABABOAArCwE4cAAWFsAQIBsHAUQQABcDswAAABAAAJAAIAyQcHALcQAQABAAD0mQ8AAAEAAQABAPMDAXMBFxEAAAECAaAwAAAQf3UB4RMAAAECAaBQAAAUQ3MBhBIAAAgUAAABuwUAADswAAABAABDAAIAEQkAAHIMQgAigAIA5lAAAAcqZG4BAgD07xAAAAEAkQEBAAAAAADz2g8AAAEA8gAB1BEAAAAFZXJyb3IAAPI4EwAAAQAA8oJhdAIAZXMBAgD0WhEAAAEAewEBAAACbmkAAPQJEwAAAQCRAAEAAAADaWRpAAD0PhMAAAEAmAABAADyhW9ja2VkAgBidfKAAQFsafIAAgFidHX1ZgcAAIkAAQABAAEAOjAAAAEAAAAAAAoAEAAAAAEQxhCSExYUYxRkFGYUcxR0FJsUnxQADGVjZWl2ZXdpbmRvdwAA8/gTAAABAAEAAAAJaW1pdGVycm9yAADyNhMAAAEAAAACZm8AAAHwDAAAACKAAgDQAAkMAAFkbgAHaW5pc2hlZAAAAYsSAAAAAAR2ZW50AADy2A8AAAEAAAAGbG9ja2VkAAD0BxMAAAEAkQABAADyA2F0YQEAYgD07RAAAAEAkQEBAPICYW2HgDgJAzswAAABAAAFAAIAtwUDACgHAQA6MAAAAQAAAAAAHgAQAAAAIQzlDOYM5wzrDOwM7QzQD9EPIhAjECcQKBBFEEYQRxBIEEkQShBLEI4QkxC+EL8QchOWE70TWRRlFIcUmBTyAAICZWlhdQHpAQAA80UHAADuAQEAAQEA9/cMAAABADgDAQDMAGEAAQAABmVuZGluZwAA9NYPAAABAPQAAQAACBcAAAF9EAAAAPpJAQBlOzAAAAEAAAcAAgBgBwUA7QcBAAASgAEBsCASoAABZWkyAAIBsFAQXwABcHNl84kEAAABAAEAAFKAAQKgMAAADA1wZ3IACnN5c3RlbXRpbWUAAPL+EAAAAQAAAQABsEATOAABZfNuEQAAWQEBAAEIAPRtEwAAAQCIAAEAEgFzAANj8IX9voewQBM8AAFhYmXzJQcAAAEAAQA6MAAAAQAAAAAACQAQAAAA4QziDK8PIBBuEG8QcBBxEIcSWxMAAAAAOzAAAAEAAAwAAgC7BAEAfgUKADowAAABAAAAAAAVABAAAAAZCRoJGwwcDNMM1AyFDqAPoQ+iD6MPpA+3D9oP2w/NEM4Q3xByEc8R0BHUEfKAAgFzdWUAA2ltZQAA8rAPAAABAAAIJQAAOzAAAAEAAAkAAQAzEQkAADrTAgGwYBNnAAFzdGkBBAD0wg8AAAEAAQABAACAAQHmQAAADHGwUBPmAAFpb/KBbAEBZWnwgAMBAAMGWmJydG72QwAAxMgmCRCOG6WwQAllAAFNYEQvs9x02TeyXcagEAAAEF5AApAAgGUiCAA7MAAAAQAAFgAJAFsHAADJDwAAqBABAA0RAABJEQYAnxEDAIwSAAB3EwMATRQBAAGAA7AAE8kAAbAAE8sAAbBwFEQAATQ2YQEEAPSZFAAAAQAKAAEAQ4EDsMAT8AABoEAAABFDZG1wAQEAOzAAAAEAAA4ABABeBwEA9QwBADMRCQBxEQAAAQEAOzAAAAEAAAYAAgDzCwAA/QwFAGOAA7BwEPgCImJnbwEAAbAwDPUAAXM7MAAAAQAACAADALoSAAAvEwAAphQGAEOAA6BAAAAML7AwDEgAAW1ycwEBADswAAABAAAKAAMAVxECAKETAwCMFAMAI4ADoDAAABA+oLAAABNTZGdtAAVocmVzaAAA8mUJAAABAAC2CwAAAQeggAAAEpSggAAAEYB0YWRlaXJ1dgA7MAAAAQAAGAAJAN0PAQD7EAEALxEBAD8RAwC2EQUApRMDAPUTAABUFAEAiBQBAAUBAICFsBAN8gABsMAT8AABoAAAAASUoEAAABFDCimQAAABcAAA8yIHAAABAAEAAAACaWQAAPXoBwAAAQABAAEAAQAA8oACAGNpMoABAqBAAAAMLmNldQGAArBgFCEAAaDQAAAT72VvAYABbpy/o1DxdAECAPYLCQAAVwAHBwEAtgABAPOAAm5yAYACoPAAABQssGAUlAABbnIAFHJpb3VzY29uZ2VzdGlvbmV2ZW50AQDQABRnAAFzADswAAABAAAFAAEAgRQFAAgtAAABiAQAAPIDDQAAAQAIAAAA83sFAAABAAEAABKAAQKwEBMWAUqwwBODAAF3Y3T2BgCAg4SgIAAAB0QBAQgQSAEBAAA7MAAAAQAADgAFAP4GCACrDwAAuA8AAIkQAAACEwIAAANyY2UAAbCwFG4AAWw7MAAAAQAACAABABkHCAD08wwAAAEAOgMBAACAAgDAAAAAEHvAAAAAEHw0NvIDZGRyAQB2Ae8MAAAAGgkBAaBAAAARbmFzAPcjDAAAvANlAUkBAQABALQBEoFrAQGgIAAADPllcnYAAIACA6AAAAAFo6BwAAAS02N1bnJzAQAAOzAAAAEAAAwAAwDrCAAAtxMFAHUUBQABhgKgYAAAE8CgkAAAFC1lbvOAAmx08QEB4XP2FgkAAAEAAQASCD8AIQEIPAAAAdUPAAAA+gsBAHIBhwQAAADSgAEDsDAR5QABb2FpdQADaWNlAAGgIAAADNhtAXoFAAAAABRpcHNlcnZlcnZlcmlmaWNhdGlvbgAAAQ0UAAAAAQMA86QSAAABAAEAI4QCsSAUoAABY2sAA2ludAAA9G0MAAABAAEAAQAAAAAAAbBQFCMAAXIBugQAAAA6MAICsEASogABsEAUYQABZGhldPOEBAAAAQABADowAAABAAAAAAAkABAAAACOBf8IAAlYCcEMsw8kECUQMhBiEMEQwhDDEAwRgxLiEuMS5BLlEhETIBMhE04TTxO+E78TwxPEE8UT1RPWEz8UQBRWFFcUfRR+FAGBAqAwAAAUWKBQAAAT4WNzAAVjaGVtZQAAAd0RAAAACDEAAPIxEQAAAQAAOlkCAaBAAAAUIGxzdgDyGxQAAAEA+g0BAWFl8oIEAAABADswAAABAAAGAAIA8wsAAP0MBQAIBAABsJATrAABbvQJAgAAAQB2AgEAAXUTAAB2BACAg4SwcBD4AiKwcBLtAAFp78B+6P5IAIACYAIBAAGgIAAAD8ZyAaUFAAABAAKhQAAAFGugQAAAEOBjZTswAAABAAAGAAMA+AgBAMIPAwCLEgAA84ACZHIBAQA6MAAAAQAAAAAACQAQAAAAwxHEEd4S3xLOE88TJRQmFKAUoRQACmxkdHJhbnNtaXQAAPJpEQAAIQEAYoACAfdABLgAAXJ1bAEIAPWQEQAAAQABAAEAAQAAAmVkAAABGAcAAABSgAECIfR6PsZbcmtucgACAqBQAAARKWFvZXUAOzAAAAEAABwABQD7AQAAUAMUAOEGAgBlCQEApg8BAAEGADswAAABAAAFAAIA2BADAG8TAQAjgAKwUBOvAAFjaAEDAbBAEUYAAWUBgA4AAAGAArAACWMAAbAwEd8AAWRyAAtwY29ubmVjdGlvbgAAARkTAAAAAQMA9NAPAAABAO0AAQAAA3R1cwAAAa8PAAAA8oABAWFyAAl2ZXJjb25maWcAAPIXEwAAAQAAAAVpbmRvdwAA8ogSAAABAAAACXVmZmVyc2l6ZQAAAdYTAAAA+qICAGJ38oACAG5y8oACAGV0AApidWZmZXJzaXplAAAB1RMAAAAACWl2ZXdpbmRvdwAA8mETAAABAADygmVjAgBldgAGaW9yaXR5AADyyRAAAAEAAAgoAADyPBMAAAEAAPIFdGF0dXMBAGUBhxIAAAAADWF4aWRsZXRpbWVvdXQAAAFdFAAAAAAQZWVwYWxpdmVpbnRlcnZhbAAAAYoUAAAA8oJ0aAMAa21z8oACAGFyAApkbGV0aW1lb3V0AAAB1BMAAAAACW5pc3RyZWFtcwAAAZ8UAAAAAAZ0cmVhbXMAAAFmFAAAAAAEYXRocwAAAUYUAAAAAAhpc3RyZWFtcwAAAZsUAAAA8olvbmN1cnJlbnSEgAKACvKCYXgCAGNpAAdpZmV0aW1lAADyxxAAAAEAAAAOcGFsaXZlaW50ZXJ2YWwAAAEyFAAAABqaAQGwAAxmAAFleQASZWZhdWx0Y2xpZW50Y29uZmlnAAABXBQAAAAADGxwbnByb3RvY29scwAA9F0TAAABAAEAAQAA9h8AAImCoEAAAAzosHATVAABCZwLEAEAADswAAABAAALAAQAsgQDAHUFAgDwBgEAsxECAAhPAADyXgcAAAEAOzAAAAEAAAwAAwD1DAEAMxEJAHERAADyAAEAcwA7MAAAAQAACAADALoSAAAvEwAAphQGAAEAADswAAABAAALAAIA6QYFAMASBQAADWlkZW5vdGFsbG93ZWQAAPKsEwAAAQAAAANhbWUAAPTuDAAA5gJYAAEA8vwSAAABAAAHbmRwb2ludAAAAYYSAAAAAARuZmlnAAA7MAAAAQAABQABAHUQBQD1thIAAAEAYAABACYBAIZlcmlmaWUCAMAAAAATOsAAAAATO2Ry8gJydAEAdgABZhMAAPKAAgBlbwgxAAAB0w8AAAD2AQAABQGgsAAAFA1hY2Vuc3Y7MAAAAQAABwACALQFAgATBwQA8kgMAAABABKAAQEoGDvdT/RyZBKAAQGgQAAAEItlYZIAAQOwQA+4ANGwIBM8AAF2ZXJ0APIkEwAAZwEADGxwbXR1ZHByb2JlcwAA8pcTAAABAAASAAECsBAUIQABsOAUIwABcGVv8n4EAAABADswAAABAAAFAAIA4gcBAKMRAwABAgD01g8AAAEA9AABAAEBAPMiDAAAewNtAQggAADzKRAAAAEAAQDyiBIAAAEAAAlucmVsaWFibGUAAAGFEgAAAAAFdHJlYW0BAMAAAAAQxnP16wwAAAEAAQA6AwEAAAAGdWFudHVtAADyxBAAAAEAAAAVYnNlcnZlZGFkZHJlc3NyZXBvcnRzAADymRQAAAEAAAAHYWlybmVzcwAA8i8RAAABAAAAB25zdHJlYW0AAAGWEwAAAAADYWl0AAABXBMAAAAIIQAA8gUTAAABAADyA3JhbQIAZXfydBAAALoAAPrPAgBnbwADYWNlAAABFhMAAAFgFAAAAAJ6ZQAA8uQSAAABAPPWEwAAgAABAPKAAgBpcAAGZW5jaGVzAADzkxMAAAEAAQAA8gV1ZmZlcgIAYnMBJhAAAADyAMjCgApQFQBEAADzewQAAAEAAQDy6QsAAHkA8oACAGR0CAIAAPPBEAAAAQABAADyAAEAaQA6MAAAAQAAAAAABwAQAAAAnBDTEsETwhPyE/MTFBQVFBKEbWVudAEBsLAUcQABc2HmIwoAxsr2wAR5AAGgcAAAEoOwMBSUAAE8eRzrjaGgcAAAEPSgsAAAFFigQAAADC+wYBJ6AAFAEIgDgEdmIAA7MAAAAQAAGgANAJkEAADRBAAAXgUDAG4FAQAIBwEACwkAAGIJAAClDwAAaRABACARAQDHEQMAlRIDAMkSAQABggKhEAAAFF+wAAjyAAFhZAACdWIAAPLAEQAAAQAAAAJkZAAA9LwRAAABAAEAAQAA8od1cmF0aW5nAgBhcwGAA6CQAAATU6AwAAAT76BQAAAUe2NwcgAGbGVzaXplAADyJBAAAAEAAPKAAQFwZXQBAIACBQADBqAwAAAQPo0uMn/SuyR4BMJBBaptdGJkZ2x2AAAOCRxrPdmawzueyHn9RgjxIpU2Mj+3podQ+HejqyHybjuK3HnOvVdEYfG3fhaBIXsUk0VLzfIFViswkFzkR29CG7Ijgy3Rt8ICJ3Ld3ejOTVbEwyApMG6lnsuRHQk4/V88tKAgAAAFlqBgAAATMx8LVdPbwlu8zw0cXDkLs7rRam+Aqymzww6cwutSw2FlaGlrbG1vcHJzdHV5MDEyOGJjZGZ2ADswAAABAAAPAW0ArAEBAO4BAAD9AQEADQIBAJUEAAC7BAEAwAQBAM0EAwBwBQEAfgUKAJsFAAANBwUAJQcCAGYHAABxBwAA1wcHAO8HAwDtCAAAGQkBAB8JAABbCQAA/wsAABsMAQAuDAAAOAwDAD4MAgBXDAEAtwwAAL0MAADTDAEA4AwCAIUOAACZDwMAoA8EAK8PAAC3DwAAyg8BANoPAQDdDwEAARAAAAQQAwAfEAEAbhADAIAQBACUEAMAnBAAAJ8QCACqEAMAxhAAAM0QAQDTEAAA3xAAAOgQAQD7EAEABREBAC8RAQA/EQMAUhEAAHIRAACbEQMAthEFAMIRAADGEQAAzxEBANQRAACHEgAApBICALoSAADNEgYA2hIBAOASAADvEgAA+BIBAA0TAQAbEwAALxMBAFITAABWEwEAWhMBAF0TAwBjEwEAahMAAHETAAB2EwAAexMAAIcTAQCREwQAlxMBAKUTAwDBEwEAyBMAANkTBQDuEwAA8hMDAAAUAQAUFAIAHRQBACwUAAAzFAUARhQBAFQUAQBeFAAAYxQBAGYUAgBzFAEAiBQBAJkUAgCdFAIApBQIAAEAADswAAABAAAHAAIAKwcDACYJAwABgAKwIBRPAAGwIBPmAAFhbwEIAPNgEQAAAQABAAGAAoAMDnGgWrBAEvgAAWR0Y4AEoEAAAAwGsHAUaQABYWVpdgELAPR+EwAAAQABAAEAAQUA9I8QAAABAAEAAQDzAQJjZAHgEAAAAYADYlgNgWQpsBAMCwABoDAAABCzY2VwAQMA9PYSAAABAJkBAQAjgQKgUAAAEn5ldBMAAqCwAAATMGFj8l4EAAD8BAEAAbBQDMIAAWP08AEAAAEAAQAgAAEEADswAAABAAAKAAMASRMEACcUBACcFAAAAAN0bHMAADswAAABAAAFAAEADQcFAAEuDAAAAAN5cGUAAPLSDwAA7gAA+lUBAHTzXAcAAAEAigDz1Q8AAKkA+wHyAAEAdPLnAQAAAQAAsoACAj4JUZ8n1G5zY3ABBQA7MAAAAQAACQABADMRCQATAAOgcAAAE82gMAAAEP1lZnT2BREAAAEAkwMBAAoAAQABCwGgQAAAFB9l8k0UAAABACOBArBQENQAAWN2AQAAOzAAAAEAAAYAAgBxEwAA2RMFACOIArBAFFEAAWVzAQ0BsEAUSwABZfUPFAAAAQABAAEAAQABBQA7MAAAAQAACAADAOERAgDmEgQADhQAAAEHAPToEQAAAQABAAEAAQMA9JsRAAABAAEAAQAACHN0aW1hdG9yAADychAAAAEAAJYBAAABBKBwAAAR57AgD7AAAWVhaWty9OMBAAABAAEAAQA6MAAAAQAAAAAACAAQAAAAbAfVB9YHQglDDO4P7w/wDwERNg8AAMHJI4//c3rnTi+GaibXoDAAAAwHL4WAsES4AAAAAgBFoQ0BeAAAADswAAABAAAUAAcAuAQBAOkGBQC4DAIAwBIFAO0SAQBmEwAAWBQAAAEAAWQYDfWZbG07MAAAAQAA7QACAJkFAADDBewAAQYA9KETAAABAAEAAQABDwD0jBQAAAEAAQABAPOEAmht8wABcjswAAABAAAHAAIA8wEAABkCBgABgAKwQBMmAAEzGjeut4dlbAEBAPSHEwAAAQAPAAEAI4ACoLAAABPvYWUABHRvcmUAAAH9EAAAABKFdGNlcnQBAbBwFB0AAXNpdoIAAMHLWPx+dZkdoPAAABN1Jfzic+W6oHAAABCcd3mNkwtWWYABgngWsEAUQQABAAAQAIAArh8A8gsCAAABAAGAArAQES8AAaCgAAAUX3N0AQEA9u4MAADmAlgAAQDPAgEAAQMA86sTAABsAAEAE4ACsEARUwABYWXzgAJsbQAEc2VlZAAA8mMJAAABAADygAECZ2FlAYACsFAT+wABoAAAAAj9Y3QBgAKgMAAAFCBr4sGaxmhhbwEDAPTcDAAA1QagAAEAAYACsBARMQABoEAAABKFZWkBBQD06BEAAAEAAQABAAGAAqDQAAAUfKBQAAAULGNuAAtrZW5saWZldGltZQAA8tITAAABAAAIbgAA9WgMAAABAAEAAQABAADygAIAYW8IFQAA8ukMAAABAADyAAIAZXT0tgQAAAEAwQABADswAAABAAAFAAIASgwBAJURAwDygAEBeWEADnJlY29ubmVjdGlvbmlkAAD0NBMAAAEAmwABAPQKFAAAAQBPAAEA8oACAGlyAQAA9HcTAAABAAEAAQABAgA6MAAAAQAAAAAACQAQAAAAwxHEEd4S3xLOE88TJRQmFKAUoRTzAAFv8mUJAAABAAACbHQAADswAAABAAAFAAEArgUFAAFqEQAAAARvbnNlAADyCwkAAFcA9GkQAAABALYAAQAIHwAA9NAPAAABAO0AAQAAAAJleQAA8mYMAAABAAAIBQAAAegMAAAAcgADAbBwE1QAAWVrc2k7MAAAAQAABgACALIEAwB1BQIA87MRAAABAAEAEoABAbAwFCEAAXRudgkAgIOFoEAAABHdsQAUbgABIYFt1YYDEIAIhEAGAQUA9C4UAAABAAEAAQABBQD0HhMAAAEAlAABAPMCAmN19TwQAAABAMUAAQABAAhXAAABWwcAAPPJDwAA3wABAPKCdWUBAXNuABByZGVyaW5ndGhyZXNob2xkAADyzhMAAAEAAAASYXR0cmF2ZXJzYWxhZGRyZXNzAADyiBQAAAEAAAAGZGRyZXNzAAD0+xAAAAEAvgABAAA6tQIBoAAAABFDYW5kARQA9agUAAABAAEAAQABAAhDAAD0bhAAAAEAAQABAAAACWF0aHN0YXR1cwAAAVsTAAAAAApwZW5zdHJlYW1zAAABkhMAAAAADGlkc2V4aGF1c3RlZAAA8m8TAAABAAAACGFsaWRhdGVkAADyRBQAAAEAAPIFZHJlc3MBAHb0thEAAAEAAQABAPSlEwAAAQABAAEAAAdhbmRvbmVkAADy2xEAAAEAAPKAAgBiZPoHBQFhY29wc27zCgcAAAEAAQDy6Q8AAJIE8oFvAgB0dgECAPPxCAAASwMBAAAJYW5kc2hha2VzAAABWhMAAADyAAEAaADyIhMAAAEA8gJzZQEAZPIIBwAAAQAAAYoCsAATyQABsAATywABNDYAA25jZQAAAWUMAAAA8oFyAQFlcrIAAgKwEBA5AAFldWltAPWUBAAACgDFAAEAlQcBCwD0WRQAAAwAIgARAAEJADowAAABAAAAAAAHABAAAAA0EzUT0BPREwoUCxRaFFsUE4ADsBAUIwABsGATgwABbnJ2CBUAAPXlDAAAAQABADsDAQABchMAAAADZXRhAAABCgkAAAAIWQAA8uISAAABAAHVEwAA8gADAGJtc/N2BAAAAQABAAFhDAAACDoAAPOwEQAAAQABAPVhEwAAAQCWAAEAAQAAE25pZGlyZWN0aW9uYWxzdHJlYW0AAAGYFAAAAAg4AADypBQAAAEAAAAEZXNldAAA87MRAAABAAEAAAAGdGFncmFtAAAB4RIAAADyAAIAYXIA89QQAAABAMgBABFkaXJlY3Rpb25hbHN0cmVhbQAAAYcUAAAAEoABAbCAFH0AAWl19gEAg2l2ZYWCoAAAABC0sHATwQABCkAYAAADtgEAgAIDoEAAABLGZXZsb3QAB2JzdHJhY3QAAAGEEgAAAPIDaW5kAQBhAQcHAAAACGsAADswAAABAAAIAAEA/gYIAPW4DwAA0QB5AgEAAQASgAEBsHASegABb2UACG5vcmRlcmVkAAABrxEAAAAIIQAAAWgRAAAA8gRvZW5kAQBlASEQAAAAAAlhbnljaHVua3MAAAHgEgAAAAAIYW5kc2hha2UAAPSrEQAAAQABAAEAAAgDAAABZxEAAADyA2FjdAEAZQGuDwAAAAADcm9yAADzHQwAAAEAAQAA8oACAHJ4AAdhdGFncmFtAADybRAAAMAAAAADdW5rAADyrA8AAAEAAAALcnRzZnJvbWZpbGUAAAHNEwAAAPKAAgBlaAACdWYAAAFaBwAAAAgGAAAB+hAAAADyA2JsZQEAZfIICQAAAQAA9h8AAImBoAAAABMpnxAMAABA9AgCAABrAgEAAQA6MAAAAQAAAAAACAAQAAAA9Qv2C14MXwxgDPIN8w0qE0ATAQEA9qkSAAABAHIAAQBRAQEAAIR0aXB2AgDQABFjAAHQABFlAAE0NvICdXQBAGHyYwwAAAEAAPKBaAEBb2V2AACAAwJ3gi1xavegcAAAEQpjZHNtdPbfNADJyUFVbj1FlSyusCVLWkZVpjZbLLBwEd8AAcAJVAMAgqosADswAAABAAAyAA0ApAUAAOcGAQB4BxMAIAwAANUMAAD3DAEA8Q8NADAQAQD9EAAAVREAAF4RAQDcEgEAGxQBAAEJAPONEgAAAQABAAEBAPPIBwAAzQcBABOBAqBQAAAQjmVzAQQBsDATIAABc/OwEAAAAQABAAGAAqAQAAAP07AACOIAAWRlAQIA88oMAAC5BgEAABRvbm5lY3Rpb25pZGdlbmVyYXRvcgAAAXAUAAAAEgJvbQECsEAUAAABoFAAABHRY2JmAf0GAAAAEoACAeZQAAAEsX5QjWI4UGRnc7YTAIDBzHuFgCuHprAAB0YAAWiMHh0PP11kbB3sjk7J2WtgugOq5Xwb3KAAAAAEwgAACACAU6ZKAADG0GbGlm0cv23I2o3hcGcnk9BN/XHHshCCizM4bpYkLBDsN8qjG1JNFG5pbxFkZe1lhDN9l5/WtWcY7Pu3ZUYC61O04mFAWFJmLEO+9YdB4CJgQ/wNyjRsDDO1Lm6AHNW2smpSJRJOO0K5gFK7lm5B3y9avCjXtfWryB9LqPbL7TQZHAVq+0AEGAaEW6dpADswAAABAADCAF8AgwACAK0AAwDpAQAADwICAJ8ECwCvBAEAZQUAAKMFAAClBQEAtAUCALAGAADlBgEAEwcEAEUHAABMBwEA5gcAAPwIAAABCQEAMwkBAGEJAAAGDAAAFQwBAB0MAgAiDAAALwwAAEEMAQBEDAAASAwBAE4MAgC+DAAAzwwBANwMAADoDAIA7wwAAPoMAQCTDwEAlw8BAJ0PAAC0DwEAuQ8BALwPAQDGDwAA0w8AAAIQAQAmEAAATBACAFMQAQBYEAEAXRAAAGUQAwByEAEAfRAAALQQAADqEAAA9BAAAPkQAQAHEQQAFREBAEURAgBTEQEAZxEBAGsRAgDLEQEA1xEBAN4RAgDkEQAAmRIAAKISAQCnEgEAqxIDAPASAQAAEwEABRMBAAsTAQAkEwAAJhMBADMTAAA2EwMAOxMCAJ0TAQCrEwAAsRMAAN8TAQDmEwEA8BMBAAwUAAAXFAEAHxQAAEEUAQBIFAQAURQCAGkUAgBwFAAAfBQAAIsUAAABgAKgIAAACVqgMAAADFNlcgEFADswAAABAAADAQIA0QwBAIgOAQEAC2VydmVyY29uZmlnAADythIAAAEAAAALbGllbnRjb25maWcAAPK0EgAAAQAAMoACAbAgEnoAAWNzYvKAAQFjdhKAAQOwQBDEAAGgEAAAB+FDFOTVEAFpYWRlAANhdGgAAPKpEQAAAQAAAAJudgAA8isRAAABAAD6hAIAZXAACWxlZmFjdG9yeQAA8tkRAAABAAAABWN0b3J5AAD0zg8AAAEA7QABAADygAMAYWlyAAJpcgAAAWEJAAAAAAVvbmZpZwAA8uMMAAABAADyAm9nAwBjZGYBcgQAAADyAAIBbHVwADswAAABAABbAAMACwBZANkBAAAUDAAAAQABB9G3v0HuYfJlEQAAAQABAAFkAGZv2CZh8mMRAAABAPOAAjQ2AQAA9MAEAAABACcMAQABCAD0XRMAAAEAAQABAPOAAnBzAQgA9IcTAAABAA8AAQAjgAKgoAAAFBVhdAEEAaAAAAATMHP0tg8AAPMDAQAEAPMCAXMBsAYAAAEAAPQrBwAAAQABAAEA8YUCl5hobQAIaW9sYXRpb24AAPIxEwAAAQAAEgJvbAEBUxu98XZkdnPyXwkAAAEA8uITAAABAPIAAQBjAAFTDAAAAQICoLAAABQ9oLAAABQ+Y3PztRAAAAEAXgAADG5pdGlhbGhlYWRlcgAAAQwUAAAAAAVlYWRlcgAA8tcRAAABAADygWQCAGhp8oJjdAEBZWnygAIAZW9wgAEDAJwJsEAUQQABdGJwdvGCAqKjaG0ACmtjczhrZXlkZXIAAAEzEwAAAAAFZXlkZXIAAAH5EAAAAPKDYXRlAgBrcAAEcml0eQAA8l0JAAABAPLJEAAAAQAAA3NvbgAAAasPAAAA+kcBAGrzcgUAAAEAAQAA8oADAW5vdmEAjHJyZWRhZGRyZXNzdgIA0AATyQAB0AATywABNDYSgAEBsBAQOQABZWkSgAEBsFAUIQABZnPygAMBZWlveAGAAV8fZsp5W3QACWlibGV2YWx1ZQAAAfgQAAABGhMAAPIAAQBzAAG3DAAAAARyaXRlAADzqA8AAAEAAQAAAAZhbnNtaXQAAAEqEQAAAAAFbWVvdXQAAAG7EAAAAPKAAgBpcgAGdXRkb3duAAABKREAAAAAAm5kAAABYgwAAADygAIAZWgIDgAA814MAAABAAEAABrDAgDnYAAADGFhYwADZXh0AAD0WgwAAAEAAQABAPI9EQAAAQAABGx1c2gAAPKmDwAAAQAAAA1uZHBvaW50ZXZlbnRzAAAByBMAAAAABGxvc2UAAAGlDwAAAPIBbIiANCAXOzAAAAEAAA8AAgAHAgAAYwQOADswAAABAAAJAAEA3wsJAAGBAqAgAAAQ37BQEqAAAWFvAQMBoAAAABPIcwHeEAAAIoABAcAAAAAH5nJ2UgJudAECsFARUAABZWNzADswAAABAAAIAAQA9QgCACMJAQBVEAIAhhIAAHYCAICDgzPhQuoqcLAQE+YAAQAJAgBgAQEAAPU9CQAAAQABAAEAAQAIIgAAAVkMAAAA8gABAHA7MAAAAQAABQACAAUCAQBfBAMA8qASAAABAPIAAQBnAY8AAADziAQAAHsIAQDygAEBbmQBAQA7MAAAAQAA7QACALwFAAD1B+wAAQABoEAAABB9ZTswAAABAAAHAAIAYAcFAO0HAQABBAD0lBAAAAEAAQABAAGBAqAgAAARALBAE1YAAWVp84ECZXQAGHN0ZW50Y29uZ2VzdGlvbnRocmVzaG9sZAAA8qAUAAABAAASgAEBsEAUlAABaWUAC2lwaGVyc3VpdGVzAAABMBMAAADyAAEAYwFeBAAAAVoJAAAygAICoJAAABRTsEAQMwABZnNhYgASZW50b3ZlcnNpemVkcmVjb3JkAADyIxQAAAEAAAAJaXNiZWhhdmVkAADzYBEAAAEAAQAAAApjb21wYXRpYmxlAADzsRIAAAEAAQAAAAZlbnRpdHkAAPUkEQAAAQABAAEAAQAA8oACAGRu+kwDAGltcwDyCxMAAAEA9gEAAIKFfn7hbUbRaJBkVx46EAABDCECADowAAABAAAAAAAOABAAAAD+CDkJOgk7CTwJCAwJDJEPkg+7D9IPwBDlEHMTdBMBCAD1wxMAAAEAAQB6AAEAAQEA83sFAAABAAEAAQsA9oUTAAABAKwAWAAYAAEAAQMA87ESAAABAAEAAQ0A810UAAA1AAEAAA1uc3RhYmxlb3Jwb29yAADy5hMAAAEAAAAFYWNrdXAAAPSvEgAAAQCoAAEAAAAIdmFpbGFibGUAAPRtEwAAAQCIAAEAADIBcwIBsEATPAABYWJl8+EMAAABAD4D8ocSAADUAAAAAAA7MAAAAQAABgACABsMAQCgDwQAAPKCYXQCAHN1AAZsb2NrZWQAAPRrEAAAAQC2AAEAAPIAAgBidPJwBQAAAQDzRhQAAFcAAQAAD2lyZWNvbm5lY3Rpb25pZAAA9AoUAAABAE8AAQAACGoAAPRpEAAAAQC2AAEAAAAQdXJjZWxpbWl0cmVhY2hlZAAA8m4UAAABAADygAIAb3A6lwIBsIATgwABc3RjAA5ic2VydmVkYWRkcmVzcwAAAfUTAAAAAAxjb25uZWN0aW9uaWQAAPRrEwAAAQCtAAEAABKAAQGgcAAAFC13ZxKAAQF/3Lue8J5lbwgbAAGwYBKpAAFy86wFAAABADgC9A8MAAABAO8DAQABgAKgQAAAE8CwMBLcAAFhcwADZW50AQDQAAzgAz9z9BcMAAABAAEAAQAACEYAAPIVDAAAAQDyRhEAAAEA8oACAXJ2bgAJZHNibG9ja2VkAAD01REAAAEAPgEBAAAAB2FsbGVuZ2UAAPT2EAAAAQCwAAEAAPKAAgBoaQhFAAABWQcAAAAAAmNuAADy3gwAAAEAABIBawIA0AAMVwABZXPyVwcAAAEAAAAFZWFzb24AAPMCEwAAAQABAADyBWFuZG9uAQBy9MwPAAABAOwAAQAA8oACAGJj9j8BAIqEoFAAABFvfwjlid1vsEAUnQABF2ELgJQA9QICAAABAAEAWAIBADowAAABAAAAAAAXABAAAAAGCREMVQxWDFkMxAzFDMYMxwzIDMkMgA5fEGAQsxBcEV0RqRGqEe8TOxQ8FE8UUBTygAEBaGkBAAA7MAAAAQAAEwABAHgHEwABhAKwQBRRAAFpc4qzwkFlcyOAAqAgAAAMymJtAAJyZAAAAd0MAAAAAAVlY29kZQAAAfUQAAAAAAJtcAAAOzAAAAEAABMAAQALEBMAALKDaWFshIDnEAAADBQcQAAAB250ZXJyb3IAAAH0EAAAAAAHeXRlc2l6ZQAAAYMSAAAAOhICAbAAEyQBZ2JpcvJuBQAAAQAA8oACAnN0YWUABG9tdHUAAPKeDwAAAQAAAAJuZwAA8lUHAAABAAASgAEBoAAAAAWmaXLygAIAZHQBAgD0oxEAAAEAAQABAPMAAWX0qhAAAAEAAQABAAEDAPSfEQAAAQABAAEAAAhocmVzaG9sZAAA8t4SAAABAAAAAmV5AADyEgwAAAEAAAAKZWNvZGVlcnJvcgAA8gATAAABAADyAmV0AwJka3Rsc/P6BgAAAQABAAAABm5ncmF0ZQAA8gkQAAABAAAygAIBsAATFgFKaWtl9g4AgISEsBAHTAABDAAFAAlQ9pEhAMXNoJAAABMZaQL7q8QHsEAI4gABoJAAABMzT8evutCCe+gwx/NzPRLhg3g+Zw8KkJG5KF3kPIjtQESQAAAzLw8AOzAAAAEAAEYADwCZABMAqwEAAO0BAADzAQAAGQIGAIkEAgCXBQEA0wYAACIHAgDhBwAAUQkEAAsQEwCvEgEAWBMBAJYUAQABAAA7MAAAAQAACAACAIAAAAAwDAcAEQACn6AgAAAH4XB1AdkBAAABAwD1AQkAAAEAugYBAI0EI4ACTjGCPsQqbnIBgAJfxKkEGaOgIAAAEGJjaQEBAbCwFG4AAWw7MAAAAQAADAADABkHCADzDAEALhABABOAAqBAAAAMB2N0AQABTWR2zfWBafKWFAAAAQABAgKgIAAAEcKgMAAAEoxiaAGyDwAAI4ECoIAAABMvYXABhAKwABFjAAGwABFlAAE0NgAHZmJvdW5kcwAA8soPAAABAAAIdQAAAZETAAAAEoRvaW5nAQGw0BSUAAFoYvIAAgFnb2EAOjAAAAEAAAAAAA4AEAAAAA4JDwkQCWMMZAzHD8gPuxB3EdQS1RLUE10UkhSTFFYOAIABB6AQAAAM27CQEWkBIT4/ENXlUHQzZ2xucHJzAQMA9DkJAAABAAEAAQABAAEPUzjyucdjAVMMAAABAAGw8BSWAAFrOzAAAAEAAAgAAwD9EwAAAhQGAC0UAADzAwFk8r4SAAABAAGCArBwFB0AAaBAAAAQ/WlzE4ACsGATnwABZW8BAQOgcAAAEcWwQBOsAAGgMAAAD7NkbHPyYRQAAAEACFoAAAHcDAAAAPICZXIBAGXyrwQAAAEAAHYNAACBh3maAXj9U0au4emcZoAAABVhBADzigAAAFABAQABBADzuBIAAAEArADzAAFjOjAAAAEAAAAAAAoAEAAAAM4Pzw+8EL0QSBHZEdoR9hL3EpAUkRQBgAKgQAAAEH5SK0Rm3ANucAECAWQYDfWZbG07MAAAAQAA7QACAJkFAADDBewAAYADoEAAAAwGoBAAAAwOsHAUaQABYWV2AYACYlgNgWQpoDAAABCzY3ABBADzyQ8AAN8AAQAjAAKgoAAAE1JkcTowAAABAAAAAAAIABAAAADnBugG9wz4DDAQMRD9EF4RXxEBBwD0cxEAAAEAAQABAAEBADswAAABAAAIAAIAig8GAEETAQDzgAJkbiOAArCQEssAAWVvAQAA9F0JAAABAGsHAQAjgAKgEAAAE2loeQAGZHN0Y2lkAADytxAAAAEAAPKAAQFndAGBAqAQAAATKqBwAAATcmFj8wEBcvKPEQAAIAAICwABsIATzgABdAEHCQAAAPqrAQFpZfIAAQBlAY4AAADz3QwAAEYHAQD2awAAgolqUiUSTjtPjEnFI/QIAQAUlE0AOzAAAAEAAGkAMgCfBAsAZQUAAKMFAAD8CAAAAQkBABUMAQAdDAIAQQwBANwMAADoDAIA+gwBALQPAAC5DwEAvA8BAMYPAABMEAIAUxABAFgQAQBdEAAAchABAH0QAAD0EAAA+hAAAAcRAgBGEQEAUxEBAGcRAQBrEQIAyxEBAN4RAgDkEQAAmRIAAKISAQCnEgEA8BIBAAATAQAFEwEAJhMBADYTAwA8EwEAnRMBALETAADfEwEA5hMBAPATAQAfFAAASBQEAFEUAgBrFAAAcBQAAAEHAqCwAAAUPaCwAAAUPmNz87UQAAABAF4AAYMCBnQzPQ9pMo6wShm0aG3zgAJpbwACb24AAAGrBQAAAPIAAQBp84sAAAABAAEAAfwLAAAAEGlyZWN0aW9uYWxzdHJlYW0AAAFlFAAAAPICbmkBAGTyVAcAAAgCAAADYXRzAAAB1BEAAADyBnRyZWFtcwEAdAABkhMAAAAFbnN1cmUAAPLcEgAAAQAA+jkBAGXzBgkAAE8DAQDyXxAAAAEAAApvbm5lY3Rpb25zAADy2hIAAAEAAAgGAAABWRQAAADyAWkBAGTyqAUAADwCALYDAACGgPZABakAARaACvNZBAAAAQABAAASgAEBoKAAABRTbnI2AgAAgoWgsAAAFBWgsAAAExpJOEuTmLcyTPYZJ+8QAAQA0AMAOzAAAAEAAAoAAwDzAQAAGQIGAIkEAgABhAKgEAAADNugMAAAEGIzcwEDAPMCEwAAAQABABOBArBAEyIAAWFmAYACsFAUcQABoBAAAAv8ZnABAgA7MAAAAQAABgABAEkRBgABAgGgQAAAFB9l8k0UAAABAPOAAmlvAYACsEASnAEWoCAAABENYWkBgQKwABLHAAGwABHPAAFlcxOAAqAwAAATlmFyAQgA86QSAAABAAEAI4ACoBAAABN1ZHMAFXVyaW91c2Nvbmdlc3Rpb25ldmVudAAAOzAAAAEAAAUAAQCBFAUAAAGBAqBAAAAQi6BwAAAUPmFlIgABAfdAB+IAAW5yAPYLCQAAVwAHBwEAtgABAPIAAgJlcGl0APXaEgAAAQCIAAEA+gAIKQAA9KMRAAABAAEAAQAAAANvc3QAAPSfEQAAAQABAAEAAPKDa2V0AgBscxKAAQGwEBQ7AAFjdBKAAQGwYBPiAAFhcgAHdXVwZGF0ZQAAOzAAAAEAAAcAAQAYEQcAABKAAQGwYBHlAAF0aQAGdGVydmFsAAABCBAAAAASgAEBUszaI47ybmQBAwDzuxIAAAEAAQAjgAKwMBPwAAFjaQAFdGJ5dGUAAAEXEQAAABKAAQGwIBMoAFpzbRKAAQKw4BPqAAEL31J7XT9yZGfygAEBaWEBAAD0DRMAAAEAWQEBAPMDAXM7MAAAAQAADwADAM0RAQCJEwcAgRQFAAEFADswAAABAACMAAIA3BAAAO0RiwABggKwIBBjAAGwQBFeAAFsc/OBAmt1AQMA9csRAAABACQBAQBiAQEDADswAAABAACLAAEA9A2LAAEAADswAAABAAAFAAIA7gsDANsRAQABAgA7MAAAAQAABwABADAMBwAABWRhY2tzAAD0BBAAAAEAAQABAADyAAEGbmJkaXJ0dgA7MAAAAQAAkQAFAAECAACYBAAA0gSLAOMQAQB+EQEAAQkA82MUAAABADsAAYECsIAUcwABoGAAABSbZHMXAQCFhbDQFJ0AAaBAAAAURrBgFBYAUAKQCgALZXN0aW9uZXZlbnQAADswAAABAAAHAAEAiRMHAAASgW4BAbDQFJAAAWd0AAAAAPIFCQAATwM7MAAAAQAABQACAJoSAQDyEgMA8gJzZQEAZAA7MAAAAQAACQADAMcRAwCVEgMAyRIBABKAAQGgkAAAFD1vafKAAgFsb3UABHl0ZXMAAAFbCQAAAAEFAPRZFAAADAAiABEAI4ACoGAAABNpZ3MADnJlcXVlbmN5dXBkYXRlAAD0LhQAAAEAAQABAAD6LwEAZjswAAABAAAHAAEA8gYHAABSgAECsJAUOwABY2Js9p9NAImKsAAUlAABKawMc2/BHDZSTPkTazVlmTcYsFATDwABN5ECyGiVADswAAABAABPAB8AqwUAAP4GCABeBwEA5AgFAPQIAAAiCQAA/gsAAEwMAQDMDAIA9QwBAIEOAwCGDgEAqw8AALgPAADMDwEA5A8EAIkQAAC1EAEAuRABAPEQAAAUEQAAMxEJAHERAABzEQMAxREAAAITAgAZEwAAMRMBAEMTBQBTEwAADRQAAAEBAqDAAAATQKBgAAAQfmNy8r4EAADOAAGAAV1Mumb3bW7yAAACZWkBCQAAAAAIIQAA8vAGAAABAAASgAEBsDAUcQABc2wSgAECsFAPygABFwh/h7b+ZmJ1AYQCsHAT+wABsDATTgABZXMABGFkZHIAAPKtEgAAAQAACAAAAPKrEgAAAQAA8oJwdgIANDYAC3h0ZXJuYWxhZGRyAADzqxMAAGwAAQAAAQYA9JkUAAABAAoAAQDzAgFyAfUTAADyA2RkcgABZTswAAABAAAFAAIAZRADABURAQAA8oVlcnZlZAMAYWVpkoABA6CwAAAT7wJ4ksmVVnNhZXUAAAgPKRXEbpieTV9ClDEED258FK/0bAI+LAsVMBBrazGYFVCS7cB2JzHnVeI7QhQlq4r6oDAAAAxDPZmNo2DZHNU8GPFAXCugFPkjSloZMVwEOBMBdEzZFp77P+IQDFe0+Ma0H5qONlTqMtXiCBoVHHMyHeNnUj0JiIeBeAOYof/lMtnaHQMVFyYRNvrwYmZrbnBydHUwYWNkZWdpbG1vcXN2d3gAOzAAAAEAAIcCEgD8AQAACwIBAGUD7AC+BAAAjAUAAKEFAQC7BgoAUQcCAGoHAABZCQAA8grsAFMMAADwDAAA9A2LADcQAQBSEAAAjxADAGwUAQABAAOggAAAErqwUBLgADuwcBQUAAFhY3M7MAAAAQAACAACAH4AAABGCQcAAQEBoQAAABRlZPJUBwAACAIjgAOgAAAADCShYAAAFF9tbnMVAACBhUywQBMiAAGgUAAAEoWwoBNUAAGwcBHfAAEhCAoBAQFLAVM8y6V18t8HAAABAAEJAPO4EgAAAQCsAFOAA7BgEeUAAWVpdAEBAPS1DAAAAQBEBgEAEwADoDAAAAdrCc647imHZW5z9ZYEAAABABULAQCOAAEAAaEAAAAUWWTyqAUAADwCE4ADoFAAABMloDAAAAlbaWx5AQABoKAAABNSZPLnBgAAAQABBAGgkAAAE3Jy8o8RAAAgAPOAAmRlAAAAAbAABK4CvW47MAAAAQAABgABANwBBgAAAAlpYWJsZXBhdGgAAPJcEQAAAQAAAA5ub3VnaGFkZHJlc3NlcwAAAS8TAAAAEgABA7BgE6wAAbBwE58AAUau4emcZmVhY27zigAAAFABAQAAAAJkcAAAAeEHAAAAAANvdG8AAAFTDAAAAAACcmYAAAFaCQAAAPKAAgBlcvIAAgBwdQHZAQAAAAh8AAABPhQAAAAIfAAAAT0UAAAA8ghvdGVjdGlvbgIAY3PztRAAAAEAXgAAEoABAaAAAAAL/HJ0AANpemUAAAFiEAAAACKEZXJvdQIA4yAAAAzbM3MABHRvcmUAAPJeEQAAAQAACHkAAPJjEAAAAQAA8oRva2VuAgBsc/IAAQB0AQECAAAA8oACAGV6ABFuaXRpYWxjaXBoZXJzdWl0ZQAA8qkTAAABAAAIMQAA8gEJAAABAAAAEmVmYXVsdGNsaWVudGNvbmZpZwAA8uQTAAABAAAAEWRhdmFpbGFibGVmb3JwYXRoAADyTxQAAAEAAAATcnRpZmljYXRlc3ByZXNlbnRlZAAA8iEUAAABAAAygAIBsEAQ8gABZWlvABJwcGxpY2F0aW9ucHJvdG9jb2wAAPLiEwAAAQAA9n8BAMvDsFAUcQABoLAAABMvQEdoGgAIgAQA9FEHAAABAAEABgIBBQD0BBAAAAEAAQABAAEAAqAwAAAUWKBQAAAT4WNzOzAAAAEAAJcABwCpBQEAZwcAAMgHAABnCYsA7gsDAJUPAQDbEQEAAYABePWfYtqTdAAHcnR0a2V5cwAA9JsRAAABAAEAAQAA8gABADHyVwQAAAEAOzAAAAEAAAUAAgBaDAMAPREBABKAAQGggAAAEQB0cAARaXRoYWJzdHJhY3Rzb2NrZXQAAAFDFAAAAAADa2VuAAD0AwkAAAEATQMBAAAAA21lcgAA804MAAABAAEAAPKAAgBpbwAFZW5kZXIAAAGdDwAAAAAFb25maWcAAPLyEAAAAQAA8gFvAQBj9FEHAAABAAEABgIAAAVlaXZlcgAAAbQQAAAA+skCAGNuAAx4cGVjdGVkaXRlbXMAAPJWEwAAAQAACFsAAPTSEQAAAQA/AQEA9GsTAAABAK0AAQDyCW9ubmVjdGlvbgEAaQHxEAAAAPIAhoAUABc7MAAAAQAAIgABALYBIgAA8YACZWZrdQg3AAABsxAAAAAAA2ludAAA8v4SAAABAADyBWhhbmdlAQBoAPPsEwAAAQByAPKDb3JrAgBjcPKAAQF3bwAPZXlleGNoYW5nZWdyb3VwAADylhQAAAEAAPIAAQBrADswAAABAAAIAAMA/RMAAAIUBgAtFAAA8gZvdGlhdGUBAGQA8r4SAAABAPSfAADEyAADCRZpGRprG13XjbmQ4LAQES8AAXAAEAAygEOICQA7MAAAAQAAkQAFAAECAACYBAAA0gSLAOMQAQB+EQEAAYkCBnQzPQ9pMo6wShm0aG0BBAD0WRQAAAwAIgARACOAArBwEqIAAWl0BwEAgIUc7RNMnU+wQBFTAAGgYAAAE2mgIAAACSVRAQIBDgD0LhQAAAEAAQABAPMBAWY7MAAAAQAABwABAPIGBwABgQKgAAAAE8CxIBRxAAFkcyOAArCAFDsAAWFsAQIDsCARMQABoFAAABHdoEAAABQgbHN28hsUAAABAAECAPPmDwAAAQABAAEBADswAAABAAAGAAEAphQGAPMGAWX0VBQAAAEAMwABAAAGcGRhdGVzAADyahMAAIoAAAAEbmRlZAAAAQkUAAAA8gRvdW5kAQBlAPJ/FAAAAQDyCHJhdmVyc2FsAgFydWEBYRAAAPLYEgAAAQDygAECdGl1AAVsYXllcgAA8gIQAAABAAAaDgEBsDATcwABZHQA9u4MAADmAlgAAQDPAgEA8oACBG10YmNscAAMbmF0dHJhdmVyc2FsAADy2BIAAAEAAAAABBRGI3453CNfwm9XdiwtuD0FYzs6/tJd4dMEvt/wMJBeI5G+FNtvkgCG15dR9lBW7sEII2PQMrhgqEb3pqVzJETk3RZaNblr7ul7sgP2yQQw+ozucQNZs/t/gCFxDjpHrtZVBnE57SRhgUU5fm9SgAa8JPkRRKWXC7oyt0DQd2GwUBMPAAE3O+b5/0ZYp9JpN+0wYWVvYmNkZmdoaWtsbW5wcnN0dXZ3eXoAOzAAAAEAAI0AOQAHAAAAjwAAALEAAwCuAQMAtAEAAOcBAQBSBAQAWQQCAIIEAQCIBAAArgQAAHsFAgCLBQAAkwUBAKsFAAD+BggAXgcBAGsHAABtBwMA8wcBAOQIBQD0CAAAAwkBACIJAADzCwAA/gsAAEwMAQBRDAEAzAwCAN4MAQD1DAEA/QwHAIEOAwCGDgEAqw8AALgPAADMDwEA5A8EAIkQAAC1EAEAuRABAOYQAQDxEAAAFBEAACkRAAAxEQsAcREAAHMRAwDFEQAA6xIBAAITAgAZEwAAMRMBAEMTBQBTEwAAfBMBAA0UAAABBQDz+BMAAAEAAQBDhAOwkBR9AAGgAAAAEp1iZHcTgQKgMAAAD+ljbQGAAqAQAAATzaBQAAASi2xuI4ACoEAAABHRYWkBBgD0BxMAAAEAkQABAPMDAWL07RAAAAEAkQEBAAGAArBAEZkAAaEwAAAUcGhvAQIA8z4MAAABAAEAE4CEoCAAAAwvsBAJLgABsDAUAAABAAlIAQEBsIATUAABdfI3CQAAAQAAAAAAAbUBAAA7MAAAAQAA/wAJAJoFAADrCAAA8QgAAPsLAAA8DAEA2AwAAAUN7AC3EwUAdRQFAAALdG5lZ290aWF0ZWQAADswAAABAAAGAAEAAhQGAAASgAEBoIAAABQtb2USBnRpcGF0aAECoGAAABPAsEAUnQABbmVwAREMAAAAtgAAgAIDsBAJNQABsGARmQABbHQzNm0BBgDz1hAAAAEAFQEBAgGgQAAAEd5l8/IMAAAMBAEAAA1zY292ZXJ5Y29uZmlnAAD0LRMAAAEAmAABAAASgAEBbQomrUFiaXASAAEBSwFTPMulZHUAOzAAAAEAAAoABADfBwEAng8BAOEPAgDqDwMA8gABAnVpbwA7MAAAAQAA8AABALcA8AABBgDzYBEAAAEAAQATgAKwQBHlAAFibQEDAbBAFGEAAXTyvhMAAAEAI4UCoEAAABPuYnMjAAOwQBDoAAGgUAAAEVVhYmb3+ggAAAEASABrBwEAYQABAAAMZXNldGludGVydmFsAADyVBMAAAEAAAACdHUAAPLfBwAAAQAAAAltdW1jaGFuZ2UAAPKZEQAAAQAAdgEAAIODsBAM8ANioFAAABHkABEBYAAEAbQBAAAAAAZyYXRpb24AAPJMDAAAAQAAtgAAgAIDsAAJDAABBdqr9TrCZ25kc3QBAwDz2A8AAAEABAEBAwA7MAAAAQAACgADAFcRAgChEwMAjBQDAAEEAPTzDAAAAQA6AwEAMwADsDAM0wABb3N0AUcUAAABAAD0JgkAAAEAAQABAEMAA6BAAAAUe7AgEwUAAWVyePRODAAAAQABAG4AAQgA87gSAAABAKwAAYACsLATnQABoDAAABHebnIBAgDzDgkAAAEAAQABBgD0WxAAAAEAsgABAFMAA7BAEAIAAWlsb/MODAAAGgdaAAECADowAAABAAAAAAAHABAAAAAIDAkMkQ+SD9IPwBBzE3QTAQAA9CoJAAABAAEAAQABgQKwQBRRAAFpc4qzwkFlcwADaWNzAAA7MAAAAQAABwABANcHBwDy+BIAAAEAVgMAgAEFoAAAAAkKsHARwwABcmFlaHh51uIAAMHLoJAAABNToBAAABFWGQqT4aAHUHnw7aqXoDAAABPvAAAAAgBHvAkAOjAAAAEAAAAAACIAEAAAAGYFZwVcB10H5wcbCU8JUAnZDNoM7gzyDLAPsQ/UD9UPLBAtEH4QhRCGEIcQiBDHEMgQ/hD/EN0ReRL8Ev0S0hPTE/4T/xMBAgA7MAAAAQAACwACALcTBQB1FAUA8wgBbTswAAABAAAMAAMAUhEAAM0SBQAzFAUAAARldHJ5AADySgwAAAEAAAAHcmFnbWVudAAA87AQAAABAAEAAAAGZWZyYW1lAADy2QwAAAEAAPKAAwBiZnIADWRwcGF5bG9hZHNpemUAAPPDEwAAAQABAPI/FAAAAQAAD3JhbnNtaXRzZWdtZW50cwAA8vITAAABAAAIVQAA9O8QAAABAJEBAQAAAANpZGkAAPRaEQAAAQB7AQEAAPIAAgBidQEBEAAAAAADYXRhAAD07RAAAAEAkQEBAADyhHJlYW0CAGRzAAJ6ZQAA8/8IAAABAFgAAPKAAgBpdAAYb3RlbmF0dHJhdmVyc2FsYWRkcmVzc2VzAADzqhQAAAEAAQAAAAxlaXZlc2VnbWVudHMAAPLBEwAAAQAA+pUCAGNtAAZlYWNoZWQAAPKpEgAAAQAA8gVhdGhpZAEAcvQPDAAAAQDvAwEAAAAVdXRnb2luZ2J5dGVzcGVyc2Vjb25kAADylBQAAAEAAAAGY29taW5nAADyrhAAAAEAAAAJbGV0aW1lb3V0AADy1BIAAAEA9NQTAACJADUAAQDygAIAZG4ACnNvc2VnbWVudHMAAAHTEgAAAAAHcmFtc2l6ZQAAARETAAAA+gwBAGf0TwcAAAEABgIBAAAACW5pc3RyZWFtcwAA8mMUAAABAAGfFAAACGQAAAEWFAAAAWYUAAAADXVsdGlwYXRocGF0aHMAAPKdFAAAAQAAAAhpc3RyZWFtcwAA8nMUAAABAAAa4gEBoGAAABSbZHP2AACJb25jdXJyZW50BAGgQAAAFEZibXN1cAAHY2tkZWxheQAA8hIRAAABAAD2fwAAi4GwsBMgAAFNwQ8gAAD0hgAAAAEAAQABAAAABmZvcm1lZAAAAQ4MAAAACFwAAPVSBAAAAQABAAEAAQAA9gQAgISEsDAHSAABVzthyRYGYCXBJclRAAlgBCAF9h8EAMXNoCAAAAkysJATNgABVK7HahPeOeJHwkZpAwLMkwwFYE6Ud5aksHAUaQABoDAAABNcQEQABoALtxkAOzAAAAEAAGUCIgD6AQAAIAIvAbcFAwD9BgAAKAcBAGkHAADuCAIAIAkBAPMJ/gANDAAAIQwAACQMAADlDAIA6wwCANAPAQAiEAEAJxABAEUQBgBtEAAAdBAAAI4QAACTEAAAvhABAMQQAQAtEQEA4RIAAGkTAAByEwAAlhMAAL0TAABZFAAAZRQAAIcUAACYFAAAAQMA86gPAAABAAEAAQQAOzAAAAEAAA0AAQCBEQ0A84ACaXIBgAKgMAAAEFqwcBMxAAFhaQEAAqAwAAAMJKBwAAARxmV281oQAACeACICE4ADsFATagCKsBAPpgABZXBzAQEA8+4PAAABAAEAAQMBoEAAABQJZfJ/FAAAAQABAQDzXgwAAAEAAQATgAKgEAAADGFhY3OAhKBQAAAP1RBADAEJAPSHEwAAAQAPAAEAE4ACb0W/Ka29bW4BAgA7MAAAAQAABQACAFoMAwA9EQEAI4ACsTAUpgABYWUBAgD06g8AAAEAAQABAAECADswAAABAAAIAAEAnxAIAPMAAWUBFAIAAAGAAqBAAAAUDKBAAAAQXmVvAQABoFAAABNpbwEeCQAAAYADsDAPpgABoEAAAAwOoJAAABRYbG9zAQEAOzAAAAEAABMAAQALEBMAg4CEoEAAABLvsJATqQABoDAAAA+lgBkAAYADsLATJgABsBATrAABsDANAwABY2VzAQEBoHAAABMqZQHdDAAAAQgA89YQAAABABUBE4ACoMAAABQVbXABAgD3MhAAANoAtwIBAAEAegABABOAArAwEM4ApGl08wEBc/JxFAAAAQAAC3BtdHVkcHJvYmVzAADyhxMAAAEAAAAFY2tldHMAAPSqEAAAAQABAAEAAPKAAgBhbAhdAAD0mQ8AAAEAAQABAADyAAIAYnAAOzAAAAEAAAoAAgBJEQYAnxEDAAAPcmVkdWN0aW9uZmFjdG9yAADy8BMAAAEAADKAAgEwbdl/AXlzdGUAA3lwZQAAAf4IAAAA8gABAHTy/wEAAAEAAWgHAAASgAEBIrAnkKJHZ2UBggKwIBErAAGwMBGpAAFlcAEFAPTODwAAAQDtAAEAU4ADsJAR2QABYWlyQgAAA7BQDOMAAaAgAAAJYWNkZvKyAQAAAQA6MAAAAQAAAAAACAAQAAAAcgTvBhIJEwljEGQQ1hDXEOwRAAd5Y2xvc2VkAADy6xAAAAEAAAAAAAD1UQkAAAEAAQABAAEAABKAAQGgoAAAFHtwZgAFb3N0djQAAAFeEAAAAAhaAADylw8AAAEAAPYAAAFsBAGxQBSmAAFhaGlsbvJsBQAAAQAAEoABAUUTxz1762Fr9h0AAISFoEAAABRKRCACEUARAPJsFAAAAQABAQFwU217QU9pAU4JAAABAAGgQAAAFB9l8k0UAAABAAECAPTCDwAAAQABAAEA84ACaHQBAAD1UQkAAAEAAQABAAEAAQYA9BwTAAABAFEBAQABBAD0NhMAAAEAEQEBAPMCAmVy9HwSAAABAG0BAQABgQEK3SvOGlJ0AAR0aW1lAADyTwkAAAEAOzAAAAEAAAcAAwCFEAMAxxABANITAQASgAEBoJAAABR7ZXMWLwAAwcqgMAAAEoVm3KoFtbt9CxMXefywYBPqAAGgEAAACSUACAAAQAckiwABggAAAAEFADowAAABAAAAAAAIABAAAADHD8gPdxHUEtUS1BNdFJIUkxQjgAKwABOfAAFlaQGAA7BwE7UAAbEQFHEAAbAgECQAAWRlaQECAPZTEQAAAQDSAQEAJAEBAEMAhBCHTq6/R7BgEvgAAbCAENQAASAQBfMvDAAAoAABAAGCAqCgAAATKqBAAAAQWm92AYACsFAR2QABsFAUTwABYW8BgAKgMAAAEPqwMBCdAAFydgGEArCgE/4AAbCgFAAAAWNy8wABdPfyCAAAAQDeCOoAAQABAAMBAYADsCAS7QABoKAAABO9sGATfAABZWxvAQAAOzAAAAEAAAMBAgDRDAEAiA4BAQEAAPT2EAAAAQCwAAEAE4ACoTAAABSLZXTCAICEoLAAABPIsLAT7AABWAAE9K4BAAABAAEAAQA7MAAAAQAABwADAG0HAwDzBwEAMREBANaHAwDBzqEQAAAUX6AQAAAMcbBQE+YAAbAwEVwAAaAAAAAMFLBAEPgCIgAACABAX/ALADowAAABAAAAAAAgABAAAACJBYoFjQXkBggJCQkcCR0J9wv4C/kL+gsADAUMBwy7DMoMNRA2EG8RhRKcErESshKzEm0TbhODE4QTshPNE/YT9xMBgAKwsBRpAAGwIBMxAAFmafECAbVl9FQUAAABADMAAQDzAgFl9ZcPAAABABMEbAABAAGFArBwE3wAAbCAE7UAAWNzAQIBsGAUTwABZjswAAABAAAHAAMA9wsDAG0TAQD2EwEAAIACAMAAAAAHTudAAAAI/WR4+nsBAGUA8gIQAAABAPIAAQBlAPNEBwAAzgkBAAAJdG9wZW5wYXRoAADyXxAAAAEAAAADZ2VkAAABpwUAAAD2CwCAg4WwIAkUAAFAAEIOAAUAAMTRa0Rvw3NiAUqbeXLGBE+xG8rTLMlVtRBCf/Xbh42bdLKJ/R+7OKIpgKjJLz8uyRmNH0qlZSzROs1HcjMcUEPcXn2CUkCPtJyLJlXlUbkjdArx6OJIBAqe4NnLCQyIXZuGBScYXAzXUXcU/+mNAiwX1UycAZII+k6ZDW+93GLXQEQQAAA7r18AOzAAAAEAAEIAGQAHAgAAEwIAAGMEDgB5BAEAhwQAAGwFAQBGBwEASgcAAEQJAQBfCQEA3wsJALwMAAAIEAAAYRAAAM8QAwCeEgEA2BIBAFQTAQCFEwEA4hMBAA8UBAAyFAAAYRQBAIoUAACiFAEAAQEA9pcFAAABABcNAQCoAAEAAQAAOjAAAAEAAAAAAAwAEAAAAA0CDgKbBVcMWAwEEAUQBhAHENMQ4BLvEhsTAQMBoFAAABFvaAGzEAAAAQACoMAAABNAoGAAABB+Y3LyvgQAAM4AAQEA9ZARAAABAAEAAQABADOAA6EgAAAUDW5vcAGCArBwE3wAAbCAE7UAAWNzI4ACsEAQNQABYW8BCAI4c15juKl4vGGK24NjdfU8EAAAAQDFAAEAAQABBAGwMBL+AAFo8+wTAAABAHIAE4ADoOAAABOyoHAAABMzaG9zAQgAOzAAAAEAAAUAAQDAEgUAAANvd24AAAGuBAAAAWsHAAABAAGgwAAAFCBp9FITAAAIABwAGwABBgD0jBQAAAEAAQABAAEAAPSTBQAAAQBKBwEAE4ACsHATKABabm8BCgGwQBRLAAFl9Q8UAAABAAEAAQABAAhRAADypxIAAAEAAPIFcGRhdGUBAGUA8ssSAAABAAAAAADy/QEAAAEAOzAAAAEAAA8AAwCfEAgAmxEDAKQSAgAAA2FpcgAA8kwHAAABAAAIVwAAAe8GAAAA9gQAAISDsCAQ+QI6sMAUlgABAIgKGAEAADowAAABAAAAAAAMABAAAABIB0kHMgkLDAwMEgwTDGYMZwy1DLYM+hL7EgAOcGFsaXZlaW50ZXJ2YWwAAPKFEwAAAQD0MhQAAFgAGAABAHYSAACCiCR9XlCb5LEAFCUAAST9cvwfbB7sUUkgczog2tISkhAAQAw4BwA7MAAAAQAADgAEAIwEBQAKDAAAqxEDAJASAwD23gAAgoqwQBESAAGgIAAADPkQIACtgQsAOzAAAAEAABYACgAJAAAAewABABQCAACWBAEA8gYHAFcHAQCsDwEAOxAAAFsQAQAOEQEAAQACoZAAABSLoDAAAAkbZXTyORAAAAEAAQEBsEAQ+AIidvOxEgAAAQABACOAAqAQAAAUfGVsAQABVAJbKEESbDswAAABAAAIAAMAXQkBAMkQAQAkEQQAAQABsGAT8gABZfQ+DAAAAQABAOwHAQUA9BwTAAABAFEBAQABAgEEMMun8rF0OzAAAAEAAAgAAgD0CwAAJgwHAPOAAmFlAYACsCAJHAABoOAAABPhYmwBgAKggAAAExuwABNWAAFhcwEIAPSQEgAAAQABAAEAAQQBoAAAABDTcwE7EAAAAQIBoFAAABLvYwG8DAAAAQIA9DYTAAABABEBAQAAA3RvcgAAAfwIAAABXRAAAFIAAQKgIAAAC/5haXIAAUQMAAB2AwAAgYawQAz6AAGgAAAAEzAAAAGVEAIAOjAAAAEAAAAAAA0AEAAAAL0EvwSPBZAFkQW/DMAMqA+pD6oPtg+pE6oTrhM2DgAAgYdFgbgZTKZjn8g3pdWwEAWJAAEQAACBCUMAOzAAAAEAAJoACgCTBAAAFgkCANwQAAAqEQAAaREAAO0RiwB6EgMAihIAAFwTAADqEwEAAAlhbGlkcmV0cnkAAPSVEQAAAQABAAEAAAAEcnZlcgAA8kgMAAABAAAADG1lY29ubmVjdGlvbgAAAVMTAAAA8oACAGFlAA5vYmFibHlzYW1lcGF0aAAAAe8TAAAAAA10aHJlY292ZXJhYmxlAADygxMAAAEAADKAAgGgIAAAB0RhcmwACWVnb3RpYXRlZAAAAS0UAAAAAAZuYWJsZWQAAAHAEwAAAPKHbHRpcGF0aAIAZW4SgAEBsDAR5QABdWEBAAGgQAAAEOBlOzAAAAEAAAYAAwD4CAEAwg8DAIsSAAATgAKgMAAAD8ZkcgAJbmRzaGFraW5nAAD1kBEAAAEAAQABAAEAAPIAAQFhZQDz4QYAAAEAAQAABnJhaW5lZAAA8pUPAAABAAABBAFEa2YlrhRjAZQSAAABBAD0AQwAAAEAAQABAAADc2VkAAABRwwAAAAAA2VudAAA8kUMAAABAADygAIAaW/igAIC0AAFagABZWxhbwADaXZlAAABTgkAAAASgAEBsDAI8gABbGIAA3J0dAAA8tUHAAABAAD0PwCACQIAAQMJCg4SFRjkLXYpBodHMGFjZGhtcHN2YnQBgAKggAAAFC1/3Lue8J5lbyMBA6BgAAATwLBAFJ0AAWVucAERDAAAAAJkcgAAAaYFAAAA8oABAWR0FgAAAAEEME88xS8CoRAAABQNsDAP5AABGZuXkz/6YWhzdHYAOzAAAAEAAAgAAwDTBgAAIgcCAFEJBAABEAGwABRnAAFzOzAAAAEAAAUAAQCBFAUAAQIA9F0JAAABAGsHAQATgAKgUAAAEH5pdQAGaWNlbXV0AAAB2AwAAADyAAEAbPKsAQAAAQAAFgMAAAEFoMAAABNAsFATMQABc47c8toLc2NsbnJ1APK+BAAAzgAIaQAA8uYQAAABAAAACWVydmVybmFtZQAA8vwSAAABAAAADGVtb3RlYWRkcmVzcwAA9KUTAAABAAEAAQAAAAZlc3NhZ2UAAPNXEQAAAQABAAAABWVhZGVyAAAB6hAAAAAABnJhbWVpZAAAAVYRAAAAABNuY3J5cHRlZGNsaWVudGhlbGxvAADybBQAAAEAAAANZXZvY2F0aW9ubGlzdAAA8k0UAAABAAAABmZpY2F0ZQAA8isTAAABAADygnJ0AgBpchKAAgD2QAzWAAFlafKEYWxpZIiAtBAHAAhub3JkZXJlZAAAAY8RAAAAAAZlcXVlc3QAAPPJDwAA3wABAAAIHQAAAV0QAAAA8gJlcgEAYQFEDAAAAAgAAADykw8AAAEAAAAFb21pbmcAAPIQEQAAAQAA8oACAGNu8oACAG50AAV1dHVyZQAAOzAAAAEAAA4AAgDVDAAA8Q8NAAAAAm55AAA7MAAAAQAABwABAEYJBwAACBUAAAFDDAAAAPYDAAAGAaCwAAAUFDBhZmlydW07MAAAAQAA7QACAPwBAABlA+wAOzAAAAEAAHkBAwBqBwAA8grsAPQNiwAAAmFsAADyRAkAAAEAOjAAAAEAAAAAAAgAEAAAAAgQVBNVE4UThhMyFIoUohSjFAAHYWxlcnJvcgAA8lMRAAABAAAACGFjZWluZGV4AAABfhIAAAByAAMBsCAQ9ADwZm52cgAB5gcAAAAJcml0eWxpbWl0AADyfBIAAAEAADKAAgFJRwQdv85ncnY2AAAAAgSw0BOvAAGwUBFQAAGgMAAAD6tNE6Gaco1lb2JjanMAOzAAAAEAABQACQByBQIAvQUBADMHAAD1CAIAIwkBAG0MAwBVEAIAhhIAAP4SAQAAAm50AAABSwcAAPJnEwAAAQASgAEBoDAAABCOYXIIDwAAOzAAAAEAAAUAAQDpBgUAOzAAAAEAAAUAAQDAEgUA8oACAGV0AQABoEAAABDgZfP4CAAAAQCSCROAAqAwAAAPxmRy8wEBZfPhBgAAAQABAAARbmF0dHJhdmVyc2Fscm91bmQAAPJ/FAAAAQAAEoACAPcgDEEAAWVvAAVpbmRvdwAAOzAAAAEAAA0AAQCBEQ0AAAACdHQAAPPuDwAAAQABAAAIYgAA9OoPAAABAAEAAQAAAANleXMAADswAAABAAAIAAEAnxAIAAAADXN0Y2lkcHJvdmlkZXIAAPJBFAAAAQAA9gEAAIWCsKATqQABoFAAABQMCBQRhAAAAUoHAAA7MAAAAQAABQACAM8QAwCeEgEA8oFhAgBsdPIAAQBpADswAAABAACMAAIA3BAAAO0RiwBSgAECsIARmQABdG1zAQEAOzAAAAEAACQACAA2BwcAKRACAIERDQCwEQIAiBIBAA8TAQBhEwEA+BMCAAABeAAA8mgFAAABAAF+EgAAUgABA6BwAAAShKBQAAAP32Vhb3MAAQcHAAAAC3Npc3RlbnRrZXlzAADzpBIAAAEAAQAAAQYA87ESAAABAAEACBEAAAFVEQAAAAAEb3RhbAAA8mEUAAABAADyA2l6ZQEAdPK+EwAAAQAACEcAAAHuEwAAAPKFdWZmZXICAGJzAARscG5zAADy6BAAAAEAAPICbmcDAGFiZvP6CAAAAQBIAPSuEAAAAQBhAAEA8oABAWlw8oFvAgBtbgEDAPPmDwAAAQABAAGAAqAgAAAJJbCAEqIAAWlzCCcAAPShEwAAAQABAAEAAAAPYW5kc2hha2VtZXNzYWdlAAD0jBQAAAEAAQABAADyiXByb3ByaWF0ZQIAaG3ygAECcGx09gcAAMfGG0FXE87+sBAM8ANicWOzTJQlsCAH3wABsCAPkwABsMATVAABQEMACwAcjAAAOjAAAAEAAAAAAAkAEAAAAI8AtAFSBFMEVARVBFYEiAQDDQQN8QECubplcvR8EgAAAQBtAQEAAAllZGlhdGVhY2sAAPRbEAAAAQCyAAEAABYBAIACBMAAAAABq7AwEHIAAWuRs7nkF7BwEZkAAW1wYWVpdfEAAa509NERAADqAAEAAQATAAKwYBHZAAFkZgHNEwAAAQACoGAAABOxoOAAABQOZXQ7MAAAAQAACAABAMQECAABAwGwYBRPAAFmOzAAAAEAAAcAAwD3CwMAbRMBAPYTAQAABGFsdWUAAAFaEAAAAAAKcmRlcmVkcmVhZAAAASoTAAAA8oRlZ2FsAgBvdvKAAQNsYWRlAYICoFAAABLGoQAAABR8Y3QBBAOwIBExAAGgUAAAEd2gQAAAFCBsc3byGxQAAAEAAApoYW5kc2hha2VzAAABUhMAAADyAnJlAQBk8ucGAAABAAAyAAECMhz07aEWb2FlADswAAABAAAIAAMAggQBAPMLAAD9DAUAFgIAAAEFsFAQtwABsEAPuQABoRAAABPhsFAMTAABbmRlaXJ1ADswAAABAAA3ABYAuQYBAMIMAQDjDAEAvg8BAD8QBQB1EAUA8hABAFARAQDhEQIAtBIDAOYSBAAXEwEAHhMBAC0TAQCzEwEAxhMBAOQTAQDoEwEADhQAADkUAQA9FAEAXBQAAAEHADswAAABAAAFAAIA2BADAG8TAQAjgAKwcBL8AAFyeAEGAPTVEQAAAQA+AQEA84ACYmUBgQOwQBKpAAEFfwD9RMF+RhX10WthbXQBgAKwQBMkAWewYBRBAAFhcgEOAPRZFAAADAAiABEAEwACsGAUcwABcnM6MAAAAQAAAAAABwAQAAAAWhFbEdYS1xI+Ez8T1xPYEwEIAPcHEQAAAQABANYCAQCLAAUAAQUA9IUQAAABAAEAAQATgAKwcBRIAAFmbQEAAPRtBwAAAQABAAEAAAZpbWVvdXQAAPLHDwAAAQD21BIAAAEA/wCJADUAAQDyAAEBdG4AAbsMAADygAEBZWkW6gAAgYw7cXBIJL4doQTii7EAX0FpJq2gYAAAEVagUAAAEOplojxhWSiwQBDmAAEACAD1kQc7MAAAAQAAEwACAAgAAABlABIAOzAAAAEAACYBGQCsBQEAvAUAAMkHBwDlBwAA6AcEAPUH7AAMCQEAPQkEAA8MAQBxDAAA1gwBAP8PAQBPEAIAmBADALcQAQBWEQAA0hEBABITAQA0EwEAaxMBANATAQAKFAEAGRQBACAUAABaFAEA5r8gAMrJ4yAAAAGqBAmJaIpUff3nBHqvTUILowIWXAStBvrzKBJcf1tCP7VkLlmufaWt5bg7BBI+A8ANgKgAOjAAAAEAAAAAABwAEAAAAAIAAwCCAJAAkQCoBVQH5AfqCFwJ8gslDPwM7xDwEFoRWxGBEoIS1hLXEgkTChM+Ez8TmxOcE9cT2BMBAAEJzrjuKYdz9ZYEAAABABULAQCOABOAArCwE+YAAWtzE4ACoEAAABEpbnQBBQKgIAAAEcKgMAAAEoxiaAGyDwAAI4ACsNAUHQABb3UjgAI9xtCO3GtlbwEBAqAgAAARwqAwAAASjGJoAbIPAACDgISwMBSdAAGw4BRpAAGwQBA5AAEBCAkBCwDzXRQAADUAAQAAA2tleQAA8kgHAAABAADygAEBY3jyAAEAYQABaRMAAAEAADswAAABAAAFAAIAbQwDAP4SAQABBAD0zxAAAAEAAQABAPOAAml0AARncmFtAAABDQwAAADyAXQBAG8A8g0RAAB/AVKAAQI7TrJ7RvtzZG4BAwGwAAzgAz9z9BcMAAABAAEAAQABAAFkGVx2vWFtOzAAAAEAABAABQC9DAAAUhEAAM0SBQCTEwIAMxQFAAECAPUVDAAAAQCwA4ABAQAjAAOgQAAADNxoy+04vVplcnP0rwQAAAEA9QALAQEBAaAAAAAUR3MB3REAAAECAaBAAAAUSmXybBQAAAEAAQACoUAAABRroEAAABDgY2U7MAAAAQAADAAGAPgIAQDCDwMAixIAAKkSAQAcEwEAbhQBABGBAsewEBNQAAFhZQACZXkAAPILDAAAAQAA8gNkZXIBAGvy5QYAAAEA9OoQAADtAAEANAL2HQAAwclBcR4El/OwUBEFAAFAAAAAAAOOKQDzuBIAAAEArAABAgDzYBEAAAEAAQABBAA7MAAAAQAABQACANgQAwBvEwEAAQUA9PYQAAABALAAAQABAAKwIAzeAAGwAAxXAAFlc/JXBwAAAQAjgAKgMAAACOxoawEFAPMCEwAAAQABAPMEAXL0zA8AAAEA7AABABOAAqCwAAAUQ2FzAAl1YmNvbW1hbmQAAAGAEQAAAAARZW5kaW5ncmV0cmFuc21pdHMAAAEsFAAAAAAVY29ubmVjdGlvbmlkZ2VuZXJhdG9yAAABaxQAAAASgAIAwAAAAAWlZHLyAAEAZTswAAABAAAVAAIA+wEAAFADFAAACCkAAAFCCQAAAPKABAAwaHBzAQECsEAUlgABsDAS/gABZ2j1mREAAAEAUgIBAHIAAQIA9ZARAAABAAEAAQABAAACcGUAAAHlEAAAAAAGa2Vua2V5AADy+hIAAAEAAPKAAgBveQAMZ25hdHVyZXZhbGlkAAABIBQAAADyAAEAaQD0UhMAAAgAHAAbAAAKb3Rjb21wbGV0ZQAA8p8TAAABAAAAAm5lAAD04xAAAAEAmgABAAAABGVhZHkAAAEpEwAAAPICdGEBAHI7MAAAAQAABwACAOEQAQB4EQUAAPKAAgBhbwAIb25maXJtZWQAAPIoEwAAWgAA8AAFAQABBQYI5GNkbnN0bQEKDAAAOzAAAAEAAAcAAgCrEQMAkBIDAPKDaGFrAQFlaQgSAAABdxEAAAAADGV0d29ya2NoYW5nZQAA8uwTAAABAAAABHZlbnQAAPKdEAAAAQAAAAtsaWVudHN0cmVhbQAAAb0TAAAA8gFlhIAUIAQB5AYAAPU1EAAAAQA5AS0BFgHygAIAbHPygAEBZGe2aQCAwsmgEAAACOygcAAAEy8h9Ho+xlugIAAABxgAAAgBggODDPbAAgCEiqAgAAAHWXgroauw+KBQAAARb38I5YndbzMLQPCu3jXb+p5AnCnScMenUhERAIbkCwA7MAAAAQAAPgAYAPsBAAACAgIAUAMUAFwEAQDDBAAA4QYCAAYJAABlCQEAEQwAAFUMAQBZDAAAxAwFAIAOAACmDwEA3A8AAF8QAQCKEAAAsxAAAFwRAQCpEQEA5REBAO8TAAA7FAEATxQBAAEGAPTOEwAAAQBWAAEAI4ACoRAAABSLY3ITgAKgIAAACP5oeQGBArAwEu0AAbAAB/MAAWNuAYACoHAAABMvoHAAABORZG4TgAJi81zRFEphcAGAhHOcZ5I4M7CQEdkAAVJhaIdNKaBAAAARVQEBCQGAAqAQAAAJYbBAELcAAWlzAYICsCAM4wABoKAAABN1Zm4BhAKgQAAAE+5Wd12RrMZicxOAArDAFJQAAXV5AYAEoAAAAAkeoKAAABMqsCAQ6AABoEAAABBaZ29wdgEBAPS8EQAAAQABAAEA84ACZGwAAWQAAPU9CQAAAQABAAEAAQAA8gFwAQBpAPKWFAAAAQAAB2VnbWVudHMAAAGcEAAAAPKAAgBzdQAJc2VxdWljYml0AADyehIAAAEAABKAAQGggAAAFCxhdDKAAgJa0UPQX1mwcBJ8AAFlb2FpAYICsNAUlAABoJAAABORYmgIbwAA9DkJAAABAAEAAQAAEoABASuRCr32unRpMoABAqBAAAATaXRpcgEDAPS5DwAAAQDjAwEAABltb3RlbmF0dHJhdmVyc2FsYWRkcmVzc2VzAADyqBQAAAEAAAAFYWlsZWQAAAHREQAAABKEbmRvbQEBsEAUAAABZmLygAIAYWUAEGF4dWRwcGF5bG9hZHNpemUAAPI/FAAAAQAAAA1wbWFueXNlZ21lbnRzAAABFRQAAAAAF2FsbmF0dHJhdmVyc2FsYWRkcmVzc2VzAADyphQAAAEAAPqsAgBjbwAPbnRvbWFueXNlZ21lbnRzAAABFBQAAAD2AAAABAGwoBP+AAFpbG1yY/KoAQAAAQAAAQEA9wcRAAABAAEA1gIBAIsABQAADWVsZnNpZ25lZGNlcnQAAAFYFAAAAAACaWQAAPSYEAAAAQABAAEAAPKAAgBjc/KAAQFlbyKDZXJhAgDQAAdGAAFsdDYCAACChaAAAAAFp7BAFJYAAbAwEv4AARaFC+/h6wAgBMgAAwA7MAAAAQAAFgAKALEEAAAUCQEA9hABAFcRAgCZEQEApxEBAKETAwDsEwEAXxQAAIwUAwD0rzAAg48AEhYdICIjJKAgAAAFpyWhEAAAE+EnJgzUxamySHaecpo6oDAAAAxZVxwNVsRVKIUQQAHvuQ4AOzAAAAEAAK8AMQCyAQEA/wEBAAUCAQBfBAMAcgQAAJ0EAAC7BQAAtwYDAO8GAABVBwEAaAcAAPoIAQAHCQAAEQkCAB4JAABDCQAAaAwEAHIMQgDCDAEAywwAAOMMAQC+DwEA1g8BAD8QBQBjEAEAdRAFAK4QAQDLEAEA1hABAPIQAQAQEQEAUBEBAJARBADhEQIA7BEAAKASAQC0EgMA5hIEABcTAQAeEwEALRMBALMTAQDGEwEA5BMBAOgTAQAOFAAAORQBAD0UAQBcFAAAAYMCoJAAABQboJAAABQcMjMBgAKgUAAAFEeggAAAE2ZjZfMAAnN08jQHAAABAAGAA6CAAAAUe7AQBvAAAaCAAAAUWGFlaQEBAQnPdOVpSWTyCAcAAAEAAAN1cmUAAAGkBQAAOzAAAAEAABAABAAgDAAA1QwAAPEPDQBVEQAA8oABAXRzAQYCOHNeY7ipeLxhituDY3X1PBAAAAEAxQABAAEAAAhuY2hlY2tlZAAA8lATAAABAADyATQBAHXyNwkAAAEAACKAAgDzIAk1AAEzNgACdHIAAPNFBwAA7gEBAAAABWVtb3RlAAAB6Q8AAAAIKAAAATIJAAAAAAJudgAA8jAJAAABAPIrEQAAAQAAAnRzAADzPgwAAAEAAQAAEoACAPZgCS4AAWl1AAJ1dAAAOzAAAAEAAAUAAQC3EwUAOzAAAAEAAAUAAQB1FAUA8glyZ21hdGNoZXMBAG07MAAAAQAABgACAFIRAADNEgUAOzAAAAEAAAUAAQAzFAUA9gcAAW2Hg6AwAAATzaBwAAAR5LAwEakAARMECyCBADswAAABAAAwAQIA+gEAACACLwE7MAAAAQAA/wACAGkHAADzCf4ACCgAAPQIDAAAAQCIAwEAAAAAAAD0KgkAAAEAAQABAADygAIAeHkABHRhdHMAAPLTDAAAAQAACEcAAPQmCQAAAQABAAEAAAAMbmNvZGluZ2Vycm9yAADynRMAAAEAAP4AAB8EAaAQAAARVmVyc3Rp8mYFAAABAPLZDAAAAQASgAEBMddn5M/obWfygAIBYW9lAAZydHVwbGUAAAEHDAAAAAEFADswAAABAAAKAAMASRMEACcUBACcFAAAAAR0dGVyAAABBgwAAAASgAECoBAAAAwOsHAUaQABYWV2AAhubmVjdGlvbgAA9HMRAAABAAEAAQAAAAlrZXl1cGRhdGUAAPLLEgAAAQAA8oACAGVvsoACArAwFE8AAWNtcHXyAAIAcnUA8vAMAABiAwEBAPSaBQAAVwNLAwEAAAAAADswAAABAADwAAEAtwDwAADygAEBdHUADGNvbnRyb2xlcnJvcgAA8iYTAAABAAASgAEBsBAUcQABd2ESgAEBsCAPpgABb3UBgAKwIBMoAFqgUAAAERdtcwEBAbBgEdkAAWYBzRMAAAEAAaCwAAATsWLzOxMAAC4BAQAjgAKgAAAAEzpkcgEAA6AgAAARRaBAAAASmWcyd5ly7GRlc/PgDwAASwMBABOCAqAgAAAUDWVpABlkbGVuZ3RoY29ubmVjdGlvbmlkcGFyc2VyAAABixQAAADyAAEAZQDyORAAAAEAAANyb3IAAAHGDwAAAAAEYXJseQAAAeAQAAAA8gABAGXy+AgAAAEAAYsSAADygAIAZHLyAnNoAQBl8+EGAAABAAEAAAAIaXplZXJyb3IAAPKiEgAAAQAAAAJ6ZQAAASUJAAAA8oFsAgBpc/KAAgBhaXYNAICChrDgE+oAAQvfUntdPwAgIFwIAQEAADowAAABAAAAAAAKABAAAADOD88PvBC9EEgR2RHaEfYS9xKQFJEU8wIBefLwEwAAAQAjgAKgUAAAEn5ldAAKYW5kb21ieXRlcwAA8gAUAAABAAAACnVycmVudHRpbWUAAPL+EwAAAQAA8oRvZ2V0AgBjcvICZWQBAHQA9NERAADqAAEAAQASgAEBsEARLwABbHIygAECcQa18e7FaWN1xjsoAMrI4yAAAAC140AAAAC2wAAAAAAHsFAPygABoLAAABMwoAAAAAHvY5SJ35R8WqqQtV/CoPAAABRfZECeBIAPAEMAOjAAAAEAAAAAAAsAEAAAAIEAXgSUBJ4EYwVkBVkHWgcuCS8JWgn5DAGAAqEgAAAUmLBgEd8AAW5zAYICoCAAABD5sFASywABZHUBBAD0rxMAAAEASwABABOAArCQFJYAAWVoAQIBoEAAABFnZQGuDwAAAQcA9JsRAAABAAEAAQABBwDzqxMAAGwAAQAAEXNpb25ub3RuZWdvdGlhdGVkAAAB/RMAAADygAEBbnLyAAEBZTEAOjAAAAEAAAAAAAkAEAAAAFcEWARCB/0IWgxbDFwMXQw9ET4RAARycm9yAADySxQAAAEAAPINZXlpbmdtYXRlcmlhbAEAZfUPFAAAAQABAAEAAQAAGuUBAbAAEQUAAWtzAAR5cGVzAAD0lBAAAAEAAQABAAASgmN0AQFLH63IBp50ZfKAAgBlb/YAAAACA3Se31k5o3B0YWNoAPNoBQAAAQAVDQGAAqAQAAAUILAQEPgCIml1E4ACoFAAABHGbHIABHJlYW0AAAGTEAAAAPIAAQB0APfgDAAAPwPuAgEAugCfAAEA+moBAHPzqwQAAAEAAQA7MAAAAQAAGQAHABcMAwDYDwEAnRABAN0QAQDNEQEAiRMHAIEUBQAygAECa+LBmsZoZWFvAQIAOzAAAAEAAAYAAgCuBQUAahEAAAEDAPYLCQAAVwAHBwEAtgABACOAA7BwFJQAAbBwFCEAAWVvcgEEAbCwFG4AAWz08wwAAAEAOgMBAAECADswAAABAAAMAAMA6wgAALcTBQB1FAUAAQEA8yQQAAABAF4CI4ACoLAAABQgZ3oBgAKgMAAAEd2wYA/kAAFocgEBAPRuEAAAAQABAAEAI4ACsAAM0wABc3UABmxpc2hlZAAA9MIPAAABAAEAAQAA8oABAWJ0EgABAS7x+e5LG2FpADowAAABAAAAAAAHABAAAABbB8kPqBCpEHcTeBN5E3oTlngCAMHNoCAAAAWVoCAAAAWWsHATtQABYYbZYoCJczakjDnOsDAN8gABEv+RBVF0AAAAAgNntAUAOzAAAAEAADgAGQCVBAAAzQQDAFsJAAC9DAAAmQ8DAJQQAwBSEQAAuhIAAM0SBQAvEwEAUhMAAFoTAABqEwAAdhMAAIcTAQCREwAAkxMCAJcTAQDuEwAA9BMAAAAUAQAdFAEAMxQFAEcUAACmFAYAAQICoHAAABHnL0bp6zCNYXIBbAcAABMAAml2ZP6ALXJ18gsCAAABAAGAAj1q9jxqM6CwAAAUX2FlAYACsFAS+AABCKVkCXHOZWkBBQDzkxMAAAEAAQATgISwMBAzAAGgoAAAE7GgMAAAE+4QQEgBiAKwABPJAAGwABPLAAE0NhOAAqAwAAARamRzAANvZGUAADswAAABAAAGAAEAig8GAPJBEwAAAQDyAXIBAGM7MAAAAQAADAACAJ8ECwBlBQAAOzAAAAEAAEoAJQABCQEAFQwBAB0MAgDcDAAA6AwCAPoMAQC0DwAAuQ8BALwPAQDGDwAATBACAFMQAQBYEAEAfRAAAPQQAAD6EAAARhEBAFMRAQBnEQEAaxECAMsRAQDeEQIA5BEAAJkSAACiEgEApxIBAPASAQAAEwEABRMBACYTAQA2EwMAPBMBAJ0TAQCxEwAAHxQAAEgUBABRFAIA8gABAW9lAYUAAAAAVnAAAMHPI1uSnD53atOOgXbiASXL1bujRgLrU7TiAMT2WPRvsCAMCwABGXBd2MP7RwGqDBSZXmCiJobiWmFhXyGSaNl/O5VeAACAAMBNHWsAOjAAAAEAAAAAADQAEAAAAK8EsASlBbQFtQW2BbAG5QbmBhMHFAcVBxYHFwfmBwYMIgwvDEQMSAxJDE4MTwxQDL4MzwzQDJMPlA+dDwIQAxAmELQQ6hD5EAoRCxFFEdcR2BELEwwTJBMzEzsTDBRBFEIUaRRqFHwUixQABWFsZW50AAA7MAAAAQAAAwECANEMAQCIDgEBABKAAQGwMBJ6AAF2YxKAAQFDFOTVEAFpZfIAAQB1OzAAAAEAAFkAAQALAFkAARQMAAABAAD0MAkAAAEA+gcBAAGAAqAAAAAMJDGbZDUpmm1uAQQBoDAAABHUdAGSEwAAAQIA9vcMAAABADgDAQAtAQEA84ACb3IjgAOg8AAAE/2wIBLcAAFpdHUBAgGwUBLcAAFl9QYJAABPAwEACQQBABOAAqAQAAAMt2FvAYACsGATrAABsHATnwABYWMjAAOwUBDyAAGgsAAAEy9jdHX0UQcAAAEAAQAGAgEKAPO4EgAAAQCsAAEBAPcSCQAAAQBQBwEAcgABABUBI4ACsGAT0gABaW8BAgD0tQwAAAEARAYBAAEBAaEAAAAUWWTyqAUAADwCAQUA9SkQAAABAAEAXQIBAAEFAaAAAAAQxnP16wwAAAEAAQA6AwEAAQIBoKAAABQscvTWDwAAAQD0AAEAAQABoCAAABFocvMiDAAAewNtASOAAqAAAAAUCWRyAQMCsEATBQABoDAAABNcZXfydBAAALoAE4MCoHAAABOWZ28BAgD15BIAAAEA8QCAAAEAE4ACsDATFgFKaXABBgDzkxMAAAEAAQDzBQJicwEmEAAAAQMA9AQQAAABAAEAAQAABXBwaW5nAADyoBIAAAEAAAglAAAB3xAAAAD60gIAYW8IQQABoAAAABPIcwHeEAAAAAAFb25maWcAAPJQEQAAAQAA8gRvaW50AwBjZXP19QgAAAEAAQAsAAEAAYYSAAD2pQAAwcuwcBEvAAGxUBSZAAGwYBDEAAGgkAAAEoUAACAAwE5QFQD3ewQAAAEAAQBsB3kAvwPfAAEAAjhzXmO4qXi8YYrbg2N19TwQAAABAMUAAQABAAGAAoATkwAQAbBQEQUAAXN48wABZQHDBAAACBsAAPJYEAAAAQAACBEAAAFKFAAAAPILY2xpZW50aGVsbG8BAGUA8mwUAAABAPKAAgBkcvIDeXB0AQBl9NEHAAABAAEAAQAAAQIA9OgTAAABAFAAAQATgAKwcBLaAAFmbgAAAAA7MAAAAQAADAABANQGDAAAAANibGUAAAEFDAAAADKAAgGwcBOdAAFhZWnygAEBZG62AQCAAgOgAAAADGVvcmVoeQEBAPWoFAAAAQABAAEAAQDzBgFl8ogUAAABABOJArBAFH8AAWFyABJlZ21lbnRhdGlvbm9mZmxvYWQAAPJxFAAAAQAAEoJsZQEBoAAAABPAc2TygAEBYnT2fA0Aw80v8wrwjiERIiUMFjiwkBHfAAEqbWa7NaFAAwAAgBS3DwA7MAAAAQAAFwAKAK4BAwBZBAIAiwUAAG0HAwDzBwEAAwkBAFEMAQDmEAEAMREBAOsSAQABAAN5PQ3SeyugMAAACQoFFMKaXJtibXP0dgQAAAEAAQDpBwGAArBwEp4AAbAwEFMAAWRy8wMBZTswAAABAAAFAAEAjgcFAAEEAaBQAAATvXMBxhIAAAGAAgWR6JSGWbAQE1AAAWFlAQEBsJATbwABc/SYEAAAAQABAAEAAQMBoAAAABDTcwE7EAAAAAAAAPP3AQAAAQABAAAAAAAA8/QBAAABAAEAADYAAAACBaBQAAARcBOdSbR3WH3Kc6+U8GuKxSGEzaAwAAAQijAxY2VpdHcA8owHAAABAAAIb2RlcG9pbnQAAPNVEAAAAQABAADyAAEAY/SxAAAAAQABAAEA9JMFAAABAEoHAQC2NwAAwsgHD2wCV+FiQ1YVFLkAAAgCAGSTCAABkgAAAAEBADswAAABAAANAAQA/gYIALgPAACJEAAAAhMCACOAArBwEnoAAWVvAQABoFAAABLvYwG8DAAA8wABbPJ5BAAAAQABgAOg8AAAE7KwUBA1AAGgkAAAEW9jaHAAC2F0YWFjY2VwdGVkAAD0fhMAAAEAAQABAAAABXJ5cHRvAAD0jxAAAAEAAQABAADyAmx5AgBjZAAB4BAAABYWAICBibBwEdsAASQ5JOOSTltDhkKQIKEAAAAUX3eCLXFq96BwAAARCgAAAS4cBgAAyNE/b0abT98W8F3ripBWwd/Avlhx5JYV1N9afbZcfz04TyZjyLttScUItKUYWnc6okU24CEZC+JZYECnTHcQrhSjUD4viNzDtfgeLh2WiZ5T2/Syg5xNE0tSfdhCGCbR6FN8EDNgU1N53pQW6TwmheQopl14h41dq9kfJC1Y82t/n7DnE0IWoxrC4wlFOb2KqCMJ0VApmLtAAcgpgP43VgA7MAAAAQAACQKMAAQAAgABAgAACQIBABUCAACABAEAhAQCAIwEBQCYBAEAsQQAAL0EAAC/BAAA0QSQAGYFAQBqBQEAbgUBAHoFAACJBQEAjQUEAKQFAADGBgwA1AYMAOQGAADnBgEACAcEABkHCAAqBwAAXAcBAHgHEwDnBwAA4ggBAP4IAgAICQEACwkAABQJAQAbCQIAJQkAADkJAwBOCQIAWAkAAGIJAADqCwMA9wsDAAAMAAAFDAAABwwDACAMAABlDAAAuwwAAL8MAgDKDAAA1QwAANkMAQDuDAAA8gwCAPcMAQCKDwgApQ8AAKgPAgCwDwEAsw8AALYPAAC7DwAA0g8AANQPAQDgDwAA6Q8AAPEPDQAJEAEAJBABACwQBgA1EAEAWhAAAGIQAABpEAEAfhAAAIUQAwCLEAAAwBADAMcQAQDjEAIA9RADAP0QAgAMEQAAFxEKAFURAABXEQIAXhEBAG4RAQB+EQEAmREBAKcRAQCrEQMAxxEDAN0RAAB5EgAAgxIAAIUSAACQEgMAlRIDAJwSAACxEgIAuBIBAL4SAQDHEgUA3BIBAOISAwD8EgEAERMAABYTAAAaEwAAIBMBACsTAQBBEwEASRMGAGUTAABtEwEAcxMCAIMTAQCfEwUAqRMBAK4TAACyEwAAvhMBAMMTAgDNEwAA0hMBANUTAQDsEwEA9hMBAP4TAQAbFAEAJxQEAC4UAwA/FAEAVhQBAF8UAQB7FAAAfRQBAIwUAwCcFAAAAQMBgBNjABD6czswAAABAAAFAAEAQxMFACOCAqCQAAAUR2lzAYECsFATcwABsQAUVAABbXQBBQDzVxEAAAEAAQABgAKgcAAAEuCwwBMgAAFuePOAAmFlAQQA8+oQAADtAAEAAQcBgBNSAIAcc/SrEQAAAQABAAEA84ACYWUBBwD1SRMAAAEAAQABAAEAAYACoEAAABFIsFARLwABY2kzgAOgUAAAEVZhb3IBAAD0CRMAAAEAkQABABOAA6BgAAARr6BwAAAShWlvcgADaXN0AAABDREAAADyBWF0aW9uAQBo8vQIAAAuAAHFEQAAUoABArCgE/AAAXJjbgGBASH0ej7GW2sBgAKwcBL8AAErqtISIlNyeAECAaAAAAAQxnM7MAAAAQAACwADAOsMAgAnEAEARRAGABOAAqAgAAAT4WV1CEAAAbBwFEEAAXA7MAAAAQAABwABAMkHBwDytxAAAAEAkoACAucAAAAG07AgEM4ApGNpYXJWAQAAgYczvHVwMraggAAAFJx0NWLQ0jWgQAAAD9+wgBD+AAEAAASyQUAA9O0IAADdBgEAsAMITgAAOzAAAAEAAAcAAgDzAQAAGQIGAAAABXRyZWFtAAABjhAAAAAIUQAAAcgHAADylQ8AAAEA8oJpbgIAZXMyAAICH1TM95/eXg11T4RcYW9ldgA6MAAAAQAAAAAAFQAQAAAADwIQAhECpgXvDJcPmA+1D9MPZRBmEGcQaBAVERYRqxKsEq0SrhKrExcUGBQBAQDzDgkAAAEAAQABAAOwQBQAAAGhQAAAFHCgUAAAEdFiY2YB/QYAAAADYXRzAAABchEAAAAIKgAAAQwRAAAA8oRvYWRzAgBpdPIAAQBsAPMpEQAAUwIBAPIAAQBuADswAAABAAAkAAgANgcHACkQAgCBEQ0AsBECAIgSAQAPEwEAYRMBAPgTAgBWAgCAAQWxQBSZAAFg+/LKkregwAAAFFN3Ym1ucHUBBAGwIBFBAAFl8q0SAAABAAEEAbAgET8AAWXyqxIAAAEA84ECNDYBBQD0WxAAAAEAsgABAAADbGF5AAABRAcAAAAABHJkZWQAAPQBDAAAAQABAAEAABKAAQE9uvW38nVhbwAEYmxlZAAA8vIIAAABAABygAMBsFAUcwABYWNwdAENAPRZFAAADAAiABEA8gAAAWU7MAAAAQAABQACAIMAAQCtAAMAAWEJAAAAEml0YWxseXNpZ25lZHN0cnVjdAAAAeETAAAA9gIAAIOECrUQnfh6sDATVgABQAADAaAEADowAAABAAAAAAAHABAAAABaEVsR1hLXEj4TPxPXE9gTAQkA86sTAABsAAEA8QACsgBhdPNoBQAAAQAVDQEEAPNVEAAAAQABAAEBAPUkEQAAAQABAAEAAQAjgQKwkBPqAAFhdAEAAaAQAAAUCWUBABEAAKOAhLEQFGwAAbCAE6wAAQxABAEAADowAAABAAAAAAAJABAAAAABDAIMAwwEDD4QrxOwE/sT/BMJFAAHaW5hdGlvbgAA8+YPAAABAAEAAAAHcmlwdGlvbgAA8uQPAAABAADygAIAY3QBAgD1HQwAAAEAAQBeBOsAAQIBsIATzgABdAEHCQAAAQABfJ/mcf4EcvKPEQAAIAAIRAAA8/EIAABLAwEAAPIAAQBt854EAADFAAEAADKAAQKgMAAAE7FmZHKyAAEDsCAMCwABZWlrcgA6MAAAAQAAAAAADQAQAAAA5QbmBiIMnQ/qEPkQChFFEdcR2BEzEwwUQRRCFAELAPPkEwAAAQB3AAAQdXBwb3J0ZWR2ZXJzaW9ucwAAAV4UAAAAAAZ1bnRpbWUAAAF5EgAAAAANYXhpZGxldGltZW91dAAA8pIUAAABAAAAEGVlcGFsaXZlaW50ZXJ2YWwAAPKiFAAAAQAA8oNhdGgCAGtt8gRhdWx0AwFwcnNjOzAAAAEAADQAAgBDBwAAlAczAAAIIQAA8lMQAAABAAAAB2luaXRpYWwAAPKeEgAAAQAA8oACAGRy+iABAGU7MAAAAQAABQABAI4HBQAAAAAAAbBAEwAAAWU7MAAAAQAADAABAMYGDAAB9RAAAAggAAABAAwAAAD6lQIAYWXygAIAb3IAAnVnAAABnQQAAAD2ZQEAhYigMAAAEOCwQBMAAAGwIBESAAGwUBO1AAEmAAMZqCQAOzAAAAEAACwACwAJAgEAFQIAAIAEAQDGBgwA1AYMACoHAADiCAEAig8GAPUQAABBEwEAdRMAAAEEADswAAABAAAGAAIA8wsAAP0MBQABAgD0BBAAAAEAAQABAAEBAaBAAAAQ+mX0CAkAAAEA9wIFABOAAqBQAAAShGxzAQQBbxlqE4InY/LrEgAAAQABBQDzuxIAAAEAAQDzggJmdAAHcmFuc2ZlcgAAAQsRAAAAAAhuYmxvY2tlZAAAASUTAAAAAANhY2UAAAFgFAAAAAgXAADyVhQAAAEAAPKJbmRidWZmZXJzAgBpcDIAAgGgIAAAExFldWn1/wsAADkAAQABAAEAAAAJdWZmZXJzaXplAADyfRQAAAEAABKGZWNlaXZlAgDAAAAAEp1iZAADbmZvAAABUhAAAAAAA2VudAAAAd0QAAAAEoABAbAwEwUAAXZy9gAAA3JhbQQBoDAAABNcZWlyc3f17ggAAAEAAQAwAAEA9W0QAAAHALkAAQCzAQAGbG9ja2VkAAD0wA8AAAEAywABAPQHEwAAAQCRAAEAdgAAAIODd8eoD+DXoHAAABOWoEAAABMpQgAEAUABARgCAAA7MAAAAQAADwAGAE8HAQBWCQEA4RABAO0QAQB4EQUAfxIBAFKAAQI9jZ7zoxxhZWl2AQCAAQUcQ7FEzaSw4BRPAAF0YmNkc3YAAAcRN8s6miu9VHfPA37sblWz7f1BEmdRxCGFDCkbbm8WDcdj/kmCf1rFwNPeoDAAABEBVqlsug8kVCePGZWgJLmPxKQ7fGoiQGPfPyaYHXvkAzPkNmbFsPAUlgABU3bHe0qNXePp8DFlTqHnXOtEF6w89vADsGAQxAABMlfBDmbVQi+8H+uuLQzd3VnboAAAABMpYWVpb3JzdTBiY2RmZ2hrbG1ucHF0dnd5ADswAAABAACeAncACAAAAGUAEgB9AAAAjgAAAJMABQDvAQAACAIAABYCAQBzBAIAewQCALoEAADEBAgAnwUBAKcFAACpBQEArAUBALwFAAC2BgAABwcAABgHAAA+BwMATgcAAGAHBQBnBwAAcgcFAMgHCADlBwAA6AcGAPUH7ADyCAEA+AgBAAUJAAAMCQEAPQkEAGMJAQBnCYsA6QsAAO4LAwD1CwEA/QsAAAEMAwAODAIARwwAAFQMAABeDAIAYgwAAHEMAADWDAEA3QwAAPEMAADyDQEAlQ8BAMAPBQD/DwEAIRAAADMQAQA+EAAATxACAGsQAQB/EAAAjBABAJgQAwC3EAEA1BABANgQAwDrEAEAABEAACIRAQBDEQAAVhEAAGARAgCAEQAAjxEAAK8RAAC8EQMAwxEBANERAgDVEQEA2xEBAOcRBACLEgAAmhIBAJ0SAACpEgEAuxICAN4SAQDyEgMABxMBABITAwAcEwEAIhMBACUTAAAoEwAAKhMAADQTAQA6EwAAQBMAAFATAQBrEwEAbxMBAH4TBACZEwEArBMBAK8TAQC1EwEAwBMAAM4TAwD7EwIAAhQJABkUAQAgFAYALRQAAEQUAQBaFAEAbhQBAHEUAQB/FAEAlBQBAKAUAQABAAKgYAAAEzOwoBK2AAE4ZTswAAABAAAJAAIA1wcHAPgSAQABCADzVRAAAAEAAQDzAAFjOzAAAAEAAAcAAwCxAAMAkwUBAN4MAQAAgAEB9kACFgABsDAMvwABbnIAAm9yAAABowUAAAABgAKgQAAAE0CwIBP+AAFoaQABdQAA8+EPAAABAAEAABKAAQGwwBSdAAF0dR4BAIQBBVwuuziCtqBAAAAURrBgFBYAUGCuCR8rVm1icHN0dfKAAgBycwg3AADyvg8AAAEAAPICaWMBAGP0mgQAAAEAAQDGAAAygAIBoHAAABFEYnJkAQMA9FMQAAABAAQAAQABCgGgQAAAFEpl8mwUAAABABOAArBgEp4AAWNp84ACZHIABmVzc2lvbgAAAXERAAAACDcAAPK8DwAAAQAAAANpemUAAPJOEwAAAQAAAAd4Y2VlZGVkAADy+xMAAAEAAPKFdWZmZXICAGVz8gADAGJlczswAAABAAAMAAIAoQUBALsGCgA7MAAAAQAABQACADcQAQCPEAMA8gJwdAEBb2UAOzAAAAEAAAkAAgCOBwUA0QcDAAAFZXJpb24AAAH+CwAAABKAAQKgIAAAFHywQA/kAAF0YnAAB2VzZW5kZXIAAAEKEQAAABKBYQEBsAAL9QABdGTygAMAZWl5AYICsDATgwABaNl/O5VeYXkBAAJTG73xdmSwgBMxAAFzdvRfCQAAAQCCCgEAAYaFXC67OIK2sNAUnQABoEAAABRGsGAUFgBQYK4JHytWApAKAAZldHJpY3MAAPL4EgAAAQAAAAZhY3RvcnkAAPL2EgAAAQDykBQAAAEA8gJlcgIAZm3yzwwAAAEAABKCb2wBAbBAEyYAAWxlAAN5cGUAAAG7DwAAACrJAgDAAAAACR9zdBKAAgDnQAAAB0JuePKAAgBlcgEJAPOkEgAAAQABAAAHcnZhdGl2ZQAAAYsQAAAA8oABAWVpCG0AAAGFDgAAAAADaXRoAAABihAAAAAINwAA8s8RAAABAAASAAEBoCAAABN1dGkA8toSAAABAAAGZWZ1c2VkAADyIhMAAAEAAAADb3N0AAA7MAAAAQAABgABAEkRBgAAAAVhcnNlcgAAASQTAAABixQAAAAJaW1pdGVycm9yAADySBQAAAEAAAAIZW5lcmF0b3IAAPLfEwAAAQDyaxQAAAUA+ikDAGdscPNPEAAAAQABADowAAABAAAAAAAPABAAAADSEdMREhMTEzQTNRNrE2wT0BPREwoUCxQZFBoUWhRbFAAFYW5kbGUAAAGcEgAAAbITAAAIHAAA8s0RAAABAAAIMAAA8ssRAAABAADygAIAcnYABGxvc2UBANAAEpoAAWQ7MAAAAQAABQACAMcRAwDJEgEAAPIBboeAlAkD9cwMAAABAAEAuAEBAPfxEAAAggABAAEAAQCjAToAIoACAOZwAAAMy25vCAIAAPNMEAAAAQABAAAigAIAwAAAAAv9ZHIABWxpZW50AAABcBEAAAD6ZISAFAEQ8owHAAABAADygAIAZXMABWluZG93AADyDxMAAAEAAAAEdGF0ZQAA8scSAAABAAAAAAAA8g0TAAABAPJnFAAAAQD6XQEAcwA7MAAAAQAADQACAIkTBwCBFAUAABBvbnRyb2xsZXJmYWN0b3J5AADykBQAAAEAAAAIbGdvcml0aG0AAAFpEwAAAPYBAAVzdGlvbgUBsIAUoAABYWNlc3d09IEOAAABAAEAAQAAAAJsZwAAAR4JAAAA8oACAGFlABByYWNpbmdzdWJzY3JpYmVyAAABfBQAAAAICQAAAcYSAAAA8oJyZQIAY3QIKQAA8rkPAAABAADyAAIAZXXyuQYAAAEAOzAAAAEAADUAFQDCDAEA4wwBAL4PAQA/EAUAdRAFAPIQAQBQEQEA4RECALQSAwDmEgQAFxMBAB4TAQAtEwEAsxMBAMYTAQDkEwEA6BMBAA4UAAA5FAEAPRQBAFwUAAAADmVudGlhbGl0eWxpbWl0AADy6hMAAAEAADKBaQIBsDATKABaZGdy9gMAgIWCsAAUlAABYCAGDAAAAQIDsEAQ6AABAnPx/zBVoFAAABFVYWJm9/oIAAABAEgAawcBAGEAAQABBADzsRIAAAEAAQAAAAAAOzAAAAEAABMAAQB4BxMAAAhAAAABygwAAADygAIAYWXygAEBcnQSgAEBsDATnwABYWwABG5vcHQAAAH8CwAAAAAAAAGggAAAFJxmAe0IAAABexMAAAAHcnVwZGF0ZQAA9UkTAAABAAEAAQABAAAABWN0b3J5AAABSBEAAADygAIAYW/yAm5kAgBmczswAAABAAAGAAIAQQcAAHIHBQDy8QwAAI8E8oACAGFv8oACAW1waQACbmcAAbBAE50AAWXytwYAAAEAACAAAAKwQBMAAAGaZXABFQIAADswAAABAAAjAAUAxgYMANQGDACKDwYA9RAAAEETAQAygAIBsDAMAAAFZWlh9gQAgAMEsFATfAABsBAUIwABZG1ubG9ydghdAAA7MAAAAQAAEwABAJkAEwA7MAAAAQAAEwABAAsQEwAABWVhc29uAADyuA8AANEAAAgKAADyRhEAAAEAAPpOAQBlAYAOAAAACGAAADswAAABAAAGAAEARRAGAAAIAQAAOzAAAAEAAAUAAQDEDAUAAPIAAgBwc/OfBQAAAQAWATowAAABAAAAAAAKABAAAAAFCUcMVAzrEOwQmhKbEvIS8xL0EvUS8gFlAwBkcHL1mQQAAMUAAQABAAEAOzAAAAEAAAoABAClDwAAxxEDAJUSAwDJEgEAAAdvdW5pbml0AAA7MAAAAQAAjAACANwQAADtEYsAAAADbnRvAAA7MAAAAQAAiwABAPQNiwAAAAJveAAAOzAAAAEAAAcAAQAwDAcAAPoDAwBiaXQ7MAAAAQAAjAACAJgEAADSBIsAAPKAAgBucwEEAaBAAAAUSmXybBQAAAEACHsAAAG3DwAAABKAAQGgMAAAE71hcvIAAQB0AXEHAAAACEEAADswAAABAAAFAAEAPxAFAPa0EgAAAQAvAQEAWAAfAAADZGRyAAABtQ8AAADyAm50AwFhY3NoOzAAAAEAAAcAAgCcBQIAsQYEAPRFDAAAAQAqBVYB8gABAGUBggAAAADygAIAaW8BgQKwMBAJAAGgkAAAFHxycwAEdWl0ZQABoAAAABMwcwG2DwAA86kTAAABAAQA+jgBAHMBsAYAAADyAAEAaADzIgcAAAEAAQAACHhoYXVzdGVkAAD02BAAAAEAAQABAPJvEwAAAQASgAEBM7x1cDK2ZWIABmZldGltZQAA9IUQAAABAAEAAQAACCkAAPRtBwAAAQABAAEAAPKAAgBlaQgwAADzBxEAAAEAAQAAdgAAAAMCsPAUTwABsHAUQQABZ2xzYXAAOzAAAAEAABQABQDJBwcA6AcEANYMAQCYEAMAtxABAHKAAgKwwBQlAAFkcG50AQIBUi336H4kYfJjDAAAAQAAAAAAAZsFAAD00xAAAA0CDwAsAPICbmsBAHPylgQAAAEA86wPAAABAI4AAQAA9qkSAAABAHIAAQBRAQEAAAhuZGluc2VydAAAOzAAAAEAAAUAAQDAEgUAABKBawEBsBATUAABYWU2AAAAAQRkP9WH4KdgXosAiPmwUBEFAAFjZG1zeADzuBIAAAEArAABAgKwQBSWAAGwMBL+AAFnaPWZEQAAAQBSAgEAcgAABGhhMjAAAAHsCAAAAJKAAQOgEAAACOwYwJvpalpjMmxu9gAAAAMCoDAAAAzBYWV1b3MA88MEAAAiDQEAAQMA9K8TAAABAEsAAQABgAKgcAAAE82gMAAAEP1mdAgWAAABHxQAAADyDWV2b2NhdGlvbmxpc3QBAGUA8k0UAAABAAgBAAABmRIAAAAICwAAAUURAAAAMgZmaWNhdGUCAWcyd5ly7GRlcwHgDwAA8isTAAABAHpDAgIvhYCwRLhpcnN2APTtEgAAAQB4APIANgAAAIGGDCWPXhd2sIASywABsLAUbgABoCAAAAzYNlQCTUbOAAABEJ0A8wQAAAABAAEAOzAAAAEAABIACABqBQEAegUAABkHCABlDAAA8wwBAC4QAQAWEwAAYBQAAAEDAPQBDAAAAQABAAEAAQIA87gSAAABAKwAAAJzZQAAAdEEAAAAtgAAgAEEQUW6dSDwRRgxsx+PdWNscnT2PwYAysiwIBJ6AAEcvU/QATJ4zOcEjh4dBdl+Rupx3iETB9A9B5RO5vBAZJYUgAEJSwD2fwAAABMACAQBAAEAxgABAQGwUAzCAAFj9PABAAABAAEAIAABBgFg/Xk4r2xz8vEMAACPBAADZWFkAADy8g0AAAEAAAhxAAAB6wgAAAAyAAICoCAAABKDsIAUlAABbXJpcPWVBAAAOAABAAEAAQA6MAAAAQAAAAAABwAQAAAAWwmZD5oPmw+cD+4TABQBFPoXAQBzAAEXEQAAEoABAbAwEwsAAXRwAA5yYW5zcG9ydGNvbmZpZwAAAQ4UAAAAEgJsZAEBoGAAABOxdGU7MAAAAQAACAABAMQECAAAAYEBIhuKEyHccggDAAABmgUAAADyAAEBbWYBgQAAAPRZBwAAAQDUAQEAMoADAeawAAACFKAAAAAEnWZpbGcBAgFjVyfmVidz8jMQAAABAAAAAAABgAAAADswAAABAAAHAAEAMAwHAAgAAAA7MAAAAQAA7QACAPsLAAAFDewAAPIDcm93AQBtOzAAAAEAAO0AAgCZBQAAwwXsAADigAMB5sAAAAITb3J4dQAJbXRva2VubG9nAADz1hAAAAEAFQEAAQIA9AkTAAABAJEAAQABAwD0PhMAAAEAmAABAPIDa2VkAAJidfM+BwAAAQABADowAAABAAAAAAAQABAAAADAD8EPaxBsEIwQjRAiESMR1RHWEQcTCBMUExUTJROZE5oT8oACAGNvAAhkZXRlY3RlZAAA8rUTAAABAAAAB29vbGRvd24AAPJ8EwAAAQAA8oZja2hvbGUCAGNzNgAAgAIDF7eoe+w5UH92JeQ4oIAAABPvYW9laXkBAwEEMMun8rF0OzAAAAEAAAgAAgD0CwAAJgwHABMAAoAMPgAQAXJz8noSAAABAAEBArDgFFkALrBgFHMAAXJzOjAAAAEAAAAAAAcAEAAAAFoRWxHWEtcSPhM/E9cT2BMBAAGwUA++AAFj9JoEAAABAAEAxgAABW9ja2V0AAAB3w8AAAAaSQEBoHAAABKEc2EAAQcHAAB2AQAAAQSgYAAAFJtuY2RzdPICAAAAAQD0qAUAADwCBgE7AwEAAPSHEwAAAQAPAAEAAQQA82ARAAABAAEAAAVwb3J0cwAA8gURAAABAAASgAEBgBOTABABeHPyAmNoAQBlAcMEAAAAVgEAgAEEsEAM2QABoAAAABR8bmZocnMIJAAA8sIMAAABAADyAnIzAQBj9PABAAABAAEAIAAAAQEAOzAAAAEAAAUAAgDuCwMA2xEBABMDA7AwFDsAAQyKR/BfMWVwcvTMDwAAAQDsAAEAAAZjaHNpemUAAAHBDAAAAAAPbWF4ZnJhZ21lbnRzaXplAADyIBMAAAEAAAADa3VwAADylwUAAAEA9K8SAAABAKgAAQB2AQCAAwKgoAAAE+9jZHRibvZfAACIhKAQAAARwipW30mLKBNJSASAAwDywBEAAAEAAAZpbGFibGUAAbBgFE8AAWb09wsAAAEAAQABAPRtEwAAAQCIAAEAEoABASiIcl/pN2FlAQIAOzAAAAEAAAUAAgDYEAMAbxMBACOAAqAAAAAE0WV0AAhvcnVwZGF0ZQAAAZwUAAAA8gp1YmNvbW1hbmRzAQBmAXsTAAAACAEAAPUnFAAAAQABAAEAAQAA8gNyZ3MBAGb1gBAAAAEAAQABAAEAAPKEbWVudAIAYXNSgAECIaMWWWtlZ2xzAYACoVAAABRroAAAAAWlZHLzAAFlOzAAAAEAABUAAgD7AQAAUAMUAAAEcml0ZQAA8r8MAAABAAAACGRwc29ja2V0AAABRBEAAAAABGltZXIAAAG+DAAAAAgeAADy9QsAAAEAAPKCbmOEgAAAHQECADswAAABAAAGAAIA8wsAAP0MBQAABW1ibGVyAAABLwwAAAAygAECoIAAABGAZWl1AARzdGxzAAABLgwAAAAigAIA5mAAAASUZXXmBACABAbmQAAAAe+gMAAACUKwcBJ6AAEAY0MxbK2hEAAAFCywgBBfAAFmcnN5MGVob3B0AQABBDDLp/KxdDswAAABAAAIAAIA9AsAACYMBwABAQOgcAAAEoOgcAAAEPSwABMkAWdiaXLybgUAAAEAAQEBsNATrwABYvO9BQAAAQB1ASOAAqAwAAARxmFuAQACoCAAABMpoAAAAAcYYWQ7MAAAAQAAEwABAHgHEwABAgD0AQwAAAEAAQABAAACYXkAAAHCBAAAAAEIAPUnFAAAAQABAAEAAQDyAAABZgHuAQAA9YAQAAABAAEAAQABAAAGYXRjaGVzAAFkGVx2vWFtAb0MAAA7MAAAAQAADAADAFIRAADNEgUAMxQFADKAAgGwAAkUAAFtc2UAB2FrZXJlc3QAAPR3EwAAAQABAAEAAPIGaXRyYXJ5AQB0OzAAAAEAAAgAAgD0CwAAJgwHAADWrgCAhIjAAAAAAH9x5VeUTIU0bAwztS4cKOljXuxGAAEZCUYBCADzjRIAAAEAAQABhwIGdDM9D2kyjrBKGbRobQgrAADy8BIAAAEAAAAAAAD08hIAAAEAAQABAAD6XwEAZPSVEgAAAQABAAEAAAAKYmFuZG9ucGF0aAAA8jsUAAABAAByh2ljYXRpb24DAbBwE+IAAWFjZXDygAEBbHLygAEBcHUBAAKgAAAAEcawEBDEAAFzdfNLBwAAHAwBAAEBArBAFJYAAbAwEv4AAWdo9rEEAADoDAEAUgIBAHIAAAAAA6CAAAASurBQEuAAO7BwFBQAAWFjcwF+AAAAOzAAAAEAAAcAAQBGCQcAVgEAgAEEVKOiTpjwflCNYjhQeWRnc3QBBQA7MAAAAQAADQABAIERDQABgAJxxtXbwQiwUBNqAIplcAECAbBAEqIAAWUBsw8AABOAAmAeRZjyCGl0AYACHFDsBfY2Zd10UYIdb3QBAgGgcAAAEyplAd0MAAABAwA7MAAAAQAACAABAJ8QCAABgAOgQAAAEPWwwBRBAAGgYAAAEcVlc3UBgAKwkBOpAAFSiPiXms5pbQEDARAgysWQfmX1lw8AAAEAEwRsAAEAAAAAAPLABAAAAQDy6BAAAAEAGrcBATJ4psC9LnNwAYACsFAQ6wABoLAAABPhY3MBAwD09hAAAAEAsAABAAADcGluAADyAw0AAAEAABKBdwEBsBATrAABc2XSAAEDoFAAABLvb2NleQABvAwAAAEBADswAAABAAADAQIA0QwBAIgOAQEBAgD0UxEAAAEA9wIBAAAIcmVjZWl2ZWQAAPLUEAAAAQAA8oABAXRyMoABAqAAAAAMFHJucfZDXgDD0KBQAAAMDi8/LskZjR9KpWUs0WH6n+M51lJAj7Sci7FAFKYAAaBAAAAQWgAEIgBAe50dADowAAABAAAAAAAhABAAAAB5BHoEbAVtBUYHRwdKB0QJRQkIEGEQzxDQENEQ0hCeEp8S2BLZElQTVROFE4YTDxQQFBEUEhQTFDIUYRRiFIoUohSjFAACNTYAAAGWBQAAAAACMjgAAAGVBQAAAPKAAgAxMgANZGxpbWl0cmVhY2hlZAAA8hwTAAABAADygAIAYXMBAgGgQAAAEWhlASEQAAATgQKwIA+eAAFlbQGBAqBwAAAS4LDAEyAAAW54AQgA9KsRAAABAAEAAQABgAKgsAAAE82wMA+sAAFlaAEDAaBAAAAQ+mXyCAkAAAEAAQEAOzAAAAEAAAgAAwC6EgAALxMAAKYUBgAABG92ZWQAAAFDEQAAABqwAQFGVaY2WyxtcAAIaXNjb3ZlcnkAAAGUEgAAAAAEZGRlZAAAAT4QAAAA9gAAAnNzAwJy/c8fM6JhZHJldgA7MAAAAQAAFgAIAN0PAQD7EAEAPxEDALYRBQClEwMA9RMAAFQUAQCIFAEAEgABAV4NdU+EXGV28w8CAAABAAEAOjAAAAEAAAAAABIAEAAAAKYF7wyXD5gPtQ/TD2UQZhBnEGgQFREWEasSrBKtEq4SqxMXFBgUABJhdHRyYXZlcnNhbGFkZHJlc3MAAPJUFAAAAQAAAAdhZGRyZXNzAADyQREAAAEAAAgAAADyPxEAAAEAAPKBdgIANDYSgAEBsBAHVQABcG4ABHNpZ24AADswAAABAAAGAAIA8wsAAP0MBQAAAAVkcmVzcwAA8t0PAAABAAAygAIBsFAQbQDAZHN09gAAAAQBoBAAABA+YWlucmU7MAAAAQAABgACAH0AAACTAAUA9LwRAAABAAEAAQC2KgAAgYugIAAAB1p0Ig5mP82wsBMcAAE425l1iOGggAAAEa+gAAAAEykIAACXGE4AOjAAAAEAAAAAAA4AEAAAAAgCcwR0BHUE9Qv2C14MXwxgDPIN8w0qE0ATcRRyFAGCArAwEAkAAaCQAAAUfHJzAQABoEAAABJ+afIWEwAASgEBgwKwcBN8AAGwgBO1AAFjcwAAAADyDQIAAAEAOzAAAAEAAAUAAgBXDAEABBADAAgrAAD0HhMAAAEAlAABAAASCHJlcXVlbmN5AQF4vGGK24NjdfU8EAAAAQDFAAEAAQAAABBpY2l0aW5ndGhyZXNob2xkAADyJRQAAAEAAAgUAADykwUAAAEA8t4MAAABADKAAgJ35nkHCh2wAAdIAAFjbHR5dgEAAIODsEAREgABcAdiGMRoMAACiAAI8nsAAAABADswAAABAAANAAQA8gYHAFcHAQBbEAEADhEBAAADaXRoAAAB3A8AAAAAAm5pAADy8gsAAAoBAAh4AAABtA8AAAAACWFuZHNoYWtlcwAAAXYTAAAAAANydHQAAAEBEQAAAPIAAgAwaAD15xEAAJcBAQABAAEA8oACAGRy4gNlcHSEgPaQCOoDOxIAGPWSBQAALQABAAEAAQAAdgEAgAIEQcnZSjV1OE9VapYTY2tlaGl0AQUBoFAAABRDcwGEEgAACDgAAPTuCwAAAQABAAEA8tsRAAABABIEbmRvbgECsDAUOwABDIpH8F8xZXByAPTMDwAAAQDsAAEAUoABAi/rts6vkmFscwAAy8xa0tOWYncMSD61dj1JgKdbbCASUsPNp25JEXP7rUIS5TNrr2sSyvcV6NBpcM0VogVbRmfiw/xKqh7JdfYb8QW0IcagEAAACOx3x6gP4NehAAAAFF9cU+i94cwhpq1eUiodmDYOff8KmMn8gRKgcAAAE5ZXdfjS3Rw8dfMnFrpHAr3ag2hETUSX1H2AB6oNQlgVcgA7MAAAAQAAEQAIABgCAABPBwEACgkAAFYJAQDhEAEA7RABAHgRBQB/EgEAAIAaDuKQAAAAAOPgAAAAAeXQAAAACinH1Vzkrm0rqTZipW11s3vi/S0rmktyZ1KwJlqp4CbxwdMeLlPUEYxYZRuEgLhkn2ycvx90OU6dXOGoczTL9Nw9cUfTHgPkojc69FlSNVGEHnzI4hJTu4YkJQCdbKnULhHqoICjTmPbjzhPyBjYrFNizjbcSSK5tV5uQQcHUSttqq0bBBLSBlZf06AAAAAAAFrdh9+pZhmjBtVXfQPfRR1oTw8ZcbJvdjVnhNAZRaAQAAAFlmGvpqP2mW6Wtg2o46AAAAAAAaAAAAAAChGJF77Q82e9iIsRg2uAS6oxXCgtW2FiY2RlZmdoaWtsbW5vcHFyc3R1dnd6KTAxMjM0NTY4Pl1qeHk=","N":"ADJm","E":"OjAAAAAAAAA=","H":"3rjr8A16"},"crateNames":{"N":"j","E":"OjAAAAAAAAA=","H":"JFDJy0NX"},"name":{"N":"ADJm","E":"OjAAAAAAAAA=","H":"FOBShTna"},"path":{"N":"BBc","E":"OzAAAAEAAIkSuwACAAQACQAAAAsAbgB7AAEAhQAEAJAAAQCTACEAtwDyAKwBCAC2ASIA2gEOAO0BAADyAQAA9AEFAAACAQAEAgIACAIAAAsCBgAVAkYCXQQAAF8EEgBzBAIAdwQDAHwECgCIBAkAnAQAAK4EAgCyBAMAtwQAALkEAAC+BAAAwAQBAMQEnQBjBQEAZwUIAHEFCAB7BQIAgQUJAI8FAgCTBQUAnAUCAKAFAgCqBQAAtAUCALgFAgC/BfEAsgYEALgGAAC6BgAAvQY8APsGEQAPBwMAFAcDABkHGQA0BwwARgcBAEoHAABPBwEAUwcAAFUHAwBhBwAAYwcCAGgHAABrB3UA4gcDAOgHBgDwB/kA7AgAAO4IAQDyCAEA+QgAAP8IBQAJCQAACwkAAA4JAgAUCQEAHQkFACUJHQBECRQAWwmSAvALAQD4CwIA/QsAAAMMAQAKDAAADgwDAB0MAAAlDAkAMAwHADkMGQBUDBAAZgxRALkMAwDBDAAAxAwBAMcMAQDeDAEA/AyEAYMOAgCIDisBtQ8AAMAPAQDEDwEAyg8DANAPAQDTDwEA1g8BANwPAwDhD0AAIxACACgQFgBFEAEASBACAFMQAQBYEAQAXhADAGcQBQBwEAEAgBASAJQQIAC2EB8A2BADAOAQAADjEAEA5hABAOoQBwD2EAEA+xABAAERAwAGEQAACBECAAwRNwBKEQUAUxEBAFgRBQBhEQUAaxEBAHARVwDJEQAAzREAANIRAQDVEQEA3BEAAOURAQDoEQMA7RGmAJUSAACXEgAAmhIBAJ0SAACgEgMApRILALISAQC6EgMAvxIYANkSGADzEgIA/BIBAAcTEQAaEwMAIBMDACUTBwAvEwMANBMFAD4TAgBEEwAARhMBAEkTGwBmEwIAaxMFAHMTHwCVEwsAoxMFAKwTAQCzEyUA2hMEAOITBQDqEx0ACRQCAA4UEAAhFBsAPxQKAE0UBQBUFBYAbBQDAHEUGQCOFB4A","H":"k0x7Zu3T"},"entry":{"N":"ACJg","E":"OjAAAAEAAAAAAAUBEAAAAAAAAQAHAAgACgB6AH0AfgB/AIAAgQCKAI4AjwCSALUAtgCqAasBtQHpAeoB6wHsAe4B7wHzAfoB+wH8Af8BAgIHAhICEwJcBHIEdgR7BIcEkgSTBJQElQSYBJkEnQSeBJ8EoAShBKIEowSkBKUEpgSrBLEEtgS4BLoEvQS/BMIEYgVlBWYFcAV6BX4FfwWABYsFjAWNBY4FmQWaBaMFpAWlBaYFqQWrBa4FrwWwBbEFsgW3BbsFvAWxBrcGuQa7BrwG+gYNBw4HEwcYBzMHQQdCB0MHRAdFB0sHTgdZB1oHWwdcB2AHZwdpB2oH5gfnB+8H6wjxCPQI9Qj4CPwI/QgHCQgJEQkbCRwJIwkkCUMJWQnuC+8L8wv0C/UL9gv3C/sL/gsBDAIMBgwNDBQMFwwYDCAMIQwjDCQMLww4DGUMuAy9DL8MwAzKDMwM0QzSDNUM2AzZDNsM3AzdDOUM6wzuDO8M8QzyDIEOgg6GDocOuA+7D8IPww/JD9gPIhAmECcQPxBAEEwQTxBdEGIQZRBmEG4QbxByEHUQdhB7EHwQfxCTELUQ3BDlEOgQ9BD4EPkQ/RAFEQcRRRFIEUkRUhFXEWAR0RHXEdsR3RHeEeER5BHsEZkSpBKxEr4S2BLyEgATAhMDEzMTOhM7E0MTZROTE5QToROiE64TsRPZE+ETHxQgFEoUUxSMFI0U","H":"iniw6txe"},"desc":{"N":"GBe","E":"OzAAAAEAAIcNhQEAAAEABwABAAoAWgB4AAAAegAIAIoAAQCNAAIAkgAaALUA8gCqAQEAtQECANYBAgDaAQEA3wEBAOIBAADnAQgA8gEBAPoBAgD/AQAAAgIAAAUCAgANAgEAEgICABkCBgA4AgAASAICAEwCAABOAgAAUwIBAFYCAABYAgAAYAIBAGMCAgBoAgAAdAICAH4CAACEAgEAiAIAAIoCAACOAgAAlQIAAKUCBACrAgUAyAICAMwCAQDRAgAA5QIAAOoCAADwAgAA9gIBAP4CAAAEAwAADAMAABIDAAAUAwAAGwMAACEDAAApAwoAQQMAAFADFABSBAQAWAQAAFwEAABeBAAAYAQAAGIEAABkBAwAcgQAAHYEAAB4BAAAewQAAIQEAACHBAAAjQQBAJAEBQCYBAEAnAQKAKsEAACxBAAAtgQAALgEAAC6BAUAwgQBAMUEAgDJBAMA0gSLAGIFBABvBQEAcgUCAHoFAAB8BQUAiAUAAIsFAwCTBQMAmQUBAKEFBQCpBQAAqwUAAK4FBAC3BQEAugUCAMMF7ACxBgEAtAYBALcGAAC5BgAAuwYBAL8GAADDBgAAxgYIANAGAQDUBgsA4wYAAOoGAQDtBgEA8wYCAPcGAwANBwIAEQcAABMHAQAWBwsAKAcBADMHAAA3BwIAOwcCAEEHBABLBwAATgcCAFMHAABVBwcAYAcAAGcHAABpBwEAbwcAAHIHGQCUBzAAxgcBANgHAgDcBwIA5gcBAO8HAADxBwAA9QfsAOsIBADxCAAA9AgBAPgIAAD8CAEAAwkBAAcJAQARCQAAGwkBACAJBAAzCQEAPQkEAEMJAABHCQIASwkCAFYJAQBZCQIAZwnBACsKAAAtClIAgwplAe0LAgDzCwQA+wsBAP4LAAABDAEABgwAAAgMAQANDAAADwwBABQMAAAXDAEAIAwBACMMAQAmDAkAMQwCADUMAwA8DAEARAwAAE8MAwBXDAEAWgwDAF8MAQBjDAIAaQwAAGsMBQByDEIAuAwAAL0MAAC/DAEAygwAAMwMAADRDAEA1QwAANgMAQDbDAQA5QwAAOsMAADuDAQA/QwFAAUN7AD0DYsAgQ4BAIYOAwGdDwAApQ8DAKoPAQCyDwEAtQ8DALsPAADADwMAyQ8AAMwPAQDQDwQA1g8CANoPAQDdDwMA8Q8PAAIQAQAFEAAABxABAAsQEwAiEAAAJhABADwQAQA/EAEATBAAAE8QAABSEAAAWxADAGIQAABlEAEAaRADAG4QAQByEAAAdRABAHsQAQB/EAUAhxABAIwQAgCQEAAAkhABAJoQAQChEAEApRAEALQQAgC5EAEAvhABAMsQAwDcEAAA4xACAOgQAADtEAMA9BAAAPYQAwD7EAIAAhEBAAURAAAHEQAACxEAAA0RAgAVEQIAGRECAB0RBgApEQAANREBADkRCQBFEQAASBEBAFIRAABXEQAAWhEBAGARAABjEQMAahEAAHIRAAB0EQAAdhEAAHsRAAB9EQMAghEAAIQRAACGEQAAiREAAIsRAACNEQAAkREAAJQRAACWEQAAmBEAAJwRAACeEQAAoBEAAKIRAACkEQAAphECAKwRAACuEQAAuhEBAMIRAADFEQIAyREAANERAgDVEQIA2xEAAN0RAQDhEQAA5BEAAOwRjAB/EgQAhRIAAIsSAQCOEgEAkRIAAJMSAACVEgAAlxIAAJkSAACkEgAAqxIGAL4SAADBEgEAxBICAMkSAQDNEgUA1hICAOESAADmEgAA8hIAAAATAAACEwEABxMDABITAwAZEwEAMBMAADMTAgA6EwEAPhMCAEMTAABJEwQAWBMBAGUTAQBpEwAAaxMDAHcTBAB/EwAAgRMAAIoTAgCOEwIAkxMBAJYTAACZEwMAoRMBAK4TAACxEwAAtxMGAM0TAADQEwEA1xMCANsTAADeEwAA4RMAAPYTAQAKFAEADRQBABEUAAATFAAAGRQDAB8UAQAnFAQALxQAADEUAAAzFAUAPRQBAEcUAABKFAAAUxQAAFgUAwBlFAAAdRQFAHwUAACDFAAAhhQBAIwUAQCYFAAAnBQAAA==","H":"eR28HKea"},"function":{"N":"AAAc","E":"OzAAAAEAAJkDVwAAAAoAegAYALUAAQCqAQEAtQEAANkBAADpASMAEgICAFwEAABeBAAAcgQAAHYEAAB7BAAAhwQAAJIELQDCBAEAYgUAAGUFAQBwBQAAegUAAH4FAwCIBQAAiwUDAJIFLACxBgAAtAYAALcGAgC7BgIAwQYAAPoGAAANBwYAFgcAABgHAAAzBwAAPgcuAOEHAADmBwEA7wcAAOoIMwAjCQEAQwkAAFkJAQDuCzYALwwAADgMAABTDAAAZQwAALgMAAC9DAMAwgw5AIEOAwCGDgEAtA8AALYPHADVDwYA4A8AACIQAAAmEAEAPBAhAF8QIACTEAAAtRABANQQLAAFEQIACxEAAD8RMADHESUAlBIrANgSAQDwEhwAGRMAABwTEwAxExcAZRMAAGkTDACTEwIAnRMVANkTAADcEwAA3xMKAPsTEgAfFAUAOxQDAEgUCwBfFAAAaxQFAIsUBAA=","H":"vPSqyN7V"},"type":{"N":"AJo","E":"OzAAAAEAAP0SnAACAAQACQAAAAsAbgB7AAIAhQAFAI4AAACQAAEAkwAhALcA8gCrAQkAtgEyAO0BAQDyAQkAAAIBAAQCAgAIAgAACwIHABQCfQKYBAAAnAQCAKEEAACjBAAAqwQAAK4EAgCyBAMAtwQAALkEAAC+BAAAwAQBAMMEtgB7BREAjwUCAJMFBgCcBQIAoAUCAKoFAAC0BQYAvwWBAUMHBABKBwAATwcBAFMHAABVBwMAYQcAAGMHAgBnB34A5wcCAewIAADuCAEA8QgCAPkIAAD8CAEA/wgFAAkJAAALCQAADgkCABQJAQAdCdAC8AsBAPMLAwD4CwMA/QsAAAMMAQAKDAAADgwDABQMAAAXDAEAHQwAACAMAQAkDEAAZgxWAL8MAgDEDAEAxwwBAMoMAADRDAEA1QwAAN0MAgDxDAAA/Ay3ArUPAADADwEAxA8BAMkPBADQDwEA0w8BANYPAgDcD2IARRABAEgQAgBTEAEAWBAJAGcQBQBwEAEAgBBVANgQBADgEAAA4xABAOYQAQDqEAcA9hABAPsQAQABEUIASBEAAEoRBQBSEQIAWBEFAGERBQBrEQEAcBFXAMkRAADNEQAA0hEBANURAgDcEQAA5REBAOgRqwCVEgAAlxIAAJoSAQCdEgAAoBIDAKUSCwCyEgEAuhIDAL8SMgDzEgIA/BIBAAATAAACEwAABxMWACATAwAlEwcALxMDADQTBQA7EwAAPhMCAEQTAABGEwEASRMfAGsTBQBzEy0AoxMFAKwTAQCzEysA4hMFAOoTHQAJFAIADhQQACEUGwA/FAoATRQFAFQUFgBsFAMAcRQZAI4UHgA=","H":"XRSM5gMt"},"alias":{"N":"`","E":"OzAAAAEAAKwUAQAAAKwU","H":"p2IVDFMs"},"generic_inverted_index":{"N":"b","E":"OjAAAAAAAAA=","H":"6YSJCKEj"}}') \ No newline at end of file diff --git a/pr/451/docs/search.index/type/42acd02dfb5a.js b/pr/451/docs/search.index/type/42acd02dfb5a.js deleted file mode 100644 index 6d75cda5a..000000000 --- a/pr/451/docs/search.index/type/42acd02dfb5a.js +++ /dev/null @@ -1 +0,0 @@ -rd_("CBk[\"\",\"ADswAAABAAAFAAIAOwQEAPYTAAACyAYAAOcGAAA6MAAAAQAAAAAACgAQAAAA3QHeAVkFfAwQElQSaRLVE+ET/xMOFDowAAABAAAAAAAaABAAAAAMAg0CDgIPAhACEQISAkoEWAVdBZYGxAbzBjcJrRATElUS5xKUE5UT2BP7E/wT/RP+ExEUFRQ7MAAAAQAALAESAEgEAABFBQAAVwUAAMkGAADmBgAA9gYAADIMAwC7DAUAqg2GADcQAACuEAcADhEAAH0RhgAUEgAAFhIAAJ4SAADoEgAABxMAADowAAABAAAAAAAdABAAAABaBWAFYQViBcIGwwbKBssGzAbNBs4Gzwa1C7YLtwu4CwgMTA9QD18QxRAPEhsTWBNZE28T0BPUE+ITABQ7MAAAAQAAPQAUAD8DFABmBAAAcgQCAEMFAQBGBQAAWwUBAHUFAAC1BgwAwgcDALQLAAAeDAAApg8AANoPAABgEAAABxEAAJ8SAADjEgMAUBMAANETAAABFAAAOzAAAAEAAFcADQDbAD0AigEBAGUEAABwBwAAcwcAALIHAAC0BwAAKRADAEkSBQBAEwAAcBMAAK8TBQDCEwAAOzAAAAEAABUABwBMBAAAfQwAADEOAACiDwMAERIAAA4TBwDvEwUAOzAAAAEAABMACQBiBAEAawQBANMGBwCxBwAAswcAAEYPAgBPDwAAwBAAAHcSAAADEgcAABMHAABmEgAAAScMAAAFbQcAAG8HAAByBwAAJAwAAL8QAAAAOzAAAAEAAAUAAgDCAQAAjxMEAAAAAacFAAA=\"]Bo[\"AAAAAAAAAAACvRMAAL4TAAAAAAAAAp4QAACfEAAA\",\"\"]0Bg[\"\",\"AAAFGQkAABoJAAAbCQAAHAkAAB0JAAA=\"]AGg[\"AAAAAAKzAQAAwwEAAAAAAkMFAABEBQAABHAHAABzBwAAsgcAALQHAAA7MAAAAQAADQAFAHMFAQA/CQEALQwEAH0MAAA6EQMAOjAAAAEAAAAAAAsAEAAAAGsEbASxB7MHTw9pEGoQLBEtES4RLxF3EgRcBAAAEgcAABMHAAC/DwAABEIEAABEBAAAmxMAAJwTAAAFYQQAAG8HAAByBwAASg8AAEsPAAADIwwAACUMAABJDwAAOzAAAAEAAAYAAwDCAQAAsAYAAI8TBAAB7BIAAAEmDAAA\",\"AAAAAVYRAAAAAkgFAABJBQAAAAIBDAAAAgwAAAAFLQwAAC4MAAAvDAAAMAwAADEMAAAAAAJCBAAARAQAAA==\"]ANg[\"AAAEUgIAALgCAACfBgAAowYAAAMDDAAABAwAAAUMAAACswEAAMMBAAA7MAAAAQAAhgABAH0RhgAAOzAAAAEAAAcAAwBDBQEA4xIDACEUAQAEcAcAAHMHAACyBwAAtAcAADswAAABAAASAAcAcwUBAOgGAAA/CQEALQwEAH0MAAA6EQMAGxIDADowAAABAAAAAAAKABAAAABiBGMEawRsBLEHswdPDywRLREuES8RBVwEAABeBQAAEgcAABMHAADBEAAABEIEAABEBAAAmxMAAJwTAAAEbwcAAHIHAABKDwAASw8AAATFBgAAIwwAACUMAABJDwAABY8TAACQEwAAkRMAAJITAACTEwAAAewSAAAAAacFAAA=\",\"AAAAAAACSAUAAEkFAAAAAgEMAAACDAAAAcAPAAAFLQwAAC4MAAAvDAAAMAwAADEMAAAAAAJCBAAARAQAAAAAAAAAA94GAAAVEQAAFhEAAA==\"]Dc[\"\",\"AAAAADswAAABAAAHAAEAIgkHADswAAABAAAJAAIAuhAEABARBAAAAQ8RAAA=\"]ADm[\"AAAABrABAACyAQAAvwEAAMEBAADWDwAA1w8AAAS2AQAAyQEAADUKAAA5CgAAOjAAAAEAAAAAAAkAEAAAALEBtwG4AcABygHLAacKqgqdE54TOzAAAAEAAA0ABACsBAcAeg8AAGUQAwBvEwAAArsBAACfEgAAOzAAAAEAABUACgCmAQEArQEAACsMAQB2DAEAzQ8DAFQQAQBvEgMAQBMAAL0TAQAIFAEAAb8TAAAAAAAAAAAAAAGnBQAA\",\"AAABqwEAAAK0BAAABBIAAAAAAAJADwAATBAAAAAAAAAAAAAAAAAEoQYAAKUGAAB6DAAAewwAAA==\",1]Fk[\"AAAABOsEAAAVBwAAuAcAABkRAAACIwkAAPYLAAABrxAAAAABwwcAAAELAQAAAqMPAAAPEwAAAdQGAAA=\",\"AAAAAesEAAABtgEAAA==\"]Fk[\"AAAABDUFAAAZBwAAvAcAACARAAACJwkAAPoLAAABsxAAAAGOAQAAAcUHAAAAAqUPAAATEwAAAdgGAAA=\",\"AAAAATUFAAAByQEAAA==\"]AEa[\"AAAAAq4BAAC6AQAAOzAAAAEAAAcAAQAiCQcAAARgBQAAYQUAAGIFAAAPEgAAAAABvxMAAAAAAAKeEAAAnxAAAA==\",\"AAAAADswAAABAAAQAAMAlgYAACIJBwD1CwcAOzAAAAEAAAsABAD2BgAAoBAAALoQBAAQEQQAOzAAAAEAAA8ABQCsBAcAEwwCABgSAgBQEgAA1BMAADowAAABAAAAAAAKABAAAADLEM0QzxDRENMQDxEyETMRNBE1EVATBCkQAAAqEAAAKxAAACwQAAAAAAXKEAAAzBAAAM4QAADQEAAA0hAAAAAAAAAAAAKnBQAARQ8AAA==\",1]Ec[\"AAAAAAAAOzAAAAEAAAYAAgCsBgMAsQYCADswAAABAAAGAAIApwYEAAoJAQADXgQAAFMFAABUBQAA\",\"\"]Dg[\"AAAAAAAAAAABsBMAAAHwEwAAAAAAAAAAAAABVAcAAA==\",\"AAAAAAABVxIAAAE7EwAA\"]Eo[\"AAAAAekEAAABtQEAAAIqAAAAmQAAAAGuDwAAAUQDAAACBgEAAAcBAAA=\",\"AAABqAAAAAHpBAAAAf0SAAAAAe0LAAA=\"]Ic[\"AAAAASsFAAADxwEAABcTAACWEwAAAlgAAACmAAAABnsBAAB8AQAAQwQAAKYFAAC7DwAA4hMAAAFRAwAA\",\"AAABqQAAAAIrBQAA/xIAAAAB8QsAAAAAAAAAAAAAAAAAAAKqAAAAqwAAAA==\"]Ik[\"AAAAArwEAABuEgAAAfMGAAABkwYAAAGyAAAAAQcRAAABrxMAAAIREgAA7xMAAAAAAAAAAAAAOjAAAAEAAAAAAAkAEAAAAGcFkQZRB9IHiA+fD9EPWBCiED4R\",\"AAAAAbwEAAAAAVYSAAABOhMAAA==\"]Ck[\"AAAAAd0BAAAAAAAAAbMTAAAB8xMAAA==\",\"AAAAAAABWhIAAAE+EwAA\"]Ck[\"AAAAAd4BAAAAAAAAAbQTAAAB9BMAAA==\",\"AAAAAAABWxIAAAE/EwAA\"]Kk[\"AAAAAAAABFoFAABgBQAAYQUAAGIFAAAAOzAAAAEAAAkAAQCqCwkAOzAAAAEAAA0AAQBMBA0AOzAAAAEAAAkABAAfDAMARg8CAMAQAADUEAEAAAEnDAAAAyQMAABKDwAASw8AAAIlDAAASQ8AAA==\",\"AAAAAAABoBAAAAMTDAAAFAwAABUMAAA=\"]Nk[\"AAAAAAAAAnUSAAB2EgAABOMSAADkEgAA5RIAAOYSAAAC6RIAAOoSAAAF6AYAABsSAAAcEgAAHRIAAB4SAAACYgQAAGMEAAACXgUAAMEQAAAAAAHFBgAAAAAAAacFAAA=\",\"AAAAAAJWBQAAwxMAAAAF+RMAABwUAAAdFAAAHhQAAB8UAAABUBMAAALADwAA+hMAAAAAAAAAAAAAAAQuEAAALxAAADAQAAAxEAAA\"]Bg[\"AAAAAAAAAARTEQAAVBEAAHMTAAB0EwAA\",\"\"]Bk[\"\",\"AAAAAVIRAAAAAAAAAAAAAAAAAAAAAAFTDwAA\"]Cc[\"AAAAAAAAAV8QAAABYBAAAA==\",\"AAAAAAACOQkAADoJAAA=\"]FGo[\"AAAAADswAAABAAAVAAkADAIGAEoEAABrBQAA1AcAAP8LAAAyEAMAVRIAAJQTAQD7EwMAOzAAAAEAABMCEgBBBAAAWgQAAGAEAADJBgAA7wsDALsMBQDDDG0Bwg8AAMcPAAA3EAAAUxAAAK4QBwAOEQAAfRGGABQSAAAWEgAA6BIAAEsTAAA7MAAAAQAAwwAPALIAKAAZAXAAjAEPAEMEAABaBQAApgUAAKwGAwCxBgIAwgYBAOsLAwBEDwAAGxMAADoTBQDiEwAA9RMAADswAAABAACiAEIAPwMUAF0EAABnBAEAcgQCAEYFAABbBQEAdQUAAKcGBAC1BgwAvwcBAMIHAwDACAEACgkBACAJAQArCQEAAQwBABEMAQAeDAAAwQwBAEEPAQBRDwEAiQ8BAIwPAQCmDwAAyA8BANIPAQDaDwAAOBAAAEYQAQBdEAEAYBAAAMYQAQAPEQAAGhEAABwRAAAeEQAAIREAACMRAAAlEQAAMBEFAD8RAQBCEQIAUxEBAAUSAQBnEgEAnxIAANQSAQDaEgEA4xIDAAETAQBBEwEAURMBAFUTAQBzEwEAexMBAKUTAQC1EwEA0RMCAN0TAwDrEwMAARQAAA8UAQATFAEAFhQBABoUAQAhFAEAOzAAAAEAAI8AJgDbAD0AigEBAKYBAQBABAAAXgQAAGUEAABTBQEAbQUBAHAHAABzBwAAsgcAALQHAACqCwkAKwwBAHYMAQBNDwEAgg8BAMAPAADNDwMAVBABAKgQAQDCEAEAEhIAAF0SAQBvEgMA6RIBAAoTAQA2EwEASRMBAEwTAwBwEwAArxMFAL0TAQDCEwAA9xMBAPoTAAAIFAMAGBQBADswAAABAAAxAA0ATAQNAHMFAQDoBgAAPwkBABsMAAB9DAAAMQ4AAKIPAwA6EQMAGxIDAA4TBwBGEwEA7xMFADswAAABAAAcAA0AYgQBAGsEAQDTBgcAsQcAALMHAAAfDAMARg8CAE8PAABpEAEAwBAAANQQAQB3EgAAAhQAADowAAABAAAAAAAHABAAAABcBF4Fvw/BEDgRORFmEtkTA0IEAABEBAAAJwwAADowAAABAAAAAAAJABAAAABhBG0HbwdyByQMSg9LD54QnxC/EATFBgAAIwwAACUMAABJDwAAOzAAAAEAAAUAAgCwBgAAjxMEAAABJgwAAAGnBQAA\",\"AAAAAAA7MAAAAQAA5gABAMMM5gAAOjAAAAEAAAAAAFkAEAAAAGcEaAS/B8AHwAjBCCAJIQkrCSwJAQwCDBEMEgzBDMIMQQ9CD1EPUg+JD4oPjA+ND8gPyQ/SD9MPRhBHEF0QXhDGEMcQGhEcER4RIREjESURMBExET8RQBFTEVQRBRIGEmcSaBLUEtUS2hLbEgETAhNBE0ITURNSE1UTVhNzE3QTexN8E6UTphO1E7YT0hPTE90T3hPfE+AT6xPsE+0T7hMPFBAUExQUFBYUFxQaFBsUIRQiFDswAAABAAAvABQApgEBAG0FAQArDAEAdgwBAIIPAQDNDwMAVBABAKgQAQDCEAEAXRIBAG8SAwDpEgEAChMBADYTAQBJEwEATBMDAL0TAQD3EwEACBQDABgUAQAEPwkAAEAJAABGEwAARxMAAAACOBEAADkRAAAAAp4QAACfEAAA\"]Ic[\"AAAAARcRAAAABFcFAAAPCQAAEAkAAA4SAAA7MAAAAQAADAADAHUEBQDKBgUAjhMAAAADbAcAAMIQAADDEAAAASQQAAAAB8oQAADMEAAAzhAAANAQAADSEAAAOBEAADkRAAAAAW0HAAA=\",\"\"]Lk[\"AAACUwIAALcCAAAAArYBAADJAQAAOzAAAAEAAAsAAwC3AQEAygEBAK4QBwA7MAAAAQAABwABAKwEBwA6MAAAAQAAAAAABwAQAAAAvwfAB4kPig/SD9MPMBExEQACRhMAAEcTAAA=\",\"AAAAAYAPAAAAAAAAAAAAAAAAAAAAAAV/DwAAgQ8AAIgPAACLDwAASBMAAA==\"]AAg[\"AAAEVQIAALUCAAARCQAAEgkAAASsAQAAtAEAALkBAADGAQAAAloCAAC/AgAAAAA6MAAAAQAAAAAAEwAQAAAAZwRoBMAIwQg6EDwQPhBAEEEQyxDNEM8Q0RDTEGcSaBITFBQUFhQXFALpEgAA6hIAAAUtDAAALgwAAC8MAAAwDAAAMQwAAAAFyhAAAMwQAADOEAAA0BAAANIQAAAAAAACwgEAALAGAAA=\",\"AAAAAAAAAAAAAAAAAAAAAAAABMIIAADDCAAAxAgAAMUIAAA=\"]BKo[\"AAADDgkAANYSAADXEgAABaoBAAAXCgAAUhEAANwSAADdEgAAOjAAAAEAAAAAAAcAEAAAALUBxwETCRQJAQpSCnoKExIBFBIAAAEbEwAAOjAAAAEAAAAAABEAEAAAAMIHwwfEB8UHQA/ID8kPTBAaERwRHhEhESMRJRFBE0IT3RPeEwFAEwAAOzAAAAEAAAsAAgCiDwMADhMHADswAAABAAAJAAMA0wYHALEHAACzBwAAAAACbwcAAHIHAAA=\",\"AAAETQUAAE4FAAA4DwAAOQ8AADswAAABAAAWAAsAFAcHABgRAQAbEQAAHREAAB8RAQAiEQAAJBEAAAcSAQBtEwEAcRMAALsTAQACFxMAAJYTAAABDhIAAAAAAAAAAAAAAAAAADowAAABAAAAAABrABAAAACgAaEBowGkAQICAwIJAgoCSQRLBG0EtgS5BKAGpAbRBtIGBgcHB/wI/QgyCTMJ/gsADBYMFwwcDB0MKAwpDD4PPw9UD3sPfA+dD54P0Q8mECcQRBBFEFEQUhBWEFcQYRBiEJgQmRCiEKQQpRCrEKwQthC3ELgQuRA2ETcRShFLEQoSCxIMEg0SUhJTEl8SYBJrEm0SlBKVEpYSlxKZEpoSmxKcEtgS3hLfEuAS+xIMEw0TFhMcEx0THhMfEyATIRM4EzkTUxNUE1oTWxN5E3oTmROaE9YT1xM=\"]Ac[\"AAAAAAEwAwAA\",\"\"]Ca[\"AAAAAAJaAgAAvwIAADswAAABAAAFAAEATBEFAA==\",\"\",1]Ek[\"AAAAAAAAAAAAAAAAAAAAAAAAAsYGAADcBgAA\",\"AAAAAAACORAAADsQAAAAAjoQAAA8EAAAAAIbEgAAHBIAAA==\"]Eo[\"AAAAAAAAAAAAAAAAAAAAAAAAAscGAADdBgAA\",\"AAAAAAACPRAAAD8QAAAAAz4QAABAEAAAQRAAAAACHRIAAB4SAAA=\"]Ck[\"AAAAAAAAAAAAAAAAAAAAAAAABXAAAACkAQAArQcAAMwHAADECAAA\",\"\"]Bg[\"AAAAAAAAAUwPAAAAAAAAAjgRAAA5EQAA\",\"\"]Gk[\"AAAABGEAAADFBAAAagUAANUPAAABDgIAAAYOAAAASAQAAEUFAAAEBwAASBEAAJgTAAACxwAAAFAPAAAAAlcTAACuEwAA\",\"AAAAAcUEAAABvwgAAAFGBAAA\"]Ig[\"\",\"AAAAAAAAAAA7MAAAAQAACQABAKoLCQA7MAAAAQAADQABAEwEDQA7MAAAAQAACQAEAB8MAwBGDwIAwBAAANQQAQAAAScMAAAEJAwAAEoPAABLDwAAvxAAAAMjDAAAJQwAAEkPAAAAAAEmDAAA\"]Bg[\"AAAAAAAAAAAAAAAAAAKeEAAAnxAAAA==\",\"\"]Hk[\"AAAD0AEAAAoMAAANDAAAAegEAAABtQEAAAQpAAAAmAAAADkQAAA7EAAAAa0PAAABQwMAAAMFAQAAChEAAAsRAAA=\",\"AAAC0AEAAAYMAAACaQQAAOgEAAAB/BIAAAAB7AsAAA==\"]Hk[\"AAAD0QEAAAsMAAAODAAAASoFAAABxwEAAARXAAAApQAAAD0QAAA/EAAAAnoBAAC6DwAAAVADAAADXgQAAAwRAAANEQAA\",\"AAAC0QEAAAcMAAADagQAACoFAAD+EgAAAAHwCwAA\"]2ALo[\"AAAAAAEoEAAAAAA6MAAAAQAAAAAAEQAQAAAAuwFdBBEMEgzBDMIMQQ9CD8YQxxAFEgYS6xPsEw8UEBQaFBsUAmUEAABAEwAAOzAAAAEAAAsAAgCiDwMADhMHADswAAABAAAHAAEA0wYHAAAAAAACwgEAALAGAAA=\",\"AAAHTwUAAFAFAAAKDAAACwwAAA0MAAAODAAAFRIAADowAAABAAAAAAAXABAAAAC1B7YHHgkqCQwMDwwQDDoPOw9IEEkQShCXEBcRJxEoESkRKhErEUMTrBOtE8ATwRM7MAAAAQAA+QACADkO9QADEwMAOzAAAAEAAFkABAALAFUA2RIAAAgTAQByEwAAAfUTAAAAAAQ6EQAAOxEAADwRAAA9EQAABCwRAAAtEQAALhEAAC8RAAABZhIAAAAAAAAAAAdxBAAAZwUAANAGAAA2DgAAmhAAAKoQAAA+EQAA\"]Dk[\"AAAAAAL+CQAAUQoAAAAAADswAAABAAAFAAEASRIFAAACYgQAAGMEAAAAAkIEAABEBAAA\",\"\"]BOc[\"\",\"ADswAAABAAAFAAIAOwQEAPYTAAACyAYAAOcGAAA6MAAAAQAAAAAACgAQAAAA3QHeAVkFfAwQElQSaRLVE+ET/xMOFDowAAABAAAAAAAaABAAAAAMAg0CDgIPAhACEQISAkoEWAVdBZYGxAbzBjcJrRATElUS5xKUE5UT2BP7E/wT/RP+ExEUFRQ7MAAAAQAAKAERAEgEAABFBQAAVwUAAMkGAADmBgAA9gYAALsMBQCqDYYANxAAAK4QBwAOEQAAfRGGABQSAAAWEgAAnhIAAOgSAAAHEwAAOjAAAAEAAAAAABsAEAAAAFoFYAVhBWIFwgbDBsoGywbMBs0GzgbPBrULtgu3C7gLTA9QD18QxRAPEhsTWBNZE28T0BPiEwAUOzAAAAEAADsAEgA/AxQAZgQAAHIEAgBDBQEARgUAAFsFAQB1BQAAtQYMAMIHAwC0CwAAHgwAAKYPAADaDwAAYBAAAJ8SAADjEgMA0RMAAAEUAAA7MAAAAQAAUgALANsAPQCKAQEAZQQAAHAHAABzBwAAsgcAALQHAABJEgUAcBMAAK8TBQDCEwAAOzAAAAEAABQABgBMBAAAfQwAADEOAACiDwMADhMHAO8TBQA7MAAAAQAAEwAJAGIEAQBrBAEA0wYHALEHAACzBwAARg8CAE8PAADAEAAAdxIAAAISBwAAEwcAAAEnDAAABG8HAAByBwAAJAwAAL8QAAAABY8TAACQEwAAkRMAAJITAACTEwAA\"]Bc[\"AAAAAAAAAAAAAAACOBEAADkRAAA=\",\"\"]Kc[\"AAAAAAHiEgAAADswAAABAAAGAAIAygYFAMUQAAACRgUAANoPAAABZQQAAAADaRAAAGoQAAB3EgAAAb8PAAA=\",\"AAAAAAABYAQAADswAAABAAAFAAEAdQQFAAAAAAAAAAAAAAAABwsCAAC1BAAAuAQAAN8GAADgBgAA4QYAAOIGAAA=\"]Do[\"AAAAAAABGAAAAAAAAd8AAAAAAAAAAAAAAAACtQQAANEGAAA=\",\"AAAAAAAAAAACTQ8AAE4PAAA=\"]Dk[\"AAAAAAABMAAAAAEfAQAAAAAAAAAAAAAAAAACuAQAANIGAAA=\",\"AAAAAAAAAAABQAQAAA==\"]Ac[\"AAABYgIAAA==\",\"\"]Fg[\"AAAABO0EAAAWBwAAuQcAABsRAAADJAkAAPcLAAD8EwAAAbAQAAAAAAENAQAAARATAAAB1QYAAA==\",\"AAAAAe0EAAAAAbcBAAA=\"]Fg[\"AAAABDcFAAAaBwAAvQcAACIRAAADKAkAAPsLAAD+EwAAAbQQAAABkAEAAAAAARQTAAAB2QYAAA==\",\"AAAAATcFAAAAAcoBAAA=\"]Bk[\"\",\"AAAAAAAAAAA7MAAAAQAAPwACANsAPQCKAQEA\"]Ck[\"\",\"AAAAAAGWBgAAOzAAAAEAAAkAAgD2BgAA+AYIAAHUEwAAAVATAAA=\"]Bo[\"\",\"AAAAAAAAAAAAAAAAApsTAACcEwAAAAAAAewSAAA=\"]Ck[\"AAACYQIAAHMCAAA=\",\"AAAAAAAAAl0TAABeEwAAAAACOhEAADsRAAA=\"]Eo[\"\",\"AAAAAAA7MAAAAQAABQABAFYSBQA7MAAAAQAABQABADoTBQAAOzAAAAEAAAUAAQCvEwUAOzAAAAEAAAUAAQDvEwUA\"]Cc[\"\",\"AAAAAAL8EgAA/RIAAAAE6wsAAOwLAADtCwAA7gsAAA==\"]Eg[\"AAAAAfEEAAAAAS0AAAAAAAIVAQAAFgEAAA==\",\"AAA7MAAAAQAACgABAJICCgAC8QQAAOMTAAAAAAF1EgAA\"]Eo[\"AAAAATEFAAAAAV8AAAAAAAKKAQAAiwEAAA==\",\"AAA7MAAAAQAACgACABgDAwAdAwYAAjEFAADkEwAAAAABdhIAAA==\"]Dg[\"AAABAAMAAAEwBQAAAAFdAAAAAoUBAACGAQAA\",\"AAABDwMAAAEwBQAAAAAAAAFeBAAA\"]Eo[\"AAAAAAAAApUBAACWAQAA\",\"AAAAAAAAB9ATAAD5EwAAABQAABwUAAAdFAAAHhQAAB8UAAAC0RMAAAEUAAAB+hMAAA==\"]Bk[\"AAAAAAAAAUUBAAA=\",\"AAAB2AIAAAAAAVoEAAA=\"]Bg[\"AAAAATsFAAAAAAGXAQAA\",\"AAAAATsFAAA=\"]Bg[\"\",\"AAAAAAAAAAAAAAAAAAAAAAAAAXgMAAA=\"]Cc[\"AAAAAAAAAAAAAAAAAAAAAAAAA64HAADNBwAAxQgAAA==\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAnEEAACvBwAA\",\"\"]Cc[\"AAAAAAAAAAIKCQAACwkAAAAAAAAAAAACwgEAALAGAAA=\",\"\"]Fo[\"AAAAAAACVwUAABYSAAAAAAAAAAAAAAAAAAAE0wcAAFQPAABjEAAAVREAAA==\",\"AAF1BwAAAAAAAAAAAAAAAAAAAAAAAAJkEAAACREAAA==\"]Bk[\"AAAAAVkFAAAAAAFMDwAAAaYPAAA=\",\"AAGmBwAA\"]Bg[\"AAAAAAAABGUQAABmEAAAZxAAAGgQAAA=\",\"\"]Ag[\"AAAAAq4BAAC6AQAA\",\"\"]Dg[\"AAAAAAAAAAAAAj8JAABACQAA\",\"AAAAAAAAAAAABS0MAAAuDAAALwwAADAMAAAxDAAA\"]AEg[\"AAAAAAKzAQAAwwEAAAAAAkMFAABEBQAABHAHAABzBwAAsgcAALQHAAA7MAAAAQAACwAEAHMFAQAtDAQAfQwAADoRAwA6MAAAAQAAAAAACwAQAAAAawRsBLEHswdPD2kQahAsES0RLhEvEXcSBFwEAAASBwAAEwcAAL8PAAAEQgQAAEQEAACbEwAAnBMAAAVhBAAAbwcAAHIHAABKDwAASw8AAAMjDAAAJQwAAEkPAAA7MAAAAQAABgADAMIBAACwBgAAjxMEAAHsEgAAASYMAAA=\",\"AAAAAVYRAAAAAkgFAABJBQAAAAIBDAAAAgwAAAAAAAACQgQAAEQEAAA=\"]He[\"\",\"AAAAAAAEMgwAADMMAAA0DAAANQwAAAIIDAAA1BMAAAIHEQAAUBMAAAUpEAAAKhAAACsQAAAsEAAAQBMAAAEREgAAAAFmEgAAAAFtBwAAAAHCAQAAAAABpwUAAA==\",1]AMk[\"AAACvAEAAMUBAAAGrAEAALQBAAC5AQAAxgEAANwSAADdEgAABFgFAAA3CQAA/wkAAFAKAAAEMgwAADMMAAA0DAAANQwAAAJYEwAAWRMAADowAAABAAAAAAAHABAAAAAKCQsJRhBHENQS1RLSE9MTOjAAAAEAAAAAAAkAEAAAAEAEcAdzB7IHtAdND04PwA/3E/gTAAKxBwAAswcAAA==\",\"AAAAOjAAAAEAAAAAABAAEAAAAKIBTQdOB08HUAfVB9YHNgzDD8QPXBDIEMkQYxJkEp0S3BMDNAkAACgQAACYEgAABjIMAAAzDAAANAwAADUMAABqEgAAbBIAAAAAAAJzBQAAdAUAAAACXAQAAL8PAAAAA2EEAABKDwAASw8AAAMjDAAAJQwAAEkPAAAAAAImDAAAggwAADowAAABAAAAAAALABAAAAClAW4EbwS3BLoEjwaQBggHUQfSB9MHnw8=\"]Bg[\"AAAAAAAAAAAAAVcEAAA=\",\"AAAAAaQFAAA=\"]Bk[\"AAAAAAAAADswAAABAAANAAIAdQUAALUGDAA=\",\"\"]Cc[\"AAABAQkAAAABEgIAAAAAAAFABAAA\",\"AAAAAAAAAAFdBAAA\"]Bc[\"AAAAAAAAAAAAAAAAAAAAAbAGAAA=\",\"\"]Be[\"AAAAAAAAA2AFAABhBQAAYgUAAA==\",\"\",1]Bg[\"AAAAAAAAADswAAABAAAUAAEAPwMUAA==\",\"\"]Dk[\"AAAAAAHIAQAA\",\"AAAAAAUtCQAALgkAAC8JAAAwCQAAMQkAAAAAAAAAAAAAAAAAAAH9DwAA\"]Cc[\"AAAAAcQEAAAAAQ0AAAACvwAAAMAAAAA=\",\"AAAAAcQEAAA=\"]Bg[\"AAAAAAAAAAAAAVIEAAA=\",\"AAAAAcYHAAA=\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAWYAAAA=\",\"\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAaoAAAA=\",\"\"]C@i[\"AAAAAAHIAQAAAZ4SAAABbxMAAAK7AQAAnxIAADowAAABAAAAAAAbABAAAACtAW0FbgWCD4MPqBCpEF0SXhIKEwsTNhM3E0kTShNME00TThNPE1cTcBOuE/cT+BMKFAsUGBQZFDswAAABAAAGAAMAPwkBAKIPAwC/EwAAAQIUAAAB2RMAAAAAAcUGAAAAAAABpwUAAA==\",\"AAAFGQkAABoJAAAbCQAAHAkAAB0JAAAEAwwAAAQMAAAFDAAABBIAADowAAABAAAAAAAXABAAAACcAakEbgdxB8cHvgi/CC0JLgkvCTAJMQk0CR8QIBAhECIQIxCYEqESAxMEEwUTBhM7MAAAAQAAIAAOANkBAADbAQAAQQQAAEYEAABaBAAAYAQAAPgGCAAyDAMAQw8AAFMQAAC6EAQAEBEEAOESAABLEwAAOzAAAAEAACMACQBDBAAAdQQFAKYFAADQBwAARA8AAKkPEwBQEgEAXRMBAGATAQA7MAAAAQAACAAEADgQAAAyEQMAQhECAHgTAAA7MAAAAQAADAAGAEAEAABNDwEAKRADAAoRAwBXEwAArhMAAAQbEgAAHBIAAB0SAAAeEgAAOzAAAAEAAAYAAwAfDAMA1BABAAIUAAAEXAQAAL8PAADBEAAA2RMAAAAAAcUGAAABwgEAAAAAOjAAAAEAAAAAABwAEAAAAKwArQCuAK8Apga0BgEHAgcDB/oIOwk8CT0JPglBCUIJjg+nD6gPyg/LD8wPWRBaEFsQQREJEn0TIBQ=\",1]Bg[\"AAAAAAAAAAAAAAAAAAKeEAAAnxAAAA==\",\"\"]Jo[\"AAAAAc8EAAAAOjAAAAEAAAAAAAcAEAAAABkAlQDZAUYEQw9NEVARCBMCqg8AAFASAAACQAMAALYGAAAC4AAAAOEAAAAAAbEHAAAAAAFvBwAA\",\"AAF6BwAAAANhAAAAYgAAAM8EAAACWgIAAMcHAAACTREAAFARAAAAAagGAAA=\"]A@c[\"AAAAAfgEAAAABzIAAACdAAAA2wEAAE8RAABREQAA4RIAAAkTAAAHIgEAACMBAABEDwAAsg8AAL4PAABLEAAAURIAAARIAwAAuwYAAB4MAAB4EwAAARISAAABMQ4AAAKzBwAAAhQAAAHZEwAAAAFyBwAA\",\"AAGPBwAAAAH4BAAAAr8CAABWBQAAAk8RAABREQAAAa0GAAAAAAEbDAAAAAFmEgAAAAAAAAAABWYAAABnAAAAaAAAAGkAAABqAAAA\"]Bo[\"\",\"AAAAAAAAOzAAAAEAAKkAAwCyACgAGQFwAIwBDwA=\"]CDe[\"\",\"AAA7MAAAAQAA5gABAMMK5gA7MAAAAQAA7QAMAMoJMwAACgAAAgoyADYKAgA6ChUAUwoeAHMKBgB7CioAqAoBAKwKFgBPEgAA4xMBADowAAABAAAAAAASABAAAABdBaUFlgYTCRQJ/gn/CQEKNQo5ClAKUQpSCnIKegqmCqsK/BL9EjowAAABAAAAAAASABAAAABIBEUF9gYEBzkJOgmnCqoKSBFWElcSWBJZEloSWxIHE5gTnROeEzowAAABAAAAAAApABAAAADCBsMG0Ae1C7YLtwu4C+sL7AvtC+4LCAxQD3oPvg9LEF8QZRBmEGcQaBDFEHUSdhI6EzsTPBM9Ez4TPxNdE14TYBNhE9AT1BP5EwAUHBQdFB4UHxQ6MAAAAQAAAAAAGgAQAAAAXQRyBHMEdARbBVwFpwaoBqkGqgarBh4M2g86EDwQPhBAEEEQYBAPEUIRQxFEEVATeBPREwEUOzAAAAEAAFkADwDbAD0AigEBAEAEAABeBAAAZQQAAFMFAQBsBwAATQ8BAMAPAAASEgAASRIFAFcTAACuEwYAwhMAAPoTAAA7MAAAAQAAEQAIAHMFAQAbDAAAfQwAADEOAAAkEAAAOhEDAEYTAQDvEwUAOjAAAAEAAAAAAAgAEAAAAGIEYwRPD2kQahDUENUQdxICFDowAAABAAAAAAALABAAAABcBF4FEgcTB78PyhDMEM4Q0BDSEGYS2RMEQgQAAEQEAACbEwAAnBMAAARtBwAAbwcAAHIHAAC/EAAAAiMMAABJDwAAOzAAAAEAAAYAAwDCAQAAsAYAAI8TBAAB7BIAAAABpwUAAA==\",1]Le[\"\",\"AAAAAswBAACUBgAAA/QGAAA8DwAAPQ8AAAPmBgAAahIAAGwSAAA6MAAAAQAAAAAABwAQAAAATA8PEhgSGRIaElgTWROOEwJmBAAAtAsAAAGtAQAAAb8TAAAERg8AAEcPAABIDwAAwBAAAAABJwwAAARhBAAAJAwAAEoPAABLDwAAASUMAAAAAAEmDAAA\",1]Gc[\"\",\"AAAAOjAAAAEAAAAAAAcAEAAAAN0B3gEQEmkS1RPhE/8TDhQC8wYAACgQAAAFkwYAAA8JAAAQCQAAfQ8AAA4SAAABGxMAAAEHEQAAAUATAAA=\"]Bo[\"\",\"AAAAAv4SAAD/EgAAAATvCwAA8AsAAPELAADyCwAA\"]Bk[\"\",\"AAAAAAAAOzAAAAEAAAYAAgCsBgMAsQYCAA==\"]Fc[\"AAAAAcQBAAAAAAAABG0FAABuBQAAgg8AAIMPAAA=\",\"AAA7MAAAAQAAPgABADcMPgAAAAAAAAAAAAAAAAAAAAAB4wYAAA==\"]Ao[\"\",\"AAA7MAAAAQAA5gABANcH5gA=\"]A@o[\"AAAEaQIAAHoCAACAAgAAOA8AAAHNBAAAA+cSAAARFAAAFRQAAAMWAAAAlAAAAEwRAAABqQ8AADowAAABAAAAAAAMABAAAAA/A3IEcwRDBUQFWwW1Bj8RURN7E6UT3xPtEwLbAAAA3AAAAA==\",\"AAF5BwAAOzAAAAEAAAYAAwBRAgQAEQkAANYSAAABzQQAAAQTCQAA/gkAAP8JAAABCgAAAUwRAAAB6wsAAAOnBgAAQhEAAEMRAAAAAAAAAAAAAAAAATAPAAA=\"]ABc[\"AAAE3gIAAAEDAAAIAwAAOQ8AAAH1BAAAAAQuAAAAmwAAAE4RAADoEgAABBsBAAAcAQAAsA8AAOITAAA6MAAAAQAAAAAACgAQAAAARgN0BEYFXAW6BkARUhN8E6YT4BPuEwABMQ4AAA==\",\"AAGOBwAAB7UCAAC2AgAAtwIAALgCAAC6AgAAEgkAANcSAAAB9QQAAAQUCQAAUAoAAFEKAABSCgAAAu8LAABOEQAAAqwGAADQBwAAAUQRAAAAAAAAAAAAAAAABDIPAAAzDwAANA8AADUPAAA=\"]Gc[\"AAA7MAAAAQAACwAEABoQBAAAEwAApxMEABIUAAA=\",\"ADswAAABAAAJAAMAUgcBAFUHAgDPEgQAOzAAAAEAAAsABAAaEAQAABMAAKcTBAASFAAA\"]Ig[\"AAAAAAAAAAA7MAAAAQAACQABAKoLCQA7MAAAAQAADQABAEwEDQA7MAAAAQAACQAEAB8MAwBGDwIAwBAAANQQAQAAAScMAAAEJAwAAEoPAABLDwAAvxAAAAMjDAAAJQwAAEkPAAAAAAEmDAAA\",\"\"]Cc[\"AAAAArABAADIEAAAAAAAAAErDAAAAAFrBAAAARIHAAA=\",\"\"]Cc[\"AAAAAr8BAADJEAAAAAAAAAEsDAAAAAFsBAAAARMHAAA=\",\"\"]ABg[\"AAAAAu0SAADuEgAAAAW3AQAAuAEAAMoBAADLAQAADhEAADswAAABAAALAAMArAQHAFoFAAATDAIAOjAAAAEAAAAAAAcAEAAAAEYFwgfDB8QHxQdRD1IPpg8HChEAAAsRAAAMEQAADREAAHATAADCEwAA+hMAADswAAABAAANAAQAGwwAADEOAACiDwMADhMHADswAAABAAAHAAEA0wYHAAPBEAAAZhIAANkTAAAAAW0HAAABxQYAAAAAAAGnBQAA\",\"AAAAA9MBAADUAQAA1QEAAAABUxAAAA==\"]Ek[\"AAAAAAAAAAAAAAAAAAAAAAACUQUAAOkGAAA=\",\"AAAAAAAAAAIyEQAAMxEAAAAAAAAAAAAAAAACxgYAANwGAAA=\"]Ek[\"AAAAAAAAAAAAAAAAAAAAAAACUgUAAOsGAAA=\",\"AAAAAAAAAAI0EQAANREAAAAAAAAAAAAAAAACxwYAAN0GAAA=\"]Ak[\"\",\"AAAAAAKgDwAAoQ8AAA==\"]Eo[\"AAAABO8EAAAXBwAAugcAAB0RAAACJQkAAPgLAAABsRAAAAAAAQ8BAAABERMAAAHWBgAA\",\"AAAAAe8EAAAAAbgBAAA=\"]Eo[\"AAAABDkFAAAbBwAAvgcAACQRAAACKQkAAPwLAAABtRAAAAGSAQAAAAABFRMAAAHaBgAA\",\"AAAAATkFAAAAAcsBAAA=\"]Bg[\"AAAAAAAAAAAAAVEEAAA=\",\"AAAAATgJAAA=\"]Ag[\"AAACrwEAAL4BAAA=\",\"\"]Bg[\"AAAAAAAAAAAAAAAAAAIkDAAAvxAAAA==\",\"\"]Ag[\"\",\"AAACQhAAAEMQAAA=\"]Bg[\"AAAAAAAAAAAAARESAAA=\",\"AAABbwAAAA==\"]Dg[\"AAAAAtMBAADUAQAAAAADYAUAABMMAAAYEgAAAAGtAQAAAb8TAAA=\",\"AAAAAQQSAAA=\"]Hg[\"AAAAAScRAAABAxMAAAM5EAAAuxAAABERAAABXRMAAAEyEQAAASkQAAACOhEAABsSAAABLBEAAAAAAAABkBMAAA==\",\"AAAAAAAAAALLEAAAzRAAAAAAAALKEAAAzBAAAA==\"]Ig[\"AAAAASoRAAABBRMAAAM9EAAAvRAAABMRAAABYBMAAAE0EQAAASsQAAACPBEAAB0SAAABLhEAAAAAAAABkhMAAA==\",\"AAAAAAEIEQAAAAADzxAAANEQAADTEAAAAAAAA84QAADQEAAA0hAAAA==\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAm4AAAA1DwAA\",\"\"]Bk[\"AAAAAAAAAdoAAAAAAAFZBAAA\",\"AAAAAc8HAAA=\"]Cc[\"AAAAAVwQAAABFxMAAAACQwQAAKYFAAA=\",\"AAAAAAHUBwAA\"]Ek[\"AAAAAdQEAAAAAR0AAAAAAAHmAAAAAAAAAAAAAAAAArcEAACPBgAA\",\"AAF/BwAAAAHUBAAAAAAAAAAAAWkQAAA=\"]Ek[\"AAAAAQUFAAAAAT0AAAABNAEAAAAAAAAAAAAAAAAAAroEAACQBgAA\",\"AAGWBwAAAAEFBQAAAAAAAAAAAWoQAAA=\"]Be[\"AAAAAAA7MAAAAQAAhgABAH0RhgA=\",\"\",1]Bg[\"AAAAAAAAAAAAAVQEAAA=\",\"AAAAAeoLAAA=\"]Ec[\"AAAAAAAAAAAAAAAAAAJvBwAAcgcAAAHFBgAAAsIBAACwBgAA\",\"AAAAAAAAAAAAAAAAAAAAAcIBAAA=\"]Dk[\"AAAAAAAAAAABsRMAAAHxEwAA\",\"AAAAAAABWBIAAAE8EwAAAAAAAAAAAAAAAAABVAcAAA==\"]Dg[\"AAAAAR0FAAAAAAJfAQAAYAEAAAG+BgAAAAAAAAAAAAAAAAELAgAA\",\"AAAAAR0FAAA=\"]ABc[\"AAAAA6oBAAARCgAAUhEAAAPEBgAAcgoAAK0QAAAAADowAAABAAAAAAANABAAAAAgCSEJKwksCYwPjQ9dEF4Q2hLbEgETAhNVE1YTOzAAAAEAAA8ABwCoEAEASRIFAAoTAQBXEwAArhMAAAoUAQAYFAEAAAECFAAAAdkTAAA=\",\"AAAAA50BAACoAQAAJRAAAAQfEAAAIBAAACEQAAAiEAAAAtkBAADbAQAAAAACVxMAAK4TAAAAAQIUAAAB2RMAAAAAAAAAAATaAQAA3AEAAP4IAABVEQAA\"]Bk[\"AAAAAAHzBgAA\",\"AAAAAAAAAAEHEQAAAAEREgAA\"]Ho[\"AAAABcIEAABpBQAApAUAAHwMAABjEgAAAj0PAADYEwAAAuYGAACeEgAAA70AAAAPEgAAbxMAAAFEBQAAAWwHAAABJBAAAA==\",\"AAAAAsIEAACUBgAAAfQGAAAAAAABrQEAAAG/EwAA\"]Gc[\"AAAAAmQSAABxEwAAAZsGAAAByQYAAAEaAQAAAjgQAACfEgAAAAHoBgAAAAFeBQAAAAFtBwAAAcUGAAAAAAABpwUAAA==\",\"AAAAAAAAAAG7AQAA\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAW0AAAA=\",\"\"]Fg[\"AAADyAYAAOcGAACaDwAABA8MAABPEAAARxEAAMATAAAFDQIAAF0FAAClBQAAbgcAAC8JAAAAAsMAAAB6DwAA\",\"AAABphAAAA==\"]Fg[\"AAAABBAMAABQEAAASREAAMETAAACcQcAADAJAAAByQYAAAFiAQAAAAAB6AYAAAABXgUAAAAAAAAAAAGnBQAA\",\"AAABpxAAAA==\"]Dg[\"AAAAASIFAAAAAVEAAAABawEAAA==\",\"AAAAASIFAAAAAAAAAAAAAAAAAAAAAAFwAAAA\"]Bk[\"AAAAAAEMAgAAAAAAAAFOBAAA\",\"AAAAARkMAAA=\"]Cc[\"AAAAAAHHBwAAAAAAAAFPBAAA\",\"AAAAAAACGgwAAGUSAAA=\"]Dk[\"\",\"AAAAADswAAABAAATAAEAWAcTADswAAABAAATAAEAlAATADswAAABAAATAAEAqQ8TAA==\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAWsAAAA=\",\"\"]Fg[\"AAAAATwFAAAAAAGZAQAAAAAAAAAAAWEEAAAAAAABJgwAAAauAAAApQEAAAUCAAC0BgAACAcAAAkSAAA=\",\"AAGlBwAAAAE8BQAA\"]Hk[\"AAAEqAAAAFQCAABNBQAABgwAAAHqBAAAAAIrAAAAmgAAAAGvDwAAAkUDAAC5BgAAAggBAAAJAQAA\",\"AAABgAIAAARjAAAAZAAAAGUAAADqBAAAAbUBAAAAAe4LAAABqwYAAA==\"]Kc[\"AAAEqQAAALYCAABOBQAABwwAAAEsBQAAAARZAAAApwAAAMIPAADHDwAAA30BAAB+AQAAvA8AAAJSAwAAwAYAAA==\",\"AAABCAMAAAEsBQAAAccBAAAB8gsAAANDBAAApgUAALIGAAAAAAAAAAAAAAAAAARrAAAAbAAAAG0AAABuAAAA\"]Ak[\"AAAAAALYDwAA2Q8AAA==\",\"\"]Ao[\"AAAAAAAAAAACpgEAAEkSAAA=\",\"\"]Ao[\"AAAAAAAAAAACpwEAAEwSAAA=\",\"\"]Dc[\"AAAAAAAAAAABCgEAAAAAAAAAAAAAAAWsAAAAbgQAAAEHAACEDwAAWRAAAA==\",\"\"]Fc[\"AAAAAAAAAX8BAAAAAAAAAAAAAAAAAAWtAAAAbwQAAAIHAACFDwAAWhAAAA==\",\"AAAAAAAAAAAAAegGAAAAAl4FAADBEAAA\"]Fc[\"AAAAAT0FAAAAAAGaAQAAAmYEAAC0CwAAAAAAAAEnDAAAAAAAAAAFrwAAAAMHAAD7CAAAhg8AAFsQAAA=\",\"AAAAAT0FAAA=\"]Hg[\"AAABjQAAAAHQBAAAAAIaAAAAuwwAAAAAAeIAAAAAAAAAAAAAAAADoAEAALYEAAD+CwAA\",\"AAF7BwAAAAHQBAAAAAAAAAAAAAAAAAAAAAAECQcAAAoHAAANBwAADgcAAA==\"]Hg[\"AAAAApAAAAD/BAAAAAI4AAAAvgwAAAEsAQAAAAAAAAAAAAAAAAADoQEAALkEAAAADAAA\",\"AAGSBwAAAAH/BAAAAAAAAAAAAAAAAAAAAAAECwcAAAwHAAAPBwAAEAcAAA==\"]Bc[\"AAAAAAAAAAAAAAAAAAAAAAHsEgAA\",\"\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAWwAAAA=\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAmcAAADlBgAA\",\"\"]Bg[\"AAAAAAAAAAAAAVMEAAA=\",\"AAAAAboMAAA=\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAasAAAA=\",\"\"]Dk[\"AAAAAewEAAABtgEAAAABrQQAAAEaEQAAAQwBAAA=\",\"AAGIBwAAAAHsBAAAAAAAARoRAAA=\"]Dk[\"AAAAATYFAAAByQEAAAACjwEAALEEAAABIREAAA==\",\"AAGhBwAAAAE2BQAAAAAAASERAAA=\"]Hc[\"AAAAAAABfQ8AAAGOEwAAAAGyEwAAAfITAAAAAAAAAAAAADowAAABAAAAAAANABAAAADQBvoIPQk2DosPjg/KD5oQqhBBEWsSbRJIE30T\",\"AAAAAAABWRIAAAE9EwAA\"]Ak[\"AAAAAAKUEwAAlRMAAA==\",\"\"]Cg[\"AAAAAZ0SAAABNAkAAAFgBAAAAAABZQQAAA==\",\"AAAAAAH/CwAA\"]Ag[\"\",\"AAAAAAAAAbMGAAA=\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAmgAAABFDwAA\",\"\"]Ag[\"AAAAAAAAAAHBBgAA\",\"\"]Dk[\"AAAAAAAAOzAAAAEAAKkAAwCyACgAGQFwAIwBDwAAOzAAAAEAAD8AAgDbAD0AigEBAA==\",\"\"]Hg[\"AAAAAi0FAADqBgAAATEJAAAEWgAAAAgTAAAJEwAAchMAAAOAAQAAgQEAAPUTAAABUwMAAAABGwwAAAABZhIAAAAAAcUGAAA=\",\"AAABhw8AAAEtBQAAAcgBAAAAAUsQAAA=\"]Dk[\"AAAAAeAEAAAAASIAAAAAAbcGAAAC9gAAAPcAAAA=\",\"AAAAAuAEAAAXCgAAAAAAAakGAAA=\"]Dk[\"AAAAARcFAAAAAUgAAAACUQEAAFIBAAABvAYAAA==\",\"AAAAARcFAAABegoAAAABrgYAAA==\"]Ck[\"AAAAAcMPAAAAAAAAAnAHAACyBwAA\",\"AAAAAAAAAAACKRAAACoQAAA=\"]Ck[\"AAAAAcQPAAAAAAAAAnMHAAC0BwAA\",\"AAAAAAAAAAACKxAAACwQAAA=\"]Bk[\"\",\"AAAAAAAAAAAAAAAAAAAAAAAAAqMQAAAXEgAA\"]Ek[\"AAAAA9UHAAAHEgAAbRMAAAAAAAAAAAGxBwAAAAABbwcAAA==\",\"AAAAAAAAAAIyEQAAMxEAAAIpEAAAKhAAAA==\"]Ek[\"AAAAA9YHAAAIEgAAbhMAAAAAAAAAAAGzBwAAAAABcgcAAA==\",\"AAAAAAAAAAI0EQAANREAAAIrEAAALBAAAA==\"]Dg[\"AAAAAQsFAAAAAUQAAAACPwEAAEABAAA=\",\"AAAAAQsFAAAAAAAAAAEbDAAAAAFmEgAA\"]Cc[\"AAAB2AIAAAH7BAAAAAE0AAAAASYBAAA=\",\"AAAAAfsEAAA=\"]Ic[\"AAABjwAAAAHSBAAAAAIcAAAAvQwAAAAAAeQAAAAAAAAAAAAAAAA6MAAAAQAAAAAADAAQAAAA2gEJAgkHDQcCCQYJPg9/D0QQlBIMExwTOBM=\",\"AAF9BwAAAALSBAAAagUAAAABQw8AAA==\"]Ho[\"AAAAApIAAAABBQAAAAI6AAAAwAwAAAEuAQAAAAAAAAAAAAAAAAA6MAAAAQAAAAAADAAQAAAA3AEKAgsHDwcECQgJPw+BD0UQlRINEx0TORM=\",\"AAGUBwAAAAEBBQAAAAABRA8AAA==\"]Gk[\"AAACOgIAAEACAAAByAQAAAACEQAAAPoGAAACzQAAAM4AAAA=\",\"AAACPAIAAD0CAAAByAQAAAAAAAACTQ8AAE4PAAAAAAJcBAAAvw8AAAABvxAAAAEjDAAA\"]Do[\"AAAAAQMFAAAAAjsAAACeAAAAAzABAAAxAQAAsw8AAAFJAwAA\",\"AAAAAQMFAAAAAAAAAUAEAAA=\"]Ck[\"AAAAAnUTAAB2EwAAAAAAAAAAAAABJwwAAA==\",\"AAAAAAABoBAAAA==\"]Co[\"AAAAAAAB5gYAAAMYEgAAGRIAABoSAAAAAa0BAAA=\",\"AAAAAAABfQ8AAA==\"]K@m[\"AAA7MAAAAQAArAEDANcH5gBDCYYANww+ADswAAABAADrAUMAYQAEAJ0BAACiAQAAqAEAANIBBgBpBAEAvASGAFkFAABoBQIApAUAAKgF5gCXBgAA6gYAABQHBwBNBwMAqQcBALUHCQDGBwAAzwcAANUHAQAeCQAAKgkAADgJAADqCwAADAwAAA8MAQAZDAAANgwAAHwMAAC6DAAAOg8BAIAPAAC9DwAAww8BANQPAQAlEAAANhAAAEgQAgBPEAEAXBAAAJcQAADEEAAAyBABABcRAgAbEQAAHREAAB8RAQAiEQAAJBEAACcRBABFEQIASREAAAcSAQBUEgAAXBIAAGMSAQBuEgAAnRIAAEMTAgBtEwEAcRMAAHUTAgCXEwAArBMBALsTAQDAEwEA3BMAADswAAABAABWASwAnAEAALMBAADDAQAADAIGADADAABKBAAAqQQAAFYFAABYBQAAawUAAJsGAADEBgAAWAcTAG4HAABxBwAAxwcAANQHAAC+CAEALQkEADQJAAA3CQAA9QsHAP8LAAA5DvUAPA8BAKAPAQAfEAQAMhADAK0QAAAIEQAAExIAAFUSAACYEgAAoRIAAOISAADnEgAAAxMDABcTAACUEwIAwxMAANgTAAD7EwMAERQAABUUAAA7MAAAAQAAuQI2AAsAVQCUABMA2QEAANsBAABBBAAARgQAAEgEAABaBAAAYAQAAEUFAABIBQEAVwUAAMkGAADmBgAA+AYIAAQHAAAPCQEAOQkBAO8LAwAaDAAAuwwFAMMMbQFDDwAAfQ8AAMIPAADHDwAANxAAADkQAAA7EAAAPRAAAD8QAABTEAAAoBAAAK4QBwC6EAQADhEAABARBABIEQAAfRGGAA4SAAAUEgAAFhIAAFYSBQBlEgAAahIAAGwSAACeEgAA2RIAAOESAADoEgAABxMCAEsTAAByEwAAmBMAADswAAABAAAJASgAsgAoABkBcACMAQ8AQwQAAHUEBQBaBQAAYAUCAKYFAACsBgMAsQYCAMIGAQDKBgUA0AcAALULAwDrCwMAEwwCAEQPAABMDwAAUA8AAKkPEwC+DwAASxAAAF8QAADFEAAADxIAABgSAgBQEgEAGxMAADoTBQBYEwEAXRMBAGATAQBvEwAAjhMAANATAADiEwAA9RMAAPkTAAAAFAAAHBQDADswAAABAACzAE8APwMUAF0EAABmBAIAcgQCAEMFAQBGBQAAWwUBAHUFAACnBgQAtQYMAL8HAQDCBwMAwAgBAAoJAQAgCQEAKwkBALQLAAABDAEAEQwBAB4MAADBDAEAQA8CAFEPAQCJDwEAjA8BAKYPAADIDwEA0g8BANoPAAA4EAAAOhAAADwQAAA+EAAAQBABAEYQAQBMEAAAXRABAGAQAADGEAEAyxAAAM0QAADPEAAA0RAAANMQAAAPEQAAGhEAABwRAAAeEQAAIREAACMRAAAlEQAAMBEFAD8RAQBCEQIAUxEBAAUSAQBnEgEAnxIAANQSAQDaEgEA4xIDAAETAQBBEwEAURMBAFUTAQBzEwEAeBMAAHsTAQClEwEAtRMBANETAgDdEwMA6xMDAAEUAAAPFAEAExQBABYUAQAaFAEAIRQBADswAAABAACgACsA2wA9AIoBAQCmAQEAQAQAAF4EAABlBAAAUwUBAG0FAQBsBwAAcAcAAHMHAACyBwAAtAcAAKoLCQArDAEAdgwBAE0PAQCCDwEAwA8AAM0PAwApEAMAVBABAKgQAQDCEAEAChEDABISAABJEgUAXRIBAG8SAwDpEgEAChMBADYTAQBJEwEATBMDAFcTAABwEwAArhMGAL0TAQDCEwAA9xMBAPoTAAAIFAMAGBQBADswAAABAAA4ABAATAQNAHMFAQDoBgAAPwkBABsMAAAtDAQAfQwAADEOAACiDwMAJBAAADoRAwAREgAAGxIDAA4TBwBGEwEA7xMFADswAAABAAAgAA4AYgQBAGsEAQDTBgcAsQcAALMHAAAfDAMARg8CAE8PAABpEAEAwBAAANQQAQAsEQMAdxIAAAIUAAA6MAAAAQAAAAAADgAQAAAAXAReBRIHEwe/D8EQyhDMEM4Q0BDSEDgRORFmEtkTBUIEAABEBAAAJwwAAJsTAACcEwAAOjAAAAEAAAAAAAkAEAAAAGEEbQdvB3IHJAxKD0sPnhCfEL8QBMUGAAAjDAAAJQwAAEkPAAA7MAAAAQAABgADAMIBAACwBgAAjxMEAAHsEgAAASYMAAABpwUAAA==\",\"AAAAOzAAAAEAAOcAAgCoBeYAVhEAAAKbBgAACBEAADswAAABAADxAAMASAUBAPgGCADDDOYAAVESAAA6MAAAAQAAAAAAWQAQAAAAZwRoBL8HwAfACMEIIAkhCSsJLAkBDAIMEQwSDMEMwgxBD0IPUQ9SD4kPig+MD40PyA/JD9IP0w9GEEcQXRBeEMYQxxAaERwRHhEhESMRJREwETERPxFAEVMRVBEFEgYSZxJoEtQS1RLaEtsSARMCE0ETQhNRE1ITVRNWE3MTdBN7E3wTpROmE7UTthPSE9MT3RPeE98T4BPrE+wT7RPuEw8UEBQTFBQUFhQXFBoUGxQhFCIUOzAAAAEAAC8AFACmAQEAbQUBACsMAQB2DAEAgg8BAM0PAwBUEAEAqBABAMIQAQBdEgEAbxIDAOkSAQAKEwEANhMBAEkTAQBMEwMAvRMBAPcTAQAIFAMAGBQBAAQ/CQAAQAkAAEYTAABHEwAAAAI4EQAAOREAAAJCBAAARAQAAAKeEAAAnxAAAAAAAANxDwAAthIAANoTAAAB+wgAAA==\",1]Eo[\"AAAAAAA7MAAAAQAABQABAFYSBQA7MAAAAQAABQABADoTBQAAOzAAAAEAAAUAAQCvEwUAOzAAAAEAAAUAAQDvEwUA\",\"\"]Ck[\"AAAAAAAAAVoFAAAAAAFMBAAA\",\"AAAAAAAAAxMMAAAUDAAAFQwAAA==\"]Cc[\"\",\"AAAAAAAESAQAAAQHAABIEQAAmBMAAAAAAlcTAACuEwAA\"]Fc[\"AAAAAf4EAAAAATcAAAACKgEAACsBAAA=\",\"AAGRBwAAAAH+BAAAAAACvg8AAEsQAAACHgwAAHgTAAAAAAECFAAAAdkTAAA=\"]Eo[\"AAAAAUURAAABLQkAAAACuAAAAAgMAAABDxEAAAABUAQAAA==\",\"AAAAAAGlBQAAAAIIDAAAeg8AAAABbAcAAAEkEAAA\"]Dk[\"AAAABhASAABpEgAA1RMAAOETAAD/EwAADhQAAAAAAAABQBMAAA==\",\"AAAAAAAAAAEHEQAA\"]A@g[\"AAAAOjAAAAEAAAAAABIAEAAAAGkEvwRoBZcGxgc4CeoLGQw2DLoMvQ/UD8QQRhFUEkMTdxOXE9wTOjAAAAEAAAAAAAcAEAAAAC4JIxATEpgS4hLnEhEUFRQ6MAAAAQAAAAAABwAQAAAA2QFGBBoMQw+6EBARZRIHEzowAAABAAAAAAAHABAAAAC5AMUQUBLQE/kTABQcFB4UAUMFAAAAAAAAAAAAAY8TAAA=\",\"AAAAAb8EAAABvggAAAABCAwAAA==\"]ACk[\"AAAAOjAAAAEAAAAAAAgAEAAAAGoEHgkMDDoPgA+XECkRrBOtEzowAAABAAAAAAAHABAAAABKBFYFawXUB/8LCBFVEpYTOjAAAAEAAAAAAAoAEAAAANsBWgTCD8cPNxBTEA4RFBLhEugSSxM6MAAAAQAAAAAABwAQAAAAGQFED74PSxBREuITHRQfFAVGBQAAHgwAAHgTAADREwAAARQAAAQSEgAAcBMAAMITAAD6EwAAAhsMAAAxDgAAAQIUAAADwRAAAGYSAADZEwAA\",\"AAAAAAAAAAAAAAAAAAFtBwAAAAAAAAGnBQAA\"]Ho[\"AAAAAxQHAAC3BwAAGBEAAAMiCQAA9QsAAPsTAAABrhAAAAABwgcAAAACog8AAA4TAAAB0wYAAA==\",\"AAAAAAT1CwAA9gsAAPcLAAD4CwAAAAWsBAAArQQAAK4EAACvBAAAUBIAAA==\"]Ho[\"AAAAAxgHAAC7BwAAHxEAAAMmCQAA+QsAAP0TAAABshAAAAABxAcAAAACpA8AABITAAAB1wYAAA==\",\"AAAAAAT5CwAA+gsAAPsLAAD8CwAAAAWwBAAAsQQAALIEAACzBAAAURIAAA==\"]No[\"AAABjgAAAAHRBAAAAAIbAAAAvAwAAAAAAeMAAAAAAAAAAAAAAAA6MAAAAQAAAAAAJAAQAAAAAgJJBKAGBgf8CDIJFgwcDCgMew+dDyYQURBWEGEQmBCkEKsQthC4EBURNhFKEQoSDBJSEl8SmRKbEt4SHhMgE1MTWhN5E5kT1hM=\",\"AAF8BwAAAAHRBAAAAAAAAAAAAAAAAAAAAAAEAgkAAAMJAAAGCQAABwkAAA==\"]No[\"AAAAApEAAAAABQAAAAI5AAAAvwwAAAEtAQAAAAAAAAAAAAAAAAA6MAAAAQAAAAAAJAAQAAAAAwJLBKQGBwf9CDMJFwwdDCkMfA+eDycQUhBXEGIQmRClEKwQtxC5EBYRNxFLEQsSDRJTEmASmhKcEt8SHxMhE1QTWxN6E5oT1xM=\",\"AAGTBwAAAAEABQAAAAAAAAAAAAAAAAAAAAAEBAkAAAUJAAAICQAACQkAAA==\"]Cc[\"AAAAAecEAAAAAAAAAQMBAAA=\",\"AAAAAecEAAAAAAG1CwAA\"]Ck[\"AAAAASgFAAAAAAF3AQAA\",\"AAAAASgFAAAAAAO2CwAAtwsAALgLAAA=\"]Bk[\"AAAAAAAAAAAAAAAAAAFhBAAAAAAAASYMAAA=\",\"\"]Bg[\"\",\"AAAAAAAAAAAAAAAAAAAAAAAAATYPAAA=\"]Eo[\"AAAAARgFAAAAAUoAAAACVQEAAFYBAAABvQYAAA==\",\"AAAAARgFAAAAAAGvBgAAAAAAAAAAAAAAAAACTAUAADcPAAA=\"]Bk[\"\",\"AAAAAAAAAAAAAAAAAAAAAAAAAgwUAAANFAAA\"]Ck[\"AAAAAAAAAAABwhMAAA==\",\"AAAAAAAAAAAAAAAAAAAAAAABIxQAAA==\"]Ag[\"AAACmgIAABoDAAA=\",\"\"]Bo[\"AAAAAAAAAcEAAAAAAAABIQwAAA==\",\"AAAAAb0PAAA=\"]Cc[\"AAAAAAACGgwAAGUSAAABUA8AAA==\",\"AAAAAAABBAcAAA==\"]Gc[\"AAAAAfoEAAAAATMAAAABJQEAAAABEhIAAAEbDAAAAAFmEgAA\",\"AAGQBwAAAAH6BAAAAAHhEgAAAb4PAAAAARISAAAAAAAAAAAAAAABBQcAAA==\"]Do[\"AAAAAAAAAAFRDwAAAm0FAACCDwAA\",\"AAAAAa4BAAAAAAABUQ8AAARtBQAAgg8AAAoRAAALEQAA\"]Do[\"AAAAAAAAAAFSDwAAAm4FAACDDwAA\",\"AAAAAboBAAAAAAABUg8AAARuBQAAgw8AAAwRAAANEQAA\"]Ao[\"AAAAAAEoEAAAAAABTBAAAA==\",\"\"]Gc[\"AAABJhEAAAJjAAAAtQcAAAEQAgAAAAHJAAAAAnIEAABCEQAAAk0PAADADwAAAAFPDwAAAlwEAAC/DwAAAAIkDAAAvxAAAAIjDAAAJQwAAA==\",\"\"]Cc[\"AAAAAAAAAANdBAAAdAQAAEQRAAA=\",\"AAAAAAABwg8AAA==\"]Ec[\"AAABPAIAAAHJBAAAAAISAAAA+wYAAALPAAAA0AAAAA==\",\"AAABPwIAAAHJBAAAAAAAAkIRAABDEQAA\"]Bo[\"AAABphAAAAAAAALEAAAAxQAAAA==\",\"AAAAAAFdBQAA\"]Ck[\"AAABpxAAAAAAAAJmAQAAZwEAAA==\",\"AAAAAAAAAAAAAAABXgUAAA==\"]Ao[\"AAAAAAAAARsTAAABQA8AAA==\",\"\"]Hg[\"AAAAAmUAAADPBwAAARECAAABOQkAAAPVAAAAwgYAAF8QAAACWwUAANoPAAAAAnMFAAB9DAAABkYPAABHDwAASA8AAGkQAADAEAAAdxIAAAAAAkoPAABLDwAAAUkPAAA=\",\"\"]Dk[\"AAAAAAABOgkAAALDBgAA0AcAAAJcBQAAYBAAAAABdAUAAAFqEAAA\",\"AAAAAAABxw8AAA==\"]Bc[\"AAAAAAAAAAAAAAAAAAAAAAHsEgAA\",\"\"]ABc[\"AAABhw8AAAGUBgAAAsgBAAD0BgAABJMGAAAaDAAAfQ8AAGUSAAAC0BMAAAAUAAADBxEAANETAAABFAAABWwHAAAKEQAACxEAAAwRAAANEQAAASQQAAAAAAABbQcAAA==\",\"AAAABeoGAABFEQAARhEAAEcRAABJEQAAA5wBAAA8DwAAPQ8AAAFIEQAABfkTAAAcFAAAHRQAAB4UAAAfFAAAAXgTAAAB+hMAAAEREgAAAAAAAAAAAAJWDwAAcA8AAAUEAgAABQIAAIQPAACFDwAAhg8AAA==\"]Cc[\"AAAAARkFAAAAAUsAAAACVwEAAFgBAAA=\",\"AAAAARkFAAA=\"]Dc[\"AAAAAAAAAAA7MAAAAQAABQABAEkSBQA=\",\"AAAABNIBAADWAQAA1wEAANgBAAA=\"]Ao[\"AAAAAdIBAAAAAAAAAc0PAAA=\",\"\"]Ao[\"AAAAAdcBAAAAAAAAAc4PAAA=\",\"\"]Eo[\"AAAAASgRAAABBBMAAAM7EAAAvBAAABIRAAABXhMAAAEzEQAAASoQAAACOxEAABwSAAABLREAAAAAAAABkRMAAA==\",\"\"]Eo[\"AAAAASsRAAABBhMAAAM/EAAAvhAAABQRAAABYRMAAAE1EQAAASwQAAACPREAAB4SAAABLxEAAAAAAAABkxMAAA==\",\"\"]Bc[\"AAAAAAAAAnUEAADKBgAAAAHPDwAA\",\"\"]Bc[\"AAAAAAAAAngEAADNBgAAAAHQDwAA\",\"\"]Ec[\"AAAAAcwBAAAAAmoSAABsEgAAAlgTAABZEwAAAmYEAAC0CwAAAAAAAAABYQQAAA==\",\"AAAAAAEwAwAA\"]Gg[\"AAAAAcsEAAAAAhQAAAD9BgAAAtYAAADXAAAA\",\"AAADRQIAAEYCAABHAgAAAcsEAAAAAAAB2g8AAAACcwUAAH0MAAACaRAAAHcSAAAAAAABSQ8AAA==\"]Eg[\"AAAAAQYFAAAAAj4AAACgAAAAAzUBAAA2AQAAtQ8AAAFLAwAA\",\"AAAAAQYFAAAAAAAAAAF0BQAAAWoQAAA=\"]Ec[\"AAAAAAAAAWUBAAAAAAAAAAAAAAAAAAK7BAAAPgkAAA==\",\"AAAAAAAAAAAAAAAAAAAAAAAAAacFAAA=\"]Ec[\"AAAB8wsAAAG+BAAAAAELAAAAAbQAAAA=\",\"AAAAAb4EAAAAARAJAAAAAAAAAAAAAAAAAAABkQYAAA==\"]Dg[\"AAAAAAFYBQAAAAAAAAAAAAAAAAAAAAJkEAAACREAAA==\",\"AAF2BwAAAAAAAZMGAAA=\"]Ac[\"AAABZAIAAA==\",\"\"]Do[\"AAAAAd8EAAAAASEAAAAAAAL0AAAA9QAAAA==\",\"AAACcAIAAHECAAAB3wQAAAAAAAAAAUYTAAA=\"]Dk[\"AAAAARYFAAAAAUcAAAACTwEAAFABAAA=\",\"AAAC5AIAAOUCAAABFgUAAAAAAAAAAUcTAAA=\"]Bk[\"\",\"AAAAAAAAAAAAAAAAAAAAAAAAAhUJAAAXCQAA\"]Dk[\"AAAAAAAAAAABEwEAAA==\",\"AAABjQIAAAAAAAAAAAABYgQAAAESBwAAAUIEAAABbwcAAA==\"]Dg[\"AAAAAAAAAYgBAAA=\",\"AAABEwMAAAAAAAAAAAABYwQAAAETBwAAAUQEAAABcgcAAA==\"]Dk[\"AAAAAe4EAAAAAbcBAAABrgQAAAEcEQAAAQ4BAAA=\",\"AAGJBwAAAAHuBAAAAAAAARwRAAA=\"]Dk[\"AAAAATgFAAAAAcoBAAACkQEAALIEAAABIxEAAA==\",\"AAGiBwAAAAE4BQAAAAAAASMRAAA=\"]Ck[\"AAAAAQcFAAAAAT8AAAACNwEAADgBAAA=\",\"AAAAAQcFAAAAAAHDBgAA\"]Eg[\"AAAAAdoEAAAAAiAAAACWAAAAAasPAAABQQMAAALvAAAAXRIAAA==\",\"AAGEBwAAAWkCAAAC2gQAABEKAAA=\"]Eg[\"AAAAAREFAAAAAkYAAAChAAAAAkoBAAC2DwAAAUwDAAABXhIAAA==\",\"AAGcBwAAAd4CAAABEQUAAAFyCgAA\"]Ao[\"AAAAAAAAAAACVBAAAAoRAAA=\",\"\"]Ao[\"AAAAAAAAAAACVRAAAAwRAAA=\",\"\"]Do[\"AAABbwAAAAG9BAAAAAABswAAAA==\",\"AAAAAb0EAAAAAQ8JAAAAAAAAAAAAAAAAAAABWBAAAA==\"]Bg[\"AAAAAAAABNQAAABiBQAAFQwAABoSAAA=\",\"\"]Ak[\"AAAAAAABQAAAAAE5AQAA\",\"\"]Do[\"AAAAAAAAAAAAAAAAAAAAAAAAAqMQAAAXEgAA\",\"AAF0BwAAAAAAAAAAAAAAAAAAAAAAAAFjEAAA\"]Cg[\"AAAAAAPEBgAANwkAAK0QAAA=\",\"AAAAAAAAAAJADwAATBAAAA==\"]Ag[\"AAACjQIAABMDAAA=\",\"\"]Cc[\"AAAAATQKAAACNQoAAKYKAAABpwoAAAJlEAAAZxAAAA==\",\"\"]Ac[\"\",\"AAAAAU8SAAA=\"]Gg[\"AAAAAd4EAAAB2BMAAAAAAcAIAAAE8wAAAM8PAABwEgAAvRMAAAEkEAAA\",\"AAAAA7IBAADeBAAA4xMAAAABnRMAAAABwAgAAAPPDwAAcBIAAL0TAAA=\"]Bc[\"AAAAAbIBAAAAAAAAAAAAAcoQAAA=\",\"\"]Gg[\"AAAAARUFAAAAAAFOAQAAAcEIAAAD0A8AAHISAAC+EwAAAAAAAAFtBwAA\",\"AAAAA8EBAAAVBQAA5BMAAAABnhMAAAABwQgAAAPQDwAAchIAAL4TAAA=\"]Bc[\"AAAAAcEBAAAAAAAAAAAAAc4QAAA=\",\"\"]Fg[\"AAABRgIAAAHVBAAAAAEeAAAAAAAC5wAAAOgAAAA=\",\"AAGABwAAAAHVBAAAAAE5CQAAAsIGAABfEAAAA3IEAABzBAAAWwUAAA==\"]Eo[\"AAAAAQkFAAAAAUIAAAACOwEAADwBAAA=\",\"AAGYBwAAAAEJBQAAAAE6CQAAAdAHAAAEdAQAAFwFAABgEAAARBEAAA==\"]Ao[\"\",\"AAAAAAAAAAAAAAAB0hAAAA==\"]Ek[\"AAAAAeMEAAAAASUAAAAAAAL8AAAA/QAAAA==\",\"AAABdwIAAAHjBAAAAAAAAAFsBwAAASQQAAAAAsoQAADMEAAA\"]Eg[\"AAAAAR8FAAAAAVAAAAACYwEAAGQBAAA=\",\"AAAB8QIAAAEfBQAAAAAAAAAAAALOEAAA0BAAAAABbQcAAA==\"]Ao[\"AAAAAAAAAAFBEAAAAAExDAAA\",\"\"]Jk[\"AAAAAeQEAAAABSYAAACXAAAASAUAADkQAAA7EAAAArULAACsDwAABEIDAAABDAAAOhAAADwQAAAE/gAAAP8AAAAKEQAACxEAAAItDAAALgwAAAIsEQAALREAAA==\",\"AAAAAuQEAABPEAAAArMBAAAyEAAAAAABCgkAAA==\"]Og[\"AAAAASQFAAAABVMAAACkAAAASQUAAD0QAAA/EAAABnABAABxAQAAtgsAALcLAAC4CwAAuQ8AAARPAwAAAgwAAD4QAABAEAAAAgwRAAANEQAAAi8MAAAwDAAAAi4RAAAvEQAA\",\"AAAABCQFAACpBwAAqgcAAFAQAAAEwwEAADMQAAA0EAAANRAAAAAAAQsJAAACUwUAAFQFAAAAAAAAAAAAAAA7MAAAAQAACgACAKsHBQDKBwQA\"]Cc[\"AAAAARoFAAAAAUwAAAACWQEAAFoBAAA=\",\"AAAAARoFAAA=\"]Ck[\"AAABTwUAAAHlBAAAAAEnAAAAAAABAAEAAA==\",\"AAAAAuUEAAADDAAA\"]Dg[\"AAABUAUAAAElBQAAAAFUAAAAAXIBAAAAAAAAAAAAAcUGAAA=\",\"AAAAAiUFAAAEDAAA\"]Cc[\"AAAAAT4FAAAAAWAAAAABmwEAAA==\",\"AAAAAj4FAAAFDAAA\"]Dg[\"AAAAAAAAAAAAAAABwRAAAA==\",\"AAAABjYQAABcEgAARBMAAEUTAAB1EwAAdhMAAA==\"]Ak[\"AAAAAAAAAAABSxIAAA==\",\"\"]Ak[\"AAAAAAAAAAABThIAAA==\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAmoAAAAEAgAA\",\"\"]Bg[\"AAAAAAAAAAAAAVUEAAA=\",\"AAAAAcQQAAA=\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAmkAAAAFBwAA\",\"\"]Bk[\"AAAAAAAAAAAAAATTBgAA1AYAANUGAADWBgAA\",\"\"]Ec[\"AAAABJ0BAACoAQAA+QQAACUQAAAAAAEkAQAAAAAABNcGAADYBgAA2QYAANoGAAA=\",\"AAAAAfkEAAA=\"]Bg[\"AAAAAAAAAAAAAVYEAAA=\",\"AAAAAAHiEgAA\"]Cc[\"AAAAAToKAAACOQoAAKsKAAABqgoAAAJmEAAAaBAAAA==\",\"\"]Ag[\"AAAAAAAB9gYAAA==\",\"\"]No[\"AAAAAdwEAAAB9AYAAAGeEgAAAXoPAAAGEQwAAEYQAABBEwAAVRMAALUTAADdEwAAB/EAAACtAQAAdgwAAM0PAABvEgAATBMAAE4TAAABvxMAAAAAAAAAAAAAA6EGAAB6DAAAcxIAAA==\",\"AAAAAtwEAADWDwAAAAGxAQAAAXUSAAAGEQwAAEYQAABBEwAAVRMAALUTAADdEwAABXYMAADNDwAAbxIAAEwTAABOEwAA\"]Bo[\"AAAAAdYPAAAAAbEBAAAAAjoQAADLEAAAAAEtDAAA\",\"\"]Nc[\"AAAAARMFAAAAAAFMAQAAOjAAAAEAAAAAAAcAEAAAALsBEgxHEJ8SQhNWE7YT3hMFdwwAAM4PAABxEgAATRMAAE8TAAAAAAAAAAAAAAAEpwUAAKUGAAB7DAAAdBIAAA==\",\"AAAAAhMFAADXDwAAAAHAAQAAAXYSAAAGEgwAAEcQAABCEwAAVhMAALYTAADeEwAABXcMAADODwAAcRIAAE0TAABPEwAA\"]Bo[\"AAAAAdcPAAAAAcABAAAAAj4QAADPEAAAAAEvDAAA\",\"\"]Ao[\"AAAAAAAAAAACTBMAAE0TAAA=\",\"\"]Ao[\"AAAAAAAAAAACThMAAE8TAAA=\",\"\"]Dc[\"AAABRQIAAAHMBAAAAAIVAAAA/gYAAALYAAAA2QAAAA==\",\"AAABSQIAAAHMBAAA\"]Bk[\"AAAAAdUBAAAAAATTAAAAYQUAABQMAAAZEgAA\",\"\"]Ec[\"AAAAAv4SAAD/EgAAAvwSAAD9EgAABO8LAADwCwAA8QsAAPILAAAE6wsAAOwLAADtCwAA7gsAAA==\",\"\"]Cc[\"AAAAAAAAAAABShIAAA==\",\"AAF3BwAAAAKsAQAA3BIAAA==\"]Cc[\"AAAAAAAAAAABTRIAAA==\",\"AAGMBwAAAAK5AQAA3RIAAA==\"]Bc[\"\",\"AAAAAAAAAAAAAAAAAAABxQYAAA==\"]Cg[\"AAAAATsPAAAAAAFvAQAAATgQAAA=\",\"AAGoBwAAAAAAAUsTAAA=\"]Ek[\"AAAAAcMEAAAAAAG+AAAAAAAAAAAAAAAAAAAE2BIAAOASAAD7EgAAFhMAAA==\",\"AAF4BwAAAALDBAAAaQUAAA==\"]Cc[\"AAAAAAAAAAAAAAAAAAAAAAAAAzsJAADLDwAADBQAAA==\",\"\"]Cc[\"AAAAAAAAAAAAAAAAAAAAAAAAAzwJAADMDwAADRQAAA==\",\"\"]Bk[\"\",\"AAAAAAAAAAAAAAAAAAAAAAAAAhYJAAAYCQAA\"]Bc[\"AAAAAAABQQQAAA==\",\"AAAAAAGpBAAA\"]Ec[\"AAAAAfAEAAAAAbgBAAABrwQAAAIeEQAAcxMAAAEQAQAA\",\"AAGKBwAAAAHwBAAAAAAAAh4RAABzEwAA\"]Ec[\"AAAAAToFAAAAAcsBAAACkwEAALMEAAACJREAAHQTAAA=\",\"AAGjBwAAAAE6BQAAAAAAAiURAAB0EwAA\"]Ag[\"\",\"AAAAAAABDhIAAA==\"]Co[\"AAAAAqIBAACpBwAAAAABaAEAAA==\",\"AAAAAAAAAAAAAAAAAAAAAcIBAAA=\"]Ac[\"\",\"AAAAAAGhEgAA\"]Cg[\"AAAAAAAAAnUSAAB2EgAAAAABERIAAA==\",\"AAAAAAAAAAFQEwAA\"]Do[\"AAAAAQQFAAAAAjwAAACfAAAAAzIBAAAzAQAAtA8AAAFKAwAA\",\"AAAAAQQFAAAAAAABXQQAAA==\"]Ak[\"AAAAAAACnRMAAJ4TAAA=\",\"\"]Ac[\"AAAAAdYBAAA=\",\"\"]Ac[\"AAAAAdgBAAA=\",\"\"]Eo[\"AAAC+QIAAA8DAAABLgUAAAABWwAAAAKCAQAAgwEAAA==\",\"AAAAAS4FAAAAAAAFpwYAAKgGAACpBgAAqgYAAKsGAAA=\"]Fk[\"AAAABEgQAABJEAAARBMAAEUTAAABPA8AAAGgEAAAAQ8SAAAAAAG/EwAAAAAAAAAAAAEmDAAA\",\"AAAAAAAAAxgSAAAZEgAAGhIAAA==\"]Fc[\"AAAAAAABkwYAAAJ1EgAAdhIAAAEHEQAAAAEREgAAAAACmxMAAJwTAAAAAAAB7BIAAA==\",\"AAAAAAAAAdQTAAABUBMAAA==\"]Ag[\"\",\"AAAAAAABRQUAAA==\"]Co[\"AAAAAQwFAAAAAUUAAAACQQEAAEIBAAA=\",\"AAAAAQwFAAAAAAAAAAExDgAA\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAeQGAAA=\",\"\"]Jc[\"AAAAAtsEAAC7EwAAAAAAAgUSAADaEgAABPAAAACtAQAAKwwAAOkSAAADPwkAAEYTAAC/EwAAAAAAAZ4QAAA=\",\"AAGFBwAAAAKwAQAA2wQAAAAAAAIFEgAA2hIAAAIrDAAA6RIAAAI/CQAARhMAAAAAAAGeEAAA\"]Jg[\"AAAAAhIFAAC8EwAAAAABSwEAAAO7AQAABhIAANsSAAACLAwAAOoSAAACQAkAAEcTAAAAAAABnxAAAA==\",\"AAGdBwAAAAK/AQAAEgUAAAGbBgAAAAACBhIAANsSAAACLAwAAOoSAAACQAkAAEcTAAAAAAABnxAAAA==\"]Ao[\"AAAAAAAAAcYAAAAAAAFYBAAA\",\"\"]Bc[\"AAAAAAABSQAAAAJTAQAAVAEAAA==\",\"\"]Ag[\"AAAClQIAACIDAAA=\",\"\"]Ak[\"AAAAAAAAAnYEAADLBgAA\",\"\"]Ak[\"AAAAAAAAAnkEAADOBgAA\",\"\"]Ag[\"AAAClgIAAB4DAAA=\",\"\"]Dk[\"AAAAAcoEAAAAAhMAAAD8BgAAAtEAAADSAAAA\",\"AAABQAIAAAHKBAAAAAAAAAAAAU8PAAA=\"]Dg[\"AAAAAccEAAAAAhAAAAD5BgAAAssAAADMAAAA\",\"AAABOgIAAAHHBAAAAAAAAAHADwAA\"]Co[\"AAABFRIAAAEIBQAAAAFBAAAAAToBAAA=\",\"AAGXBwAAAQEJAAABCAUAAA==\"]Ec[\"AAAAAAABFhIAAAG1AAAAAAAAAAAAAAAAAAADbQQAAP4IAABTDwAA\",\"AAAAAaoBAAAAAAAAAUATAAA=\"]Hk[\"AAAC0gIAAOsCAAABJgUAAAABVQAAAAJzAQAAdAEAAAAAAAAAAAAAAAAABAgCAABMBQAApgYAAOMGAAA=\",\"AAABAAMAAALEAQAAJgUAAAAAAmATAABhEwAAAAACPBEAAD0RAAA=\"]Fc[\"AAAAAzYQAABKEAAAXBIAAAACahIAAGwSAAADmAEAAFgTAABZEwAAAmYEAAC0CwAAAAAAAAABYQQAAA==\",\"AAAAAcwBAAA=\"]Dc[\"AAAAAmIAAADGBAAAAg8CAAC/CAAAAQ8AAAAByAAAAA==\",\"AAAAAsYEAADVDwAA\"]Eg[\"AAAAAeEEAAAAASMAAAAAAAL4AAAA+QAAAAAAAAAAAAAAAAPfBgAALw8AADYPAAA=\",\"AAABcwIAAAHhBAAA\"]Eg[\"AAAAARsFAAAAAU0AAAACWwEAAFwBAAAAAAAAAAAAAAAAAAPhBgAAMQ8AADcPAAA=\",\"AAAB6wIAAAEbBQAA\"]Hk[\"AAAFKgIAAD0CAAA/AgAARwIAAEkCAAAB1gQAAAACHwAAAP8GAAAAAALpAAAA6gAAAA==\",\"AAADYQIAAGICAABkAgAAAtYEAACXBgAAAqUFAAAjEAAAAQcTAAABeg8AAAEPEQAA\"]Ek[\"AAAAAQoFAAAAAkMAAAAABwAAAj0BAAA+AQAA\",\"AAAB0gIAAAEKBQAAAAAAAAAAAAAAAAAAAAACuwQAAOQGAAA=\"]Bk[\"AAAAAAABNxAAAAFuAQAA\",\"AAAAAAAAAAE4EAAA\"]Fc[\"AAABjwAAAAHTBAAAAAG9DAAAAAAB5QAAAAAAAAAAAAAAAAQKBwAADgcAAAMJAAAHCQAA\",\"AAF+BwAAAALTBAAAaAUAAA==\"]Fc[\"AAAAApIAAAACBQAAAAHADAAAAS8BAAAAAAAAAAAAAAAAAAQMBwAAEAcAAAUJAAAJCQAA\",\"AAGVBwAAAAECBQAAAWsFAAA=\"]Ag[\"AAAClAIAAB0DAAA=\",\"\"]Cg[\"AAAAAUAFAAAAAAADQA8AAKYPAABMEAAA\",\"AAGnBwAAAAFABQAA\"]Dc[\"AAAAAiAFAACqBwAAAAABaQEAAA==\",\"AAAAASAFAAAAAAAAAAAAAAAAAAGwBgAA\"]Dg[\"AAAAAe0SAAAB2A8AAAAAAALrAAAACxEAAA==\",\"AAEMCQAAAa8BAAAB7RIAAAHYDwAA\"]Dg[\"AAAAAe4SAAAB2Q8AAAABRgEAAAABDREAAA==\",\"AAENCQAAAb4BAAAB7hIAAAHZDwAA\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAATQPAAA=\",\"\"]Ac[\"\",\"AAAAAAHDEwAA\"]Ag[\"AAAClwIAAB8DAAA=\",\"\"]Bc[\"\",\"AAAAAAAAAAADSRIAAEoSAABLEgAA\"]Bc[\"\",\"AAAAAAAAAAADTBIAAE0SAABOEgAA\"]Ak[\"\",\"AAAAAW4SAAAAAAGOEwAA\"]ADc[\"AAAAAdcEAAAAAAA6MAAAAQAAAAAAFAAQAAAAvwfBDEEPiQ+MD8gPxhA/EVMRZxLUEnsT0hPfE+sT7RMPFBMUFhQaFCEUOjAAAAEAAAAAAAwAEAAAAOwAVBDCEF0SbxJwEgoTNhNJE/cTCBQKFBgUAAABOBEAAA==\",\"AAGBBwAAAAHXBAAAAAAAOjAAAAEAAAAAABQAEAAAAL8HwQxBD4kPjA/ID8YQPxFTEWcS1BJ7E9IT3xPrE+0TDxQTFBYUGhQhFDowAAABAAAAAAAJABAAAABUEMIQXRIKEzYTSRP3EwgUChQYFAAAATgRAAA=\"]ADc[\"AAAAAQ4FAAAAAAFHAQAAOjAAAAEAAAAAABQAEAAAAMAHwgxCD4oPjQ/JD8cQQBFUEWgS1RJ8E9MT4BPsE+4TEBQUFBcUGxQiFDowAAABAAAAAAALABAAAABVEMMQXhJxEnISCxM3E0oT+BMJFAsUGRQAAAE5EQAA\",\"AAGZBwAAAAEOBQAAAAAAOjAAAAEAAAAAABQAEAAAAMAHwgxCD4oPjQ/JD8cQQBFUEWgS1RJ8E9MT4BPsE+4TEBQUFBcUGxQiFDowAAABAAAAAAAJABAAAABVEMMQXhILEzcTShP4EwkUCxQZFAAAATkRAAA=\"]Ag[\"AAACcQIAAOQCAAA=\",\"\"]Bk[\"AAAAAAAAAAAAAU0EAAA=\",\"AAAAAAAAAQgMAAA=\"]Bk[\"AAAAAAAAAAAAAAEgDAAA\",\"AAAAAAABmBMAAA==\"]Do[\"AAAAAeIEAAAAASQAAAAAAAL6AAAA+wAAAAAAAAAAAAAAAALgBgAAMA8AAA==\",\"AAAAAeIEAAA=\"]Do[\"AAAAARwFAAAAAU4AAAACXQEAAF4BAAAAAAAAAAAAAAAAAALiBgAAMg8AAA==\",\"AAAAARwFAAA=\"]Ag[\"AAACmQIAACEDAAA=\",\"\"]Fc[\"AAAAAR4FAAAAAk8AAACiAAAAAmEBAAC3DwAAAk0DAAA4EAAA\",\"AAAAAR4FAAAAAAAAAAAAAAABbQcAAAAAAAABpwUAAA==\"]Ac[\"\",\"AAAAAAFuBwAA\"]Ao[\"AAAAAAGpBAAA\",\"AAAAAAFxBwAA\"]Ag[\"AAACkgIAABkDAAA=\",\"\"]Ag[\"AAACkwIAABsDAAA=\",\"\"]Dg[\"AAAAAfIEAAAAAAAB4xIAAAAAAAHMEAAA\",\"AAAAAvIEAAA0CgAAATUKAAAAAWUQAAA=\"]Do[\"AAABzQEAAAEyBQAAAAAAAeUSAAAAAAAB0BAAAA==\",\"AAAAATIFAAABpgoAAAGnCgAAAWcQAAA=\"]Do[\"AAAAAfQEAAAAAAADPBAAAM0QAADkEgAAAAEuDAAA\",\"AAAAAvQEAAA6CgAAATkKAAAAAWYQAAA=\"]Eg[\"AAABzgEAAAE0BQAAAAAAA0AQAADREAAA5hIAAAABMAwAAA==\",\"AAAAATQFAAABqwoAAAGqCgAAAWgQAAA=\"]Cc[\"AAAAAAAAAncEAADMBgAAAAEEAQAA\",\"AAGHBwAAAAG0AQAA\"]Cc[\"AAAAAAAAA3kBAAB6BAAAzwYAAA==\",\"AAGgBwAAAAHGAQAA\"]Cc[\"AAAAAAAAAAAAAAAAAAAAAAAAA94GAACwBwAAzgcAAA==\",\"\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAATMPAAA=\",\"\"]Ao[\"AAAAAAAAAawEAAAAAQgUAAA=\",\"\"]Bk[\"AAAAAAAAAbAEAAAAAQkUAAA=\",\"AAAAAbQEAAA=\"]Eg[\"AAAAAAAAAAABEQEAAAAAAAAAAAAAAANBCQAApw8AAJYSAAA=\",\"AAGLBwAAAAS3BwAAuAcAALkHAAC6BwAA\"]Eg[\"AAAAAAAAAZQBAAAAAAAAAAAAAAAAAANCCQAAqA8AAJcSAAA=\",\"AAGkBwAAAAS7BwAAvAcAAL0HAAC+BwAA\"]Bo[\"AAAAAAABsQEAAAAAAXYMAAAAAWIEAAAAAUIEAAA=\",\"\"]Bo[\"AAAAAAABwAEAAAAAAXcMAAAAAWMEAAAAAUQEAAA=\",\"\"]Ak[\"AAAAAAABCBMAAAFvEwAA\",\"\"]Ao[\"AAAAAAABCRMAAAAAAXATAAA=\",\"\"]Fo[\"AAAAASMFAAAAAlIAAACjAAAAA2wBAABtAQAAuA8AAAFOAwAA\",\"AAAB+QIAAAEjBQAAAAAAAAJTBQAAVAUAAAAAAAAAAALCAQAAsAYAAA==\"]Dg[\"AAAAAfwEAAAAATUAAAABJwEAAA==\",\"AAAAAfwEAAAAAAAAAAAAAAAAAAAAAAHlBgAA\"]Dg[\"AAAAAcEEAAAAAgwAAAD4BgAAArsAAAC8AAAA\",\"AAABKgIAAAHBBAAAAAABxRAAAA==\"]Do[\"AAAAAfcEAAAAAjEAAACcAAAAAyABAAAhAQAAsQ8AAAFHAwAA\",\"AAAAAfcEAAAAAAAAAWUEAAA=\"]Ek[\"AAAAAdgEAAAAAAACURMAAKUTAAAD7QAAAKgQAAA2EwAA\",\"AAGCBwAAAAHYBAAAAAAAAlETAAClEwAAAagQAAA=\"]Ek[\"AAAAAQ8FAAAAAAFIAQAAAlITAACmEwAAAqkQAAA3EwAA\",\"AAGaBwAAAAEPBQAAAAAAAlITAACmEwAAAakQAAA=\"]Bo[\"AAAAAAAAAAABUwUAAAAAAAAAAALCAQAAsAYAAA==\",\"\"]Fg[\"AAAAAdkEAAAAAAAEIAkAANIPAAAwEQAAARMAAALuAAAASRMAAA==\",\"AAGDBwAAAAHZBAAAAAAABCAJAADSDwAAMBEAAAETAAA=\"]Fg[\"AAAAARAFAAAAAAFJAQAABCEJAADTDwAAMREAAAITAAABShMAAA==\",\"AAGbBwAAAAEQBQAAAAAABCEJAADTDwAAMREAAAITAAA=\"]Ag[\"\",\"AAAAAAAAAdQTAAA=\"]Bc[\"\",\"AAAAAAAAAAAAAAAAAAAAAAHsEgAA\"]Ag[\"\",\"AAAAAAAAAVAPAAA=\"]Co[\"AAAAAf0EAAAAATYAAAACKAEAACkBAAA=\",\"AAAAAf0EAAAAAAAAARISAAA=\"]Fg[\"AAABUQIAAAHmBAAAAAEoAAAAAAG4BgAAAgEBAAACAQAA\",\"AAACegIAAJ8GAAAB5gQAAAAAAAGqBgAAAAAAAAAAAAAAAAEvDwAA\"]Gc[\"AAABugIAAALEAQAAJwUAAAABVgAAAAJ1AQAAdgEAAAG/BgAA\",\"AAACAQMAAKMGAAABJwUAAAAAAbEGAAAAAAAAAAAAAAAAAAIIAgAAMQ8AAA==\"]Cc[\"AAAAAAAAAAAAAAAAAAAAAAAAA6wHAADLBwAAwwgAAA==\",\"\"]Cg[\"AAABdwIAAAAAAAAAARQBAAA=\",\"AAAAAAAAAAI6EAAAPBAAAA==\"]Cg[\"AAAB8QIAAAAAAAGJAQAA\",\"AAAAAAAAAAM+EAAAQBAAAEEQAAA=\"]Bo[\"AAAAArQEAAA/BQAAAaESAAA=\",\"AAAAAj8FAABWEQAA\"]Bo[\"AAAAAAAAAcIAAAAAAAABIgwAAA==\",\"AAAAAXcTAAA=\"]Io[\"AAAAAS8FAAAAAVwAAAABhAEAAAZ1BQAAyxAAAM0QAADPEAAA0RAAANMQAAAAAAAFyhAAAMwQAADOEAAA0BAAANIQAAAAAAAAAAABIBQAAA==\",\"AAAAAS8FAAAAAARdEwAAXhMAAGATAABhEwAAAAFeBAAA\"]Co[\"AAAAAmQAAAC2BwAAAAABygAAAAJzBAAAQxEAAAFODwAA\",\"AAABJhEAAA==\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAhUJAAAuEAAA\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAhcJAAAwEAAA\",\"\"]Co[\"AAAAAfMEAAAAAAAAAhcBAAAYAQAA\",\"AAAAAfMEAAAAAAJlEAAAZhAAAA==\"]Dc[\"AAAAATMFAAAAAAKMAQAAjQEAAA==\",\"AAAAAjMFAABPEgAAAAACZxAAAGgQAAA=\"]Cc[\"AAAAAAGcAQAAAAG3AAAAAAAAAR8MAAA=\",\"AAAAAZcTAAA=\"]Ac[\"\",\"AAAB8wsAAA==\"]Ec[\"AAABcAIAAAHOBAAAAAEXAAAAAAAC3QAAAN4AAAA=\",\"AAAAAc4EAAAEEwkAAP4JAAD/CQAAAQoAAA==\"]Do[\"AAAB5QIAAAH2BAAAAAEvAAAAAh0BAAAeAQAA\",\"AAAAAfYEAAAEFAkAAFAKAABRCgAAUgoAAA==\"]Ak[\"\",\"AAAAAAACnRMAAJ4TAAA=\"]Ck[\"AAAAAsAEAAAqCQAAAb4IAAAB2RIAAAG6AAAA\",\"AAAAAsAEAADUDwAA\"]Cg[\"AAAAAU0HAAACHxAAADIQAAAAAbULAAAAAAAAAAABnhAAAA==\",\"\"]Ck[\"AAAAAU4HAAACIBAAADMQAAAAArYLAAC3CwAAAAAAAAAAAZ8QAAA=\",\"\"]Bc[\"AAAAAAAAAAAAAAAAApsTAACcEwAA\",\"\"]Fo[\"AAAAAd0EAAAAAAADZwQAACsJAAC1EwAAAvIAAACmAQAA\",\"AAGGBwAAAAHdBAAAAAAAAmcEAAArCQAAAaYBAAAAAAAAAAAAAAABcxIAAA==\"]Fo[\"AAAAARQFAAAAAAFNAQAAA2gEAAAsCQAAthMAAAGnAQAA\",\"AAGeBwAAAAEUBQAAAAAAAmgEAAAsCQAAAacBAAAAAAAAAAAAAAABdBIAAA==\"]Ck[\"AAAAAQ0FAAAAAAJDAQAARAEAAA==\",\"AAAAAQ0FAAAAAAAAAcITAAA=\"]Ec[\"AAAAASEFAAAAAAFqAQAAAAAAAAAAAAAAAAAFowEAAKsHAADKBwAAwggAAHgMAAA=\",\"AAAAASEFAAA=\"]Dg[\"AAAAAAHDEwAAAAG2AAAAAAAAAAACmxMAAJwTAAAAAAAB7BIAAA==\",\"AAABqwEAAA==\"]Ag[\"AAACmAIAACMDAAA=\",\"\"]Bc[\"\",\"AAAAAAAAAAAAAAAAApsTAACcEwAA\"]Cc[\"AAAAAUEFAAAAAAAAAAAAAdIQAAA=\",\"AAABzQEAAAFBBQAA\"]Ck[\"AAAAAUIFAAAAAAACQRAAANMQAAAAATEMAAA=\",\"AAABzgEAAAFCBQAA\"]Ag[\"AAACnAIAACADAAA=\",\"\"]Dg[\"AAAAAAABLAAAAAAAARIBAAA=\",\"AAAAAAAAAAAAAAAAAAAAA48TAACQEwAAkRMAAA==\"]Ck[\"AAAAAAABXgAAAAGHAQAA\",\"AAAAAAAAAAAAAAAAAAAAApITAACTEwAA\"]Ag[\"AAACmwIAABgDAAA=\",\"\"]Do[\"AAAAAU8HAAACIRAAADQQAAAAAbgLAAABXRAAAA==\",\"AAK9AQAAjQcAAAEOCQAAAAAAAAFdEAAA\"]Ec[\"AAAAAikFAABQBwAAAiIQAAA1EAAAAAF4AQAAAV4QAAA=\",\"AAGfBwAAAcUBAAABKQUAAAAAAAFeEAAA\"]Bg[\"AAAAAAAAAAABVAUAAA==\",\"AAABvAEAAA==\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAhYJAAAvEAAA\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAhgJAAAxEAAA\",\"\"]") \ No newline at end of file diff --git a/pr/451/docs/search.index/type/5d148ce6032d.js b/pr/451/docs/search.index/type/5d148ce6032d.js new file mode 100644 index 000000000..75ab77e3a --- /dev/null +++ b/pr/451/docs/search.index/type/5d148ce6032d.js @@ -0,0 +1 @@ +rd_("CCo[\"\",\"ADswAAABAAAFAAIAUgQEAHwUAAAC5wYAAAgHAAA6MAAAAQAAAAAACgAQAAAA5wHoAXQFuwyFEssS4RJZFGUUhxSYFDowAAABAAAAAAAcABAAAAAZAhoCGwIcAh0CHgIfAmEEcwV4BbUG4wYUB1sJFxGIEswSYRMUFBUUXBSBFIIUgxSEFIUUhhSbFJ8UOzAAAAEAADYBEgBfBAAAYAUAAHIFAADoBgAABwcAABcHAABtDAMA/QwFAPQNiwCdEAAAGBEHAHcRAADtEYsAiRIAAIsSAAAXEwAAYhMAAIITAAA6MAAAAQAAAAAAIAAQAAAAdQV7BXwFfQXhBuIG6QbqBusG7AbtBu4G6gvrC+wL7QtDDKsPyRAuEYQSlhPVE9YT7BMuFC8UMBQxFFQUWBRmFIgUOzAAAAEAAEMAFQBQAxQAfQQAAIkEAgBeBQEAYQUAAHYFAQCRBQAA1AYMAOIHAQDpCwAAWQwAAAgQAAA7EAAAyhAAAHARAACfEQcAGBMAAF0TAwDNEwAAVRQAAIkUAAA7MAAAAQAAXwANAOEARQCXAQEAfAQAAJAHAACTBwAA0gcAANQHAACPEAMAwBIFAL0TAADtEwAAMxQFAEYUAAA7MAAAAQAADQAGAGMEAAC8DAAAgA4AAAQQAwCGEgAAdRQFADswAAABAAATAAkAeQQBAIIEAQDRBwAA0wcAAKUPAgCuDwAAKREAAO8SAACJEwcAOzAAAAEAAAoAAwDyBgcANAcBAN0SAAABYgwAAASNBwAAjwcAAJIHAABgDAAAADswAAABAAAFAAIAzQEAAA8UBAAAAAHCBQAA\"]Bk[\"AAAAAAAAAAAECBEAAAkRAABBFAAAQhQAAA==\",\"\"]0Bg[\"\",\"AAAFPQkAAD4JAAA/CQAAQAkAAEEJAAA=\"]AGo[\"AAAAAAK/AQAAzgEAAAAAAl4FAABfBQAABJAHAACTBwAA0gcAANQHAAA7MAAAAQAADQAFAI8FAQBjCQEAaAwEALwMAACrEQMAOjAAAAEAAAAAAAkAEAAAAIIEgwTRB9MHrg+VEZYRlxGYEe8SBHMEAAA0BwAANQcAAOASAAAGWQQAAFsEAADTEAAAGxMAABsUAAAcFAAABXgEAACPBwAAkgcAAKgPAACqDwAAA14MAABfDAAAqQ8AADswAAABAAAGAAMAzQEAAM8GAAAPFAQAAWYTAAABYQwAAA==\",\"AAAAAcYRAAAAAmMFAABkBQAAAAI8DAAAPQwAAAAFaAwAAGkMAABqDAAAawwAAGwMAAAAAAJZBAAAWwQAAA==\"]ANg[\"AAAEZQIAAMkCAAC+BgAAwgYAAAM+DAAAPwwAAEAMAAACvwEAAM4BAAA7MAAAAQAAiwABAO0RiwAAOzAAAAEAAAcAAwBeBQEAXRMDAKoUAQAEkAcAAJMHAADSBwAA1AcAADswAAABAAASAAcAjwUBAAkHAABjCQEAaAwEALwMAACrEQMAkBIDADowAAABAAAAAAAKABAAAAB5BHoEggSDBNEH0weuD5URlhGXEZgRBXMEAAB5BQAANAcAADUHAAAqEQAABFkEAABbBAAAGxQAABwUAAAEjwcAAJIHAACoDwAAqg8AAATkBgAAXgwAAF8MAACpDwAABQ8UAAAQFAAAERQAABIUAAATFAAAAWYTAAAAAcIFAAA=\",\"AAAAAAACYwUAAGQFAAAAAjwMAAA9DAAAASEQAAAFaAwAAGkMAABqDAAAawwAAGwMAAAAAAJZBAAAWwQAAAAAAAAAA/0GAAB+EQAAfxEAAA==\"]Dc[\"\",\"AAAAADswAAABAAAHAAEARgkHADswAAABAAAJAAIAJBEEAHkRBAAAAXgRAAA=\"]ADi[\"AAAABrwBAAC+AQAAygEAAMwBAAA3EAAAOBAAAAJmCgAAaAoAADowAAABAAAAAAAJABAAAAC9AcIBwwHLAdQB1QHUCtoKHRQeFDswAAABAAANAAQAxAQHANwPAADPEAMA7BMAAALGAQAAGBMAADswAAABAAAXAAsAsgEBALkBAABmDAEAtQwBAC4QAwC8EAEACBEBAOcSAwC9EwAAQRQBAJAUAQABQxQAAAAAAAAAAAAAAcIFAAA=\",\"AAABtwEAAALMBAAAeRIAAAAAAAKdDwAAtBAAAAAAAAAAAAAAAAAEwAYAAMQGAAC5DAAAugwAAA==\",1]ADe[\"AAAAAroBAADFAQAAOzAAAAEAAAcAAQBGCQcAAAR7BQAAfAUAAH0FAACEEgAAAAABQxQAAA==\",\"AAAAADswAAABAAAQAAMAtQYAAEYJBwAwDAcAOzAAAAEAAAsABAAXBwAAChEAACQRBAB5EQQAOzAAAAEAAA8ABQDEBAcATgwCAI0SAgDHEgAAWBQAADowAAABAAAAAAAKABAAAAA0ETYROBE6ETwReBGbEZwRnRGeEc0TBI8QAACQEAAAkRAAAJIQAAAAAAUzEQAANREAADcRAAA5EQAAOxEAAAAAAAAAAAPCBQAAog8AAKQPAAA=\",1]Ec[\"AAAAAAAAOzAAAAEAAAYAAgDLBgMA0AYCADswAAABAAAGAAIAxgYEAC4JAQADdQQAAG4FAABvBQAA\",\"\"]Dg[\"AAAAAAAAAAABNBQAAAF2FAAAAAAAAAAAAAABdAcAAA==\",\"AAAAAAABzhIAAAG4EwAA\"]Eo[\"AAAAAQUFAAABwQEAAAIvAAAAngAAAAEQEAAAAVUDAAACFQEAABYBAAA=\",\"AAABrQAAAAEFBQAAAXgTAAAAASgMAAA=\"]Ic[\"AAAAAUYFAAAD0gEAAJITAAAWFAAAAlwAAACrAAAABogBAACJAQAAWgQAAMEFAAAdEAAAZhQAAAFiAwAA\",\"AAABrgAAAAJGBQAAehMAAAABLAwAAAAAAAAAAAAAAAAAAAKvAAAAsAAAAA==\"]Ik[\"AAAAAtIEAADmEgAAARQHAAABsgYAAAG3AAAAAXARAAABMxQAAAKGEgAAdRQAAAAAAAAAAAAAOjAAAAEAAAAAAAkAEAAAAIIFsAZxB/AH6g8BEDIQwBAMEa8R\",\"AAAAAdIEAAAAAc0SAAABtxMAAA==\"]Ck[\"AAAAAecBAAAAAAAAATcUAAABeRQAAA==\",\"AAAAAAAB0RIAAAG7EwAA\"]Ck[\"AAAAAegBAAAAAAAAATgUAAABehQAAA==\",\"AAAAAAAB0hIAAAG8EwAA\"]Kk[\"AAAAAAAABHUFAAB7BQAAfAUAAH0FAAAAOzAAAAEAAAkAAQDfCwkAOzAAAAEAAA0AAQBjBA0AOzAAAAEAAAkABABaDAMApQ8CACkRAAA9EQEAAAFiDAAAA2AMAACoDwAAqg8AAAJfDAAAqQ8AAA==\",\"AAAAAAABChEAAANODAAATwwAAFAMAAA=\"]Oc[\"AAAAAAAAAu0SAADuEgAABF0TAABeEwAAXxMAAGATAAACYxMAAGQTAAAFCQcAAJASAACREgAAkhIAAJMSAAACeQQAAHoEAAACeQUAACoRAAAAAAHkBgAAAAAAAcIFAAA=\",\"AAAAAAJxBQAARxQAAAAFfxQAAKYUAACnFAAAqBQAAKkUAAABzRMAAAIhEAAAgBQAAAAAAAAAAAAAAAWhDwAAlBAAAJUQAACWEAAAlxAAAA==\"]Bg[\"AAAAAAAAAATDEQAAxBEAAPATAADxEwAA\",\"\"]Bk[\"\",\"AAAAAcIRAAAAAAAAAAAAAAAAAAAAAAGyDwAA\"]Cc[\"AAAAAAAAAckQAAAByhAAAA==\",\"AAAAAAACXQkAAF4JAAA=\"]FIc[\"AAAAADswAAABAAAXAAkAGQIGAGEEAACHBQAA8gcAADoMAACYEAMAzBIAABQUAQCBFAUAOzAAAAEAACMCEwBYBAAAcQQAAHcEAADoBgAAKgwDAP0MBQAFDewA9A2LACMQAAAoEAAAnRAAALsQAAAYEQcAdxEAAO0RiwCJEgAAixIAAGITAADIEwAAOzAAAAEAAMYAEAC3ACkAJwFvAJkBDgBaBAAAdQUAAMEFAADLBgMA0AYCAOEGAQAmDAMAow8AAJYTAAC3EwUALhQDAGYUAAB7FAAAOzAAAAEAAKgAQgBQAxQAdAQAAH4EAQCJBAIAYQUAAHYFAQCRBQAAxgYEANQGDADfBwEA4gcBAOQIAQAuCQEARAkBAE8JAQA8DAEATAwBAFkMAAADDQEAng8BALAPAQDrDwEA7g8BAAgQAAApEAEAMxABADsQAACeEAAArhABAMcQAQDKEAAALxEBAHgRAACDEQAAhREAAIcRAACKEQAAjBEAAI4RAACZEQ0AsBEBALMRAgDDEQEAehIBAN4SAQAYEwAAThMBAFQTAQBdEwMAfBMBAL4TAQDOEwEA0hMBAPATAQD4EwEAJRQBADkUAQBVFAIAYRQDAHEUAwCJFAAAmRQBAJ0UAQCgFAEApBQBAKoUAQA7MAAAAQAAmwAnAOEARQCXAQEAsgEBAFcEAAB1BAAAfAQAAG4FAQCJBQEAkAcAAJMHAADSBwAA1AcAAN8LCQBmDAEAtQwBAKwPAQDkDwEAIRAAAC4QAwC8EAEACBEBABIRAQArEQEAhxIAANQSAQDnEgMAYxMBAIUTAQCzEwEAxhMBAMkTAwDtEwAAMxQFAEEUAQBGFAAAfRQBAIAUAACQFAUAohQBADswAAABAAApAAwAYwQNAI8FAQAJBwAAYwkBAFYMAAC8DAAAgA4AAAQQAwCrEQMAkBIDAMMTAQB1FAUAOzAAAAEAABoADAB5BAEAggQBANEHAADTBwAAWgwDAKUPAgCuDwAAKREAAD0RAQDvEgAAiRMHAIoUAAA6MAAAAQAAAAAADwAQAAAAcwR5BfIG8wb0BvUG9gb3BvgG+QYqEakRqhHdEuASXRQFWQQAAFsEAABiDAAA0xAAABsTAAAHeAQAAI0HAACPBwAAkgcAAGAMAACoDwAAqg8AAATkBgAAXgwAAF8MAACpDwAAOzAAAAEAAAUAAgDPBgAADxQEAAABYQwAAAHCBQAA\",\"AAAAAAA7MAAAAQAA7AABAAUN7AAAOjAAAAEAAAAAAFkAEAAAAH4EfwTfB+AH5AjlCEQJRQlPCVAJPAw9DEwMTQwDDQQNng+fD7APsQ/rD+wP7g/vDykQKhAzEDQQrhCvEMcQyBAvETARgxGFEYcRihGMEY4RmRGaEbARsRHDEcQRehJ7Et4S3xJOE08TVBNVE3wTfRO+E78TzhPPE9IT0xPwE/ET+BP5EyUUJhQ5FDoUVhRXFGEUYhRjFGQUcRRyFHMUdBSZFJoUnRSeFKAUoRSkFKUUqhSrFDswAAABAAAzABUAsgEBAIkFAQBmDAEAtQwBAOQPAQAuEAMAvBABAAgRAQASEQEAKxEBANQSAQDnEgMAYxMBAIUTAQCzEwEAxhMBAMkTAwBBFAEAfRQBAJAUBQCiFAEABGMJAABkCQAAwxMAAMQTAAAAAqkRAACqEQAA\"]Ic[\"AAAAAYARAAAABHIFAAAzCQAANAkAAIMSAAA7MAAAAQAADAADAIwEBQDpBgUADhQAAAADjAcAACsRAAAsEQAAAYoQAAAABzMRAAA1EQAANxEAADkRAAA7EQAAqREAAKoRAAAAAY0HAAA=\",\"\"]Ko[\"AAACZAIAAMoCAAAAADswAAABAAALAAMAwgEBANQBAQAYEQcAOzAAAAEAAAcAAQDEBAcAOzAAAAEAAA8ABQDfBwEA6w8BADMQAQCZEQEAnxEHAAACwxMAAMQTAAA=\",\"AAAAAeIPAAAAAAAAAAAAAAAAAAAAAAXhDwAA4w8AAOoPAADtDwAAxRMAAA==\"]AAg[\"AAAEYAIAAMwCAAA1CQAANgkAAAS4AQAAwAEAAMQBAADRAQAAAmgCAADRAgAAAAA6MAAAAQAAAAAAEwAQAAAAfgR/BOQI5QigEKIQpBCmEKcQNBE2ETgROhE8Ed4S3xKdFJ4UoBShFAJjEwAAZBMAAAVoDAAAaQwAAGoMAABrDAAAbAwAAAAFMxEAADURAAA3EQAAOREAADsRAAAAAAACzQEAAM8GAAA=\",\"AAAAAAAAAAAAAAAAAAAAAAAABOYIAADnCAAA6AgAAOkIAAA=\"]CAk[\"AAADMgkAAFATAABREwAABbYBAABHCgAAwhEAAFYTAABXEwAAOjAAAAEAAAAAAAcAEAAAAMEB0gE3CTgJLAqACqgKiBIBiRIAAAWWEwAALhQAAC8UAAAwFAAAMRQAADowAAABAAAAAAAXABAAAADiB+MHnQ8pECoQtBCDEYURhxGKEYwRjhGfEaARoRGiEaMRpBGlEaYRvhO/E2EUYhQDvRMAAJQUAACVFAAABAQQAAAFEAAABhAAAAcQAAA7MAAAAQAACQADANEHAADTBwAAiRMHADswAAABAAAHAAEA8gYHAAACjwcAAJIHAAA=\",\"AAAEaAUAAGkFAACTDwAAlA8AADswAAABAAAWAAsANgcHAIERAQCEEQAAhhEAAIgRAQCLEQAAjREAAHwSAQDqEwEA7hMAAD8UAQACkhMAABYUAAAD8g0AAPMNAACDEgAAAAAAAAAAAAAAAAAAOjAAAAEAAAAAAHEAEAAAAKwBrQGvAbABDQIOAhYCFwJgBGIEhATOBNAEvwbDBvAG8QYoBykHIAkhCVYJVwk5DDsMUQxSDFcMWAxjDGQMmQ+aD5sPnA+zD90P3g//DwAQMhCMEI0QqhCrEKwQrRC5ELoQvhC/EMsQzBACEQMRDBEOEQ8RFREWESARIREiESMRpxGoEboRuxF/EoASgRKCEskSyhLWEtcS4xLlEg0TDhMPExATEhMTExQTFRNSE1gTWRNaE3YThxOIE5ETlxOYE5kTmhObE5wTtRO2E9AT0RPXE9gT9hP3ExkUGhRaFFsUZxRoFA==\"]Ac[\"AAAAAAFBAwAA\",\"\"]Gc[\"AAAABAcFAAA3BwAA2AcAAIIRAAADRwkAADEMAACCFAAAARkRAAABLxQAAAKgEQAApBEAAAEaAQAAAQUQAAABihMAAAHzBgAA\",\"AAAAAQcFAAA=\"]Gc[\"AAAABFAFAAA7BwAA3AcAAIkRAAADSwkAADUMAACFFAAAAR0RAAACmwEAADEUAAACohEAAKYRAAAAAQcQAAABjhMAAAH3BgAA\",\"AAAAAVAFAAA=\"]Ca[\"AAAAAAJoAgAA0QIAADswAAABAAAFAAEAvBEFAA==\",\"\",1]Ek[\"AAAAAAAAAAAAAAAAAAAAAAAAAuUGAAD7BgAA\",\"AAAAAAACnxAAAKEQAAAAAqAQAACiEAAAAAKQEgAAkRIAAA==\"]Eo[\"AAAAAAAAAAAAAAAAAAAAAAAAAuYGAAD8BgAA\",\"AAAAAAACoxAAAKUQAAAAA6QQAACmEAAApxAAAAACkhIAAJMSAAA=\"]Ck[\"AAAAAAAAAAAAAAAAAAAAAAAABXkAAACwAQAAzQcAAOoHAADoCAAA\",\"\"]Bg[\"AAAAAAAAAasPAAAAAAAAAqkRAACqEQAA\",\"\"]Hc[\"AAAABGUAAADcBAAAhQUAADYQAAABGwIAAAYOAAAAXwQAAGAFAAAlBwAAuBEAABgUAAADzQAAAFMJAACvDwAAAALUEwAAMhQAAA==\",\"AAAAAdwEAAAB4wgAAAFdBAAA\"]Ic[\"\",\"AAAAAAAAAAA7MAAAAQAACQABAN8LCQA7MAAAAQAADQABAGMEDQA7MAAAAQAACQAEAFoMAwClDwIAKREAAD0RAQAAAWIMAAADYAwAAKgPAACqDwAAA14MAABfDAAAqQ8AAAAAAWEMAAA=\"]Hk[\"AAAD2gEAAEUMAABIDAAAAQQFAAABwQEAAAQuAAAAnQAAAJ8QAAChEAAAAQ8QAAABVAMAAAMUAQAAcxEAAHQRAAA=\",\"AAAC2gEAAEEMAAACgAQAAAQFAAABdxMAAAABJwwAAA==\"]Hk[\"AAAD2wEAAEYMAABJDAAAAUUFAAAB0gEAAARbAAAAqgAAAKMQAAClEAAAAocBAAAcEAAAAWEDAAADdQQAAHURAAB2EQAA\",\"AAAC2wEAAEIMAAADgQQAAEUFAAB5EwAAAAErDAAA\"]AMo[\"AAAAAAGOEAAAAAA6MAAAAQAAAAAAEQAQAAAAxgF0BEwMTQwDDQQNng+fDy8RMBF6EnsScRRyFJkUmhSkFKUUAnwEAAC9EwAABAQQAAAFEAAABhAAAAcQAAA7MAAAAQAABwABAIkTBwA7MAAAAQAABwABAPIGBwAAAAACzQEAAM8GAAA=\",\"AAAHagUAAGsFAABFDAAARgwAAEgMAABJDAAAihIAADowAAABAAAAAAAXABAAAADVB9YHQglOCUcMSgxLDJUPlg+wELEQshABEYARkBGREZIRkxGUEcATLBQtFEQURRQ7MAAAAQAABQECAIgOAQF+EwMAOzAAAAEAAF0ABAALAFkAUxMAAIMTAQDvEwAAAXsUAAAAAASrEQAArBEAAK0RAACuEQAABJURAACWEQAAlxEAAJgRAAAB3RIAAAAAAAAAAAeIBAAAggUAAO8GAACFDgAABBEAABQRAACvEQAA\"]Dk[\"AAAAAAIqCgAAggoAAAAAADswAAABAAAFAAEAwBIFAAACeQQAAHoEAAAAAlkEAABbBAAA\",\"\"]C@k[\"\",\"ADswAAABAAAFAAIAUgQEAHwUAAAC5wYAAAgHAAA6MAAAAQAAAAAACgAQAAAA5wHoAXQFuwyFEssS4RJZFGUUhxSYFDowAAABAAAAAAAcABAAAAAZAhoCGwIcAh0CHgIfAmEEcwV4BbUG4wYUB1sJFxGIEswSYRMUFBUUXBSBFIIUgxSEFIUUhhSbFJ8UOzAAAAEAADIBEQBfBAAAYAUAAHIFAADoBgAABwcAABcHAAD9DAUA9A2LAJ0QAAAYEQcAdxEAAO0RiwCJEgAAixIAABcTAABiEwAAghMAADowAAABAAAAAAAeABAAAAB1BXsFfAV9BeEG4gbpBuoG6wbsBu0G7gbqC+sL7AvtC6sPyRAuEYQSlhPVE9YT7BMuFC8UMBQxFFQUZhSIFDswAAABAABBABMAUAMUAH0EAACJBAIAXgUBAGEFAAB2BQEAkQUAANQGDADiBwEA6QsAAFkMAAAIEAAAOxAAAMoQAACfEQcAGBMAAF0TAwBVFAAAiRQAADswAAABAABaAAsA4QBFAJcBAQB8BAAAkAcAAJMHAADSBwAA1AcAAMASBQDtEwAAMxQFAEYUAAA7MAAAAQAADAAFAGMEAAC8DAAAgA4AAAQQAwB1FAUAOzAAAAEAABMACQB5BAEAggQBANEHAADTBwAApQ8CAK4PAAApEQAA7xIAAIkTBwA7MAAAAQAACQACAPIGBwA0BwEAAWIMAAADjwcAAJIHAABgDAAAAAUPFAAAEBQAABEUAAASFAAAExQAAA==\"]Bc[\"AAAAAAAAAAAAAAACqREAAKoRAAA=\",\"\"]Kg[\"AAAAAAFcEwAAADswAAABAAAGAAIA6QYFAC4RAAACYQUAADsQAAABfAQAAAAB7xIAAAHgEgAAAtMQAAAbEwAA\",\"AAAAAAABdwQAADswAAABAAAFAAEAjAQFAAABrA8AAAAAAAAAAAAAAAcYAgAAzQQAAM8EAAD/BgAAAAcAAAIHAAADBwAA\"]Dk[\"AAAAAAABGAAAAAAAAeUAAAAAAAAAAAAAAAACzQQAAPAGAAA=\",\"AAAAAAAAAAABrQ8AAA==\"]Dk[\"AAAAAAABNQAAAAEtAQAAAAAAAAAAAAAAAAACzwQAAPEGAAA=\",\"AAAAAAAAAAABVwQAAA==\"]Ac[\"AAABdQIAAA==\",\"\"]Fg[\"AAAABAkFAAA4BwAA2QcAAIQRAAADSAkAADIMAACDFAAAARoRAAAAAAEcAQAAAAGLEwAAAfQGAAA=\",\"AAAAAQkFAAAAAcIBAAA=\"]Fg[\"AAAABFIFAAA8BwAA3QcAAIsRAAADTAkAADYMAACGFAAAAR4RAAABnQEAAAAAAAGPEwAAAfgGAAA=\",\"AAAAAVIFAAAAAdQBAAA=\"]Bk[\"\",\"AAAAAAAAAAA7MAAAAQAARwACAOEARQCXAQEA\"]Ck[\"\",\"AAAAAAG1BgAAOzAAAAEAAAkAAgAXBwAAGQcIAAFYFAAAAc0TAAA=\"]Bo[\"\",\"AAAAAAAAAAAAAAAAAhsUAAAcFAAAAAAAAWYTAAA=\"]Ck[\"AAACdgIAAIgCAAA=\",\"AAAAAAAAAtoTAADbEwAAAAACqxEAAKwRAAA=\"]Eo[\"\",\"AAAAAAA7MAAAAQAABQABAM0SBQA7MAAAAQAABQABALcTBQAAOzAAAAEAAAUAAQAzFAUAOzAAAAEAAAUAAQB1FAUA\"]Cc[\"\",\"AAAAAAJ3EwAAeBMAAAAEJgwAACcMAAAoDAAAKQwAAA==\"]Eo[\"AAAAAQ0FAAAAATIAAAAAAAIjAQAAJAEAAA==\",\"AAA7MAAAAQAACgACAKUCBACrAgUAAg0FAABpFAAAAAAB7RIAAA==\"]Eg[\"AAAAAUwFAAAAAWMAAAAAAAKXAQAAmAEAAA==\",\"AAA7MAAAAQAACgABACkDCgACTAUAAGoUAAAAAAHuEgAA\"]Dg[\"AAABEgMAAAFLBQAAAAFhAAAAApIBAACTAQAA\",\"AAABIQMAAAFLBQAAAAAAAAF1BAAA\"]Eo[\"AAAAAAAAAqEBAACiAQAA\",\"AAAAAAAAB1QUAAB/FAAAiBQAAKYUAACnFAAAqBQAAKkUAAACVRQAAIkUAAABgBQAAA==\"]Bk[\"AAAAAAAAAVIBAAA=\",\"AAAB6gIAAAAAAXEEAAA=\"]Bg[\"AAAAAVYFAAAAAAGjAQAA\",\"AAAAAVYFAAA=\"]Bg[\"\",\"AAAAAAAAAAAAAAAAAAAAAAAAAbcMAAA=\"]Cc[\"AAAAAAAAAAAAAAAAAAAAAAAAA84HAADrBwAA6QgAAA==\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAogEAADPBwAA\",\"\"]Cc[\"AAAAAAAAAAIuCQAALwkAAAAAAAAAAAACzQEAAM8GAAA=\",\"\"]Fo[\"AAAAAAACcgUAAIsSAAAAAAAAAAAAAAAAAAAE8QcAALMPAADNEAAAxREAAA==\",\"AAGVBwAAAAAAAAAAAAAAAAAAAAAAAALOEAAAchEAAA==\"]Bk[\"AAAAAXQFAAAAAAGrDwAAAQgQAAA=\",\"AAHGBwAA\"]Bg[\"AAAAAAAABM8QAADQEAAA0RAAANIQAAA=\",\"\"]Ag[\"AAAAAroBAADFAQAA\",\"\"]Dg[\"AAAAAAAAAAAAAmMJAABkCQAA\",\"AAAAAAAAAAAABWgMAABpDAAAagwAAGsMAABsDAAA\"]AEk[\"AAAAAAK/AQAAzgEAAAAAAl4FAABfBQAABJAHAACTBwAA0gcAANQHAAA7MAAAAQAACwAEAI8FAQBoDAQAvAwAAKsRAwA6MAAAAQAAAAAACQAQAAAAggSDBNEH0weuD5URlhGXEZgR7xIEcwQAADQHAAA1BwAA4BIAAAZZBAAAWwQAANMQAAAbEwAAGxQAABwUAAAFeAQAAI8HAACSBwAAqA8AAKoPAAADXgwAAF8MAACpDwAAOzAAAAEAAAYAAwDNAQAAzwYAAA8UBAABZhMAAAFhDAAA\",\"AAAAAcYRAAAAAmMFAABkBQAAAAI8DAAAPQwAAAAAAAACWQQAAFsEAAA=\"]He[\"\",\"AAAAAAAEbQwAAG4MAABvDAAAcAwAAAJDDAAAWBQAAAJwEQAAzRMAAAWPEAAAkBAAAJEQAACSEAAAvRMAAAGGEgAAAAHdEgAAAAGNBwAAAAHNAQAAAAABwgUAAA==\",1]AMk[\"AAACxwEAANABAAAGuAEAAMABAADEAQAA0QEAAFYTAABXEwAABHMFAABbCQAAKQoAAIEKAAAEbQwAAG4MAABvDAAAcAwAAALVEwAA1hMAADowAAABAAAAAAAHABAAAAAuCS8JrhCvEE4TTxNWFFcUOjAAAAEAAAAAAAkAEAAAAFcEkAeTB9IH1AesD60PIRB9FH4UAALRBwAA0wcAAA==\",\"AAAAOjAAAAEAAAAAABAAEAAAAK4BbQduB28HcAfzB/QHcQwkECUQxhAxETIR2hLbEhYTYBQDWAkAAI4QAAAREwAABm0MAABuDAAAbwwAAHAMAADiEgAA5BIAAAAAAAKPBQAAkAUAAAACcwQAAOASAAAC0xAAABsTAAADeAQAAKgPAACqDwAAA14MAABfDAAAqQ8AAAAAAmEMAADBDAAAOjAAAAEAAAAAAAcAEAAAALEBhQSGBCoHcQfwB/EHARA=\"]Bg[\"AAAAAAAAAAAAAW4EAAA=\",\"AAAAAb8FAAA=\"]Bk[\"AAAAAAAAADswAAABAAANAAIAkQUAANQGDAA=\",\"\"]Cc[\"AAABJQkAAAABHwIAAAAAAAFXBAAA\",\"AAAAAAAAAAF0BAAA\"]Dg[\"AAAAAdYEAAAAAAG/AAAAAAAAAAAAAAAAAAADhgUAAP4GAAChDwAA\",\"AAAAAdYEAAA=\"]Bc[\"AAAAAAAAAAAAAAAAAAAAAc8GAAA=\",\"\"]Be[\"AAAAAAAAA3sFAAB8BQAAfQUAAA==\",\"\",1]Bg[\"AAAAAAAAADswAAABAAAUAAEAUAMUAA==\",\"\"]Dk[\"AAAAAAHTAQAA\",\"AAAAAARRCQAAUgkAAFQJAABVCQAAAAFTCQAAAAAAAAAAAAAAAAFeEAAA\"]Cc[\"AAAAAdsEAAAAAQ0AAAACxQAAAMYAAAA=\",\"AAAAAdsEAAA=\"]Bg[\"AAAAAAAAAAAAAWkEAAA=\",\"AAAAAeQHAAA=\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAa8AAAA=\",\"\"]CAe[\"AAAAAAHTAQAAARcTAAAB7BMAAALGAQAAGBMAADowAAABAAAAAAAdABAAAAC5AYkFigXkD+UPEhETEdQS1RKFE4YTsxO0E8YTxxPJE8oTyxPME9QT7RMyFH0UfhSSFJMUlBSVFKIUoxQ7MAAAAQAABgADAGMJAQAEEAMAQxQAAAGKFAAAAV0UAAAAAAHkBgAAAAAAAcIFAAA=\",\"AAAFPQkAAD4JAAA/CQAAQAkAAEEJAAAEPgwAAD8MAABADAAAeRIAADowAAABAAAAAAAXABAAAACoAcAEwQSOB5EH5QfiCOMIUQlSCVQJVQlYCYUQhhCHEIgQiRARExoTfhN/E4ATgRM7MAAAAQAAIAAOAOMBAADlAQAAWAQAAF0EAABxBAAAdwQAABkHCABtDAMAoA8AALsQAAAkEQQAeREEAFsTAADIEwAAOzAAAAEAACQACgBaBAAAjAQFAMEFAADuBwAAUwkAAKMPAAALEBMAxxIBANoTAQDdEwEAOzAAAAEAAAgABACeEAAAmxEDALMRAgD1EwAAOzAAAAEAAAwABgBXBAAArA8BAI8QAwBzEQMA1BMAADIUAAAEkBIAAJESAACSEgAAkxIAADswAAABAAAGAAMAWgwDAD0RAQCKFAAABHMEAAAqEQAA4BIAAF0UAAAAAAHkBgAAAc0BAAAAADswAAABAAAgABAAsQADAMUGAADTBgAAIgcCAB4JAABfCQMAZQkBAPAPAAAJEAEAKxACAMEQBACyEQAAfhIAAPoTAACWFAEArBQAAA==\",1]Mg[\"AAAAAeYEAAAAOjAAAAEAAAAAAAcAEAAAABkAmgDjAV0EoA+9EcARgxMCDBAAAMcSAAACUQMAANUGAAAC5gAAAOcAAAAAAdEHAAAAAAGPBwAA\",\"AAGaBwAAAANlAAAAZgAAAOYEAAACaAIAAOUHAAACvREAAMARAAAAAccGAAAAAAAAAAAAAAAABWoAAABrAAAAbAAAAG0AAABuAAAA\"]A@c[\"AAAAARQFAAAABzcAAACiAAAA5QEAAL8RAADBEQAAWxMAAIQTAAAHMAEAADEBAACjDwAAFBAAACAQAACzEAAAyBIAAARZAwAA2gYAAFkMAAD1EwAAAYcSAAABgA4AAALTBwAAihQAAAFdFAAAAAGSBwAA\",\"AAGwBwAAAAEUBQAAAtECAABxBQAAAr8RAADBEQAAAcwGAAAAAAFWDAAAAAHdEgAAAAAAAAAABW8AAABwAAAAcQAAAHIAAABzAAAA\"]Bo[\"\",\"AAAAAAAAOzAAAAEAAKgAAwC3ACkAJwFvAJkBDgA=\"]CEa[\"\",\"AAA7MAAAAQAA7AABAPIK7AA7MAAAAQAA8wANAPMJNQArCgAALQo4AGcKAABpChYAgwoeAKMKBACpCioA1QoAANcKAQDbChYAxhIAAGkUAQA6MAAAAQAAAAAAEgAQAAAAeAXABbUGNwk4CSkKKgosCmYKaAqACoEKggqiCqgK1grZCncTeBM6MAAAAQAAAAAAFAAQAAAAXwRgBRcHJQddCV4J1AraCvIN8w24Ec0SzhLPEtAS0RLSEoITGBQdFB4UOjAAAAEAAAAAACoAEAAAAOEG4gbuB1MJ6gvrC+wL7QsmDCcMKAwpDEMMrw/cDyAQsxDJEM8Q0BDRENIQLhHtEu4StxO4E7kTuhO7E7wT2hPbE90T3hNUFFgUfxSIFKYUpxSoFKkUOjAAAAEAAAAAABoAEAAAAHQEiQSKBIsEdgV3BcYGxwbIBskGygZZDDsQoBCiEKQQphCnEMoQeBGzEbQRtRHNE/UTVRSJFDswAAABAABhAA8A4QBFAJcBAQBXBAAAdQQAAHwEAABuBQEAjAcAAKwPAQAhEAAAhxIAAMASBQDUEwAAMhQGAEYUAACAFAAAOzAAAAEAABEACACPBQEAVgwAALwMAACADgAAihAAAKsRAwDDEwEAdRQFAAd5BAAAegQAAK4PAAA9EQAAPhEAAO8SAACKFAAAOjAAAAEAAAAAAAsAEAAAAHMEeQU0BzUHMxE1ETcRORE7Ed0S4BJdFAZZBAAAWwQAANMQAAAbEwAAGxQAABwUAAADjQcAAI8HAACSBwAAAl4MAACpDwAAOzAAAAEAAAYAAwDNAQAAzwYAAA8UBAABZhMAAAABwgUAAA==\",1]Le[\"\",\"AAAAAtYBAACzBgAAAxUHAACXDwAAmA8AAAMHBwAA4hIAAOQSAAA6MAAAAQAAAAAABwAQAAAAqw+EEo0SjhKPEtUT1hMOFAJ9BAAA6QsAAAG5AQAAAUMUAAAEpQ8AAKYPAACnDwAAKREAAAABYgwAAAR4BAAAYAwAAKgPAACqDwAAAV8MAAAAAAFhDAAA\",1]Gc[\"\",\"AAAAOjAAAAEAAAAAAAcAEAAAAOcB6AGFEuESWRRlFIcUmBQCFAcAAI4QAAAFsgYAADMJAAA0CQAA3w8AAIMSAAABlhMAAAFwEQAAAb0TAAA=\"]Bo[\"\",\"AAAAAnkTAAB6EwAAAAQqDAAAKwwAACwMAAAtDAAA\"]Bk[\"\",\"AAAAAAAAOzAAAAEAAAYAAgDLBgMA0AYCAA==\"]Fc[\"AAAAAc8BAAAAAAAABIkFAACKBQAA5A8AAOUPAAA=\",\"AAA7MAAAAQAAQgABAHIMQgAAAAAAAAAAAAAAAAAAAAABBAcAAA==\"]Ao[\"\",\"AAA7MAAAAQAA7AABAPUH7AA=\"]AAg[\"AAAEfgIAAI4CAACVAgAAkw8AAAHkBAAAA2ETAACbFAAAnxQAAAMWAAAAmQAAALwRAAABCxAAADowAAABAAAAAAAMABAAAABQA4kEigReBV8FdgXUBrARzhP4EyUUYxRzFALhAAAA4gAAAA==\",\"AAGZBwAAB2ACAABhAgAAYwIAAGQCAABlAgAANQkAAFATAAAB5AQAAAQ3CQAAKQoAACoKAAAsCgAAAbwRAAABJgwAAAPGBgAAsxEAALQRAAAAAAAAAAAAAAAAAYsPAAA=\"]ABc[\"AAAE8AIAABQDAAAbAwAAlA8AAAERBQAAAAQzAAAAoAAAAL4RAABiEwAABCkBAAAqAQAAEhAAAGYUAAA6MAAAAQAAAAAACgAQAAAAVwOLBGEFdwXZBrERzxP5EyYUZBR0FAABgA4AAA==\",\"AAGvBwAAB8gCAADJAgAAygIAAMwCAADNAgAANgkAAFETAAABEQUAAAQ4CQAAgAoAAIEKAACCCgAAAioMAAC+EQAAAssGAADuBwAAAbURAAAAAAAAAAAAAAAABI0PAACODwAAjw8AAJAPAAA=\"]Gc[\"AAA7MAAAAQAACwAEAIAQBAB7EwAAJxQEAJwUAAA=\",\"ADswAAABAAAJAAMAcgcBAHUHAgBJEwQAOzAAAAEAAAsABACAEAQAexMAACcUBACcFAAA\"]Ic[\"AAAAAAAAAAA7MAAAAQAACQABAN8LCQA7MAAAAQAADQABAGMEDQA7MAAAAQAACQAEAFoMAwClDwIAKREAAD0RAQAAAWIMAAADYAwAAKgPAACqDwAAA14MAABfDAAAqQ8AAAAAAWEMAAA=\",\"\"]Cc[\"AAAAArwBAAAxEQAAAAAAAAFmDAAAAAGCBAAAATQHAAA=\",\"\"]Cc[\"AAAAAsoBAAAyEQAAAAAAAAFnDAAAAAGDBAAAATUHAAA=\",\"\"]ACg[\"AAAAAmcTAABoEwAAAAXCAQAAwwEAANQBAADVAQAAdxEAADswAAABAAALAAMAxAQHAHUFAABODAIAOzAAAAEAAA0ABQBhBQAA4gcBALAPAQAIEAAAnxEHAAdzEQAAdBEAAHURAAB2EQAA7RMAAEYUAACAFAAABlYMAACADgAABBAAAAUQAAAGEAAABxAAADswAAABAAAHAAEAiRMHADswAAABAAAKAAQA8gYHACoRAADdEgAAXRQAAAABjQcAAAHkBgAAAAAAAcIFAAA=\",\"AAAAA90BAADeAQAA3wEAAAABuxAAAA==\"]Ek[\"AAAAAAAAAAAAAAAAAAAAAAACbAUAAAoHAAA=\",\"AAAAAAAAAAKbEQAAnBEAAAAAAAAAAAAAAAAC5QYAAPsGAAA=\"]Ek[\"AAAAAAAAAAAAAAAAAAAAAAACbQUAAAwHAAA=\",\"AAAAAAAAAAKdEQAAnhEAAAAAAAAAAAAAAAAC5gYAAPwGAAA=\"]Ak[\"\",\"AAAAAAICEAAAAxAAAA==\"]Fc[\"AAAABAsFAAA5BwAA2gcAAIYRAAACSQkAADMMAAABGxEAAAAAAR4BAAAAAYwTAAAB9QYAAA==\",\"AAAAAQsFAAAAAcMBAAA=\"]Fc[\"AAAABFQFAAA9BwAA3gcAAI0RAAACTQkAADcMAAABHxEAAAGfAQAAAAAAAZATAAAB+QYAAA==\",\"AAAAAVQFAAAAAdUBAAA=\"]Bg[\"AAAAAAAAAAAAAWgEAAA=\",\"AAAAAVwJAAA=\"]Ag[\"AAACuwEAAMkBAAA=\",\"\"]Ao[\"AAAAAAAAAAAAAAAAAAFgDAAA\",\"\"]Ag[\"\",\"AAACqBAAAKkQAAA=\"]Bg[\"AAAAAAAAAAAAAYYSAAA=\",\"AAABeAAAAA==\"]Dg[\"AAAAAt0BAADeAQAAAAADewUAAE4MAACNEgAAAAG5AQAAAUMUAAA=\",\"AAAAAXkSAAA=\"]Hg[\"AAAAAZARAAABfhMAAAOfEAAAJREAAHoRAAAB2hMAAAGbEQAAAY8QAAACqxEAAJASAAABlREAAAAAAAABEBQAAA==\",\"AAAAAAAAAAI0EQAANhEAAAAAAAIzEQAANREAAA==\"]Ig[\"AAAAAZMRAAABgBMAAAOjEAAAJxEAAHwRAAAB3RMAAAGdEQAAAZEQAAACrREAAJISAAABlxEAAAAAAAABEhQAAA==\",\"AAAAAAFxEQAAAAADOBEAADoRAAA8EQAAAAAAAzcRAAA5EQAAOxEAAA==\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAncAAACQDwAA\",\"\"]Bk[\"AAAAAAAAAeAAAAAAAAFwBAAA\",\"AAAAAe0HAAA=\"]Cc[\"AAAAAcYQAAABkhMAAAACWgQAAMEFAAA=\",\"AAAAAAHyBwAA\"]Be[\"AAAAAAA7MAAAAQAAiwABAO0RiwA=\",\"\",1]Bg[\"AAAAAAAAAAAAAWsEAAA=\",\"AAAAASUMAAA=\"]Ec[\"AAAAAAAAAAAAAAAAAAKPBwAAkgcAAAHkBgAAAs0BAADPBgAA\",\"AAAAAAAAAAAAAAAAAAAAAc0BAAA=\"]Dk[\"AAAAAAAAAAABNRQAAAF3FAAA\",\"AAAAAAABzxIAAAG5EwAAAAAAAAAAAAAAAAABdAcAAA==\"]Dg[\"AAAAATgFAAAAAAJsAQAAbQEAAAHdBgAAAAAAAAAAAAAAAAEYAgAA\",\"AAAAATgFAAA=\"]ACg[\"AAAAA7YBAABACgAAwhEAAAPjBgAAogoAABcRAAAABC4UAAAvFAAAMBQAADEUAAA6MAAAAQAAAAAADQAQAAAARAlFCU8JUAnuD+8PxxDIEFQTVRN8E30T0hPTEzswAAABAAAPAAcAEhEBAMASBQCFEwEA1BMAADIUAACSFAEAohQBAAABihQAAAFdFAAA\",\"AAAAA6kBAAC0AQAAixAAAASFEAAAhhAAAIcQAACIEAAAAuMBAADlAQAAAAAC1BMAADIUAAAAAYoUAAABXRQAAAAAAAAAAATkAQAA5gEAACIJAADFEQAA\"]Bk[\"AAAAAAEUBwAA\",\"AAAAAAAAAAFwEQAAAAGGEgAA\"]Ho[\"AAAABdkEAACEBQAAvwUAALsMAADaEgAAApgPAABcFAAAAgcHAAAXEwAAA8MAAACEEgAA7BMAAAFfBQAAAYwHAAABihAAAA==\",\"AAAAAtkEAACzBgAAARUHAAAAAAABuQEAAAFDFAAA\"]Gc[\"AAAAAtsSAADuEwAAAboGAAAB6AYAAAEoAQAAAp4QAAAYEwAAAAEJBwAAAAF5BQAAAAGNBwAAAeQGAAAAAAABwgUAAA==\",\"AAAAAAAAAAHGAQAA\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAXYAAAA=\",\"\"]Fg[\"AAAD5wYAAAgHAAD8DwAABEoMAAC3EAAAtxEAAEQUAAAFGgIAAHgFAADABQAAjgcAAFIJAAAAAskAAADcDwAA\",\"AAABEBEAAA==\"]Fg[\"AAAABEsMAAC4EAAAuREAAEUUAAACkQcAAFQJAAAB6AYAAAFvAQAAAAABCQcAAAABeQUAAAAAAAAAAAHCBQAA\",\"AAABEREAAA==\"]Dg[\"AAAAAT0FAAAAAVUAAAABeAEAAA==\",\"AAAAAT0FAAAAAAAAAAAAAAAAAAAAAAF5AAAA\"]Bk[\"AAAAAAEZAgAAAAAAAAFlBAAA\",\"AAAAAVQMAAA=\"]Cc[\"AAAAAAHlBwAAAAAAAAFmBAAA\",\"AAAAAAACVQwAANwSAAA=\"]Dk[\"\",\"AAAAADswAAABAAATAAEAeAcTADswAAABAAATAAEAmQATADswAAABAAATAAEACxATAA==\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAXQAAAA=\",\"\"]Fg[\"AAAAAVcFAAAAAAGlAQAAAAAAAAAAAXgEAAAAAAABYQwAAAazAAAAsQEAABECAADTBgAAKgcAAH4SAAA=\",\"AAHFBwAAAAFXBQAA\"]Hk[\"AAAErQAAAGECAABoBQAAQQwAAAEGBQAAAAIwAAAAnwAAAAEREAAAAlYDAADYBgAAAhcBAAAYAQAA\",\"AAABlQIAAARnAAAAaAAAAGkAAAAGBQAAAcEBAAAAASkMAAABygYAAA==\"]Kc[\"AAAErgAAAMgCAABpBQAAQgwAAAFHBQAAAARdAAAArAAAACMQAAAoEAAAA4oBAACLAQAAHhAAAAJjAwAA3wYAAA==\",\"AAABGwMAAAFHBQAAAdIBAAABLQwAAANaBAAAwQUAANEGAAAAAAAAAAAAAAAAAAR0AAAAdQAAAHYAAAB3AAAA\"]Ak[\"AAAAAAI5EAAAOhAAAA==\",\"\"]Ao[\"AAAAAAAAAAACsgEAAMASAAA=\",\"\"]Ao[\"AAAAAAAAAAACswEAAMMSAAA=\",\"\"]Dc[\"AAAAAAAAAAABGQEAAAAAAAAAAAAAAAWxAAAAhQQAACIHAADmDwAAwRAAAA==\",\"\"]Fc[\"AAAAAAAAAYwBAAAAAAAAAAAAAAAAAAWyAAAAhgQAACMHAADnDwAAwhAAAA==\",\"AAAAAAAAAAAAAQkHAAAAAnkFAAAqEQAA\"]Fc[\"AAAAAVgFAAAAAAGmAQAAAn0EAADpCwAAAAAAAAFiDAAAAAAAAAAFtAAAACQHAAAfCQAA6A8AAMMQAAA=\",\"AAAAAVgFAAA=\"]Hg[\"AAABkwAAAAHrBAAAAAIeAAAA/QwAAAAAAe4AAAAAAAAAAAAAAAADrAEAAM4EAAA5DAAA\",\"AAGdBwAAAAHrBAAAAAAAAAAAAAAAAAAAAAAEKwcAACwHAAAvBwAAMAcAAA==\"]Hg[\"AAAAApYAAAAbBQAAAAI9AAAAAA0AAAE6AQAAAAAAAAAAAAAAAAADrQEAANAEAAA7DAAA\",\"AAGzBwAAAAEbBQAAAAAAAAAAAAAAAAAAAAAELQcAAC4HAAAxBwAAMgcAAA==\"]Bc[\"AAAAAAAAAAAAAAAAAAAAAAFmEwAA\",\"\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAXUAAAA=\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAmsAAAABBwAA\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAnAAAAAGBwAA\",\"\"]Bg[\"AAAAAAAAAAAAAWoEAAA=\",\"AAAAAfwMAAA=\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAbAAAAA=\",\"\"]Hc[\"AAAAAAAB3w8AAAEOFAAAAAE2FAAAAXgUAAAAAAAAAAAAADowAAABAAAAAAANABAAAADvBh4JYQmFDu0P8A8rEAQRFBGyEeMS5RLFE/oT\",\"AAAAAAAB0BIAAAG6EwAA\"]Ak[\"AAAAAAIUFAAAFRQAAA==\",\"\"]Cg[\"AAAAARYTAAABWAkAAAF3BAAAAAABfAQAAA==\",\"AAAAAAE6DAAA\"]Ag[\"\",\"AAAAAAAAAdIGAAA=\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAmwAAACiDwAA\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAnEAAACkDwAA\",\"\"]Ag[\"AAAAAAAAAAHgBgAA\",\"\"]Dk[\"AAAAAAAAOzAAAAEAAKgAAwC3ACkAJwFvAJkBDgAAOzAAAAEAAEcAAgDhAEUAlwEBAA==\",\"\"]Hg[\"AAAAAkgFAAALBwAAAVUJAAAEXgAAAIMTAACEEwAA7xMAAAONAQAAjgEAAHsUAAABZAMAAAABVgwAAAAB3RIAAAAAAeQGAAA=\",\"AAAB6Q8AAAFIBQAAAdMBAAAAAbMQAAA=\"]Dk[\"AAAAAfwEAAAAAScAAAAAAdYGAAACBQEAAAYBAAA=\",\"AAAAAvwEAABHCgAAAAAAAcgGAAA=\"]Dk[\"AAAAATIFAAAAAUwAAAACXgEAAF8BAAAB2wYAAA==\",\"AAAAATIFAAABqAoAAAABzQYAAA==\"]Ck[\"AAAAASQQAAAAAAAAApAHAADSBwAA\",\"AAAAAAAAAAACjxAAAJAQAAA=\"]Ck[\"AAAAASUQAAAAAAAAApMHAADUBwAA\",\"AAAAAAAAAAACkRAAAJIQAAA=\"]Bk[\"\",\"AAAAAAAAAAAAAAAAAAAAAAAAAg0RAACMEgAA\"]Ek[\"AAAAA/MHAAB8EgAA6hMAAAAAAAAAAAHRBwAAAAABjwcAAA==\",\"AAAAAAAAAAKbEQAAnBEAAAKPEAAAkBAAAA==\"]Ek[\"AAAAA/QHAAB9EgAA6xMAAAAAAAAAAAHTBwAAAAABkgcAAA==\",\"AAAAAAAAAAKdEQAAnhEAAAKREAAAkhAAAA==\"]Cg[\"AAAAAfEEAAAAASMAAAAAAAL2AAAA9wAAAA==\",\"AAAAAfEEAAA=\"]Dg[\"AAAAASYFAAAAAUgAAAACTAEAAE0BAAA=\",\"AAAAASYFAAAAAAAAAAFWDAAAAAHdEgAA\"]Bo[\"AAAAAegEAAAAARsAAAAAAAHpAAAA\",\"AAAAAegEAAA=\"]Cc[\"AAAB6gIAAAEXBQAAAAE5AAAAATQBAAA=\",\"AAAAARcFAAA=\"]Ig[\"AAABlQAAAAHtBAAAAAIgAAAA/wwAAAAAAfAAAAAAAAAAAAAAAAA6MAAAAQAAAAAADQAQAAAA5AEWAisHLwcmCSoJmQ/hD6oQDROHE5cTtRNnFA==\",\"AAGfBwAAAALtBAAAhQUAAAABoA8AAA==\"]Ic[\"AAAAApgAAAAdBQAAAAI/AAAAAg0AAAE8AQAAAAAAAAAAAAAAAAA6MAAAAQAAAAAADQAQAAAA5gEXAi0HMQcoCSwJmw/jD6wQDhOIE5gTthNoFA==\",\"AAG1BwAAAAEdBQAAAAABow8AAA==\"]Gg[\"AAACSAIAAE4CAAAB3wQAAAACEQAAABsHAAAC0wAAANQAAAA=\",\"AAACSQIAAEoCAAAB3wQAAAAAAAACrA8AAK0PAAAAAAJzBAAA4BIAAAAAAV4MAAA=\"]Do[\"AAAAAR8FAAAAAkAAAACjAAAAAz4BAAA/AQAAFRAAAAFaAwAA\",\"AAAAAR8FAAAAAAAAAVcEAAA=\"]Ck[\"AAAAAvITAADzEwAAAAAAAAAAAAABYgwAAA==\",\"AAAAAAABChEAAA==\"]Co[\"AAAAAAABBwcAAAONEgAAjhIAAI8SAAAAAbkBAAA=\",\"AAAAAAAB3w8AAA==\"]KBe[\"AAA7MAAAAQAAuwEDAPUH7ABnCYsAcgxCADswAAABAAD1AUMAZQAEAKkBAACuAQAAtAEAANwBBgCABAEA0gSLAHQFAACDBQIAvwUAAMMF7AC2BgAACwcAADYHBwBtBwMAyQcBANUHCQDkBwAA7QcAAPMHAQBCCQAATgkAAFwJAAAlDAAARwwAAEoMAQBUDAAAcQwAALsMAAD8DAAAlQ8BAOIPAAAfEAAAJBABADUQAQCLEAAAnBAAALAQAgC3EAEAxhAAAAERAAAtEQAAMREBAIARAgCEEQAAhhEAAIgRAQCLEQAAjREAAJARBAC2EQEAuREAAHwSAQDLEgAA0xIAANoSAQDmEgAAFhMAAMATAgDqEwEA7hMAAPITAgAXFAAALBQBAD8UAQBEFAEAYBQAADswAAABAABkAS0AqAEAAL8BAADOAQAAGQIGAEEDAABhBAAAwAQBAHEFAABzBQAAhwUAALoGAADjBgAAeAcTAI4HAACRBwAA5QcAAPIHAADiCAEAUQkBAFQJAQBYCQAAWwkAADAMBwA6DAAAiA4BAZcPAQACEAEAhRAEAJgQAwAXEQAAcREAAIgSAADMEgAAERMAABoTAABcEwAAYRMAAH4TAwCSEwAAFBQCAEcUAABcFAAAgRQFAJsUAACfFAAAOzAAAAEAAM8CNgALAFkAmQATAOMBAADlAQAAWAQAAF0EAABfBAAAcQQAAHcEAABgBQAAYwUBAHIFAADoBgAABwcAABkHCAAlBwAAMwkBAF0JAQAqDAMAVQwAAP0MBQAFDXoBoA8AAN8PAAAjEAAAKBAAAJ0QAACfEAAAoRAAAKMQAAClEAAAuxAAAAoRAAAYEQcAJBEEAHcRAAB5EQQAuBEAAO0RiwCDEgAAiRIAAIsSAADNEgUA3BIAAOISAADkEgAAFxMAAFMTAABbEwAAYhMAAIITAgDIEwAA7xMAABgUAAA7MAAAAQAADQEqALcAKQAnAW8AmQEOAFoEAACMBAUAdQUAAHsFAgDBBQAAywYDANAGAgDhBgEA6QYFAO4HAABTCQAA6gsDACYMAwBODAIAow8AAKsPAACvDwAACxATACAQAACzEAAAyRAAAC4RAACEEgAAjRICAMcSAQCWEwAAtxMFANUTAQDaEwEA3RMBAOwTAAAOFAAALhQDAFQUAABmFAAAexQAAH8UAACIFAAAphQDADswAAABAAC5AE8AUAMUAHQEAAB9BAIAiQQCAF4FAQBhBQAAdgUBAJEFAADGBgQA1AYMAN8HAQDiBwEA5AgBAC4JAQBECQEATwkBAOkLAAA8DAEATAwBAFkMAAADDQEAnQ8CALAPAQDrDwEA7g8BAAgQAAApEAEAMxABADsQAACeEAAAoBAAAKIQAACkEAAAphABAK4QAQC0EAAAxxABAMoQAAAvEQEANBEAADYRAAA4EQAAOhEAADwRAAB4EQAAgxEAAIURAACHEQAAihEAAIwRAACOEQAAmRENALARAQCzEQIAwxEBAHoSAQDeEgEAGBMAAE4TAQBUEwEAXRMDAHwTAQC+EwEAzhMBANITAQDwEwEA9RMAAPgTAQAlFAEAORQBAFUUAgBhFAMAcRQDAIkUAACZFAEAnRQBAKAUAQCkFAEAqhQBADswAAABAACsACwA4QBFAJcBAQCyAQEAVwQAAHUEAAB8BAAAbgUBAIkFAQCMBwAAkAcAAJMHAADSBwAA1AcAAN8LCQBmDAEAtQwBAKwPAQDkDwEAIRAAAC4QAwCPEAMAvBABAAgRAQASEQEAKxEBAHMRAwCHEgAAwBIFANQSAQDnEgMAYxMBAIUTAQCzEwEAxhMBAMkTAwDUEwAA7RMAADIUBgBBFAEARhQAAH0UAQCAFAAAkBQFAKIUAQA7MAAAAQAAMAAPAGMEDQCPBQEACQcAAGMJAQBWDAAAaAwEALwMAACADgAABBADAIoQAACrEQMAhhIAAJASAwDDEwEAdRQFADswAAABAAAeAA0AeQQBAIIEAQDRBwAA0wcAAFoMAwClDwIArg8AACkRAAA9EQEAlREDAO8SAACJEwcAihQAADowAAABAAAAAAAWABAAAABzBHkF8gbzBvQG9Qb2BvcG+Ab5BjQHNQcqETMRNRE3ETkROxGpEaoR3RLgEl0UB1kEAABbBAAAYgwAANMQAAAbEwAAGxQAABwUAAAHeAQAAI0HAACPBwAAkgcAAGAMAACoDwAAqg8AAATkBgAAXgwAAF8MAACpDwAAOzAAAAEAAAYAAwDNAQAAzwYAAA8UBAABZhMAAAFhDAAAAcIFAAA=\",\"AAAAOzAAAAEAAO0AAgDDBewAxhEAAAK6BgAAcREAADswAAABAAD3AAMAYwUBABkHCAAFDewAAcgSAAA6MAAAAQAAAAAAWQAQAAAAfgR/BN8H4AfkCOUIRAlFCU8JUAk8DD0MTAxNDAMNBA2eD58PsA+xD+sP7A/uD+8PKRAqEDMQNBCuEK8QxxDIEC8RMBGDEYURhxGKEYwRjhGZEZoRsBGxEcMRxBF6EnsS3hLfEk4TTxNUE1UTfBN9E74TvxPOE88T0hPTE/AT8RP4E/kTJRQmFDkUOhRWFFcUYRRiFGMUZBRxFHIUcxR0FJkUmhSdFJ4UoBShFKQUpRSqFKsUOzAAAAEAADMAFQCyAQEAiQUBAGYMAQC1DAEA5A8BAC4QAwC8EAEACBEBABIRAQArEQEA1BIBAOcSAwBjEwEAhRMBALMTAQDGEwEAyRMDAEEUAQB9FAEAkBQFAKIUAQAEYwkAAGQJAADDEwAAxBMAAAACqREAAKoRAAACWQQAAFsEAAAAAAAAA9QPAAAwEwAAXhQAAAEfCQAA\",1]Eo[\"AAAAAAA7MAAAAQAABQABAM0SBQA7MAAAAQAABQABALcTBQAAOzAAAAEAAAUAAQAzFAUAOzAAAAEAAAUAAQB1FAUA\",\"\"]Ck[\"AAAAAAAAAXUFAAAAAAFjBAAA\",\"AAAAAAAAA04MAABPDAAAUAwAAA==\"]Dg[\"AAAAAQgFAAAAAAHFBAAAAYMRAAABGwEAAA==\",\"AAGpBwAAAAEIBQAAAAAAAYMRAAA=\"]Dc[\"AAAAAVEFAAAAAAKcAQAAyQQAAAGKEQAA\",\"AAHBBwAAAAFRBQAAAAAAAYoRAAA=\"]Fg[\"AAAAAeoEAAAAAR0AAAAAAALsAAAA7QAAAA==\",\"AAGcBwAAAAHqBAAAAARfBAAAJQcAALgRAAAYFAAAAVMJAAAAAtQTAAAyFAAA\"]Fc[\"AAAAARoFAAAAATwAAAACOAEAADkBAAA=\",\"AAGyBwAAAAEaBQAAAAACIBAAALMQAAACWQwAAPUTAAAAAAGKFAAAAV0UAAA=\"]Eo[\"AAAAAbYRAAABUQkAAAACvQAAAEMMAAABeBEAAAABZwQAAA==\",\"AAAAAAHABQAAAAJDDAAA3A8AAAABjAcAAAGKEAAA\"]Dk[\"AAAABoUSAADhEgAAWRQAAGUUAACHFAAAmBQAAAAAAAABvRMAAA==\",\"AAAAAAAAAAFwEQAA\"]Oo[\"AAAAOjAAAAEAAAAAABEAEAAAAIAE1QSDBbYG5AdcCSUMVAxxDPwMHxA1EC0RyxLAE/QTFxRgFAeJEAAAiBIAABETAABcEwAAYRMAAJsUAACfFAAAOjAAAAEAAAAAAAcAEAAAAOMBXQRVDKAPJBF5EdwSghM6MAAAAQAAAAAABwAQAAAAvgAuEccSVBR/FIgUphSoFAFeBQAAAAAAAAAAAAEPFAAA\",\"AAAAAdUEAAAB4ggAAAABQwwAAA==\"]ACk[\"AAAAOjAAAAEAAAAAAAgAEAAAAIEEQglHDJUP4g8BEZIRLBQtFDowAAABAAAAAAAHABAAAABhBHEFhwXyBzoMcRHMEhYUOjAAAAEAAAAAAAoAEAAAAOUBcQQjECgQnRC7EHcRiRJbE2ITyBM6MAAAAQAAAAAABwAQAAAAJwGjDyAQsxDIEmYUpxSpFAVhBQAAWQwAAPUTAABVFAAAiRQAAASHEgAA7RMAAEYUAACAFAAAAlYMAACADgAAAYoUAAADKhEAAN0SAABdFAAA\",\"AAAAAAAAAAAAAAAAAAGNBwAAAAAAAAHCBQAA\"]Io[\"AAAAAzYHAADXBwAAgREAAANGCQAAMAwAAIEUAAABGBEAAAEuFAAAA+IHAACfEQAAoxEAAAABBBAAAAGJEwAAAfIGAAA=\",\"AAAAAAQwDAAAMQwAADIMAAAzDAAAAAXEBAAAxQQAAMYEAADHBAAAxxIAAA==\"]Io[\"AAAAAzoHAADbBwAAiBEAAANKCQAANAwAAIQUAAABHBEAAAEwFAAAA+MHAAChEQAApREAAAABBhAAAAGNEwAAAfYGAAA=\",\"AAAAAAQ0DAAANQwAADYMAAA3DAAAAAXIBAAAyQQAAMoEAADLBAAAyBIAAA==\"]No[\"AAABlAAAAAHsBAAAAAIfAAAA/gwAAAAAAe8AAAAAAAAAAAAAAAA6MAAAAQAAAAAAJAAQAAAADQJgBL8GKAcgCVYJUQxXDGMM3Q//D4wQuRC+EMsQAhEOERURIBEiEX4RpxG6EX8SgRLJEtYSEhMUE1gTmRObE9AT1xP2ExkUWhQ=\",\"AAGeBwAAAAHsBAAAAAAAAAAAAAAAAAAAAAAEJgkAACcJAAAqCQAAKwkAAA==\"]No[\"AAAAApcAAAAcBQAAAAI+AAAAAQ0AAAE7AQAAAAAAAAAAAAAAAAA6MAAAAQAAAAAAJAAQAAAADgJiBMMGKQchCVcJUgxYDGQM3g8AEI0QuhC/EMwQAxEPERYRIREjEX8RqBG7EYASghLKEtcSExMVE1kTmhOcE9ET2BP3ExoUWxQ=\",\"AAG0BwAAAAEcBQAAAAAAAAAAAAAAAAAAAAAEKAkAACkJAAAsCQAALQkAAA==\"]Cc[\"AAAAAQMFAAAAAAAAARIBAAA=\",\"AAAAAQMFAAAAAAHqCwAA\"]Ck[\"AAAAAUMFAAAAAAGEAQAA\",\"AAAAAUMFAAAAAAPrCwAA7AsAAO0LAAA=\"]Bk[\"AAAAAAAAAAAAAAAAAAF4BAAAAAAAAWEMAAA=\",\"\"]Bg[\"\",\"AAAAAAAAAAAAAAAAAAAAAAAAAZEPAAA=\"]Eo[\"AAAAATMFAAAAAU4AAAACYgEAAGMBAAAB3AYAAA==\",\"AAAAATMFAAAAAAHOBgAAAAAAAAAAAAAAAAACZwUAAJIPAAA=\"]Ak[\"AAAAAAAAAAABRhQAAA==\",\"\"]Ag[\"AAACpQIAACwDAAA=\",\"\"]Bo[\"AAAAAAAAAccAAAAAAAABXAwAAA==\",\"AAAAAR8QAAA=\"]Fg[\"AAAAAecEAAAAAxoAAABVDAAA3BIAAAGvDwAAAAHoAAAA\",\"AAGbBwAAAAHnBAAAAAElBwAAAa8PAAAAAAAAAAAAAAAAAAEmBwAA\"]Gc[\"AAAAARYFAAAAATgAAAABMwEAAAABhxIAAAFWDAAAAAHdEgAA\",\"AAGxBwAAAAEWBQAAAAFbEwAAASAQAAAAAYcSAAAAAAAAAAAAAAABJwcAAA==\"]Do[\"AAAAAAAAAAGwDwAAAokFAADkDwAA\",\"AAAAAboBAAAAAAABsA8AAASJBQAA5A8AAHMRAAB0EQAA\"]Do[\"AAAAAAAAAAGxDwAAAooFAADlDwAA\",\"AAAAAcUBAAAAAAABsQ8AAASKBQAA5Q8AAHURAAB2EQAA\"]Ao[\"AAAAAAGOEAAAAAABtBAAAA==\",\"\"]Gc[\"AAABjxEAAAJnAAAA1QcAAAEdAgAAAfINAAABzwAAAAKJBAAAsxEAAAKsDwAAIRAAAAABrg8AAAJzBAAA4BIAAAABYAwAAAJeDAAAXwwAAA==\",\"\"]Cg[\"AAAAAAAB8w0AAAADdAQAAIsEAAC1EQAA\",\"AAAAAAABIxAAAA==\"]Ec[\"AAABSgIAAAHgBAAAAAISAAAAHAcAAALVAAAA1gAAAA==\",\"AAABTAIAAAHgBAAAAAAAArMRAAC0EQAA\"]Bo[\"AAABEBEAAAAAAALKAAAAywAAAA==\",\"AAAAAAF4BQAA\"]Ck[\"AAABEREAAAAAAAJzAQAAdAEAAA==\",\"AAAAAAAAAAAAAAABeQUAAA==\"]Ao[\"AAAAAAAAAZYTAAABnQ8AAA==\",\"\"]Hg[\"AAAAAmkAAADtBwAAAR4CAAABXQkAAAPbAAAA4QYAAMkQAAACdgUAADsQAAAAAo8FAAC8DAAABaUPAACmDwAApw8AACkRAADvEgAAAAEbEwAAAqgPAACqDwAAAakPAAA=\",\"\"]Do[\"AAAAAAABXgkAAALiBgAA7gcAAAJ3BQAAyhAAAAABkAUAAAAAAdMQAAA=\",\"AAAAAAABKBAAAA==\"]Bc[\"AAAAAAAAAAAAAAAAAAAAAAFmEwAA\",\"\"]AAk[\"AAAB6Q8AAAGzBgAAAtMBAAAVBwAABLIGAABVDAAA3w8AANwSAAACVBQAAIgUAAADcBEAAFUUAACJFAAABYwHAABzEQAAdBEAAHURAAB2EQAAAYoQAAAAAAABjQcAAA==\",\"AAAABAsHAAC2EQAAtxEAALkRAAADqAEAAJcPAACYDwAAAbgRAAAFfxQAAKYUAACnFAAAqBQAAKkUAAAB9RMAAAGAFAAAAYYSAAAAAAAAAAAAArUPAADTDwAAOzAAAAEAAAUAAgAPAgIA5g8CAA==\"]Cc[\"AAAAATQFAAAAAU8AAAACZAEAAGUBAAA=\",\"AAAAATQFAAA=\"]Dc[\"AAAAAAAAAAA7MAAAAQAABQABAMASBQA=\",\"AAAABNwBAADgAQAA4QEAAOIBAAA=\"]Ao[\"AAAAAdwBAAAAAAAAAS4QAAA=\",\"\"]Ao[\"AAAAAeEBAAAAAAAAAS8QAAA=\",\"\"]Eo[\"AAAAAZERAAABfxMAAAOhEAAAJhEAAHsRAAAB2xMAAAGcEQAAAZAQAAACrBEAAJESAAABlhEAAAAAAAABERQAAA==\",\"\"]Eo[\"AAAAAZQRAAABgRMAAAOlEAAAKBEAAH0RAAAB3hMAAAGeEQAAAZIQAAACrhEAAJMSAAABmBEAAAAAAAABExQAAA==\",\"\"]Bc[\"AAAAAAAAAowEAADpBgAAAAEwEAAA\",\"\"]Bc[\"AAAAAAAAAo8EAADsBgAAAAExEAAA\",\"\"]Ec[\"AAAAAdYBAAAAAuISAADkEgAAAtUTAADWEwAAAn0EAADpCwAAAAAAAAABeAQAAA==\",\"AAAAAAFBAwAA\"]Gg[\"AAAAAeIEAAAAAhQAAAAeBwAAAtwAAADdAAAA\",\"AAADUwIAAFQCAABWAgAAAeIEAAAAAAABOxAAAAACjwUAALwMAAAB7xIAAAABGxMAAAABqQ8AAA==\"]Ek[\"AAAAASEFAAAAAkIAAAClAAAAA0IBAABDAQAAFxAAAAFcAwAA\",\"AAAAASEFAAAAAAAAAAGQBQAAAAAB0xAAAA==\"]Ec[\"AAAAAAAAAXIBAAAAAAAAAAAAAAAAAALRBAAAYgkAAA==\",\"AAAAAAAAAAAAAAAAAAAAAAAAAcIFAAA=\"]Ec[\"AAABLgwAAAHUBAAAAAELAAAAAbkAAAA=\",\"AAAAAdQEAAAAATQJAAAAAAAAAAAAAAAAAAABsAYAAA==\"]Dg[\"AAAAAAFzBQAAAAAAAAAAAAAAAAAAAALOEAAAchEAAA==\",\"AAGWBwAAAAAAAbIGAAA=\"]Ac[\"AAABdAIAAA==\",\"\"]Do[\"AAAAAfsEAAAAASYAAAAAAAIDAQAABAEAAA==\",\"AAAChAIAAIUCAAAB+wQAAAAAAAAAAcMTAAA=\"]Dk[\"AAAAATEFAAAAAUsAAAACXAEAAF0BAAA=\",\"AAAC9gIAAPcCAAABMQUAAAAAAAAAAcQTAAA=\"]Bk[\"\",\"AAAAAAAAAAAAAAAAAAAAAAAAAjkJAAA7CQAA\"]Dc[\"AAAAAAAAAAABIQEAAA==\",\"AAAAAAAAAAAAAAF5BAAAATQHAAABWQQAAAGPBwAA\"]Co[\"AAAAAAAAAZUBAAA=\",\"AAAAAAAAAAAAAAF6BAAAATUHAAABWwQAAAGSBwAA\"]Dk[\"AAAAAQoFAAAAAcIBAAABxgQAAAGFEQAAAR0BAAA=\",\"AAGqBwAAAAEKBQAAAAAAAYURAAA=\"]Dk[\"AAAAAVMFAAAAAdQBAAACngEAAMoEAAABjBEAAA==\",\"AAHCBwAAAAFTBQAAAAAAAYwRAAA=\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAWoAAAA=\",\"\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAW8AAAA=\",\"\"]Ck[\"AAAAASIFAAAAAUMAAAACRAEAAEUBAAA=\",\"AAAAASIFAAAAAAHiBgAA\"]Eg[\"AAAAAfYEAAAAAiUAAACbAAAAAQ0QAAABUgMAAAL+AAAA1BIAAA==\",\"AAGlBwAAAX4CAAAC9gQAAEAKAAA=\"]Eg[\"AAAAASwFAAAAAkoAAACmAAAAAlcBAAAYEAAAAV0DAAAB1RIAAA==\",\"AAG8BwAAAfACAAABLAUAAAGiCgAA\"]Ao[\"AAAAAAAAAAACvBAAAHMRAAA=\",\"\"]Ao[\"AAAAAAAAAAACvRAAAHURAAA=\",\"\"]Do[\"AAABeAAAAAHTBAAAAAABuAAAAA==\",\"AAAAAdMEAAAAATMJAAAAAAAAAAAAAAAAAAABwBAAAA==\"]Bg[\"AAAAAAAABNoAAAB9BQAAUAwAAI8SAAA=\",\"\"]Ak[\"AAAAAAABRAAAAAFGAQAA\",\"\"]Do[\"AAAAAAAAAAAAAAAAAAAAAAAAAg0RAACMEgAA\",\"AAGUBwAAAAAAAAAAAAAAAAAAAAAAAAHNEAAA\"]Cg[\"AAAAAAPjBgAAWwkAABcRAAA=\",\"AAAAAAAAAAKdDwAAtBAAAA==\"]Cc[\"AAAAAWUKAAACZgoAANYKAAAB1AoAAALPEAAA0RAAAA==\",\"\"]Ac[\"\",\"AAAAAcYSAAA=\"]Gg[\"AAAAAfoEAAABXBQAAAAAAeQIAAAEAgEAADAQAADoEgAAQRQAAAGKEAAA\",\"AAAAA74BAAD6BAAAaRQAAAABHRQAAAAB5AgAAAMwEAAA6BIAAEEUAAA=\"]Bc[\"AAAAAb4BAAAAAAAAAAAAATMRAAA=\",\"\"]Gg[\"AAAAATAFAAAAAAFbAQAAAeUIAAADMRAAAOoSAABCFAAAAAAAAAGNBwAA\",\"AAAAA8wBAAAwBQAAahQAAAABHhQAAAAB5QgAAAMxEAAA6hIAAEIUAAA=\"]Bc[\"AAAAAcwBAAAAAAAAAAAAATcRAAA=\",\"\"]Fk[\"AAABVAIAAAHvBAAAAAEhAAAAAAAC8gAAAPMAAAA=\",\"AAGhBwAAAAHvBAAAAAJdCQAA8g0AAALhBgAAyRAAAAOJBAAAigQAAHYFAAA=\"]Fc[\"AAAAASQFAAAAAUYAAAACSAEAAEkBAAA=\",\"AAG4BwAAAAEkBQAAAAJeCQAA8w0AAAHuBwAABIsEAAB3BQAAyhAAALURAAA=\"]Ao[\"\",\"AAAAAAAAAAAAAAABOxEAAA==\"]Ek[\"AAAAAf8EAAAAASoAAAAAAAILAQAADAEAAA==\",\"AAABigIAAAH/BAAAAAAAAAGMBwAAAYoQAAAAAjMRAAA1EQAA\"]Eg[\"AAAAAToFAAAAAVQAAAACcAEAAHEBAAA=\",\"AAABBAMAAAE6BQAAAAAAAAAAAAI3EQAAOREAAAABjQcAAA==\"]Ao[\"AAAAAAAAAAGnEAAAAAFsDAAA\",\"\"]Jk[\"AAAAAQAFAAAABSsAAACcAAAAYwUAAJ8QAAChEAAAAuoLAAAOEAAABFMDAAA8DAAAoBAAAKIQAAAEDQEAAA4BAABzEQAAdBEAAAJoDAAAaQwAAAKVEQAAlhEAAA==\",\"AAAAAgAFAAC3EAAAAr8BAACYEAAAAAABLgkAAA==\"]Og[\"AAAAAT8FAAAABVcAAACpAAAAZAUAAKMQAAClEAAABn0BAAB+AQAA6wsAAOwLAADtCwAAGxAAAARgAwAAPQwAAKQQAACmEAAAAnURAAB2EQAAAmoMAABrDAAAApcRAACYEQAA\",\"AAAABD8FAADJBwAAygcAALgQAAAEzgEAAJkQAACaEAAAmxAAAAAAAS8JAAACbgUAAG8FAAAAAAAAAAAAAAA7MAAAAQAACgACAMsHBQDoBwQA\"]Cc[\"AAAAATUFAAAAAVAAAAACZgEAAGcBAAA=\",\"AAAAATUFAAA=\"]Ck[\"AAABagUAAAEBBQAAAAEsAAAAAAABDwEAAA==\",\"AAAAAgEFAAA+DAAA\"]Dg[\"AAABawUAAAFABQAAAAFYAAAAAX8BAAAAAAAAAAAAAeQGAAA=\",\"AAAAAkAFAAA/DAAA\"]Cc[\"AAAAAVkFAAAAAWQAAAABpwEAAA==\",\"AAAAAlkFAABADAAA\"]Dg[\"AAAAAAAAAAAAAAABKhEAAA==\",\"AAAABpwQAADTEgAAwRMAAMITAADyEwAA8xMAAA==\"]Ak[\"AAAAAAAAAAABwhIAAA==\",\"\"]Ak[\"AAAAAAAAAAABxRIAAA==\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAm4AAAAPAgAA\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAnMAAAAQAgAA\",\"\"]Bg[\"AAAAAAAAAAAAAWwEAAA=\",\"AAAAAS0RAAA=\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAm0AAAAmBwAA\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAnIAAAAnBwAA\",\"\"]Bo[\"AAAAAAAAAAAAAAAE8gYAAPMGAAD0BgAA9QYAAA==\",\"\"]Ec[\"AAAABKkBAAC0AQAAFQUAAIsQAAAAAAEyAQAAAAAAAAT2BgAA9wYAAPgGAAD5BgAA\",\"AAAAARUFAAA=\"]Bg[\"AAAAAAAAAAAAAW0EAAA=\",\"AAAAAAFcEwAA\"]Cc[\"AAAAAWkKAAACaAoAANkKAAAB2goAAALQEAAA0hAAAA==\",\"\"]Ag[\"AAAAAAABFwcAAA==\",\"\"]No[\"AAAAAfgEAAABFQcAAAEXEwAAAdwPAAAGTAwAAK4QAAC+EwAA0hMAADkUAABhFAAABwABAAC5AQAAtQwAAC4QAADnEgAAyRMAAMsTAAABQxQAAAAAAAAAAAAAA8AGAAC5DAAA6xIAAA==\",\"AAAAAvgEAAA3EAAAAAG9AQAAAe0SAAAGTAwAAK4QAAC+EwAA0hMAADkUAABhFAAABbUMAAAuEAAA5xIAAMkTAADLEwAA\"]Bo[\"AAAAATcQAAAAAb0BAAAAAqAQAAA0EQAAAAFoDAAA\",\"\"]Nc[\"AAAAAS4FAAAAAAFZAQAAOjAAAAEAAAAAAAcAEAAAAMYBTQyvEBgTvxPTEzoUYhQFtgwAAC8QAADpEgAAyhMAAMwTAAAAAAAAAAAAAAAEwgUAAMQGAAC6DAAA7BIAAA==\",\"AAAAAi4FAAA4EAAAAAHLAQAAAe4SAAAGTQwAAK8QAAC/EwAA0xMAADoUAABiFAAABbYMAAAvEAAA6RIAAMoTAADMEwAA\"]Bo[\"AAAAATgQAAAAAcsBAAAAAqQQAAA4EQAAAAFqDAAA\",\"\"]Ao[\"AAAAAAAAAAACyRMAAMoTAAA=\",\"\"]Ao[\"AAAAAAAAAAACyxMAAMwTAAA=\",\"\"]Dc[\"AAABVgIAAAHjBAAAAAIVAAAAHwcAAALeAAAA3wAAAA==\",\"AAABWAIAAAHjBAAA\"]Bk[\"AAAAAd8BAAAAAATZAAAAfAUAAE8MAACOEgAA\",\"\"]Ec[\"AAAAAnkTAAB6EwAAAncTAAB4EwAABCoMAAArDAAALAwAAC0MAAAEJgwAACcMAAAoDAAAKQwAAA==\",\"\"]Cc[\"AAAAAAAAAAABwRIAAA==\",\"AAGXBwAAAAK4AQAAVhMAAA==\"]Cc[\"AAAAAAAAAAABxBIAAA==\",\"AAGtBwAAAALEAQAAVxMAAA==\"]Bc[\"\",\"AAAAAAAAAAAAAAAAAAAB5AYAAA==\"]Cg[\"AAAAAZYPAAAAAAF8AQAAAZ4QAAA=\",\"AAHIBwAAAAAAAcgTAAA=\"]Ek[\"AAAAAdoEAAAAAAHEAAAAAAAAAAAAAAAAAAAEUhMAAFoTAAB2EwAAkRMAAA==\",\"AAGYBwAAAALaBAAAhAUAAA==\"]Cc[\"AAAAAAAAAAAAAAAAAAAAAAAAA18JAAAsEAAAlhQAAA==\",\"\"]Cc[\"AAAAAAAAAAAAAAAAAAAAAAAAA2AJAAAtEAAAlxQAAA==\",\"\"]Bk[\"\",\"AAAAAAAAAAAAAAAAAAAAAAAAAjoJAAA8CQAA\"]Ac[\"\",\"AAAAAAHABAAA\"]Bc[\"AAAAAAABWAQAAA==\",\"AAAAAAHBBAAA\"]Ec[\"AAAAAQwFAAAAAcMBAAABxwQAAAKHEQAA8BMAAAEfAQAA\",\"AAGrBwAAAAEMBQAAAAAAAocRAADwEwAA\"]Ec[\"AAAAAVUFAAAAAdUBAAACoAEAAMsEAAACjhEAAPETAAA=\",\"AAHDBwAAAAFVBQAAAAAAAo4RAADxEwAA\"]Ag[\"\",\"AAAAAAABgxIAAA==\"]Co[\"AAAAAq4BAADJBwAAAAABdQEAAA==\",\"AAAAAAAAAAAAAAAAAAAAAc0BAAA=\"]Ac[\"\",\"AAAAAAEaEwAA\"]Cg[\"AAAAAAAAAu0SAADuEgAAAAABhhIAAA==\",\"AAAAAAAAAAHNEwAA\"]Do[\"AAAAASAFAAAAAkEAAACkAAAAA0ABAABBAQAAFhAAAAFbAwAA\",\"AAAAASAFAAAAAAABdAQAAA==\"]Ak[\"AAAAAAACHRQAAB4UAAA=\",\"\"]Ac[\"AAAAAeABAAA=\",\"\"]Ac[\"AAAAAeIBAAA=\",\"\"]Eo[\"AAACDAMAACEDAAABSQUAAAABXwAAAAKPAQAAkAEAAA==\",\"AAAAAUkFAAAAAAAFxgYAAMcGAADIBgAAyQYAAMoGAAA=\"]Fk[\"AAAABLAQAACxEAAAwRMAAMITAAABlw8AAAEKEQAAAYQSAAAAAAFDFAAAAAAAAAAAAAFhDAAA\",\"AAAAAAAAA40SAACOEgAAjxIAAA==\"]Fc[\"AAAAAAABsgYAAALtEgAA7hIAAAFwEQAAAAGGEgAAAAACGxQAABwUAAAAAAABZhMAAA==\",\"AAAAAAAAAVgUAAABzRMAAA==\"]Co[\"AAAAAfIEAAAAASQAAAAAAAL4AAAA+QAAAA==\",\"AAAAAfIEAAAAAWAFAAA=\"]Co[\"AAAAAScFAAAAAUkAAAACTgEAAE8BAAA=\",\"AAAAAScFAAAAAAAAAAGADgAA\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAQUHAAA=\",\"\"]Ik[\"AAAAAvcEAAA/FAAAAAAAAnoSAABUEwAABf8AAAC5AQAAZgwAAAgRAABjEwAAA2MJAADDEwAAQxQAAA==\",\"AAGmBwAAAAK8AQAA9wQAAAAAAAJ6EgAAVBMAAANmDAAACBEAAGMTAAACYwkAAMMTAAA=\"]Ig[\"AAAAAi0FAABAFAAAAAABWAEAAAPGAQAAexIAAFUTAAADZwwAAAkRAABkEwAAAmQJAADEEwAA\",\"AAG9BwAAAALKAQAALQUAAAG6BgAAAAACexIAAFUTAAADZwwAAAkRAABkEwAAAmQJAADEEwAA\"]Ao[\"AAAAAAAAAcwAAAAAAAFvBAAA\",\"\"]Bc[\"AAAAAAABTQAAAAJgAQAAYQEAAA==\",\"\"]Ag[\"AAACrgIAADEDAAA=\",\"\"]Ak[\"AAAAAAAAAo0EAADqBgAA\",\"\"]Ak[\"AAAAAAAAApAEAADtBgAA\",\"\"]Ag[\"AAACpgIAADMDAAA=\",\"\"]Dk[\"AAAAAeEEAAAAAhMAAAAdBwAAAtcAAADYAAAA\",\"AAABTgIAAAHhBAAAAAAAAAAAAa4PAAA=\"]Dg[\"AAAAAd4EAAAAAhAAAAAaBwAAAtEAAADSAAAA\",\"AAABSAIAAAHeBAAAAAAAAAEhEAAA\"]Co[\"AAABihIAAAEjBQAAAAFFAAAAAUcBAAA=\",\"AAG3BwAAASUJAAABIwUAAA==\"]Ec[\"AAAAAAABixIAAAG6AAAAAAAAAAAAAAAAAAADhAQAACIJAACyDwAA\",\"AAAAAbYBAAAAAAAAAb0TAAA=\"]Hk[\"AAAC5QIAAP4CAAABQQUAAAABWQAAAAKAAQAAgQEAAAAAAAAAAAAAAAAABBUCAABnBQAAxQYAAAQHAAA=\",\"AAABEgMAAALPAQAAQQUAAAAAAt0TAADeEwAAAAACrREAAK4RAAA=\"]Fc[\"AAAAA5wQAACyEAAA0xIAAAAC4hIAAOQSAAADpAEAANUTAADWEwAAAn0EAADpCwAAAAAAAAABeAQAAA==\",\"AAAAAdYBAAA=\"]Dc[\"AAAAAmYAAADdBAAAAhwCAADjCAAAAQ8AAAABzgAAAA==\",\"AAAAAt0EAAA2EAAA\"]Eg[\"AAAAAf0EAAAAASgAAAAAAAIHAQAACAEAAAAAAAAAAAAAAAP/BgAAig8AAJEPAAA=\",\"AAABiAIAAAH9BAAA\"]Eg[\"AAAAATYFAAAAAVEAAAACaAEAAGkBAAAAAAAAAAAAAAAAAAMCBwAAjA8AAJIPAAA=\",\"AAAB/gIAAAE2BQAA\"]Io[\"AAAFOAIAAEkCAABMAgAAUwIAAFgCAAAB8AQAAAACIgAAACAHAAAAAAL0AAAA9QAAAA==\",\"AAADdAIAAHUCAAB2AgAAAvAEAAC2BgAAAsAFAACJEAAAAYITAAAB3A8AAAF4EQAAAAAAAAAAAAAAAAH+BgAA\"]Ek[\"AAAAASUFAAAAAkcAAAAhBwAAAkoBAABLAQAA\",\"AAAB5QIAAAElBQAAAAAAAAAAAAAAAAAAAAAC0QQAAAUHAAA=\"]Bk[\"AAAAAAABnRAAAAF7AQAA\",\"AAAAAAAAAAGeEAAA\"]Hg[\"AAABlQAAAAHuBAAAAAH/DAAAAAAB8QAAAAAAAAAAAAAAAAYsBwAAMAcAACcJAAArCQAAmg8AAKsQAAA=\",\"AAGgBwAAAALuBAAAgwUAAAAAAAAAAAAAAAAAAAAAAYYFAAA=\"]Fo[\"AAAAApgAAAAeBQAAAAECDQAAAT0BAAAAAAAAAAAAAAAAAAYuBwAAMgcAACkJAAAtCQAAnA8AAK0QAAA=\",\"AAG2BwAAAAEeBQAAAYcFAAA=\"]Ag[\"AAACsAIAAC0DAAA=\",\"\"]Cg[\"AAAAAVsFAAAAAAADnQ8AAAgQAAC0EAAA\",\"AAHHBwAAAAFbBQAA\"]Dc[\"AAAAAjsFAADKBwAAAAABdgEAAA==\",\"AAAAATsFAAAAAAAAAAAAAAAAAAHPBgAA\"]Dg[\"AAAAAWcTAAABORAAAAAAAAL6AAAAdBEAAA==\",\"AAEwCQAAAbsBAAABZxMAAAE5EAAA\"]Dg[\"AAAAAWgTAAABOhAAAAABUwEAAAABdhEAAA==\",\"AAExCQAAAckBAAABaBMAAAE6EAAA\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAY8PAAA=\",\"\"]Ac[\"\",\"AAAAAAFHFAAA\"]Ag[\"AAACrwIAADIDAAA=\",\"\"]Bc[\"\",\"AAAAAAAAAAADwBIAAMESAADCEgAA\"]Bc[\"\",\"AAAAAAAAAAADwxIAAMQSAADFEgAA\"]Ak[\"\",\"AAAAAeYSAAAAAAEOFAAA\"]ADg[\"AAAAAfMEAAAAAAA6MAAAAQAAAAAAFAAQAAAA3wcDDZ4P6w/uDykQLxGwEcMR3hJOE/gTVhRjFHEUcxSZFJ0UoBSkFKoUOjAAAAEAAAAAAA0AEAAAAPsAvBArEdQS5xLoEoUTsxPGE30UkBSSFJQUohQAAAGpEQAA\",\"AAGiBwAAAAHzBAAAAAAAOjAAAAEAAAAAABQAEAAAAN8HAw2eD+sP7g8pEC8RsBHDEd4SThP4E1YUYxRxFHMUmRSdFKAUpBSqFDowAAABAAAAAAAKABAAAAC8ECsR1BKFE7MTxhN9FJAUkhSUFKIUAAABqREAAA==\"]ADk[\"AAAAASkFAAAAAAFUAQAAOjAAAAEAAAAAABQAEAAAAOAHBA2fD+wP7w8qEDARsRHEEd8STxP5E1cUZBRyFHQUmhSeFKEUpRSrFDowAAABAAAAAAAMABAAAAC9ECwR1RLpEuoShhO0E8cTfhSRFJMUlRSjFAAAAaoRAAA=\",\"AAG5BwAAAAEpBQAAAAAAOjAAAAEAAAAAABQAEAAAAOAHBA2fD+wP7w8qEDARsRHEEd8STxP5E1cUZBRyFHQUmhSeFKEUpRSrFDowAAABAAAAAAAKABAAAAC9ECwR1RKGE7QTxxN+FJEUkxSVFKMUAAABqhEAAA==\"]Ag[\"AAAChAIAAPcCAAA=\",\"\"]Bk[\"AAAAAAAAAAAAAWQEAAA=\",\"AAAAAAAAAUMMAAA=\"]Bk[\"AAAAAAAAAAAAAAFbDAAA\",\"AAAAAAABGBQAAA==\"]Do[\"AAAAAf4EAAAAASkAAAAAAAIJAQAACgEAAAAAAAAAAAAAAAIABwAAiw8AAA==\",\"AAAAAf4EAAA=\"]Do[\"AAAAATcFAAAAAVIAAAACagEAAGsBAAAAAAAAAAAAAAAAAAIDBwAAjQ8AAA==\",\"AAAAATcFAAA=\"]Ag[\"AAACqQIAACoDAAA=\",\"\"]Fc[\"AAAAATkFAAAAAlMAAACnAAAAAm4BAAAZEAAAAl4DAACeEAAA\",\"AAAAATkFAAAAAAAAAAAAAAABjQcAAAAAAAABwgUAAA==\"]Ao[\"AAAAAAHABAAA\",\"AAAAAAGOBwAA\"]Ao[\"AAAAAAHBBAAA\",\"AAAAAAGRBwAA\"]Ag[\"AAACpwIAACsDAAA=\",\"\"]Ag[\"AAACrAIAADADAAA=\",\"\"]Dg[\"AAAAAQ4FAAAAAAABXRMAAAAAAAE1EQAA\",\"AAAAAg4FAABlCgAAAWYKAAAAAc8QAAA=\"]Do[\"AAAB1wEAAAFNBQAAAAAAAV8TAAAAAAABOREAAA==\",\"AAAAAU0FAAAB1goAAAHUCgAAAdEQAAA=\"]Do[\"AAAAARAFAAAAAAADohAAADYRAABeEwAAAAFpDAAA\",\"AAAAAhAFAABpCgAAAWgKAAAAAdAQAAA=\"]Eg[\"AAAB2AEAAAFPBQAAAAAAA6YQAAA6EQAAYBMAAAABawwAAA==\",\"AAAAAU8FAAAB2QoAAAHaCgAAAdIQAAA=\"]Cc[\"AAAAAAAAAo4EAADrBgAAAAETAQAA\",\"AAGoBwAAAAHAAQAA\"]Cc[\"AAAAAAAAA4YBAACRBAAA7gYAAA==\",\"AAHABwAAAAHRAQAA\"]Cc[\"AAAAAAAAAAAAAAAAAAAAAAAAA/0GAADQBwAA7AcAAA==\",\"\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAY4PAAA=\",\"\"]Ao[\"AAAAAAAAAcQEAAAAAZAUAAA=\",\"\"]Bk[\"AAAAAAAAAcgEAAAAAZEUAAA=\",\"AAAAAcwEAAA=\"]Eg[\"AAAAAAAAAAAAAAAAAAAAAAAABGUJAAAJEAAAxBAAAA8TAAA=\",\"AAGsBwAAAATXBwAA2AcAANkHAADaBwAA\"]Eg[\"AAAAAAAAAAAAAAAAAAAAAAAABGYJAAAKEAAAxRAAABATAAA=\",\"AAHEBwAAAATbBwAA3AcAAN0HAADeBwAA\"]Bo[\"AAAAAAABvQEAAAAAAbUMAAAAAXkEAAAAAVkEAAA=\",\"\"]Bo[\"AAAAAAABywEAAAAAAbYMAAAAAXoEAAAAAVsEAAA=\",\"\"]Ak[\"AAAAAAABgxMAAAHsEwAA\",\"\"]Ao[\"AAAAAAABhBMAAAAAAe0TAAA=\",\"\"]Fo[\"AAAAAT4FAAAAAlYAAACoAAAAA3kBAAB6AQAAGhAAAAFfAwAA\",\"AAABDAMAAAE+BQAAAAAAAAJuBQAAbwUAAAAAAAAAAALNAQAAzwYAAA==\"]Bg[\"\",\"AAAAAAAAAAAAAAAAAAAAAAAAAQEHAAA=\"]Dg[\"AAAAARgFAAAAAToAAAABNQEAAA==\",\"AAAAARgFAAAAAAAAAAAAAAAAAAAAAAEGBwAA\"]Dg[\"AAAAAdgEAAAAAgwAAAAZBwAAAsEAAADCAAAA\",\"AAABOAIAAAHYBAAAAAABLhEAAA==\"]Do[\"AAAAARMFAAAAAjYAAAChAAAAAy4BAAAvAQAAExAAAAFYAwAA\",\"AAAAARMFAAAAAAAAAXwEAAA=\"]Ek[\"AAAAAfQEAAAAAAACzhMAACUUAAAD/AAAABIRAACzEwAA\",\"AAGjBwAAAAH0BAAAAAAAAs4TAAAlFAAAARIRAAA=\"]Ek[\"AAAAASoFAAAAAAFVAQAAAs8TAAAmFAAAAhMRAAC0EwAA\",\"AAG6BwAAAAEqBQAAAAAAAs8TAAAmFAAAARMRAAA=\"]Bo[\"AAAAAAAAAAABbgUAAAAAAAAAAALNAQAAzwYAAA==\",\"\"]Fg[\"AAAAAfUEAAAAAAAERAkAADMQAACZEQAAfBMAAAL9AAAAxhMAAA==\",\"AAGkBwAAAAH1BAAAAAAABEQJAAAzEAAAmREAAHwTAAA=\"]Fg[\"AAAAASsFAAAAAAFWAQAABEUJAAA0EAAAmhEAAH0TAAABxxMAAA==\",\"AAG7BwAAAAErBQAAAAAABEUJAAA0EAAAmhEAAH0TAAA=\"]Ag[\"\",\"AAAAAAAAAVgUAAA=\"]Bc[\"\",\"AAAAAAAAAAAAAAAAAAAAAAFmEwAA\"]Co[\"AAAAAekEAAAAARwAAAAAAALqAAAA6wAAAA==\",\"AAAAAekEAAAAAAGvDwAA\"]Co[\"AAAAARkFAAAAATsAAAACNgEAADcBAAA=\",\"AAAAARkFAAAAAAAAAYcSAAA=\"]Fg[\"AAABYwIAAAECBQAAAAEtAAAAAAHXBgAAAhABAAARAQAA\",\"AAACjgIAAL4GAAABAgUAAAAAAAHJBgAAAAAAAAAAAAAAAAGKDwAA\"]Gc[\"AAABzQIAAALPAQAAQgUAAAABWgAAAAKCAQAAgwEAAAHeBgAA\",\"AAACFAMAAMIGAAABQgUAAAAAAdAGAAAAAAAAAAAAAAAAAAIVAgAAjA8AAA==\"]Cc[\"AAAAAAAAAAAAAAAAAAAAAAAAA8wHAADpBwAA5wgAAA==\",\"\"]Cg[\"AAABigIAAAAAAAAAASIBAAA=\",\"AAAAAAAAAAKgEAAAohAAAA==\"]Cg[\"AAABBAMAAAAAAAGWAQAA\",\"AAAAAAAAAAOkEAAAphAAAKcQAAA=\"]Bo[\"AAAAAswEAABaBQAAARoTAAA=\",\"AAAAAloFAADGEQAA\"]Bo[\"AAAAAAAAAcgAAAAAAAABXQwAAA==\",\"AAAAAfQTAAA=\"]Io[\"AAAAAUoFAAAAAWAAAAABkQEAAAaRBQAANBEAADYRAAA4EQAAOhEAADwRAAAAAAAFMxEAADURAAA3EQAAOREAADsRAAAAAAAAAAABrBQAAA==\",\"AAAAAUoFAAAAAATaEwAA2xMAAN0TAADeEwAAAAF1BAAA\"]Co[\"AAAAAmgAAADWBwAAAAAB0AAAAAKKBAAAtBEAAAGtDwAA\",\"AAABjxEAAA==\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAjkJAACUEAAA\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAjsJAACWEAAA\",\"\"]Co[\"AAAAAQ8FAAAAAAAAAiUBAAAmAQAA\",\"AAAAAQ8FAAAAAALPEAAA0BAAAA==\"]Dc[\"AAAAAU4FAAAAAAKZAQAAmgEAAA==\",\"AAAAAk4FAADGEgAAAAAC0RAAANIQAAA=\"]Cc[\"AAAAAAGoAQAAAAG8AAAAAAAAAVoMAAA=\",\"AAAAARcUAAA=\"]Ac[\"\",\"AAABLgwAAA==\"]Ec[\"AAABhQIAAAHlBAAAAAEXAAAAAAAC4wAAAOQAAAA=\",\"AAAAAeUEAAAENwkAACkKAAAqCgAALAoAAA==\"]Do[\"AAAB9gIAAAESBQAAAAE0AAAAAisBAAAsAQAA\",\"AAAAARIFAAAEOAkAAIAKAACBCgAAggoAAA==\"]Ak[\"\",\"AAAAAAACHRQAAB4UAAA=\"]Ck[\"AAAAAtcEAABOCQAAAeIIAAABUxMAAAHAAAAA\",\"AAAAAtcEAAA1EAAA\"]Bg[\"AAAAAW0HAAAChRAAAJgQAAAAAeoLAAA=\",\"\"]Bk[\"AAAAAW4HAAAChhAAAJkQAAAAAusLAADsCwAA\",\"\"]Bc[\"AAAAAAAAAAAAAAAAAhsUAAAcFAAA\",\"\"]Fo[\"AAAAAfkEAAAAAAADfgQAAE8JAAA5FAAAAgEBAACyAQAA\",\"AAGnBwAAAAH5BAAAAAAAAn4EAABPCQAAAbIBAAAAAAAAAAAAAAAB6xIAAA==\"]Fo[\"AAAAAS8FAAAAAAFaAQAAA38EAABQCQAAOhQAAAGzAQAA\",\"AAG+BwAAAAEvBQAAAAAAAn8EAABQCQAAAbMBAAAAAAAAAAAAAAAB7BIAAA==\"]Ck[\"AAAAASgFAAAAAAJQAQAAUQEAAA==\",\"AAAAASgFAAAAAAAAAUYUAAA=\"]Ec[\"AAAAATwFAAAAAAF3AQAAAAAAAAAAAAAAAAAFrwEAAMsHAADoBwAA5ggAALcMAAA=\",\"AAAAATwFAAA=\"]Dg[\"AAAAAAFHFAAAAAG7AAAAAAAAAAACGxQAABwUAAAAAAABZhMAAA==\",\"AAABtwEAAA==\"]Ag[\"AAACrQIAACkDAAA=\",\"\"]Bc[\"\",\"AAAAAAAAAAAAAAAAAhsUAAAcFAAA\"]Cc[\"AAAAAVwFAAAAAAAAAAAAATsRAAA=\",\"AAAB1wEAAAFcBQAA\"]Ck[\"AAAAAV0FAAAAAAACpxAAADwRAAAAAWwMAAA=\",\"AAAB2AEAAAFdBQAA\"]Ag[\"AAACqwIAAC4DAAA=\",\"\"]Dg[\"AAAAAAABMQAAAAAAASABAAA=\",\"AAAAAAAAAAAAAAAAAAAAAw8UAAAQFAAAERQAAA==\"]Ck[\"AAAAAAABYgAAAAGUAQAA\",\"AAAAAAAAAAAAAAAAAAAAAhIUAAATFAAA\"]Ag[\"AAACqAIAAC8DAAA=\",\"\"]Do[\"AAAAAW8HAAAChxAAAJoQAAAAAe0LAAABxxAAAA==\",\"AALIAQAArgcAAAEyCQAAAAAAAAHHEAAA\"]Ec[\"AAAAAkQFAABwBwAAAogQAACbEAAAAAGFAQAAAcgQAAA=\",\"AAG/BwAAAdABAAABRAUAAAAAAAHIEAAA\"]Bg[\"AAAAAAAAAAABbwUAAA==\",\"AAABxwEAAA==\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAjoJAACVEAAA\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAjwJAACXEAAA\",\"\"]") \ No newline at end of file diff --git a/pr/451/docs/src-files.js b/pr/451/docs/src-files.js index e1e7f41c4..25a9d0216 100644 --- a/pr/451/docs/src-files.js +++ b/pr/451/docs/src-files.js @@ -1,2 +1,2 @@ -createSrcSidebar('[["bench",["",[],["lib.rs","stats.rs"]]],["bulk",["",[],["bulk.rs"]]],["certificate",["",[],["certificate.rs"]]],["data_transfer",["",[],["data-transfer.rs"]]],["noq",["",[["runtime",[],["mod.rs","smol.rs","tokio.rs"]]],["connection.rs","endpoint.rs","event_stream.rs","incoming.rs","lib.rs","mutex.rs","path.rs","recv_stream.rs","send_stream.rs","work_limiter.rs"]]],["noq_perf",["",[],["perf.rs"]]],["noq_proto",["",[["config",[],["mod.rs","qlog.rs","transport.rs"]],["congestion",[["bbr",[],["bw_estimation.rs","min_max.rs","mod.rs"]]],["cubic.rs","new_reno.rs"]],["connection",[["streams",[],["mod.rs","recv.rs","send.rs","state.rs"]]],["ack_frequency.rs","assembler.rs","cid_state.rs","datagrams.rs","mod.rs","mtud.rs","pacing.rs","packet_builder.rs","packet_crypto.rs","paths.rs","qlog.rs","send_buffer.rs","spaces.rs","state.rs","stats.rs","timer.rs","transmit_buf.rs"]],["crypto",[],["ring_like.rs","rustls.rs"]],["range_set",[],["array_range_set.rs","mod.rs"]]],["address_discovery.rs","bloom_token_log.rs","cid_generator.rs","cid_queue.rs","coding.rs","congestion.rs","constant_time.rs","crypto.rs","endpoint.rs","frame.rs","lib.rs","n0_nat_traversal.rs","packet.rs","shared.rs","token.rs","token_memory_cache.rs","transport_error.rs","transport_parameters.rs","varint.rs"]]],["noq_udp",["",[["cmsg",[],["mod.rs","unix.rs"]]],["lib.rs","unix.rs"]]],["perf",["",[],["client.rs","lib.rs","noprotection.rs","server.rs","stats.rs"]]],["set_up_connection",["",[],["set-up-connection.rs"]]]]'); -//{"start":19,"fragment_lengths":[39,29,43,47,208,33,885,73,81,55]} \ No newline at end of file +createSrcSidebar('[["bench",["",[],["lib.rs","stats.rs"]]],["bulk",["",[],["bulk.rs"]]],["certificate",["",[],["certificate.rs"]]],["data_transfer",["",[],["data-transfer.rs"]]],["noq",["",[["runtime",[],["mod.rs","smol.rs","tokio.rs"]]],["connection.rs","endpoint.rs","event_stream.rs","incoming.rs","lib.rs","mutex.rs","path.rs","recv_stream.rs","send_stream.rs","work_limiter.rs"]]],["noq_perf",["",[],["perf.rs"]]],["noq_proto",["",[["config",[],["mod.rs","qlog.rs","transport.rs"]],["congestion",[["bbr3",[],["max_filter.rs","mod.rs"]]],["cubic.rs","new_reno.rs"]],["connection",[["streams",[],["mod.rs","recv.rs","send.rs","state.rs"]]],["ack_frequency.rs","assembler.rs","cid_state.rs","datagrams.rs","mod.rs","mtud.rs","pacing.rs","packet_builder.rs","packet_crypto.rs","paths.rs","qlog.rs","send_buffer.rs","spaces.rs","state.rs","stats.rs","timer.rs","transmit_buf.rs"]],["crypto",[],["ring_like.rs","rustls.rs"]],["range_set",[],["array_range_set.rs","mod.rs"]]],["address_discovery.rs","bloom_token_log.rs","cid_generator.rs","cid_queue.rs","coding.rs","congestion.rs","constant_time.rs","crypto.rs","endpoint.rs","frame.rs","lib.rs","n0_nat_traversal.rs","packet.rs","shared.rs","token.rs","token_memory_cache.rs","transport_error.rs","transport_parameters.rs","varint.rs"]]],["noq_udp",["",[["cmsg",[],["mod.rs","unix.rs"]]],["lib.rs","unix.rs"]]],["perf",["",[],["client.rs","lib.rs","noprotection.rs","server.rs","stats.rs"]]],["set_up_connection",["",[],["set-up-connection.rs"]]]]'); +//{"start":19,"fragment_lengths":[39,29,43,47,208,33,870,73,81,55]} \ No newline at end of file diff --git a/pr/451/docs/src/bench/lib.rs.html b/pr/451/docs/src/bench/lib.rs.html index 56da6c782..0ff1245ef 100644 --- a/pr/451/docs/src/bench/lib.rs.html +++ b/pr/451/docs/src/bench/lib.rs.html @@ -111,7 +111,7 @@ 111 Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(), 112 ]; 113 -114 while let Some(n) = stream.read_chunks(&mut bufs[..]).await? { +114 while let Some(n) = stream.read_many_chunks(&mut bufs[..]).await? { 115 read += bufs.iter().take(n).map(|buf| buf.len()).sum::<usize>(); 116 } 117 } diff --git a/pr/451/docs/src/noq/connection.rs.html b/pr/451/docs/src/noq/connection.rs.html index 2a9cd944a..a834619f0 100644 --- a/pr/451/docs/src/noq/connection.rs.html +++ b/pr/451/docs/src/noq/connection.rs.html @@ -6,1875 +6,1885 @@ 6 net::{IpAddr, SocketAddr}, 7 num::NonZeroUsize, 8 pin::Pin, -9 sync::{Arc, Weak}, -10 task::{Context, Poll, Waker, ready}, -11}; -12 -13use bytes::Bytes; -14use pin_project_lite::pin_project; -15use rustc_hash::FxHashMap; -16use thiserror::Error; -17use tokio::sync::{Notify, futures::Notified, mpsc, oneshot, watch}; -18use tracing::{Instrument, Span, debug_span}; -19 -20use crate::{ -21 ConnectionEvent, Duration, Instant, Path, VarInt, -22 endpoint::ensure_ipv6, -23 mutex::{Mutex, MutexGuard}, -24 path::OpenPath, -25 recv_stream::RecvStream, -26 runtime::{AsyncTimer, Runtime, UdpSender}, -27 send_stream::SendStream, -28 udp_transmit, -29}; -30use proto::{ -31 ConnectionError, ConnectionHandle, ConnectionStats, Dir, EndpointEvent, FourTuple, PathError, -32 PathEvent, PathId, PathStats, PathStatus, Side, StreamEvent, StreamId, TransportError, -33 TransportErrorCode, congestion::Controller, n0_nat_traversal, -34}; -35 -36/// In-progress connection attempt future -37#[derive(Debug)] -38pub struct Connecting { -39 conn: Option<ConnectionRef>, -40 connected: oneshot::Receiver<bool>, -41 handshake_data_ready: Option<oneshot::Receiver<()>>, -42} -43 -44impl Connecting { -45 pub(crate) fn new( -46 handle: ConnectionHandle, -47 conn: proto::Connection, -48 endpoint_events: mpsc::UnboundedSender<(ConnectionHandle, EndpointEvent)>, -49 conn_events: mpsc::UnboundedReceiver<ConnectionEvent>, -50 sender: Pin<Box<dyn UdpSender>>, -51 runtime: Arc<dyn Runtime>, -52 ) -> Self { -53 let (on_handshake_data_send, on_handshake_data_recv) = oneshot::channel(); -54 let (on_connected_send, on_connected_recv) = oneshot::channel(); -55 -56 let conn = ConnectionRef(Arc::new(Arc::new(ConnectionInner { -57 state: Mutex::new(State::new( -58 conn, -59 handle, -60 endpoint_events, -61 conn_events, -62 on_handshake_data_send, -63 on_connected_send, -64 sender, -65 runtime.clone(), -66 )), -67 shared: Shared::default(), -68 }))); -69 -70 let driver = ConnectionDriver(conn.clone()); -71 runtime.spawn(Box::pin( -72 async { -73 if let Err(e) = driver.await { -74 tracing::error!("I/O error: {e}"); -75 } -76 } -77 .instrument(Span::current()), -78 )); -79 -80 Self { -81 conn: Some(conn), -82 connected: on_connected_recv, -83 handshake_data_ready: Some(on_handshake_data_recv), -84 } -85 } -86 -87 /// Convert into a 0-RTT or 0.5-RTT connection at the cost of weakened security -88 /// -89 /// Returns `Ok` immediately if the local endpoint is able to attempt sending 0/0.5-RTT data. -90 /// If so, the returned [`Connection`] can be used to send application data without waiting for -91 /// the rest of the handshake to complete, at the cost of weakened cryptographic security -92 /// guarantees. The returned [`ZeroRttAccepted`] future resolves when the handshake does -93 /// complete, at which point subsequently opened streams and written data will have full -94 /// cryptographic protection. -95 /// -96 /// ## Outgoing -97 /// -98 /// For outgoing connections, the initial attempt to convert to a [`Connection`] which sends -99 /// 0-RTT data will proceed if the [`crypto::ClientConfig`][crate::crypto::ClientConfig] -100 /// attempts to resume a previous TLS session. However, **the remote endpoint may not actually -101 /// _accept_ the 0-RTT data**--yet still accept the connection attempt in general. This -102 /// possibility is conveyed through the [`ZeroRttAccepted`] future--when the handshake -103 /// completes, it resolves to true if the 0-RTT data was accepted and false if it was rejected. -104 /// If it was rejected, the existence of streams opened and other application data sent prior -105 /// to the handshake completing will not be conveyed to the remote application, and local -106 /// operations on them will return `ZeroRttRejected` errors. -107 /// -108 /// A server may reject 0-RTT data at its discretion, but accepting 0-RTT data requires the -109 /// relevant resumption state to be stored in the server, which servers may limit or lose for -110 /// various reasons including not persisting resumption state across server restarts. -111 /// -112 /// If manually providing a [`crypto::ClientConfig`][crate::crypto::ClientConfig], check your -113 /// implementation's docs for 0-RTT pitfalls. +9 sync::{ +10 Arc, Weak, +11 atomic::{AtomicUsize, Ordering}, +12 }, +13 task::{Context, Poll, Waker, ready}, +14}; +15 +16use bytes::Bytes; +17use pin_project_lite::pin_project; +18use rustc_hash::FxHashMap; +19use thiserror::Error; +20use tokio::sync::{Notify, futures::Notified, mpsc, oneshot, watch}; +21use tracing::{Instrument, Span, debug_span}; +22 +23use crate::{ +24 ConnectionEvent, Duration, Instant, Path, VarInt, +25 endpoint::ensure_ipv6, +26 mutex::{Mutex, MutexGuard}, +27 path::{OpenPath, PathRef, PathRefOwner}, +28 recv_stream::RecvStream, +29 runtime::{AsyncTimer, Runtime, UdpSender}, +30 send_stream::SendStream, +31 udp_transmit, +32}; +33use proto::{ +34 ConnectionError, ConnectionHandle, ConnectionStats, Dir, EndpointEvent, FourTuple, PathError, +35 PathEvent, PathId, PathStats, PathStatus, Side, StreamEvent, StreamId, TransportError, +36 TransportErrorCode, congestion::Controller, n0_nat_traversal, +37}; +38 +39/// In-progress connection attempt future +40#[derive(Debug)] +41pub struct Connecting { +42 conn: Option<ConnectionRef>, +43 connected: oneshot::Receiver<bool>, +44 handshake_data_ready: Option<oneshot::Receiver<()>>, +45} +46 +47impl Connecting { +48 pub(crate) fn new( +49 handle: ConnectionHandle, +50 conn: proto::Connection, +51 endpoint_events: mpsc::UnboundedSender<(ConnectionHandle, EndpointEvent)>, +52 conn_events: mpsc::UnboundedReceiver<ConnectionEvent>, +53 sender: Pin<Box<dyn UdpSender>>, +54 runtime: Arc<dyn Runtime>, +55 ) -> Self { +56 let (on_handshake_data_send, on_handshake_data_recv) = oneshot::channel(); +57 let (on_connected_send, on_connected_recv) = oneshot::channel(); +58 +59 let conn = ConnectionRef(Arc::new(Arc::new(ConnectionInner { +60 state: Mutex::new(State::new( +61 conn, +62 handle, +63 endpoint_events, +64 conn_events, +65 on_handshake_data_send, +66 on_connected_send, +67 sender, +68 runtime.clone(), +69 )), +70 shared: Shared::default(), +71 }))); +72 +73 let driver = ConnectionDriver(conn.clone()); +74 runtime.spawn(Box::pin( +75 async { +76 if let Err(e) = driver.await { +77 tracing::error!("I/O error: {e}"); +78 } +79 } +80 .instrument(Span::current()), +81 )); +82 +83 Self { +84 conn: Some(conn), +85 connected: on_connected_recv, +86 handshake_data_ready: Some(on_handshake_data_recv), +87 } +88 } +89 +90 /// Convert into a 0-RTT or 0.5-RTT connection at the cost of weakened security +91 /// +92 /// Returns `Ok` immediately if the local endpoint is able to attempt sending 0/0.5-RTT data. +93 /// If so, the returned [`Connection`] can be used to send application data without waiting for +94 /// the rest of the handshake to complete, at the cost of weakened cryptographic security +95 /// guarantees. The returned [`ZeroRttAccepted`] future resolves when the handshake does +96 /// complete, at which point subsequently opened streams and written data will have full +97 /// cryptographic protection. +98 /// +99 /// ## Outgoing +100 /// +101 /// For outgoing connections, the initial attempt to convert to a [`Connection`] which sends +102 /// 0-RTT data will proceed if the [`crypto::ClientConfig`][crate::crypto::ClientConfig] +103 /// attempts to resume a previous TLS session. However, **the remote endpoint may not actually +104 /// _accept_ the 0-RTT data**--yet still accept the connection attempt in general. This +105 /// possibility is conveyed through the [`ZeroRttAccepted`] future--when the handshake +106 /// completes, it resolves to true if the 0-RTT data was accepted and false if it was rejected. +107 /// If it was rejected, the existence of streams opened and other application data sent prior +108 /// to the handshake completing will not be conveyed to the remote application, and local +109 /// operations on them will return `ZeroRttRejected` errors. +110 /// +111 /// A server may reject 0-RTT data at its discretion, but accepting 0-RTT data requires the +112 /// relevant resumption state to be stored in the server, which servers may limit or lose for +113 /// various reasons including not persisting resumption state across server restarts. 114 /// -115 /// ## Incoming -116 /// -117 /// For incoming connections, conversion to 0.5-RTT will always fully succeed. `into_0rtt` will -118 /// always return `Ok` and the [`ZeroRttAccepted`] will always resolve to true. +115 /// If manually providing a [`crypto::ClientConfig`][crate::crypto::ClientConfig], check your +116 /// implementation's docs for 0-RTT pitfalls. +117 /// +118 /// ## Incoming 119 /// -120 /// If manually providing a [`crypto::ServerConfig`][crate::crypto::ServerConfig], check your -121 /// implementation's docs for 0-RTT pitfalls. +120 /// For incoming connections, conversion to 0.5-RTT will always fully succeed. `into_0rtt` will +121 /// always return `Ok` and the [`ZeroRttAccepted`] will always resolve to true. 122 /// -123 /// ## Security -124 /// -125 /// On outgoing connections, this enables transmission of 0-RTT data, which is vulnerable to -126 /// replay attacks, and should therefore never invoke non-idempotent operations. +123 /// If manually providing a [`crypto::ServerConfig`][crate::crypto::ServerConfig], check your +124 /// implementation's docs for 0-RTT pitfalls. +125 /// +126 /// ## Security 127 /// -128 /// On incoming connections, this enables transmission of 0.5-RTT data, which may be sent -129 /// before TLS client authentication has occurred, and should therefore not be used to send -130 /// data for which client authentication is being used. -131 pub fn into_0rtt(mut self) -> Result<(Connection, ZeroRttAccepted), Self> { -132 // This lock borrows `self` and would normally be dropped at the end of this scope, so we'll -133 // have to release it explicitly before returning `self` by value. -134 let conn = (self.conn.as_mut().unwrap()).lock_without_waking("into_0rtt"); -135 -136 let is_ok = conn.inner.has_0rtt() || conn.inner.side().is_server(); -137 drop(conn); +128 /// On outgoing connections, this enables transmission of 0-RTT data, which is vulnerable to +129 /// replay attacks, and should therefore never invoke non-idempotent operations. +130 /// +131 /// On incoming connections, this enables transmission of 0.5-RTT data, which may be sent +132 /// before TLS client authentication has occurred, and should therefore not be used to send +133 /// data for which client authentication is being used. +134 pub fn into_0rtt(mut self) -> Result<(Connection, ZeroRttAccepted), Self> { +135 // This lock borrows `self` and would normally be dropped at the end of this scope, so we'll +136 // have to release it explicitly before returning `self` by value. +137 let conn = (self.conn.as_mut().unwrap()).lock_without_waking("into_0rtt"); 138 -139 if is_ok { -140 let conn = self.conn.take().unwrap(); -141 Ok((Connection(conn), ZeroRttAccepted(self.connected))) -142 } else { -143 Err(self) -144 } -145 } -146 -147 /// Parameters negotiated during the handshake -148 /// -149 /// The dynamic type returned is determined by the configured -150 /// [`Session`](proto::crypto::Session). For the default `rustls` session, the return value can -151 /// be [`downcast`](Box::downcast) to a -152 /// [`crypto::rustls::HandshakeData`](crate::crypto::rustls::HandshakeData). -153 pub async fn handshake_data(&mut self) -> Result<Box<dyn Any>, ConnectionError> { -154 // Taking &mut self allows us to use a single oneshot channel rather than dealing with -155 // potentially many tasks waiting on the same event. It's a bit of a hack, but keeps things -156 // simple. -157 if let Some(x) = self.handshake_data_ready.take() { -158 let _ = x.await; -159 } -160 let conn = self.conn.as_ref().unwrap(); -161 let inner = conn.lock_without_waking("handshake"); -162 inner -163 .inner -164 .crypto_session() -165 .handshake_data() -166 .ok_or_else(|| { -167 inner -168 .error -169 .clone() -170 .expect("spurious handshake data ready notification") -171 }) -172 } -173 -174 /// The local IP address which was used when the peer established -175 /// the connection -176 /// -177 /// This can be different from the address the endpoint is bound to, in case -178 /// the endpoint is bound to a wildcard address like `0.0.0.0` or `::`. +139 let is_ok = conn.inner.has_0rtt() || conn.inner.side().is_server(); +140 drop(conn); +141 +142 if is_ok { +143 let conn = self.conn.take().unwrap(); +144 Ok((Connection(conn), ZeroRttAccepted(self.connected))) +145 } else { +146 Err(self) +147 } +148 } +149 +150 /// Parameters negotiated during the handshake +151 /// +152 /// The dynamic type returned is determined by the configured +153 /// [`Session`](proto::crypto::Session). For the default `rustls` session, the return value can +154 /// be [`downcast`](Box::downcast) to a +155 /// [`crypto::rustls::HandshakeData`](crate::crypto::rustls::HandshakeData). +156 pub async fn handshake_data(&mut self) -> Result<Box<dyn Any>, ConnectionError> { +157 // Taking &mut self allows us to use a single oneshot channel rather than dealing with +158 // potentially many tasks waiting on the same event. It's a bit of a hack, but keeps things +159 // simple. +160 if let Some(x) = self.handshake_data_ready.take() { +161 let _ = x.await; +162 } +163 let conn = self.conn.as_ref().unwrap(); +164 let inner = conn.lock_without_waking("handshake"); +165 inner +166 .inner +167 .crypto_session() +168 .handshake_data() +169 .ok_or_else(|| { +170 inner +171 .error +172 .clone() +173 .expect("spurious handshake data ready notification") +174 }) +175 } +176 +177 /// The local IP address which was used when the peer established +178 /// the connection 179 /// -180 /// This will return `None` for clients, or when the platform does not expose this -181 /// information. See [`noq_udp::RecvMeta::dst_ip`](udp::RecvMeta::dst_ip) for a list of -182 /// supported platforms when using [`noq_udp`](udp) for I/O, which is the default. -183 /// -184 /// Will panic if called after `poll` has returned `Ready`. -185 pub fn local_ip(&self) -> Option<IpAddr> { -186 let conn = self.conn.as_ref().unwrap(); -187 let inner = conn.lock_without_waking("local_ip"); -188 -189 inner -190 .inner -191 .network_path(PathId::ZERO) -192 .expect("path exists when connecting") -193 .local_ip() -194 } -195 -196 /// The peer's UDP addresses -197 /// -198 /// Will panic if called after `poll` has returned `Ready`. -199 pub fn remote_address(&self) -> SocketAddr { -200 let conn_ref: &ConnectionRef = self.conn.as_ref().expect("used after yielding Ready"); -201 // TODO: another unwrap -202 conn_ref -203 .lock_without_waking("remote_address") -204 .inner -205 .network_path(PathId::ZERO) -206 .expect("path exists when connecting") -207 .remote() -208 } -209} -210 -211impl Future for Connecting { -212 type Output = Result<Connection, ConnectionError>; -213 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { -214 Pin::new(&mut self.connected).poll(cx).map(|_| { -215 let conn = self.conn.take().unwrap(); -216 let inner = conn.lock_without_waking("connecting"); -217 if inner.connected { -218 drop(inner); -219 Ok(Connection(conn)) -220 } else { -221 Err(inner -222 .error -223 .clone() -224 .expect("connected signaled without connection success or error")) -225 } -226 }) -227 } -228} -229 -230/// Future that completes when a connection is fully established -231/// -232/// For clients, the resulting value indicates if 0-RTT was accepted. For servers, the resulting -233/// value is meaningless. -234pub struct ZeroRttAccepted(oneshot::Receiver<bool>); -235 -236impl Future for ZeroRttAccepted { -237 type Output = bool; -238 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { -239 Pin::new(&mut self.0).poll(cx).map(|x| x.unwrap_or(false)) -240 } -241} -242 -243/// A future that drives protocol logic for a connection -244/// -245/// This future handles the protocol logic for a single connection, routing events from the -246/// `Connection` API object to the `Endpoint` task and the related stream-related interfaces. -247/// It also keeps track of outstanding timeouts for the `Connection`. -248/// -249/// If the connection encounters an error condition, this future will yield an error. It will -250/// terminate (yielding `Ok(())`) if the connection was closed without error. Unlike other -251/// connection-related futures, this waits for the draining period to complete to ensure that -252/// packets still in flight from the peer are handled gracefully. -253#[must_use = "connection drivers must be spawned for their connections to function"] -254#[derive(Debug)] -255struct ConnectionDriver(ConnectionRef); -256 -257impl Future for ConnectionDriver { -258 type Output = Result<(), io::Error>; -259 -260 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { -261 let conn = &mut *self.0.lock_without_waking("poll"); -262 -263 let span = debug_span!("drive", id = conn.handle.0); -264 let _guard = span.enter(); -265 -266 if let Err(e) = conn.process_conn_events(&self.0.shared, cx) { -267 conn.terminate(e, &self.0.shared); -268 return Poll::Ready(Ok(())); -269 } -270 let mut keep_going = conn.drive_transmit(cx)?; -271 // If a timer expires, there might be more to transmit. When we transmit something, we -272 // might need to reset a timer. Hence, we must loop until neither happens. -273 keep_going |= conn.drive_timer(cx); -274 conn.forward_endpoint_events(); -275 conn.forward_app_events(&self.0.shared); -276 -277 if !conn.inner.is_drained() { -278 if keep_going { -279 // If the connection hasn't processed all tasks, schedule it again -280 cx.waker().wake_by_ref(); -281 } else { -282 conn.driver = Some(cx.waker().clone()); -283 } -284 return Poll::Pending; -285 } -286 if conn.error.is_none() { -287 unreachable!("drained connections always have an error"); -288 } -289 Poll::Ready(Ok(())) -290 } -291} -292 -293/// A QUIC connection. -294/// -295/// If all references to a connection (including every clone of the `Connection` handle, streams of -296/// incoming streams, and the various stream types) have been dropped, then the connection will be -297/// automatically closed with an `error_code` of 0 and an empty `reason`. You can also close the -298/// connection explicitly by calling [`Connection::close()`]. -299/// -300/// Closing the connection immediately abandons efforts to deliver data to the peer. Upon -301/// receiving CONNECTION_CLOSE the peer *may* drop any stream data not yet delivered to the -302/// application. [`Connection::close()`] describes in more detail how to gracefully close a -303/// connection without losing application data. -304/// -305/// May be cloned to obtain another handle to the same connection. +180 /// This can be different from the address the endpoint is bound to, in case +181 /// the endpoint is bound to a wildcard address like `0.0.0.0` or `::`. +182 /// +183 /// This will return `None` for clients, or when the platform does not expose this +184 /// information. See [`noq_udp::RecvMeta::dst_ip`](udp::RecvMeta::dst_ip) for a list of +185 /// supported platforms when using [`noq_udp`](udp) for I/O, which is the default. +186 /// +187 /// Will panic if called after `poll` has returned `Ready`. +188 pub fn local_ip(&self) -> Option<IpAddr> { +189 let conn = self.conn.as_ref().expect("used after yielding Ready"); +190 let inner = conn.lock_without_waking("local_ip"); +191 +192 inner +193 .inner +194 .network_path(PathId::ZERO) +195 .expect("PathId::ZERO is the only path during the handshake") +196 .local_ip() +197 } +198 +199 /// The peer's UDP addresses +200 /// +201 /// Will panic if called after `poll` has returned `Ready`. +202 pub fn remote_address(&self) -> SocketAddr { +203 let conn_ref: &ConnectionRef = self.conn.as_ref().expect("used after yielding Ready"); +204 conn_ref +205 .lock_without_waking("remote_address") +206 .inner +207 .network_path(PathId::ZERO) +208 .expect("PathId::ZERO is the only path during the handshake") +209 .remote() +210 } +211} +212 +213impl Future for Connecting { +214 type Output = Result<Connection, ConnectionError>; +215 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { +216 Pin::new(&mut self.connected).poll(cx).map(|_| { +217 let conn = self.conn.take().unwrap(); +218 let inner = conn.lock_without_waking("connecting"); +219 if inner.connected { +220 drop(inner); +221 Ok(Connection(conn)) +222 } else { +223 Err(inner +224 .error +225 .clone() +226 .expect("connected signaled without connection success or error")) +227 } +228 }) +229 } +230} +231 +232/// Future that completes when a connection is fully established +233/// +234/// For clients, the resulting value indicates if 0-RTT was accepted. For servers, the resulting +235/// value is meaningless. +236pub struct ZeroRttAccepted(oneshot::Receiver<bool>); +237 +238impl Future for ZeroRttAccepted { +239 type Output = bool; +240 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { +241 Pin::new(&mut self.0).poll(cx).map(|x| x.unwrap_or(false)) +242 } +243} +244 +245/// A future that drives protocol logic for a connection +246/// +247/// This future handles the protocol logic for a single connection, routing events from the +248/// `Connection` API object to the `Endpoint` task and the related stream-related interfaces. +249/// It also keeps track of outstanding timeouts for the `Connection`. +250/// +251/// If the connection encounters an error condition, this future will yield an error. It will +252/// terminate (yielding `Ok(())`) if the connection was closed without error. Unlike other +253/// connection-related futures, this waits for the draining period to complete to ensure that +254/// packets still in flight from the peer are handled gracefully. +255#[must_use = "connection drivers must be spawned for their connections to function"] +256#[derive(Debug)] +257struct ConnectionDriver(ConnectionRef); +258 +259impl Future for ConnectionDriver { +260 type Output = Result<(), io::Error>; +261 +262 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { +263 let conn = &mut *self.0.lock_without_waking("poll"); +264 +265 let span = debug_span!("drive", id = conn.handle.0); +266 let _guard = span.enter(); +267 +268 if let Err(e) = conn.process_conn_events(&self.0.shared, cx) { +269 conn.terminate(e, &self.0.shared); +270 return Poll::Ready(Ok(())); +271 } +272 let mut keep_going = conn.drive_transmit(cx)?; +273 // If a timer expires, there might be more to transmit. When we transmit something, we +274 // might need to reset a timer. Hence, we must loop until neither happens. +275 keep_going |= conn.drive_timer(cx); +276 conn.forward_endpoint_events(); +277 conn.forward_app_events(&self.0.shared); +278 +279 if !conn.inner.is_drained() { +280 if keep_going { +281 // If the connection hasn't processed all tasks, schedule it again +282 cx.waker().wake_by_ref(); +283 } else { +284 conn.driver = Some(cx.waker().clone()); +285 } +286 return Poll::Pending; +287 } +288 if conn.error.is_none() { +289 unreachable!("drained connections always have an error"); +290 } +291 Poll::Ready(Ok(())) +292 } +293} +294 +295/// A QUIC connection. +296/// +297/// If all references to a connection (including every clone of the `Connection` handle, streams of +298/// incoming streams, and the various stream types) have been dropped, then the connection will be +299/// automatically closed with an `error_code` of 0 and an empty `reason`. You can also close the +300/// connection explicitly by calling [`Connection::close()`]. +301/// +302/// Closing the connection immediately abandons efforts to deliver data to the peer. Upon +303/// receiving CONNECTION_CLOSE the peer *may* drop any stream data not yet delivered to the +304/// application. [`Connection::close()`] describes in more detail how to gracefully close a +305/// connection without losing application data. 306/// -307/// [`Connection::close()`]: Connection::close -308#[derive(Debug, Clone)] -309pub struct Connection(ConnectionRef); -310 -311impl Connection { -312 /// Returns a weak reference to the inner connection struct. -313 pub fn weak_handle(&self) -> WeakConnectionHandle { -314 self.0.weak_handle() -315 } -316 -317 /// Initiate a new outgoing unidirectional stream. -318 /// -319 /// Streams are cheap and instantaneous to open unless blocked by flow control. As a -320 /// consequence, the peer won't be notified that a stream has been opened until the stream is -321 /// actually used. -322 pub fn open_uni(&self) -> OpenUni<'_> { -323 OpenUni { -324 conn: &self.0, -325 notify: self.0.shared.stream_budget_available[Dir::Uni as usize].notified(), -326 } -327 } -328 -329 /// Initiate a new outgoing bidirectional stream. -330 /// -331 /// Streams are cheap and instantaneous to open unless blocked by flow control. As a -332 /// consequence, the peer won't be notified that a stream has been opened until the stream is -333 /// actually used. Calling [`open_bi()`] then waiting on the [`RecvStream`] without writing -334 /// anything to [`SendStream`] will never succeed. -335 /// -336 /// [`open_bi()`]: crate::Connection::open_bi -337 /// [`SendStream`]: crate::SendStream -338 /// [`RecvStream`]: crate::RecvStream -339 pub fn open_bi(&self) -> OpenBi<'_> { -340 OpenBi { -341 conn: &self.0, -342 notify: self.0.shared.stream_budget_available[Dir::Bi as usize].notified(), -343 } -344 } -345 -346 /// Accept the next incoming uni-directional stream -347 pub fn accept_uni(&self) -> AcceptUni<'_> { -348 AcceptUni { -349 conn: &self.0, -350 notify: self.0.shared.stream_incoming[Dir::Uni as usize].notified(), -351 } -352 } -353 -354 /// Accept the next incoming bidirectional stream -355 /// -356 /// **Important Note**: The `Connection` that calls [`open_bi()`] must write to its [`SendStream`] -357 /// before the other `Connection` is able to `accept_bi()`. Calling [`open_bi()`] then -358 /// waiting on the [`RecvStream`] without writing anything to [`SendStream`] will never succeed. -359 /// -360 /// [`accept_bi()`]: crate::Connection::accept_bi -361 /// [`open_bi()`]: crate::Connection::open_bi -362 /// [`SendStream`]: crate::SendStream -363 /// [`RecvStream`]: crate::RecvStream -364 pub fn accept_bi(&self) -> AcceptBi<'_> { -365 AcceptBi { -366 conn: &self.0, -367 notify: self.0.shared.stream_incoming[Dir::Bi as usize].notified(), -368 } -369 } -370 -371 /// Receive an application datagram -372 pub fn read_datagram(&self) -> ReadDatagram<'_> { -373 ReadDatagram { -374 conn: &self.0, -375 notify: self.0.shared.datagram_received.notified(), -376 } -377 } -378 -379 /// Opens a new path if no path exists yet for the remote address. -380 /// -381 /// Otherwise behaves exactly as [`open_path`]. +307/// May be cloned to obtain another handle to the same connection. +308/// +309/// [`Connection::close()`]: Connection::close +310#[derive(Debug, Clone)] +311pub struct Connection(ConnectionRef); +312 +313impl Connection { +314 /// Returns a weak reference to the inner connection struct. +315 pub fn weak_handle(&self) -> WeakConnectionHandle { +316 self.0.weak_handle() +317 } +318 +319 /// Initiate a new outgoing unidirectional stream. +320 /// +321 /// Streams are cheap and instantaneous to open unless blocked by flow control. As a +322 /// consequence, the peer won't be notified that a stream has been opened until the stream is +323 /// actually used. +324 pub fn open_uni(&self) -> OpenUni<'_> { +325 OpenUni { +326 conn: &self.0, +327 notify: self.0.shared.stream_budget_available[Dir::Uni as usize].notified(), +328 } +329 } +330 +331 /// Initiate a new outgoing bidirectional stream. +332 /// +333 /// Streams are cheap and instantaneous to open unless blocked by flow control. As a +334 /// consequence, the peer won't be notified that a stream has been opened until the stream is +335 /// actually used. Calling [`open_bi()`] then waiting on the [`RecvStream`] without writing +336 /// anything to [`SendStream`] will never succeed. +337 /// +338 /// [`open_bi()`]: crate::Connection::open_bi +339 /// [`SendStream`]: crate::SendStream +340 /// [`RecvStream`]: crate::RecvStream +341 pub fn open_bi(&self) -> OpenBi<'_> { +342 OpenBi { +343 conn: &self.0, +344 notify: self.0.shared.stream_budget_available[Dir::Bi as usize].notified(), +345 } +346 } +347 +348 /// Accept the next incoming uni-directional stream +349 pub fn accept_uni(&self) -> AcceptUni<'_> { +350 AcceptUni { +351 conn: &self.0, +352 notify: self.0.shared.stream_incoming[Dir::Uni as usize].notified(), +353 } +354 } +355 +356 /// Accept the next incoming bidirectional stream +357 /// +358 /// **Important Note**: The `Connection` that calls [`open_bi()`] must write to its [`SendStream`] +359 /// before the other `Connection` is able to `accept_bi()`. Calling [`open_bi()`] then +360 /// waiting on the [`RecvStream`] without writing anything to [`SendStream`] will never succeed. +361 /// +362 /// [`accept_bi()`]: crate::Connection::accept_bi +363 /// [`open_bi()`]: crate::Connection::open_bi +364 /// [`SendStream`]: crate::SendStream +365 /// [`RecvStream`]: crate::RecvStream +366 pub fn accept_bi(&self) -> AcceptBi<'_> { +367 AcceptBi { +368 conn: &self.0, +369 notify: self.0.shared.stream_incoming[Dir::Bi as usize].notified(), +370 } +371 } +372 +373 /// Receive an application datagram +374 pub fn read_datagram(&self) -> ReadDatagram<'_> { +375 ReadDatagram { +376 conn: &self.0, +377 notify: self.0.shared.datagram_received.notified(), +378 } +379 } +380 +381 /// Opens a new path if no path exists yet for the remote address. 382 /// -383 /// [`open_path`]: Self::open_path -384 pub fn open_path_ensure(&self, addr: SocketAddr, initial_status: PathStatus) -> OpenPath { -385 let mut state = self.0.lock_and_wake("open_path"); -386 -387 // If endpoint::State::ipv6 is true we want to keep all our IP addresses as IPv6. -388 // If not, we do not support IPv6. We can not access endpoint::State from here -389 // however, but either all our paths use an IPv6 address, or all our paths use an -390 // IPv4 address. So we can use that information. -391 let ipv6 = state -392 .inner -393 .paths() -394 .iter() -395 .filter_map(|id| { -396 state -397 .inner -398 .network_path(*id) -399 .map(|addrs| addrs.remote().is_ipv6()) -400 .ok() -401 }) -402 .next() -403 .unwrap_or_default(); -404 if addr.is_ipv6() && !ipv6 { -405 return OpenPath::rejected(PathError::InvalidRemoteAddress(addr)); -406 } -407 let addr = if ipv6 { -408 SocketAddr::V6(ensure_ipv6(addr)) -409 } else { -410 addr -411 }; -412 -413 let now = state.runtime.now(); -414 // TODO(matheus23): For now this means it's impossible to make use of short-circuiting path validation currently. -415 // However, changing that would mean changing the API. -416 let addrs = FourTuple::from_remote(addr); -417 let open_res = state.inner.open_path_ensure(addrs, initial_status, now); -418 match open_res { -419 Ok((path_id, existed)) if existed => { -420 let recv = state.open_path.get(&path_id).map(|tx| tx.subscribe()); -421 drop(state); -422 match recv { -423 Some(recv) => OpenPath::new(path_id, recv, self.0.clone()), -424 None => OpenPath::ready(path_id, self.0.clone()), -425 } -426 } -427 Ok((path_id, _)) => { -428 let (tx, rx) = watch::channel(Ok(())); -429 state.open_path.insert(path_id, tx); -430 drop(state); -431 OpenPath::new(path_id, rx, self.0.clone()) -432 } -433 Err(err) => OpenPath::rejected(err), -434 } -435 } -436 -437 /// Opens an additional path if the multipath extension is negotiated. -438 /// -439 /// The returned future completes once the path is either fully opened and ready to -440 /// carry application data, or if there was an error. -441 /// -442 /// Dropping the returned future does not cancel the opening of the path, the -443 /// [`PathEvent::Opened`] event will still be emitted from [`Self::path_events`] if the -444 /// path opens. The [`PathId`] for the events can be extracted from -445 /// [`OpenPath::path_id`]. -446 /// -447 /// Failure to open a path can either occur immediately, before polling the returned -448 /// future, or at a later time. If the failure is immediate [`OpenPath::path_id`] will -449 /// return `None` and the future will be ready immediately. If the failure happens -450 /// later, a [`PathEvent`] will be emitted. -451 pub fn open_path(&self, addr: SocketAddr, initial_status: PathStatus) -> OpenPath { -452 let mut state = self.0.lock_and_wake("open_path"); -453 -454 // If endpoint::State::ipv6 is true we want to keep all our IP addresses as IPv6. -455 // If not, we do not support IPv6. We can not access endpoint::State from here -456 // however, but either all our paths use an IPv6 address, or all our paths use an -457 // IPv4 address. So we can use that information. -458 let ipv6 = state -459 .inner -460 .paths() -461 .iter() -462 .filter_map(|id| { -463 state -464 .inner -465 .network_path(*id) -466 .map(|addrs| addrs.remote().is_ipv6()) -467 .ok() -468 }) -469 .next() -470 .unwrap_or_default(); -471 if addr.is_ipv6() && !ipv6 { -472 return OpenPath::rejected(PathError::InvalidRemoteAddress(addr)); -473 } -474 let addr = if ipv6 { -475 SocketAddr::V6(ensure_ipv6(addr)) -476 } else { -477 addr -478 }; -479 -480 let (on_open_path_send, on_open_path_recv) = watch::channel(Ok(())); -481 let now = state.runtime.now(); -482 // TODO(matheus23): For now this means it's impossible to make use of short-circuiting path validation currently. -483 // However, changing that would mean changing the API. -484 let addrs = FourTuple::from_remote(addr); -485 let open_res = state.inner.open_path(addrs, initial_status, now); -486 match open_res { -487 Ok(path_id) => { -488 state.open_path.insert(path_id, on_open_path_send); -489 drop(state); -490 OpenPath::new(path_id, on_open_path_recv, self.0.clone()) -491 } -492 Err(err) => OpenPath::rejected(err), -493 } -494 } -495 -496 /// Returns the [`Path`] structure of an open path -497 pub fn path(&self, id: PathId) -> Option<Path> { -498 Path::new(&self.0, id) -499 } -500 -501 /// A stream of [`PathEvent`]s for all paths in this connection. -502 /// -503 /// The stream will yield a [`PathEvent`] whenever there is a change in the state of any path in this connection. -504 /// The events need to be processed immediately, since there isn't an unbounded buffer for them. -505 /// -506 /// If processing of events lags behind too much, you will get an error of type [`crate::Lagged`] indicating -507 /// how many events were lost. The stream continues after a lag, delivering the oldest retained message next. -508 pub fn path_events(&self) -> crate::PathEvents { -509 crate::PathEvents::new( -510 self.0 -511 .lock_without_waking("path_events") -512 .path_events -513 .subscribe(), -514 ) -515 } -516 -517 /// A stream of NAT traversal updates for this connection. -518 /// -519 /// The events need to be processed immediately, since there isn't an unbounded buffer for them. +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 /// If processing of events lags behind too much, you will get an error of type [`crate::Lagged`] indicating -522 /// how many events were lost. The stream continues after a lag, delivering the oldest retained message next. -523 pub fn nat_traversal_updates(&self) -> crate::NatTraversalUpdates { -524 crate::NatTraversalUpdates::new( -525 self.0 -526 .lock_without_waking("nat_traversal_updates") -527 .nat_traversal_updates -528 .subscribe(), -529 ) -530 } -531 -532 /// Wait for the connection to be closed for any reason -533 /// -534 /// Despite the return type's name, closed connections are often not an error condition at the -535 /// application layer. Cases that might be routine include [`ConnectionError::LocallyClosed`] -536 /// and [`ConnectionError::ApplicationClosed`]. -537 pub async fn closed(&self) -> ConnectionError { -538 { -539 let conn = self.0.lock_without_waking("closed"); -540 if let Some(error) = conn.error.as_ref() { -541 return error.clone(); -542 } -543 // Construct the future while the lock is held to ensure we can't miss a wakeup if -544 // the `Notify` is signaled immediately after we release the lock. `await` it after -545 // the lock guard is out of scope. -546 self.0.shared.closed.notified() -547 } -548 .await; -549 self.0 -550 .lock_without_waking("closed") -551 .error -552 .as_ref() -553 .expect("closed without an error") -554 .clone() -555 } -556 -557 /// Wait for the connection to be closed without keeping a strong reference to the connection -558 /// -559 /// Returns a future that resolves, once the connection is closed, to a tuple of -560 /// ([`ConnectionError`], [`ConnectionStats`]). -561 /// -562 /// Calling [`Self::closed`] keeps the connection alive until it is either closed locally via [`Connection::close`] -563 /// or closed by the remote peer. This function instead does not keep the connection itself alive, -564 /// so if all *other* clones of the connection are dropped, the connection will be closed implicitly even -565 /// if there are futures returned from this function still being awaited. -566 pub fn on_closed(&self) -> OnClosed { -567 let (tx, rx) = oneshot::channel(); -568 let mut state = self.0.lock_without_waking("on_closed"); -569 if let Some(error) = &state.error { -570 // Connection already closed, send immediately -571 let _ = tx.send((error.clone(), state.inner.stats())); -572 } else { -573 state.on_closed.push(tx); -574 } -575 drop(state); -576 OnClosed { -577 conn: self.weak_handle(), -578 rx, -579 } -580 } -581 -582 /// If the connection is closed, the reason why. -583 /// -584 /// Returns `None` if the connection is still open. -585 pub fn close_reason(&self) -> Option<ConnectionError> { -586 self.0.lock_without_waking("close_reason").error.clone() -587 } -588 -589 /// Close the connection immediately. +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). 590 /// -591 /// Pending operations will fail immediately with [`ConnectionError::LocallyClosed`]. No -592 /// more data is sent to the peer and the peer may drop buffered data upon receiving -593 /// the CONNECTION_CLOSE frame. -594 /// -595 /// `error_code` and `reason` are not interpreted, and are provided directly to the peer. -596 /// -597 /// `reason` will be truncated to fit in a single packet with overhead; to improve odds that it -598 /// is preserved in full, it should be kept under 1KiB. -599 /// -600 /// # Gracefully closing a connection +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. +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 /// Only the peer last receiving application data can be certain that all data is -603 /// delivered. The only reliable action it can then take is to close the connection, -604 /// potentially with a custom error code. The delivery of the final CONNECTION_CLOSE -605 /// frame is very likely if both endpoints stay online long enough, and -606 /// [`Endpoint::wait_idle()`] can be used to provide sufficient time. Otherwise, the -607 /// remote peer will time out the connection, provided that the idle timeout is not -608 /// disabled. -609 /// -610 /// The sending side can not guarantee all stream data is delivered to the remote -611 /// application. It only knows the data is delivered to the QUIC stack of the remote -612 /// endpoint. Once the local side sends a CONNECTION_CLOSE frame in response to calling -613 /// [`close()`] the remote endpoint may drop any data it received but is as yet -614 /// undelivered to the application, including data that was acknowledged as received to -615 /// the local endpoint. +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 /// [`ConnectionError::LocallyClosed`]: crate::ConnectionError::LocallyClosed -618 /// [`Endpoint::wait_idle()`]: crate::Endpoint::wait_idle -619 /// [`close()`]: Connection::close -620 pub fn close(&self, error_code: VarInt, reason: &[u8]) { -621 let conn = &mut *self.0.lock_without_waking("close"); // conn.close self-wakes -622 conn.close(error_code, Bytes::copy_from_slice(reason), &self.0.shared); -623 } -624 -625 /// Wait for the handshake to be confirmed. -626 /// -627 /// As a server, who must be authenticated by clients, -628 /// this happens when the handshake completes -629 /// upon receiving a TLS Finished message from the client. -630 /// In return, the server send a HANDSHAKE_DONE frame. -631 /// -632 /// As a client, this happens when receiving a HANDSHAKE_DONE frame. -633 /// At this point, the server has either accepted our authentication, -634 /// or, if client authentication is not required, accepted our lack of authentication. -635 pub async fn handshake_confirmed(&self) -> Result<(), ConnectionError> { -636 { -637 let conn = self.0.lock_without_waking("handshake_confirmed"); -638 if let Some(error) = conn.error.as_ref() { -639 return Err(error.clone()); -640 } -641 if conn.handshake_confirmed { -642 return Ok(()); -643 } -644 // Construct the future while the lock is held to ensure we can't miss a wakeup if -645 // the `Notify` is signaled immediately after we release the lock. `await` it after -646 // the lock guard is out of scope. -647 self.0.shared.handshake_confirmed.notified() -648 } -649 .await; -650 if let Some(error) = self -651 .0 -652 .lock_without_waking("handshake_confirmed") -653 .error -654 .as_ref() -655 { -656 Err(error.clone()) -657 } else { -658 Ok(()) -659 } -660 } -661 -662 /// Transmit `data` as an unreliable, unordered application datagram -663 /// -664 /// Application datagrams are a low-level primitive. They may be lost or delivered out of order, -665 /// and `data` must both fit inside a single QUIC packet and be smaller than the maximum -666 /// dictated by the peer. -667 /// -668 /// Previously queued datagrams which are still unsent may be discarded to make space for this -669 /// datagram, in order of oldest to newest. -670 pub fn send_datagram(&self, data: Bytes) -> Result<(), SendDatagramError> { -671 let conn = &mut *self.0.lock_and_wake("send_datagram"); -672 if let Some(ref x) = conn.error { -673 return Err(SendDatagramError::ConnectionLost(x.clone())); -674 } -675 use proto::SendDatagramError::*; -676 match conn.inner.datagrams().send(data, true) { -677 Ok(()) => Ok(()), -678 Err(e) => Err(match e { -679 Blocked(..) => unreachable!(), -680 UnsupportedByPeer => SendDatagramError::UnsupportedByPeer, -681 Disabled => SendDatagramError::Disabled, -682 TooLarge => SendDatagramError::TooLarge, -683 }), -684 } -685 } -686 -687 /// Transmit `data` as an unreliable, unordered application datagram -688 /// -689 /// Unlike [`send_datagram()`], this method will wait for buffer space during congestion -690 /// conditions, which effectively prioritizes old datagrams over new datagrams. -691 /// -692 /// See [`send_datagram()`] for details. -693 /// -694 /// [`send_datagram()`]: Connection::send_datagram -695 pub fn send_datagram_wait(&self, data: Bytes) -> SendDatagram<'_> { -696 SendDatagram { -697 conn: &self.0, -698 data: Some(data), -699 notify: self.0.shared.datagrams_unblocked.notified(), -700 } -701 } -702 -703 /// Compute the maximum size of datagrams that may be passed to [`send_datagram()`]. -704 /// -705 /// Returns `None` if datagrams are unsupported by the peer or disabled locally. -706 /// -707 /// This may change over the lifetime of a connection according to variation in the path MTU -708 /// estimate. The peer can also enforce an arbitrarily small fixed limit, but if the peer's -709 /// limit is large this is guaranteed to be a little over a kilobyte at minimum. -710 /// -711 /// Not necessarily the maximum size of received datagrams. -712 /// -713 /// [`send_datagram()`]: Connection::send_datagram -714 pub fn max_datagram_size(&self) -> Option<usize> { -715 self.0 -716 .lock_without_waking("max_datagram_size") -717 .inner -718 .datagrams() -719 .max_size() -720 } -721 -722 /// Bytes available in the outgoing datagram buffer -723 /// -724 /// When greater than zero, calling [`send_datagram()`](Self::send_datagram) with a datagram of -725 /// at most this size is guaranteed not to cause older datagrams to be dropped. -726 pub fn datagram_send_buffer_space(&self) -> usize { -727 self.0 -728 .lock_without_waking("datagram_send_buffer_space") -729 .inner -730 .datagrams() -731 .send_buffer_space() -732 } -733 -734 /// The side of the connection (client or server) -735 pub fn side(&self) -> Side { -736 self.0.lock_without_waking("side").inner.side() -737 } -738 -739 /// The peer's UDP address -740 /// -741 /// If [`ServerConfig::migration`] is `true`, clients may change addresses at will, -742 /// e.g. when switching to a cellular internet connection. -743 /// -744 /// If [`multipath`] is enabled this will return the address of *any* -745 /// path, and may not be consistent. Prefer [`Path::remote_address`] instead. -746 /// -747 /// [`ServerConfig::migration`]: crate::ServerConfig::migration -748 /// [`multipath`]: crate::TransportConfig::max_concurrent_multipath_paths -749 pub fn remote_address(&self) -> SocketAddr { -750 // TODO: an unwrap again -751 let state = self.0.lock_without_waking("remote_address"); -752 state -753 .inner -754 .paths() -755 .iter() -756 .filter_map(|id| state.inner.network_path(*id).ok()) -757 .next() -758 .unwrap() -759 .remote() -760 } -761 -762 /// The local IP address which was used when the peer established -763 /// the connection -764 /// -765 /// This can be different from the address the endpoint is bound to, in case -766 /// the endpoint is bound to a wildcard address like `0.0.0.0` or `::`. -767 /// -768 /// This will return `None` for clients, or when the platform does not expose this -769 /// information. See [`noq_udp::RecvMeta::dst_ip`](udp::RecvMeta::dst_ip) for a list of -770 /// supported platforms when using [`noq_udp`](udp) for I/O, which is the default. -771 pub fn local_ip(&self) -> Option<IpAddr> { -772 // TODO: an unwrap again -773 let state = self.0.lock_without_waking("remote_address"); -774 state -775 .inner -776 .paths() -777 .iter() -778 .filter_map(|id| state.inner.network_path(*id).ok()) -779 .next() -780 .unwrap() -781 .local_ip() -782 } -783 -784 /// Current best estimate of this connection's latency (round-trip-time) -785 pub fn rtt(&self, path_id: PathId) -> Option<Duration> { -786 self.0.lock_without_waking("rtt").inner.rtt(path_id) -787 } -788 -789 /// Returns connection statistics -790 pub fn stats(&self) -> ConnectionStats { -791 self.0.lock_without_waking("stats").inner.stats() -792 } -793 -794 /// Returns path statistics -795 pub fn path_stats(&self, path_id: PathId) -> Option<PathStats> { -796 self.0.lock_without_waking("path_stats").path_stats(path_id) -797 } -798 -799 /// Current state of the congestion control algorithm, for debugging purposes -800 pub fn congestion_state(&self, path_id: PathId) -> Option<Box<dyn Controller>> { -801 self.0 -802 .lock_without_waking("congestion_state") -803 .inner -804 .congestion_state(path_id) -805 .map(|c| c.clone_box()) -806 } -807 -808 /// Parameters negotiated during the handshake -809 /// -810 /// Guaranteed to return `Some` on fully established connections or after -811 /// [`Connecting::handshake_data()`] succeeds. See that method's documentations for details on -812 /// the returned value. -813 /// -814 /// [`Connection::handshake_data()`]: crate::Connecting::handshake_data -815 pub fn handshake_data(&self) -> Option<Box<dyn Any>> { -816 self.0 -817 .lock_without_waking("handshake_data") -818 .inner -819 .crypto_session() -820 .handshake_data() -821 } -822 -823 /// Cryptographic identity of the peer -824 /// -825 /// The dynamic type returned is determined by the configured -826 /// [`Session`](proto::crypto::Session). For the default `rustls` session, the return value can -827 /// be [`downcast`](Box::downcast) to a <code>Vec<[rustls::pki_types::CertificateDer]></code> -828 pub fn peer_identity(&self) -> Option<Box<dyn Any>> { -829 self.0 -830 .lock_without_waking("peer_identity") -831 .inner -832 .crypto_session() -833 .peer_identity() -834 } -835 -836 /// A stable identifier for this connection -837 /// -838 /// Peer addresses and connection IDs can change, but this value will remain -839 /// fixed for the lifetime of the connection. -840 pub fn stable_id(&self) -> usize { -841 self.0.stable_id() -842 } -843 -844 /// Update traffic keys spontaneously -845 /// -846 /// This primarily exists for testing purposes. -847 pub fn force_key_update(&self) { -848 self.0 -849 .lock_and_wake("force_key_update") -850 .inner -851 .force_key_update() -852 } -853 -854 /// Derive keying material from this connection's TLS session secrets. -855 /// -856 /// When both peers call this method with the same `label` and `context` -857 /// arguments and `output` buffers of equal length, they will get the -858 /// same sequence of bytes in `output`. These bytes are cryptographically -859 /// strong and pseudorandom, and are suitable for use as keying material. -860 /// -861 /// See [RFC5705](https://tools.ietf.org/html/rfc5705) for more information. -862 pub fn export_keying_material( -863 &self, -864 output: &mut [u8], -865 label: &[u8], -866 context: &[u8], -867 ) -> Result<(), proto::crypto::ExportKeyingMaterialError> { -868 self.0 -869 .lock_without_waking("export_keying_material") -870 .inner -871 .crypto_session() -872 .export_keying_material(output, label, context) -873 } -874 -875 /// Modify the number of remotely initiated unidirectional streams that may be concurrently open -876 /// -877 /// No streams may be opened by the peer unless fewer than `count` are already open. Large -878 /// `count`s increase both minimum and worst-case memory consumption. -879 pub fn set_max_concurrent_uni_streams(&self, count: VarInt) { -880 let mut conn = self.0.lock_and_wake("set_max_concurrent_uni_streams"); -881 conn.inner.set_max_concurrent_streams(Dir::Uni, count); -882 } -883 -884 /// See [`proto::TransportConfig::send_window()`] -885 pub fn set_send_window(&self, send_window: u64) { -886 let mut conn = self.0.lock_and_wake("set_send_window"); -887 conn.inner.set_send_window(send_window); -888 } -889 -890 /// See [`proto::TransportConfig::receive_window()`] -891 pub fn set_receive_window(&self, receive_window: VarInt) { -892 let mut conn = self.0.lock_and_wake("set_receive_window"); -893 conn.inner.set_receive_window(receive_window); -894 } -895 -896 /// Modify the number of remotely initiated bidirectional streams that may be concurrently open -897 /// -898 /// No streams may be opened by the peer unless fewer than `count` are already open. Large -899 /// `count`s increase both minimum and worst-case memory consumption. -900 pub fn set_max_concurrent_bi_streams(&self, count: VarInt) { -901 let mut conn = self.0.lock_and_wake("set_max_concurrent_bi_streams"); -902 conn.inner.set_max_concurrent_streams(Dir::Bi, count); -903 } -904 -905 /// Track changes on our external address as reported by the peer. -906 pub fn observed_external_addr(&self) -> crate::ObservedExternalAddr { -907 let conn = self.0.lock_without_waking("external_addr"); -908 crate::ObservedExternalAddr::new(conn.observed_external_addr.subscribe()) -909 } -910 -911 /// Is multipath enabled? -912 // TODO(flub): not a useful API, once we do real things with multipath we can remove -913 // this again. -914 pub fn is_multipath_enabled(&self) -> bool { -915 let conn = self.0.lock_without_waking("is_multipath_enabled"); -916 conn.inner.is_multipath_negotiated() -917 } -918 -919 /// Registers one address at which this endpoint might be reachable -920 /// -921 /// When the NAT traversal extension is negotiated, servers send these addresses to clients in -922 /// `ADD_ADDRESS` frames. This allows clients to obtain server address candidates to initiate -923 /// NAT traversal attempts. Clients provide their own reachable addresses in `REACH_OUT` frames -924 /// when [`Self::initiate_nat_traversal_round`] is called. -925 pub fn add_nat_traversal_address( -926 &self, -927 address: SocketAddr, -928 ) -> Result<(), n0_nat_traversal::Error> { -929 let mut conn = self.0.lock_and_wake("add_nat_traversal_addresses"); -930 conn.inner.add_nat_traversal_address(address) -931 } -932 -933 /// Removes one or more addresses from the set of addresses at which this endpoint is reachable -934 /// -935 /// When the NAT traversal extension is negotiated, servers send address removals to -936 /// clients in `REMOVE_ADDRESS` frames. This allows clients to stop using outdated -937 /// server address candidates that are no longer valid for NAT traversal. -938 /// -939 /// For clients, removed addresses will no longer be advertised in `REACH_OUT` frames. -940 /// -941 /// Addresses not present in the set will be silently ignored. -942 pub fn remove_nat_traversal_address( -943 &self, -944 address: SocketAddr, -945 ) -> Result<(), n0_nat_traversal::Error> { -946 let mut conn = self.0.lock_and_wake("remove_nat_traversal_addresses"); -947 conn.inner.remove_nat_traversal_address(address) -948 } -949 -950 /// Get the current local nat traversal addresses -951 pub fn get_local_nat_traversal_addresses( -952 &self, -953 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { -954 let conn = self -955 .0 -956 .lock_without_waking("get_local_nat_traversal_addresses"); -957 conn.inner.get_local_nat_traversal_addresses() -958 } -959 -960 /// Get the currently advertised nat traversal addresses by the server -961 pub fn get_remote_nat_traversal_addresses( -962 &self, -963 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { -964 let conn = self -965 .0 -966 .lock_without_waking("get_remote_nat_traversal_addresses"); -967 conn.inner.get_remote_nat_traversal_addresses() -968 } -969 -970 /// Initiates a new nat traversal round -971 /// -972 /// A nat traversal round involves advertising the client's local addresses in `REACH_OUT` -973 /// frames, and initiating probing of the known remote addresses. When a new round is -974 /// initiated, the previous one is cancelled, and paths that have not been opened are closed. -975 /// -976 /// Returns the server addresses that are now being probed. -977 pub fn initiate_nat_traversal_round(&self) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { -978 let mut conn = self.0.lock_and_wake("initiate_nat_traversal_round"); -979 let now = conn.runtime.now(); -980 conn.inner.initiate_nat_traversal_round(now) -981 } -982} -983 -984pin_project! { -985 /// Future produced by [`Connection::open_uni`] -986 pub struct OpenUni<'a> { -987 conn: &'a ConnectionRef, -988 #[pin] -989 notify: Notified<'a>, -990 } -991} -992 -993impl Future for OpenUni<'_> { -994 type Output = Result<SendStream, ConnectionError>; -995 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { -996 let this = self.project(); -997 let (conn, id, is_0rtt) = ready!(poll_open(ctx, this.conn, this.notify, Dir::Uni))?; -998 Poll::Ready(Ok(SendStream::new(conn, id, is_0rtt))) +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() +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. +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())); +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( +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} -1001 -1002pin_project! { -1003 /// Future produced by [`Connection::open_bi`] -1004 pub struct OpenBi<'a> { -1005 conn: &'a ConnectionRef, -1006 #[pin] -1007 notify: Notified<'a>, -1008 } -1009} -1010 -1011impl Future for OpenBi<'_> { -1012 type Output = Result<(SendStream, RecvStream), ConnectionError>; -1013 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { -1014 let this = self.project(); -1015 let (conn, id, is_0rtt) = ready!(poll_open(ctx, this.conn, this.notify, Dir::Bi))?; -1016 -1017 Poll::Ready(Ok(( -1018 SendStream::new(conn.clone(), id, is_0rtt), -1019 RecvStream::new(conn, id, is_0rtt), -1020 ))) -1021 } -1022} +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 -1024fn poll_open<'a>( -1025 ctx: &mut Context<'_>, -1026 conn: &'a ConnectionRef, -1027 mut notify: Pin<&mut Notified<'a>>, -1028 dir: Dir, -1029) -> Poll<Result<(ConnectionRef, StreamId, bool), ConnectionError>> { -1030 let mut state = conn.lock_without_waking("poll_open"); -1031 if let Some(ref e) = state.error { -1032 return Poll::Ready(Err(e.clone())); -1033 } else if let Some(id) = state.inner.streams().open(dir) { -1034 let is_0rtt = state.inner.side().is_client() && state.inner.is_handshaking(); -1035 drop(state); // Release the lock so clone can take it -1036 return Poll::Ready(Ok((conn.clone(), id, is_0rtt))); +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 loop { -1039 match notify.as_mut().poll(ctx) { -1040 // `state` lock ensures we didn't race with readiness -1041 Poll::Pending => return Poll::Pending, -1042 // Spurious wakeup, get a new future -1043 Poll::Ready(()) => { -1044 notify.set(conn.shared.stream_budget_available[dir as usize].notified()) -1045 } -1046 } -1047 } -1048} -1049 -1050pin_project! { -1051 /// Future produced by [`Connection::accept_uni`] -1052 pub struct AcceptUni<'a> { -1053 conn: &'a ConnectionRef, -1054 #[pin] -1055 notify: Notified<'a>, -1056 } -1057} -1058 -1059impl Future for AcceptUni<'_> { -1060 type Output = Result<RecvStream, ConnectionError>; -1061 -1062 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { -1063 let this = self.project(); -1064 let (conn, id, is_0rtt) = ready!(poll_accept(ctx, this.conn, this.notify, Dir::Uni))?; -1065 Poll::Ready(Ok(RecvStream::new(conn, id, is_0rtt))) -1066 } -1067} -1068 -1069pin_project! { -1070 /// Future produced by [`Connection::accept_bi`] -1071 pub struct AcceptBi<'a> { -1072 conn: &'a ConnectionRef, -1073 #[pin] -1074 notify: Notified<'a>, -1075 } -1076} -1077 -1078impl Future for AcceptBi<'_> { -1079 type Output = Result<(SendStream, RecvStream), ConnectionError>; -1080 -1081 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { -1082 let this = self.project(); -1083 let (conn, id, is_0rtt) = ready!(poll_accept(ctx, this.conn, this.notify, Dir::Bi))?; -1084 Poll::Ready(Ok(( -1085 SendStream::new(conn.clone(), id, is_0rtt), -1086 RecvStream::new(conn, id, is_0rtt), -1087 ))) -1088 } -1089} -1090 -1091fn poll_accept<'a>( -1092 ctx: &mut Context<'_>, -1093 conn: &'a ConnectionRef, -1094 mut notify: Pin<&mut Notified<'a>>, -1095 dir: Dir, -1096) -> Poll<Result<(ConnectionRef, StreamId, bool), ConnectionError>> { -1097 let mut state = conn.lock_and_wake("poll_accept"); -1098 // Check for incoming streams before checking `state.error` so that already-received streams, -1099 // which are necessarily finite, can be drained from a closed connection. -1100 if let Some(id) = state.inner.streams().accept(dir) { -1101 let is_0rtt = state.inner.is_handshaking(); -1102 drop(state); // Release the lock (wake on drop) so clone can take it -1103 return Poll::Ready(Ok((conn.clone(), id, is_0rtt))); -1104 } else if let Some(ref e) = state.error { -1105 return Poll::Ready(Err(e.clone())); -1106 } -1107 loop { -1108 match notify.as_mut().poll(ctx) { -1109 // `state` lock ensures we didn't race with readiness -1110 Poll::Pending => return Poll::Pending, -1111 // Spurious wakeup, get a new future -1112 Poll::Ready(()) => notify.set(conn.shared.stream_incoming[dir as usize].notified()), -1113 } -1114 } -1115} -1116 -1117pin_project! { -1118 /// Future produced by [`Connection::read_datagram`] -1119 pub struct ReadDatagram<'a> { -1120 conn: &'a ConnectionRef, -1121 #[pin] -1122 notify: Notified<'a>, -1123 } -1124} -1125 -1126impl Future for ReadDatagram<'_> { -1127 type Output = Result<Bytes, ConnectionError>; -1128 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { -1129 let mut this = self.project(); -1130 let mut state = this.conn.lock_without_waking("ReadDatagram::poll"); -1131 // Check for buffered datagrams before checking `state.error` so that already-received -1132 // datagrams, which are necessarily finite, can be drained from a closed connection. -1133 if let Some(x) = state.inner.datagrams().recv() { -1134 return Poll::Ready(Ok(x)); -1135 } else if let Some(ref e) = state.error { -1136 return Poll::Ready(Err(e.clone())); -1137 } -1138 loop { -1139 match this.notify.as_mut().poll(ctx) { -1140 // `state` lock ensures we didn't race with readiness -1141 Poll::Pending => return Poll::Pending, -1142 // Spurious wakeup, get a new future -1143 Poll::Ready(()) => this -1144 .notify -1145 .set(this.conn.shared.datagram_received.notified()), -1146 } -1147 } -1148 } -1149} -1150 -1151pin_project! { -1152 /// Future produced by [`Connection::send_datagram_wait`] -1153 pub struct SendDatagram<'a> { -1154 conn: &'a ConnectionRef, -1155 data: Option<Bytes>, -1156 #[pin] -1157 notify: Notified<'a>, -1158 } -1159} -1160 -1161impl Future for SendDatagram<'_> { -1162 type Output = Result<(), SendDatagramError>; -1163 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { -1164 let mut this = self.project(); -1165 let mut state = this.conn.lock_and_wake("SendDatagram::poll"); -1166 if let Some(ref e) = state.error { -1167 return Poll::Ready(Err(SendDatagramError::ConnectionLost(e.clone()))); -1168 } -1169 use proto::SendDatagramError::*; -1170 match state -1171 .inner -1172 .datagrams() -1173 .send(this.data.take().unwrap(), false) -1174 { -1175 Ok(()) => Poll::Ready(Ok(())), -1176 Err(e) => Poll::Ready(Err(match e { -1177 Blocked(data) => { -1178 this.data.replace(data); -1179 loop { -1180 match this.notify.as_mut().poll(ctx) { -1181 Poll::Pending => return Poll::Pending, -1182 // Spurious wakeup, get a new future -1183 Poll::Ready(()) => this -1184 .notify -1185 .set(this.conn.shared.datagrams_unblocked.notified()), -1186 } -1187 } -1188 } -1189 UnsupportedByPeer => SendDatagramError::UnsupportedByPeer, -1190 Disabled => SendDatagramError::Disabled, -1191 TooLarge => SendDatagramError::TooLarge, -1192 })), -1193 } -1194 } -1195} -1196 -1197/// Future returned by [`Connection::on_closed`] -1198/// -1199/// Resolves to a tuple of ([`ConnectionError`], [`ConnectionStats`]). -1200pub struct OnClosed { -1201 rx: oneshot::Receiver<(ConnectionError, ConnectionStats)>, -1202 conn: WeakConnectionHandle, -1203} -1204 -1205impl Drop for OnClosed { -1206 fn drop(&mut self) { -1207 if self.rx.is_terminated() { -1208 return; -1209 }; -1210 if let Some(conn) = self.conn.upgrade() { -1211 self.rx.close(); -1212 conn.0 -1213 .lock_without_waking("OnClosed::drop") -1214 .on_closed -1215 .retain(|tx| !tx.is_closed()); -1216 } -1217 } -1218} -1219 -1220impl Future for OnClosed { -1221 type Output = (ConnectionError, ConnectionStats); -1222 -1223 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { -1224 let this = self.get_mut(); -1225 // The `expect` is safe because `State::drop` ensures that all senders are triggered -1226 // before being dropped. -1227 Pin::new(&mut this.rx) -1228 .poll(cx) -1229 .map(|x| x.expect("on_close sender is never dropped before sending")) -1230 } -1231} -1232 -1233#[derive(Debug)] -1234#[allow(clippy::redundant_allocation)] -1235pub(crate) struct ConnectionRef(Arc<Arc<ConnectionInner>>); +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 } +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, +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 -1237impl ConnectionRef { -1238 #[allow(clippy::redundant_allocation)] -1239 fn from_arc(inner: Arc<Arc<ConnectionInner>>) -> Self { -1240 inner.lock_without_waking("from_arc").ref_count += 1; -1241 Self(inner) -1242 } -1243 -1244 pub(crate) fn stable_id(&self) -> usize { -1245 &*self.0 as *const _ as usize -1246 } -1247 -1248 pub(crate) fn weak_handle(&self) -> WeakConnectionHandle { -1249 WeakConnectionHandle(Arc::downgrade(&self.0)) -1250 } -1251} -1252 -1253impl Clone for ConnectionRef { -1254 fn clone(&self) -> Self { -1255 Self::from_arc(Arc::clone(&self.0)) +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 -1259impl Drop for ConnectionRef { -1260 fn drop(&mut self) { -1261 let conn = &mut *self.lock_without_waking("drop"); -1262 if let Some(x) = conn.ref_count.checked_sub(1) { -1263 conn.ref_count = x; -1264 if x == 0 && !conn.inner.is_closed() { -1265 // If the driver is alive, it's just it and us, so we'd better shut it down. If it's -1266 // not, we can't do any harm. If there were any streams being opened, then either -1267 // the connection will be closed for an unrelated reason or a fresh reference will -1268 // be constructed for the newly opened stream. -1269 conn.implicit_close(&self.shared); -1270 } -1271 } -1272 } -1273} -1274 -1275impl std::ops::Deref for ConnectionRef { -1276 type Target = ConnectionInner; -1277 fn deref(&self) -> &Self::Target { -1278 &self.0 -1279 } -1280} -1281 -1282#[derive(Debug)] -1283pub(crate) struct ConnectionInner { -1284 /// Kept private intentionally, use [`Self::lock_and_wake`]. -1285 state: Mutex<State>, -1286 pub(crate) shared: Shared, -1287} -1288 -1289impl ConnectionInner { -1290 /// Lock the state and return a guard that wakes the connection driver on drop. -1291 /// -1292 /// Use this for operations that may queue frames. The wake ensures the driver sends queued frames. -1293 /// If that's not needed, use [`Self::lock_without_waking`]. -1294 pub(crate) fn lock_and_wake(&self, purpose: &'static str) -> WakeGuard<'_> { -1295 WakeGuard { -1296 guard: self.state.lock(purpose), -1297 wake: true, -1298 } -1299 } -1300 -1301 /// Lock the state and return a guard that unlocks once dropped. -1302 /// -1303 /// Use this for operations that don't require any action from the connection driver. -1304 /// Otherwise, use [`Self::lock_and_wake`] instead. -1305 pub(crate) fn lock_without_waking(&self, purpose: &'static str) -> WakeGuard<'_> { -1306 WakeGuard { -1307 guard: self.state.lock(purpose), -1308 wake: false, -1309 } -1310 } -1311} -1312 -1313/// [`MutexGuard`] wrapper that calls [`State::wake`] on drop. -1314#[derive(derive_more::Deref, derive_more::DerefMut)] -1315pub(crate) struct WakeGuard<'a> { -1316 #[deref] -1317 #[deref_mut] -1318 guard: MutexGuard<'a, State>, -1319 wake: bool, -1320} -1321 -1322impl WakeGuard<'_> { -1323 pub(crate) fn skip_waking(&mut self) { -1324 self.wake = false; -1325 } -1326} -1327 -1328impl Drop for WakeGuard<'_> { -1329 fn drop(&mut self) { -1330 if self.wake { -1331 self.guard.wake(); -1332 } -1333 } -1334} -1335 -1336/// A handle to some connection internals, use with care. -1337/// -1338/// This contains a weak reference to the connection so will not itself keep the connection -1339/// alive. -1340#[derive(Debug, Clone)] -1341pub struct WeakConnectionHandle(Weak<Arc<ConnectionInner>>); -1342 -1343impl WeakConnectionHandle { -1344 /// Returns `true` if the [`Connection`] associated with this handle is still alive. -1345 pub fn is_alive(&self) -> bool { -1346 self.0.upgrade().is_some() -1347 } -1348 -1349 /// Upgrade the handle to a full `Connection` -1350 pub fn upgrade(&self) -> Option<Connection> { -1351 self.upgrade_to_ref().map(Connection) -1352 } -1353 -1354 pub(crate) fn upgrade_to_ref(&self) -> Option<ConnectionRef> { -1355 self.0.upgrade().map(ConnectionRef::from_arc) -1356 } -1357 -1358 /// Returns `true` if the two [`WeakConnectionHandle`] point at the same connection. -1359 pub fn is_same_connection(&self, other: &Self) -> bool { -1360 self.0.ptr_eq(&other.0) -1361 } -1362} -1363 -1364#[derive(Debug, Default)] -1365pub(crate) struct Shared { -1366 handshake_confirmed: Notify, -1367 /// Notified when new streams may be locally initiated due to an increase in stream ID flow -1368 /// control budget -1369 stream_budget_available: [Notify; 2], -1370 /// Notified when the peer has initiated a new stream -1371 stream_incoming: [Notify; 2], -1372 datagram_received: Notify, -1373 datagrams_unblocked: Notify, -1374 closed: Notify, -1375} -1376 -1377pub(crate) struct State { -1378 pub(crate) inner: proto::Connection, -1379 driver: Option<Waker>, -1380 handle: ConnectionHandle, -1381 on_handshake_data: Option<oneshot::Sender<()>>, -1382 on_connected: Option<oneshot::Sender<bool>>, -1383 connected: bool, -1384 handshake_confirmed: bool, -1385 timer: Option<Pin<Box<dyn AsyncTimer>>>, -1386 timer_deadline: Option<Instant>, -1387 conn_events: mpsc::UnboundedReceiver<ConnectionEvent>, -1388 endpoint_events: mpsc::UnboundedSender<(ConnectionHandle, EndpointEvent)>, -1389 pub(crate) blocked_writers: FxHashMap<StreamId, Waker>, -1390 pub(crate) blocked_readers: FxHashMap<StreamId, Waker>, -1391 pub(crate) stopped: FxHashMap<StreamId, Arc<Notify>>, -1392 /// Always set to Some before the connection becomes drained -1393 pub(crate) error: Option<ConnectionError>, -1394 /// Tracks paths being opened -1395 open_path: FxHashMap<PathId, watch::Sender<Result<(), PathError>>>, -1396 /// Tracks reference counts for paths. -1397 /// -1398 /// I.e. how many [`Path`] and [`WeakPathHandle`] structs are alive for a path. -1399 /// -1400 /// [`WeakPathHandle`]: crate::path::WeakPathHandle -1401 pub(crate) path_refs: FxHashMap<PathId, usize>, -1402 /// Final path stats for discarded paths. -1403 /// -1404 /// We only insert entries if the discarded path has a non-zero reference count in [`Self::path_refs`]. -1405 /// When the last reference to a path is dropped via [`Self::decrement_path_refs`] its value is cleared. -1406 pub(crate) final_path_stats: FxHashMap<PathId, PathStats>, -1407 pub(crate) path_events: tokio::sync::broadcast::Sender<PathEvent>, -1408 /// Number of live handles that can be used to initiate or handle I/O; excludes the driver -1409 ref_count: usize, -1410 sender: Pin<Box<dyn UdpSender>>, -1411 pub(crate) runtime: Arc<dyn Runtime>, -1412 send_buffer: Vec<u8>, -1413 /// We buffer a transmit when the underlying I/O would block -1414 buffered_transmit: Option<proto::Transmit>, -1415 /// Our last external address reported by the peer. When multipath is enabled, this will be the -1416 /// last report across all paths. -1417 pub(crate) observed_external_addr: watch::Sender<Option<SocketAddr>>, -1418 pub(crate) nat_traversal_updates: tokio::sync::broadcast::Sender<n0_nat_traversal::Event>, -1419 on_closed: Vec<oneshot::Sender<(ConnectionError, ConnectionStats)>>, -1420} -1421 -1422impl State { -1423 #[allow(clippy::too_many_arguments)] -1424 fn new( -1425 inner: proto::Connection, -1426 handle: ConnectionHandle, -1427 endpoint_events: mpsc::UnboundedSender<(ConnectionHandle, EndpointEvent)>, -1428 conn_events: mpsc::UnboundedReceiver<ConnectionEvent>, -1429 on_handshake_data: oneshot::Sender<()>, -1430 on_connected: oneshot::Sender<bool>, -1431 sender: Pin<Box<dyn UdpSender>>, -1432 runtime: Arc<dyn Runtime>, -1433 ) -> Self { -1434 Self { -1435 inner, -1436 driver: None, -1437 handle, -1438 on_handshake_data: Some(on_handshake_data), -1439 on_connected: Some(on_connected), -1440 connected: false, -1441 handshake_confirmed: false, -1442 timer: None, -1443 timer_deadline: None, -1444 conn_events, -1445 endpoint_events, -1446 blocked_writers: FxHashMap::default(), -1447 blocked_readers: FxHashMap::default(), -1448 stopped: FxHashMap::default(), -1449 open_path: FxHashMap::default(), -1450 error: None, -1451 ref_count: 0, -1452 sender, -1453 runtime, -1454 send_buffer: Vec::new(), -1455 buffered_transmit: None, -1456 path_events: tokio::sync::broadcast::channel(32).0, -1457 observed_external_addr: watch::Sender::new(None), -1458 nat_traversal_updates: tokio::sync::broadcast::channel(32).0, -1459 on_closed: Vec::new(), -1460 final_path_stats: Default::default(), -1461 path_refs: Default::default(), -1462 } -1463 } -1464 -1465 fn drive_transmit(&mut self, cx: &mut Context<'_>) -> io::Result<bool> { -1466 let now = self.runtime.now(); -1467 let mut transmits = 0; -1468 -1469 let max_datagrams = self -1470 .sender -1471 .max_transmit_segments() -1472 .min(MAX_TRANSMIT_SEGMENTS); -1473 -1474 loop { -1475 // Retry the last transmit, or get a new one. -1476 let t = match self.buffered_transmit.take() { -1477 Some(t) => t, -1478 None => { -1479 self.send_buffer.clear(); -1480 match self -1481 .inner -1482 .poll_transmit(now, max_datagrams, &mut self.send_buffer) -1483 { -1484 Some(t) => { -1485 transmits += match t.segment_size { -1486 None => 1, -1487 Some(s) => t.size.div_ceil(s), // round up -1488 }; -1489 t -1490 } -1491 None => break, -1492 } -1493 } -1494 }; -1495 -1496 let len = t.size; -1497 match self -1498 .sender -1499 .as_mut() -1500 .poll_send(&udp_transmit(&t, &self.send_buffer[..len]), cx) -1501 { -1502 Poll::Pending => { -1503 self.buffered_transmit = Some(t); -1504 return Ok(false); -1505 } -1506 Poll::Ready(Err(e)) => return Err(e), -1507 Poll::Ready(Ok(())) => {} -1508 } -1509 -1510 if transmits >= MAX_TRANSMIT_DATAGRAMS { -1511 // TODO: What isn't ideal here yet is that if we don't poll all -1512 // datagrams that could be sent we don't go into the `app_limited` -1513 // state and CWND continues to grow until we get here the next time. -1514 // See https://github.com/quinn-rs/quinn/issues/1126 -1515 return Ok(true); -1516 } -1517 } -1518 -1519 Ok(false) -1520 } -1521 -1522 fn forward_endpoint_events(&mut self) { -1523 while let Some(event) = self.inner.poll_endpoint_events() { -1524 // If the endpoint driver is gone, noop. -1525 let _ = self.endpoint_events.send((self.handle, event)); -1526 } -1527 } -1528 -1529 /// If this returns `Err`, the endpoint is dead, so the driver should exit immediately. -1530 fn process_conn_events( -1531 &mut self, -1532 shared: &Shared, -1533 cx: &mut Context<'_>, -1534 ) -> Result<(), ConnectionError> { -1535 loop { -1536 match self.conn_events.poll_recv(cx) { -1537 Poll::Ready(Some(ConnectionEvent::Rebind(sender))) => { -1538 self.sender = sender; -1539 self.inner.handle_network_change(None, self.runtime.now()); -1540 } -1541 Poll::Ready(Some(ConnectionEvent::LocalAddressChanged(hint))) => { -1542 self.inner -1543 .handle_network_change(hint.as_deref().map(|x| x as _), self.runtime.now()); -1544 } -1545 Poll::Ready(Some(ConnectionEvent::Proto(event))) => { -1546 self.inner.handle_event(event); -1547 } -1548 Poll::Ready(Some(ConnectionEvent::Close { reason, error_code })) => { -1549 self.close(error_code, reason, shared); -1550 } -1551 Poll::Ready(None) => { -1552 return Err(ConnectionError::TransportError(TransportError::new( -1553 TransportErrorCode::INTERNAL_ERROR, -1554 "endpoint driver future was dropped".to_string(), -1555 ))); -1556 } -1557 Poll::Pending => { -1558 return Ok(()); -1559 } -1560 } -1561 } -1562 } -1563 -1564 fn forward_app_events(&mut self, shared: &Shared) { -1565 while let Some(event) = self.inner.poll() { -1566 use proto::Event::*; -1567 match event { -1568 HandshakeDataReady => { -1569 if let Some(x) = self.on_handshake_data.take() { -1570 let _ = x.send(()); -1571 } -1572 } -1573 Connected => { -1574 self.connected = true; -1575 if let Some(x) = self.on_connected.take() { -1576 // We don't care if the on-connected future was dropped -1577 let _ = x.send(self.inner.accepted_0rtt()); -1578 } -1579 if self.inner.side().is_client() && !self.inner.accepted_0rtt() { -1580 // Wake up rejected 0-RTT streams so they can fail immediately with -1581 // `ZeroRttRejected` errors. -1582 wake_all(&mut self.blocked_writers); -1583 wake_all(&mut self.blocked_readers); -1584 wake_all_notify(&mut self.stopped); -1585 } -1586 } -1587 HandshakeConfirmed => { -1588 self.handshake_confirmed = true; -1589 shared.handshake_confirmed.notify_waiters(); +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)) +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"); +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 } +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); +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 ConnectionLost { reason } => { -1592 self.terminate(reason, shared); -1593 } -1594 Stream(StreamEvent::Writable { id }) => wake_stream(id, &mut self.blocked_writers), -1595 Stream(StreamEvent::Opened { dir: Dir::Uni }) => { -1596 shared.stream_incoming[Dir::Uni as usize].notify_waiters(); -1597 } -1598 Stream(StreamEvent::Opened { dir: Dir::Bi }) => { -1599 shared.stream_incoming[Dir::Bi as usize].notify_waiters(); -1600 } -1601 DatagramReceived => { -1602 shared.datagram_received.notify_waiters(); -1603 } -1604 DatagramsUnblocked => { -1605 shared.datagrams_unblocked.notify_waiters(); -1606 } -1607 Stream(StreamEvent::Readable { id }) => wake_stream(id, &mut self.blocked_readers), -1608 Stream(StreamEvent::Available { dir }) => { -1609 // Might mean any number of streams are ready, so we wake up everyone -1610 shared.stream_budget_available[dir as usize].notify_waiters(); +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::Finished { id }) => wake_stream_notify(id, &mut self.stopped), -1613 Stream(StreamEvent::Stopped { id, .. }) => { -1614 wake_stream_notify(id, &mut self.stopped); -1615 wake_stream(id, &mut self.blocked_writers); -1616 } -1617 Path(ref evt @ PathEvent::ObservedAddr { addr: observed, .. }) => { -1618 self.path_events.send(evt.clone()).ok(); -1619 self.observed_external_addr.send_if_modified(|addr| { -1620 let old = addr.replace(observed); -1621 old != *addr -1622 }); -1623 } -1624 Path(ref evt @ PathEvent::Opened { id }) => { -1625 self.path_events.send(evt.clone()).ok(); -1626 if let Some(sender) = self.open_path.remove(&id) { -1627 sender.send_modify(|value| *value = Ok(())); -1628 } +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(); 1629 } -1630 Path(ref evt @ PathEvent::Discarded { id, ref path_stats }) => { -1631 if self.path_refs.contains_key(&id) { -1632 self.final_path_stats.insert(id, *path_stats.clone()); -1633 } -1634 self.path_events.send(evt.clone()).ok(); -1635 } -1636 Path(ref evt @ PathEvent::Abandoned { id, .. }) => { -1637 if let Some(sender) = self.open_path.remove(&id) { -1638 // We don't care for the reason why this path was closed here, because semantically -1639 // all close reasons for a path that has not yet been opened equals to `ValidationFailed`. -1640 // With the noq API, there is no way to application-close a not-yet-opened path, so -1641 // `ApplicationClosed` cannot occur. And all other variants will only occur for paths -1642 // that have already been opened. -1643 // The previous iteration of this code had another event `PathEvent::LocallyClosed` which -1644 // contained a `PathError`, but that was only ever set to `ValidationFailed`. -1645 let error = PathError::ValidationFailed; -1646 sender.send_modify(|value| *value = Err(error)); -1647 } -1648 // this will happen also for already opened paths -1649 self.path_events.send(evt.clone()).ok(); -1650 } -1651 Path(evt @ PathEvent::RemoteStatus { .. }) => { -1652 self.path_events.send(evt).ok(); +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 NatTraversal(update) => { -1655 self.nat_traversal_updates.send(update).ok(); -1656 } -1657 } -1658 } -1659 } -1660 -1661 fn drive_timer(&mut self, cx: &mut Context<'_>) -> bool { -1662 // Check whether we need to (re)set the timer. If so, we must poll again to ensure the -1663 // timer is registered with the runtime (and check whether it's already -1664 // expired). -1665 match self.inner.poll_timeout() { -1666 Some(deadline) => { -1667 if let Some(delay) = &mut self.timer { -1668 // There is no need to reset the tokio timer if the deadline -1669 // did not change -1670 if self -1671 .timer_deadline -1672 .map(|current_deadline| current_deadline != deadline) -1673 .unwrap_or(true) -1674 { -1675 delay.as_mut().reset(deadline); -1676 } -1677 } else { -1678 self.timer = Some(self.runtime.new_timer(deadline)); -1679 } -1680 // Store the actual expiration time of the timer -1681 self.timer_deadline = Some(deadline); -1682 } -1683 None => { -1684 self.timer_deadline = None; -1685 return false; -1686 } -1687 } -1688 -1689 if self.timer_deadline.is_none() { -1690 return false; -1691 } -1692 -1693 let delay = self -1694 .timer -1695 .as_mut() -1696 .expect("timer must exist in this state") -1697 .as_mut(); -1698 if delay.poll(cx).is_pending() { -1699 // Since there wasn't a timeout event, there is nothing new -1700 // for the connection to do -1701 return false; -1702 } -1703 -1704 // A timer expired, so the caller needs to check for -1705 // new transmits, which might cause new timers to be set. -1706 self.inner.handle_timeout(self.runtime.now()); -1707 self.timer_deadline = None; -1708 true -1709 } +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 /// Wake up a blocked `Driver` task to process I/O -1712 pub(crate) fn wake(&mut self) { -1713 if let Some(x) = self.driver.take() { -1714 x.wake(); -1715 } -1716 } -1717 -1718 /// Used to wake up all blocked futures when the connection becomes closed for any reason -1719 fn terminate(&mut self, reason: ConnectionError, shared: &Shared) { -1720 self.error = Some(reason.clone()); -1721 if let Some(x) = self.on_handshake_data.take() { -1722 let _ = x.send(()); -1723 } -1724 wake_all(&mut self.blocked_writers); -1725 wake_all(&mut self.blocked_readers); -1726 shared.stream_budget_available[Dir::Uni as usize].notify_waiters(); -1727 shared.stream_budget_available[Dir::Bi as usize].notify_waiters(); -1728 shared.stream_incoming[Dir::Uni as usize].notify_waiters(); -1729 shared.stream_incoming[Dir::Bi as usize].notify_waiters(); -1730 shared.datagram_received.notify_waiters(); -1731 shared.datagrams_unblocked.notify_waiters(); -1732 if let Some(x) = self.on_connected.take() { -1733 let _ = x.send(false); -1734 } -1735 shared.handshake_confirmed.notify_waiters(); -1736 wake_all_notify(&mut self.stopped); -1737 shared.closed.notify_waiters(); -1738 -1739 // Send to the registered on_closed futures. -1740 let stats = self.inner.stats(); -1741 for tx in self.on_closed.drain(..) { -1742 tx.send((reason.clone(), stats.clone())).ok(); -1743 } -1744 } -1745 -1746 fn close(&mut self, error_code: VarInt, reason: Bytes, shared: &Shared) { -1747 self.inner.close(self.runtime.now(), error_code, reason); -1748 self.terminate(ConnectionError::LocallyClosed, shared); -1749 self.wake(); -1750 } -1751 -1752 /// Close for a reason other than the application's explicit request -1753 pub(crate) fn implicit_close(&mut self, shared: &Shared) { -1754 self.close(0u32.into(), Bytes::new(), shared); -1755 } +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(()); +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(); 1756 -1757 pub(crate) fn check_0rtt(&self) -> Result<(), ()> { -1758 if self.inner.is_handshaking() -1759 || self.inner.accepted_0rtt() -1760 || self.inner.side().is_server() -1761 { -1762 Ok(()) -1763 } else { -1764 Err(()) -1765 } -1766 } -1767 -1768 /// Returns [`PathStats`] for a path, if available. -1769 /// -1770 /// This gets the stats from [`proto::Connection`]. If that returns `None` -1771 /// it gets them from `Self::final_path_stats` instead. -1772 pub(crate) fn path_stats(&mut self, path_id: PathId) -> Option<PathStats> { -1773 self.inner -1774 .path_stats(path_id) -1775 .or_else(|| self.final_path_stats.get(&path_id).copied()) -1776 } -1777 -1778 /// Increment the reference counter for a path in this connection. -1779 /// -1780 /// This counts how many [`Path`] or [`WeakPathHandle`] structs exist for a path. -1781 /// Currently this is used to determine whether to store the final stats after a path is -1782 /// abandoned. -1783 /// -1784 /// [`WeakPathHandle`]: crate::path::WeakPathHandle -1785 pub(crate) fn increment_path_refs(&mut self, path_id: PathId) { -1786 *self.path_refs.entry(path_id).or_default() += 1; -1787 } -1788 -1789 /// Decrement the reference counter for a path in this connection. -1790 pub(crate) fn decrement_path_refs(&mut self, path_id: PathId) { -1791 if let Some(refs) = self.path_refs.get_mut(&path_id) { -1792 *refs = refs.saturating_sub(1); -1793 if *refs == 0 { -1794 self.path_refs.remove(&path_id); -1795 self.final_path_stats.remove(&path_id); -1796 } -1797 } -1798 } -1799} -1800 -1801impl Drop for State { -1802 fn drop(&mut self) { -1803 if !self.inner.is_drained() { -1804 // Ensure the endpoint can tidy up -1805 let _ = self -1806 .endpoint_events -1807 .send((self.handle, proto::EndpointEvent::drained())); -1808 } +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()) +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) +1807 } +1808} 1809 -1810 if !self.on_closed.is_empty() { -1811 // Ensure that all on_closed oneshot senders are triggered before dropping. -1812 let reason = self.error.as_ref().expect("closed without error reason"); -1813 let stats = self.inner.stats(); -1814 for tx in self.on_closed.drain(..) { -1815 tx.send((reason.clone(), stats.clone())).ok(); -1816 } +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 } -1818 } -1819} -1820 -1821impl fmt::Debug for State { -1822 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -1823 f.debug_struct("State").field("inner", &self.inner).finish() -1824 } -1825} -1826 -1827fn wake_stream(stream_id: StreamId, wakers: &mut FxHashMap<StreamId, Waker>) { -1828 if let Some(waker) = wakers.remove(&stream_id) { -1829 waker.wake(); -1830 } -1831} -1832 -1833fn wake_all(wakers: &mut FxHashMap<StreamId, Waker>) { -1834 wakers.drain().for_each(|(_, waker)| waker.wake()) +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 } +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_notify(stream_id: StreamId, wakers: &mut FxHashMap<StreamId, Arc<Notify>>) { -1838 if let Some(notify) = wakers.remove(&stream_id) { -1839 notify.notify_waiters() +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_notify(wakers: &mut FxHashMap<StreamId, Arc<Notify>>) { -1844 wakers -1845 .drain() -1846 .for_each(|(_, notify)| notify.notify_waiters()) -1847} -1848 -1849/// Errors that can arise when sending a datagram -1850#[derive(Debug, Error, Clone, Eq, PartialEq)] -1851pub enum SendDatagramError { -1852 /// The peer does not support receiving datagram frames -1853 #[error("datagrams not supported by peer")] -1854 UnsupportedByPeer, -1855 /// Datagram support is disabled locally -1856 #[error("datagram support disabled")] -1857 Disabled, -1858 /// The datagram is larger than the connection can currently accommodate -1859 /// -1860 /// Indicates that the path MTU minus overhead or the limit advertised by the peer has been -1861 /// exceeded. -1862 #[error("datagram too large")] -1863 TooLarge, -1864 /// The connection was lost -1865 #[error("connection lost")] -1866 ConnectionLost(#[from] ConnectionError), -1867} -1868 -1869/// The maximum amount of datagrams which will be produced in a single `drive_transmit` call -1870/// -1871/// This limits the amount of CPU resources consumed by datagram generation, -1872/// and allows other tasks (like receiving ACKs) to run in between. -1873const MAX_TRANSMIT_DATAGRAMS: usize = 20; -1874 -1875/// The maximum amount of datagrams that are sent in a single transmit -1876/// -1877/// This can be lower than the maximum platform capabilities, to avoid excessive -1878/// memory allocations when calling `poll_transmit()`. Benchmarks have shown -1879/// that numbers around 10 are a good compromise. -1880const MAX_TRANSMIT_SEGMENTS: NonZeroUsize = NonZeroUsize::new(10).expect("known");

    \ No newline at end of file +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 diff --git a/pr/451/docs/src/noq/endpoint.rs.html b/pr/451/docs/src/noq/endpoint.rs.html index 7df6f984b..d03c5a42c 100644 --- a/pr/451/docs/src/noq/endpoint.rs.html +++ b/pr/451/docs/src/noq/endpoint.rs.html @@ -8,958 +8,975 @@ 8 num::NonZeroUsize, 9 pin::Pin, 10 str, -11 sync::{Arc, Mutex}, -12 task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, -13}; -14 -15#[cfg(all( -16 not(wasm_browser), -17 any(feature = "runtime-tokio", feature = "runtime-smol"), -18 any(feature = "aws-lc-rs", feature = "ring"), -19))] -20use crate::runtime::default_runtime; -21use crate::{ -22 Instant, -23 runtime::{AsyncUdpSocket, Runtime, UdpSender}, -24 udp_transmit, -25}; -26use bytes::{Bytes, BytesMut}; -27use pin_project_lite::pin_project; -28use proto::{ -29 self as proto, ClientConfig, ConnectError, ConnectionError, ConnectionHandle, DatagramEvent, -30 EndpointEvent, FourTuple, NetworkChangeHint, ServerConfig, -31}; -32use rustc_hash::FxHashMap; -33#[cfg(all( -34 not(wasm_browser), -35 any(feature = "runtime-tokio", feature = "runtime-smol"), -36 any(feature = "aws-lc-rs", feature = "ring"), -37))] -38use socket2::{Domain, Protocol, Socket, Type}; -39use tokio::sync::{Notify, futures::Notified, mpsc}; -40use tracing::{Instrument, Span, trace}; -41use udp::{BATCH_SIZE, RecvMeta}; -42 -43use crate::{ -44 ConnectionEvent, EndpointConfig, IO_LOOP_BOUND, RECV_TIME_BOUND, VarInt, -45 connection::Connecting, incoming::Incoming, work_limiter::WorkLimiter, -46}; -47 -48/// A QUIC endpoint. -49/// -50/// An endpoint corresponds to a single UDP socket, may host many connections, and may act as both -51/// client and server for different connections. +11 sync::{ +12 Arc, Mutex, +13 atomic::{AtomicUsize, Ordering}, +14 }, +15 task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, +16}; +17 +18#[cfg(all( +19 not(wasm_browser), +20 any(feature = "runtime-tokio", feature = "runtime-smol"), +21 any(feature = "aws-lc-rs", feature = "ring"), +22))] +23use crate::runtime::default_runtime; +24use crate::{ +25 Instant, +26 runtime::{AsyncUdpSocket, Runtime, UdpSender}, +27 udp_transmit, +28}; +29use bytes::{Bytes, BytesMut}; +30use pin_project_lite::pin_project; +31use proto::{ +32 self as proto, ClientConfig, ConnectError, ConnectionError, ConnectionHandle, DatagramEvent, +33 EndpointEvent, FourTuple, NetworkChangeHint, ServerConfig, +34}; +35use rustc_hash::FxHashMap; +36#[cfg(all( +37 not(wasm_browser), +38 any(feature = "runtime-tokio", feature = "runtime-smol"), +39 any(feature = "aws-lc-rs", feature = "ring"), +40))] +41use socket2::{Domain, Protocol, Socket, Type}; +42use tokio::sync::{Notify, futures::Notified, mpsc}; +43use tracing::{Instrument, Span, trace}; +44use udp::{BATCH_SIZE, RecvMeta}; +45 +46use crate::{ +47 ConnectionEvent, EndpointConfig, IO_LOOP_BOUND, RECV_TIME_BOUND, VarInt, +48 connection::Connecting, incoming::Incoming, work_limiter::WorkLimiter, +49}; +50 +51/// A QUIC endpoint. 52/// -53/// May be cloned to obtain another handle to the same endpoint. -54#[derive(Debug, Clone)] -55pub struct Endpoint { -56 pub(crate) inner: EndpointRef, -57 runtime: Arc<dyn Runtime>, -58} -59 -60impl Endpoint { -61 /// Helper to construct an endpoint for use with outgoing connections only -62 /// -63 /// Note that `addr` is the *local* address to bind to, which should usually be a wildcard -64 /// address like `0.0.0.0:0` or `[::]:0`, which allow communication with any reachable IPv4 or -65 /// IPv6 address respectively from an OS-assigned port. -66 /// -67 /// If an IPv6 address is provided, attempts to make the socket dual-stack so as to allow -68 /// communication with both IPv4 and IPv6 addresses. As such, calling `Endpoint::client` with -69 /// the address `[::]:0` is a reasonable default to maximize the ability to connect to other -70 /// address. For example: -71 /// -72 /// ``` -73 /// noq::Endpoint::client((std::net::Ipv6Addr::UNSPECIFIED, 0).into()); -74 /// ``` -75 /// -76 /// Some environments may not allow creation of dual-stack sockets, in which case an IPv6 -77 /// client will only be able to connect to IPv6 servers. An IPv4 client is never dual-stack. -78 #[cfg(all( -79 not(wasm_browser), -80 any(feature = "runtime-tokio", feature = "runtime-smol"), -81 any(feature = "aws-lc-rs", feature = "ring"), // `EndpointConfig::default()` is only available with these -82 ))] -83 pub fn client(addr: SocketAddr) -> io::Result<Self> { -84 let socket = Socket::new(Domain::for_address(addr), Type::DGRAM, Some(Protocol::UDP))?; -85 if addr.is_ipv6() -86 && let Err(e) = socket.set_only_v6(false) -87 { -88 tracing::debug!(%e, "unable to make socket dual-stack"); -89 } -90 socket.bind(&addr.into())?; -91 let runtime = -92 default_runtime().ok_or_else(|| io::Error::other("no async runtime found"))?; -93 Self::new_with_abstract_socket( -94 EndpointConfig::default(), -95 None, -96 runtime.wrap_udp_socket(socket.into())?, -97 runtime, -98 ) -99 } -100 -101 /// Returns relevant stats from this Endpoint -102 pub fn stats(&self) -> EndpointStats { -103 self.inner.state.lock().unwrap().stats -104 } -105 -106 /// Helper to construct an endpoint for use with both incoming and outgoing connections -107 /// -108 /// Platform defaults for dual-stack sockets vary. For example, any socket bound to a wildcard -109 /// IPv6 address on Windows will not by default be able to communicate with IPv4 -110 /// addresses. Portable applications should bind an address that matches the family they wish to -111 /// communicate within. -112 #[cfg(all( -113 not(wasm_browser), -114 any(feature = "runtime-tokio", feature = "runtime-smol"), -115 any(feature = "aws-lc-rs", feature = "ring"), // `EndpointConfig::default()` is only available with these -116 ))] -117 pub fn server(config: ServerConfig, addr: SocketAddr) -> io::Result<Self> { -118 let socket = std::net::UdpSocket::bind(addr)?; -119 let runtime = -120 default_runtime().ok_or_else(|| io::Error::other("no async runtime found"))?; -121 Self::new_with_abstract_socket( -122 EndpointConfig::default(), -123 Some(config), -124 runtime.wrap_udp_socket(socket)?, -125 runtime, -126 ) -127 } -128 -129 /// Construct an endpoint with arbitrary configuration and socket -130 #[cfg(not(wasm_browser))] -131 pub fn new( -132 config: EndpointConfig, -133 server_config: Option<ServerConfig>, -134 socket: std::net::UdpSocket, -135 runtime: Arc<dyn Runtime>, -136 ) -> io::Result<Self> { -137 let socket = runtime.wrap_udp_socket(socket)?; -138 Self::new_with_abstract_socket(config, server_config, socket, runtime) -139 } -140 -141 /// Construct an endpoint with arbitrary configuration and pre-constructed abstract socket -142 /// -143 /// Useful when `socket` has additional state (e.g. sidechannels) attached for which shared -144 /// ownership is needed. -145 pub fn new_with_abstract_socket( -146 config: EndpointConfig, -147 server_config: Option<ServerConfig>, -148 socket: Box<dyn AsyncUdpSocket>, -149 runtime: Arc<dyn Runtime>, -150 ) -> io::Result<Self> { -151 let addr = socket.local_addr()?; -152 let allow_mtud = !socket.may_fragment(); -153 let rc = EndpointRef::new( -154 socket, -155 proto::Endpoint::new(Arc::new(config), server_config.map(Arc::new), allow_mtud), -156 addr.is_ipv6(), -157 runtime.clone(), -158 ); -159 let driver = EndpointDriver(rc.clone()); -160 runtime.spawn(Box::pin( -161 async { -162 if let Err(e) = driver.await { -163 tracing::error!("I/O error: {}", e); -164 } -165 } -166 .instrument(Span::current()), -167 )); -168 Ok(Self { inner: rc, runtime }) -169 } -170 -171 /// Get the next incoming connection attempt from a client -172 /// -173 /// Yields [`Incoming`]s, or `None` if the endpoint is [`close`](Self::close)d. [`Incoming`] -174 /// can be `await`ed to obtain the final [`Connection`](crate::Connection), or used to e.g. -175 /// filter connection attempts or force address validation, or converted into an intermediate -176 /// `Connecting` future which can be used to e.g. send 0.5-RTT data. -177 pub fn accept(&self) -> Accept<'_> { -178 Accept { -179 endpoint: self, -180 notify: self.inner.shared.incoming.notified(), -181 } -182 } -183 -184 /// Set the client configuration used by `connect` -185 pub fn set_default_client_config(&self, config: ClientConfig) { -186 self.inner.0.state.lock().unwrap().default_client_config = Some(config); -187 } -188 -189 /// Connect to a remote endpoint -190 /// -191 /// `server_name` must be covered by the certificate presented by the server. This prevents a -192 /// connection from being intercepted by an attacker with a valid certificate for some other -193 /// server. -194 /// -195 /// May fail immediately due to configuration errors, or in the future if the connection could -196 /// not be established. -197 pub fn connect(&self, addr: SocketAddr, server_name: &str) -> Result<Connecting, ConnectError> { -198 let Some(config) = self -199 .inner -200 .0 -201 .state -202 .lock() -203 .unwrap() -204 .default_client_config -205 .clone() -206 else { -207 return Err(ConnectError::NoDefaultClientConfig); -208 }; -209 -210 self.connect_with(config, addr, server_name) -211 } -212 -213 /// Connect to a remote endpoint using a custom configuration. -214 /// -215 /// See [`connect()`] for details. -216 /// -217 /// [`connect()`]: Endpoint::connect -218 pub fn connect_with( -219 &self, -220 config: ClientConfig, -221 addr: SocketAddr, -222 server_name: &str, -223 ) -> Result<Connecting, ConnectError> { -224 let mut endpoint = self.inner.state.lock().unwrap(); -225 if endpoint.driver_lost || endpoint.recv_state.connections.close.is_some() { -226 return Err(ConnectError::EndpointStopping); -227 } -228 if addr.is_ipv6() && !endpoint.ipv6 { -229 return Err(ConnectError::InvalidRemoteAddress(addr)); -230 } -231 let addr = if endpoint.ipv6 { -232 SocketAddr::V6(ensure_ipv6(addr)) -233 } else { -234 addr -235 }; -236 -237 let (ch, conn) = endpoint -238 .inner -239 .connect(self.runtime.now(), config, addr, server_name)?; -240 -241 let sender = endpoint.socket.create_sender(); -242 endpoint.stats.outgoing_handshakes += 1; -243 Ok(endpoint -244 .recv_state -245 .connections -246 .insert(ch, conn, sender, self.runtime.clone())) -247 } -248 -249 /// Switch to a new UDP socket -250 /// -251 /// See [`Endpoint::rebind_abstract()`] for details. -252 #[cfg(not(wasm_browser))] -253 pub fn rebind(&self, socket: std::net::UdpSocket) -> io::Result<()> { -254 self.rebind_abstract(self.runtime.wrap_udp_socket(socket)?) -255 } -256 -257 /// Switch to a new UDP socket -258 /// -259 /// Allows the endpoint's address to be updated live, affecting all active connections. Incoming -260 /// connections and connections to servers unreachable from the new address will be lost. -261 /// -262 /// On error, the old UDP socket is retained. -263 pub fn rebind_abstract(&self, socket: Box<dyn AsyncUdpSocket>) -> io::Result<()> { -264 let addr = socket.local_addr()?; -265 let mut inner = self.inner.state.lock().unwrap(); -266 inner.prev_socket = Some(mem::replace(&mut inner.socket, socket)); -267 inner.ipv6 = addr.is_ipv6(); -268 -269 // Update connection socket references -270 for sender in inner.recv_state.connections.senders.values() { -271 // Ignoring errors from dropped connections -272 let _ = sender.send(ConnectionEvent::Rebind(inner.socket.create_sender())); -273 } -274 if let Some(driver) = inner.driver.take() { -275 // Ensure the driver can register for wake-ups from the new socket -276 driver.wake(); -277 } -278 -279 Ok(()) -280 } -281 -282 /// Notify connections that the local network address has changed. -283 /// -284 /// This informs all active connections that the local address may have changed (e.g., due to a -285 /// network interface change), triggering liveness checks and recovery procedures on each -286 /// connection without requiring a socket rebind. -287 /// -288 /// Unlike [`Self::rebind`], this does not change the underlying socket. Use this when the -289 /// network topology changes but the socket remains valid (e.g., if bound to the unspecified -290 /// address and switching from WiFi to cellular, or when the local IP address changes). -291 /// -292 /// The optional `hint` allows callers to indicate which paths may still be recoverable after -293 /// the network change. If `None`, all paths are assumed to be non-recoverable. For client-side -294 /// multipath connections, unrecoverable paths will be closed and replaced with new paths to -295 /// the same remote addresses. -296 pub fn handle_network_change(&self, hint: Option<Arc<dyn NetworkChangeHint + Sync + Send>>) { -297 let mut inner = self.inner.state.lock().unwrap(); -298 for sender in inner.recv_state.connections.senders.values() { -299 // Ignoring errors from dropped connections -300 let _ = sender.send(ConnectionEvent::LocalAddressChanged(hint.clone())); -301 } -302 if let Some(driver) = inner.driver.take() { -303 driver.wake(); -304 } -305 } -306 -307 /// Replace the server configuration, affecting new incoming connections only +53/// An endpoint corresponds to a single UDP socket, may host many connections, and may act as both +54/// client and server for different connections. +55/// +56/// May be cloned to obtain another handle to the same endpoint. +57#[derive(Debug, Clone)] +58pub struct Endpoint { +59 pub(crate) inner: EndpointRef, +60 runtime: Arc<dyn Runtime>, +61} +62 +63impl Endpoint { +64 /// Helper to construct an endpoint for use with outgoing connections only +65 /// +66 /// Note that `addr` is the *local* address to bind to, which should usually be a wildcard +67 /// address like `0.0.0.0:0` or `[::]:0`, which allow communication with any reachable IPv4 or +68 /// IPv6 address respectively from an OS-assigned port. +69 /// +70 /// If an IPv6 address is provided, attempts to make the socket dual-stack so as to allow +71 /// communication with both IPv4 and IPv6 addresses. As such, calling `Endpoint::client` with +72 /// the address `[::]:0` is a reasonable default to maximize the ability to connect to other +73 /// address. For example: +74 /// +75 /// ``` +76 /// noq::Endpoint::client((std::net::Ipv6Addr::UNSPECIFIED, 0).into()); +77 /// ``` +78 /// +79 /// Some environments may not allow creation of dual-stack sockets, in which case an IPv6 +80 /// client will only be able to connect to IPv6 servers. An IPv4 client is never dual-stack. +81 #[cfg(all( +82 not(wasm_browser), +83 any(feature = "runtime-tokio", feature = "runtime-smol"), +84 any(feature = "aws-lc-rs", feature = "ring"), // `EndpointConfig::default()` is only available with these +85 ))] +86 pub fn client(addr: SocketAddr) -> io::Result<Self> { +87 let socket = Socket::new(Domain::for_address(addr), Type::DGRAM, Some(Protocol::UDP))?; +88 if addr.is_ipv6() +89 && let Err(e) = socket.set_only_v6(false) +90 { +91 tracing::debug!(%e, "unable to make socket dual-stack"); +92 } +93 socket.bind(&addr.into())?; +94 let runtime = +95 default_runtime().ok_or_else(|| io::Error::other("no async runtime found"))?; +96 Self::new_with_abstract_socket( +97 EndpointConfig::default(), +98 None, +99 runtime.wrap_udp_socket(socket.into())?, +100 runtime, +101 ) +102 } +103 +104 /// Returns relevant stats from this Endpoint +105 pub fn stats(&self) -> EndpointStats { +106 self.inner.state.lock().unwrap().stats +107 } +108 +109 /// Helper to construct an endpoint for use with both incoming and outgoing connections +110 /// +111 /// Note that `addr` is the *local* address to bind to, which should usually be a wildcard +112 /// address like `0.0.0.0:0` or `[::]:0`, which allow communication with any reachable IPv4 or +113 /// IPv6 address respectively from an OS-assigned port. +114 /// +115 /// If an IPv6 address is provided, attempts to make the socket dual-stack so as to allow +116 /// communication with both IPv4 and IPv6 clients. As such, calling `Endpoint::server` with +117 /// the address `[::]:0` is a reasonable default to maximize the ability to accept connections +118 /// from any address. +119 /// +120 /// Some environments may not allow creation of dual-stack sockets, in which case an IPv6 +121 /// server will only be able to accept connections from IPv6 clients. An IPv4 server is never +122 /// dual-stack. +123 #[cfg(all( +124 not(wasm_browser), +125 any(feature = "runtime-tokio", feature = "runtime-smol"), +126 any(feature = "aws-lc-rs", feature = "ring"), // `EndpointConfig::default()` is only available with these +127 ))] +128 pub fn server(config: ServerConfig, addr: SocketAddr) -> io::Result<Self> { +129 let socket = Socket::new(Domain::for_address(addr), Type::DGRAM, Some(Protocol::UDP))?; +130 if addr.is_ipv6() +131 && let Err(e) = socket.set_only_v6(false) +132 { +133 tracing::debug!(%e, "unable to make socket dual-stack"); +134 } +135 socket.bind(&addr.into())?; +136 let runtime = +137 default_runtime().ok_or_else(|| io::Error::other("no async runtime found"))?; +138 Self::new_with_abstract_socket( +139 EndpointConfig::default(), +140 Some(config), +141 runtime.wrap_udp_socket(socket.into())?, +142 runtime, +143 ) +144 } +145 +146 /// Construct an endpoint with arbitrary configuration and socket +147 #[cfg(not(wasm_browser))] +148 pub fn new( +149 config: EndpointConfig, +150 server_config: Option<ServerConfig>, +151 socket: std::net::UdpSocket, +152 runtime: Arc<dyn Runtime>, +153 ) -> io::Result<Self> { +154 let socket = runtime.wrap_udp_socket(socket)?; +155 Self::new_with_abstract_socket(config, server_config, socket, runtime) +156 } +157 +158 /// Construct an endpoint with arbitrary configuration and pre-constructed abstract socket +159 /// +160 /// Useful when `socket` has additional state (e.g. sidechannels) attached for which shared +161 /// ownership is needed. +162 pub fn new_with_abstract_socket( +163 config: EndpointConfig, +164 server_config: Option<ServerConfig>, +165 socket: Box<dyn AsyncUdpSocket>, +166 runtime: Arc<dyn Runtime>, +167 ) -> io::Result<Self> { +168 let addr = socket.local_addr()?; +169 let allow_mtud = !socket.may_fragment(); +170 let rc = EndpointRef::new( +171 socket, +172 proto::Endpoint::new(Arc::new(config), server_config.map(Arc::new), allow_mtud), +173 addr.is_ipv6(), +174 runtime.clone(), +175 ); +176 let driver = EndpointDriver(rc.clone()); +177 runtime.spawn(Box::pin( +178 async { +179 if let Err(e) = driver.await { +180 tracing::error!("I/O error: {}", e); +181 } +182 } +183 .instrument(Span::current()), +184 )); +185 Ok(Self { inner: rc, runtime }) +186 } +187 +188 /// Get the next incoming connection attempt from a client +189 /// +190 /// Yields [`Incoming`]s, or `None` if the endpoint is [`close`](Self::close)d. [`Incoming`] +191 /// can be `await`ed to obtain the final [`Connection`](crate::Connection), or used to e.g. +192 /// filter connection attempts or force address validation, or converted into an intermediate +193 /// `Connecting` future which can be used to e.g. send 0.5-RTT data. +194 pub fn accept(&self) -> Accept<'_> { +195 Accept { +196 endpoint: self, +197 notify: self.inner.shared.incoming.notified(), +198 } +199 } +200 +201 /// Set the client configuration used by `connect` +202 pub fn set_default_client_config(&self, config: ClientConfig) { +203 self.inner.0.state.lock().unwrap().default_client_config = Some(config); +204 } +205 +206 /// Connect to a remote endpoint +207 /// +208 /// `server_name` must be covered by the certificate presented by the server. This prevents a +209 /// connection from being intercepted by an attacker with a valid certificate for some other +210 /// server. +211 /// +212 /// May fail immediately due to configuration errors, or in the future if the connection could +213 /// not be established. +214 pub fn connect(&self, addr: SocketAddr, server_name: &str) -> Result<Connecting, ConnectError> { +215 let Some(config) = self +216 .inner +217 .0 +218 .state +219 .lock() +220 .unwrap() +221 .default_client_config +222 .clone() +223 else { +224 return Err(ConnectError::NoDefaultClientConfig); +225 }; +226 +227 self.connect_with(config, addr, server_name) +228 } +229 +230 /// Connect to a remote endpoint using a custom configuration. +231 /// +232 /// See [`connect()`] for details. +233 /// +234 /// [`connect()`]: Endpoint::connect +235 pub fn connect_with( +236 &self, +237 config: ClientConfig, +238 addr: SocketAddr, +239 server_name: &str, +240 ) -> Result<Connecting, ConnectError> { +241 let mut endpoint = self.inner.state.lock().unwrap(); +242 if endpoint.driver_lost || endpoint.recv_state.connections.close.is_some() { +243 return Err(ConnectError::EndpointStopping); +244 } +245 if addr.is_ipv6() && !endpoint.ipv6 { +246 return Err(ConnectError::InvalidRemoteAddress(addr)); +247 } +248 let addr = if endpoint.ipv6 { +249 SocketAddr::V6(ensure_ipv6(addr)) +250 } else { +251 addr +252 }; +253 +254 let (ch, conn) = endpoint +255 .inner +256 .connect(self.runtime.now(), config, addr, server_name)?; +257 +258 let sender = endpoint.socket.create_sender(); +259 endpoint.stats.outgoing_handshakes += 1; +260 Ok(endpoint +261 .recv_state +262 .connections +263 .insert(ch, conn, sender, self.runtime.clone())) +264 } +265 +266 /// Switch to a new UDP socket +267 /// +268 /// See [`Endpoint::rebind_abstract()`] for details. +269 #[cfg(not(wasm_browser))] +270 pub fn rebind(&self, socket: std::net::UdpSocket) -> io::Result<()> { +271 self.rebind_abstract(self.runtime.wrap_udp_socket(socket)?) +272 } +273 +274 /// Switch to a new UDP socket +275 /// +276 /// Allows the endpoint's address to be updated live, affecting all active connections. Incoming +277 /// connections and connections to servers unreachable from the new address will be lost. +278 /// +279 /// On error, the old UDP socket is retained. +280 pub fn rebind_abstract(&self, socket: Box<dyn AsyncUdpSocket>) -> io::Result<()> { +281 let addr = socket.local_addr()?; +282 let mut inner = self.inner.state.lock().unwrap(); +283 inner.prev_socket = Some(mem::replace(&mut inner.socket, socket)); +284 inner.ipv6 = addr.is_ipv6(); +285 +286 // Update connection socket references +287 for sender in inner.recv_state.connections.senders.values() { +288 // Ignoring errors from dropped connections +289 let _ = sender.send(ConnectionEvent::Rebind(inner.socket.create_sender())); +290 } +291 if let Some(driver) = inner.driver.take() { +292 // Ensure the driver can register for wake-ups from the new socket +293 driver.wake(); +294 } +295 +296 Ok(()) +297 } +298 +299 /// Notify connections that the local network address has changed. +300 /// +301 /// This informs all active connections that the local address may have changed (e.g., due to a +302 /// network interface change), triggering liveness checks and recovery procedures on each +303 /// connection without requiring a socket rebind. +304 /// +305 /// Unlike [`Self::rebind`], this does not change the underlying socket. Use this when the +306 /// network topology changes but the socket remains valid (e.g., if bound to the unspecified +307 /// address and switching from WiFi to cellular, or when the local IP address changes). 308 /// -309 /// Useful for e.g. refreshing TLS certificates without disrupting existing connections. -310 pub fn set_server_config(&self, server_config: Option<ServerConfig>) { -311 self.inner -312 .state -313 .lock() -314 .unwrap() -315 .inner -316 .set_server_config(server_config.map(Arc::new)) -317 } -318 -319 /// Get the local `SocketAddr` the underlying socket is bound to -320 pub fn local_addr(&self) -> io::Result<SocketAddr> { -321 self.inner.state.lock().unwrap().socket.local_addr() +309 /// The optional `hint` allows callers to indicate which paths may still be recoverable after +310 /// the network change. If `None`, all paths are assumed to be non-recoverable. For client-side +311 /// multipath connections, unrecoverable paths will be closed and replaced with new paths to +312 /// the same remote addresses. +313 pub fn handle_network_change(&self, hint: Option<Arc<dyn NetworkChangeHint + Sync + Send>>) { +314 let mut inner = self.inner.state.lock().unwrap(); +315 for sender in inner.recv_state.connections.senders.values() { +316 // Ignoring errors from dropped connections +317 let _ = sender.send(ConnectionEvent::LocalAddressChanged(hint.clone())); +318 } +319 if let Some(driver) = inner.driver.take() { +320 driver.wake(); +321 } 322 } 323 -324 /// Get the number of connections that are currently open -325 pub fn open_connections(&self) -> usize { -326 self.inner.state.lock().unwrap().inner.open_connections() -327 } -328 -329 /// Close all of this endpoint's connections immediately and cease accepting new connections. -330 /// -331 /// See [`Connection::close()`] for details. -332 /// -333 /// [`Connection::close()`]: crate::Connection::close -334 pub fn close(&self, error_code: VarInt, reason: &[u8]) { -335 let reason = Bytes::copy_from_slice(reason); -336 let mut endpoint = self.inner.state.lock().unwrap(); -337 endpoint.recv_state.connections.close = Some((error_code, reason.clone())); -338 for sender in endpoint.recv_state.connections.senders.values() { -339 // Ignoring errors from dropped connections -340 let _ = sender.send(ConnectionEvent::Close { -341 error_code, -342 reason: reason.clone(), -343 }); -344 } -345 self.inner.shared.incoming.notify_waiters(); -346 // Wake the endpoint driver so that it can shutdown if the endpoint is already drained. -347 if let Some(waker) = endpoint.driver.take() { -348 waker.wake(); -349 } -350 } -351 -352 /// Wait for all connections on the endpoint to be cleanly shut down -353 /// -354 /// Waiting for this condition before exiting ensures that a good-faith effort is made to notify -355 /// peers of recent connection closes, whereas exiting immediately could force them to wait out -356 /// the idle timeout period. -357 /// -358 /// Does not proactively close existing connections or cause incoming connections to be -359 /// rejected. Consider calling [`close()`] if that is desired. -360 /// -361 /// [`close()`]: Endpoint::close -362 pub async fn wait_idle(&self) { -363 loop { -364 { -365 let endpoint = &mut *self.inner.state.lock().unwrap(); -366 if endpoint.recv_state.connections.is_empty() { -367 break; -368 } -369 // Construct future while lock is held to avoid race -370 self.inner.shared.idle.notified() -371 } -372 .await; -373 } -374 } -375} -376 -377/// Statistics on [Endpoint] activity -378#[non_exhaustive] -379#[derive(Debug, Default, Copy, Clone)] -380pub struct EndpointStats { -381 /// Cumulative number of Quic handshakes accepted by this [Endpoint] -382 pub accepted_handshakes: u64, -383 /// Cumulative number of Quic handshakes sent from this [Endpoint] -384 pub outgoing_handshakes: u64, -385 /// Cumulative number of Quic handshakes refused on this [Endpoint] -386 pub refused_handshakes: u64, -387 /// Cumulative number of Quic handshakes ignored on this [Endpoint] -388 pub ignored_handshakes: u64, -389} -390 -391/// A future that drives IO on an endpoint -392/// -393/// This task functions as the switch point between the UDP socket object and the -394/// `Endpoint` responsible for routing datagrams to their owning `Connection`. -395/// In order to do so, it also facilitates the exchange of different types of events -396/// flowing between the `Endpoint` and the tasks managing `Connection`s. As such, -397/// running this task is necessary to keep the endpoint's connections running. -398/// -399/// `EndpointDriver` futures terminate when all clones of the `Endpoint` have been dropped, or when -400/// an I/O error occurs. -401#[must_use = "endpoint drivers must be spawned for I/O to occur"] -402#[derive(Debug)] -403pub(crate) struct EndpointDriver(pub(crate) EndpointRef); -404 -405impl Future for EndpointDriver { -406 type Output = Result<(), io::Error>; +324 /// Replace the server configuration, affecting new incoming connections only +325 /// +326 /// Useful for e.g. refreshing TLS certificates without disrupting existing connections. +327 pub fn set_server_config(&self, server_config: Option<ServerConfig>) { +328 self.inner +329 .state +330 .lock() +331 .unwrap() +332 .inner +333 .set_server_config(server_config.map(Arc::new)) +334 } +335 +336 /// Get the local `SocketAddr` the underlying socket is bound to +337 pub fn local_addr(&self) -> io::Result<SocketAddr> { +338 self.inner.state.lock().unwrap().socket.local_addr() +339 } +340 +341 /// Get the number of connections that are currently open +342 pub fn open_connections(&self) -> usize { +343 self.inner.state.lock().unwrap().inner.open_connections() +344 } +345 +346 /// Close all of this endpoint's connections immediately and cease accepting new connections. +347 /// +348 /// See [`Connection::close()`] for details. +349 /// +350 /// [`Connection::close()`]: crate::Connection::close +351 pub fn close(&self, error_code: VarInt, reason: &[u8]) { +352 let reason = Bytes::copy_from_slice(reason); +353 let mut endpoint = self.inner.state.lock().unwrap(); +354 endpoint.recv_state.connections.close = Some((error_code, reason.clone())); +355 for sender in endpoint.recv_state.connections.senders.values() { +356 // Ignoring errors from dropped connections +357 let _ = sender.send(ConnectionEvent::Close { +358 error_code, +359 reason: reason.clone(), +360 }); +361 } +362 self.inner.shared.incoming.notify_waiters(); +363 // Wake the endpoint driver so that it can shutdown if the endpoint is already drained. +364 if let Some(waker) = endpoint.driver.take() { +365 waker.wake(); +366 } +367 } +368 +369 /// Wait for all connections on the endpoint to be cleanly shut down +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 +379 pub async fn wait_idle(&self) { +380 loop { +381 { +382 let endpoint = &mut *self.inner.state.lock().unwrap(); +383 if endpoint.recv_state.connections.is_empty() { +384 break; +385 } +386 // Construct future while lock is held to avoid race +387 self.inner.shared.idle.notified() +388 } +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 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { -409 let mut endpoint = self.0.state.lock().unwrap(); -410 if endpoint.driver.is_none() { -411 endpoint.driver = Some(cx.waker().clone()); -412 } -413 -414 let now = endpoint.runtime.now(); -415 let mut keep_going = false; -416 keep_going |= endpoint.drive_recv(cx, now)?; -417 keep_going |= endpoint.handle_events(cx, &self.0.shared); -418 -419 if !endpoint.recv_state.incoming.is_empty() { -420 self.0.shared.incoming.notify_waiters(); -421 } -422 -423 // Stop the driver if either: -424 // - all `Endpoint` structs are dropped and all connections are drained, -425 // - or `Endpoint::close` has been called and all connections are drained. -426 if endpoint.recv_state.connections.is_empty() -427 && (endpoint.ref_count == 0 || endpoint.recv_state.connections.close.is_some()) -428 { -429 trace!("endpoint driver stopping"); -430 Poll::Ready(Ok(())) -431 } else { -432 drop(endpoint); -433 // If there is more work to do schedule the endpoint task again. -434 // `wake_by_ref()` is called outside the lock to minimize -435 // lock contention on a multithreaded runtime. -436 if keep_going { -437 cx.waker().wake_by_ref(); -438 } -439 Poll::Pending -440 } -441 } -442} -443 -444impl Drop for EndpointDriver { -445 fn drop(&mut self) { -446 let mut endpoint = self.0.state.lock().unwrap(); -447 endpoint.driver_lost = true; -448 self.0.shared.incoming.notify_waiters(); -449 // Drop all outgoing channels, signaling the termination of the endpoint to the associated -450 // connections. -451 endpoint.recv_state.connections.senders.clear(); -452 } -453} -454 -455#[derive(Debug)] -456pub(crate) struct EndpointInner { -457 pub(crate) state: Mutex<State>, -458 pub(crate) shared: Shared, -459} -460 -461impl EndpointInner { -462 pub(crate) fn accept( -463 &self, -464 incoming: proto::Incoming, -465 server_config: Option<Arc<ServerConfig>>, -466 ) -> Result<Connecting, ConnectionError> { -467 let mut state = self.state.lock().unwrap(); -468 let mut response_buffer = Vec::new(); -469 let now = state.runtime.now(); -470 match state -471 .inner -472 .accept(incoming, now, &mut response_buffer, server_config) -473 { -474 Ok((handle, conn)) => { -475 state.stats.accepted_handshakes += 1; -476 let sender = state.socket.create_sender(); -477 let runtime = state.runtime.clone(); -478 Ok(state -479 .recv_state -480 .connections -481 .insert(handle, conn, sender, runtime)) -482 } -483 Err(error) => { -484 if let Some(transmit) = error.response { -485 respond(transmit, &response_buffer, &mut state.sender); -486 } -487 Err(error.cause) -488 } -489 } -490 } -491 -492 pub(crate) fn refuse(&self, incoming: proto::Incoming) { -493 let mut state = self.state.lock().unwrap(); -494 state.stats.refused_handshakes += 1; -495 let mut response_buffer = Vec::new(); -496 let transmit = state.inner.refuse(incoming, &mut response_buffer); -497 respond(transmit, &response_buffer, &mut state.sender); -498 } -499 -500 pub(crate) fn retry(&self, incoming: proto::Incoming) -> Result<(), proto::RetryError> { -501 let mut state = self.state.lock().unwrap(); -502 let mut response_buffer = Vec::new(); -503 let transmit = state.inner.retry(incoming, &mut response_buffer)?; -504 respond(transmit, &response_buffer, &mut state.sender); -505 Ok(()) -506 } -507 -508 pub(crate) fn ignore(&self, incoming: proto::Incoming) { -509 let mut state = self.state.lock().unwrap(); -510 state.stats.ignored_handshakes += 1; -511 state.inner.ignore(incoming); -512 } -513} -514 -515#[derive(Debug)] -516pub(crate) struct State { -517 socket: Box<dyn AsyncUdpSocket>, -518 sender: Pin<Box<dyn UdpSender>>, -519 /// During an active migration, abandoned_socket receives traffic -520 /// until the first packet arrives on the new socket. -521 prev_socket: Option<Box<dyn AsyncUdpSocket>>, -522 inner: proto::Endpoint, -523 recv_state: RecvState, -524 driver: Option<Waker>, -525 ipv6: bool, -526 events: mpsc::UnboundedReceiver<(ConnectionHandle, EndpointEvent)>, -527 /// Number of live handles that can be used to initiate or handle I/O; excludes the driver -528 ref_count: usize, -529 driver_lost: bool, -530 runtime: Arc<dyn Runtime>, -531 stats: EndpointStats, -532 default_client_config: Option<ClientConfig>, -533} -534 -535#[derive(Debug)] -536pub(crate) struct Shared { -537 incoming: Notify, -538 idle: Notify, -539} -540 -541impl State { -542 fn drive_recv(&mut self, cx: &mut Context<'_>, now: Instant) -> Result<bool, io::Error> { -543 let get_time = || self.runtime.now(); -544 self.recv_state.recv_limiter.start_cycle(get_time); -545 if let Some(socket) = &mut self.prev_socket { -546 // We don't care about the `PollProgress` from old sockets. -547 let poll_res = self.recv_state.poll_socket( -548 cx, -549 &mut self.inner, -550 &mut **socket, -551 &mut self.sender, -552 &*self.runtime, -553 now, -554 ); -555 if poll_res.is_err() { -556 self.prev_socket = None; -557 } -558 }; -559 let poll_res = self.recv_state.poll_socket( -560 cx, -561 &mut self.inner, -562 &mut *self.socket, -563 &mut self.sender, -564 &*self.runtime, -565 now, -566 ); -567 self.recv_state.recv_limiter.finish_cycle(get_time); -568 let poll_res = poll_res?; -569 if poll_res.received_connection_packet { -570 // Traffic has arrived on self.socket, therefore there is no need for the abandoned -571 // one anymore. TODO: Account for multiple outgoing connections. -572 self.prev_socket = None; -573 } -574 Ok(poll_res.keep_going) -575 } -576 -577 fn handle_events(&mut self, cx: &mut Context<'_>, shared: &Shared) -> bool { -578 for _ in 0..IO_LOOP_BOUND { -579 let (ch, event) = match self.events.poll_recv(cx) { -580 Poll::Ready(Some(x)) => x, -581 Poll::Ready(None) => unreachable!("EndpointInner owns one sender"), -582 Poll::Pending => { -583 return false; -584 } -585 }; -586 -587 if event.is_drained() { -588 self.recv_state.connections.senders.remove(&ch); -589 if self.recv_state.connections.is_empty() { -590 shared.idle.notify_waiters(); -591 } -592 } -593 let Some(event) = self.inner.handle_event(ch, event) else { -594 continue; -595 }; -596 // Ignoring errors from dropped connections that haven't yet been cleaned up -597 let _ = self -598 .recv_state -599 .connections -600 .senders -601 .get_mut(&ch) -602 .unwrap() -603 .send(ConnectionEvent::Proto(event)); -604 } -605 -606 true -607 } -608} -609 -610impl Drop for State { -611 fn drop(&mut self) { -612 for incoming in self.recv_state.incoming.drain(..) { -613 self.inner.ignore(incoming); -614 } -615 } -616} -617 -618fn respond( -619 transmit: proto::Transmit, -620 response_buffer: &[u8], -621 sender: &mut Pin<Box<dyn UdpSender>>, -622) { -623 // Send if there's kernel buffer space; otherwise, drop it -624 // -625 // As an endpoint-generated packet, we know this is an -626 // immediate, stateless response to an unconnected peer, -627 // one of: -628 // -629 // - A version negotiation response due to an unknown version -630 // - A `CLOSE` due to a malformed or unwanted connection attempt -631 // - A stateless reset due to an unrecognized connection -632 // - A `Retry` packet due to a connection attempt when -633 // `use_retry` is set -634 // -635 // In each case, a well-behaved peer can be trusted to retry a -636 // few times, which is guaranteed to produce the same response -637 // from us. Repeated failures might at worst cause a peer's new -638 // connection attempt to time out, which is acceptable if we're -639 // under such heavy load that there's never room for this code -640 // to transmit. This is morally equivalent to the packet getting -641 // lost due to congestion further along the link, which -642 // similarly relies on peer retries for recovery. -643 -644 // Copied from rust 1.85's std::task::Waker::noop() implementation for backwards compatibility -645 const NOOP: RawWaker = { -646 const VTABLE: RawWakerVTable = RawWakerVTable::new( -647 // Cloning just returns a new no-op raw waker -648 |_| NOOP, -649 // `wake` does nothing -650 |_| {}, -651 // `wake_by_ref` does nothing -652 |_| {}, -653 // Dropping does nothing as we don't allocate anything -654 |_| {}, -655 ); -656 RawWaker::new(std::ptr::null(), &VTABLE) -657 }; -658 // SAFETY: Copied from rust stdlib, the NOOP waker is thread-safe and doesn't violate the RawWakerVTable contract, -659 // it doesn't access the data pointer at all. -660 let waker = unsafe { Waker::from_raw(NOOP) }; -661 let mut cx = Context::from_waker(&waker); -662 _ = sender.as_mut().poll_send( -663 &udp_transmit(&transmit, &response_buffer[..transmit.size]), -664 &mut cx, -665 ); -666} -667 -668#[inline] -669fn proto_ecn(ecn: udp::EcnCodepoint) -> proto::EcnCodepoint { -670 match ecn { -671 udp::EcnCodepoint::Ect0 => proto::EcnCodepoint::Ect0, -672 udp::EcnCodepoint::Ect1 => proto::EcnCodepoint::Ect1, -673 udp::EcnCodepoint::Ce => proto::EcnCodepoint::Ce, -674 } -675} -676 -677#[derive(Debug)] -678struct ConnectionSet { -679 /// Senders for communicating with the endpoint's connections -680 senders: FxHashMap<ConnectionHandle, mpsc::UnboundedSender<ConnectionEvent>>, -681 /// Stored to give out clones to new ConnectionInners -682 sender: mpsc::UnboundedSender<(ConnectionHandle, EndpointEvent)>, -683 /// Set if the endpoint has been manually closed -684 close: Option<(VarInt, Bytes)>, -685} -686 -687impl ConnectionSet { -688 fn insert( -689 &mut self, -690 handle: ConnectionHandle, -691 conn: proto::Connection, -692 sender: Pin<Box<dyn UdpSender>>, -693 runtime: Arc<dyn Runtime>, -694 ) -> Connecting { -695 let (send, recv) = mpsc::unbounded_channel(); -696 if let Some((error_code, ref reason)) = self.close { -697 send.send(ConnectionEvent::Close { -698 error_code, -699 reason: reason.clone(), -700 }) -701 .unwrap(); -702 } -703 self.senders.insert(handle, send); -704 Connecting::new(handle, conn, self.sender.clone(), recv, sender, runtime) -705 } -706 -707 fn is_empty(&self) -> bool { -708 self.senders.is_empty() -709 } -710} -711 -712pub(crate) fn ensure_ipv6(x: SocketAddr) -> SocketAddrV6 { -713 match x { -714 SocketAddr::V6(x) => x, -715 SocketAddr::V4(x) => SocketAddrV6::new(x.ip().to_ipv6_mapped(), x.port(), 0, 0), -716 } -717} -718 -719pin_project! { -720 /// Future produced by [`Endpoint::accept`] -721 pub struct Accept<'a> { -722 endpoint: &'a Endpoint, -723 #[pin] -724 notify: Notified<'a>, -725 } -726} -727 -728impl Future for Accept<'_> { -729 type Output = Option<Incoming>; -730 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { -731 let mut this = self.project(); -732 let mut endpoint = this.endpoint.inner.state.lock().unwrap(); -733 if endpoint.driver_lost { -734 return Poll::Ready(None); -735 } -736 if let Some(incoming) = endpoint.recv_state.incoming.pop_front() { -737 // Release the mutex lock on endpoint so cloning it doesn't deadlock -738 drop(endpoint); -739 let incoming = Incoming::new(incoming, this.endpoint.inner.clone()); -740 return Poll::Ready(Some(incoming)); -741 } -742 if endpoint.recv_state.connections.close.is_some() { -743 return Poll::Ready(None); -744 } -745 loop { -746 match this.notify.as_mut().poll(ctx) { -747 // `state` lock ensures we didn't race with readiness -748 Poll::Pending => return Poll::Pending, -749 // Spurious wakeup, get a new future -750 Poll::Ready(()) => this -751 .notify -752 .set(this.endpoint.inner.shared.incoming.notified()), -753 } -754 } -755 } -756} -757 -758#[derive(Debug)] -759pub(crate) struct EndpointRef(Arc<EndpointInner>); -760 -761impl EndpointRef { -762 pub(crate) fn new( -763 socket: Box<dyn AsyncUdpSocket>, -764 inner: proto::Endpoint, -765 ipv6: bool, -766 runtime: Arc<dyn Runtime>, -767 ) -> Self { -768 let (sender, events) = mpsc::unbounded_channel(); -769 let recv_state = RecvState::new(sender, socket.max_receive_segments(), &inner); -770 let sender = socket.create_sender(); -771 Self(Arc::new(EndpointInner { -772 shared: Shared { -773 incoming: Notify::new(), -774 idle: Notify::new(), -775 }, -776 state: Mutex::new(State { -777 socket, -778 sender, -779 prev_socket: None, -780 inner, -781 ipv6, -782 events, -783 driver: None, -784 ref_count: 0, -785 driver_lost: false, -786 recv_state, -787 runtime, -788 stats: EndpointStats::default(), -789 default_client_config: None, -790 }), -791 })) -792 } -793} -794 -795impl Clone for EndpointRef { -796 fn clone(&self) -> Self { -797 self.0.state.lock().unwrap().ref_count += 1; -798 Self(self.0.clone()) -799 } -800} -801 -802impl Drop for EndpointRef { -803 fn drop(&mut self) { -804 let endpoint = &mut *self.0.state.lock().unwrap(); -805 if let Some(x) = endpoint.ref_count.checked_sub(1) { -806 endpoint.ref_count = x; -807 if x == 0 { -808 // If the driver is about to be on its own, ensure it can shut down if the last -809 // connection is gone. -810 if let Some(task) = endpoint.driver.take() { -811 task.wake(); -812 } -813 } -814 } -815 } -816} -817 -818impl std::ops::Deref for EndpointRef { -819 type Target = EndpointInner; -820 fn deref(&self) -> &Self::Target { -821 &self.0 -822 } -823} -824 -825/// State directly involved in handling incoming packets -826struct RecvState { -827 incoming: VecDeque<proto::Incoming>, -828 connections: ConnectionSet, -829 recv_buf: Box<[u8]>, -830 recv_limiter: WorkLimiter, -831} -832 -833impl RecvState { -834 fn new( -835 sender: mpsc::UnboundedSender<(ConnectionHandle, EndpointEvent)>, -836 max_receive_segments: NonZeroUsize, -837 endpoint: &proto::Endpoint, -838 ) -> Self { -839 let recv_buf = vec![ -840 0; -841 endpoint.config().get_max_udp_payload_size().min(64 * 1024) as usize -842 * max_receive_segments.get() -843 * BATCH_SIZE -844 ]; -845 Self { -846 connections: ConnectionSet { -847 senders: FxHashMap::default(), -848 sender, -849 close: None, -850 }, -851 incoming: VecDeque::new(), -852 recv_buf: recv_buf.into(), -853 recv_limiter: WorkLimiter::new(RECV_TIME_BOUND), -854 } -855 } -856 -857 fn poll_socket( -858 &mut self, -859 cx: &mut Context<'_>, -860 endpoint: &mut proto::Endpoint, -861 socket: &mut dyn AsyncUdpSocket, -862 sender: &mut Pin<Box<dyn UdpSender>>, -863 runtime: &dyn Runtime, -864 now: Instant, -865 ) -> Result<PollProgress, io::Error> { -866 let mut received_connection_packet = false; -867 let mut metas = [RecvMeta::default(); BATCH_SIZE]; -868 let mut iovs: [IoSliceMut<'_>; BATCH_SIZE] = { -869 let mut bufs = self -870 .recv_buf -871 .chunks_mut(self.recv_buf.len() / BATCH_SIZE) -872 .map(IoSliceMut::new); +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>; +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} +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 // expect() safe as self.recv_buf is chunked into BATCH_SIZE items -875 // and iovs will be of size BATCH_SIZE, thus from_fn is called -876 // exactly BATCH_SIZE times. -877 std::array::from_fn(|_| bufs.next().expect("BATCH_SIZE elements")) -878 }; -879 loop { -880 match socket.poll_recv(cx, &mut iovs, &mut metas) { -881 Poll::Ready(Ok(msgs)) => { -882 self.recv_limiter.record_work(msgs); -883 for (meta, buf) in metas.iter().zip(iovs.iter()).take(msgs) { -884 let mut data: BytesMut = buf[0..meta.len].into(); -885 while !data.is_empty() { -886 let buf = data.split_to(meta.stride.min(data.len())); -887 let mut response_buffer = Vec::new(); -888 let addresses = FourTuple::new(meta.addr, meta.dst_ip); -889 match endpoint.handle( -890 now, -891 addresses, -892 meta.ecn.map(proto_ecn), -893 buf, -894 &mut response_buffer, -895 ) { -896 Some(DatagramEvent::NewConnection(incoming)) => { -897 if self.connections.close.is_none() { -898 self.incoming.push_back(incoming); -899 } else { -900 let transmit = -901 endpoint.refuse(incoming, &mut response_buffer); -902 respond(transmit, &response_buffer, sender); -903 } -904 } -905 Some(DatagramEvent::ConnectionEvent(handle, event)) => { -906 // Ignoring errors from dropped connections that haven't yet been cleaned up -907 received_connection_packet = true; -908 let _ = self -909 .connections -910 .senders -911 .get_mut(&handle) -912 .unwrap() -913 .send(ConnectionEvent::Proto(event)); -914 } -915 Some(DatagramEvent::Response(transmit)) => { -916 respond(transmit, &response_buffer, sender); -917 } -918 None => {} -919 } -920 } -921 } -922 } -923 Poll::Pending => { -924 return Ok(PollProgress { -925 received_connection_packet, -926 keep_going: false, -927 }); -928 } -929 // Ignore ECONNRESET as it's undefined in QUIC and may be injected by an -930 // attacker -931 Poll::Ready(Err(ref e)) if e.kind() == io::ErrorKind::ConnectionReset => { -932 continue; -933 } -934 Poll::Ready(Err(e)) => { -935 return Err(e); -936 } -937 } -938 if !self.recv_limiter.allow_work(|| runtime.now()) { -939 return Ok(PollProgress { -940 received_connection_packet, -941 keep_going: true, -942 }); -943 } -944 } -945 } -946} -947 -948impl fmt::Debug for RecvState { -949 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -950 f.debug_struct("RecvState") -951 .field("incoming", &self.incoming) -952 .field("connections", &self.connections) -953 // recv_buf too large -954 .field("recv_limiter", &self.recv_limiter) -955 .finish_non_exhaustive() -956 } -957} -958 -959#[derive(Default)] -960struct PollProgress { -961 /// Whether a datagram was routed to an existing connection -962 received_connection_packet: bool, -963 /// Whether datagram handling was interrupted early by the work limiter for fairness -964 keep_going: bool, -965}
    \ No newline at end of file +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 diff --git a/pr/451/docs/src/noq/event_stream.rs.html b/pr/451/docs/src/noq/event_stream.rs.html index 3ad2b9203..0e9f5811e 100644 --- a/pr/451/docs/src/noq/event_stream.rs.html +++ b/pr/451/docs/src/noq/event_stream.rs.html @@ -9,8 +9,8 @@ 9use proto::n0_nat_traversal; 10use thiserror::Error; 11use tokio::sync::{broadcast, watch}; -12use tokio_stream::wrappers::{BroadcastStream, WatchStream, errors::BroadcastStreamRecvError}; -13use tokio_stream::Stream; +12use tokio_stream::Stream; +13use tokio_stream::wrappers::{BroadcastStream, WatchStream, errors::BroadcastStreamRecvError}; 14 15/// The receiver lagged too far behind. 16/// diff --git a/pr/451/docs/src/noq/incoming.rs.html b/pr/451/docs/src/noq/incoming.rs.html index e632f6314..d36dcf1e9 100644 --- a/pr/451/docs/src/noq/incoming.rs.html +++ b/pr/451/docs/src/noq/incoming.rs.html @@ -6,7 +6,7 @@ 6 task::{Context, Poll}, 7}; 8 -9use proto::{ConnectionError, ConnectionId, ServerConfig}; +9use proto::{ConnectionError, ConnectionId, DecryptedInitial, ServerConfig}; 10use thiserror::Error; 11 12use crate::{ @@ -103,7 +103,7 @@ 103 /// 104 /// This clones and decrypts the packet payload (~1200 bytes). 105 /// Can be used to extract information from the TLS ClientHello without completing the handshake. -106 pub fn decrypt(&self) -> Option<proto::DecryptedInitial> { +106 pub fn decrypt(&self) -> Option<DecryptedInitial> { 107 self.0.as_ref()?.inner.decrypt() 108 } 109} diff --git a/pr/451/docs/src/noq/lib.rs.html b/pr/451/docs/src/noq/lib.rs.html index 7e9aeb412..92dbf5471 100644 --- a/pr/451/docs/src/noq/lib.rs.html +++ b/pr/451/docs/src/noq/lib.rs.html @@ -61,82 +61,83 @@ 61#[cfg(feature = "bloom")] 62pub use proto::BloomTokenLog; 63pub use proto::{ -64 AckFrequencyConfig, ApplicationClose, Chunk, ClientConfig, ClosedStream, ConfigError, -65 ConnectError, ConnectionClose, ConnectionError, ConnectionId, ConnectionIdGenerator, -66 ConnectionStats, Dir, EcnCodepoint, EndpointConfig, FrameStats, FrameType, IdleTimeout, -67 InvalidCid, MtuDiscoveryConfig, NetworkChangeHint, NoneTokenLog, NoneTokenStore, PathId, -68 PathStats, ServerConfig, Side, StdSystemTime, StreamId, TimeSource, TokenLog, TokenMemoryCache, -69 TokenReuseError, TokenStore, Transmit, TransportConfig, TransportErrorCode, UdpStats, -70 ValidationTokenConfig, VarInt, VarIntBoundsExceeded, Written, congestion, crypto, -71}; -72#[cfg(feature = "qlog")] -73pub use proto::{QlogConfig, QlogFactory, QlogFileFactory}; -74#[cfg(feature = "rustls")] -75pub use rustls; -76pub use udp; -77 -78pub use crate::connection::{ -79 AcceptBi, AcceptUni, Connecting, Connection, OnClosed, OpenBi, OpenUni, ReadDatagram, -80 SendDatagram, SendDatagramError, WeakConnectionHandle, ZeroRttAccepted, -81}; -82pub use crate::event_stream::{ObservedExternalAddr, Lagged, NatTraversalUpdates, PathEvents}; +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::incoming::{Incoming, IncomingFuture, RetryError}; -85pub use crate::path::{AddressDiscovery, OpenPath, Path, WeakPathHandle}; -86pub use crate::recv_stream::{ -87 ReadError, ReadExactError, ReadToEndError, RecvStream, ResetError, UnorderedRecvStream, -88}; -89#[cfg(feature = "runtime-smol")] -90pub use crate::runtime::SmolRuntime; -91#[cfg(feature = "runtime-tokio")] -92pub use crate::runtime::TokioRuntime; -93#[cfg(any(feature = "runtime-tokio", feature = "runtime-smol"))] -94pub use crate::runtime::default_runtime; -95pub use crate::runtime::{AsyncTimer, AsyncUdpSocket, Runtime, UdpSender}; -96pub use crate::send_stream::{SendStream, Stopped, StoppedError, WriteError}; -97 -98#[cfg(test)] -99mod tests; -100 -101#[derive(Debug)] -102enum ConnectionEvent { -103 Close { -104 error_code: VarInt, -105 reason: bytes::Bytes, -106 }, -107 Proto(proto::ConnectionEvent), -108 Rebind(Pin<Box<dyn UdpSender>>), -109 LocalAddressChanged(Option<Arc<dyn NetworkChangeHint + Sync + Send>>), -110} -111 -112fn udp_transmit<'a>(t: &proto::Transmit, buffer: &'a [u8]) -> udp::Transmit<'a> { -113 udp::Transmit { -114 destination: t.destination, -115 ecn: t.ecn.map(udp_ecn), -116 contents: buffer, -117 segment_size: t.segment_size, -118 src_ip: t.src_ip, -119 } -120} -121 -122fn udp_ecn(ecn: proto::EcnCodepoint) -> udp::EcnCodepoint { -123 match ecn { -124 proto::EcnCodepoint::Ect0 => udp::EcnCodepoint::Ect0, -125 proto::EcnCodepoint::Ect1 => udp::EcnCodepoint::Ect1, -126 proto::EcnCodepoint::Ce => udp::EcnCodepoint::Ce, -127 } -128} -129 -130/// Maximum number of datagrams processed in send/recv calls to make before moving on to other processing -131/// -132/// This helps ensure we don't starve anything when the CPU is slower than the link. -133/// Value is selected by picking a low number which didn't degrade throughput in benchmarks. -134const IO_LOOP_BOUND: usize = 160; -135 -136/// The maximum amount of time that should be spent in `recvmsg()` calls per endpoint iteration -137/// -138/// 50us are chosen so that an endpoint iteration with a 50us sendmsg limit blocks -139/// the runtime for a maximum of about 100us. -140/// Going much lower does not yield any noticeable difference, since a single `recvmmsg` -141/// batch of size 32 was observed to take 30us on some systems. -142const RECV_TIME_BOUND: Duration = Duration::from_micros(50);

    \ No newline at end of file +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 diff --git a/pr/451/docs/src/noq/path.rs.html b/pr/451/docs/src/noq/path.rs.html index 51350e088..f9db9f0fe 100644 --- a/pr/451/docs/src/noq/path.rs.html +++ b/pr/451/docs/src/noq/path.rs.html @@ -1,290 +1,290 @@ path.rs - source

    noq/
    path.rs

    1use std::future::Future;
    -2use std::net::SocketAddr;
    +2use std::net::{IpAddr, SocketAddr};
     3use std::pin::Pin;
     4use std::sync::Arc;
    -5use std::task::{Context, Poll, ready};
    -6use std::time::Duration;
    -7
    -8use proto::{
    -9    ClosePathError, ClosedPath, PathError, PathEvent, PathId, PathStats, PathStatus,
    -10    SetPathStatusError, TransportErrorCode,
    -11};
    -12use tokio::sync::watch;
    -13use tokio_stream::{Stream, wrappers::WatchStream};
    -14
    -15use crate::connection::ConnectionRef;
    -16use crate::{Runtime, WeakConnectionHandle};
    -17
    -18/// Future produced by [`crate::Connection::open_path`]
    -19pub struct OpenPath(OpenPathInner);
    -20
    -21enum OpenPathInner {
    -22    /// Opening a path in underway
    -23    ///
    -24    /// This might fail later on.
    -25    Ongoing {
    -26        opened: WatchStream<Result<(), PathError>>,
    -27        path_id: PathId,
    -28        conn: ConnectionRef,
    -29    },
    -30    /// Opening a path failed immediately
    -31    Rejected {
    -32        /// The error that occurred
    -33        err: PathError,
    -34    },
    -35    /// The path is already open
    -36    Ready {
    -37        path_id: PathId,
    -38        conn: ConnectionRef,
    -39    },
    -40}
    -41
    -42impl OpenPath {
    -43    pub(crate) fn new(
    -44        path_id: PathId,
    -45        opened: watch::Receiver<Result<(), PathError>>,
    -46        conn: ConnectionRef,
    -47    ) -> Self {
    -48        Self(OpenPathInner::Ongoing {
    -49            opened: WatchStream::from_changes(opened),
    -50            path_id,
    -51            conn,
    -52        })
    -53    }
    -54
    -55    pub(crate) fn ready(path_id: PathId, conn: ConnectionRef) -> Self {
    -56        Self(OpenPathInner::Ready { path_id, conn })
    -57    }
    -58
    -59    pub(crate) fn rejected(err: PathError) -> Self {
    -60        Self(OpenPathInner::Rejected { err })
    -61    }
    -62
    -63    /// Returns the path ID of the new path being opened.
    -64    ///
    -65    /// If an error occurred before a path ID was allocated, `None` is returned.  In this
    -66    /// case the future is ready and polling it will immediately yield the error.
    -67    ///
    -68    /// The returned value remains the same for the entire lifetime of this future.
    -69    pub fn path_id(&self) -> Option<PathId> {
    -70        match self.0 {
    -71            OpenPathInner::Ongoing { path_id, .. } => Some(path_id),
    -72            OpenPathInner::Rejected { .. } => None,
    -73            OpenPathInner::Ready { path_id, .. } => Some(path_id),
    -74        }
    -75    }
    -76}
    -77
    -78impl Future for OpenPath {
    -79    type Output = Result<Path, PathError>;
    -80    fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> {
    -81        match self.get_mut().0 {
    -82            OpenPathInner::Ongoing {
    -83                ref mut opened,
    -84                path_id,
    -85                ref mut conn,
    -86            } => match ready!(Pin::new(opened).poll_next(ctx)) {
    -87                Some(value) => {
    -88                    Poll::Ready(value.map(|_| Path::new_unchecked(conn.clone(), path_id)))
    -89                }
    -90                None => {
    -91                    // This only happens if receiving a notification change failed, this means the
    -92                    // sender was dropped. This generally should not happen so we use a transient
    -93                    // error
    -94                    Poll::Ready(Err(PathError::ValidationFailed))
    -95                }
    -96            },
    -97            OpenPathInner::Ready {
    -98                path_id,
    -99                ref mut conn,
    -100            } => Poll::Ready(Ok(Path::new_unchecked(conn.clone(), path_id))),
    -101            OpenPathInner::Rejected { err } => Poll::Ready(Err(err)),
    -102        }
    -103    }
    -104}
    -105
    -106/// An open network transmission within a multipath-enabled connection.
    -107///
    -108/// As long as a [`Path`] or [`WeakPathHandle`] is alive, it is ensured that the
    -109/// [`PathStats`] for this path are not dropped even after the path is abandoned.
    -110///
    -111/// [`WeakPathHandle`]: crate::path::WeakPathHandle
    -112#[derive(Debug)]
    -113pub struct Path {
    -114    id: PathId,
    +5use std::sync::atomic::{AtomicUsize, Ordering};
    +6use std::task::{Context, Poll, ready};
    +7use std::time::Duration;
    +8
    +9use proto::{
    +10    ClosePathError, ClosedPath, PathError, PathEvent, PathId, PathStats, PathStatus,
    +11    SetPathStatusError, TransportErrorCode,
    +12};
    +13use tokio::sync::watch;
    +14use tokio_stream::{Stream, wrappers::WatchStream};
    +15
    +16use crate::connection::ConnectionRef;
    +17use crate::{Runtime, WeakConnectionHandle};
    +18
    +19/// Future produced by [`crate::Connection::open_path`]
    +20pub struct OpenPath(OpenPathInner);
    +21
    +22enum OpenPathInner {
    +23    /// Opening a path in underway
    +24    ///
    +25    /// This might fail later on.
    +26    Ongoing {
    +27        opened: WatchStream<Result<(), PathError>>,
    +28        path_id: PathId,
    +29        conn: ConnectionRef,
    +30    },
    +31    /// Opening a path failed immediately
    +32    Rejected {
    +33        /// The error that occurred
    +34        err: PathError,
    +35    },
    +36    /// The path is already open
    +37    Ready {
    +38        path_id: PathId,
    +39        conn: ConnectionRef,
    +40    },
    +41}
    +42
    +43impl OpenPath {
    +44    pub(crate) fn new(
    +45        path_id: PathId,
    +46        opened: watch::Receiver<Result<(), PathError>>,
    +47        conn: ConnectionRef,
    +48    ) -> Self {
    +49        Self(OpenPathInner::Ongoing {
    +50            opened: WatchStream::from_changes(opened),
    +51            path_id,
    +52            conn,
    +53        })
    +54    }
    +55
    +56    pub(crate) fn ready(path_id: PathId, conn: ConnectionRef) -> Self {
    +57        Self(OpenPathInner::Ready { path_id, conn })
    +58    }
    +59
    +60    pub(crate) fn rejected(err: PathError) -> Self {
    +61        Self(OpenPathInner::Rejected { err })
    +62    }
    +63
    +64    /// Returns the path ID of the new path being opened.
    +65    ///
    +66    /// If an error occurred before a path ID was allocated, `None` is returned.  In this
    +67    /// case the future is ready and polling it will immediately yield the error.
    +68    ///
    +69    /// The returned value remains the same for the entire lifetime of this future.
    +70    pub fn path_id(&self) -> Option<PathId> {
    +71        match self.0 {
    +72            OpenPathInner::Ongoing { path_id, .. } => Some(path_id),
    +73            OpenPathInner::Rejected { .. } => None,
    +74            OpenPathInner::Ready { path_id, .. } => Some(path_id),
    +75        }
    +76    }
    +77}
    +78
    +79impl Future for OpenPath {
    +80    type Output = Result<Path, PathError>;
    +81    fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> {
    +82        match self.get_mut().0 {
    +83            OpenPathInner::Ongoing {
    +84                ref mut opened,
    +85                path_id,
    +86                ref mut conn,
    +87            } => match ready!(Pin::new(opened).poll_next(ctx)) {
    +88                Some(value) => {
    +89                    Poll::Ready(value.map(|_| Path::new_unchecked(conn.clone(), path_id)))
    +90                }
    +91                None => {
    +92                    // This only happens if receiving a notification change failed, this means the
    +93                    // sender was dropped. This generally should not happen so we use a transient
    +94                    // error
    +95                    Poll::Ready(Err(PathError::ValidationFailed))
    +96                }
    +97            },
    +98            OpenPathInner::Ready {
    +99                path_id,
    +100                ref mut conn,
    +101            } => Poll::Ready(Ok(Path::new_unchecked(conn.clone(), path_id))),
    +102            OpenPathInner::Rejected { err } => Poll::Ready(Err(err)),
    +103        }
    +104    }
    +105}
    +106
    +107/// An open network transmission within a multipath-enabled connection.
    +108///
    +109/// As long as a [`Path`] or [`WeakPathHandle`] is alive, it is ensured that the
    +110/// [`PathStats`] for this path are not dropped even after the path is abandoned.
    +111///
    +112/// [`WeakPathHandle`]: crate::path::WeakPathHandle
    +113#[derive(Debug, Clone)]
    +114pub struct Path {
     115    conn: ConnectionRef,
    -116}
    -117
    -118impl Clone for Path {
    -119    fn clone(&self) -> Self {
    -120        self.conn
    -121            .lock_without_waking("Path::clone")
    -122            .increment_path_refs(self.id);
    -123        Self {
    -124            id: self.id,
    -125            conn: self.conn.clone(),
    -126        }
    -127    }
    -128}
    -129
    -130impl Drop for Path {
    -131    fn drop(&mut self) {
    -132        let mut state = self.conn.lock_without_waking("Path::drop");
    -133        state.decrement_path_refs(self.id);
    -134    }
    -135}
    -136
    -137impl Path {
    -138    /// Returns a [`Path`] for a path id, after checking that the path is not closed.
    -139    pub(crate) fn new(conn: &ConnectionRef, id: PathId) -> Option<Self> {
    -140        {
    -141            let mut state = conn.lock_without_waking("Path::new");
    -142            // TODO(flub): Using this to know if the path still exists is... hacky.
    -143            state.inner.path_status(id).ok()?;
    -144            state.increment_path_refs(id);
    -145        }
    -146        Some(Self {
    -147            id,
    -148            conn: conn.clone(),
    -149        })
    -150    }
    -151
    -152    /// Returns a [`Path`] for a path id without checking if the path exists or is closed.
    -153    fn new_unchecked(conn: ConnectionRef, id: PathId) -> Self {
    -154        conn.lock_without_waking("Path::new_unchecked")
    -155            .increment_path_refs(id);
    -156        Self { id, conn }
    +116    path_ref: PathRef,
    +117}
    +118
    +119impl Path {
    +120    /// Returns a [`Path`] for a path id, after checking that the path is not closed.
    +121    pub(crate) fn new(conn: &ConnectionRef, id: PathId) -> Option<Self> {
    +122        let path_ref = {
    +123            let mut state = conn.lock_without_waking("Path::new");
    +124            // TODO(flub): Using this to know if the path still exists is... hacky.
    +125            state.inner.path_status(id).ok()?;
    +126            state.acquire_path_ref(id)
    +127        };
    +128        Some(Self {
    +129            conn: conn.clone(),
    +130            path_ref,
    +131        })
    +132    }
    +133
    +134    /// Returns a [`Path`] for a path id without checking if the path exists or is closed.
    +135    fn new_unchecked(conn: ConnectionRef, id: PathId) -> Self {
    +136        let path_ref = conn
    +137            .lock_without_waking("Path::new_unchecked")
    +138            .acquire_path_ref(id);
    +139        Self { conn, path_ref }
    +140    }
    +141
    +142    /// Returns a [`WeakPathHandle`] for this path.
    +143    ///
    +144    /// Holding a [`WeakPathHandle`] does not keep a connection alive, but ensures that the
    +145    /// path's stats are not dropped until the underlying connection is dropped, even if the
    +146    /// path is abandoned.
    +147    pub fn weak_handle(&self) -> WeakPathHandle {
    +148        WeakPathHandle {
    +149            path_ref: self.path_ref.clone(),
    +150            conn: self.conn.weak_handle(),
    +151        }
    +152    }
    +153
    +154    /// The [`PathId`] of this path.
    +155    pub fn id(&self) -> PathId {
    +156        self.path_ref.id
     157    }
     158
    -159    /// Returns a [`WeakPathHandle`] for this path.
    -160    ///
    -161    /// Holding a [`WeakPathHandle`] does not keep a connection alive, but ensures that the
    -162    /// path's stats are not dropped until the underlying connection is dropped, even if the
    -163    /// path is abandoned.
    -164    pub fn weak_handle(&self) -> WeakPathHandle {
    -165        self.conn
    -166            .lock_without_waking("Path::weak_handle")
    -167            .increment_path_refs(self.id);
    -168        WeakPathHandle {
    -169            id: self.id,
    -170            conn: self.conn.weak_handle(),
    -171        }
    -172    }
    -173
    -174    /// The [`PathId`] of this path.
    -175    pub fn id(&self) -> PathId {
    -176        self.id
    -177    }
    -178
    -179    /// The current local [`PathStatus`] of this path.
    -180    pub fn status(&self) -> Result<PathStatus, ClosedPath> {
    -181        self.conn
    -182            .lock_without_waking("path status")
    -183            .inner
    -184            .path_status(self.id)
    -185    }
    -186
    -187    /// Sets the [`PathStatus`] of this path.
    -188    pub fn set_status(&self, status: PathStatus) -> Result<(), SetPathStatusError> {
    -189        self.conn
    -190            .lock_and_wake("set path status")
    -191            .inner
    -192            .set_path_status(self.id, status)?;
    -193        Ok(())
    -194    }
    -195
    -196    /// Returns the [`PathStats`] for this path.
    -197    pub fn stats(&self) -> PathStats {
    -198        // The `expect` is safe:
    -199        // - `Path` can only be created for non-closed paths.
    -200        // - `Path` and its clones or `WeakPathHandle`s all increment the connection state's `path_ref`
    -201        //   reference counter
    -202        // - As long as a path is not abandoned, its stats are available from `proto::Connection`
    -203        // - If a path is abandoned, the `crate::Connection` stores the final stats as long as
    -204        //   the path's refcount is not 0
    -205        // - Therefore, we always get stats here.
    -206        self.conn
    -207            .lock_without_waking("Path::stats")
    -208            .path_stats(self.id)
    -209            .expect("either path stats or discarded path stats are always set as long as Path is not dropped")
    -210    }
    -211
    -212    /// Closes this path.
    -213    ///
    -214    /// The path is immediately considered closed by the local endpoint. Once the state is removed,
    -215    /// after a short period of time for any in-flight packets, a [`PathEvent::Abandoned`] is
    -216    /// returned.
    -217    pub fn close(&self) -> Result<(), ClosePathError> {
    -218        let mut state = self.conn.lock_and_wake("close_path");
    -219        state.inner.close_path(
    -220            crate::Instant::now(),
    -221            self.id,
    -222            TransportErrorCode::APPLICATION_ABANDON_PATH.into(),
    -223        )
    -224    }
    -225
    -226    /// Sets the max idle timeout for a specific path
    -227    ///
    -228    /// See [`TransportConfig::default_path_max_idle_timeout`] for details.
    -229    ///
    -230    /// Returns the previous value of the setting.
    -231    ///
    -232    /// [`TransportConfig::default_path_max_idle_timeout`]: crate::TransportConfig::default_path_max_idle_timeout
    -233    pub fn set_max_idle_timeout(
    -234        &self,
    -235        timeout: Option<Duration>,
    -236    ) -> Result<Option<Duration>, ClosedPath> {
    -237        let mut state = self.conn.lock_and_wake("path_set_max_idle_timeout");
    -238        let now = state.runtime.now();
    -239        state.inner.set_path_max_idle_timeout(now, self.id, timeout)
    +159    /// The current local [`PathStatus`] of this path.
    +160    pub fn status(&self) -> Result<PathStatus, ClosedPath> {
    +161        self.conn
    +162            .lock_without_waking("path status")
    +163            .inner
    +164            .path_status(self.id())
    +165    }
    +166
    +167    /// Sets the [`PathStatus`] of this path.
    +168    ///
    +169    /// Returns the previous status of the path.
    +170    pub fn set_status(&self, status: PathStatus) -> Result<PathStatus, SetPathStatusError> {
    +171        self.conn
    +172            .lock_and_wake("set path status")
    +173            .inner
    +174            .set_path_status(self.id(), status)
    +175    }
    +176
    +177    /// Returns the [`PathStats`] for this path.
    +178    pub fn stats(&self) -> PathStats {
    +179        // The `expect` is safe:
    +180        // - `Path` can only be created for non-closed paths.
    +181        // - `Path` and its clones or `WeakPathHandle`s all increment the connection state's `path_ref`
    +182        //   reference counter
    +183        // - As long as a path is not abandoned, its stats are available from `proto::Connection`
    +184        // - If a path is abandoned, the `crate::Connection` stores the final stats as long as
    +185        //   the path's refcount is not 0
    +186        // - Therefore, we always get stats here.
    +187        self.conn
    +188            .lock_without_waking("Path::stats")
    +189            .path_stats(self.id())
    +190            .expect("either path stats or discarded path stats are always set as long as Path is not dropped")
    +191    }
    +192
    +193    /// Closes this path.
    +194    ///
    +195    /// The path is immediately considered closed by the local endpoint. Once the state is removed,
    +196    /// after a short period of time for any in-flight packets, a [`PathEvent::Abandoned`] is
    +197    /// returned.
    +198    pub fn close(&self) -> Result<(), ClosePathError> {
    +199        let mut state = self.conn.lock_and_wake("close_path");
    +200        state.inner.close_path(
    +201            crate::Instant::now(),
    +202            self.id(),
    +203            TransportErrorCode::APPLICATION_ABANDON_PATH.into(),
    +204        )
    +205    }
    +206
    +207    /// Sets the max idle timeout for a specific path
    +208    ///
    +209    /// See [`TransportConfig::default_path_max_idle_timeout`] for details.
    +210    ///
    +211    /// Returns the previous value of the setting.
    +212    ///
    +213    /// [`TransportConfig::default_path_max_idle_timeout`]: crate::TransportConfig::default_path_max_idle_timeout
    +214    pub fn set_max_idle_timeout(
    +215        &self,
    +216        timeout: Option<Duration>,
    +217    ) -> Result<Option<Duration>, ClosedPath> {
    +218        let mut state = self.conn.lock_and_wake("path_set_max_idle_timeout");
    +219        let now = state.runtime.now();
    +220        state
    +221            .inner
    +222            .set_path_max_idle_timeout(now, self.id(), timeout)
    +223    }
    +224
    +225    /// Sets the keep_alive_interval for a specific path
    +226    ///
    +227    /// See [`TransportConfig::default_path_keep_alive_interval`] for details.
    +228    ///
    +229    /// Returns the previous value of the setting.
    +230    ///
    +231    /// [`TransportConfig::default_path_keep_alive_interval`]: crate::TransportConfig::default_path_keep_alive_interval
    +232    pub fn set_keep_alive_interval(
    +233        &self,
    +234        interval: Option<Duration>,
    +235    ) -> Result<Option<Duration>, ClosedPath> {
    +236        let mut state = self.conn.lock_and_wake("path_set_keep_alive_interval");
    +237        state
    +238            .inner
    +239            .set_path_keep_alive_interval(self.id(), interval)
     240    }
     241
    -242    /// Sets the keep_alive_interval for a specific path
    +242    /// Track changes on our external address as reported by the peer.
     243    ///
    -244    /// See [`TransportConfig::default_path_keep_alive_interval`] for details.
    -245    ///
    -246    /// Returns the previous value of the setting.
    -247    ///
    -248    /// [`TransportConfig::default_path_keep_alive_interval`]: crate::TransportConfig::default_path_keep_alive_interval
    -249    pub fn set_keep_alive_interval(
    -250        &self,
    -251        interval: Option<Duration>,
    -252    ) -> Result<Option<Duration>, ClosedPath> {
    -253        let mut state = self.conn.lock_and_wake("path_set_keep_alive_interval");
    -254        state.inner.set_path_keep_alive_interval(self.id, interval)
    +244    /// If the address-discovery extension is not negotiated, the stream will never return.
    +245    pub fn observed_external_addr(&self) -> Result<AddressDiscovery, ClosedPath> {
    +246        let state = self.conn.lock_without_waking("per_path_observed_address");
    +247        let path_events = state.path_events.subscribe();
    +248        let initial_value = state.inner.path_observed_address(self.id())?;
    +249        Ok(AddressDiscovery::new(
    +250            self.id(),
    +251            path_events,
    +252            initial_value,
    +253            state.runtime.clone(),
    +254        ))
     255    }
     256
    -257    /// Track changes on our external address as reported by the peer.
    -258    ///
    -259    /// If the address-discovery extension is not negotiated, the stream will never return.
    -260    pub fn observed_external_addr(&self) -> Result<AddressDiscovery, ClosedPath> {
    -261        let state = self.conn.lock_without_waking("per_path_observed_address");
    -262        let path_events = state.path_events.subscribe();
    -263        let initial_value = state.inner.path_observed_address(self.id)?;
    -264        Ok(AddressDiscovery::new(
    -265            self.id,
    -266            path_events,
    -267            initial_value,
    -268            state.runtime.clone(),
    -269        ))
    +257    /// The peer's UDP address for this path.
    +258    pub fn remote_address(&self) -> Result<SocketAddr, ClosedPath> {
    +259        let state = self.conn.lock_without_waking("per_path_remote_address");
    +260        Ok(state.inner.network_path(self.id())?.remote())
    +261    }
    +262
    +263    /// The local IP used for this path, if known.
    +264    ///
    +265    /// Returns `Ok(None)` for clients or when the platform does not expose this information; see
    +266    /// [`noq_udp::RecvMeta::dst_ip`](udp::RecvMeta::dst_ip) for supported platforms.
    +267    pub fn local_ip(&self) -> Result<Option<IpAddr>, ClosedPath> {
    +268        let state = self.conn.lock_without_waking("per_path_local_ip");
    +269        Ok(state.inner.network_path(self.id())?.local_ip())
     270    }
     271
    -272    /// The peer's UDP address for this path.
    -273    pub fn remote_address(&self) -> Result<SocketAddr, ClosedPath> {
    -274        let state = self.conn.lock_without_waking("per_path_remote_address");
    -275        Ok(state.inner.network_path(self.id)?.remote())
    +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    /// Ping the remote endpoint over this path.
    -279    pub fn ping(&self) -> Result<(), ClosedPath> {
    -280        let mut state = self.conn.lock_and_wake("ping");
    -281        state.inner.ping_path(self.id)
    +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()
    +287        self.id() == other.id() && self.conn.stable_id() == other.conn.stable_id()
     288    }
     289}
     290
    @@ -296,119 +296,173 @@
     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)]
    +299#[derive(Debug, Clone)]
     300pub struct WeakPathHandle {
    -301    id: PathId,
    -302    conn: WeakConnectionHandle,
    +301    conn: WeakConnectionHandle,
    +302    path_ref: PathRef,
     303}
     304
    -305impl Clone for WeakPathHandle {
    -306    fn clone(&self) -> Self {
    -307        if let Some(conn) = self.conn.upgrade_to_ref() {
    -308            conn.lock_without_waking("WeakPathHandle::clone")
    -309                .increment_path_refs(self.id);
    -310        }
    -311        Self {
    -312            id: self.id,
    -313            conn: self.conn.clone(),
    -314        }
    -315    }
    -316}
    -317
    -318impl PartialEq for WeakPathHandle {
    -319    fn eq(&self, other: &Self) -> bool {
    -320        self.id == other.id && self.conn.is_same_connection(&other.conn)
    -321    }
    -322}
    -323
    -324impl Eq for WeakPathHandle {}
    -325
    -326impl Drop for WeakPathHandle {
    -327    fn drop(&mut self) {
    -328        if let Some(conn) = self.conn.upgrade_to_ref() {
    -329            conn.lock_without_waking("WeakPathHandle::drop")
    -330                .decrement_path_refs(self.id);
    -331        }
    -332    }
    -333}
    -334
    -335impl WeakPathHandle {
    -336    /// Returns the [`PathId`] of this path.
    -337    pub fn id(&self) -> PathId {
    -338        self.id
    -339    }
    -340
    -341    /// Upgrades to a [`Path`].
    -342    ///
    -343    /// Returns `None` if the connection was dropped.
    -344    pub fn upgrade(&self) -> Option<Path> {
    -345        let conn = self.conn.upgrade_to_ref()?;
    -346        Some(Path::new_unchecked(conn, self.id))
    -347    }
    +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 {}
    +312
    +313impl WeakPathHandle {
    +314    /// Returns the [`PathId`] of this path.
    +315    pub fn id(&self) -> PathId {
    +316        self.path_ref.id
    +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        }
    +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
    -350/// Stream produced by [`Path::observed_external_addr`]
    -351///
    -352/// This will always return the external address most recently reported by the remote over this
    -353/// path. If the extension is not negotiated, this stream will never return.
    -354// TODO(@divma): provide a way to check if the extension is negotiated.
    -355pub struct AddressDiscovery {
    -356    watcher: WatchStream<SocketAddr>,
    -357}
    -358
    -359impl AddressDiscovery {
    -360    pub(super) fn new(
    -361        path_id: PathId,
    -362        mut path_events: tokio::sync::broadcast::Receiver<PathEvent>,
    -363        initial_value: Option<SocketAddr>,
    -364        runtime: Arc<dyn Runtime>,
    -365    ) -> Self {
    -366        let (tx, rx) = watch::channel(initial_value.unwrap_or_else(||
    -367                // if the dummy value is used, it will be ignored
    -368                SocketAddr::new([0, 0, 0, 0].into(), 0)));
    -369        let filter = async move {
    -370            loop {
    -371                match path_events.recv().await {
    -372                    Ok(PathEvent::ObservedAddr { id, addr: observed }) if id == path_id => {
    -373                        tx.send_if_modified(|addr| {
    -374                            let old = std::mem::replace(addr, observed);
    -375                            old != *addr
    -376                        });
    -377                    }
    -378                    Ok(PathEvent::Discarded { id, .. }) if id == path_id => {
    -379                        // If the path is closed, terminate the stream
    -380                        break;
    -381                    }
    -382                    Ok(_) => {
    -383                        // ignore any other event
    -384                    }
    -385                    Err(_) => {
    -386                        // A lagged error should never happen since this (detached) task is
    -387                        // constantly reading from the channel. Therefore, if an error does happen,
    -388                        // the stream can terminate
    -389                        break;
    -390                    }
    -391                }
    -392            }
    -393        };
    -394
    -395        let watcher = if initial_value.is_some() {
    -396            WatchStream::new(rx)
    -397        } else {
    -398            WatchStream::from_changes(rx)
    -399        };
    -400
    -401        runtime.spawn(Box::pin(filter));
    -402        // TODO(@divma): check if there's a way to ensure the future ends. AbortHandle is not an
    -403        // option
    -404        Self { watcher }
    -405    }
    -406}
    -407
    -408impl Stream for AddressDiscovery {
    -409    type Item = SocketAddr;
    -410
    -411    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
    -412        Pin::new(&mut self.watcher).poll_next(cx)
    -413    }
    -414}
    \ No newline at end of file +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; +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 diff --git a/pr/451/docs/src/noq/recv_stream.rs.html b/pr/451/docs/src/noq/recv_stream.rs.html index 01129b36a..ade0ce745 100644 --- a/pr/451/docs/src/noq/recv_stream.rs.html +++ b/pr/451/docs/src/noq/recv_stream.rs.html @@ -127,7 +127,7 @@ 127 /// closed. 128 /// 129 /// [`finish`]: crate::SendStream::finish -130 pub fn poll_read_buf( +130 pub(crate) fn poll_read_buf( 131 &mut self, 132 cx: &mut Context<'_>, 133 buf: &mut ReadBuf<'_>, @@ -156,604 +156,633 @@ 156 .map(|res| res.map(|_| ())) 157 } 158 -159 /// Read the next segment of data +159 /// Reads the next segment of data as zero-copy [`Bytes`]. 160 /// -161 /// Yields `None` if the stream was finished. Otherwise, yields a segment of data and its -162 /// offset in the stream. The chunk's offset will be immediately after -163 /// the last data yielded by [`RecvStream::read`] or [`RecvStream::read_chunk`]. -164 /// -165 /// For unordered reads, convert the stream into an unordered stream using [`Self::into_unordered`]. -166 /// -167 /// Slightly more efficient than [`RecvStream::read`] due to not copying. Chunk boundaries do not correspond -168 /// to peer writes, and hence cannot be used as framing. -169 /// -170 /// This operation is cancel-safe. -171 pub async fn read_chunk(&mut self, max_length: usize) -> Result<Option<Chunk>, ReadError> { -172 ReadChunk { -173 stream: self, -174 max_length, -175 ordered: true, -176 } -177 .await -178 } -179 -180 /// Attempts to read a chunk from the stream. -181 /// -182 /// On success, returns `Poll::Ready(Ok(Some(chunk)))`. If `Poll::Ready(Ok(None))` -183 /// is returned, it implies that EOF has been reached. -184 /// -185 /// If no data is available for reading, the method returns `Poll::Pending` -186 /// and arranges for the current task (via cx.waker()) to receive a notification -187 /// when the stream becomes readable or is closed. -188 fn poll_read_chunk( -189 &mut self, -190 cx: &mut Context<'_>, -191 max_length: usize, -192 ordered: bool, -193 ) -> Poll<Result<Option<Chunk>, ReadError>> { -194 self.poll_read_generic(cx, ordered, |chunks| match chunks.next(max_length) { -195 Ok(Some(chunk)) => ReadStatus::Readable(chunk), -196 res => (None, res.err()).into(), -197 }) -198 } -199 -200 /// Read the next segments of data -201 /// -202 /// Fills `bufs` with the segments of data beginning immediately after the -203 /// last data yielded by `read` or `read_chunk`, or `None` if the stream was -204 /// finished. -205 /// -206 /// Slightly more efficient than `read` due to not copying. Chunk boundaries -207 /// do not correspond to peer writes, and hence cannot be used as framing. -208 /// -209 /// This operation is cancel-safe. -210 pub async fn read_chunks(&mut self, bufs: &mut [Bytes]) -> Result<Option<usize>, ReadError> { -211 ReadChunks { stream: self, bufs }.await -212 } -213 -214 /// Foundation of [`Self::read_chunks`] -215 fn poll_read_chunks( -216 &mut self, -217 cx: &mut Context<'_>, -218 bufs: &mut [Bytes], -219 ) -> Poll<Result<Option<usize>, ReadError>> { -220 if bufs.is_empty() { -221 return Poll::Ready(Ok(Some(0))); -222 } -223 -224 self.poll_read_generic(cx, true, |chunks| { -225 let mut read = 0; -226 loop { -227 if read >= bufs.len() { -228 // We know `read > 0` because `bufs` cannot be empty here -229 return ReadStatus::Readable(read); -230 } -231 -232 match chunks.next(usize::MAX) { -233 Ok(Some(chunk)) => { -234 bufs[read] = chunk.bytes; -235 read += 1; -236 } -237 res => return (if read == 0 { None } else { Some(read) }, res.err()).into(), -238 } -239 } -240 }) -241 } -242 -243 /// Convenience method to read all remaining data into a buffer -244 /// -245 /// Fails with [`ReadToEndError::TooLong`] on reading more than `size_limit` bytes, discarding -246 /// all data read. Uses unordered reads to be more efficient than using `AsyncRead` would -247 /// allow. `size_limit` should be set to limit worst-case memory use. -248 /// -249 /// If unordered reads have already been made, the resulting buffer may have gaps containing -250 /// arbitrary data. -251 /// -252 /// This operation is *not* cancel-safe. +161 /// Yields `None` if the stream was finished. Otherwise, yields the next segment of data. The +162 /// chunk's offset will be immediately after the last data yielded by [`RecvStream::read`] or +163 /// [`RecvStream::read_chunk`]; use [`bytes_read()`](Self::bytes_read) to query that offset +164 /// explicitly. +165 /// +166 /// For unordered reads, convert the stream into an unordered stream using [`Self::into_unordered`]. +167 /// +168 /// Slightly more efficient than [`RecvStream::read`] due to not copying. Chunk boundaries do +169 /// not correspond to peer writes, and hence cannot be used as framing. +170 /// +171 /// This operation is cancel-safe. +172 pub async fn read_chunk(&mut self, max_length: usize) -> Result<Option<Bytes>, ReadError> { +173 Ok(ReadChunk { +174 stream: self, +175 max_length, +176 ordered: true, +177 } +178 .await? +179 .map(|chunk| chunk.bytes)) +180 } +181 +182 /// Attempts to read a chunk from the stream. +183 /// +184 /// On success, returns `Poll::Ready(Ok(Some(chunk)))`. If `Poll::Ready(Ok(None))` +185 /// is returned, it implies that EOF has been reached. +186 /// +187 /// If no data is available for reading, the method returns `Poll::Pending` +188 /// and arranges for the current task (via cx.waker()) to receive a notification +189 /// when the stream becomes readable or is closed. +190 fn poll_read_chunk( +191 &mut self, +192 cx: &mut Context<'_>, +193 max_length: usize, +194 ordered: bool, +195 ) -> Poll<Result<Option<Chunk>, ReadError>> { +196 self.poll_read_generic(cx, ordered, |chunks| match chunks.next(max_length) { +197 Ok(Some(chunk)) => ReadStatus::Readable(chunk), +198 res => (None, res.err()).into(), +199 }) +200 } +201 +202 /// Reads the next segments of data. +203 /// +204 /// Fills `bufs` with the segments of data beginning immediately after the last data yielded +205 /// by [`read`](Self::read), [`read_chunk`](Self::read_chunk), or +206 /// [`read_many_chunks`](Self::read_many_chunks), or `None` if the stream was finished. +207 /// +208 /// Slightly more efficient than [`read`](Self::read) due to not copying. Chunk boundaries do +209 /// not correspond to peer writes, and hence cannot be used as framing. +210 /// +211 /// This operation is cancel-safe. +212 pub async fn read_many_chunks( +213 &mut self, +214 bufs: &mut [Bytes], +215 ) -> Result<Option<usize>, ReadError> { +216 ReadChunks { stream: self, bufs }.await +217 } +218 +219 /// Foundation of [`Self::read_many_chunks`] +220 fn poll_read_chunks( +221 &mut self, +222 cx: &mut Context<'_>, +223 bufs: &mut [Bytes], +224 ) -> Poll<Result<Option<usize>, ReadError>> { +225 if bufs.is_empty() { +226 return Poll::Ready(Ok(Some(0))); +227 } +228 +229 self.poll_read_generic(cx, true, |chunks| { +230 let mut read = 0; +231 loop { +232 if read >= bufs.len() { +233 // We know `read > 0` because `bufs` cannot be empty here +234 return ReadStatus::Readable(read); +235 } +236 +237 match chunks.next(usize::MAX) { +238 Ok(Some(chunk)) => { +239 bufs[read] = chunk.bytes; +240 read += 1; +241 } +242 res => return (if read == 0 { None } else { Some(read) }, res.err()).into(), +243 } +244 } +245 }) +246 } +247 +248 /// Convenience method to read all remaining data into a buffer +249 /// +250 /// Fails with [`ReadToEndError::TooLong`] on reading more than `size_limit` bytes, discarding +251 /// all data read. Uses unordered reads to be more efficient than using `AsyncRead` would +252 /// allow. `size_limit` should be set to limit worst-case memory use. 253 /// -254 /// [`ReadToEndError::TooLong`]: crate::ReadToEndError::TooLong -255 pub async fn read_to_end(&mut self, size_limit: usize) -> Result<Vec<u8>, ReadToEndError> { -256 ReadToEnd { -257 stream: self, -258 size_limit, -259 read: Vec::new(), -260 start: u64::MAX, -261 end: 0, -262 } -263 .await -264 } -265 -266 /// Stop accepting data -267 /// -268 /// Discards unread data and notifies the peer to stop transmitting. Once stopped, further -269 /// attempts to operate on a stream will yield `ClosedStream` errors. -270 pub fn stop(&mut self, error_code: VarInt) -> Result<(), ClosedStream> { -271 let mut conn = self.conn.lock_and_wake("RecvStream::stop"); -272 if self.is_0rtt && conn.check_0rtt().is_err() { -273 conn.skip_waking(); -274 return Ok(()); -275 } -276 conn.inner.recv_stream(self.stream).stop(error_code)?; -277 self.all_data_read = true; -278 Ok(()) -279 } -280 -281 /// Check if this stream has been opened during 0-RTT. -282 /// -283 /// In which case any non-idempotent request should be considered dangerous at the application -284 /// level. Because read data is subject to replay attacks. -285 pub fn is_0rtt(&self) -> bool { -286 self.is_0rtt +254 /// If unordered reads have already been made, the resulting buffer may have gaps containing +255 /// arbitrary data. +256 /// +257 /// This operation is *not* cancel-safe. +258 /// +259 /// [`ReadToEndError::TooLong`]: crate::ReadToEndError::TooLong +260 pub async fn read_to_end(&mut self, size_limit: usize) -> Result<Vec<u8>, ReadToEndError> { +261 ReadToEnd { +262 stream: self, +263 size_limit, +264 read: Vec::new(), +265 start: u64::MAX, +266 end: 0, +267 } +268 .await +269 } +270 +271 /// Stop accepting data +272 /// +273 /// Discards unread data and notifies the peer to stop transmitting. Once stopped, further +274 /// attempts to operate on a stream will yield `ClosedStream` errors. +275 pub fn stop(&mut self, error_code: VarInt) -> Result<(), ClosedStream> { +276 let mut conn = self.conn.lock_and_wake("RecvStream::stop"); +277 if self.is_0rtt && conn.check_0rtt().is_err() { +278 conn.skip_waking(); +279 return Ok(()); +280 } +281 conn.inner.recv_stream(self.stream).stop(error_code)?; +282 self.all_data_read = true; +283 // Clean up shared state that might be left over from a cancelled read +284 // operation, so `drop` doesn't have to +285 conn.blocked_readers.remove(&self.stream); +286 Ok(()) 287 } 288 -289 /// Get the identity of this stream -290 pub fn id(&self) -> StreamId { -291 self.stream -292 } -293 -294 /// Completes when the stream has been reset by the peer or otherwise closed -295 /// -296 /// Yields `Some` with the reset error code when the stream is reset by the peer. Yields `None` -297 /// when the stream was previously [`stop()`](Self::stop)ed, or when the stream was -298 /// [`finish()`](crate::SendStream::finish)ed by the peer and all data has been received, after -299 /// which it is no longer meaningful for the stream to be reset. -300 /// -301 /// This operation is cancel-safe. -302 pub async fn received_reset(&mut self) -> Result<Option<VarInt>, ResetError> { -303 poll_fn(|cx| { -304 let mut conn = self.conn.lock_without_waking("RecvStream::reset"); -305 if self.is_0rtt && conn.check_0rtt().is_err() { -306 return Poll::Ready(Err(ResetError::ZeroRttRejected)); -307 } -308 -309 if let Some(code) = self.reset { -310 return Poll::Ready(Ok(Some(code))); -311 } -312 -313 match conn.inner.recv_stream(self.stream).received_reset() { -314 Err(_) => Poll::Ready(Ok(None)), -315 Ok(Some(error_code)) => { -316 // Stream state has just now been freed, so the connection may need to issue new -317 // stream ID flow control credit -318 conn.wake(); -319 Poll::Ready(Ok(Some(error_code))) -320 } -321 Ok(None) => { -322 if let Some(e) = &conn.error { -323 return Poll::Ready(Err(e.clone().into())); -324 } -325 // Resets always notify readers, since a reset is an immediate read error. We -326 // could introduce a dedicated channel to reduce the risk of spurious wakeups, -327 // but that increased complexity is probably not justified, as an application -328 // that is expecting a reset is not likely to receive large amounts of data. -329 conn.blocked_readers.insert(self.stream, cx.waker().clone()); -330 Poll::Pending -331 } -332 } -333 }) -334 .await -335 } -336 -337 /// Handle common logic related to reading out of a receive stream -338 /// -339 /// This takes an `FnMut` closure that takes care of the actual reading process, matching -340 /// the detailed read semantics for the calling function with a particular return type. -341 /// The closure can read from the passed `&mut Chunks` and has to return the status after -342 /// reading: the amount of data read, and the status after the final read call. -343 fn poll_read_generic<T, U>( -344 &mut self, -345 cx: &mut Context<'_>, -346 ordered: bool, -347 mut read_fn: T, -348 ) -> Poll<Result<Option<U>, ReadError>> -349 where -350 T: FnMut(&mut Chunks<'_>) -> ReadStatus<U>, -351 { -352 use proto::ReadError::*; -353 if self.all_data_read { -354 return Poll::Ready(Ok(None)); -355 } -356 -357 let mut conn = self.conn.lock_without_waking("RecvStream::poll_read"); -358 if self.is_0rtt { -359 conn.check_0rtt().map_err(|()| ReadError::ZeroRttRejected)?; -360 } -361 -362 // If we stored an error during a previous call, return it now. This can happen if a -363 // `read_fn` both wants to return data and also returns an error in its final stream status. -364 let status = match self.reset { -365 Some(code) => ReadStatus::Failed(None, Reset(code)), -366 None => { -367 let mut recv = conn.inner.recv_stream(self.stream); -368 let mut chunks = recv.read(ordered).map_err(|e| match e { -369 ReadableError::ClosedStream => ReadError::ClosedStream, -370 ReadableError::IllegalOrderedRead => { -371 // We should never get here because the only way to do unordered reads is -372 // via UnorderedRecvStream, which allows only unordered reads. It is not -373 // possible to get a RecvStream from an UnorderedRecvStream. -374 unreachable!("ordered read after unordered read") -375 } -376 })?; -377 let status = read_fn(&mut chunks); -378 if chunks.finalize().should_transmit() { -379 conn.wake(); -380 } -381 status -382 } -383 }; -384 -385 match status { -386 ReadStatus::Readable(read) => Poll::Ready(Ok(Some(read))), -387 ReadStatus::Finished(read) => { -388 self.all_data_read = true; -389 Poll::Ready(Ok(read)) -390 } -391 ReadStatus::Failed(read, Blocked) => match read { -392 Some(val) => Poll::Ready(Ok(Some(val))), -393 None => { -394 if let Some(ref x) = conn.error { -395 return Poll::Ready(Err(ReadError::ConnectionLost(x.clone()))); -396 } -397 conn.blocked_readers.insert(self.stream, cx.waker().clone()); -398 Poll::Pending -399 } -400 }, -401 ReadStatus::Failed(read, Reset(error_code)) => match read { -402 None => { -403 self.all_data_read = true; -404 self.reset = Some(error_code); -405 Poll::Ready(Err(ReadError::Reset(error_code))) -406 } -407 done => { -408 self.reset = Some(error_code); -409 Poll::Ready(Ok(done)) -410 } -411 }, -412 } -413 } -414 -415 /// Converts this stream into an unordered stream. -416 pub fn into_unordered(self) -> UnorderedRecvStream { -417 UnorderedRecvStream { inner: self } -418 } -419} -420 -421/// A stream that can be used to receive data out-of-order. -422/// -423/// Obtained by converting a [`RecvStream`] via [`RecvStream::into_unordered`]. -424/// -425/// This variant of `RecvStream` allows reading chunks of data *exclusively* -426/// out of order. Once you have done an unordered read, ordered reads are no -427/// longer possible since data may have been consumed out of order. -428/// -429/// The stream state related fns like [`Self::id`], [`Self::is_0rtt`], [`Self::stop`], and -430/// [`Self::received_reset`] behave exactly as on [`RecvStream`]. -431#[derive(Debug)] -432pub struct UnorderedRecvStream { -433 inner: RecvStream, -434} -435 -436impl UnorderedRecvStream { -437 /// Reads the next segment of data. -438 /// -439 /// Yields `None` if the stream was finished. Otherwise, yields a segment of data and its -440 /// offset in the stream. Segments may be received in any order, and the `Chunk`'s `offset` -441 /// field can be used to determine ordering in the caller. Unordered reads are less prone -442 /// to head-of-line blocking within a stream, but require the application to manage -443 /// reassembling the original data. -444 /// -445 /// This operation is cancel-safe. -446 pub async fn read_chunk(&mut self, max_length: usize) -> Result<Option<Chunk>, ReadError> { -447 ReadChunk { -448 stream: &mut self.inner, -449 max_length, -450 ordered: false, -451 } -452 .await -453 } -454 -455 /// Get the identity of this stream -456 pub fn id(&self) -> StreamId { -457 self.inner.id() -458 } -459 -460 /// Check if this stream has been opened during 0-RTT. +289 /// Check if this stream has been opened during 0-RTT. +290 /// +291 /// In which case any non-idempotent request should be considered dangerous at the application +292 /// level. Because read data is subject to replay attacks. +293 pub fn is_0rtt(&self) -> bool { +294 self.is_0rtt +295 } +296 +297 /// Get the identity of this stream +298 pub fn id(&self) -> StreamId { +299 self.stream +300 } +301 +302 /// Returns the number of bytes read from this stream. +303 /// +304 /// This is the offset of the next byte to be read, i.e. the length of the contiguous +305 /// prefix of the stream consumed by the application. +306 pub fn bytes_read(&self) -> Result<u64, ClosedStream> { +307 let mut conn = self.conn.lock_without_waking("RecvStream::bytes_read"); +308 conn.inner.recv_stream(self.stream).bytes_read() +309 } +310 +311 /// Completes when the stream has been reset by the peer or otherwise closed +312 /// +313 /// Yields `Some` with the reset error code when the stream is reset by the peer. Yields `None` +314 /// when the stream was previously [`stop()`](Self::stop)ed, or when the stream was +315 /// [`finish()`](crate::SendStream::finish)ed by the peer and all data has been received, after +316 /// which it is no longer meaningful for the stream to be reset. +317 /// +318 /// This operation is cancel-safe. +319 pub async fn received_reset(&mut self) -> Result<Option<VarInt>, ResetError> { +320 poll_fn(|cx| { +321 let mut conn = self.conn.lock_without_waking("RecvStream::reset"); +322 if self.is_0rtt && conn.check_0rtt().is_err() { +323 return Poll::Ready(Err(ResetError::ZeroRttRejected)); +324 } +325 +326 if let Some(code) = self.reset { +327 return Poll::Ready(Ok(Some(code))); +328 } +329 +330 match conn.inner.recv_stream(self.stream).received_reset() { +331 Err(_) => Poll::Ready(Ok(None)), +332 Ok(Some(error_code)) => { +333 // Stream state has just now been freed, so the connection may need to issue new +334 // stream ID flow control credit +335 conn.wake(); +336 Poll::Ready(Ok(Some(error_code))) +337 } +338 Ok(None) => { +339 if let Some(e) = &conn.error { +340 return Poll::Ready(Err(e.clone().into())); +341 } +342 // Resets always notify readers, since a reset is an immediate read error. We +343 // could introduce a dedicated channel to reduce the risk of spurious wakeups, +344 // but that increased complexity is probably not justified, as an application +345 // that is expecting a reset is not likely to receive large amounts of data. +346 conn.blocked_readers.insert(self.stream, cx.waker().clone()); +347 Poll::Pending +348 } +349 } +350 }) +351 .await +352 } +353 +354 /// Handle common logic related to reading out of a receive stream +355 /// +356 /// This takes an `FnMut` closure that takes care of the actual reading process, matching +357 /// the detailed read semantics for the calling function with a particular return type. +358 /// The closure can read from the passed `&mut Chunks` and has to return the status after +359 /// reading: the amount of data read, and the status after the final read call. +360 fn poll_read_generic<T, U>( +361 &mut self, +362 cx: &mut Context<'_>, +363 ordered: bool, +364 mut read_fn: T, +365 ) -> Poll<Result<Option<U>, ReadError>> +366 where +367 T: FnMut(&mut Chunks<'_>) -> ReadStatus<U>, +368 { +369 use proto::ReadError::*; +370 if self.all_data_read { +371 return Poll::Ready(Ok(None)); +372 } +373 +374 let mut conn = self.conn.lock_without_waking("RecvStream::poll_read"); +375 if self.is_0rtt { +376 conn.check_0rtt().map_err(|()| ReadError::ZeroRttRejected)?; +377 } +378 +379 // If we stored an error during a previous call, return it now. This can happen if a +380 // `read_fn` both wants to return data and also returns an error in its final stream status. +381 let status = match self.reset { +382 Some(code) => ReadStatus::Failed(None, Reset(code)), +383 None => { +384 let mut recv = conn.inner.recv_stream(self.stream); +385 let mut chunks = recv.read(ordered).map_err(|e| match e { +386 ReadableError::ClosedStream => ReadError::ClosedStream, +387 ReadableError::IllegalOrderedRead => { +388 // We should never get here because the only way to do unordered reads is +389 // via UnorderedRecvStream, which allows only unordered reads. It is not +390 // possible to get a RecvStream from an UnorderedRecvStream. +391 unreachable!("ordered read after unordered read") +392 } +393 })?; +394 let status = read_fn(&mut chunks); +395 if chunks.finalize().should_transmit() { +396 conn.wake(); +397 } +398 status +399 } +400 }; +401 +402 match status { +403 ReadStatus::Readable(read) => Poll::Ready(Ok(Some(read))), +404 ReadStatus::Finished(read) => { +405 self.all_data_read = true; +406 Poll::Ready(Ok(read)) +407 } +408 ReadStatus::Failed(read, Blocked) => match read { +409 Some(val) => Poll::Ready(Ok(Some(val))), +410 None => { +411 if let Some(ref x) = conn.error { +412 return Poll::Ready(Err(ReadError::ConnectionLost(x.clone()))); +413 } +414 conn.blocked_readers.insert(self.stream, cx.waker().clone()); +415 Poll::Pending +416 } +417 }, +418 ReadStatus::Failed(read, Reset(error_code)) => match read { +419 None => { +420 self.all_data_read = true; +421 self.reset = Some(error_code); +422 Poll::Ready(Err(ReadError::Reset(error_code))) +423 } +424 done => { +425 self.reset = Some(error_code); +426 Poll::Ready(Ok(done)) +427 } +428 }, +429 } +430 } +431 +432 /// Converts this stream into an unordered stream. +433 pub fn into_unordered(self) -> UnorderedRecvStream { +434 UnorderedRecvStream { inner: self } +435 } +436} +437 +438/// A stream that can be used to receive data out-of-order. +439/// +440/// Obtained by converting a [`RecvStream`] via [`RecvStream::into_unordered`]. +441/// +442/// This variant of `RecvStream` allows reading chunks of data *exclusively* +443/// out of order. Once you have done an unordered read, ordered reads are no +444/// longer possible since data may have been consumed out of order. +445/// +446/// The stream state related fns like [`Self::id`], [`Self::is_0rtt`], [`Self::stop`], and +447/// [`Self::received_reset`] behave exactly as on [`RecvStream`]. +448#[derive(Debug)] +449pub struct UnorderedRecvStream { +450 inner: RecvStream, +451} +452 +453impl UnorderedRecvStream { +454 /// Reads the next segment of data. +455 /// +456 /// Yields `None` if the stream was finished. Otherwise, yields a segment of data and its +457 /// offset in the stream. Segments may be received in any order, and the `Chunk`'s `offset` +458 /// field can be used to determine ordering in the caller. Unordered reads are less prone +459 /// to head-of-line blocking within a stream, but require the application to manage +460 /// reassembling the original data. 461 /// -462 /// In which case any non-idempotent request should be considered dangerous at the application -463 /// level. Because read data is subject to replay attacks. -464 pub fn is_0rtt(&self) -> bool { -465 self.inner.is_0rtt() -466 } -467 -468 /// Stop accepting data -469 /// -470 /// Discards unread data and notifies the peer to stop transmitting. Once stopped, further -471 /// attempts to operate on a stream will yield `ClosedStream` errors. -472 pub fn stop(&mut self, error_code: VarInt) -> Result<(), ClosedStream> { -473 self.inner.stop(error_code) -474 } -475 -476 /// Completes when the stream has been reset by the peer or otherwise closed -477 /// -478 /// Yields `Some` with the reset error code when the stream is reset by the peer. Yields `None` -479 /// when the stream was previously [`stop()`](Self::stop)ed, or when the stream was -480 /// [`finish()`](crate::SendStream::finish)ed by the peer and all data has been received, after -481 /// which it is no longer meaningful for the stream to be reset. -482 /// -483 /// This operation is cancel-safe. -484 pub async fn received_reset(&mut self) -> Result<Option<VarInt>, ResetError> { -485 self.inner.received_reset().await -486 } -487} -488 -489enum ReadStatus<T> { -490 Readable(T), -491 Finished(Option<T>), -492 Failed(Option<T>, proto::ReadError), -493} -494 -495impl<T> From<(Option<T>, Option<proto::ReadError>)> for ReadStatus<T> { -496 fn from(status: (Option<T>, Option<proto::ReadError>)) -> Self { -497 match status { -498 (read, None) => Self::Finished(read), -499 (read, Some(e)) => Self::Failed(read, e), -500 } -501 } -502} -503 -504/// Future produced by [`RecvStream::read_to_end()`]. -505/// -506/// [`RecvStream::read_to_end()`]: crate::RecvStream::read_to_end -507struct ReadToEnd<'a> { -508 stream: &'a mut RecvStream, -509 read: Vec<(Bytes, u64)>, -510 start: u64, -511 end: u64, -512 size_limit: usize, -513} -514 -515impl Future for ReadToEnd<'_> { -516 type Output = Result<Vec<u8>, ReadToEndError>; -517 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { -518 loop { -519 match ready!(self.stream.poll_read_chunk(cx, usize::MAX, false))? { -520 Some(chunk) => { -521 self.start = self.start.min(chunk.offset); -522 let end = chunk.bytes.len() as u64 + chunk.offset; -523 if (end - self.start) > self.size_limit as u64 { -524 return Poll::Ready(Err(ReadToEndError::TooLong)); -525 } -526 self.end = self.end.max(end); -527 self.read.push((chunk.bytes, chunk.offset)); -528 } -529 None => { -530 if self.end == 0 { -531 // Never received anything -532 return Poll::Ready(Ok(Vec::new())); -533 } -534 let start = self.start; -535 let mut buffer = vec![0; (self.end - start) as usize]; -536 for (data, offset) in self.read.drain(..) { -537 let offset = (offset - start) as usize; -538 buffer[offset..offset + data.len()].copy_from_slice(&data); -539 } -540 return Poll::Ready(Ok(buffer)); -541 } -542 } -543 } -544 } -545} -546 -547/// Errors from [`RecvStream::read_to_end`] -548#[derive(Debug, Error, Clone, PartialEq, Eq)] -549pub enum ReadToEndError { -550 /// An error occurred during reading -551 #[error("read error: {0}")] -552 Read(#[from] ReadError), -553 /// The stream is larger than the user-supplied limit -554 #[error("stream too long")] -555 TooLong, -556} -557 -558#[cfg(feature = "futures-io")] -559impl futures_io::AsyncRead for RecvStream { -560 fn poll_read( -561 self: Pin<&mut Self>, -562 cx: &mut Context<'_>, -563 buf: &mut [u8], -564 ) -> Poll<io::Result<usize>> { -565 let mut buf = ReadBuf::new(buf); -566 ready!(Self::poll_read_buf(self.get_mut(), cx, &mut buf))?; -567 Poll::Ready(Ok(buf.filled().len())) -568 } -569} -570 -571impl tokio::io::AsyncRead for RecvStream { -572 fn poll_read( -573 self: Pin<&mut Self>, -574 cx: &mut Context<'_>, -575 buf: &mut ReadBuf<'_>, -576 ) -> Poll<io::Result<()>> { -577 ready!(Self::poll_read_buf(self.get_mut(), cx, buf))?; -578 Poll::Ready(Ok(())) -579 } -580} -581 -582impl Drop for RecvStream { -583 fn drop(&mut self) { -584 let mut conn = self.conn.lock_and_wake("RecvStream::drop"); -585 -586 // clean up any previously registered wakers -587 conn.blocked_readers.remove(&self.stream); -588 -589 if conn.error.is_some() || (self.is_0rtt && conn.check_0rtt().is_err()) { -590 conn.skip_waking(); -591 return; -592 } -593 if !self.all_data_read { -594 // Ignore ClosedStream errors -595 let _ = conn.inner.recv_stream(self.stream).stop(0u32.into()); -596 } -597 } -598} -599 -600/// Errors that arise from reading from a stream. -601#[derive(Debug, Error, Clone, PartialEq, Eq)] -602pub enum ReadError { -603 /// The peer abandoned transmitting data on this stream -604 /// -605 /// Carries an application-defined error code. -606 #[error("stream reset by peer: error {0}")] -607 Reset(VarInt), -608 /// The connection was lost -609 #[error("connection lost")] -610 ConnectionLost(#[from] ConnectionError), -611 /// The stream has already been stopped, finished, or reset -612 #[error("closed stream")] -613 ClosedStream, -614 /// This was a 0-RTT stream and the server rejected it -615 /// -616 /// Can only occur on clients for 0-RTT streams, which can be opened using -617 /// [`Connecting::into_0rtt()`]. -618 /// -619 /// [`Connecting::into_0rtt()`]: crate::Connecting::into_0rtt() -620 #[error("0-RTT rejected")] -621 ZeroRttRejected, -622} -623 -624impl From<ResetError> for ReadError { -625 fn from(e: ResetError) -> Self { -626 match e { -627 ResetError::ConnectionLost(e) => Self::ConnectionLost(e), -628 ResetError::ZeroRttRejected => Self::ZeroRttRejected, -629 } -630 } -631} -632 -633impl From<ReadError> for io::Error { -634 fn from(x: ReadError) -> Self { -635 use ReadError::*; -636 let kind = match x { -637 Reset { .. } | ZeroRttRejected => io::ErrorKind::ConnectionReset, -638 ConnectionLost(_) | ClosedStream => io::ErrorKind::NotConnected, -639 }; -640 Self::new(kind, x) -641 } -642} -643 -644/// Errors that arise while waiting for a stream to be reset -645#[derive(Debug, Error, Clone, PartialEq, Eq)] -646pub enum ResetError { -647 /// The connection was lost -648 #[error("connection lost")] -649 ConnectionLost(#[from] ConnectionError), -650 /// This was a 0-RTT stream and the server rejected it -651 /// -652 /// Can only occur on clients for 0-RTT streams, which can be opened using -653 /// [`Connecting::into_0rtt()`]. -654 /// -655 /// [`Connecting::into_0rtt()`]: crate::Connecting::into_0rtt() -656 #[error("0-RTT rejected")] -657 ZeroRttRejected, -658} -659 -660impl From<ResetError> for io::Error { -661 fn from(x: ResetError) -> Self { -662 use ResetError::*; -663 let kind = match x { -664 ZeroRttRejected => io::ErrorKind::ConnectionReset, -665 ConnectionLost(_) => io::ErrorKind::NotConnected, -666 }; -667 Self::new(kind, x) -668 } -669} -670 -671/// Future produced by [`RecvStream::read()`]. -672/// -673/// [`RecvStream::read()`]: crate::RecvStream::read -674struct Read<'a> { -675 stream: &'a mut RecvStream, -676 buf: ReadBuf<'a>, -677} -678 -679impl Future for Read<'_> { -680 type Output = Result<Option<usize>, ReadError>; -681 -682 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { -683 let this = self.get_mut(); -684 ready!(this.stream.poll_read_buf(cx, &mut this.buf))?; -685 match this.buf.filled().len() { -686 0 if this.buf.capacity() != 0 => Poll::Ready(Ok(None)), -687 n => Poll::Ready(Ok(Some(n))), -688 } -689 } -690} -691 -692/// Future produced by [`RecvStream::read_exact()`]. -693/// -694/// [`RecvStream::read_exact()`]: crate::RecvStream::read_exact -695struct ReadExact<'a> { -696 stream: &'a mut RecvStream, -697 buf: ReadBuf<'a>, -698} -699 -700impl Future for ReadExact<'_> { -701 type Output = Result<(), ReadExactError>; -702 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { -703 let this = self.get_mut(); -704 let mut remaining = this.buf.remaining(); -705 while remaining > 0 { -706 ready!(this.stream.poll_read_buf(cx, &mut this.buf))?; -707 let new = this.buf.remaining(); -708 if new == remaining { -709 return Poll::Ready(Err(ReadExactError::FinishedEarly(this.buf.filled().len()))); -710 } -711 remaining = new; -712 } -713 Poll::Ready(Ok(())) -714 } -715} -716 -717/// Errors that arise from reading from a stream. -718#[derive(Debug, Error, Clone, PartialEq, Eq)] -719pub enum ReadExactError { -720 /// The stream finished before all bytes were read -721 #[error("stream finished early ({0} bytes read)")] -722 FinishedEarly(usize), -723 /// A read error occurred -724 #[error(transparent)] -725 ReadError(#[from] ReadError), +462 /// This operation is cancel-safe. +463 pub async fn read_chunk(&mut self, max_length: usize) -> Result<Option<Chunk>, ReadError> { +464 ReadChunk { +465 stream: &mut self.inner, +466 max_length, +467 ordered: false, +468 } +469 .await +470 } +471 +472 /// Get the identity of this stream +473 pub fn id(&self) -> StreamId { +474 self.inner.id() +475 } +476 +477 /// Check if this stream has been opened during 0-RTT. +478 /// +479 /// In which case any non-idempotent request should be considered dangerous at the application +480 /// level. Because read data is subject to replay attacks. +481 pub fn is_0rtt(&self) -> bool { +482 self.inner.is_0rtt() +483 } +484 +485 /// Stop accepting data +486 /// +487 /// Discards unread data and notifies the peer to stop transmitting. Once stopped, further +488 /// attempts to operate on a stream will yield `ClosedStream` errors. +489 pub fn stop(&mut self, error_code: VarInt) -> Result<(), ClosedStream> { +490 self.inner.stop(error_code) +491 } +492 +493 /// Completes when the stream has been reset by the peer or otherwise closed +494 /// +495 /// Yields `Some` with the reset error code when the stream is reset by the peer. Yields `None` +496 /// when the stream was previously [`stop()`](Self::stop)ed, or when the stream was +497 /// [`finish()`](crate::SendStream::finish)ed by the peer and all data has been received, after +498 /// which it is no longer meaningful for the stream to be reset. +499 /// +500 /// This operation is cancel-safe. +501 pub async fn received_reset(&mut self) -> Result<Option<VarInt>, ResetError> { +502 self.inner.received_reset().await +503 } +504} +505 +506enum ReadStatus<T> { +507 Readable(T), +508 Finished(Option<T>), +509 Failed(Option<T>, proto::ReadError), +510} +511 +512impl<T> From<(Option<T>, Option<proto::ReadError>)> for ReadStatus<T> { +513 fn from(status: (Option<T>, Option<proto::ReadError>)) -> Self { +514 match status { +515 (read, None) => Self::Finished(read), +516 (read, Some(e)) => Self::Failed(read, e), +517 } +518 } +519} +520 +521/// Future produced by [`RecvStream::read_to_end()`]. +522/// +523/// [`RecvStream::read_to_end()`]: crate::RecvStream::read_to_end +524struct ReadToEnd<'a> { +525 stream: &'a mut RecvStream, +526 read: Vec<(Bytes, u64)>, +527 start: u64, +528 end: u64, +529 size_limit: usize, +530} +531 +532impl Future for ReadToEnd<'_> { +533 type Output = Result<Vec<u8>, ReadToEndError>; +534 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { +535 loop { +536 match ready!(self.stream.poll_read_chunk(cx, usize::MAX, false))? { +537 Some(chunk) => { +538 self.start = self.start.min(chunk.offset); +539 let end = chunk.bytes.len() as u64 + chunk.offset; +540 if (end - self.start) > self.size_limit as u64 { +541 return Poll::Ready(Err(ReadToEndError::TooLong)); +542 } +543 self.end = self.end.max(end); +544 self.read.push((chunk.bytes, chunk.offset)); +545 } +546 None => { +547 if self.end == 0 { +548 // Never received anything +549 return Poll::Ready(Ok(Vec::new())); +550 } +551 let start = self.start; +552 let mut buffer = vec![0; (self.end - start) as usize]; +553 for (data, offset) in self.read.drain(..) { +554 let offset = (offset - start) as usize; +555 buffer[offset..offset + data.len()].copy_from_slice(&data); +556 } +557 return Poll::Ready(Ok(buffer)); +558 } +559 } +560 } +561 } +562} +563 +564/// Errors from [`RecvStream::read_to_end`] +565#[derive(Debug, Error, Clone, PartialEq, Eq)] +566pub enum ReadToEndError { +567 /// An error occurred during reading +568 #[error("read error: {0}")] +569 Read(#[from] ReadError), +570 /// The stream is larger than the user-supplied limit +571 #[error("stream too long")] +572 TooLong, +573} +574 +575#[cfg(feature = "futures-io")] +576impl futures_io::AsyncRead for RecvStream { +577 fn poll_read( +578 self: Pin<&mut Self>, +579 cx: &mut Context<'_>, +580 buf: &mut [u8], +581 ) -> Poll<io::Result<usize>> { +582 let mut buf = ReadBuf::new(buf); +583 ready!(Self::poll_read_buf(self.get_mut(), cx, &mut buf))?; +584 Poll::Ready(Ok(buf.filled().len())) +585 } +586} +587 +588impl tokio::io::AsyncRead for RecvStream { +589 fn poll_read( +590 self: Pin<&mut Self>, +591 cx: &mut Context<'_>, +592 buf: &mut ReadBuf<'_>, +593 ) -> Poll<io::Result<()>> { +594 ready!(Self::poll_read_buf(self.get_mut(), cx, buf))?; +595 Poll::Ready(Ok(())) +596 } +597} +598 +599impl Drop for RecvStream { +600 fn drop(&mut self) { +601 if self.all_data_read { +602 debug_assert!( +603 !self +604 .conn +605 .lock_without_waking("RecvStream:drop") +606 .blocked_readers +607 .contains_key(&self.stream), +608 "Stream {} should not have a blocked reader when all data read is true", +609 &self.stream +610 ); +611 return; +612 } +613 let mut conn = self.conn.lock_and_wake("RecvStream::drop"); +614 +615 // clean up any previously registered wakers +616 conn.blocked_readers.remove(&self.stream); +617 +618 if conn.error.is_some() || (self.is_0rtt && conn.check_0rtt().is_err()) { +619 conn.skip_waking(); +620 return; +621 } +622 +623 // Ignore ClosedStream errors +624 let _ = conn.inner.recv_stream(self.stream).stop(0u32.into()); +625 } +626} +627 +628/// Errors that arise from reading from a stream. +629#[derive(Debug, Error, Clone, PartialEq, Eq)] +630pub enum ReadError { +631 /// The peer abandoned transmitting data on this stream +632 /// +633 /// Carries an application-defined error code. +634 #[error("stream reset by peer: error {0}")] +635 Reset(VarInt), +636 /// The connection was lost +637 #[error("connection lost")] +638 ConnectionLost(#[from] ConnectionError), +639 /// The stream has already been stopped, finished, or reset +640 #[error("closed stream")] +641 ClosedStream, +642 /// This was a 0-RTT stream and the server rejected it +643 /// +644 /// Can only occur on clients for 0-RTT streams, which can be opened using +645 /// [`Connecting::into_0rtt()`]. +646 /// +647 /// [`Connecting::into_0rtt()`]: crate::Connecting::into_0rtt() +648 #[error("0-RTT rejected")] +649 ZeroRttRejected, +650} +651 +652impl From<ResetError> for ReadError { +653 fn from(e: ResetError) -> Self { +654 match e { +655 ResetError::ConnectionLost(e) => Self::ConnectionLost(e), +656 ResetError::ZeroRttRejected => Self::ZeroRttRejected, +657 } +658 } +659} +660 +661impl From<ReadError> for io::Error { +662 fn from(x: ReadError) -> Self { +663 use ReadError::*; +664 let kind = match x { +665 Reset { .. } | ZeroRttRejected => io::ErrorKind::ConnectionReset, +666 ConnectionLost(_) | ClosedStream => io::ErrorKind::NotConnected, +667 }; +668 Self::new(kind, x) +669 } +670} +671 +672/// Errors that arise while waiting for a stream to be reset +673#[derive(Debug, Error, Clone, PartialEq, Eq)] +674pub enum ResetError { +675 /// The connection was lost +676 #[error("connection lost")] +677 ConnectionLost(#[from] ConnectionError), +678 /// This was a 0-RTT stream and the server rejected it +679 /// +680 /// Can only occur on clients for 0-RTT streams, which can be opened using +681 /// [`Connecting::into_0rtt()`]. +682 /// +683 /// [`Connecting::into_0rtt()`]: crate::Connecting::into_0rtt() +684 #[error("0-RTT rejected")] +685 ZeroRttRejected, +686} +687 +688impl From<ResetError> for io::Error { +689 fn from(x: ResetError) -> Self { +690 use ResetError::*; +691 let kind = match x { +692 ZeroRttRejected => io::ErrorKind::ConnectionReset, +693 ConnectionLost(_) => io::ErrorKind::NotConnected, +694 }; +695 Self::new(kind, x) +696 } +697} +698 +699/// Future produced by [`RecvStream::read()`]. +700/// +701/// [`RecvStream::read()`]: crate::RecvStream::read +702struct Read<'a> { +703 stream: &'a mut RecvStream, +704 buf: ReadBuf<'a>, +705} +706 +707impl Future for Read<'_> { +708 type Output = Result<Option<usize>, ReadError>; +709 +710 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { +711 let this = self.get_mut(); +712 ready!(this.stream.poll_read_buf(cx, &mut this.buf))?; +713 match this.buf.filled().len() { +714 0 if this.buf.capacity() != 0 => Poll::Ready(Ok(None)), +715 n => Poll::Ready(Ok(Some(n))), +716 } +717 } +718} +719 +720/// Future produced by [`RecvStream::read_exact()`]. +721/// +722/// [`RecvStream::read_exact()`]: crate::RecvStream::read_exact +723struct ReadExact<'a> { +724 stream: &'a mut RecvStream, +725 buf: ReadBuf<'a>, 726} 727 -728/// Future produced by [`RecvStream::read_chunk()`]. -729/// -730/// [`RecvStream::read_chunk()`]: crate::RecvStream::read_chunk -731struct ReadChunk<'a> { -732 stream: &'a mut RecvStream, -733 max_length: usize, -734 ordered: bool, -735} -736 -737impl Future for ReadChunk<'_> { -738 type Output = Result<Option<Chunk>, ReadError>; -739 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { -740 let (max_length, ordered) = (self.max_length, self.ordered); -741 self.stream.poll_read_chunk(cx, max_length, ordered) +728impl Future for ReadExact<'_> { +729 type Output = Result<(), ReadExactError>; +730 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { +731 let this = self.get_mut(); +732 let mut remaining = this.buf.remaining(); +733 while remaining > 0 { +734 ready!(this.stream.poll_read_buf(cx, &mut this.buf))?; +735 let new = this.buf.remaining(); +736 if new == remaining { +737 return Poll::Ready(Err(ReadExactError::FinishedEarly(this.buf.filled().len()))); +738 } +739 remaining = new; +740 } +741 Poll::Ready(Ok(())) 742 } 743} 744 -745/// Future produced by [`RecvStream::read_chunks()`]. -746/// -747/// [`RecvStream::read_chunks()`]: crate::RecvStream::read_chunks -748struct ReadChunks<'a> { -749 stream: &'a mut RecvStream, -750 bufs: &'a mut [Bytes], -751} -752 -753impl Future for ReadChunks<'_> { -754 type Output = Result<Option<usize>, ReadError>; -755 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { -756 let this = self.get_mut(); -757 this.stream.poll_read_chunks(cx, this.bufs) -758 } -759}
    \ No newline at end of file +745/// Errors that arise from reading from a stream. +746#[derive(Debug, Error, Clone, PartialEq, Eq)] +747pub enum ReadExactError { +748 /// The stream finished before all bytes were read +749 #[error("stream finished early ({0} bytes read)")] +750 FinishedEarly(usize), +751 /// A read error occurred +752 #[error(transparent)] +753 ReadError(#[from] ReadError), +754} +755 +756/// Future produced by [`RecvStream::read_chunk()`] or [`UnorderedRecvStream::read_chunk()`]. +757/// +758/// [`RecvStream::read_chunk()`]: crate::RecvStream::read_chunk +759/// [`UnorderedRecvStream::read_chunk()`]: crate::UnorderedRecvStream::read_chunk +760struct ReadChunk<'a> { +761 stream: &'a mut RecvStream, +762 max_length: usize, +763 ordered: bool, +764} +765 +766impl Future for ReadChunk<'_> { +767 type Output = Result<Option<Chunk>, ReadError>; +768 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { +769 let (max_length, ordered) = (self.max_length, self.ordered); +770 self.stream.poll_read_chunk(cx, max_length, ordered) +771 } +772} +773 +774/// Future produced by [`RecvStream::read_many_chunks()`]. +775/// +776/// [`RecvStream::read_many_chunks()`]: crate::RecvStream::read_many_chunks +777struct ReadChunks<'a> { +778 stream: &'a mut RecvStream, +779 bufs: &'a mut [Bytes], +780} +781 +782impl Future for ReadChunks<'_> { +783 type Output = Result<Option<usize>, ReadError>; +784 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { +785 let this = self.get_mut(); +786 this.stream.poll_read_chunks(cx, this.bufs) +787 } +788}
    \ No newline at end of file diff --git a/pr/451/docs/src/noq/send_stream.rs.html b/pr/451/docs/src/noq/send_stream.rs.html index 3978c2371..801f7d568 100644 --- a/pr/451/docs/src/noq/send_stream.rs.html +++ b/pr/451/docs/src/noq/send_stream.rs.html @@ -7,7 +7,7 @@ 7 8use bytes::Bytes; 9use pin_project_lite::pin_project; -10use proto::{ClosedStream, ConnectionError, FinishError, StreamId, Written}; +10use proto::{ClosedStream, ConnectionError, FinishError, StreamId}; 11use thiserror::Error; 12use tokio::sync::futures::OwnedNotified; 13 @@ -79,411 +79,410 @@ 79 Ok(()) 80 } 81 -82 /// Write a slice of [`Bytes`] into this stream, returning how much was written -83 /// -84 /// Bytes to try to write are provided to this method as an array of cheaply cloneable chunks. -85 /// Unless this method errors, it waits until some amount of those bytes can be written into -86 /// this stream, and then writes as much as it can without waiting again. Due to congestion and -87 /// flow control, this may be less than the total number of bytes. -88 /// -89 /// On success, this method both mutates `bufs` and yields an informative [`Written`] struct -90 /// indicating how much was written: +82 /// Writes [`Bytes`] from a slice of buffers into this stream, returning how many bytes were. +83 /// written +84 /// +85 /// Bytes to try to write are provided to this method as an array of cheaply cloneable chunks. +86 /// Unless this method errors, it waits until some amount of those bytes can be written into +87 /// this stream, and then writes as much as it can without waiting again. Due to congestion and +88 /// flow control, this may be less than the total number of bytes. +89 /// +90 /// On success, this method both mutates `bufs` and returns the number of bytes written: 91 /// -92 /// - [`Bytes`] chunks that were fully written are mutated to be [empty](Bytes::is_empty). -93 /// - If a [`Bytes`] chunk was partially written, it is [split to](Bytes::split_to) contain -94 /// only the suffix of bytes that were not written. -95 /// - The yielded [`Written`] struct indicates how many chunks were fully written as well as -96 /// how many bytes were written. +92 /// - `bufs` is advanced past chunks that were fully written. +93 /// - If a [`Bytes`] chunk was partially written, the chunk at the new front of `bufs` is +94 /// [split to](Bytes::split_to) contain only the suffix of bytes that were not written. +95 /// +96 /// # Cancel safety 97 /// -98 /// # Cancel safety -99 /// -100 /// This method is cancellation safe. If this does not resolve, no bytes were written. -101 pub async fn write_chunks(&mut self, bufs: &mut [Bytes]) -> Result<Written, WriteError> { -102 poll_fn(|cx| self.execute_poll(cx, |s| s.write_chunks(bufs))).await -103 } -104 -105 /// Write a single [`Bytes`] into this stream in its entirety -106 /// -107 /// Bytes to write are provided to this method as an single cheaply cloneable chunk. This -108 /// method repeatedly calls [`write_chunks`](Self::write_chunks) until all bytes are written, -109 /// or an error occurs. -110 /// -111 /// # Cancel safety -112 /// -113 /// This method is *not* cancellation safe. Even if this does not resolve, some bytes may have -114 /// been written when previously polled. -115 pub async fn write_chunk(&mut self, buf: Bytes) -> Result<(), WriteError> { -116 self.write_all_chunks(&mut [buf]).await?; -117 Ok(()) -118 } +98 /// This method is cancellation safe. If this does not resolve, no bytes were written. +99 pub async fn write_many_chunks( +100 &mut self, +101 bufs: &mut &mut [Bytes], +102 ) -> Result<usize, WriteError> { +103 poll_fn(|cx| self.execute_poll(cx, |s| s.write_chunks(bufs))).await +104 } +105 +106 /// Writes a single [`Bytes`] into this stream in its entirety. +107 /// +108 /// Bytes to write are provided to this method as a single cheaply cloneable chunk. This +109 /// method repeatedly calls [`write_many_chunks`](Self::write_many_chunks) until all bytes +110 /// are written, or an error occurs. +111 /// +112 /// # Cancel safety +113 /// +114 /// This method is *not* cancellation safe. Even if this does not resolve, some bytes may have +115 /// been written when previously polled. +116 pub async fn write_chunk(&mut self, buf: Bytes) -> Result<(), WriteError> { +117 self.write_all_chunks(&mut [buf]).await +118 } 119 -120 /// Write a slice of [`Bytes`] into this stream in its entirety +120 /// Writes a slice of [`Bytes`] into this stream in its entirety. 121 /// 122 /// Bytes to write are provided to this method as an array of cheaply cloneable chunks. This -123 /// method repeatedly calls [`write_chunks`](Self::write_chunks) until all bytes are written, -124 /// or an error occurs. This method mutates `bufs` by mutating all chunks to be -125 /// [empty](Bytes::is_empty). -126 /// -127 /// # Cancel safety -128 /// -129 /// This method is *not* cancellation safe. Even if this does not resolve, some bytes may have -130 /// been written when previously polled. -131 pub async fn write_all_chunks(&mut self, mut bufs: &mut [Bytes]) -> Result<(), WriteError> { +123 /// method repeatedly calls [`write_many_chunks`](Self::write_many_chunks) until all bytes are +124 /// written, or an error occurs. +125 /// +126 /// # Cancel safety +127 /// +128 /// This method is *not* cancellation safe. Even if this does not resolve, some bytes may have +129 /// been written when previously polled. +130 pub async fn write_all_chunks(&mut self, bufs: &mut [Bytes]) -> Result<(), WriteError> { +131 let mut bufs = &mut bufs[..]; 132 while !bufs.is_empty() { -133 let written = self.write_chunks(bufs).await?; -134 bufs = &mut bufs[written.chunks..]; -135 } -136 Ok(()) -137 } -138 -139 fn execute_poll<F, R>( -140 &mut self, -141 cx: &mut Context<'_>, -142 write_fn: F, -143 ) -> Poll<Result<R, WriteError>> -144 where -145 F: FnOnce(&mut proto::SendStream<'_>) -> Result<R, proto::WriteError>, -146 { -147 use proto::WriteError::*; -148 let mut conn = self.conn.lock_and_wake("SendStream::poll_write"); -149 if self.is_0rtt && conn.check_0rtt().is_err() { -150 conn.skip_waking(); -151 return Poll::Ready(Err(WriteError::ZeroRttRejected)); -152 } -153 if let Some(conn_err) = conn.error.clone() { -154 conn.skip_waking(); -155 return Poll::Ready(Err(WriteError::ConnectionLost(conn_err))); -156 } -157 -158 let result = match write_fn(&mut conn.inner.send_stream(self.stream)) { -159 Ok(result) => result, -160 Err(Blocked) => { -161 conn.blocked_writers.insert(self.stream, cx.waker().clone()); -162 conn.skip_waking(); -163 return Poll::Pending; -164 } -165 Err(Stopped(error_code)) => { -166 conn.skip_waking(); -167 return Poll::Ready(Err(WriteError::Stopped(error_code))); -168 } -169 Err(ClosedStream) => { -170 conn.skip_waking(); -171 return Poll::Ready(Err(WriteError::ClosedStream)); -172 } -173 }; -174 -175 Poll::Ready(Ok(result)) -176 } -177 -178 /// Notify the peer that no more data will ever be written to this stream -179 /// -180 /// It is an error to write to a [`SendStream`] after `finish()`ing it. [`reset()`](Self::reset) -181 /// may still be called after `finish` to abandon transmission of any stream data that might -182 /// still be buffered. -183 /// -184 /// To wait for the peer to receive all buffered stream data, see [`stopped()`](Self::stopped). -185 /// -186 /// May fail if [`finish()`](Self::finish) or [`reset()`](Self::reset) was previously -187 /// called. This error is harmless and serves only to indicate that the caller may have -188 /// incorrect assumptions about the stream's state. -189 pub fn finish(&mut self) -> Result<(), ClosedStream> { -190 let mut conn = self.conn.lock_and_wake("finish"); -191 if let Err(e) = conn.inner.send_stream(self.stream).finish() { -192 conn.skip_waking(); -193 match e { -194 FinishError::ClosedStream => Err(ClosedStream::default()), -195 // Harmless. If the application needs to know about stopped streams at this point, it -196 // should call `stopped`. -197 FinishError::Stopped(_) => Ok(()), -198 } -199 } else { -200 Ok(()) -201 } -202 } -203 -204 /// Close the send stream immediately. -205 /// -206 /// No new data can be written after calling this method. Locally buffered data is dropped, and -207 /// previously transmitted data will no longer be retransmitted if lost. If an attempt has -208 /// already been made to finish the stream, the peer may still receive all written data. -209 /// -210 /// May fail if [`finish()`](Self::finish) or [`reset()`](Self::reset) was previously -211 /// called. This error is harmless and serves only to indicate that the caller may have -212 /// incorrect assumptions about the stream's state. -213 pub fn reset(&mut self, error_code: VarInt) -> Result<(), ClosedStream> { -214 let mut conn = self.conn.lock_and_wake("SendStream::reset"); -215 if self.is_0rtt && conn.check_0rtt().is_err() { -216 conn.skip_waking(); -217 return Ok(()); -218 } -219 conn.inner.send_stream(self.stream).reset(error_code)?; -220 Ok(()) -221 } -222 -223 /// Set the priority of the send stream -224 /// -225 /// Every send stream has an initial priority of 0. Locally buffered data from streams with -226 /// higher priority will be transmitted before data from streams with lower priority. Changing -227 /// the priority of a stream with pending data may only take effect after that data has been -228 /// transmitted. Using many different priority levels per connection may have a negative -229 /// impact on performance. -230 pub fn set_priority(&self, priority: i32) -> Result<(), ClosedStream> { -231 let mut conn = self.conn.lock_without_waking("SendStream::set_priority"); -232 conn.inner.send_stream(self.stream).set_priority(priority)?; -233 Ok(()) -234 } -235 -236 /// Get the priority of the send stream -237 pub fn priority(&self) -> Result<i32, ClosedStream> { -238 let mut conn = self.conn.lock_without_waking("SendStream::priority"); -239 conn.inner.send_stream(self.stream).priority() -240 } -241 -242 /// Completes when the peer stops the stream or reads the stream to completion -243 /// -244 /// Yields `Some` with the stop error code if the peer stops the stream. Yields `None` if the -245 /// local side [`finish()`](Self::finish)es the stream and then the peer acknowledges receipt -246 /// of all stream data (although not necessarily the processing of it), after which the peer -247 /// closing the stream is no longer meaningful. -248 /// -249 /// For a variety of reasons, the peer may not send acknowledgements immediately upon receiving -250 /// data. As such, relying on `stopped` to know when the peer has read a stream to completion -251 /// may introduce more latency than using an application-level response of some sort. -252 pub fn stopped(&self) -> Stopped { -253 let notified = { -254 // Create an `OwnedNotified` to move into the future. By creating it before the first poll, -255 // we make sure that we don't miss any notifications. -256 let mut conn = self.conn.lock_without_waking("SendStream::stopped"); -257 conn.stopped -258 .entry(self.stream) -259 .or_default() -260 .clone() -261 .notified_owned() -262 }; -263 Stopped { -264 conn: self.conn.clone(), -265 stream: self.stream, -266 is_0rtt: self.is_0rtt, -267 notified, -268 } -269 } -270 -271 /// Get the identity of this stream -272 pub fn id(&self) -> StreamId { -273 self.stream -274 } -275 -276 /// Attempt to write bytes from buf into the stream. -277 /// -278 /// On success, returns Poll::Ready(Ok(num_bytes_written)). -279 /// -280 /// If the stream is not ready for writing, the method returns Poll::Pending and arranges -281 /// for the current task (via cx.waker().wake_by_ref()) to receive a notification when the -282 /// stream becomes writable or is closed. -283 pub fn poll_write( -284 self: Pin<&mut Self>, -285 cx: &mut Context<'_>, -286 buf: &[u8], -287 ) -> Poll<Result<usize, WriteError>> { -288 pin!(self.get_mut().write(buf)).as_mut().poll(cx) -289 } -290} -291 -292/// Check if a send stream is stopped. -293/// -294/// Returns `Some` if the stream is stopped or the connection is closed. -295/// Returns `None` if the stream is not stopped. -296fn send_stream_stopped( -297 conn: &mut State, -298 stream: StreamId, -299 is_0rtt: bool, -300) -> Option<Result<Option<VarInt>, StoppedError>> { -301 if is_0rtt && conn.check_0rtt().is_err() { -302 return Some(Err(StoppedError::ZeroRttRejected)); -303 } -304 match conn.inner.send_stream(stream).stopped() { -305 Err(ClosedStream { .. }) => Some(Ok(None)), -306 Ok(Some(error_code)) => Some(Ok(Some(error_code))), -307 Ok(None) => conn.error.clone().map(|error| Err(error.into())), -308 } -309} -310 -311#[cfg(feature = "futures-io")] -312impl futures_io::AsyncWrite for SendStream { -313 fn poll_write( -314 self: Pin<&mut Self>, -315 cx: &mut Context<'_>, -316 buf: &[u8], -317 ) -> Poll<io::Result<usize>> { -318 self.poll_write(cx, buf).map_err(Into::into) -319 } -320 -321 fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> { -322 Poll::Ready(Ok(())) -323 } -324 -325 fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> { -326 Poll::Ready(self.get_mut().finish().map_err(Into::into)) -327 } -328} -329 -330impl tokio::io::AsyncWrite for SendStream { -331 fn poll_write( -332 self: Pin<&mut Self>, -333 cx: &mut Context<'_>, -334 buf: &[u8], -335 ) -> Poll<io::Result<usize>> { -336 self.poll_write(cx, buf).map_err(Into::into) -337 } -338 -339 fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> { -340 Poll::Ready(Ok(())) -341 } -342 -343 fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> { -344 Poll::Ready(self.get_mut().finish().map_err(Into::into)) -345 } -346} -347 -348impl Drop for SendStream { -349 fn drop(&mut self) { -350 let mut conn = self.conn.lock_and_wake("SendStream::drop"); -351 -352 // clean up any previously registered wakers -353 conn.blocked_writers.remove(&self.stream); -354 -355 if conn.error.is_some() || (self.is_0rtt && conn.check_0rtt().is_err()) { -356 conn.skip_waking(); -357 return; -358 } -359 match conn.inner.send_stream(self.stream).finish() { -360 Ok(()) => {} -361 Err(FinishError::Stopped(reason)) => { -362 if conn.inner.send_stream(self.stream).reset(reason).is_err() { -363 conn.skip_waking() -364 } -365 } -366 // Already finished or reset, which is fine. -367 Err(FinishError::ClosedStream) => { -368 conn.skip_waking(); -369 } -370 } -371 } -372} -373 -374/// Errors that arise from writing to a stream -375#[derive(Debug, Error, Clone, PartialEq, Eq)] -376pub enum WriteError { -377 /// The peer is no longer accepting data on this stream -378 /// -379 /// Carries an application-defined error code. -380 #[error("sending stopped by peer: error {0}")] -381 Stopped(VarInt), -382 /// The connection was lost -383 #[error("connection lost")] -384 ConnectionLost(#[from] ConnectionError), -385 /// The stream has already been finished or reset -386 #[error("closed stream")] -387 ClosedStream, -388 /// This was a 0-RTT stream and the server rejected it -389 /// -390 /// Can only occur on clients for 0-RTT streams, which can be opened using -391 /// [`Connecting::into_0rtt()`]. -392 /// -393 /// [`Connecting::into_0rtt()`]: crate::Connecting::into_0rtt() -394 #[error("0-RTT rejected")] -395 ZeroRttRejected, -396} -397 -398impl From<ClosedStream> for WriteError { -399 #[inline] -400 fn from(_: ClosedStream) -> Self { -401 Self::ClosedStream -402 } -403} -404 -405impl From<StoppedError> for WriteError { -406 fn from(x: StoppedError) -> Self { -407 match x { -408 StoppedError::ConnectionLost(e) => Self::ConnectionLost(e), -409 StoppedError::ZeroRttRejected => Self::ZeroRttRejected, -410 } -411 } -412} -413 -414impl From<WriteError> for io::Error { -415 fn from(x: WriteError) -> Self { -416 use WriteError::*; -417 let kind = match x { -418 Stopped(_) | ZeroRttRejected => io::ErrorKind::ConnectionReset, -419 ConnectionLost(_) | ClosedStream => io::ErrorKind::NotConnected, -420 }; -421 Self::new(kind, x) -422 } -423} -424 -425/// Errors that arise while monitoring for a send stream stop from the peer -426#[derive(Debug, Error, Clone, PartialEq, Eq)] -427pub enum StoppedError { -428 /// The connection was lost -429 #[error("connection lost")] -430 ConnectionLost(#[from] ConnectionError), -431 /// This was a 0-RTT stream and the server rejected it -432 /// -433 /// Can only occur on clients for 0-RTT streams, which can be opened using -434 /// [`Connecting::into_0rtt()`]. -435 /// -436 /// [`Connecting::into_0rtt()`]: crate::Connecting::into_0rtt() -437 #[error("0-RTT rejected")] -438 ZeroRttRejected, -439} -440 -441impl From<StoppedError> for io::Error { -442 fn from(x: StoppedError) -> Self { -443 use StoppedError::*; -444 let kind = match x { -445 ZeroRttRejected => io::ErrorKind::ConnectionReset, -446 ConnectionLost(_) => io::ErrorKind::NotConnected, -447 }; -448 Self::new(kind, x) -449 } -450} -451 -452pin_project! { -453 /// Future returned from [`SendStream::stopped`]. -454 #[derive(Debug)] -455 pub struct Stopped { -456 conn: ConnectionRef, -457 stream: StreamId, -458 is_0rtt: bool, -459 #[pin] -460 notified: OwnedNotified, -461 } -462} -463 -464impl Future for Stopped { -465 type Output = Result<Option<VarInt>, StoppedError>; -466 -467 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { -468 let mut this = self.project(); -469 loop { -470 let mut conn = this.conn.lock_without_waking("SendStream::stopped"); -471 // Check if the stream is stopped before polling the notify. This makes sure that -472 // no wakeups are missed. -473 if let Some(output) = send_stream_stopped(&mut conn, *this.stream, *this.is_0rtt) { -474 return Poll::Ready(output); -475 } -476 std::task::ready!(this.notified.as_mut().poll(cx)); -477 } -478 } -479} -480 -481#[cfg(test)] -482mod tests { -483 fn check_is_send_sync<A: Send + Sync>() {} -484 -485 #[allow(dead_code)] -486 fn test_bounds() { -487 check_is_send_sync::<super::Stopped>(); -488 } -489}
    \ No newline at end of file +133 self.write_many_chunks(&mut bufs).await?; +134 } +135 Ok(()) +136 } +137 +138 fn execute_poll<F, R>( +139 &mut self, +140 cx: &mut Context<'_>, +141 write_fn: F, +142 ) -> Poll<Result<R, WriteError>> +143 where +144 F: FnOnce(&mut proto::SendStream<'_>) -> Result<R, proto::WriteError>, +145 { +146 use proto::WriteError::*; +147 let mut conn = self.conn.lock_and_wake("SendStream::poll_write"); +148 if self.is_0rtt && conn.check_0rtt().is_err() { +149 conn.skip_waking(); +150 return Poll::Ready(Err(WriteError::ZeroRttRejected)); +151 } +152 if let Some(conn_err) = conn.error.clone() { +153 conn.skip_waking(); +154 return Poll::Ready(Err(WriteError::ConnectionLost(conn_err))); +155 } +156 +157 let result = match write_fn(&mut conn.inner.send_stream(self.stream)) { +158 Ok(result) => result, +159 Err(Blocked) => { +160 conn.blocked_writers.insert(self.stream, cx.waker().clone()); +161 conn.skip_waking(); +162 return Poll::Pending; +163 } +164 Err(Stopped(error_code)) => { +165 conn.skip_waking(); +166 return Poll::Ready(Err(WriteError::Stopped(error_code))); +167 } +168 Err(ClosedStream) => { +169 conn.skip_waking(); +170 return Poll::Ready(Err(WriteError::ClosedStream)); +171 } +172 }; +173 +174 Poll::Ready(Ok(result)) +175 } +176 +177 /// Notify the peer that no more data will ever be written to this stream +178 /// +179 /// It is an error to write to a [`SendStream`] after `finish()`ing it. [`reset()`](Self::reset) +180 /// may still be called after `finish` to abandon transmission of any stream data that might +181 /// still be buffered. +182 /// +183 /// To wait for the peer to receive all buffered stream data, see [`stopped()`](Self::stopped). +184 /// +185 /// May fail if [`finish()`](Self::finish) or [`reset()`](Self::reset) was previously +186 /// called. This error is harmless and serves only to indicate that the caller may have +187 /// incorrect assumptions about the stream's state. +188 pub fn finish(&mut self) -> Result<(), ClosedStream> { +189 let mut conn = self.conn.lock_and_wake("finish"); +190 if let Err(e) = conn.inner.send_stream(self.stream).finish() { +191 conn.skip_waking(); +192 match e { +193 FinishError::ClosedStream => Err(ClosedStream::default()), +194 // Harmless. If the application needs to know about stopped streams at this point, it +195 // should call `stopped`. +196 FinishError::Stopped(_) => Ok(()), +197 } +198 } else { +199 Ok(()) +200 } +201 } +202 +203 /// Close the send stream immediately. +204 /// +205 /// No new data can be written after calling this method. Locally buffered data is dropped, and +206 /// previously transmitted data will no longer be retransmitted if lost. If an attempt has +207 /// already been made to finish the stream, the peer may still receive all written data. +208 /// +209 /// May fail if [`finish()`](Self::finish) or [`reset()`](Self::reset) was previously +210 /// called. This error is harmless and serves only to indicate that the caller may have +211 /// incorrect assumptions about the stream's state. +212 pub fn reset(&mut self, error_code: VarInt) -> Result<(), ClosedStream> { +213 let mut conn = self.conn.lock_and_wake("SendStream::reset"); +214 if self.is_0rtt && conn.check_0rtt().is_err() { +215 conn.skip_waking(); +216 return Ok(()); +217 } +218 conn.inner.send_stream(self.stream).reset(error_code)?; +219 Ok(()) +220 } +221 +222 /// Set the priority of the send stream +223 /// +224 /// Every send stream has an initial priority of 0. Locally buffered data from streams with +225 /// higher priority will be transmitted before data from streams with lower priority. Changing +226 /// the priority of a stream with pending data may only take effect after that data has been +227 /// transmitted. Using many different priority levels per connection may have a negative +228 /// impact on performance. +229 pub fn set_priority(&self, priority: i32) -> Result<(), ClosedStream> { +230 let mut conn = self.conn.lock_without_waking("SendStream::set_priority"); +231 conn.inner.send_stream(self.stream).set_priority(priority)?; +232 Ok(()) +233 } +234 +235 /// Get the priority of the send stream +236 pub fn priority(&self) -> Result<i32, ClosedStream> { +237 let mut conn = self.conn.lock_without_waking("SendStream::priority"); +238 conn.inner.send_stream(self.stream).priority() +239 } +240 +241 /// Completes when the peer stops the stream or reads the stream to completion +242 /// +243 /// Yields `Some` with the stop error code if the peer stops the stream. Yields `None` if the +244 /// local side [`finish()`](Self::finish)es the stream and then the peer acknowledges receipt +245 /// of all stream data (although not necessarily the processing of it), after which the peer +246 /// closing the stream is no longer meaningful. +247 /// +248 /// For a variety of reasons, the peer may not send acknowledgements immediately upon receiving +249 /// data. As such, relying on `stopped` to know when the peer has read a stream to completion +250 /// may introduce more latency than using an application-level response of some sort. +251 pub fn stopped(&self) -> Stopped { +252 let notified = { +253 // Create an `OwnedNotified` to move into the future. By creating it before the first poll, +254 // we make sure that we don't miss any notifications. +255 let mut conn = self.conn.lock_without_waking("SendStream::stopped"); +256 conn.stopped +257 .entry(self.stream) +258 .or_default() +259 .clone() +260 .notified_owned() +261 }; +262 Stopped { +263 conn: self.conn.clone(), +264 stream: self.stream, +265 is_0rtt: self.is_0rtt, +266 notified, +267 } +268 } +269 +270 /// Get the identity of this stream +271 pub fn id(&self) -> StreamId { +272 self.stream +273 } +274 +275 /// Attempt to write bytes from buf into the stream. +276 /// +277 /// On success, returns Poll::Ready(Ok(num_bytes_written)). +278 /// +279 /// If the stream is not ready for writing, the method returns Poll::Pending and arranges +280 /// for the current task (via cx.waker().wake_by_ref()) to receive a notification when the +281 /// stream becomes writable or is closed. +282 pub fn poll_write( +283 self: Pin<&mut Self>, +284 cx: &mut Context<'_>, +285 buf: &[u8], +286 ) -> Poll<Result<usize, WriteError>> { +287 pin!(self.get_mut().write(buf)).as_mut().poll(cx) +288 } +289} +290 +291/// Check if a send stream is stopped. +292/// +293/// Returns `Some` if the stream is stopped or the connection is closed. +294/// Returns `None` if the stream is not stopped. +295fn send_stream_stopped( +296 conn: &mut State, +297 stream: StreamId, +298 is_0rtt: bool, +299) -> Option<Result<Option<VarInt>, StoppedError>> { +300 if is_0rtt && conn.check_0rtt().is_err() { +301 return Some(Err(StoppedError::ZeroRttRejected)); +302 } +303 match conn.inner.send_stream(stream).stopped() { +304 Err(ClosedStream { .. }) => Some(Ok(None)), +305 Ok(Some(error_code)) => Some(Ok(Some(error_code))), +306 Ok(None) => conn.error.clone().map(|error| Err(error.into())), +307 } +308} +309 +310#[cfg(feature = "futures-io")] +311impl futures_io::AsyncWrite for SendStream { +312 fn poll_write( +313 self: Pin<&mut Self>, +314 cx: &mut Context<'_>, +315 buf: &[u8], +316 ) -> Poll<io::Result<usize>> { +317 self.poll_write(cx, buf).map_err(Into::into) +318 } +319 +320 fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> { +321 Poll::Ready(Ok(())) +322 } +323 +324 fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> { +325 Poll::Ready(self.get_mut().finish().map_err(Into::into)) +326 } +327} +328 +329impl tokio::io::AsyncWrite for SendStream { +330 fn poll_write( +331 self: Pin<&mut Self>, +332 cx: &mut Context<'_>, +333 buf: &[u8], +334 ) -> Poll<io::Result<usize>> { +335 self.poll_write(cx, buf).map_err(Into::into) +336 } +337 +338 fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> { +339 Poll::Ready(Ok(())) +340 } +341 +342 fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> { +343 Poll::Ready(self.get_mut().finish().map_err(Into::into)) +344 } +345} +346 +347impl Drop for SendStream { +348 fn drop(&mut self) { +349 let mut conn = self.conn.lock_and_wake("SendStream::drop"); +350 +351 // clean up any previously registered wakers +352 conn.blocked_writers.remove(&self.stream); +353 +354 if conn.error.is_some() || (self.is_0rtt && conn.check_0rtt().is_err()) { +355 conn.skip_waking(); +356 return; +357 } +358 match conn.inner.send_stream(self.stream).finish() { +359 Ok(()) => {} +360 Err(FinishError::Stopped(reason)) => { +361 if conn.inner.send_stream(self.stream).reset(reason).is_err() { +362 conn.skip_waking() +363 } +364 } +365 // Already finished or reset, which is fine. +366 Err(FinishError::ClosedStream) => { +367 conn.skip_waking(); +368 } +369 } +370 } +371} +372 +373/// Errors that arise from writing to a stream +374#[derive(Debug, Error, Clone, PartialEq, Eq)] +375pub enum WriteError { +376 /// The peer is no longer accepting data on this stream +377 /// +378 /// Carries an application-defined error code. +379 #[error("sending stopped by peer: error {0}")] +380 Stopped(VarInt), +381 /// The connection was lost +382 #[error("connection lost")] +383 ConnectionLost(#[from] ConnectionError), +384 /// The stream has already been finished or reset +385 #[error("closed stream")] +386 ClosedStream, +387 /// This was a 0-RTT stream and the server rejected it +388 /// +389 /// Can only occur on clients for 0-RTT streams, which can be opened using +390 /// [`Connecting::into_0rtt()`]. +391 /// +392 /// [`Connecting::into_0rtt()`]: crate::Connecting::into_0rtt() +393 #[error("0-RTT rejected")] +394 ZeroRttRejected, +395} +396 +397impl From<ClosedStream> for WriteError { +398 #[inline] +399 fn from(_: ClosedStream) -> Self { +400 Self::ClosedStream +401 } +402} +403 +404impl From<StoppedError> for WriteError { +405 fn from(x: StoppedError) -> Self { +406 match x { +407 StoppedError::ConnectionLost(e) => Self::ConnectionLost(e), +408 StoppedError::ZeroRttRejected => Self::ZeroRttRejected, +409 } +410 } +411} +412 +413impl From<WriteError> for io::Error { +414 fn from(x: WriteError) -> Self { +415 use WriteError::*; +416 let kind = match x { +417 Stopped(_) | ZeroRttRejected => io::ErrorKind::ConnectionReset, +418 ConnectionLost(_) | ClosedStream => io::ErrorKind::NotConnected, +419 }; +420 Self::new(kind, x) +421 } +422} +423 +424/// Errors that arise while monitoring for a send stream stop from the peer +425#[derive(Debug, Error, Clone, PartialEq, Eq)] +426pub enum StoppedError { +427 /// The connection was lost +428 #[error("connection lost")] +429 ConnectionLost(#[from] ConnectionError), +430 /// This was a 0-RTT stream and the server rejected it +431 /// +432 /// Can only occur on clients for 0-RTT streams, which can be opened using +433 /// [`Connecting::into_0rtt()`]. +434 /// +435 /// [`Connecting::into_0rtt()`]: crate::Connecting::into_0rtt() +436 #[error("0-RTT rejected")] +437 ZeroRttRejected, +438} +439 +440impl From<StoppedError> for io::Error { +441 fn from(x: StoppedError) -> Self { +442 use StoppedError::*; +443 let kind = match x { +444 ZeroRttRejected => io::ErrorKind::ConnectionReset, +445 ConnectionLost(_) => io::ErrorKind::NotConnected, +446 }; +447 Self::new(kind, x) +448 } +449} +450 +451pin_project! { +452 /// Future returned from [`SendStream::stopped`]. +453 #[derive(Debug)] +454 pub struct Stopped { +455 conn: ConnectionRef, +456 stream: StreamId, +457 is_0rtt: bool, +458 #[pin] +459 notified: OwnedNotified, +460 } +461} +462 +463impl Future for Stopped { +464 type Output = Result<Option<VarInt>, StoppedError>; +465 +466 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { +467 let mut this = self.project(); +468 loop { +469 let mut conn = this.conn.lock_without_waking("SendStream::stopped"); +470 // Check if the stream is stopped before polling the notify. This makes sure that +471 // no wakeups are missed. +472 if let Some(output) = send_stream_stopped(&mut conn, *this.stream, *this.is_0rtt) { +473 return Poll::Ready(output); +474 } +475 std::task::ready!(this.notified.as_mut().poll(cx)); +476 } +477 } +478} +479 +480#[cfg(test)] +481mod tests { +482 fn check_is_send_sync<A: Send + Sync>() {} +483 +484 #[allow(dead_code)] +485 fn test_bounds() { +486 check_is_send_sync::<super::Stopped>(); +487 } +488}
    \ No newline at end of file diff --git a/pr/451/docs/src/noq_proto/cid_queue.rs.html b/pr/451/docs/src/noq_proto/cid_queue.rs.html index 45ad9d177..1b520749d 100644 --- a/pr/451/docs/src/noq_proto/cid_queue.rs.html +++ b/pr/451/docs/src/noq_proto/cid_queue.rs.html @@ -128,390 +128,392 @@ 128 /// 129 /// If there's no more CIDs in the ready set, this will return None. 130 /// CIDs marked as reserved will be skipped when the active one advances. -131 pub(crate) fn next_reserved(&mut self) -> Option<ConnectionId> { -132 let (i, cid_data) = self.iter_from_reserved().nth(1)?; -133 -134 self.cursor_reserved = (self.cursor_reserved + i) % Self::LEN; -135 Some(cid_data.0) -136 } -137 -138 /// Returns the number of unused CIDs (neither active nor reserved). -139 pub(crate) fn remaining(&self) -> usize { -140 self.iter_from_reserved() -141 .count() -142 .checked_sub(1) -143 .expect("iterator is non empty") -144 } -145 -146 /// Iterate CIDs in CidQueue that are not `None`, including the active CID -147 fn iter_from_active(&self) -> impl Iterator<Item = (usize, CidData)> + '_ { -148 (0..Self::LEN).filter_map(move |step| { -149 let index = (self.cursor + step) % Self::LEN; -150 self.buffer[index].map(|cid_data| (step, cid_data)) -151 }) -152 } -153 -154 /// Iterate CIDs in CidQueue that are not `None`, from [`Self::cursor_reserved`]. -155 /// -156 /// The iterator will always have at least one item, as it will include the active CID when no -157 /// CID has been reserved, or the last reserved CID otherwise. -158 /// -159 /// Along with the CID, it returns the offset counted from [`Self::cursor_reserved`] where the -160 /// CID is stored. -161 fn iter_from_reserved(&self) -> impl Iterator<Item = (usize, CidData)> + '_ { -162 (0..(Self::LEN - self.reserved_len())).filter_map(move |step| { -163 let index = (self.cursor_reserved + step) % Self::LEN; -164 self.buffer[index].map(|cid_data| (step, cid_data)) -165 }) -166 } -167 -168 /// The length of the internal buffer's section of CIDs that are marked as reserved. -169 fn reserved_len(&self) -> usize { -170 if self.cursor_reserved >= self.cursor { -171 self.cursor_reserved - self.cursor -172 } else { -173 self.cursor_reserved + Self::LEN - self.cursor -174 } -175 } -176 -177 /// Replace the initial CID -178 pub(crate) fn update_initial_cid(&mut self, cid: ConnectionId) { -179 debug_assert_eq!(self.offset, 0); -180 self.buffer[self.cursor] = Some(CidData(cid, None)); -181 } -182 -183 /// Return active remote CID itself -184 pub(crate) fn active(&self) -> ConnectionId { -185 self.buffer[self.cursor].unwrap().0 -186 } -187 -188 /// Return the sequence number of active remote CID -189 pub(crate) fn active_seq(&self) -> u64 { -190 self.offset -191 } -192 -193 pub(crate) const LEN: usize = 5; -194} -195 -196#[derive(Debug, Copy, Clone, Eq, PartialEq)] -197pub(crate) enum InsertError { -198 /// CID was already retired -199 Retired, -200 /// Sequence number violates the leading edge of the window -201 ExceedsLimit, -202} -203 -204#[cfg(test)] -205mod tests { -206 use super::*; -207 -208 fn cid(sequence: u64, retire_prior_to: u64) -> NewConnectionId { -209 NewConnectionId { -210 path_id: None, -211 sequence, -212 id: ConnectionId::new(&sequence.to_be_bytes()), -213 reset_token: ResetToken::from([0xCD; crate::RESET_TOKEN_SIZE]), -214 retire_prior_to, -215 } -216 } -217 -218 fn initial_cid() -> ConnectionId { -219 ConnectionId::new(&[0xFF; 8]) -220 } -221 -222 #[test] -223 fn next_dense() { -224 let mut q = CidQueue::new(initial_cid()); -225 assert!(q.next().is_none()); -226 assert!(q.next().is_none()); -227 -228 for i in 1..CidQueue::LEN as u64 { -229 q.insert(cid(i, 0)).unwrap(); -230 } -231 for i in 1..CidQueue::LEN as u64 { -232 let (_, retire) = q.next().unwrap(); -233 assert_eq!(q.active_seq(), i); -234 assert_eq!(retire.end - retire.start, 1); -235 } -236 assert!(q.next().is_none()); -237 } -238 -239 #[test] -240 fn next_sparse() { -241 let mut q = CidQueue::new(initial_cid()); -242 let seqs = (1..CidQueue::LEN as u64).filter(|x| x % 2 == 0); -243 for i in seqs.clone() { -244 q.insert(cid(i, 0)).unwrap(); -245 } -246 for i in seqs { -247 let (_, retire) = q.next().unwrap(); -248 dbg!(&retire); -249 assert_eq!(q.active_seq(), i); -250 assert_eq!(retire, (q.active_seq().saturating_sub(2))..q.active_seq()); -251 } -252 assert!(q.next().is_none()); -253 } -254 -255 #[test] -256 fn wrap() { -257 let mut q = CidQueue::new(initial_cid()); -258 -259 for i in 1..CidQueue::LEN as u64 { -260 q.insert(cid(i, 0)).unwrap(); -261 } -262 for _ in 1..(CidQueue::LEN as u64 - 1) { -263 q.next().unwrap(); -264 } -265 for i in CidQueue::LEN as u64..(CidQueue::LEN as u64 + 3) { -266 q.insert(cid(i, 0)).unwrap(); -267 } -268 for i in (CidQueue::LEN as u64 - 1)..(CidQueue::LEN as u64 + 3) { -269 q.next().unwrap(); -270 assert_eq!(q.active_seq(), i); -271 } -272 assert!(q.next().is_none()); -273 } -274 -275 #[test] -276 fn retire_dense() { -277 let mut q = CidQueue::new(initial_cid()); -278 -279 for i in 1..CidQueue::LEN as u64 { -280 q.insert(cid(i, 0)).unwrap(); -281 } -282 assert_eq!(q.active_seq(), 0); -283 -284 assert_eq!(q.insert(cid(4, 2)).unwrap().unwrap().0, 0..2); -285 assert_eq!(q.active_seq(), 2); -286 assert_eq!(q.insert(cid(4, 2)), Ok(None)); -287 -288 for i in 2..(CidQueue::LEN as u64 - 1) { -289 let _ = q.next().unwrap(); -290 assert_eq!(q.active_seq(), i + 1); -291 assert_eq!(q.insert(cid(i + 1, i + 1)), Ok(None)); -292 } -293 -294 assert!(q.next().is_none()); -295 } -296 -297 #[test] -298 fn retire_sparse() { -299 // Retiring CID 0 when CID 1 is not known should retire CID 1 as we move to CID 2 -300 let mut q = CidQueue::new(initial_cid()); -301 q.insert(cid(2, 0)).unwrap(); -302 assert_eq!(q.insert(cid(3, 1)).unwrap().unwrap().0, 0..2,); -303 assert_eq!(q.active_seq(), 2); -304 } -305 -306 #[test] -307 fn retire_many() { -308 let mut q = CidQueue::new(initial_cid()); -309 q.insert(cid(2, 0)).unwrap(); -310 assert_eq!( -311 q.insert(cid(1_000_000, 1_000_000)).unwrap().unwrap().0, -312 0..CidQueue::LEN as u64, -313 ); -314 assert_eq!(q.active_seq(), 1_000_000); -315 } -316 -317 #[test] -318 fn insert_limit() { -319 let mut q = CidQueue::new(initial_cid()); -320 assert_eq!(q.insert(cid(CidQueue::LEN as u64 - 1, 0)), Ok(None)); -321 assert_eq!( -322 q.insert(cid(CidQueue::LEN as u64, 0)), -323 Err(InsertError::ExceedsLimit) -324 ); -325 } -326 -327 #[test] -328 fn insert_duplicate() { -329 let mut q = CidQueue::new(initial_cid()); -330 q.insert(cid(0, 0)).unwrap(); -331 q.insert(cid(0, 0)).unwrap(); -332 } -333 -334 #[test] -335 fn insert_retired() { -336 let mut q = CidQueue::new(initial_cid()); -337 assert_eq!( -338 q.insert(cid(0, 0)), -339 Ok(None), -340 "reinserting active CID succeeds" -341 ); -342 assert!(q.next().is_none(), "active CID isn't requeued"); -343 q.insert(cid(1, 0)).unwrap(); -344 q.next().unwrap(); -345 assert_eq!( -346 q.insert(cid(0, 0)), -347 Err(InsertError::Retired), -348 "previous active CID is already retired" -349 ); -350 } -351 -352 #[test] -353 fn retire_then_insert_next() { -354 let mut q = CidQueue::new(initial_cid()); -355 for i in 1..CidQueue::LEN as u64 { -356 q.insert(cid(i, 0)).unwrap(); -357 } -358 q.next().unwrap(); -359 q.insert(cid(CidQueue::LEN as u64, 0)).unwrap(); -360 assert_eq!( -361 q.insert(cid(CidQueue::LEN as u64 + 1, 0)), -362 Err(InsertError::ExceedsLimit) -363 ); -364 } -365 -366 #[test] -367 fn always_valid() { -368 let mut q = CidQueue::new(initial_cid()); -369 assert!(q.next().is_none()); -370 assert_eq!(q.active(), initial_cid()); -371 assert_eq!(q.active_seq(), 0); -372 } -373 -374 #[test] -375 fn reserved_smoke() { -376 let mut q = CidQueue::new(initial_cid()); -377 assert_eq!(q.next_reserved(), None); -378 -379 let one = cid(1, 0); -380 q.insert(one).unwrap(); -381 assert_eq!(q.next_reserved(), Some(one.id)); -382 -383 let two = cid(2, 2); -384 let (retired_range, reset_token) = q.insert(two).unwrap().unwrap(); -385 assert_eq!(reset_token, two.reset_token); -386 assert_eq!(retired_range, 0..2); -387 -388 assert_eq!(q.next_reserved(), None); +131 #[allow(dead_code)] +132 pub(crate) fn next_reserved(&mut self) -> Option<ConnectionId> { +133 let (i, cid_data) = self.iter_from_reserved().nth(1)?; +134 +135 self.cursor_reserved = (self.cursor_reserved + i) % Self::LEN; +136 Some(cid_data.0) +137 } +138 +139 /// Returns the number of unused CIDs (neither active nor reserved). +140 #[allow(unused)] +141 pub(crate) fn remaining(&self) -> usize { +142 self.iter_from_reserved() +143 .count() +144 .checked_sub(1) +145 .expect("iterator is non empty") +146 } +147 +148 /// Iterate CIDs in CidQueue that are not `None`, including the active CID +149 fn iter_from_active(&self) -> impl Iterator<Item = (usize, CidData)> + '_ { +150 (0..Self::LEN).filter_map(move |step| { +151 let index = (self.cursor + step) % Self::LEN; +152 self.buffer[index].map(|cid_data| (step, cid_data)) +153 }) +154 } +155 +156 /// Iterate CIDs in CidQueue that are not `None`, from [`Self::cursor_reserved`]. +157 /// +158 /// The iterator will always have at least one item, as it will include the active CID when no +159 /// CID has been reserved, or the last reserved CID otherwise. +160 /// +161 /// Along with the CID, it returns the offset counted from [`Self::cursor_reserved`] where the +162 /// CID is stored. +163 fn iter_from_reserved(&self) -> impl Iterator<Item = (usize, CidData)> + '_ { +164 (0..(Self::LEN - self.reserved_len())).filter_map(move |step| { +165 let index = (self.cursor_reserved + step) % Self::LEN; +166 self.buffer[index].map(|cid_data| (step, cid_data)) +167 }) +168 } +169 +170 /// The length of the internal buffer's section of CIDs that are marked as reserved. +171 fn reserved_len(&self) -> usize { +172 if self.cursor_reserved >= self.cursor { +173 self.cursor_reserved - self.cursor +174 } else { +175 self.cursor_reserved + Self::LEN - self.cursor +176 } +177 } +178 +179 /// Replace the initial CID +180 pub(crate) fn update_initial_cid(&mut self, cid: ConnectionId) { +181 debug_assert_eq!(self.offset, 0); +182 self.buffer[self.cursor] = Some(CidData(cid, None)); +183 } +184 +185 /// Return active remote CID itself +186 pub(crate) fn active(&self) -> ConnectionId { +187 self.buffer[self.cursor].unwrap().0 +188 } +189 +190 /// Return the sequence number of active remote CID +191 pub(crate) fn active_seq(&self) -> u64 { +192 self.offset +193 } +194 +195 pub(crate) const LEN: usize = 5; +196} +197 +198#[derive(Debug, Copy, Clone, Eq, PartialEq)] +199pub(crate) enum InsertError { +200 /// CID was already retired +201 Retired, +202 /// Sequence number violates the leading edge of the window +203 ExceedsLimit, +204} +205 +206#[cfg(test)] +207mod tests { +208 use super::*; +209 +210 fn cid(sequence: u64, retire_prior_to: u64) -> NewConnectionId { +211 NewConnectionId { +212 path_id: None, +213 sequence, +214 id: ConnectionId::new(&sequence.to_be_bytes()), +215 reset_token: ResetToken::from([0xCD; crate::RESET_TOKEN_SIZE]), +216 retire_prior_to, +217 } +218 } +219 +220 fn initial_cid() -> ConnectionId { +221 ConnectionId::new(&[0xFF; 8]) +222 } +223 +224 #[test] +225 fn next_dense() { +226 let mut q = CidQueue::new(initial_cid()); +227 assert!(q.next().is_none()); +228 assert!(q.next().is_none()); +229 +230 for i in 1..CidQueue::LEN as u64 { +231 q.insert(cid(i, 0)).unwrap(); +232 } +233 for i in 1..CidQueue::LEN as u64 { +234 let (_, retire) = q.next().unwrap(); +235 assert_eq!(q.active_seq(), i); +236 assert_eq!(retire.end - retire.start, 1); +237 } +238 assert!(q.next().is_none()); +239 } +240 +241 #[test] +242 fn next_sparse() { +243 let mut q = CidQueue::new(initial_cid()); +244 let seqs = (1..CidQueue::LEN as u64).filter(|x| x % 2 == 0); +245 for i in seqs.clone() { +246 q.insert(cid(i, 0)).unwrap(); +247 } +248 for i in seqs { +249 let (_, retire) = q.next().unwrap(); +250 dbg!(&retire); +251 assert_eq!(q.active_seq(), i); +252 assert_eq!(retire, (q.active_seq().saturating_sub(2))..q.active_seq()); +253 } +254 assert!(q.next().is_none()); +255 } +256 +257 #[test] +258 fn wrap() { +259 let mut q = CidQueue::new(initial_cid()); +260 +261 for i in 1..CidQueue::LEN as u64 { +262 q.insert(cid(i, 0)).unwrap(); +263 } +264 for _ in 1..(CidQueue::LEN as u64 - 1) { +265 q.next().unwrap(); +266 } +267 for i in CidQueue::LEN as u64..(CidQueue::LEN as u64 + 3) { +268 q.insert(cid(i, 0)).unwrap(); +269 } +270 for i in (CidQueue::LEN as u64 - 1)..(CidQueue::LEN as u64 + 3) { +271 q.next().unwrap(); +272 assert_eq!(q.active_seq(), i); +273 } +274 assert!(q.next().is_none()); +275 } +276 +277 #[test] +278 fn retire_dense() { +279 let mut q = CidQueue::new(initial_cid()); +280 +281 for i in 1..CidQueue::LEN as u64 { +282 q.insert(cid(i, 0)).unwrap(); +283 } +284 assert_eq!(q.active_seq(), 0); +285 +286 assert_eq!(q.insert(cid(4, 2)).unwrap().unwrap().0, 0..2); +287 assert_eq!(q.active_seq(), 2); +288 assert_eq!(q.insert(cid(4, 2)), Ok(None)); +289 +290 for i in 2..(CidQueue::LEN as u64 - 1) { +291 let _ = q.next().unwrap(); +292 assert_eq!(q.active_seq(), i + 1); +293 assert_eq!(q.insert(cid(i + 1, i + 1)), Ok(None)); +294 } +295 +296 assert!(q.next().is_none()); +297 } +298 +299 #[test] +300 fn retire_sparse() { +301 // Retiring CID 0 when CID 1 is not known should retire CID 1 as we move to CID 2 +302 let mut q = CidQueue::new(initial_cid()); +303 q.insert(cid(2, 0)).unwrap(); +304 assert_eq!(q.insert(cid(3, 1)).unwrap().unwrap().0, 0..2,); +305 assert_eq!(q.active_seq(), 2); +306 } +307 +308 #[test] +309 fn retire_many() { +310 let mut q = CidQueue::new(initial_cid()); +311 q.insert(cid(2, 0)).unwrap(); +312 assert_eq!( +313 q.insert(cid(1_000_000, 1_000_000)).unwrap().unwrap().0, +314 0..CidQueue::LEN as u64, +315 ); +316 assert_eq!(q.active_seq(), 1_000_000); +317 } +318 +319 #[test] +320 fn insert_limit() { +321 let mut q = CidQueue::new(initial_cid()); +322 assert_eq!(q.insert(cid(CidQueue::LEN as u64 - 1, 0)), Ok(None)); +323 assert_eq!( +324 q.insert(cid(CidQueue::LEN as u64, 0)), +325 Err(InsertError::ExceedsLimit) +326 ); +327 } +328 +329 #[test] +330 fn insert_duplicate() { +331 let mut q = CidQueue::new(initial_cid()); +332 q.insert(cid(0, 0)).unwrap(); +333 q.insert(cid(0, 0)).unwrap(); +334 } +335 +336 #[test] +337 fn insert_retired() { +338 let mut q = CidQueue::new(initial_cid()); +339 assert_eq!( +340 q.insert(cid(0, 0)), +341 Ok(None), +342 "reinserting active CID succeeds" +343 ); +344 assert!(q.next().is_none(), "active CID isn't requeued"); +345 q.insert(cid(1, 0)).unwrap(); +346 q.next().unwrap(); +347 assert_eq!( +348 q.insert(cid(0, 0)), +349 Err(InsertError::Retired), +350 "previous active CID is already retired" +351 ); +352 } +353 +354 #[test] +355 fn retire_then_insert_next() { +356 let mut q = CidQueue::new(initial_cid()); +357 for i in 1..CidQueue::LEN as u64 { +358 q.insert(cid(i, 0)).unwrap(); +359 } +360 q.next().unwrap(); +361 q.insert(cid(CidQueue::LEN as u64, 0)).unwrap(); +362 assert_eq!( +363 q.insert(cid(CidQueue::LEN as u64 + 1, 0)), +364 Err(InsertError::ExceedsLimit) +365 ); +366 } +367 +368 #[test] +369 fn always_valid() { +370 let mut q = CidQueue::new(initial_cid()); +371 assert!(q.next().is_none()); +372 assert_eq!(q.active(), initial_cid()); +373 assert_eq!(q.active_seq(), 0); +374 } +375 +376 #[test] +377 fn reserved_smoke() { +378 let mut q = CidQueue::new(initial_cid()); +379 assert_eq!(q.next_reserved(), None); +380 +381 let one = cid(1, 0); +382 q.insert(one).unwrap(); +383 assert_eq!(q.next_reserved(), Some(one.id)); +384 +385 let two = cid(2, 2); +386 let (retired_range, reset_token) = q.insert(two).unwrap().unwrap(); +387 assert_eq!(reset_token, two.reset_token); +388 assert_eq!(retired_range, 0..2); 389 -390 let four = cid(4, 2); -391 q.insert(four).unwrap(); -392 println!("{q:?}"); -393 assert_eq!(q.next_reserved(), Some(four.id)); -394 assert_eq!(q.active(), two.id); -395 -396 assert_eq!(q.next(), None); -397 } -398 -399 #[test] -400 fn reserve_multiple() { -401 let mut q = CidQueue::new(initial_cid()); -402 let one = cid(1, 0); -403 let two = cid(2, 0); -404 q.insert(one).unwrap(); -405 q.insert(two).unwrap(); -406 assert_eq!(q.next_reserved(), Some(one.id)); -407 assert_eq!(q.next_reserved(), Some(two.id)); -408 assert_eq!(q.next_reserved(), None); -409 } -410 -411 #[test] -412 fn reserve_multiple_sparse() { -413 let mut q = CidQueue::new(initial_cid()); -414 let two = cid(2, 0); -415 let four = cid(4, 0); -416 q.insert(two).unwrap(); -417 q.insert(four).unwrap(); -418 assert_eq!(q.next_reserved(), Some(two.id)); -419 assert_eq!(q.next_reserved(), Some(four.id)); -420 assert_eq!(q.next_reserved(), None); -421 } -422 -423 #[test] -424 fn reserve_many_next_clears() { -425 let mut q = CidQueue::new(initial_cid()); -426 for i in 1..CidQueue::LEN { -427 q.insert(cid(i as u64, 0)).unwrap(); -428 } -429 -430 for _ in 0..CidQueue::LEN - 2 { -431 assert!(q.next_reserved().is_some()); -432 } -433 -434 assert!(q.next().is_some()); -435 assert_eq!(q.next(), None); -436 } -437 -438 #[test] -439 fn reserve_many_next_reserved_none() { -440 let mut q = CidQueue::new(initial_cid()); -441 for i in 1..CidQueue::LEN { -442 q.insert(cid(i as u64, 0)).unwrap(); -443 } -444 -445 for _ in 0..CidQueue::LEN - 1 { -446 assert!(q.next_reserved().is_some()); -447 } -448 -449 assert_eq!(q.next_reserved(), None); -450 } -451 -452 #[test] -453 fn one_active_all_else_reserved_next_none() { -454 let mut q = CidQueue::new(initial_cid()); -455 for i in 1..CidQueue::LEN { -456 q.insert(cid(i as u64, 0)).unwrap(); -457 } -458 -459 for _ in 0..CidQueue::LEN - 1 { -460 assert!(q.next_reserved().is_some()); -461 } -462 -463 assert_eq!(q.next(), None); -464 } -465 -466 #[test] -467 fn insert_reserve_advance() { -468 let mut q = CidQueue::new(initial_cid()); -469 -470 let first = cid(1, 0); -471 let second = cid(2, 0); -472 let third = cid(3, 0); -473 -474 q.insert(first).unwrap(); -475 q.insert(second).unwrap(); -476 -477 assert_eq!(q.next_reserved(), Some(first.id)); -478 q.insert(third).unwrap(); -479 q.next(); -480 assert_eq!(q.active(), second.id); -481 } -482 -483 #[test] -484 fn sparse_insert_reserve_insert_advance() { -485 let mut q = CidQueue::new(initial_cid()); -486 -487 let one = cid(1, 0); -488 let two = cid(2, 0); -489 let three = cid(3, 0); -490 -491 q.insert(two).unwrap(); -492 q.insert(three).unwrap(); -493 assert_eq!(q.next_reserved(), Some(two.id)); -494 q.insert(one).unwrap(); -495 q.next(); -496 assert_eq!(q.active(), three.id); -497 assert_eq!(q.next_reserved(), None); -498 } -499 -500 #[test] -501 fn reserve_many_next_clears_across_wraparound() { -502 let mut q = CidQueue::new(initial_cid()); -503 for i in 1..CidQueue::LEN { -504 q.insert(cid(i as u64, 0)).unwrap(); -505 } -506 -507 for _ in 0..CidQueue::LEN - 2 { -508 assert!(q.next_reserved().is_some()); -509 } -510 -511 assert!(q.next().is_some()); -512 q.insert(cid(CidQueue::LEN as u64, 0)).unwrap(); -513 assert!(q.next_reserved().is_some()); -514 q.insert(cid(CidQueue::LEN as u64 + 1, 0)).unwrap(); -515 assert!(q.next().is_some()); -516 } -517}
    \ No newline at end of file +390 assert_eq!(q.next_reserved(), None); +391 +392 let four = cid(4, 2); +393 q.insert(four).unwrap(); +394 println!("{q:?}"); +395 assert_eq!(q.next_reserved(), Some(four.id)); +396 assert_eq!(q.active(), two.id); +397 +398 assert_eq!(q.next(), None); +399 } +400 +401 #[test] +402 fn reserve_multiple() { +403 let mut q = CidQueue::new(initial_cid()); +404 let one = cid(1, 0); +405 let two = cid(2, 0); +406 q.insert(one).unwrap(); +407 q.insert(two).unwrap(); +408 assert_eq!(q.next_reserved(), Some(one.id)); +409 assert_eq!(q.next_reserved(), Some(two.id)); +410 assert_eq!(q.next_reserved(), None); +411 } +412 +413 #[test] +414 fn reserve_multiple_sparse() { +415 let mut q = CidQueue::new(initial_cid()); +416 let two = cid(2, 0); +417 let four = cid(4, 0); +418 q.insert(two).unwrap(); +419 q.insert(four).unwrap(); +420 assert_eq!(q.next_reserved(), Some(two.id)); +421 assert_eq!(q.next_reserved(), Some(four.id)); +422 assert_eq!(q.next_reserved(), None); +423 } +424 +425 #[test] +426 fn reserve_many_next_clears() { +427 let mut q = CidQueue::new(initial_cid()); +428 for i in 1..CidQueue::LEN { +429 q.insert(cid(i as u64, 0)).unwrap(); +430 } +431 +432 for _ in 0..CidQueue::LEN - 2 { +433 assert!(q.next_reserved().is_some()); +434 } +435 +436 assert!(q.next().is_some()); +437 assert_eq!(q.next(), None); +438 } +439 +440 #[test] +441 fn reserve_many_next_reserved_none() { +442 let mut q = CidQueue::new(initial_cid()); +443 for i in 1..CidQueue::LEN { +444 q.insert(cid(i as u64, 0)).unwrap(); +445 } +446 +447 for _ in 0..CidQueue::LEN - 1 { +448 assert!(q.next_reserved().is_some()); +449 } +450 +451 assert_eq!(q.next_reserved(), None); +452 } +453 +454 #[test] +455 fn one_active_all_else_reserved_next_none() { +456 let mut q = CidQueue::new(initial_cid()); +457 for i in 1..CidQueue::LEN { +458 q.insert(cid(i as u64, 0)).unwrap(); +459 } +460 +461 for _ in 0..CidQueue::LEN - 1 { +462 assert!(q.next_reserved().is_some()); +463 } +464 +465 assert_eq!(q.next(), None); +466 } +467 +468 #[test] +469 fn insert_reserve_advance() { +470 let mut q = CidQueue::new(initial_cid()); +471 +472 let first = cid(1, 0); +473 let second = cid(2, 0); +474 let third = cid(3, 0); +475 +476 q.insert(first).unwrap(); +477 q.insert(second).unwrap(); +478 +479 assert_eq!(q.next_reserved(), Some(first.id)); +480 q.insert(third).unwrap(); +481 q.next(); +482 assert_eq!(q.active(), second.id); +483 } +484 +485 #[test] +486 fn sparse_insert_reserve_insert_advance() { +487 let mut q = CidQueue::new(initial_cid()); +488 +489 let one = cid(1, 0); +490 let two = cid(2, 0); +491 let three = cid(3, 0); +492 +493 q.insert(two).unwrap(); +494 q.insert(three).unwrap(); +495 assert_eq!(q.next_reserved(), Some(two.id)); +496 q.insert(one).unwrap(); +497 q.next(); +498 assert_eq!(q.active(), three.id); +499 assert_eq!(q.next_reserved(), None); +500 } +501 +502 #[test] +503 fn reserve_many_next_clears_across_wraparound() { +504 let mut q = CidQueue::new(initial_cid()); +505 for i in 1..CidQueue::LEN { +506 q.insert(cid(i as u64, 0)).unwrap(); +507 } +508 +509 for _ in 0..CidQueue::LEN - 2 { +510 assert!(q.next_reserved().is_some()); +511 } +512 +513 assert!(q.next().is_some()); +514 q.insert(cid(CidQueue::LEN as u64, 0)).unwrap(); +515 assert!(q.next_reserved().is_some()); +516 q.insert(cid(CidQueue::LEN as u64 + 1, 0)).unwrap(); +517 assert!(q.next().is_some()); +518 } +519}
    \ No newline at end of file diff --git a/pr/451/docs/src/noq_proto/config/mod.rs.html b/pr/451/docs/src/noq_proto/config/mod.rs.html index 4e37f0d59..48ebae7a0 100644 --- a/pr/451/docs/src/noq_proto/config/mod.rs.html +++ b/pr/451/docs/src/noq_proto/config/mod.rs.html @@ -33,656 +33,653 @@ 33pub use qlog::{QlogConfig, QlogFactory, QlogFileFactory}; 34pub use transport::{AckFrequencyConfig, IdleTimeout, MtuDiscoveryConfig, TransportConfig}; 35 -36#[cfg(doc)] -37pub use transport::DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED; -38 -39/// Global configuration for the endpoint, affecting all connections -40/// -41/// Default values should be suitable for most internet applications. -42#[derive(Clone)] -43pub struct EndpointConfig { -44 pub(crate) reset_key: Arc<dyn HmacKey>, -45 pub(crate) max_udp_payload_size: VarInt, -46 /// CID generator factory -47 /// -48 /// Create a cid generator for local cid in Endpoint struct -49 pub(crate) connection_id_generator_factory: -50 Arc<dyn Fn() -> Box<dyn ConnectionIdGenerator> + Send + Sync>, -51 pub(crate) supported_versions: Vec<u32>, -52 pub(crate) grease_quic_bit: bool, -53 /// Minimum interval between outgoing stateless reset packets -54 pub(crate) min_reset_interval: Duration, -55 /// Optional seed to be used internally for random number generation -56 pub(crate) rng_seed: Option<[u8; 32]>, -57} -58 -59impl EndpointConfig { -60 /// Create a default config with a particular `reset_key` -61 pub fn new(reset_key: Arc<dyn HmacKey>) -> Self { -62 let cid_factory = -63 || -> Box<dyn ConnectionIdGenerator> { Box::<HashedConnectionIdGenerator>::default() }; -64 Self { -65 reset_key, -66 max_udp_payload_size: (1500u32 - 28).into(), // Ethernet MTU minus IP + UDP headers -67 connection_id_generator_factory: Arc::new(cid_factory), -68 supported_versions: DEFAULT_SUPPORTED_VERSIONS.to_vec(), -69 grease_quic_bit: true, -70 min_reset_interval: Duration::from_millis(20), -71 rng_seed: None, -72 } -73 } -74 -75 /// Supply a custom connection ID generator factory -76 /// -77 /// Called once by each `Endpoint` constructed from this configuration to obtain the CID -78 /// generator which will be used to generate the CIDs used for incoming packets on all -79 /// connections involving that `Endpoint`. A custom CID generator allows applications to embed -80 /// information in local connection IDs, e.g. to support stateless packet-level load balancers. -81 /// -82 /// Defaults to [`HashedConnectionIdGenerator`]. -83 pub fn cid_generator<F: Fn() -> Box<dyn ConnectionIdGenerator> + Send + Sync + 'static>( -84 &mut self, -85 factory: F, -86 ) -> &mut Self { -87 self.connection_id_generator_factory = Arc::new(factory); -88 self -89 } -90 -91 /// Private key used to send authenticated connection resets to peers who were -92 /// communicating with a previous instance of this endpoint. -93 pub fn reset_key(&mut self, key: Arc<dyn HmacKey>) -> &mut Self { -94 self.reset_key = key; -95 self -96 } -97 -98 /// Maximum UDP payload size accepted from peers (excluding UDP and IP overhead). -99 /// -100 /// Must be greater or equal than 1200. -101 /// -102 /// Defaults to 1472, which is the largest UDP payload that can be transmitted in the typical -103 /// 1500 byte Ethernet MTU. Deployments on links with larger MTUs (e.g. loopback or Ethernet -104 /// with jumbo frames) can raise this to improve performance at the cost of a linear increase in -105 /// datagram receive buffer size. -106 pub fn max_udp_payload_size(&mut self, value: u16) -> Result<&mut Self, ConfigError> { -107 if !(1200..=65_527).contains(&value) { -108 return Err(ConfigError::OutOfBounds); -109 } -110 -111 self.max_udp_payload_size = value.into(); -112 Ok(self) -113 } -114 -115 /// Get the current value of [`max_udp_payload_size`](Self::max_udp_payload_size) -116 // -117 // While most parameters don't need to be readable, this must be exposed to allow higher-level -118 // layers, e.g. the `noq` crate, to determine how large a receive buffer to allocate to -119 // support an externally-defined `EndpointConfig`. -120 // -121 // While `get_` accessors are typically unidiomatic in Rust, we favor concision for setters, -122 // which will be used far more heavily. -123 pub fn get_max_udp_payload_size(&self) -> u64 { -124 self.max_udp_payload_size.into() -125 } -126 -127 /// Override supported QUIC versions -128 pub fn supported_versions(&mut self, supported_versions: Vec<u32>) -> &mut Self { -129 self.supported_versions = supported_versions; -130 self -131 } -132 -133 /// Whether to accept QUIC packets containing any value for the fixed bit -134 /// -135 /// Enabled by default. Helps protect against protocol ossification and makes traffic less -136 /// identifiable to observers. Disable if helping observers identify this traffic as QUIC is -137 /// desired. -138 pub fn grease_quic_bit(&mut self, value: bool) -> &mut Self { -139 self.grease_quic_bit = value; -140 self -141 } -142 -143 /// Minimum interval between outgoing stateless reset packets -144 /// -145 /// Defaults to 20ms. Limits the impact of attacks which flood an endpoint with garbage packets, -146 /// e.g. [ISAKMP/IKE amplification]. Larger values provide a stronger defense, but may delay -147 /// detection of some error conditions by clients. Using a [`ConnectionIdGenerator`] with a low -148 /// rate of false positives in [`validate`](ConnectionIdGenerator::validate) reduces the risk -149 /// incurred by a small minimum reset interval. -150 /// -151 /// [ISAKMP/IKE -152 /// amplification]: https://bughunters.google.com/blog/5960150648750080/preventing-cross-service-udp-loops-in-quic#isakmp-ike-amplification-vs-quic -153 pub fn min_reset_interval(&mut self, value: Duration) -> &mut Self { -154 self.min_reset_interval = value; -155 self -156 } -157 -158 /// Optional seed to be used internally for random number generation -159 /// -160 /// By default, noq will initialize an endpoint's rng using a platform entropy source. -161 /// However, you can seed the rng yourself through this method (e.g. if you need to run noq -162 /// deterministically or if you are using noq in an environment that doesn't have a source of -163 /// entropy available). -164 pub fn rng_seed(&mut self, seed: Option<[u8; 32]>) -> &mut Self { -165 self.rng_seed = seed; -166 self -167 } -168} -169 -170impl fmt::Debug for EndpointConfig { -171 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { -172 fmt.debug_struct("EndpointConfig") -173 // reset_key not debug -174 .field("max_udp_payload_size", &self.max_udp_payload_size) -175 // cid_generator_factory not debug -176 .field("supported_versions", &self.supported_versions) -177 .field("grease_quic_bit", &self.grease_quic_bit) -178 .field("rng_seed", &self.rng_seed) -179 .finish_non_exhaustive() -180 } -181} -182 -183#[cfg(any(feature = "aws-lc-rs", feature = "ring"))] -184impl Default for EndpointConfig { -185 fn default() -> Self { -186 #[cfg(all(feature = "aws-lc-rs", not(feature = "ring")))] -187 use aws_lc_rs::hmac; -188 use rand::Rng; -189 #[cfg(feature = "ring")] -190 use ring::hmac; +36/// Global configuration for the endpoint, affecting all connections +37/// +38/// Default values should be suitable for most internet applications. +39#[derive(Clone)] +40pub struct EndpointConfig { +41 pub(crate) reset_key: Arc<dyn HmacKey>, +42 pub(crate) max_udp_payload_size: VarInt, +43 /// CID generator factory +44 /// +45 /// Create a cid generator for local cid in Endpoint struct +46 pub(crate) connection_id_generator_factory: +47 Arc<dyn Fn() -> Box<dyn ConnectionIdGenerator> + Send + Sync>, +48 pub(crate) supported_versions: Vec<u32>, +49 pub(crate) grease_quic_bit: bool, +50 /// Minimum interval between outgoing stateless reset packets +51 pub(crate) min_reset_interval: Duration, +52 /// Optional seed to be used internally for random number generation +53 pub(crate) rng_seed: Option<[u8; 32]>, +54} +55 +56impl EndpointConfig { +57 /// Create a default config with a particular `reset_key` +58 pub fn new(reset_key: Arc<dyn HmacKey>) -> Self { +59 let cid_factory = +60 || -> Box<dyn ConnectionIdGenerator> { Box::<HashedConnectionIdGenerator>::default() }; +61 Self { +62 reset_key, +63 max_udp_payload_size: (1500u32 - 28).into(), // Ethernet MTU minus IP + UDP headers +64 connection_id_generator_factory: Arc::new(cid_factory), +65 supported_versions: DEFAULT_SUPPORTED_VERSIONS.to_vec(), +66 grease_quic_bit: true, +67 min_reset_interval: Duration::from_millis(20), +68 rng_seed: None, +69 } +70 } +71 +72 /// Supply a custom connection ID generator factory +73 /// +74 /// Called once by each `Endpoint` constructed from this configuration to obtain the CID +75 /// generator which will be used to generate the CIDs used for incoming packets on all +76 /// connections involving that `Endpoint`. A custom CID generator allows applications to embed +77 /// information in local connection IDs, e.g. to support stateless packet-level load balancers. +78 /// +79 /// Defaults to [`HashedConnectionIdGenerator`]. +80 pub fn cid_generator( +81 &mut self, +82 factory: Arc<dyn Fn() -> Box<dyn ConnectionIdGenerator> + Send + Sync>, +83 ) -> &mut Self { +84 self.connection_id_generator_factory = factory; +85 self +86 } +87 +88 /// Private key used to send authenticated connection resets to peers who were +89 /// communicating with a previous instance of this endpoint. +90 pub fn reset_key(&mut self, key: Arc<dyn HmacKey>) -> &mut Self { +91 self.reset_key = key; +92 self +93 } +94 +95 /// Maximum UDP payload size accepted from peers (excluding UDP and IP overhead). +96 /// +97 /// Must be greater or equal than 1200. +98 /// +99 /// Defaults to 1472, which is the largest UDP payload that can be transmitted in the typical +100 /// 1500 byte Ethernet MTU. Deployments on links with larger MTUs (e.g. loopback or Ethernet +101 /// with jumbo frames) can raise this to improve performance at the cost of a linear increase in +102 /// datagram receive buffer size. +103 pub fn max_udp_payload_size(&mut self, value: u16) -> Result<&mut Self, ConfigError> { +104 if !(1200..=65_527).contains(&value) { +105 return Err(ConfigError::OutOfBounds); +106 } +107 +108 self.max_udp_payload_size = value.into(); +109 Ok(self) +110 } +111 +112 /// Get the current value of [`max_udp_payload_size`](Self::max_udp_payload_size) +113 // +114 // While most parameters don't need to be readable, this must be exposed to allow higher-level +115 // layers, e.g. the `noq` crate, to determine how large a receive buffer to allocate to +116 // support an externally-defined `EndpointConfig`. +117 // +118 // While `get_` accessors are typically unidiomatic in Rust, we favor concision for setters, +119 // which will be used far more heavily. +120 pub fn get_max_udp_payload_size(&self) -> u64 { +121 self.max_udp_payload_size.into() +122 } +123 +124 /// Override supported QUIC versions +125 pub fn supported_versions(&mut self, supported_versions: Vec<u32>) -> &mut Self { +126 self.supported_versions = supported_versions; +127 self +128 } +129 +130 /// Whether to accept QUIC packets containing any value for the fixed bit +131 /// +132 /// Enabled by default. Helps protect against protocol ossification and makes traffic less +133 /// identifiable to observers. Disable if helping observers identify this traffic as QUIC is +134 /// desired. +135 pub fn grease_quic_bit(&mut self, value: bool) -> &mut Self { +136 self.grease_quic_bit = value; +137 self +138 } +139 +140 /// Minimum interval between outgoing stateless reset packets +141 /// +142 /// Defaults to 20ms. Limits the impact of attacks which flood an endpoint with garbage packets, +143 /// e.g. [ISAKMP/IKE amplification]. Larger values provide a stronger defense, but may delay +144 /// detection of some error conditions by clients. Using a [`ConnectionIdGenerator`] with a low +145 /// rate of false positives in [`validate`](ConnectionIdGenerator::validate) reduces the risk +146 /// incurred by a small minimum reset interval. +147 /// +148 /// [ISAKMP/IKE +149 /// amplification]: https://bughunters.google.com/blog/5960150648750080/preventing-cross-service-udp-loops-in-quic#isakmp-ike-amplification-vs-quic +150 pub fn min_reset_interval(&mut self, value: Duration) -> &mut Self { +151 self.min_reset_interval = value; +152 self +153 } +154 +155 /// Optional seed to be used internally for random number generation +156 /// +157 /// By default, noq will initialize an endpoint's rng using a platform entropy source. +158 /// However, you can seed the rng yourself through this method (e.g. if you need to run noq +159 /// deterministically or if you are using noq in an environment that doesn't have a source of +160 /// entropy available). +161 pub fn rng_seed(&mut self, seed: Option<[u8; 32]>) -> &mut Self { +162 self.rng_seed = seed; +163 self +164 } +165} +166 +167impl fmt::Debug for EndpointConfig { +168 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { +169 fmt.debug_struct("EndpointConfig") +170 // reset_key not debug +171 .field("max_udp_payload_size", &self.max_udp_payload_size) +172 // cid_generator_factory not debug +173 .field("supported_versions", &self.supported_versions) +174 .field("grease_quic_bit", &self.grease_quic_bit) +175 .field("rng_seed", &self.rng_seed) +176 .finish_non_exhaustive() +177 } +178} +179 +180#[cfg(any(feature = "aws-lc-rs", feature = "ring"))] +181impl Default for EndpointConfig { +182 fn default() -> Self { +183 #[cfg(all(feature = "aws-lc-rs", not(feature = "ring")))] +184 use aws_lc_rs::hmac; +185 use rand::Rng; +186 #[cfg(feature = "ring")] +187 use ring::hmac; +188 +189 let mut reset_key = [0; 64]; +190 rand::rng().fill_bytes(&mut reset_key); 191 -192 let mut reset_key = [0; 64]; -193 rand::rng().fill_bytes(&mut reset_key); -194 -195 Self::new(Arc::new(hmac::Key::new(hmac::HMAC_SHA256, &reset_key))) -196 } -197} -198 -199/// Parameters governing incoming connections -200/// -201/// Default values should be suitable for most internet applications. -202#[derive(Clone)] -203pub struct ServerConfig { -204 /// Transport configuration to use for incoming connections -205 pub transport: Arc<TransportConfig>, -206 -207 /// TLS configuration used for incoming connections -208 /// -209 /// Must be set to use TLS 1.3 only. -210 pub crypto: Arc<dyn crypto::ServerConfig>, +192 Self::new(Arc::new(hmac::Key::new(hmac::HMAC_SHA256, &reset_key))) +193 } +194} +195 +196/// Parameters governing incoming connections +197/// +198/// Default values should be suitable for most internet applications. +199#[derive(Clone)] +200pub struct ServerConfig { +201 /// Transport configuration to use for incoming connections +202 pub transport: Arc<TransportConfig>, +203 +204 /// TLS configuration used for incoming connections +205 /// +206 /// Must be set to use TLS 1.3 only. +207 pub crypto: Arc<dyn crypto::ServerConfig>, +208 +209 /// Configuration for sending and handling validation tokens +210 pub validation_token: ValidationTokenConfig, 211 -212 /// Configuration for sending and handling validation tokens -213 pub validation_token: ValidationTokenConfig, +212 /// Used to generate one-time AEAD keys to protect handshake tokens +213 pub(crate) token_key: Arc<dyn HandshakeTokenKey>, 214 -215 /// Used to generate one-time AEAD keys to protect handshake tokens -216 pub(crate) token_key: Arc<dyn HandshakeTokenKey>, +215 /// Duration after a retry token was issued for which it's considered valid +216 pub(crate) retry_token_lifetime: Duration, 217 -218 /// Duration after a retry token was issued for which it's considered valid -219 pub(crate) retry_token_lifetime: Duration, -220 -221 /// Whether to allow clients to migrate to new addresses -222 /// -223 /// Improves behavior for clients that move between different internet connections or suffer NAT -224 /// rebinding. Enabled by default. -225 pub(crate) migration: bool, +218 /// Whether to allow clients to migrate to new addresses +219 /// +220 /// Improves behavior for clients that move between different internet connections or suffer NAT +221 /// rebinding. Enabled by default. +222 pub(crate) migration: bool, +223 +224 pub(crate) preferred_address_v4: Option<SocketAddrV4>, +225 pub(crate) preferred_address_v6: Option<SocketAddrV6>, 226 -227 pub(crate) preferred_address_v4: Option<SocketAddrV4>, -228 pub(crate) preferred_address_v6: Option<SocketAddrV6>, -229 -230 pub(crate) max_incoming: usize, -231 pub(crate) incoming_buffer_size: u64, -232 pub(crate) incoming_buffer_size_total: u64, +227 pub(crate) max_incoming: usize, +228 pub(crate) incoming_buffer_size: u64, +229 pub(crate) incoming_buffer_size_total: u64, +230 +231 pub(crate) time_source: Arc<dyn TimeSource>, +232} 233 -234 pub(crate) time_source: Arc<dyn TimeSource>, -235} -236 -237impl ServerConfig { -238 /// Create a default config with a particular handshake token key -239 pub fn new( -240 crypto: Arc<dyn crypto::ServerConfig>, -241 token_key: Arc<dyn HandshakeTokenKey>, -242 ) -> Self { -243 Self { -244 transport: Arc::new(TransportConfig::default()), -245 crypto, +234impl ServerConfig { +235 /// Create a default config with a particular handshake token key +236 pub fn new( +237 crypto: Arc<dyn crypto::ServerConfig>, +238 token_key: Arc<dyn HandshakeTokenKey>, +239 ) -> Self { +240 Self { +241 transport: Arc::new(TransportConfig::default()), +242 crypto, +243 +244 token_key, +245 retry_token_lifetime: Duration::from_secs(15), 246 -247 token_key, -248 retry_token_lifetime: Duration::from_secs(15), -249 -250 migration: true, -251 -252 validation_token: ValidationTokenConfig::default(), +247 migration: true, +248 +249 validation_token: ValidationTokenConfig::default(), +250 +251 preferred_address_v4: None, +252 preferred_address_v6: None, 253 -254 preferred_address_v4: None, -255 preferred_address_v6: None, -256 -257 max_incoming: 1 << 16, -258 incoming_buffer_size: 10 << 20, -259 incoming_buffer_size_total: 100 << 20, -260 -261 time_source: Arc::new(StdSystemTime), -262 } -263 } -264 -265 /// Set a custom [`TransportConfig`] -266 pub fn transport_config(&mut self, transport: Arc<TransportConfig>) -> &mut Self { -267 self.transport = transport; -268 self -269 } -270 -271 /// Set a custom [`ValidationTokenConfig`] -272 pub fn validation_token_config( -273 &mut self, -274 validation_token: ValidationTokenConfig, -275 ) -> &mut Self { -276 self.validation_token = validation_token; -277 self -278 } -279 -280 /// Private key used to authenticate data included in handshake tokens -281 pub fn token_key(&mut self, value: Arc<dyn HandshakeTokenKey>) -> &mut Self { -282 self.token_key = value; -283 self -284 } -285 -286 /// Duration after a retry token was issued for which it's considered valid -287 /// -288 /// Defaults to 15 seconds. -289 pub fn retry_token_lifetime(&mut self, value: Duration) -> &mut Self { -290 self.retry_token_lifetime = value; -291 self -292 } -293 -294 /// Whether to allow clients to migrate to new addresses -295 /// -296 /// Improves behavior for clients that move between different internet connections or suffer NAT -297 /// rebinding. Enabled by default. -298 pub fn migration(&mut self, value: bool) -> &mut Self { -299 self.migration = value; -300 self -301 } -302 -303 /// The preferred IPv4 address that will be communicated to clients during handshaking -304 /// -305 /// If the client is able to reach this address, it will switch to it. -306 pub fn preferred_address_v4(&mut self, address: Option<SocketAddrV4>) -> &mut Self { -307 self.preferred_address_v4 = address; -308 self -309 } -310 -311 /// The preferred IPv6 address that will be communicated to clients during handshaking -312 /// -313 /// If the client is able to reach this address, it will switch to it. -314 pub fn preferred_address_v6(&mut self, address: Option<SocketAddrV6>) -> &mut Self { -315 self.preferred_address_v6 = address; -316 self -317 } -318 -319 /// Maximum number of [`Incoming`][crate::Incoming] to allow to exist at a time -320 /// -321 /// An [`Incoming`][crate::Incoming] comes into existence when an incoming connection attempt -322 /// is received and stops existing when the application either accepts it or otherwise disposes -323 /// of it. While this limit is reached, new incoming connection attempts are immediately -324 /// refused. Larger values have greater worst-case memory consumption, but accommodate greater -325 /// application latency in handling incoming connection attempts. -326 /// -327 /// The default value is set to 65536. With a typical Ethernet MTU of 1500 bytes, this limits -328 /// memory consumption from this to under 100 MiB--a generous amount that still prevents memory -329 /// exhaustion in most contexts. -330 pub fn max_incoming(&mut self, max_incoming: usize) -> &mut Self { -331 self.max_incoming = max_incoming; -332 self -333 } -334 -335 /// Maximum number of received bytes to buffer for each [`Incoming`][crate::Incoming] -336 /// -337 /// An [`Incoming`][crate::Incoming] comes into existence when an incoming connection attempt -338 /// is received and stops existing when the application either accepts it or otherwise disposes -339 /// of it. This limit governs only packets received within that period, and does not include -340 /// the first packet. Packets received in excess of this limit are dropped, which may cause -341 /// 0-RTT or handshake data to have to be retransmitted. -342 /// -343 /// The default value is set to 10 MiB--an amount such that in most situations a client would -344 /// not transmit that much 0-RTT data faster than the server handles the corresponding -345 /// [`Incoming`][crate::Incoming]. -346 pub fn incoming_buffer_size(&mut self, incoming_buffer_size: u64) -> &mut Self { -347 self.incoming_buffer_size = incoming_buffer_size; -348 self -349 } -350 -351 /// Maximum number of received bytes to buffer for all [`Incoming`][crate::Incoming] -352 /// collectively -353 /// -354 /// An [`Incoming`][crate::Incoming] comes into existence when an incoming connection attempt -355 /// is received and stops existing when the application either accepts it or otherwise disposes -356 /// of it. This limit governs only packets received within that period, and does not include -357 /// the first packet. Packets received in excess of this limit are dropped, which may cause -358 /// 0-RTT or handshake data to have to be retransmitted. -359 /// -360 /// The default value is set to 100 MiB--a generous amount that still prevents memory -361 /// exhaustion in most contexts. -362 pub fn incoming_buffer_size_total(&mut self, incoming_buffer_size_total: u64) -> &mut Self { -363 self.incoming_buffer_size_total = incoming_buffer_size_total; -364 self -365 } -366 -367 /// Object to get current [`SystemTime`] -368 /// -369 /// This exists to allow system time to be mocked in tests, or wherever else desired. -370 /// -371 /// Defaults to [`StdSystemTime`], which simply calls [`SystemTime::now()`](SystemTime::now). -372 pub fn time_source(&mut self, time_source: Arc<dyn TimeSource>) -> &mut Self { -373 self.time_source = time_source; -374 self -375 } -376 -377 pub(crate) fn has_preferred_address(&self) -> bool { -378 self.preferred_address_v4.is_some() || self.preferred_address_v6.is_some() -379 } -380} -381 -382#[cfg(all(feature = "rustls", any(feature = "aws-lc-rs", feature = "ring")))] -383impl ServerConfig { -384 /// Create a server config with the given certificate chain to be presented to clients -385 /// -386 /// Uses a randomized handshake token key. -387 pub fn with_single_cert( -388 cert_chain: Vec<CertificateDer<'static>>, -389 key: PrivateKeyDer<'static>, -390 ) -> Result<Self, rustls::Error> { -391 Ok(Self::with_crypto(Arc::new(QuicServerConfig::new( -392 cert_chain, key, -393 )?))) -394 } -395} -396 -397#[cfg(any(feature = "aws-lc-rs", feature = "ring"))] -398impl ServerConfig { -399 /// Create a server config with the given [`crypto::ServerConfig`] -400 /// -401 /// Uses a randomized handshake token key. -402 pub fn with_crypto(crypto: Arc<dyn crypto::ServerConfig>) -> Self { -403 use crate::crypto::ring_like::RetryTokenKey; -404 -405 let retry_token_key = RetryTokenKey::new(&mut rand::rng()); -406 Self::new(crypto, Arc::new(retry_token_key)) -407 } -408} -409 -410impl fmt::Debug for ServerConfig { -411 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { -412 fmt.debug_struct("ServerConfig") -413 .field("transport", &self.transport) -414 // crypto not debug -415 // token not debug -416 .field("retry_token_lifetime", &self.retry_token_lifetime) -417 .field("validation_token", &self.validation_token) -418 .field("migration", &self.migration) -419 .field("preferred_address_v4", &self.preferred_address_v4) -420 .field("preferred_address_v6", &self.preferred_address_v6) -421 .field("max_incoming", &self.max_incoming) -422 .field("incoming_buffer_size", &self.incoming_buffer_size) -423 .field( -424 "incoming_buffer_size_total", -425 &self.incoming_buffer_size_total, -426 ) -427 // system_time_clock not debug -428 .finish_non_exhaustive() -429 } -430} -431 -432/// Configuration for sending and handling validation tokens in incoming connections -433/// -434/// Default values should be suitable for most internet applications. -435/// -436/// ## QUIC Tokens -437/// -438/// The QUIC protocol defines a concept of "[address validation][1]". Essentially, one side of a -439/// QUIC connection may appear to be receiving QUIC packets from a particular remote UDP address, -440/// but it will only consider that remote address "validated" once it has convincing evidence that -441/// the address is not being [spoofed][2]. -442/// -443/// Validation is important primarily because of QUIC's "anti-amplification limit." This limit -444/// prevents a QUIC server from sending a client more than three times the number of bytes it has -445/// received from the client on a given address until that address is validated. This is designed -446/// to mitigate the ability of attackers to use QUIC-based servers as reflectors in [amplification -447/// attacks][3]. -448/// -449/// A path may become validated in several ways. The server is always considered validated by the -450/// client. The client usually begins in an unvalidated state upon first connecting or migrating, -451/// but then becomes validated through various mechanisms that usually take one network round trip. -452/// However, in some cases, a client which has previously attempted to connect to a server may have -453/// been given a one-time use cryptographically secured "token" that it can send in a subsequent -454/// connection attempt to be validated immediately. -455/// -456/// There are two ways these tokens can originate: -457/// -458/// - If the server responds to an incoming connection with `retry`, a "retry token" is minted and -459/// sent to the client, which the client immediately uses to attempt to connect again. Retry -460/// tokens operate on short timescales, such as 15 seconds. -461/// - If a client's path within an active connection is validated, the server may send the client -462/// one or more "validation tokens," which the client may store for use in later connections to -463/// the same server. Validation tokens may be valid for much longer lifetimes than retry token. -464/// -465/// The usage of validation tokens is most impactful in situations where 0-RTT data is also being -466/// used--in particular, in situations where the server sends the client more than three times more -467/// 0.5-RTT data than it has received 0-RTT data. Since the successful completion of a connection -468/// handshake implicitly causes the client's address to be validated, transmission of 0.5-RTT data -469/// is the main situation where a server might be sending application data to an address that could -470/// be validated by token usage earlier than it would become validated without token usage. -471/// -472/// [1]: https://www.rfc-editor.org/rfc/rfc9000.html#section-8 -473/// [2]: https://en.wikipedia.org/wiki/IP_address_spoofing -474/// [3]: https://en.wikipedia.org/wiki/Denial-of-service_attack#Amplification -475/// -476/// These tokens should not be confused with "stateless reset tokens," which are similarly named -477/// but entirely unrelated. -478#[derive(Clone)] -479pub struct ValidationTokenConfig { -480 pub(crate) lifetime: Duration, -481 pub(crate) log: Arc<dyn TokenLog>, -482 pub(crate) sent: u32, -483} -484 -485impl ValidationTokenConfig { -486 /// Duration after an address validation token was issued for which it's considered valid -487 /// -488 /// This refers only to tokens sent in NEW_TOKEN frames, in contrast to retry tokens. -489 /// -490 /// Defaults to 2 weeks. -491 pub fn lifetime(&mut self, value: Duration) -> &mut Self { -492 self.lifetime = value; -493 self -494 } -495 -496 #[allow(rustdoc::redundant_explicit_links)] // which links are redundant depends on features -497 /// Set a custom [`TokenLog`] +254 max_incoming: 1 << 16, +255 incoming_buffer_size: 10 << 20, +256 incoming_buffer_size_total: 100 << 20, +257 +258 time_source: Arc::new(StdSystemTime), +259 } +260 } +261 +262 /// Set a custom [`TransportConfig`] +263 pub fn transport_config(&mut self, transport: Arc<TransportConfig>) -> &mut Self { +264 self.transport = transport; +265 self +266 } +267 +268 /// Set a custom [`ValidationTokenConfig`] +269 pub fn validation_token_config( +270 &mut self, +271 validation_token: ValidationTokenConfig, +272 ) -> &mut Self { +273 self.validation_token = validation_token; +274 self +275 } +276 +277 /// Private key used to authenticate data included in handshake tokens +278 pub fn token_key(&mut self, value: Arc<dyn HandshakeTokenKey>) -> &mut Self { +279 self.token_key = value; +280 self +281 } +282 +283 /// Duration after a retry token was issued for which it's considered valid +284 /// +285 /// Defaults to 15 seconds. +286 pub fn retry_token_lifetime(&mut self, value: Duration) -> &mut Self { +287 self.retry_token_lifetime = value; +288 self +289 } +290 +291 /// Whether to allow clients to migrate to new addresses +292 /// +293 /// Improves behavior for clients that move between different internet connections or suffer NAT +294 /// rebinding. Enabled by default. +295 pub fn migration(&mut self, value: bool) -> &mut Self { +296 self.migration = value; +297 self +298 } +299 +300 /// The preferred IPv4 address that will be communicated to clients during handshaking +301 /// +302 /// If the client is able to reach this address, it will switch to it. +303 pub fn preferred_address_v4(&mut self, address: Option<SocketAddrV4>) -> &mut Self { +304 self.preferred_address_v4 = address; +305 self +306 } +307 +308 /// The preferred IPv6 address that will be communicated to clients during handshaking +309 /// +310 /// If the client is able to reach this address, it will switch to it. +311 pub fn preferred_address_v6(&mut self, address: Option<SocketAddrV6>) -> &mut Self { +312 self.preferred_address_v6 = address; +313 self +314 } +315 +316 /// Maximum number of [`Incoming`][crate::Incoming] to allow to exist at a time +317 /// +318 /// An [`Incoming`][crate::Incoming] comes into existence when an incoming connection attempt +319 /// is received and stops existing when the application either accepts it or otherwise disposes +320 /// of it. While this limit is reached, new incoming connection attempts are immediately +321 /// refused. Larger values have greater worst-case memory consumption, but accommodate greater +322 /// application latency in handling incoming connection attempts. +323 /// +324 /// The default value is set to 65536. With a typical Ethernet MTU of 1500 bytes, this limits +325 /// memory consumption from this to under 100 MiB--a generous amount that still prevents memory +326 /// exhaustion in most contexts. +327 pub fn max_incoming(&mut self, max_incoming: usize) -> &mut Self { +328 self.max_incoming = max_incoming; +329 self +330 } +331 +332 /// Maximum number of received bytes to buffer for each [`Incoming`][crate::Incoming] +333 /// +334 /// An [`Incoming`][crate::Incoming] comes into existence when an incoming connection attempt +335 /// is received and stops existing when the application either accepts it or otherwise disposes +336 /// of it. This limit governs only packets received within that period, and does not include +337 /// the first packet. Packets received in excess of this limit are dropped, which may cause +338 /// 0-RTT or handshake data to have to be retransmitted. +339 /// +340 /// The default value is set to 10 MiB--an amount such that in most situations a client would +341 /// not transmit that much 0-RTT data faster than the server handles the corresponding +342 /// [`Incoming`][crate::Incoming]. +343 pub fn incoming_buffer_size(&mut self, incoming_buffer_size: u64) -> &mut Self { +344 self.incoming_buffer_size = incoming_buffer_size; +345 self +346 } +347 +348 /// Maximum number of received bytes to buffer for all [`Incoming`][crate::Incoming] +349 /// collectively +350 /// +351 /// An [`Incoming`][crate::Incoming] comes into existence when an incoming connection attempt +352 /// is received and stops existing when the application either accepts it or otherwise disposes +353 /// of it. This limit governs only packets received within that period, and does not include +354 /// the first packet. Packets received in excess of this limit are dropped, which may cause +355 /// 0-RTT or handshake data to have to be retransmitted. +356 /// +357 /// The default value is set to 100 MiB--a generous amount that still prevents memory +358 /// exhaustion in most contexts. +359 pub fn incoming_buffer_size_total(&mut self, incoming_buffer_size_total: u64) -> &mut Self { +360 self.incoming_buffer_size_total = incoming_buffer_size_total; +361 self +362 } +363 +364 /// Object to get current [`SystemTime`] +365 /// +366 /// This exists to allow system time to be mocked in tests, or wherever else desired. +367 /// +368 /// Defaults to [`StdSystemTime`], which simply calls [`SystemTime::now()`](SystemTime::now). +369 pub fn time_source(&mut self, time_source: Arc<dyn TimeSource>) -> &mut Self { +370 self.time_source = time_source; +371 self +372 } +373 +374 pub(crate) fn has_preferred_address(&self) -> bool { +375 self.preferred_address_v4.is_some() || self.preferred_address_v6.is_some() +376 } +377} +378 +379#[cfg(all(feature = "rustls", any(feature = "aws-lc-rs", feature = "ring")))] +380impl ServerConfig { +381 /// Create a server config with the given certificate chain to be presented to clients +382 /// +383 /// Uses a randomized handshake token key. +384 pub fn with_single_cert( +385 cert_chain: Vec<CertificateDer<'static>>, +386 key: PrivateKeyDer<'static>, +387 ) -> Result<Self, rustls::Error> { +388 Ok(Self::with_crypto(Arc::new(QuicServerConfig::new( +389 cert_chain, key, +390 )?))) +391 } +392} +393 +394#[cfg(any(feature = "aws-lc-rs", feature = "ring"))] +395impl ServerConfig { +396 /// Create a server config with the given [`crypto::ServerConfig`] +397 /// +398 /// Uses a randomized handshake token key. +399 pub fn with_crypto(crypto: Arc<dyn crypto::ServerConfig>) -> Self { +400 use crate::crypto::ring_like::RetryTokenKey; +401 +402 let retry_token_key = RetryTokenKey::new(&mut rand::rng()); +403 Self::new(crypto, Arc::new(retry_token_key)) +404 } +405} +406 +407impl fmt::Debug for ServerConfig { +408 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { +409 fmt.debug_struct("ServerConfig") +410 .field("transport", &self.transport) +411 // crypto not debug +412 // token not debug +413 .field("retry_token_lifetime", &self.retry_token_lifetime) +414 .field("validation_token", &self.validation_token) +415 .field("migration", &self.migration) +416 .field("preferred_address_v4", &self.preferred_address_v4) +417 .field("preferred_address_v6", &self.preferred_address_v6) +418 .field("max_incoming", &self.max_incoming) +419 .field("incoming_buffer_size", &self.incoming_buffer_size) +420 .field( +421 "incoming_buffer_size_total", +422 &self.incoming_buffer_size_total, +423 ) +424 // system_time_clock not debug +425 .finish_non_exhaustive() +426 } +427} +428 +429/// Configuration for sending and handling validation tokens in incoming connections +430/// +431/// Default values should be suitable for most internet applications. +432/// +433/// ## QUIC Tokens +434/// +435/// The QUIC protocol defines a concept of "[address validation][1]". Essentially, one side of a +436/// QUIC connection may appear to be receiving QUIC packets from a particular remote UDP address, +437/// but it will only consider that remote address "validated" once it has convincing evidence that +438/// the address is not being [spoofed][2]. +439/// +440/// Validation is important primarily because of QUIC's "anti-amplification limit." This limit +441/// prevents a QUIC server from sending a client more than three times the number of bytes it has +442/// received from the client on a given address until that address is validated. This is designed +443/// to mitigate the ability of attackers to use QUIC-based servers as reflectors in [amplification +444/// attacks][3]. +445/// +446/// A path may become validated in several ways. The server is always considered validated by the +447/// client. The client usually begins in an unvalidated state upon first connecting or migrating, +448/// but then becomes validated through various mechanisms that usually take one network round trip. +449/// However, in some cases, a client which has previously attempted to connect to a server may have +450/// been given a one-time use cryptographically secured "token" that it can send in a subsequent +451/// connection attempt to be validated immediately. +452/// +453/// There are two ways these tokens can originate: +454/// +455/// - If the server responds to an incoming connection with `retry`, a "retry token" is minted and +456/// sent to the client, which the client immediately uses to attempt to connect again. Retry +457/// tokens operate on short timescales, such as 15 seconds. +458/// - If a client's path within an active connection is validated, the server may send the client +459/// one or more "validation tokens," which the client may store for use in later connections to +460/// the same server. Validation tokens may be valid for much longer lifetimes than retry token. +461/// +462/// The usage of validation tokens is most impactful in situations where 0-RTT data is also being +463/// used--in particular, in situations where the server sends the client more than three times more +464/// 0.5-RTT data than it has received 0-RTT data. Since the successful completion of a connection +465/// handshake implicitly causes the client's address to be validated, transmission of 0.5-RTT data +466/// is the main situation where a server might be sending application data to an address that could +467/// be validated by token usage earlier than it would become validated without token usage. +468/// +469/// [1]: https://www.rfc-editor.org/rfc/rfc9000.html#section-8 +470/// [2]: https://en.wikipedia.org/wiki/IP_address_spoofing +471/// [3]: https://en.wikipedia.org/wiki/Denial-of-service_attack#Amplification +472/// +473/// These tokens should not be confused with "stateless reset tokens," which are similarly named +474/// but entirely unrelated. +475#[derive(Clone)] +476pub struct ValidationTokenConfig { +477 pub(crate) lifetime: Duration, +478 pub(crate) log: Arc<dyn TokenLog>, +479 pub(crate) sent: u32, +480} +481 +482impl ValidationTokenConfig { +483 /// Duration after an address validation token was issued for which it's considered valid +484 /// +485 /// This refers only to tokens sent in NEW_TOKEN frames, in contrast to retry tokens. +486 /// +487 /// Defaults to 2 weeks. +488 pub fn lifetime(&mut self, value: Duration) -> &mut Self { +489 self.lifetime = value; +490 self +491 } +492 +493 #[allow(rustdoc::redundant_explicit_links)] // which links are redundant depends on features +494 /// Set a custom [`TokenLog`] +495 /// +496 /// If the `bloom` feature is enabled (which it is by default), defaults to a default +497 /// [`BloomTokenLog`][crate::BloomTokenLog], which is suitable for most internet applications. 498 /// -499 /// If the `bloom` feature is enabled (which it is by default), defaults to a default -500 /// [`BloomTokenLog`][crate::BloomTokenLog], which is suitable for most internet applications. -501 /// -502 /// If the `bloom` feature is disabled, defaults to [`NoneTokenLog`][crate::NoneTokenLog], -503 /// which makes the server ignore all address validation tokens (that is, tokens originating -504 /// from NEW_TOKEN frames--retry tokens are not affected). -505 pub fn log(&mut self, log: Arc<dyn TokenLog>) -> &mut Self { -506 self.log = log; -507 self -508 } -509 -510 /// Number of address validation tokens sent to a client when its path is validated -511 /// -512 /// This refers only to tokens sent in NEW_TOKEN frames, in contrast to retry tokens. -513 /// -514 /// If the `bloom` feature is enabled (which it is by default), defaults to 2. Otherwise, -515 /// defaults to 0. -516 pub fn sent(&mut self, value: u32) -> &mut Self { -517 self.sent = value; -518 self -519 } -520} -521 -522impl Default for ValidationTokenConfig { -523 fn default() -> Self { -524 #[cfg(feature = "bloom")] -525 let log = Arc::new(BloomTokenLog::default()); -526 #[cfg(not(feature = "bloom"))] -527 let log = Arc::new(NoneTokenLog); -528 Self { -529 lifetime: Duration::from_secs(2 * 7 * 24 * 60 * 60), -530 log, -531 sent: if cfg!(feature = "bloom") { 2 } else { 0 }, -532 } -533 } -534} -535 -536impl fmt::Debug for ValidationTokenConfig { -537 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { -538 fmt.debug_struct("ServerValidationTokenConfig") -539 .field("lifetime", &self.lifetime) -540 // log not debug -541 .field("sent", &self.sent) -542 .finish_non_exhaustive() -543 } -544} -545 -546/// Configuration for outgoing connections -547/// -548/// Default values should be suitable for most internet applications. -549#[derive(Clone)] -550#[non_exhaustive] -551pub struct ClientConfig { -552 /// Transport configuration to use -553 pub(crate) transport: Arc<TransportConfig>, +499 /// If the `bloom` feature is disabled, defaults to [`NoneTokenLog`][crate::NoneTokenLog], +500 /// which makes the server ignore all address validation tokens (that is, tokens originating +501 /// from NEW_TOKEN frames--retry tokens are not affected). +502 pub fn log(&mut self, log: Arc<dyn TokenLog>) -> &mut Self { +503 self.log = log; +504 self +505 } +506 +507 /// Number of address validation tokens sent to a client when its path is validated +508 /// +509 /// This refers only to tokens sent in NEW_TOKEN frames, in contrast to retry tokens. +510 /// +511 /// If the `bloom` feature is enabled (which it is by default), defaults to 2. Otherwise, +512 /// defaults to 0. +513 pub fn sent(&mut self, value: u32) -> &mut Self { +514 self.sent = value; +515 self +516 } +517} +518 +519impl Default for ValidationTokenConfig { +520 fn default() -> Self { +521 #[cfg(feature = "bloom")] +522 let log = Arc::new(BloomTokenLog::default()); +523 #[cfg(not(feature = "bloom"))] +524 let log = Arc::new(NoneTokenLog); +525 Self { +526 lifetime: Duration::from_secs(2 * 7 * 24 * 60 * 60), +527 log, +528 sent: if cfg!(feature = "bloom") { 2 } else { 0 }, +529 } +530 } +531} +532 +533impl fmt::Debug for ValidationTokenConfig { +534 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { +535 fmt.debug_struct("ServerValidationTokenConfig") +536 .field("lifetime", &self.lifetime) +537 // log not debug +538 .field("sent", &self.sent) +539 .finish_non_exhaustive() +540 } +541} +542 +543/// Configuration for outgoing connections +544/// +545/// Default values should be suitable for most internet applications. +546#[derive(Clone)] +547#[non_exhaustive] +548pub struct ClientConfig { +549 /// Transport configuration to use +550 pub(crate) transport: Arc<TransportConfig>, +551 +552 /// Cryptographic configuration to use +553 pub(crate) crypto: Arc<dyn crypto::ClientConfig>, 554 -555 /// Cryptographic configuration to use -556 pub(crate) crypto: Arc<dyn crypto::ClientConfig>, +555 /// Validation token store to use +556 pub(crate) token_store: Arc<dyn TokenStore>, 557 -558 /// Validation token store to use -559 pub(crate) token_store: Arc<dyn TokenStore>, +558 /// Provider that populates the destination connection ID of Initial Packets +559 pub(crate) initial_dst_cid_provider: Arc<dyn Fn() -> ConnectionId + Send + Sync>, 560 -561 /// Provider that populates the destination connection ID of Initial Packets -562 pub(crate) initial_dst_cid_provider: Arc<dyn Fn() -> ConnectionId + Send + Sync>, -563 -564 /// QUIC protocol version to use -565 pub(crate) version: u32, -566} -567 -568impl ClientConfig { -569 /// Create a default config with a particular cryptographic config -570 pub fn new(crypto: Arc<dyn crypto::ClientConfig>) -> Self { -571 Self { -572 transport: Default::default(), -573 crypto, -574 token_store: Arc::new(TokenMemoryCache::default()), -575 initial_dst_cid_provider: Arc::new(|| { -576 RandomConnectionIdGenerator::new(MAX_CID_SIZE).generate_cid() -577 }), -578 version: 1, -579 } -580 } -581 -582 /// Configure how to populate the destination CID of the initial packet when attempting to -583 /// establish a new connection +561 /// QUIC protocol version to use +562 pub(crate) version: u32, +563} +564 +565impl ClientConfig { +566 /// Create a default config with a particular cryptographic config +567 pub fn new(crypto: Arc<dyn crypto::ClientConfig>) -> Self { +568 Self { +569 transport: Default::default(), +570 crypto, +571 token_store: Arc::new(TokenMemoryCache::default()), +572 initial_dst_cid_provider: Arc::new(|| { +573 RandomConnectionIdGenerator::new(MAX_CID_SIZE).generate_cid() +574 }), +575 version: 1, +576 } +577 } +578 +579 /// Configure how to populate the destination CID of the initial packet when attempting to +580 /// establish a new connection +581 /// +582 /// By default, it's populated with random bytes with reasonable length, so unless you have +583 /// a good reason, you do not need to change it. 584 /// -585 /// By default, it's populated with random bytes with reasonable length, so unless you have -586 /// a good reason, you do not need to change it. -587 /// -588 /// When prefer to override the default, please note that the generated connection ID MUST be -589 /// at least 8 bytes long and unpredictable, as per section 7.2 of RFC 9000. -590 pub fn initial_dst_cid_provider( -591 &mut self, -592 initial_dst_cid_provider: Arc<dyn Fn() -> ConnectionId + Send + Sync>, -593 ) -> &mut Self { -594 self.initial_dst_cid_provider = initial_dst_cid_provider; -595 self -596 } -597 -598 /// Set a custom [`TransportConfig`] -599 pub fn transport_config(&mut self, transport: Arc<TransportConfig>) -> &mut Self { -600 self.transport = transport; -601 self -602 } -603 -604 /// Set a custom [`TokenStore`] -605 /// -606 /// Defaults to [`TokenMemoryCache`], which is suitable for most internet applications. -607 pub fn token_store(&mut self, store: Arc<dyn TokenStore>) -> &mut Self { -608 self.token_store = store; -609 self -610 } -611 -612 /// Set the QUIC version to use -613 pub fn version(&mut self, version: u32) -> &mut Self { -614 self.version = version; -615 self -616 } -617} -618 -619#[cfg(all(feature = "rustls", any(feature = "aws-lc-rs", feature = "ring")))] -620impl ClientConfig { -621 /// Create a client configuration that trusts the platform's native roots -622 #[cfg(feature = "platform-verifier")] -623 pub fn try_with_platform_verifier() -> Result<Self, rustls::Error> { -624 Ok(Self::new(Arc::new( -625 crypto::rustls::QuicClientConfig::with_platform_verifier()?, -626 ))) -627 } -628 -629 /// Create a client configuration that trusts specified trust anchors -630 pub fn with_root_certificates( -631 roots: Arc<rustls::RootCertStore>, -632 ) -> Result<Self, rustls::client::VerifierBuilderError> { -633 Ok(Self::new(Arc::new(crypto::rustls::QuicClientConfig::new( -634 WebPkiServerVerifier::builder_with_provider(roots, configured_provider()).build()?, -635 )))) -636 } -637} -638 -639impl fmt::Debug for ClientConfig { -640 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { -641 fmt.debug_struct("ClientConfig") -642 .field("transport", &self.transport) -643 // crypto not debug -644 // token_store not debug -645 .field("version", &self.version) -646 .finish_non_exhaustive() -647 } -648} -649 -650/// Errors in the configuration of an endpoint -651#[derive(Debug, Error, Clone, PartialEq, Eq)] -652#[non_exhaustive] -653pub enum ConfigError { -654 /// Value exceeds supported bounds -655 #[error("value exceeds supported bounds")] -656 OutOfBounds, -657} -658 -659impl From<TryFromIntError> for ConfigError { -660 fn from(_: TryFromIntError) -> Self { -661 Self::OutOfBounds -662 } -663} -664 -665impl From<VarIntBoundsExceeded> for ConfigError { -666 fn from(_: VarIntBoundsExceeded) -> Self { -667 Self::OutOfBounds -668 } -669} -670 -671/// Object to get current [`SystemTime`] -672/// -673/// This exists to allow system time to be mocked in tests, or wherever else desired. -674pub trait TimeSource: Send + Sync { -675 /// Get [`SystemTime::now()`](SystemTime::now) or the mocked equivalent -676 fn now(&self) -> SystemTime; -677} -678 -679/// Default implementation of [`TimeSource`] -680/// -681/// Implements `now` by calling [`SystemTime::now()`](SystemTime::now). -682pub struct StdSystemTime; -683 -684impl TimeSource for StdSystemTime { -685 fn now(&self) -> SystemTime { -686 SystemTime::now() -687 } -688}

    \ No newline at end of file +585 /// When prefer to override the default, please note that the generated connection ID MUST be +586 /// at least 8 bytes long and unpredictable, as per section 7.2 of RFC 9000. +587 pub fn initial_dst_cid_provider( +588 &mut self, +589 initial_dst_cid_provider: Arc<dyn Fn() -> ConnectionId + Send + Sync>, +590 ) -> &mut Self { +591 self.initial_dst_cid_provider = initial_dst_cid_provider; +592 self +593 } +594 +595 /// Set a custom [`TransportConfig`] +596 pub fn transport_config(&mut self, transport: Arc<TransportConfig>) -> &mut Self { +597 self.transport = transport; +598 self +599 } +600 +601 /// Set a custom [`TokenStore`] +602 /// +603 /// Defaults to [`TokenMemoryCache`], which is suitable for most internet applications. +604 pub fn token_store(&mut self, store: Arc<dyn TokenStore>) -> &mut Self { +605 self.token_store = store; +606 self +607 } +608 +609 /// Set the QUIC version to use +610 pub fn version(&mut self, version: u32) -> &mut Self { +611 self.version = version; +612 self +613 } +614} +615 +616#[cfg(all(feature = "rustls", any(feature = "aws-lc-rs", feature = "ring")))] +617impl ClientConfig { +618 /// Create a client configuration that trusts the platform's native roots +619 #[cfg(feature = "platform-verifier")] +620 pub fn try_with_platform_verifier() -> Result<Self, rustls::Error> { +621 Ok(Self::new(Arc::new( +622 crypto::rustls::QuicClientConfig::with_platform_verifier()?, +623 ))) +624 } +625 +626 /// Create a client configuration that trusts specified trust anchors +627 pub fn with_root_certificates( +628 roots: Arc<rustls::RootCertStore>, +629 ) -> Result<Self, rustls::client::VerifierBuilderError> { +630 Ok(Self::new(Arc::new(crypto::rustls::QuicClientConfig::new( +631 WebPkiServerVerifier::builder_with_provider(roots, configured_provider()).build()?, +632 )))) +633 } +634} +635 +636impl fmt::Debug for ClientConfig { +637 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { +638 fmt.debug_struct("ClientConfig") +639 .field("transport", &self.transport) +640 // crypto not debug +641 // token_store not debug +642 .field("version", &self.version) +643 .finish_non_exhaustive() +644 } +645} +646 +647/// Errors in the configuration of an endpoint +648#[derive(Debug, Error, Clone, PartialEq, Eq)] +649#[non_exhaustive] +650pub enum ConfigError { +651 /// Value exceeds supported bounds +652 #[error("value exceeds supported bounds")] +653 OutOfBounds, +654} +655 +656impl From<TryFromIntError> for ConfigError { +657 fn from(_: TryFromIntError) -> Self { +658 Self::OutOfBounds +659 } +660} +661 +662impl From<VarIntBoundsExceeded> for ConfigError { +663 fn from(_: VarIntBoundsExceeded) -> Self { +664 Self::OutOfBounds +665 } +666} +667 +668/// Object to get current [`SystemTime`] +669/// +670/// This exists to allow system time to be mocked in tests, or wherever else desired. +671pub trait TimeSource: Send + Sync { +672 /// Get [`SystemTime::now()`](SystemTime::now) or the mocked equivalent +673 fn now(&self) -> SystemTime; +674} +675 +676/// Default implementation of [`TimeSource`] +677/// +678/// Implements `now` by calling [`SystemTime::now()`](SystemTime::now). +679pub struct StdSystemTime; +680 +681impl TimeSource for StdSystemTime { +682 fn now(&self) -> SystemTime { +683 SystemTime::now() +684 } +685}

    \ 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 aa22739e6..2f2e14d12 100644 --- a/pr/451/docs/src/noq_proto/config/transport.rs.html +++ b/pr/451/docs/src/noq_proto/config/transport.rs.html @@ -14,899 +14,895 @@ 14#[cfg(feature = "qlog")] 15use crate::{QlogFactory, QlogFileFactory}; 16 -17/// When multipath is required and has not been explicitly enabled, this value will be used for -18/// [`TransportConfig::max_concurrent_multipath_paths`]. -19const DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED_: NonZeroU32 = { -20 match NonZeroU32::new(12) { -21 Some(v) => v, -22 None => panic!("to enable multipath this must be positive, which clearly it is"), -23 } -24}; -25 -26/// When multipath is required and has not been explicitly enabled, this value will be used for -27/// -28/// [`TransportConfig::max_concurrent_multipath_paths`]. -29#[cfg(doc)] -30pub const DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED: NonZeroU32 = -31 DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED_; -32 -33/// Parameters governing the core QUIC state machine -34/// -35/// Default values should be suitable for most internet applications. Applications protocols which -36/// forbid remotely-initiated streams should set `max_concurrent_bidi_streams` and -37/// `max_concurrent_uni_streams` to zero. -38/// -39/// In some cases, performance or resource requirements can be improved by tuning these values to -40/// suit a particular application and/or network connection. In particular, data window sizes can be -41/// tuned for a particular expected round trip time, link capacity, and memory availability. Tuning -42/// for higher bandwidths and latencies increases worst-case memory consumption, but does not impair -43/// performance at lower bandwidths and latencies. The default configuration is tuned for a 100Mbps -44/// link with a 100ms round trip time. -45#[derive(Clone)] -46pub struct TransportConfig { -47 pub(crate) max_concurrent_bidi_streams: VarInt, -48 pub(crate) max_concurrent_uni_streams: VarInt, -49 pub(crate) max_idle_timeout: Option<VarInt>, -50 pub(crate) stream_receive_window: VarInt, -51 pub(crate) receive_window: VarInt, -52 pub(crate) send_window: u64, -53 pub(crate) send_fairness: bool, -54 -55 pub(crate) packet_threshold: u32, -56 pub(crate) time_threshold: f32, -57 pub(crate) initial_rtt: Duration, -58 pub(crate) initial_mtu: u16, -59 pub(crate) min_mtu: u16, -60 pub(crate) mtu_discovery_config: Option<MtuDiscoveryConfig>, -61 pub(crate) pad_to_mtu: bool, -62 pub(crate) ack_frequency_config: Option<AckFrequencyConfig>, +17/// Parameters governing the core QUIC state machine +18/// +19/// Default values should be suitable for most internet applications. Applications protocols which +20/// forbid remotely-initiated streams should set `max_concurrent_bidi_streams` and +21/// `max_concurrent_uni_streams` to zero. +22/// +23/// In some cases, performance or resource requirements can be improved by tuning these values to +24/// suit a particular application and/or network connection. In particular, data window sizes can be +25/// tuned for a particular expected round trip time, link capacity, and memory availability. Tuning +26/// for higher bandwidths and latencies increases worst-case memory consumption, but does not impair +27/// performance at lower bandwidths and latencies. The default configuration is tuned for a 100Mbps +28/// link with a 100ms round trip time. +29#[derive(Clone)] +30pub struct TransportConfig { +31 pub(crate) max_concurrent_bidi_streams: VarInt, +32 pub(crate) max_concurrent_uni_streams: VarInt, +33 pub(crate) max_idle_timeout: Option<VarInt>, +34 pub(crate) stream_receive_window: VarInt, +35 pub(crate) receive_window: VarInt, +36 pub(crate) send_window: u64, +37 pub(crate) send_fairness: bool, +38 +39 pub(crate) packet_threshold: u32, +40 pub(crate) time_threshold: f32, +41 pub(crate) initial_rtt: Duration, +42 pub(crate) initial_mtu: u16, +43 pub(crate) min_mtu: u16, +44 pub(crate) mtu_discovery_config: Option<MtuDiscoveryConfig>, +45 pub(crate) pad_to_mtu: bool, +46 pub(crate) ack_frequency_config: Option<AckFrequencyConfig>, +47 pub(crate) max_outgoing_bytes_per_second: Option<u64>, +48 +49 pub(crate) persistent_congestion_threshold: u32, +50 pub(crate) keep_alive_interval: Option<Duration>, +51 pub(crate) crypto_buffer_size: usize, +52 pub(crate) allow_spin: bool, +53 pub(crate) datagram_receive_buffer_size: Option<usize>, +54 pub(crate) datagram_send_buffer_size: usize, +55 #[cfg(test)] +56 pub(crate) deterministic_packet_numbers: bool, +57 +58 pub(crate) congestion_controller_factory: Arc<dyn congestion::ControllerFactory + Send + Sync>, +59 +60 pub(crate) enable_segmentation_offload: bool, +61 +62 pub(crate) address_discovery_role: address_discovery::Role, 63 -64 pub(crate) persistent_congestion_threshold: u32, -65 pub(crate) keep_alive_interval: Option<Duration>, -66 pub(crate) crypto_buffer_size: usize, -67 pub(crate) allow_spin: bool, -68 pub(crate) datagram_receive_buffer_size: Option<usize>, -69 pub(crate) datagram_send_buffer_size: usize, -70 #[cfg(test)] -71 pub(crate) deterministic_packet_numbers: bool, -72 -73 pub(crate) congestion_controller_factory: Arc<dyn congestion::ControllerFactory + Send + Sync>, +64 pub(crate) max_concurrent_multipath_paths: Option<NonZeroU32>, +65 +66 pub(crate) default_path_max_idle_timeout: Option<Duration>, +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 -75 pub(crate) enable_segmentation_offload: bool, -76 -77 pub(crate) address_discovery_role: address_discovery::Role, -78 -79 pub(crate) max_concurrent_multipath_paths: Option<NonZeroU32>, -80 -81 pub(crate) default_path_max_idle_timeout: Option<Duration>, -82 pub(crate) default_path_keep_alive_interval: Option<Duration>, -83 -84 pub(crate) max_remote_nat_traversal_addresses: Option<NonZeroU8>, -85 -86 #[cfg(feature = "qlog")] -87 pub(crate) qlog_factory: Option<Arc<dyn QlogFactory>>, -88} -89 -90impl TransportConfig { -91 /// Maximum number of incoming bidirectional streams that may be open concurrently -92 /// -93 /// Must be nonzero for the peer to open any bidirectional streams. +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 /// Worst-case memory use is directly proportional to `max_concurrent_bidi_streams * -96 /// stream_receive_window`, with an upper bound proportional to `receive_window`. -97 pub fn max_concurrent_bidi_streams(&mut self, value: VarInt) -> &mut Self { -98 self.max_concurrent_bidi_streams = value; -99 self -100 } -101 -102 /// Variant of `max_concurrent_bidi_streams` affecting unidirectional streams -103 pub fn max_concurrent_uni_streams(&mut self, value: VarInt) -> &mut Self { -104 self.max_concurrent_uni_streams = value; -105 self -106 } -107 -108 /// Maximum duration of inactivity to accept before timing out the connection. +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 /// The true idle timeout is the minimum of this and the peer's own max idle timeout. `None` -111 /// represents an infinite timeout. Defaults to 30 seconds. -112 /// -113 /// **WARNING**: If a peer or its network path malfunctions or acts maliciously, an infinite -114 /// idle timeout can result in permanently hung futures! -115 /// -116 /// ``` -117 /// # use std::{convert::TryInto, time::Duration}; -118 /// # use noq_proto::{TransportConfig, VarInt, VarIntBoundsExceeded}; -119 /// # fn main() -> Result<(), VarIntBoundsExceeded> { -120 /// let mut config = TransportConfig::default(); -121 /// -122 /// // Set the idle timeout as `VarInt`-encoded milliseconds -123 /// config.max_idle_timeout(Some(VarInt::from_u32(10_000).into())); -124 /// -125 /// // Set the idle timeout as a `Duration` -126 /// config.max_idle_timeout(Some(Duration::from_secs(10).try_into()?)); -127 /// # Ok(()) -128 /// # } -129 /// ``` -130 pub fn max_idle_timeout(&mut self, value: Option<IdleTimeout>) -> &mut Self { -131 self.max_idle_timeout = value.map(|t| t.0); -132 self -133 } -134 -135 /// Maximum number of bytes the peer may transmit without acknowledgement on any one stream -136 /// before becoming blocked. -137 /// -138 /// This should be set to at least the expected connection latency multiplied by the maximum -139 /// desired throughput. Setting this smaller than `receive_window` helps ensure that a single -140 /// stream doesn't monopolize receive buffers, which may otherwise occur if the application -141 /// chooses not to read from a large stream for a time while still requiring data on other -142 /// streams. -143 pub fn stream_receive_window(&mut self, value: VarInt) -> &mut Self { -144 self.stream_receive_window = value; -145 self -146 } -147 -148 /// Maximum number of bytes the peer may transmit across all streams of a connection before -149 /// becoming blocked. -150 /// -151 /// This should be set to at least the expected connection latency multiplied by the maximum -152 /// desired throughput. Larger values can be useful to allow maximum throughput within a -153 /// stream while another is blocked. -154 pub fn receive_window(&mut self, value: VarInt) -> &mut Self { -155 self.receive_window = value; -156 self -157 } -158 -159 /// Maximum number of bytes to transmit to a peer without acknowledgment -160 /// -161 /// Provides an upper bound on memory when communicating with peers that issue large amounts of -162 /// flow control credit. Endpoints that wish to handle large numbers of connections robustly -163 /// should take care to set this low enough to guarantee memory exhaustion does not occur if -164 /// every connection uses the entire window. -165 pub fn send_window(&mut self, value: u64) -> &mut Self { -166 self.send_window = value; +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 /// Whether to implement fair queuing for send streams having the same priority. -171 /// -172 /// When enabled, connections schedule data from outgoing streams having the same priority in a -173 /// round-robin fashion. When disabled, streams are scheduled in the order they are written to. -174 /// -175 /// Note that this only affects streams with the same priority. Higher priority streams always -176 /// take precedence over lower priority streams. -177 /// -178 /// Disabling fairness can reduce fragmentation and protocol overhead for workloads that use -179 /// many small streams. -180 pub fn send_fairness(&mut self, value: bool) -> &mut Self { -181 self.send_fairness = value; -182 self -183 } -184 -185 /// Maximum reordering in packet number space before FACK style loss detection considers a -186 /// packet lost. Should not be less than 3, per RFC5681. -187 pub fn packet_threshold(&mut self, value: u32) -> &mut Self { -188 self.packet_threshold = value; -189 self -190 } -191 -192 /// Maximum reordering in time space before time based loss detection considers a packet lost, -193 /// as a factor of RTT -194 pub fn time_threshold(&mut self, value: f32) -> &mut Self { -195 self.time_threshold = value; -196 self -197 } -198 -199 /// The RTT used before an RTT sample is taken -200 pub fn initial_rtt(&mut self, value: Duration) -> &mut Self { -201 self.initial_rtt = value; -202 self -203 } -204 -205 /// The initial value to be used as the maximum UDP payload size before running MTU discovery -206 /// (see [`TransportConfig::mtu_discovery_config`]). -207 /// -208 /// Must be at least 1200, which is the default, and known to be safe for typical internet -209 /// applications. Larger values are more efficient, but increase the risk of packet loss due to -210 /// exceeding the network path's IP MTU. If the provided value is higher than what the network -211 /// path actually supports, packet loss will eventually trigger black hole detection and bring -212 /// it down to [`TransportConfig::min_mtu`]. -213 pub fn initial_mtu(&mut self, value: u16) -> &mut Self { -214 self.initial_mtu = value.max(INITIAL_MTU); -215 self -216 } -217 -218 pub(crate) fn get_initial_mtu(&self) -> u16 { -219 self.initial_mtu.max(self.min_mtu) -220 } -221 -222 /// The maximum UDP payload size guaranteed to be supported by the network. -223 /// -224 /// Must be at least 1200, which is the default, and lower than or equal to -225 /// [`TransportConfig::initial_mtu`]. +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 /// Real-world MTUs can vary according to ISP, VPN, and properties of intermediate network links -228 /// outside of either endpoint's control. Extreme care should be used when raising this value -229 /// outside of private networks where these factors are fully controlled. If the provided value -230 /// is higher than what the network path actually supports, the result will be unpredictable and -231 /// catastrophic packet loss, without a possibility of repair. Prefer -232 /// [`TransportConfig::initial_mtu`] together with -233 /// [`TransportConfig::mtu_discovery_config`] to set a maximum UDP payload size that robustly -234 /// adapts to the network. -235 pub fn min_mtu(&mut self, value: u16) -> &mut Self { -236 self.min_mtu = value.max(INITIAL_MTU); -237 self -238 } -239 -240 /// Specifies the MTU discovery config (see [`MtuDiscoveryConfig`] for details). -241 /// -242 /// Enabled by default. -243 pub fn mtu_discovery_config(&mut self, value: Option<MtuDiscoveryConfig>) -> &mut Self { -244 self.mtu_discovery_config = value; -245 self -246 } -247 -248 /// Pad UDP datagrams carrying application data to current maximum UDP payload size -249 /// -250 /// Disabled by default. UDP datagrams containing loss probes are exempt from padding. +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 /// Enabling this helps mitigate traffic analysis by network observers, but it increases -253 /// bandwidth usage. Without this mitigation precise plain text size of application datagrams as -254 /// well as the total size of stream write bursts can be inferred by observers under certain -255 /// conditions. This analysis requires either an uncongested connection or application datagrams -256 /// too large to be coalesced. -257 pub fn pad_to_mtu(&mut self, value: bool) -> &mut Self { -258 self.pad_to_mtu = value; -259 self -260 } -261 -262 /// Specifies the ACK frequency config (see [`AckFrequencyConfig`] for details) -263 /// -264 /// The provided configuration will be ignored if the peer does not support the acknowledgement -265 /// frequency QUIC extension. -266 /// -267 /// Defaults to `None`, which disables controlling the peer's acknowledgement frequency. Even -268 /// if set to `None`, the local side still supports the acknowledgement frequency QUIC -269 /// extension and may use it in other ways. -270 pub fn ack_frequency_config(&mut self, value: Option<AckFrequencyConfig>) -> &mut Self { -271 self.ack_frequency_config = value; -272 self -273 } -274 -275 /// Number of consecutive PTOs after which network is considered to be experiencing persistent congestion. -276 pub fn persistent_congestion_threshold(&mut self, value: u32) -> &mut Self { -277 self.persistent_congestion_threshold = value; -278 self -279 } -280 -281 /// Period of inactivity before sending a keep-alive packet -282 /// -283 /// Keep-alive packets prevent an inactive but otherwise healthy connection from timing out. -284 /// -285 /// `None` to disable, which is the default. Only one side of any given connection needs keep-alive -286 /// enabled for the connection to be preserved. Must be set lower than the idle_timeout of both -287 /// peers to be effective. -288 pub fn keep_alive_interval(&mut self, value: Option<Duration>) -> &mut Self { -289 self.keep_alive_interval = value; -290 self -291 } -292 -293 /// Maximum quantity of out-of-order crypto layer data to buffer -294 pub fn crypto_buffer_size(&mut self, value: usize) -> &mut Self { -295 self.crypto_buffer_size = value; -296 self -297 } -298 -299 /// Whether the implementation is permitted to set the spin bit on this connection -300 /// -301 /// This allows passive observers to easily judge the round trip time of a connection, which can -302 /// be useful for network administration but sacrifices a small amount of privacy. -303 pub fn allow_spin(&mut self, value: bool) -> &mut Self { -304 self.allow_spin = value; -305 self -306 } -307 -308 /// Maximum number of incoming application datagram bytes to buffer, or None to disable -309 /// incoming datagrams -310 /// -311 /// The peer is forbidden to send single datagrams larger than this size. If the aggregate size -312 /// of all datagrams that have been received from the peer but not consumed by the application -313 /// exceeds this value, old datagrams are dropped until it is no longer exceeded. -314 pub fn datagram_receive_buffer_size(&mut self, value: Option<usize>) -> &mut Self { -315 self.datagram_receive_buffer_size = value; -316 self -317 } -318 -319 /// Maximum number of outgoing application datagram bytes to buffer -320 /// -321 /// While datagrams are sent ASAP, it is possible for an application to generate data faster -322 /// than the link, or even the underlying hardware, can transmit them. This limits the amount of -323 /// memory that may be consumed in that case. When the send buffer is full and a new datagram is -324 /// sent, older datagrams are dropped until sufficient space is available. -325 pub fn datagram_send_buffer_size(&mut self, value: usize) -> &mut Self { -326 self.datagram_send_buffer_size = value; -327 self -328 } -329 -330 /// Whether to force every packet number to be used -331 /// -332 /// By default, packet numbers are occasionally skipped to ensure peers aren't ACKing packets -333 /// before they see them. -334 #[cfg(test)] -335 pub(crate) fn deterministic_packet_numbers(&mut self, enabled: bool) -> &mut Self { -336 self.deterministic_packet_numbers = enabled; -337 self -338 } -339 -340 /// How to construct new `congestion::Controller`s -341 /// -342 /// Typically the refcounted configuration of a `congestion::Controller`, -343 /// e.g. a `congestion::NewRenoConfig`. -344 /// -345 /// # Example -346 /// ``` -347 /// # use noq_proto::*; use std::sync::Arc; -348 /// let mut config = TransportConfig::default(); -349 /// config.congestion_controller_factory(Arc::new(congestion::NewRenoConfig::default())); -350 /// ``` -351 pub fn congestion_controller_factory( -352 &mut self, -353 factory: Arc<dyn congestion::ControllerFactory + Send + Sync + 'static>, -354 ) -> &mut Self { -355 self.congestion_controller_factory = factory; -356 self -357 } -358 -359 /// Whether to use "Generic Segmentation Offload" to accelerate transmits, when supported by the -360 /// environment -361 /// -362 /// Defaults to `true`. -363 /// -364 /// GSO dramatically reduces CPU consumption when sending large numbers of packets with the same -365 /// headers, such as when transmitting bulk data on a connection. However, it is not supported -366 /// by all network interface drivers or packet inspection tools. `noq-udp` will attempt to -367 /// disable GSO automatically when unavailable, but this can lead to spurious packet loss at -368 /// startup, temporarily degrading performance. -369 pub fn enable_segmentation_offload(&mut self, enabled: bool) -> &mut Self { -370 self.enable_segmentation_offload = enabled; -371 self -372 } -373 -374 /// Whether to send observed address reports to peers. -375 /// -376 /// This will aid peers in inferring their reachable address, which in most NATd networks -377 /// will not be easily available to them. -378 pub fn send_observed_address_reports(&mut self, enabled: bool) -> &mut Self { -379 self.address_discovery_role.send_reports_to_peers(enabled); -380 self -381 } -382 -383 /// Whether to receive observed address reports from other peers. -384 /// -385 /// Peers with the address discovery extension enabled that are willing to provide observed -386 /// address reports will do so if this transport parameter is set. In general, observed address -387 /// reports cannot be trusted. This, however, can aid the current endpoint in inferring its -388 /// reachable address, which in most NATd networks will not be easily available. -389 pub fn receive_observed_address_reports(&mut self, enabled: bool) -> &mut Self { -390 self.address_discovery_role -391 .receive_reports_from_peers(enabled); -392 self -393 } -394 -395 /// Enables the Multipath Extension for QUIC. -396 /// -397 /// Setting this to any nonzero value will enable the Multipath Extension for QUIC, -398 /// <https://datatracker.ietf.org/doc/draft-ietf-quic-multipath/>. -399 /// -400 /// The value provided specifies the number maximum number of paths this endpoint may open -401 /// concurrently when multipath is negotiated. For any path to be opened, the remote must -402 /// enable multipath as well. -403 pub fn max_concurrent_multipath_paths(&mut self, max_concurrent: u32) -> &mut Self { -404 self.max_concurrent_multipath_paths = NonZeroU32::new(max_concurrent); -405 self -406 } -407 -408 /// Sets a default per-path maximum idle timeout +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); +385 self +386 } +387 +388 /// Enables the Multipath Extension for QUIC. +389 /// +390 /// Setting this to any nonzero value will enable the Multipath Extension for QUIC, +391 /// <https://datatracker.ietf.org/doc/draft-ietf-quic-multipath/>. +392 /// +393 /// The value provided specifies the number maximum number of paths this endpoint may open +394 /// concurrently when multipath is negotiated. For any path to be opened, the remote must +395 /// enable multipath as well. +396 pub fn max_concurrent_multipath_paths(&mut self, max_concurrent: u32) -> &mut Self { +397 self.max_concurrent_multipath_paths = NonZeroU32::new(max_concurrent); +398 self +399 } +400 +401 /// Sets a default per-path maximum idle timeout +402 /// +403 /// If the path is idle for this long the path will be abandoned. Bear in mind this will +404 /// interact with the [`TransportConfig::max_idle_timeout`], if the last path is +405 /// abandoned the entire connection will be closed. +406 /// +407 /// You can also change this using [`Connection::set_path_max_idle_timeout`] for +408 /// existing paths. 409 /// -410 /// If the path is idle for this long the path will be abandoned. Bear in mind this will -411 /// interact with the [`TransportConfig::max_idle_timeout`], if the last path is -412 /// abandoned the entire connection will be closed. -413 /// -414 /// You can also change this using [`Connection::set_path_max_idle_timeout`] for -415 /// existing paths. -416 /// -417 /// [`Connection::set_path_max_idle_timeout`]: crate::Connection::set_path_max_idle_timeout -418 pub fn default_path_max_idle_timeout(&mut self, timeout: Option<Duration>) -> &mut Self { -419 self.default_path_max_idle_timeout = timeout; -420 self -421 } -422 -423 /// Sets a default per-path keep alive interval -424 /// -425 /// Note that this does not interact with the connection-wide -426 /// [`TransportConfig::keep_alive_interval`]. This setting will keep this path active, -427 /// [`TransportConfig::keep_alive_interval`] will keep the connection active, with no -428 /// control over which path is used for this. -429 /// -430 /// You can also change this using [`Connection::set_path_keep_alive_interval`] for -431 /// existing path. -432 /// -433 /// [`Connection::set_path_keep_alive_interval`]: crate::Connection::set_path_keep_alive_interval -434 pub fn default_path_keep_alive_interval(&mut self, interval: Option<Duration>) -> &mut Self { -435 self.default_path_keep_alive_interval = interval; -436 self -437 } -438 -439 /// Get the initial max [`crate::PathId`] this endpoint allows. -440 /// -441 /// Returns `None` if multipath is disabled. -442 pub(crate) fn get_initial_max_path_id(&self) -> Option<crate::PathId> { -443 self.max_concurrent_multipath_paths -444 // a max_concurrent_multipath_paths value of 1 only allows the first path, which -445 // has id 0 -446 .map(|nonzero_concurrent| nonzero_concurrent.get() - 1) -447 .map(Into::into) -448 } -449 -450 /// Sets the maximum number of nat traversal addresses this endpoint allows the remote to -451 /// advertise -452 /// -453 /// Setting this to any nonzero value will enable n0's nat traversal protocol, loosely based in -454 /// the Nat Traversal Extension for QUIC, see -455 /// <https://www.ietf.org/archive/id/draft-seemann-quic-nat-traversal-02.html> -456 /// -457 /// This implementation expects the multipath extension to be enabled as well. if not yet -458 /// enabled via [`Self::max_concurrent_multipath_paths`], a default value of -459 /// [`DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED`] will be used. -460 pub fn set_max_remote_nat_traversal_addresses(&mut self, max_addresses: u8) -> &mut Self { -461 self.max_remote_nat_traversal_addresses = NonZeroU8::new(max_addresses); -462 if max_addresses != 0 && self.max_concurrent_multipath_paths.is_none() { -463 self.max_concurrent_multipath_paths( -464 DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED_.get(), -465 ); -466 } +410 /// [`Connection::set_path_max_idle_timeout`]: crate::Connection::set_path_max_idle_timeout +411 pub fn default_path_max_idle_timeout(&mut self, timeout: Option<Duration>) -> &mut Self { +412 self.default_path_max_idle_timeout = timeout; +413 self +414 } +415 +416 /// Sets a default per-path keep alive interval +417 /// +418 /// Note that this does not interact with the connection-wide +419 /// [`TransportConfig::keep_alive_interval`]. This setting will keep this path active, +420 /// [`TransportConfig::keep_alive_interval`] will keep the connection active, with no +421 /// control over which path is used for this. +422 /// +423 /// You can also change this using [`Connection::set_path_keep_alive_interval`] for +424 /// existing path. +425 /// +426 /// [`Connection::set_path_keep_alive_interval`]: crate::Connection::set_path_keep_alive_interval +427 pub fn default_path_keep_alive_interval(&mut self, interval: Option<Duration>) -> &mut Self { +428 self.default_path_keep_alive_interval = interval; +429 self +430 } +431 +432 /// Get the initial max [`crate::PathId`] this endpoint allows. +433 /// +434 /// Returns `None` if multipath is disabled. +435 pub(crate) fn get_initial_max_path_id(&self) -> Option<crate::PathId> { +436 self.max_concurrent_multipath_paths +437 // a max_concurrent_multipath_paths value of 1 only allows the first path, which +438 // has id 0 +439 .map(|nonzero_concurrent| nonzero_concurrent.get() - 1) +440 .map(Into::into) +441 } +442 +443 /// Sets the maximum number of nat traversal addresses this endpoint allows the remote to +444 /// advertise +445 /// +446 /// Setting this to any nonzero value will enable n0's nat traversal protocol, loosely based in +447 /// the Nat Traversal Extension for QUIC, see +448 /// <https://www.ietf.org/archive/id/draft-seemann-quic-nat-traversal-02.html> +449 /// +450 /// This implementation expects the multipath extension to be enabled as well. if not yet +451 /// enabled via [`Self::max_concurrent_multipath_paths`], then that setting is set to 8. +452 pub fn max_remote_nat_traversal_addresses(&mut self, max_addresses: u8) -> &mut Self { +453 self.max_remote_nat_traversal_addresses = NonZeroU8::new(max_addresses); +454 if max_addresses != 0 && self.max_concurrent_multipath_paths.is_none() { +455 self.max_concurrent_multipath_paths(8); +456 } +457 self +458 } +459 +460 /// Configures qlog capturing by setting a [`QlogFactory`]. +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 by setting a [`QlogFactory`]. +470 /// Configures qlog capturing through the `QLOGDIR` environment variable. 471 /// -472 /// This assigns a [`QlogFactory`] that produces qlog capture configurations for -473 /// individual connections. -474 #[cfg(feature = "qlog")] -475 pub fn qlog_factory(&mut self, factory: Arc<dyn QlogFactory>) -> &mut Self { -476 self.qlog_factory = Some(factory); -477 self -478 } -479 -480 /// Configures qlog capturing through the `QLOGDIR` environment variable. -481 /// -482 /// This uses [`QlogFileFactory::from_env`] to create a factory to write qlog traces -483 /// into the directory set through the `QLOGDIR` environment variable. -484 /// -485 /// If `QLOGDIR` is not set, no traces will be written. If `QLOGDIR` is set to a path -486 /// that does not exist, it will be created. -487 /// -488 /// The files will be prefixed with `prefix`. -489 #[cfg(feature = "qlog")] -490 pub fn qlog_from_env(&mut self, prefix: &str) -> &mut Self { -491 self.qlog_factory(Arc::new(QlogFileFactory::from_env().with_prefix(prefix))) -492 } -493 -494 /// Configures qlog capturing into a directory. -495 /// -496 /// This uses [`QlogFileFactory`] to create a factory to write qlog traces into -497 /// the specified directory. The files will be prefixed with `prefix`. -498 #[cfg(feature = "qlog")] -499 pub fn qlog_from_path(&mut self, path: impl AsRef<Path>, prefix: &str) -> &mut Self { -500 self.qlog_factory(Arc::new( -501 QlogFileFactory::new(path.as_ref().to_owned()).with_prefix(prefix), -502 )) -503 } -504 -505 pub(crate) fn create_qlog_sink( -506 &self, -507 side: Side, -508 remote: SocketAddr, -509 initial_dst_cid: ConnectionId, -510 now: Instant, -511 ) -> QlogSink { -512 #[cfg(not(feature = "qlog"))] -513 let sink = { -514 let _ = (side, remote, initial_dst_cid, now); -515 QlogSink::default() -516 }; -517 -518 #[cfg(feature = "qlog")] -519 let sink = { -520 if let Some(config) = self -521 .qlog_factory -522 .as_ref() -523 .and_then(|factory| factory.for_connection(side, remote, initial_dst_cid, now)) -524 { -525 QlogSink::new(config, initial_dst_cid, side, now) -526 } else { -527 QlogSink::default() -528 } -529 }; -530 -531 sink -532 } -533} -534 -535impl Default for TransportConfig { -536 fn default() -> Self { -537 const EXPECTED_RTT: u32 = 100; // ms -538 const MAX_STREAM_BANDWIDTH: u32 = 12500 * 1000; // bytes/s -539 // Window size needed to avoid pipeline -540 // stalls -541 const STREAM_RWND: u32 = MAX_STREAM_BANDWIDTH / 1000 * EXPECTED_RTT; +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 Self { -544 max_concurrent_bidi_streams: 100u32.into(), -545 max_concurrent_uni_streams: 100u32.into(), -546 // 30 second default recommended by RFC 9308 § 3.2 -547 max_idle_timeout: Some(VarInt(30_000)), -548 stream_receive_window: STREAM_RWND.into(), -549 receive_window: VarInt::MAX, -550 send_window: (8 * STREAM_RWND).into(), -551 send_fairness: true, +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 packet_threshold: 3, -554 time_threshold: 9.0 / 8.0, -555 initial_rtt: Duration::from_millis(333), // per spec, intentionally distinct from EXPECTED_RTT -556 initial_mtu: INITIAL_MTU, -557 min_mtu: INITIAL_MTU, -558 mtu_discovery_config: Some(MtuDiscoveryConfig::default()), -559 pad_to_mtu: false, -560 ack_frequency_config: None, +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, 561 -562 persistent_congestion_threshold: 3, -563 keep_alive_interval: None, -564 crypto_buffer_size: 16 * 1024, -565 allow_spin: true, -566 datagram_receive_buffer_size: Some(STREAM_RWND as usize), -567 datagram_send_buffer_size: 1024 * 1024, -568 #[cfg(test)] -569 deterministic_packet_numbers: false, -570 -571 congestion_controller_factory: Arc::new(congestion::CubicConfig::default()), +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 enable_segmentation_offload: true, -574 -575 address_discovery_role: address_discovery::Role::default(), -576 -577 // disabled multipath by default -578 max_concurrent_multipath_paths: None, -579 default_path_max_idle_timeout: None, -580 default_path_keep_alive_interval: None, +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 -582 // nat traversal disabled by default -583 max_remote_nat_traversal_addresses: None, -584 -585 #[cfg(feature = "qlog")] -586 qlog_factory: None, -587 } -588 } -589} -590 -591impl fmt::Debug for TransportConfig { -592 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { -593 let Self { -594 max_concurrent_bidi_streams, -595 max_concurrent_uni_streams, -596 max_idle_timeout, -597 stream_receive_window, -598 receive_window, -599 send_window, -600 send_fairness, -601 packet_threshold, -602 time_threshold, -603 initial_rtt, -604 initial_mtu, -605 min_mtu, -606 mtu_discovery_config, -607 pad_to_mtu, -608 ack_frequency_config, -609 persistent_congestion_threshold, -610 keep_alive_interval, -611 crypto_buffer_size, -612 allow_spin, -613 datagram_receive_buffer_size, -614 datagram_send_buffer_size, -615 #[cfg(test)] -616 deterministic_packet_numbers: _, -617 congestion_controller_factory: _, -618 enable_segmentation_offload, -619 address_discovery_role, -620 max_concurrent_multipath_paths, -621 default_path_max_idle_timeout, -622 default_path_keep_alive_interval, -623 max_remote_nat_traversal_addresses, -624 #[cfg(feature = "qlog")] -625 qlog_factory, -626 } = self; -627 let mut s = fmt.debug_struct("TransportConfig"); -628 -629 s.field("max_concurrent_bidi_streams", max_concurrent_bidi_streams) -630 .field("max_concurrent_uni_streams", max_concurrent_uni_streams) -631 .field("max_idle_timeout", max_idle_timeout) -632 .field("stream_receive_window", stream_receive_window) -633 .field("receive_window", receive_window) -634 .field("send_window", send_window) -635 .field("send_fairness", send_fairness) -636 .field("packet_threshold", packet_threshold) -637 .field("time_threshold", time_threshold) -638 .field("initial_rtt", initial_rtt) -639 .field("initial_mtu", initial_mtu) -640 .field("min_mtu", min_mtu) -641 .field("mtu_discovery_config", mtu_discovery_config) -642 .field("pad_to_mtu", pad_to_mtu) -643 .field("ack_frequency_config", ack_frequency_config) -644 .field( -645 "persistent_congestion_threshold", -646 persistent_congestion_threshold, -647 ) -648 .field("keep_alive_interval", keep_alive_interval) -649 .field("crypto_buffer_size", crypto_buffer_size) -650 .field("allow_spin", allow_spin) -651 .field("datagram_receive_buffer_size", datagram_receive_buffer_size) -652 .field("datagram_send_buffer_size", datagram_send_buffer_size) -653 // congestion_controller_factory not debug -654 .field("enable_segmentation_offload", enable_segmentation_offload) -655 .field("address_discovery_role", address_discovery_role) +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 "max_concurrent_multipath_paths", -658 max_concurrent_multipath_paths, +657 "default_path_max_idle_timeout", +658 default_path_max_idle_timeout, 659 ) 660 .field( -661 "default_path_max_idle_timeout", -662 default_path_max_idle_timeout, +661 "default_path_keep_alive_interval", +662 default_path_keep_alive_interval, 663 ) 664 .field( -665 "default_path_keep_alive_interval", -666 default_path_keep_alive_interval, -667 ) -668 .field( -669 "max_remote_nat_traversal_addresses", -670 max_remote_nat_traversal_addresses, -671 ); -672 #[cfg(feature = "qlog")] -673 s.field("qlog_factory", &qlog_factory.is_some()); +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 s.finish_non_exhaustive() -676 } -677} -678 -679/// Parameters for controlling the peer's acknowledgement frequency +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/// The parameters provided in this config will be sent to the peer at the beginning of the -682/// connection, so it can take them into account when sending acknowledgements (see each parameter's -683/// description for details on how it influences acknowledgement frequency). -684/// -685/// noq's implementation follows the fourth draft of the -686/// [QUIC Acknowledgement Frequency extension](https://datatracker.ietf.org/doc/html/draft-ietf-quic-ack-frequency-04). -687/// The defaults produce behavior slightly different than the behavior without this extension, -688/// because they change the way reordered packets are handled (see -689/// [`AckFrequencyConfig::reordering_threshold`] for details). -690#[derive(Clone, Debug)] -691pub struct AckFrequencyConfig { -692 pub(crate) ack_eliciting_threshold: VarInt, -693 pub(crate) max_ack_delay: Option<Duration>, -694 pub(crate) reordering_threshold: VarInt, -695} -696 -697impl AckFrequencyConfig { -698 /// The ack-eliciting threshold we will request the peer to use -699 /// -700 /// This threshold represents the number of ack-eliciting packets an endpoint may receive -701 /// without immediately sending an ACK. +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} +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 /// The remote peer should send at least one ACK frame when more than this number of -704 /// ack-eliciting packets have been received. A value of 0 results in a receiver immediately -705 /// acknowledging every ack-eliciting packet. -706 /// -707 /// Defaults to 1, which sends ACK frames for every other ack-eliciting packet. -708 pub fn ack_eliciting_threshold(&mut self, value: VarInt) -> &mut Self { -709 self.ack_eliciting_threshold = value; -710 self -711 } -712 -713 /// The `max_ack_delay` we will request the peer to use -714 /// -715 /// This parameter represents the maximum amount of time that an endpoint waits before sending -716 /// an ACK when the ack-eliciting threshold hasn't been reached. -717 /// -718 /// The effective `max_ack_delay` will be clamped to be at least the peer's `min_ack_delay` -719 /// transport parameter, and at most the greater of the current path RTT or 25ms. -720 /// -721 /// Defaults to `None`, in which case the peer's original `max_ack_delay` will be used, as -722 /// obtained from its transport parameters. -723 pub fn max_ack_delay(&mut self, value: Option<Duration>) -> &mut Self { -724 self.max_ack_delay = value; -725 self -726 } -727 -728 /// The reordering threshold we will request the peer to use +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 /// This threshold represents the amount of out-of-order packets that will trigger an endpoint -731 /// to send an ACK, without waiting for `ack_eliciting_threshold` to be exceeded or for -732 /// `max_ack_delay` to be elapsed. +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 /// A value of 0 indicates out-of-order packets do not elicit an immediate ACK. A value of 1 -735 /// immediately acknowledges any packets that are received out of order (this is also the -736 /// behavior when the extension is disabled). -737 /// -738 /// It is recommended to set this value to [`TransportConfig::packet_threshold`] minus one. -739 /// Since the default value for [`TransportConfig::packet_threshold`] is 3, this value defaults -740 /// to 2. -741 pub fn reordering_threshold(&mut self, value: VarInt) -> &mut Self { -742 self.reordering_threshold = value; -743 self -744 } -745} -746 -747impl Default for AckFrequencyConfig { -748 fn default() -> Self { -749 Self { -750 ack_eliciting_threshold: VarInt(1), -751 max_ack_delay: None, -752 reordering_threshold: VarInt(2), -753 } -754 } -755} -756 -757/// Parameters governing MTU discovery. -758/// -759/// # The why of MTU discovery -760/// -761/// By design, QUIC ensures during the handshake that the network path between the client and the -762/// server is able to transmit unfragmented UDP packets with a body of 1200 bytes. In other words, -763/// once the connection is established, we know that the network path's maximum transmission unit -764/// (MTU) is of at least 1200 bytes (plus IP and UDP headers). Because of this, a QUIC endpoint can -765/// split outgoing data in packets of 1200 bytes, with confidence that the network will be able to -766/// deliver them (if the endpoint were to send bigger packets, they could prove too big and end up -767/// being dropped). -768/// -769/// There is, however, a significant overhead associated to sending a packet. If the same -770/// information can be sent in fewer packets, that results in higher throughput. The amount of -771/// packets that need to be sent is inversely proportional to the MTU: the higher the MTU, the -772/// bigger the packets that can be sent, and the fewer packets that are needed to transmit a given -773/// amount of bytes. -774/// -775/// Most networks have an MTU higher than 1200. Through MTU discovery, endpoints can detect the -776/// path's MTU and, if it turns out to be higher, start sending bigger packets. -777/// -778/// # MTU discovery internals -779/// -780/// noq implements MTU discovery through DPLPMTUD (Datagram Packetization Layer Path MTU -781/// Discovery), described in [section 14.3 of RFC -782/// 9000](https://www.rfc-editor.org/rfc/rfc9000.html#section-14.3). This method consists of sending -783/// QUIC packets padded to a particular size (called PMTU probes), and waiting to see if the remote -784/// peer responds with an ACK. If an ACK is received, that means the probe arrived at the remote -785/// peer, which in turn means that the network path's MTU is of at least the packet's size. If the -786/// probe is lost, it is sent another 2 times before concluding that the MTU is lower than the -787/// packet's size. -788/// -789/// MTU discovery runs on a schedule (e.g. every 600 seconds) specified through -790/// [`MtuDiscoveryConfig::interval`]. The first run happens right after the handshake, and -791/// subsequent discoveries are scheduled to run when the interval has elapsed, starting from the -792/// last time when MTU discovery completed. -793/// -794/// Since the search space for MTUs is quite big (the smallest possible MTU is 1200, and the highest -795/// is 65527), noq performs a binary search to keep the number of probes as low as possible. The -796/// lower bound of the search is equal to [`TransportConfig::initial_mtu`] in the -797/// initial MTU discovery run, and is equal to the currently discovered MTU in subsequent runs. The -798/// upper bound is determined by the minimum of [`MtuDiscoveryConfig::upper_bound`] and the -799/// `max_udp_payload_size` transport parameter received from the peer during the handshake. -800/// -801/// # Black hole detection -802/// -803/// If, at some point, the network path no longer accepts packets of the detected size, packet loss -804/// will eventually trigger black hole detection and reset the detected MTU to 1200. In that case, -805/// MTU discovery will be triggered after [`MtuDiscoveryConfig::black_hole_cooldown`] (ignoring the -806/// timer that was set based on [`MtuDiscoveryConfig::interval`]). -807/// -808/// # Interaction between peers -809/// -810/// There is no guarantee that the MTU on the path between A and B is the same as the MTU of the -811/// path between B and A. Therefore, each peer in the connection needs to run MTU discovery -812/// independently in order to discover the path's MTU. -813#[derive(Clone, Debug)] -814pub struct MtuDiscoveryConfig { -815 pub(crate) interval: Duration, -816 pub(crate) upper_bound: u16, -817 pub(crate) minimum_change: u16, -818 pub(crate) black_hole_cooldown: Duration, -819} -820 -821impl MtuDiscoveryConfig { -822 /// Specifies the time to wait after completing MTU discovery before starting a new MTU -823 /// discovery run. -824 /// -825 /// Defaults to 600 seconds, as recommended by [RFC -826 /// 8899](https://www.rfc-editor.org/rfc/rfc8899). -827 pub fn interval(&mut self, value: Duration) -> &mut Self { -828 self.interval = value; -829 self -830 } -831 -832 /// Specifies the upper bound to the max UDP payload size that MTU discovery will search for. -833 /// -834 /// Defaults to 1452, to stay within Ethernet's MTU when using IPv4 and IPv6. The highest -835 /// allowed value is 65527, which corresponds to the maximum permitted UDP payload on IPv6. -836 /// -837 /// It is safe to use an arbitrarily high upper bound, regardless of the network path's MTU. The -838 /// only drawback is that MTU discovery might take more time to finish. -839 pub fn upper_bound(&mut self, value: u16) -> &mut Self { -840 self.upper_bound = value.min(MAX_UDP_PAYLOAD); -841 self -842 } -843 -844 /// Specifies the amount of time that MTU discovery should wait after a black hole was detected -845 /// before running again. Defaults to one minute. -846 /// -847 /// Black hole detection can be spuriously triggered in case of congestion, so it makes sense to -848 /// try MTU discovery again after a short period of time. -849 pub fn black_hole_cooldown(&mut self, value: Duration) -> &mut Self { -850 self.black_hole_cooldown = value; -851 self -852 } -853 -854 /// Specifies the minimum MTU change to stop the MTU discovery phase. -855 /// Defaults to 20. -856 pub fn minimum_change(&mut self, value: u16) -> &mut Self { -857 self.minimum_change = value; -858 self -859 } -860} -861 -862impl Default for MtuDiscoveryConfig { -863 fn default() -> Self { -864 Self { -865 interval: Duration::from_secs(600), -866 upper_bound: 1452, -867 black_hole_cooldown: Duration::from_secs(60), -868 minimum_change: 20, -869 } -870 } -871} -872 -873/// Maximum duration of inactivity to accept before timing out the connection -874/// -875/// This wraps an underlying [`VarInt`], representing the duration in milliseconds. Values can be -876/// constructed by converting directly from `VarInt`, or using `TryFrom<Duration>`. -877/// -878/// ``` -879/// # use std::{convert::TryFrom, time::Duration}; -880/// # use noq_proto::{IdleTimeout, VarIntBoundsExceeded, VarInt}; -881/// # fn main() -> Result<(), VarIntBoundsExceeded> { -882/// // A `VarInt`-encoded value in milliseconds -883/// let timeout = IdleTimeout::from(VarInt::from_u32(10_000)); -884/// -885/// // Try to convert a `Duration` into a `VarInt`-encoded timeout -886/// let timeout = IdleTimeout::try_from(Duration::from_secs(10))?; -887/// # Ok(()) -888/// # } -889/// ``` -890#[derive(Default, Copy, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)] -891pub struct IdleTimeout(VarInt); -892 -893impl From<VarInt> for IdleTimeout { -894 fn from(inner: VarInt) -> Self { -895 Self(inner) -896 } -897} -898 -899impl std::convert::TryFrom<Duration> for IdleTimeout { -900 type Error = VarIntBoundsExceeded; -901 -902 fn try_from(timeout: Duration) -> Result<Self, Self::Error> { -903 let inner = VarInt::try_from(timeout.as_millis())?; -904 Ok(Self(inner)) -905 } -906} -907 -908impl fmt::Debug for IdleTimeout { -909 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -910 self.0.fmt(f) -911 } -912}

    \ No newline at end of file +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. +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; +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 diff --git a/pr/451/docs/src/noq_proto/congestion.rs.html b/pr/451/docs/src/noq_proto/congestion.rs.html index 89f42787a..9c36edc6d 100644 --- a/pr/451/docs/src/noq_proto/congestion.rs.html +++ b/pr/451/docs/src/noq_proto/congestion.rs.html @@ -1,15 +1,15 @@ congestion.rs - source

    noq_proto/
    congestion.rs

    1//! Logic for controlling the rate at which data is sent
     2
    -3use crate::Instant;
    -4use crate::connection::RttEstimator;
    +3use crate::connection::RttEstimator;
    +4use crate::{Duration, Instant};
     5use std::any::Any;
     6use std::sync::Arc;
     7
    -8mod bbr;
    +8mod bbr3;
     9mod cubic;
     10mod new_reno;
     11
    -12pub use bbr::{Bbr, BbrConfig};
    +12pub use bbr3::{Bbr3, Bbr3Config};
     13pub use cubic::{Cubic, CubicConfig};
     14pub use new_reno::{NewReno, NewRenoConfig};
     15
    @@ -17,96 +17,132 @@
     17pub trait Controller: Send + Sync + std::fmt::Debug {
     18    /// One or more packets were just sent
     19    #[allow(unused_variables)]
    -20    fn on_sent(&mut self, now: Instant, bytes: u64, last_packet_number: u64) {}
    +20    fn on_sent(&mut self, now: Instant, bytes: u64, largest_pn: u64) {}
     21
    -22    /// Packet deliveries were confirmed
    -23    ///
    -24    /// `app_limited` indicates whether the connection was blocked on outgoing
    -25    /// application data prior to receiving these acknowledgements.
    -26    #[allow(unused_variables)]
    -27    fn on_ack(
    -28        &mut self,
    -29        now: Instant,
    -30        sent: Instant,
    -31        bytes: u64,
    -32        app_limited: bool,
    -33        rtt: &RttEstimator,
    -34    ) {
    -35    }
    -36
    -37    /// Packets are acked in batches, all with the same `now` argument. This indicates one of those batches has completed.
    -38    #[allow(unused_variables)]
    -39    fn on_end_acks(
    -40        &mut self,
    -41        now: Instant,
    -42        in_flight: u64,
    -43        app_limited: bool,
    -44        largest_packet_num_acked: Option<u64>,
    -45    ) {
    -46    }
    -47
    -48    /// Packets were deemed lost or marked congested
    -49    ///
    -50    /// `in_persistent_congestion` indicates whether all packets sent within the persistent
    -51    /// congestion threshold period ending when the most recent packet in this batch was sent were
    -52    /// lost.
    -53    /// `lost_bytes` indicates how many bytes were lost. This value will be 0 for ECN triggers.
    -54    fn on_congestion_event(
    -55        &mut self,
    -56        now: Instant,
    -57        sent: Instant,
    -58        is_persistent_congestion: bool,
    -59        is_ecn: bool,
    -60        lost_bytes: u64,
    -61    );
    -62
    -63    /// Packets were incorrectly deemed lost
    -64    ///
    -65    /// This function is called when all packets that were deemed lost (for instance because
    -66    /// of packet reordering) are acknowledged after the congestion event was raised.
    -67    fn on_spurious_congestion_event(&mut self) {}
    -68
    -69    /// The known MTU for the current network path has been updated
    -70    fn on_mtu_update(&mut self, new_mtu: u16);
    -71
    -72    /// Number of ack-eliciting bytes that may be in flight
    -73    fn window(&self) -> u64;
    +22    /// One packet was just sent
    +23    #[allow(unused_variables)]
    +24    fn on_packet_sent(&mut self, now: Instant, bytes: u16, pn: u64) {}
    +25
    +26    /// Packet deliveries were confirmed
    +27    ///
    +28    /// `app_limited` indicates whether the connection was blocked on outgoing
    +29    /// application data prior to receiving these acknowledgements.
    +30    #[allow(unused_variables)]
    +31    fn on_ack(
    +32        &mut self,
    +33        now: Instant,
    +34        sent: Instant,
    +35        bytes: u64,
    +36        pn: u64,
    +37        app_limited: bool,
    +38        rtt: &RttEstimator,
    +39    ) {
    +40    }
    +41
    +42    /// Packets are acked in batches, all with the same `now` argument. This indicates one of those batches has completed.
    +43    #[allow(unused_variables)]
    +44    fn on_end_acks(
    +45        &mut self,
    +46        now: Instant,
    +47        in_flight: u64,
    +48        app_limited: bool,
    +49        largest_packet_num_acked: Option<u64>,
    +50    ) {
    +51    }
    +52
    +53    /// Packets were deemed lost or marked congested
    +54    ///
    +55    /// `in_persistent_congestion` indicates whether all packets sent within the persistent
    +56    /// congestion threshold period ending when the most recent packet in this batch was sent were
    +57    /// lost.
    +58    /// `lost_bytes` indicates how many bytes were lost. This value will be 0 for ECN triggers.
    +59    /// `largest_lost_pn` indicates the packet number of the packet with the highest packet number
    +60    /// in the congestion event.
    +61    fn on_congestion_event(
    +62        &mut self,
    +63        now: Instant,
    +64        sent: Instant,
    +65        is_persistent_congestion: bool,
    +66        is_ecn: bool,
    +67        lost_bytes: u64,
    +68        largest_lost_pn: u64,
    +69    );
    +70
    +71    /// One packet was just lost
    +72    #[allow(unused_variables)]
    +73    fn on_packet_lost(&mut self, lost_bytes: u16, pn: u64, now: Instant) {}
     74
    -75    /// Retrieve implementation-specific metrics used to populate `qlog` traces when they are enabled
    -76    fn metrics(&self) -> ControllerMetrics {
    -77        ControllerMetrics {
    -78            congestion_window: self.window(),
    -79            ssthresh: None,
    -80            pacing_rate: None,
    -81        }
    -82    }
    +75    /// Packets were incorrectly deemed lost
    +76    ///
    +77    /// This function is called when all packets that were deemed lost (for instance because
    +78    /// of packet reordering) are acknowledged after the congestion event was raised.
    +79    fn on_spurious_congestion_event(&mut self) {}
    +80
    +81    /// The known MTU for the current network path has been updated
    +82    fn on_mtu_update(&mut self, new_mtu: u16);
     83
    -84    /// Duplicate the controller's state
    -85    fn clone_box(&self) -> Box<dyn Controller>;
    -86
    -87    /// Initial congestion window
    -88    fn initial_window(&self) -> u64;
    -89
    -90    /// Returns Self for use in down-casting to extract implementation details
    -91    fn into_any(self: Box<Self>) -> Box<dyn Any>;
    -92}
    -93
    -94/// Common congestion controller metrics
    -95#[derive(Default, Debug)]
    -96#[non_exhaustive]
    -97pub struct ControllerMetrics {
    -98    /// Congestion window (bytes)
    -99    pub congestion_window: u64,
    -100    /// Slow start threshold (bytes)
    -101    pub ssthresh: Option<u64>,
    -102    /// Pacing rate (bits/s)
    -103    pub pacing_rate: Option<u64>,
    -104}
    -105
    -106/// Constructs controllers on demand
    -107pub trait ControllerFactory {
    -108    /// Construct a fresh `Controller`
    -109    fn build(self: Arc<Self>, now: Instant, current_mtu: u16) -> Box<dyn Controller>;
    -110}
    -111
    -112const BASE_DATAGRAM_SIZE: u64 = 1200;
    \ No newline at end of file +84 /// The peer's ACK-frequency parameters have changed +85 /// +86 /// `ack_eliciting_threshold` is the number of ack-eliciting packets the peer may receive +87 /// before being required to send an immediate ACK (per the QUIC ACK frequency extension). +88 /// `requested_max_ack_delay` is the maximum delay we asked the peer to wait before sending +89 /// an ACK when the threshold hasn't been reached. +90 /// +91 /// Controllers can use this to refine estimates that depend on peer ACK behavior (e.g. +92 /// BBR's offload budget). +93 #[allow(unused_variables)] +94 fn on_ack_frequency_update( +95 &mut self, +96 ack_eliciting_threshold: u64, +97 requested_max_ack_delay: Duration, +98 ) { +99 } +100 +101 /// Number of ack-eliciting bytes that may be in flight +102 fn window(&self) -> u64; +103 +104 /// Retrieve implementation-specific metrics used to populate `qlog` traces when they are enabled +105 /// This is also used to alter the pacing of the connection with +106 /// `pacing_rate` and `send_quantum` +107 fn metrics(&self) -> ControllerMetrics { +108 ControllerMetrics { +109 congestion_window: self.window(), +110 ssthresh: None, +111 pacing_rate: None, +112 send_quantum: None, +113 } +114 } +115 +116 /// Duplicate the controller's state +117 fn clone_box(&self) -> Box<dyn Controller>; +118 +119 /// Initial congestion window +120 fn initial_window(&self) -> u64; +121 +122 /// Returns Self for use in down-casting to extract implementation details +123 fn into_any(self: Box<Self>) -> Box<dyn Any>; +124} +125 +126/// Common congestion controller metrics used both for logging purposes +127/// but also to alter the pacing of the connection with +128/// `pacing_rate` and `send_quantum` +129#[derive(Default)] +130#[non_exhaustive] +131pub struct ControllerMetrics { +132 /// Congestion window (bytes) +133 pub congestion_window: u64, +134 /// Slow start threshold (bytes) +135 pub ssthresh: Option<u64>, +136 /// Pacing rate (bytes/s) +137 pub pacing_rate: Option<u64>, +138 /// Send Quantum (bytes) used to control the size of packet bursts +139 pub send_quantum: Option<u64>, +140} +141 +142/// Constructs controllers on demand +143pub trait ControllerFactory { +144 /// Construct a fresh `Controller` +145 fn build(self: Arc<Self>, now: Instant, current_mtu: u16) -> Box<dyn Controller>; +146} +147 +148const BASE_DATAGRAM_SIZE: u64 = 1200;
    \ No newline at end of file diff --git a/pr/451/docs/src/noq_proto/congestion/bbr/bw_estimation.rs.html b/pr/451/docs/src/noq_proto/congestion/bbr/bw_estimation.rs.html deleted file mode 100644 index a85f594a8..000000000 --- a/pr/451/docs/src/noq_proto/congestion/bbr/bw_estimation.rs.html +++ /dev/null @@ -1,100 +0,0 @@ -bw_estimation.rs - source

    noq_proto/congestion/bbr/
    bw_estimation.rs

    1use std::fmt::{Debug, Display, Formatter};
    -2
    -3use super::min_max::MinMax;
    -4use crate::{Duration, Instant};
    -5
    -6#[derive(Clone, Debug, Default)]
    -7pub(crate) struct BandwidthEstimation {
    -8    total_acked: u64,
    -9    prev_total_acked: u64,
    -10    acked_time: Option<Instant>,
    -11    prev_acked_time: Option<Instant>,
    -12    total_sent: u64,
    -13    prev_total_sent: u64,
    -14    sent_time: Option<Instant>,
    -15    prev_sent_time: Option<Instant>,
    -16    max_filter: MinMax,
    -17    acked_at_last_window: u64,
    -18}
    -19
    -20impl BandwidthEstimation {
    -21    pub(crate) fn on_sent(&mut self, now: Instant, bytes: u64) {
    -22        self.prev_total_sent = self.total_sent;
    -23        self.total_sent += bytes;
    -24        self.prev_sent_time = self.sent_time;
    -25        self.sent_time = Some(now);
    -26    }
    -27
    -28    pub(crate) fn on_ack(
    -29        &mut self,
    -30        now: Instant,
    -31        _sent: Instant,
    -32        bytes: u64,
    -33        round: u64,
    -34        app_limited: bool,
    -35    ) {
    -36        self.prev_total_acked = self.total_acked;
    -37        self.total_acked += bytes;
    -38        self.prev_acked_time = self.acked_time;
    -39        self.acked_time = Some(now);
    -40
    -41        let Some(prev_sent_time) = self.prev_sent_time else {
    -42            return;
    -43        };
    -44
    -45        let send_rate = match self.sent_time {
    -46            Some(sent_time) if sent_time > prev_sent_time => Self::bw_from_delta(
    -47                self.total_sent - self.prev_total_sent,
    -48                sent_time - prev_sent_time,
    -49            )
    -50            .unwrap_or(0),
    -51            _ => u64::MAX, // will take the min of send and ack, so this is just a skip
    -52        };
    -53
    -54        let ack_rate = match self.prev_acked_time {
    -55            Some(prev_acked_time) => Self::bw_from_delta(
    -56                self.total_acked - self.prev_total_acked,
    -57                now - prev_acked_time,
    -58            )
    -59            .unwrap_or(0),
    -60            None => 0,
    -61        };
    -62
    -63        let bandwidth = send_rate.min(ack_rate);
    -64        if !app_limited && self.max_filter.get() < bandwidth {
    -65            self.max_filter.update_max(round, bandwidth);
    -66        }
    -67    }
    -68
    -69    pub(crate) fn bytes_acked_this_window(&self) -> u64 {
    -70        self.total_acked - self.acked_at_last_window
    -71    }
    -72
    -73    pub(crate) fn end_acks(&mut self, _current_round: u64, _app_limited: bool) {
    -74        self.acked_at_last_window = self.total_acked;
    -75    }
    -76
    -77    pub(crate) fn get_estimate(&self) -> u64 {
    -78        self.max_filter.get()
    -79    }
    -80
    -81    pub(crate) const fn bw_from_delta(bytes: u64, delta: Duration) -> Option<u64> {
    -82        let window_duration_ns = delta.as_nanos();
    -83        if window_duration_ns == 0 {
    -84            return None;
    -85        }
    -86        let b_ns = bytes * 1_000_000_000;
    -87        let bytes_per_second = b_ns / (window_duration_ns as u64);
    -88        Some(bytes_per_second)
    -89    }
    -90}
    -91
    -92impl Display for BandwidthEstimation {
    -93    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
    -94        write!(
    -95            f,
    -96            "{:.3} MB/s",
    -97            self.get_estimate() as f32 / (1024 * 1024) as f32
    -98        )
    -99    }
    -100}
    \ No newline at end of file diff --git a/pr/451/docs/src/noq_proto/congestion/bbr/min_max.rs.html b/pr/451/docs/src/noq_proto/congestion/bbr/min_max.rs.html deleted file mode 100644 index 5bb0f3208..000000000 --- a/pr/451/docs/src/noq_proto/congestion/bbr/min_max.rs.html +++ /dev/null @@ -1,152 +0,0 @@ -min_max.rs - source

    noq_proto/congestion/bbr/
    min_max.rs

    1/*
    -2 * Based on Google code released under BSD license here:
    -3 * https://groups.google.com/forum/#!topic/bbr-dev/3RTgkzi5ZD8
    -4 */
    -5
    -6/*
    -7 * Kathleen Nichols' algorithm for tracking the minimum (or maximum)
    -8 * value of a data stream over some fixed time interval.  (E.g.,
    -9 * the minimum RTT over the past five minutes.) It uses constant
    -10 * space and constant time per update yet almost always delivers
    -11 * the same minimum as an implementation that has to keep all the
    -12 * data in the window.
    -13 *
    -14 * The algorithm keeps track of the best, 2nd best & 3rd best min
    -15 * values, maintaining an invariant that the measurement time of
    -16 * the n'th best >= n-1'th best. It also makes sure that the three
    -17 * values are widely separated in the time window since that bounds
    -18 * the worse case error when that data is monotonically increasing
    -19 * over the window.
    -20 *
    -21 * Upon getting a new min, we can forget everything earlier because
    -22 * it has no value - the new min is <= everything else in the window
    -23 * by definition and it samples the most recent. So we restart fresh on
    -24 * every new min and overwrites 2nd & 3rd choices. The same property
    -25 * holds for 2nd & 3rd best.
    -26 */
    -27
    -28use std::fmt::Debug;
    -29
    -30#[derive(Copy, Clone, Debug)]
    -31pub(super) struct MinMax {
    -32    /// round count, not a timestamp
    -33    window: u64,
    -34    samples: [MinMaxSample; 3],
    -35}
    -36
    -37impl MinMax {
    -38    pub(super) fn get(&self) -> u64 {
    -39        self.samples[0].value
    -40    }
    -41
    -42    fn fill(&mut self, sample: MinMaxSample) {
    -43        self.samples.fill(sample);
    -44    }
    -45
    -46    pub(super) fn reset(&mut self) {
    -47        self.fill(Default::default())
    -48    }
    -49
    -50    /// update_min is also defined in the original source, but removed here since it is not used.
    -51    pub(super) fn update_max(&mut self, current_round: u64, measurement: u64) {
    -52        let sample = MinMaxSample {
    -53            time: current_round,
    -54            value: measurement,
    -55        };
    -56
    -57        if self.samples[0].value == 0  /* uninitialised */
    -58            || /* found new max? */ sample.value >= self.samples[0].value
    -59            || /* nothing left in window? */ sample.time - self.samples[2].time > self.window
    -60        {
    -61            self.fill(sample); /* forget earlier samples */
    -62            return;
    -63        }
    -64
    -65        if sample.value >= self.samples[1].value {
    -66            self.samples[2] = sample;
    -67            self.samples[1] = sample;
    -68        } else if sample.value >= self.samples[2].value {
    -69            self.samples[2] = sample;
    -70        }
    -71
    -72        self.subwin_update(sample);
    -73    }
    -74
    -75    /* As time advances, update the 1st, 2nd, and 3rd choices. */
    -76    fn subwin_update(&mut self, sample: MinMaxSample) {
    -77        let dt = sample.time - self.samples[0].time;
    -78        if dt > self.window {
    -79            /*
    -80             * Passed entire window without a new sample so make 2nd
    -81             * choice the new sample & 3rd choice the new 2nd choice.
    -82             * we may have to iterate this since our 2nd choice
    -83             * may also be outside the window (we checked on entry
    -84             * that the third choice was in the window).
    -85             */
    -86            self.samples[0] = self.samples[1];
    -87            self.samples[1] = self.samples[2];
    -88            self.samples[2] = sample;
    -89            if sample.time - self.samples[0].time > self.window {
    -90                self.samples[0] = self.samples[1];
    -91                self.samples[1] = self.samples[2];
    -92                self.samples[2] = sample;
    -93            }
    -94        } else if self.samples[1].time == self.samples[0].time && dt > self.window / 4 {
    -95            /*
    -96             * We've passed a quarter of the window without a new sample
    -97             * so take a 2nd choice from the 2nd quarter of the window.
    -98             */
    -99            self.samples[2] = sample;
    -100            self.samples[1] = sample;
    -101        } else if self.samples[2].time == self.samples[1].time && dt > self.window / 2 {
    -102            /*
    -103             * We've passed half the window without finding a new sample
    -104             * so take a 3rd choice from the last half of the window
    -105             */
    -106            self.samples[2] = sample;
    -107        }
    -108    }
    -109}
    -110
    -111impl Default for MinMax {
    -112    fn default() -> Self {
    -113        Self {
    -114            window: 10,
    -115            samples: [Default::default(); 3],
    -116        }
    -117    }
    -118}
    -119
    -120#[derive(Debug, Copy, Clone, Default)]
    -121struct MinMaxSample {
    -122    /// round number, not a timestamp
    -123    time: u64,
    -124    value: u64,
    -125}
    -126
    -127#[cfg(test)]
    -128mod test {
    -129    use super::*;
    -130
    -131    #[test]
    -132    fn test() {
    -133        let round = 25;
    -134        let mut min_max = MinMax::default();
    -135        min_max.update_max(round + 1, 100);
    -136        assert_eq!(100, min_max.get());
    -137        min_max.update_max(round + 3, 120);
    -138        assert_eq!(120, min_max.get());
    -139        min_max.update_max(round + 5, 160);
    -140        assert_eq!(160, min_max.get());
    -141        min_max.update_max(round + 7, 100);
    -142        assert_eq!(160, min_max.get());
    -143        min_max.update_max(round + 10, 100);
    -144        assert_eq!(160, min_max.get());
    -145        min_max.update_max(round + 14, 100);
    -146        assert_eq!(160, min_max.get());
    -147        min_max.update_max(round + 16, 100);
    -148        assert_eq!(100, min_max.get());
    -149        min_max.update_max(round + 18, 130);
    -150        assert_eq!(130, min_max.get());
    -151    }
    -152}
    \ No newline at end of file diff --git a/pr/451/docs/src/noq_proto/congestion/bbr/mod.rs.html b/pr/451/docs/src/noq_proto/congestion/bbr/mod.rs.html deleted file mode 100644 index df809fca2..000000000 --- a/pr/451/docs/src/noq_proto/congestion/bbr/mod.rs.html +++ /dev/null @@ -1,647 +0,0 @@ -mod.rs - source

    noq_proto/congestion/bbr/
    mod.rs

    1use std::any::Any;
    -2use std::fmt::Debug;
    -3use std::sync::Arc;
    -4
    -5use rand::RngExt;
    -6
    -7use crate::congestion::ControllerMetrics;
    -8use crate::congestion::bbr::bw_estimation::BandwidthEstimation;
    -9use crate::congestion::bbr::min_max::MinMax;
    -10use crate::connection::RttEstimator;
    -11use crate::{Duration, Instant};
    -12
    -13use super::{BASE_DATAGRAM_SIZE, Controller, ControllerFactory};
    -14
    -15mod bw_estimation;
    -16mod min_max;
    -17
    -18/// Experimental! Use at your own risk.
    -19///
    -20/// Aims for reduced buffer bloat and improved performance over high bandwidth-delay product networks.
    -21/// Based on google's quiche implementation <https://source.chromium.org/chromium/chromium/src/+/master:net/third_party/quiche/src/quic/core/congestion_control/bbr_sender.cc>
    -22/// of BBR <https://datatracker.ietf.org/doc/html/draft-cardwell-iccrg-bbr-congestion-control>.
    -23/// More discussion and links at <https://groups.google.com/g/bbr-dev>.
    -24#[derive(Debug, Clone)]
    -25pub struct Bbr {
    -26    config: Arc<BbrConfig>,
    -27    current_mtu: u64,
    -28    max_bandwidth: BandwidthEstimation,
    -29    acked_bytes: u64,
    -30    mode: Mode,
    -31    loss_state: LossState,
    -32    recovery_state: RecoveryState,
    -33    recovery_window: u64,
    -34    is_at_full_bandwidth: bool,
    -35    pacing_gain: f32,
    -36    high_gain: f32,
    -37    drain_gain: f32,
    -38    cwnd_gain: f32,
    -39    high_cwnd_gain: f32,
    -40    last_cycle_start: Option<Instant>,
    -41    current_cycle_offset: u8,
    -42    init_cwnd: u64,
    -43    min_cwnd: u64,
    -44    prev_in_flight_count: u64,
    -45    exit_probe_rtt_at: Option<Instant>,
    -46    probe_rtt_last_started_at: Option<Instant>,
    -47    min_rtt: Duration,
    -48    exiting_quiescence: bool,
    -49    pacing_rate: u64,
    -50    max_acked_packet_number: u64,
    -51    max_sent_packet_number: u64,
    -52    end_recovery_at_packet_number: u64,
    -53    cwnd: u64,
    -54    current_round_trip_end_packet_number: u64,
    -55    round_count: u64,
    -56    bw_at_last_round: u64,
    -57    round_wo_bw_gain: u64,
    -58    ack_aggregation: AckAggregationState,
    -59}
    -60
    -61impl Bbr {
    -62    /// Construct a state using the given `config` and current time `now`
    -63    pub fn new(config: Arc<BbrConfig>, current_mtu: u16) -> Self {
    -64        let initial_window = config.initial_window;
    -65        Self {
    -66            config,
    -67            current_mtu: current_mtu as u64,
    -68            max_bandwidth: BandwidthEstimation::default(),
    -69            acked_bytes: 0,
    -70            mode: Mode::Startup,
    -71            loss_state: Default::default(),
    -72            recovery_state: RecoveryState::NotInRecovery,
    -73            recovery_window: 0,
    -74            is_at_full_bandwidth: false,
    -75            pacing_gain: K_DEFAULT_HIGH_GAIN,
    -76            high_gain: K_DEFAULT_HIGH_GAIN,
    -77            drain_gain: 1.0 / K_DEFAULT_HIGH_GAIN,
    -78            cwnd_gain: K_DEFAULT_HIGH_GAIN,
    -79            high_cwnd_gain: K_DEFAULT_HIGH_GAIN,
    -80            last_cycle_start: None,
    -81            current_cycle_offset: 0,
    -82            init_cwnd: initial_window,
    -83            min_cwnd: calculate_min_window(current_mtu as u64),
    -84            prev_in_flight_count: 0,
    -85            exit_probe_rtt_at: None,
    -86            probe_rtt_last_started_at: None,
    -87            min_rtt: Default::default(),
    -88            exiting_quiescence: false,
    -89            pacing_rate: 0,
    -90            max_acked_packet_number: 0,
    -91            max_sent_packet_number: 0,
    -92            end_recovery_at_packet_number: 0,
    -93            cwnd: initial_window,
    -94            current_round_trip_end_packet_number: 0,
    -95            round_count: 0,
    -96            bw_at_last_round: 0,
    -97            round_wo_bw_gain: 0,
    -98            ack_aggregation: AckAggregationState::default(),
    -99        }
    -100    }
    -101
    -102    fn enter_startup_mode(&mut self) {
    -103        self.mode = Mode::Startup;
    -104        self.pacing_gain = self.high_gain;
    -105        self.cwnd_gain = self.high_cwnd_gain;
    -106    }
    -107
    -108    fn enter_probe_bandwidth_mode(&mut self, now: Instant) {
    -109        self.mode = Mode::ProbeBw;
    -110        self.cwnd_gain = K_DERIVED_HIGH_CWNDGAIN;
    -111        self.last_cycle_start = Some(now);
    -112        // Pick a random offset for the gain cycle out of {0, 2..7} range. 1 is
    -113        // excluded because in that case increased gain and decreased gain would not
    -114        // follow each other.
    -115        let mut rand_index = rand::rng().random_range(0..K_PACING_GAIN.len() as u8 - 1);
    -116        if rand_index >= 1 {
    -117            rand_index += 1;
    -118        }
    -119        self.current_cycle_offset = rand_index;
    -120        self.pacing_gain = K_PACING_GAIN[rand_index as usize];
    -121    }
    -122
    -123    fn update_recovery_state(&mut self, is_round_start: bool) {
    -124        // Exit recovery when there are no losses for a round.
    -125        if self.loss_state.has_losses() {
    -126            self.end_recovery_at_packet_number = self.max_sent_packet_number;
    -127        }
    -128        match self.recovery_state {
    -129            // Enter conservation on the first loss.
    -130            RecoveryState::NotInRecovery if self.loss_state.has_losses() => {
    -131                self.recovery_state = RecoveryState::Conservation;
    -132                // This will cause the |recovery_window| to be set to the
    -133                // correct value in CalculateRecoveryWindow().
    -134                self.recovery_window = 0;
    -135                // Since the conservation phase is meant to be lasting for a whole
    -136                // round, extend the current round as if it were started right now.
    -137                self.current_round_trip_end_packet_number = self.max_sent_packet_number;
    -138            }
    -139            RecoveryState::Growth | RecoveryState::Conservation => {
    -140                if self.recovery_state == RecoveryState::Conservation && is_round_start {
    -141                    self.recovery_state = RecoveryState::Growth;
    -142                }
    -143                // Exit recovery if appropriate.
    -144                if !self.loss_state.has_losses()
    -145                    && self.max_acked_packet_number > self.end_recovery_at_packet_number
    -146                {
    -147                    self.recovery_state = RecoveryState::NotInRecovery;
    -148                }
    -149            }
    -150            _ => {}
    -151        }
    -152    }
    -153
    -154    fn update_gain_cycle_phase(&mut self, now: Instant, in_flight: u64) {
    -155        // In most cases, the cycle is advanced after an RTT passes.
    -156        let mut should_advance_gain_cycling = self
    -157            .last_cycle_start
    -158            .map(|last_cycle_start| now.duration_since(last_cycle_start) > self.min_rtt)
    -159            .unwrap_or(false);
    -160        // If the pacing gain is above 1.0, the connection is trying to probe the
    -161        // bandwidth by increasing the number of bytes in flight to at least
    -162        // pacing_gain * BDP.  Make sure that it actually reaches the target, as
    -163        // long as there are no losses suggesting that the buffers are not able to
    -164        // hold that much.
    -165        if self.pacing_gain > 1.0
    -166            && !self.loss_state.has_losses()
    -167            && self.prev_in_flight_count < self.get_target_cwnd(self.pacing_gain)
    -168        {
    -169            should_advance_gain_cycling = false;
    -170        }
    -171
    -172        // If pacing gain is below 1.0, the connection is trying to drain the extra
    -173        // queue which could have been incurred by probing prior to it.  If the
    -174        // number of bytes in flight falls down to the estimated BDP value earlier,
    -175        // conclude that the queue has been successfully drained and exit this cycle
    -176        // early.
    -177        if self.pacing_gain < 1.0 && in_flight <= self.get_target_cwnd(1.0) {
    -178            should_advance_gain_cycling = true;
    -179        }
    -180
    -181        if should_advance_gain_cycling {
    -182            self.current_cycle_offset = (self.current_cycle_offset + 1) % K_PACING_GAIN.len() as u8;
    -183            self.last_cycle_start = Some(now);
    -184            // Stay in low gain mode until the target BDP is hit.  Low gain mode
    -185            // will be exited immediately when the target BDP is achieved.
    -186            if DRAIN_TO_TARGET
    -187                && self.pacing_gain < 1.0
    -188                && (K_PACING_GAIN[self.current_cycle_offset as usize] - 1.0).abs() < f32::EPSILON
    -189                && in_flight > self.get_target_cwnd(1.0)
    -190            {
    -191                return;
    -192            }
    -193            self.pacing_gain = K_PACING_GAIN[self.current_cycle_offset as usize];
    -194        }
    -195    }
    -196
    -197    fn maybe_exit_startup_or_drain(&mut self, now: Instant, in_flight: u64) {
    -198        if self.mode == Mode::Startup && self.is_at_full_bandwidth {
    -199            self.mode = Mode::Drain;
    -200            self.pacing_gain = self.drain_gain;
    -201            self.cwnd_gain = self.high_cwnd_gain;
    -202        }
    -203        if self.mode == Mode::Drain && in_flight <= self.get_target_cwnd(1.0) {
    -204            self.enter_probe_bandwidth_mode(now);
    -205        }
    -206    }
    -207
    -208    fn is_min_rtt_expired(&self, now: Instant, app_limited: bool) -> bool {
    -209        !app_limited
    -210            && self
    -211                .probe_rtt_last_started_at
    -212                .map(|last| now.saturating_duration_since(last) > Duration::from_secs(10))
    -213                .unwrap_or(true)
    -214    }
    -215
    -216    fn maybe_enter_or_exit_probe_rtt(
    -217        &mut self,
    -218        now: Instant,
    -219        is_round_start: bool,
    -220        bytes_in_flight: u64,
    -221        app_limited: bool,
    -222    ) {
    -223        let min_rtt_expired = self.is_min_rtt_expired(now, app_limited);
    -224        if min_rtt_expired && !self.exiting_quiescence && self.mode != Mode::ProbeRtt {
    -225            self.mode = Mode::ProbeRtt;
    -226            self.pacing_gain = 1.0;
    -227            // Do not decide on the time to exit ProbeRtt until the
    -228            // |bytes_in_flight| is at the target small value.
    -229            self.exit_probe_rtt_at = None;
    -230            self.probe_rtt_last_started_at = Some(now);
    -231        }
    -232
    -233        if self.mode == Mode::ProbeRtt {
    -234            match self.exit_probe_rtt_at {
    -235                None => {
    -236                    // If the window has reached the appropriate size, schedule exiting
    -237                    // ProbeRtt.  The CWND during ProbeRtt is
    -238                    // kMinimumCongestionWindow, but we allow an extra packet since QUIC
    -239                    // checks CWND before sending a packet.
    -240                    if bytes_in_flight < self.get_probe_rtt_cwnd() + self.current_mtu {
    -241                        const K_PROBE_RTT_TIME: Duration = Duration::from_millis(200);
    -242                        self.exit_probe_rtt_at = Some(now + K_PROBE_RTT_TIME);
    -243                    }
    -244                }
    -245                Some(exit_time) if is_round_start && now >= exit_time => {
    -246                    if !self.is_at_full_bandwidth {
    -247                        self.enter_startup_mode();
    -248                    } else {
    -249                        self.enter_probe_bandwidth_mode(now);
    -250                    }
    -251                }
    -252                Some(_) => {}
    -253            }
    -254        }
    -255
    -256        self.exiting_quiescence = false;
    -257    }
    -258
    -259    fn get_target_cwnd(&self, gain: f32) -> u64 {
    -260        let bw = self.max_bandwidth.get_estimate();
    -261        let bdp = self.min_rtt.as_micros() as u64 * bw;
    -262        let bdpf = bdp as f64;
    -263        let cwnd = ((gain as f64 * bdpf) / 1_000_000f64) as u64;
    -264        // BDP estimate will be zero if no bandwidth samples are available yet.
    -265        if cwnd == 0 {
    -266            return self.init_cwnd;
    -267        }
    -268        cwnd.max(self.min_cwnd)
    -269    }
    -270
    -271    fn get_probe_rtt_cwnd(&self) -> u64 {
    -272        const K_MODERATE_PROBE_RTT_MULTIPLIER: f32 = 0.75;
    -273        if PROBE_RTT_BASED_ON_BDP {
    -274            return self.get_target_cwnd(K_MODERATE_PROBE_RTT_MULTIPLIER);
    -275        }
    -276        self.min_cwnd
    -277    }
    -278
    -279    fn calculate_pacing_rate(&mut self) {
    -280        let bw = self.max_bandwidth.get_estimate();
    -281        if bw == 0 {
    -282            return;
    -283        }
    -284        let target_rate = (bw as f64 * self.pacing_gain as f64) as u64;
    -285        if self.is_at_full_bandwidth {
    -286            self.pacing_rate = target_rate;
    -287            return;
    -288        }
    -289
    -290        // Pace at the rate of initial_window / RTT as soon as RTT measurements are
    -291        // available.
    -292        if self.pacing_rate == 0 && self.min_rtt.as_nanos() != 0 {
    -293            self.pacing_rate =
    -294                BandwidthEstimation::bw_from_delta(self.init_cwnd, self.min_rtt).unwrap();
    -295            return;
    -296        }
    -297
    -298        // Do not decrease the pacing rate during startup.
    -299        if self.pacing_rate < target_rate {
    -300            self.pacing_rate = target_rate;
    -301        }
    -302    }
    -303
    -304    fn calculate_cwnd(&mut self, bytes_acked: u64, excess_acked: u64) {
    -305        if self.mode == Mode::ProbeRtt {
    -306            return;
    -307        }
    -308        let mut target_window = self.get_target_cwnd(self.cwnd_gain);
    -309        if self.is_at_full_bandwidth {
    -310            // Add the max recently measured ack aggregation to CWND.
    -311            target_window += self.ack_aggregation.max_ack_height.get();
    -312        } else {
    -313            // Add the most recent excess acked.  Because CWND never decreases in
    -314            // STARTUP, this will automatically create a very localized max filter.
    -315            target_window += excess_acked;
    -316        }
    -317        // Instead of immediately setting the target CWND as the new one, BBR grows
    -318        // the CWND towards |target_window| by only increasing it |bytes_acked| at a
    -319        // time.
    -320        if self.is_at_full_bandwidth {
    -321            self.cwnd = target_window.min(self.cwnd + bytes_acked);
    -322        } else if (self.cwnd_gain < target_window as f32) || (self.acked_bytes < self.init_cwnd) {
    -323            // If the connection is not yet out of startup phase, do not decrease
    -324            // the window.
    -325            self.cwnd += bytes_acked;
    -326        }
    -327
    -328        // Enforce the limits on the congestion window.
    -329        if self.cwnd < self.min_cwnd {
    -330            self.cwnd = self.min_cwnd;
    -331        }
    -332    }
    -333
    -334    fn calculate_recovery_window(&mut self, bytes_acked: u64, bytes_lost: u64, in_flight: u64) {
    -335        if !self.recovery_state.in_recovery() {
    -336            return;
    -337        }
    -338        // Set up the initial recovery window.
    -339        if self.recovery_window == 0 {
    -340            self.recovery_window = self.min_cwnd.max(in_flight + bytes_acked);
    -341            return;
    -342        }
    -343
    -344        // Remove losses from the recovery window, while accounting for a potential
    -345        // integer underflow.
    -346        if self.recovery_window >= bytes_lost {
    -347            self.recovery_window -= bytes_lost;
    -348        } else {
    -349            // k_max_segment_size = current_mtu
    -350            self.recovery_window = self.current_mtu;
    -351        }
    -352        // In CONSERVATION mode, just subtracting losses is sufficient.  In GROWTH,
    -353        // release additional |bytes_acked| to achieve a slow-start-like behavior.
    -354        if self.recovery_state == RecoveryState::Growth {
    -355            self.recovery_window += bytes_acked;
    -356        }
    -357
    -358        // Sanity checks.  Ensure that we always allow to send at least an MSS or
    -359        // |bytes_acked| in response, whichever is larger.
    -360        self.recovery_window = self
    -361            .recovery_window
    -362            .max(in_flight + bytes_acked)
    -363            .max(self.min_cwnd);
    -364    }
    -365
    -366    /// <https://datatracker.ietf.org/doc/html/draft-cardwell-iccrg-bbr-congestion-control#section-4.3.2.2>
    -367    fn check_if_full_bw_reached(&mut self, app_limited: bool) {
    -368        if app_limited {
    -369            return;
    -370        }
    -371        let target = (self.bw_at_last_round as f64 * K_STARTUP_GROWTH_TARGET as f64) as u64;
    -372        let bw = self.max_bandwidth.get_estimate();
    -373        if bw >= target {
    -374            self.bw_at_last_round = bw;
    -375            self.round_wo_bw_gain = 0;
    -376            self.ack_aggregation.max_ack_height.reset();
    -377            return;
    -378        }
    -379
    -380        self.round_wo_bw_gain += 1;
    -381        if self.round_wo_bw_gain >= K_ROUND_TRIPS_WITHOUT_GROWTH_BEFORE_EXITING_STARTUP as u64
    -382            || (self.recovery_state.in_recovery())
    -383        {
    -384            self.is_at_full_bandwidth = true;
    -385        }
    -386    }
    -387}
    -388
    -389impl Controller for Bbr {
    -390    fn on_sent(&mut self, now: Instant, bytes: u64, last_packet_number: u64) {
    -391        self.max_sent_packet_number = last_packet_number;
    -392        self.max_bandwidth.on_sent(now, bytes);
    -393    }
    -394
    -395    fn on_ack(
    -396        &mut self,
    -397        now: Instant,
    -398        sent: Instant,
    -399        bytes: u64,
    -400        app_limited: bool,
    -401        rtt: &RttEstimator,
    -402    ) {
    -403        self.max_bandwidth
    -404            .on_ack(now, sent, bytes, self.round_count, app_limited);
    -405        self.acked_bytes += bytes;
    -406        if self.is_min_rtt_expired(now, app_limited) || self.min_rtt > rtt.min() {
    -407            self.min_rtt = rtt.min();
    -408        }
    -409    }
    -410
    -411    fn on_end_acks(
    -412        &mut self,
    -413        now: Instant,
    -414        in_flight: u64,
    -415        app_limited: bool,
    -416        largest_packet_num_acked: Option<u64>,
    -417    ) {
    -418        let bytes_acked = self.max_bandwidth.bytes_acked_this_window();
    -419        let excess_acked = self.ack_aggregation.update_ack_aggregation_bytes(
    -420            bytes_acked,
    -421            now,
    -422            self.round_count,
    -423            self.max_bandwidth.get_estimate(),
    -424        );
    -425        self.max_bandwidth.end_acks(self.round_count, app_limited);
    -426        if let Some(largest_acked_packet) = largest_packet_num_acked {
    -427            self.max_acked_packet_number = largest_acked_packet;
    -428        }
    -429
    -430        let mut is_round_start = false;
    -431        if bytes_acked > 0 {
    -432            is_round_start =
    -433                self.max_acked_packet_number > self.current_round_trip_end_packet_number;
    -434            if is_round_start {
    -435                self.current_round_trip_end_packet_number = self.max_sent_packet_number;
    -436                self.round_count += 1;
    -437            }
    -438        }
    -439
    -440        self.update_recovery_state(is_round_start);
    -441
    -442        if self.mode == Mode::ProbeBw {
    -443            self.update_gain_cycle_phase(now, in_flight);
    -444        }
    -445
    -446        if is_round_start && !self.is_at_full_bandwidth {
    -447            self.check_if_full_bw_reached(app_limited);
    -448        }
    -449
    -450        self.maybe_exit_startup_or_drain(now, in_flight);
    -451
    -452        self.maybe_enter_or_exit_probe_rtt(now, is_round_start, in_flight, app_limited);
    -453
    -454        // After the model is updated, recalculate the pacing rate and congestion window.
    -455        self.calculate_pacing_rate();
    -456        self.calculate_cwnd(bytes_acked, excess_acked);
    -457        self.calculate_recovery_window(bytes_acked, self.loss_state.lost_bytes, in_flight);
    -458
    -459        self.prev_in_flight_count = in_flight;
    -460        self.loss_state.reset();
    -461    }
    -462
    -463    fn on_congestion_event(
    -464        &mut self,
    -465        _now: Instant,
    -466        _sent: Instant,
    -467        _is_persistent_congestion: bool,
    -468        _is_ecn: bool,
    -469        lost_bytes: u64,
    -470    ) {
    -471        self.loss_state.lost_bytes += lost_bytes;
    -472    }
    -473
    -474    fn on_mtu_update(&mut self, new_mtu: u16) {
    -475        self.current_mtu = new_mtu as u64;
    -476        self.min_cwnd = calculate_min_window(self.current_mtu);
    -477        self.init_cwnd = self.config.initial_window.max(self.min_cwnd);
    -478        self.cwnd = self.cwnd.max(self.min_cwnd);
    -479    }
    -480
    -481    fn window(&self) -> u64 {
    -482        if self.mode == Mode::ProbeRtt {
    -483            return self.get_probe_rtt_cwnd();
    -484        } else if self.recovery_state.in_recovery() && self.mode != Mode::Startup {
    -485            return self.cwnd.min(self.recovery_window);
    -486        }
    -487        self.cwnd
    -488    }
    -489
    -490    fn metrics(&self) -> ControllerMetrics {
    -491        ControllerMetrics {
    -492            congestion_window: self.window(),
    -493            ssthresh: None,
    -494            pacing_rate: Some(self.pacing_rate * 8),
    -495        }
    -496    }
    -497
    -498    fn clone_box(&self) -> Box<dyn Controller> {
    -499        Box::new(self.clone())
    -500    }
    -501
    -502    fn initial_window(&self) -> u64 {
    -503        self.config.initial_window
    -504    }
    -505
    -506    fn into_any(self: Box<Self>) -> Box<dyn Any> {
    -507        self
    -508    }
    -509}
    -510
    -511/// Configuration for the [`Bbr`] congestion controller
    -512#[derive(Debug, Clone)]
    -513pub struct BbrConfig {
    -514    initial_window: u64,
    -515}
    -516
    -517impl BbrConfig {
    -518    /// Default limit on the amount of outstanding data in bytes.
    -519    ///
    -520    /// Recommended value: `min(10 * max_datagram_size, max(2 * max_datagram_size, 14720))`
    -521    pub fn initial_window(&mut self, value: u64) -> &mut Self {
    -522        self.initial_window = value;
    -523        self
    -524    }
    -525}
    -526
    -527impl Default for BbrConfig {
    -528    fn default() -> Self {
    -529        Self {
    -530            initial_window: K_MAX_INITIAL_CONGESTION_WINDOW * BASE_DATAGRAM_SIZE,
    -531        }
    -532    }
    -533}
    -534
    -535impl ControllerFactory for BbrConfig {
    -536    fn build(self: Arc<Self>, _now: Instant, current_mtu: u16) -> Box<dyn Controller> {
    -537        Box::new(Bbr::new(self, current_mtu))
    -538    }
    -539}
    -540
    -541#[derive(Debug, Default, Copy, Clone)]
    -542struct AckAggregationState {
    -543    max_ack_height: MinMax,
    -544    aggregation_epoch_start_time: Option<Instant>,
    -545    aggregation_epoch_bytes: u64,
    -546}
    -547
    -548impl AckAggregationState {
    -549    fn update_ack_aggregation_bytes(
    -550        &mut self,
    -551        newly_acked_bytes: u64,
    -552        now: Instant,
    -553        round: u64,
    -554        max_bandwidth: u64,
    -555    ) -> u64 {
    -556        // Compute how many bytes are expected to be delivered, assuming max
    -557        // bandwidth is correct.
    -558        let expected_bytes_acked = max_bandwidth
    -559            * now
    -560                .saturating_duration_since(self.aggregation_epoch_start_time.unwrap_or(now))
    -561                .as_micros() as u64
    -562            / 1_000_000;
    -563
    -564        // Reset the current aggregation epoch as soon as the ack arrival rate is
    -565        // less than or equal to the max bandwidth.
    -566        if self.aggregation_epoch_bytes <= expected_bytes_acked {
    -567            // Reset to start measuring a new aggregation epoch.
    -568            self.aggregation_epoch_bytes = newly_acked_bytes;
    -569            self.aggregation_epoch_start_time = Some(now);
    -570            return 0;
    -571        }
    -572
    -573        // Compute how many extra bytes were delivered vs max bandwidth.
    -574        // Include the bytes most recently acknowledged to account for stretch acks.
    -575        self.aggregation_epoch_bytes += newly_acked_bytes;
    -576        let diff = self.aggregation_epoch_bytes - expected_bytes_acked;
    -577        self.max_ack_height.update_max(round, diff);
    -578        diff
    -579    }
    -580}
    -581
    -582#[derive(Debug, Clone, Copy, Eq, PartialEq)]
    -583enum Mode {
    -584    // Startup phase of the connection.
    -585    Startup,
    -586    // After achieving the highest possible bandwidth during the startup, lower
    -587    // the pacing rate in order to drain the queue.
    -588    Drain,
    -589    // Cruising mode.
    -590    ProbeBw,
    -591    // Temporarily slow down sending in order to empty the buffer and measure
    -592    // the real minimum RTT.
    -593    ProbeRtt,
    -594}
    -595
    -596// Indicates how the congestion control limits the amount of bytes in flight.
    -597#[derive(Debug, Clone, Copy, Eq, PartialEq)]
    -598enum RecoveryState {
    -599    // Do not limit.
    -600    NotInRecovery,
    -601    // Allow an extra outstanding byte for each byte acknowledged.
    -602    Conservation,
    -603    // Allow two extra outstanding bytes for each byte acknowledged (slow
    -604    // start).
    -605    Growth,
    -606}
    -607
    -608impl RecoveryState {
    -609    pub(super) fn in_recovery(&self) -> bool {
    -610        !matches!(self, Self::NotInRecovery)
    -611    }
    -612}
    -613
    -614#[derive(Debug, Clone, Default)]
    -615struct LossState {
    -616    lost_bytes: u64,
    -617}
    -618
    -619impl LossState {
    -620    pub(super) fn reset(&mut self) {
    -621        self.lost_bytes = 0;
    -622    }
    -623
    -624    pub(super) fn has_losses(&self) -> bool {
    -625        self.lost_bytes != 0
    -626    }
    -627}
    -628
    -629fn calculate_min_window(current_mtu: u64) -> u64 {
    -630    4 * current_mtu
    -631}
    -632
    -633// The gain used for the STARTUP, equal to 2/ln(2).
    -634const K_DEFAULT_HIGH_GAIN: f32 = 2.885;
    -635// The newly derived CWND gain for STARTUP, 2.
    -636const K_DERIVED_HIGH_CWNDGAIN: f32 = 2.0;
    -637// The cycle of gains used during the ProbeBw stage.
    -638const K_PACING_GAIN: [f32; 8] = [1.25, 0.75, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0];
    -639
    -640const K_STARTUP_GROWTH_TARGET: f32 = 1.25;
    -641const K_ROUND_TRIPS_WITHOUT_GROWTH_BEFORE_EXITING_STARTUP: u8 = 3;
    -642
    -643// Do not allow initial congestion window to be greater than 200 packets.
    -644const K_MAX_INITIAL_CONGESTION_WINDOW: u64 = 200;
    -645
    -646const PROBE_RTT_BASED_ON_BDP: bool = true;
    -647const DRAIN_TO_TARGET: bool = true;
    \ No newline at end of file diff --git a/pr/451/docs/src/noq_proto/congestion/bbr3/max_filter.rs.html b/pr/451/docs/src/noq_proto/congestion/bbr3/max_filter.rs.html new file mode 100644 index 000000000..572726b6a --- /dev/null +++ b/pr/451/docs/src/noq_proto/congestion/bbr3/max_filter.rs.html @@ -0,0 +1,151 @@ +max_filter.rs - source

    noq_proto/congestion/bbr3/
    max_filter.rs

    1use std::fmt::Debug;
    +2
    +3const MAX_FILTER_LEN: usize = 3;
    +4
    +5/// Based on Linux kernel code released here:
    +6/// <https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f672258391b42a5c7cc2732c9c063e56a85c8dbe>
    +7///
    +8/// Kathleen Nichols' algorithm for tracking the maximum
    +9/// value of a data stream over some fixed time interval.  (E.g.,
    +10/// the maximum Bandwidth achieved over the past 3 rounds.) It uses constant
    +11/// space and constant time per update yet almost always delivers
    +12/// the same maximum as an implementation that has to keep all the
    +13/// data in the window.
    +14///
    +15/// The algorithm keeps track of the best, 2nd best & 3rd highest max
    +16/// values, maintaining an invariant that the measurement time of
    +17/// the n'th best >= n-1'th best. It also makes sure that the three
    +18/// values are widely separated in the time window since that bounds
    +19/// the worst case error when that data is monotonically increasing
    +20/// over the window.
    +21///
    +22/// Upon getting a new max, we can forget everything earlier because
    +23/// it has no value - the new max is >= everything else in the window
    +24/// by definition, and it samples the most recent one. So we restart fresh on
    +25/// every new max and overwrites 2nd & 3rd choices. The same property
    +26/// holds for 2nd & 3rd best.
    +27#[derive(Copy, Clone, Debug)]
    +28pub(super) struct MaxFilter {
    +29    window: u64,
    +30    // sample on index 0 has the maximum value followed in descending order
    +31    // by samples on index 1 and then 2
    +32    samples: [MaxSample; MAX_FILTER_LEN],
    +33}
    +34
    +35impl MaxFilter {
    +36    pub(super) fn new(window: u64) -> Self {
    +37        Self {
    +38            window,
    +39            samples: [Default::default(); MAX_FILTER_LEN],
    +40        }
    +41    }
    +42    pub(super) fn get_max(&self) -> u64 {
    +43        self.samples[0].value.unwrap_or(0)
    +44    }
    +45
    +46    /// `current_round` represents a sequence number counting upwards from 0 monotonically
    +47    /// `measurement` is what is tracked as the max values over time
    +48    pub(super) fn update_max(&mut self, current_round: u64, measurement: u64) {
    +49        let sample = MaxSample {
    +50            round: current_round,
    +51            value: Some(measurement),
    +52        };
    +53
    +54        if self.samples[0].value.is_none()  // uninitialised
    +55            ||  sample.value >= self.samples[0].value // found new max?
    +56            ||  sample.round.saturating_sub(self.samples[2].round) > self.window
    +57        // nothing left in window?
    +58        {
    +59            self.samples.fill(sample); // forget earlier samples
    +60            return;
    +61        }
    +62
    +63        if sample.value >= self.samples[1].value {
    +64            self.samples[1] = sample;
    +65            self.samples[2] = sample;
    +66        } else if sample.value >= self.samples[2].value {
    +67            self.samples[2] = sample;
    +68        }
    +69
    +70        self.subwin_update(sample);
    +71    }
    +72
    +73    /// As time advances, update the 1st, 2nd, and 3rd choices.
    +74    fn subwin_update(&mut self, sample: MaxSample) {
    +75        let dt = sample.round.saturating_sub(self.samples[0].round);
    +76        if dt > self.window {
    +77            /*
    +78             * Passed entire window without a new sample so make 2nd
    +79             * choice the new sample & 3rd choice the new 2nd choice.
    +80             * we may have to iterate this since our 2nd choice
    +81             * may also be outside the window (we checked on entry
    +82             * that the third choice was in the window).
    +83             */
    +84            self.samples[0] = self.samples[1];
    +85            self.samples[1] = self.samples[2];
    +86            self.samples[2] = sample;
    +87            if sample.round.saturating_sub(self.samples[0].round) > self.window {
    +88                self.samples[0] = self.samples[1];
    +89                self.samples[1] = self.samples[2];
    +90                self.samples[2] = sample;
    +91            }
    +92        } else if self.samples[1].round == self.samples[0].round && dt > self.window / 4 {
    +93            /*
    +94             * We've passed a quarter of the window without a new sample
    +95             * so take a 2nd choice from the 2nd quarter of the window.
    +96             */
    +97            self.samples[2] = sample;
    +98            self.samples[1] = sample;
    +99        } else if self.samples[2].round == self.samples[1].round && dt > self.window / 2 {
    +100            /*
    +101             * We've passed half the window without finding a new sample
    +102             * so take a 3rd choice from the last half of the window
    +103             */
    +104            self.samples[2] = sample;
    +105        }
    +106    }
    +107}
    +108
    +109impl Default for MaxFilter {
    +110    fn default() -> Self {
    +111        Self {
    +112            window: 10,
    +113            samples: [Default::default(); MAX_FILTER_LEN],
    +114        }
    +115    }
    +116}
    +117
    +118#[derive(Debug, Copy, Clone, Default)]
    +119struct MaxSample {
    +120    /// `round` count, not a timestamp as per <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.1>
    +121    /// can also be a count of cycle as per <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.6>
    +122    round: u64,
    +123    value: Option<u64>,
    +124}
    +125
    +126#[cfg(test)]
    +127mod test {
    +128    use super::*;
    +129
    +130    #[test]
    +131    fn test() {
    +132        let round = 25;
    +133        let mut max_filter = MaxFilter::default();
    +134        max_filter.update_max(round + 1, 100);
    +135        assert_eq!(100, max_filter.get_max());
    +136        max_filter.update_max(round + 3, 120);
    +137        assert_eq!(120, max_filter.get_max());
    +138        max_filter.update_max(round + 5, 160);
    +139        assert_eq!(160, max_filter.get_max());
    +140        max_filter.update_max(round + 7, 100);
    +141        assert_eq!(160, max_filter.get_max());
    +142        max_filter.update_max(round + 10, 100);
    +143        assert_eq!(160, max_filter.get_max());
    +144        max_filter.update_max(round + 14, 100);
    +145        assert_eq!(160, max_filter.get_max());
    +146        max_filter.update_max(round + 16, 100);
    +147        assert_eq!(100, max_filter.get_max());
    +148        max_filter.update_max(round + 18, 130);
    +149        assert_eq!(130, max_filter.get_max());
    +150    }
    +151}
    \ No newline at end of file diff --git a/pr/451/docs/src/noq_proto/congestion/bbr3/mod.rs.html b/pr/451/docs/src/noq_proto/congestion/bbr3/mod.rs.html new file mode 100644 index 000000000..0537f6225 --- /dev/null +++ b/pr/451/docs/src/noq_proto/congestion/bbr3/mod.rs.html @@ -0,0 +1,1743 @@ +mod.rs - source

    noq_proto/congestion/bbr3/
    mod.rs

    1mod max_filter;
    +2
    +3use crate::RttEstimator;
    +4use crate::congestion::bbr3::max_filter::MaxFilter;
    +5use crate::congestion::{Controller, ControllerFactory, ControllerMetrics};
    +6use crate::{Duration, Instant};
    +7use rand::{RngExt, SeedableRng};
    +8use rand_pcg::Pcg32;
    +9use std::any::Any;
    +10use std::cmp::{max, min};
    +11use std::collections::VecDeque;
    +12use std::sync::Arc;
    +13
    +14/// equivalent to BBR.MaxBwFilterLen <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-2.10>
    +15const MAX_BW_FILTER_LEN: usize = 2;
    +16
    +17/// equivalent to BBR.ExtraAckedFilterLen <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-2.11>
    +18const EXTRA_ACKED_FILTER_LEN: usize = 10;
    +19
    +20/// safety mechanism to flag packets as stale within our tracking VecDeque. rounds refer to <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.1>.
    +21/// The value of 10 rounds is picked because normally after max(kTimeThreshold * max(smoothed_rtt, latest_rtt), kGranularity) <https://datatracker.ietf.org/doc/html/rfc9002#section-6.1.2>
    +22/// the packet should have been declared lost already, this is just to guarantee that the VecDeque doesn't grow indefinitely.
    +23const ROUND_COUNT_WINDOW: u64 = 10;
    +24
    +25/// the minimum for the maximum datagram size <https://datatracker.ietf.org/doc/html/rfc9000#section-14>
    +26const MIN_MAX_DATAGRAM_SIZE: u16 = 1200;
    +27
    +28/// the maximum for the maximum datagram size <https://datatracker.ietf.org/doc/html/rfc9000#section-18.2>
    +29const MAX_DATAGRAM_SIZE: u64 = 65527;
    +30
    +31/// 1.2Mbps in bytes/sec used to determine send_quantum
    +32/// this is the pacing rate used where we don't authorize a burst bigger than a full packet
    +33/// inspired by a previous version of BBR2 used in cloudflare's quiche
    +34const PACING_RATE_1_2MBPS: f64 = 1200.0 * 1000.0;
    +35
    +36/// 24Mbps in bytes/sec
    +37/// this is the pacing rate used where we don't authorize a burst bigger than two full packets
    +38/// inspired by a previous version of BBR2 used in cloudflare's quiche
    +39const PACING_RATE_24MBPS: f64 = 24000.0 * 1000.0;
    +40
    +41/// 64 Kb in bytes
    +42/// this is the maximum size we want for a quantum in `set_send_quantum`
    +43/// inspired by a previous version of BBR2 used in cloudflare's quiche
    +44const HIGH_PACE_MAX_QUANTUM: u64 = 64 * 1000;
    +45
    +46/// equivalent to BBR.StartupPacingGain: A constant specifying the minimum gain value for calculating the pacing rate that will allow
    +47/// the sending rate to double each round (4 * ln(2) ~= 2.77)
    +48/// BBRStartupPacingGain; used in Startup mode for BBR.pacing_gain. <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.1>
    +49const STARTUP_PACING_GAIN: f64 = 2.773;
    +50
    +51/// default pacing gain is 1, when cruising, probing for RTT or refilling <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.1>
    +52const DEFAULT_PACING_GAIN: f64 = 1.0;
    +53
    +54/// pacing gain when probing bandwidth down <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.1>
    +55const PROBE_BW_DOWN_PACING_GAIN: f64 = 0.9;
    +56
    +57/// pacing gain when probing bandwidth up <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.1>
    +58const PROBE_BW_UP_PACING_GAIN: f64 = 1.25;
    +59
    +60/// equivalent to BBR.PacingMarginPercent: The static discount factor of 1% used to scale BBR.bw to produce C.pacing_rate.
    +61const PACING_MARGIN_PERCENT: f64 = 1.0;
    +62
    +63/// equivalent to BBR.DefaultCwndGain: A constant specifying the minimum gain value that allows the sending rate to double each round (2) BBRStartupCwndGain.
    +64/// Used by default in most phases for BBR.cwnd_gain.
    +65const DEFAULT_CWND_GAIN: f64 = 2.0;
    +66
    +67/// equivalent to BBR.DrainPacingGain: A constant specifying the pacing gain value used in Drain mode,
    +68/// to attempt to drain the estimated queue at the bottleneck link in one round-trip or less.
    +69/// As noted in BBRDrainPacingGain, any value at or below 1 / BBRStartupCwndGain = 1 / 2 = 0.5 will theoretically achieve this.
    +70/// BBR uses the value 0.5, which has been shown to offer good performance when compared with other alternatives.
    +71/// <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-2.4>
    +72/// <https://github.com/google/bbr/blob/master/Documentation/startup/gain/analysis/bbr_drain_gain.pdf>
    +73const DRAIN_PACING_GAIN: f64 = 1.0 / DEFAULT_CWND_GAIN;
    +74
    +75/// cwnd gain used when probing up <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.1>
    +76const PROBE_BW_UP_CWND_GAIN: f64 = 2.25;
    +77
    +78/// cwnd gain used when probing RTT <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.1>
    +79const PROBE_RTT_CWND_GAIN: f64 = 0.5;
    +80
    +81/// equivalent to BBR.ProbeRTTDuration: A constant specifying the minimum duration for which ProbeRTT state holds C.inflight to BBR.MinPipeCwnd or fewer packets: 200 ms.
    +82const PROBE_RTT_DURATION_MS: u64 = 200;
    +83
    +84/// equivalent to BBR.ProbeRTTInterval: A constant specifying the minimum time interval between ProbeRTT states: 5 secs.
    +85const PROBE_RTT_INTERVAL_SEC: u64 = 5;
    +86
    +87/// equivalent to BBR.LossThresh: A constant specifying the maximum tolerated per-round-trip packet loss rate when probing for bandwidth (the default is 2%).
    +88const LOSS_THRESH: f64 = 0.02;
    +89
    +90/// equivalent to BBR.Beta: A constant specifying the default multiplicative decrease to make upon each round trip during which the connection detects packet loss (the value is 0.7).
    +91const BETA: f64 = 0.7;
    +92
    +93/// equivalent to BBR.Headroom: A constant specifying the multiplicative factor to apply to BBR.inflight_longterm when calculating
    +94/// a volume of free headroom to try to leave unused in the path
    +95/// (e.g. free space in the bottleneck buffer or free time slots in the bottleneck link) that can be used by cross traffic (the value is 0.15).
    +96const HEADROOM: f64 = 0.15;
    +97
    +98/// equivalent to BBR.MinRTTFilterLen: A constant specifying the length of the BBR.min_rtt min filter window, BBR.MinRTTFilterLen is 10 secs.
    +99const MIN_RTT_FILTER_LEN: u64 = 10;
    +100
    +101/// multiplier used to check growth when validating if the full bandwidth has been reached
    +102/// <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.1.2-6>
    +103const FULL_BW_GROWTH: f64 = 1.25;
    +104
    +105/// maximum number of rounds needed before we consider that the pipe is full <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.1.2-6>
    +106const MAX_FULL_BW_COUNT: u64 = 3;
    +107
    +108/// when setting `bw_probe_up_rounds` when raising our inflight long term slope we don't go above this
    +109/// <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.6-8>
    +110const MAX_LONG_TERM_PROBE_UP_ROUNDS: u32 = 30;
    +111
    +112/// max number of rounds used when deciding to coexist with Reno / CUBIC <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.5.1>
    +113const MAX_RENO_ROUNDS: u64 = 63;
    +114
    +115/// minimum amount of time to wait before probing again <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.5.3-5>
    +116const MIN_PROBE_WAIT_MS: u64 = 2000;
    +117
    +118/// when waiting before probing again we add up to one second of added wait time
    +119/// <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.5.3-5>
    +120const MAX_ADDED_PROBE_WAIT_MS: u64 = 1000;
    +121
    +122/// Substates when probing bandwidth
    +123/// <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3>
    +124#[derive(Debug, Clone, Copy, Eq, PartialEq)]
    +125enum ProbeBwSubstate {
    +126    /// Deceleration: sends slower than delivery rate to reduce queue
    +127    /// equivalent to ProbeBW_DOWN <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.1>
    +128    Down,
    +129
    +130    /// Cruising: sends at delivery rate to maintain high utilization
    +131    /// equivalent to ProbeBW_CRUISE <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.2>
    +132    Cruise,
    +133
    +134    /// Refill: sends at BBR.bw for one RTT to fill pipe before probing up
    +135    /// equivalent to ProbeBW_REFILL <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.3>
    +136    Refill,
    +137
    +138    /// Acceleration: sends faster than delivery rate to probe for more bandwidth
    +139    /// equivalent to ProbeBW_UP <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.4>
    +140    Up,
    +141}
    +142
    +143/// State Machine description from BBR3
    +144/// <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3>
    +145#[derive(Debug, Clone, Copy, Eq, PartialEq)]
    +146enum BbrState {
    +147    /// Initial state: rapidly probes for bandwidth using high pacing_gain
    +148    /// equivalent to Startup <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.1>
    +149    Startup,
    +150
    +151    /// Drains queue created during Startup by using low pacing_gain (< 1.0)
    +152    /// equivalent to Drain <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.2>
    +153    Drain,
    +154
    +155    /// Steady-state phase that cycles through bandwidth probing tactics
    +156    /// equivalent to ProbeBW states <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3>
    +157    ProbeBw(ProbeBwSubstate),
    +158
    +159    /// Temporarily reduces inflight to measure true min_rtt
    +160    /// equivalent to ProbeRTT <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.4>
    +161    ProbeRtt,
    +162}
    +163
    +164/// Ack phases used during ProbeBW states
    +165/// equivalent to BBR.ack_phase states <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.6>
    +166#[derive(Debug, Clone, Copy, Eq, PartialEq)]
    +167enum AckPhase {
    +168    /// equivalent to ACKS_PROBE_STARTING
    +169    ProbeStarting,
    +170    /// equivalent to ACKS_PROBE_STOPPING
    +171    ProbeStopping,
    +172    /// equivalent to ACKS_REFILLING
    +173    Refilling,
    +174    /// equivalent to ACKS_PROBE_FEEDBACK
    +175    ProbeFeedback,
    +176}
    +177
    +178/// Description of a packet for the purposes of analysis through BBR3
    +179/// all volumes of data use bytes, all rates of data use bytes/sec
    +180/// equivalent to P <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-4.1.2.1.2>
    +181#[derive(Debug, Clone, Copy)]
    +182struct BbrPacket {
    +183    /// equivalent to P.delivered: C.delivered when the packet was sent from transport connection C.
    +184    delivered: u64,
    +185    /// equivalent to P.delivered_time: C.delivered_time when the packet was sent.
    +186    delivered_time: Instant,
    +187    /// equivalent to P.first_send_time: C.first_send_time when the packet was sent.
    +188    first_send_time: Instant,
    +189    /// equivalent to P.send_time: The pacing departure time selected when the packet was scheduled to be sent.
    +190    send_time: Instant,
    +191    /// equivalent to P.is_app_limited: true if C.app_limited was non-zero when the packet was sent, else false.
    +192    is_app_limited: bool,
    +193    /// equivalent to P.tx_in_flight: C.inflight immediately after the transmission of packet P.
    +194    tx_in_flight: u64,
    +195    /// packet number from the connection
    +196    packet_number: u64,
    +197    /// packet size in bytes
    +198    size: u16,
    +199    /// equivalent to P.lost: C.lost when the packet was sent
    +200    lost: u64,
    +201    /// used to flag acknowledgement within our VecDeque, a packet can be flagged lost after having been flagged acknowledged
    +202    /// hence the necessity of this flag being set before we remove it from packets.
    +203    acknowledged: bool,
    +204    /// once a packet has been acknowledged on a given round it is marked for removal on the next round.
    +205    stale: bool,
    +206    /// used to mark packets stale if they're far from the current round <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.1>
    +207    round_count: u64,
    +208}
    +209
    +210/// Description of a per-ack rate sample state that will allow us to determine a short term evolution of the connection
    +211/// equivalent to RS <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-2.2>
    +212#[derive(Debug, Clone, Copy)]
    +213struct BbrRateSample {
    +214    /// equivalent to RS.delivery_rate: The delivery rate (aka bandwidth) sample obtained from the packet that has just been ACKed.
    +215    delivery_rate: f64,
    +216    /// equivalent to RS.is_app_limited: The P.is_app_limited from the most recent packet
    +217    ///    delivered; indicates whether the rate sample is application-limited.
    +218    is_app_limited: bool,
    +219    /// equivalent to RS.interval: The length of the sampling interval.
    +220    interval: Duration,
    +221    /// equivalent to RS.delivered: The volume of data delivered between the transmission of the packet that has just been ACKed and the current time.
    +222    delivered: u64,
    +223    /// equivalent to RS.prior_delivered: The P.delivered count from the most recent packet delivered.
    +224    prior_delivered: u64,
    +225    /// equivalent to RS.prior_time: The P.delivered_time from the most recent packet delivered.
    +226    prior_time: Instant,
    +227    /// equivalent to RS.send_elapsed: Send time interval calculated from the most recent
    +228    ///    packet delivered (see the "Send Rate" section above).
    +229    send_elapsed: Duration,
    +230    /// equivalent to RS.ack_elapsed: ACK time interval calculated from the most recent
    +231    ///    packet delivered (see the "ACK Rate" section above).
    +232    ack_elapsed: Duration,
    +233    /// equivalent to RS.rtt: The RTT sample calculated based on the most recently-sent packet of the packets that have just been ACKed.
    +234    rtt: Duration,
    +235    /// equivalent to RS.tx_in_flight: C.inflight at the time of the transmission of the packet that has just been ACKed
    +236    /// (the most recently sent packet among packets ACKed by the ACK that was just received).
    +237    tx_in_flight: u64,
    +238    /// equivalent to RS.newly_acked: The volume of data in bytes cumulatively or selectively acknowledged upon the ACK that was just received.
    +239    newly_acked: u64,
    +240    /// equivalent to RS.newly_lost: The volume of data in bytes newly marked lost upon the ACK that was just received.
    +241    newly_lost: u64,
    +242    /// equivalent to RS.lost: The volume of data in bytes that was declared lost between the transmission
    +243    /// and acknowledgment of the packet that has just been ACKed (the most recently sent packet among packets ACKed by the ACK that was just received).
    +244    lost: u64,
    +245    /// equivalent to RS.last_end_seq
    +246    last_end_seq: u64,
    +247    /// represents the last packet that was used in the generation of this rate sample
    +248    last_packet: BbrPacket,
    +249}
    +250
    +251/// Experimental! Use at your own risk.
    +252///
    +253/// Aims for reduced buffer bloat and improved performance over high bandwidth-delay product networks.
    +254/// Based on <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html>
    +255/// equivalent to a combination of BBR and C states
    +256/// <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-2.4>
    +257/// <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-2.1>
    +258#[derive(Debug, Clone)]
    +259pub struct Bbr3 {
    +260    /// equivalent to C.SMSS The Sender Maximum Send Size in bytes. <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-2.1>
    +261    /// <https://www.rfc-editor.org/rfc/rfc9000#name-datagram-size>
    +262    smss: u64,
    +263    /// equivalent to C.InitialCwnd: The initial congestion window set by the transport protocol implementation for the connection at initialization time.
    +264    initial_cwnd: u64,
    +265    /// equivalent to C.delivered: The total amount of data delivered so far over the lifetime of the transport connection C.
    +266    /// This MUST NOT include pure ACK packets. It SHOULD include spurious retransmissions that have been acknowledged as delivered.
    +267    delivered: u64,
    +268    /// equivalent to C.inflight: The connection's best estimate of the number of bytes outstanding in the network.
    +269    /// This includes the number of bytes that have been sent and have not been acknowledged or marked as lost since their last transmission
    +270    /// (e.g. "pipe" from RFC6675 or "bytes_in_flight" from RFC9002). This MUST NOT include pure ACK packets.
    +271    inflight: u64,
    +272    /// equivalent to C.is_cwnd_limited: True if the connection has fully utilized C.cwnd at any point in the last packet-timed round trip.
    +273    is_cwnd_limited: bool,
    +274    /// equivalent to BBR.cycle_count: The virtual time used by the BBR.max_bw filter window.
    +275    /// since the BBR.max_bw_filter only needs to track samples from two time slots: the previous ProbeBW cycle and the current ProbeBW cycle.
    +276    cycle_count: u64,
    +277    /// equivalent to C.cwnd: The transport sender's congestion window. When transmitting data, the sending connection ensures that C.inflight does not exceed C.cwnd.
    +278    cwnd: u64,
    +279    /// equivalent to C.pacing_rate: The current pacing rate for a BBR flow, which controls inter-packet spacing.
    +280    pacing_rate: f64,
    +281    /// equivalent to C.send_quantum: The maximum size of a data aggregate scheduled and transmitted together as a unit, e.g., to amortize per-packet transmission overheads.
    +282    send_quantum: u64,
    +283    /// equivalent to BBR.pacing_gain: The dynamic gain factor used to scale BBR.bw to produce C.pacing_rate.
    +284    pacing_gain: f64,
    +285    /// default pacing gain is 1, when cruising, probing for RTT or refilling <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.1>
    +286    default_pacing_gain: f64,
    +287    /// pacing gain when probing bandwidth down <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.1>
    +288    probe_bw_down_pacing_gain: f64,
    +289    /// pacing gain when probing bandwidth up <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.1>
    +290    probe_bw_up_pacing_gain: f64,
    +291    /// equivalent to BBR.StartupPacingGain: A constant specifying the minimum gain value for calculating the pacing rate that will allow
    +292    /// the sending rate to double each round (4 * ln(2) ~= 2.77)
    +293    /// BBRStartupPacingGain; used in Startup mode for BBR.pacing_gain. <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.1>
    +294    startup_pacing_gain: f64,
    +295    /// equivalent to BBR.DrainPacingGain: A constant specifying the pacing gain value used in Drain mode,
    +296    /// to attempt to drain the estimated queue at the bottleneck link in one round-trip or less.
    +297    /// As noted in BBRDrainPacingGain, any value at or below 1 / BBRStartupCwndGain = 1 / 2 = 0.5 will theoretically achieve this.
    +298    /// BBR uses the value 0.5, which has been shown to offer good performance when compared with other alternatives.
    +299    /// <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.1>
    +300    drain_pacing_gain: f64,
    +301    /// equivalent to BBR.PacingMarginPercent: The static discount factor of 1% used to scale BBR.bw to produce C.pacing_rate.
    +302    pacing_margin_percent: f64,
    +303    /// equivalent to BBR.cwnd_gain: The dynamic gain factor used to scale the estimated BDP to produce a congestion window (C.cwnd).
    +304    cwnd_gain: f64,
    +305    /// equivalent to BBR.DefaultCwndGain: A constant specifying the minimum gain value that allows the sending rate to double each round (2) BBRStartupCwndGain.
    +306    /// Used by default in most phases for BBR.cwnd_gain.
    +307    default_cwnd_gain: f64,
    +308    /// used to generate random numbers when deciding how long to wait before probing again
    +309    /// using Pcg32 as it's a fast general purpose random number generator and fits our purpose here
    +310    /// these numbers will not be security critical as they're only used to decide when to probe the connection next.
    +311    probe_rng: Pcg32,
    +312    /// cwnd gain used when probing up <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.1>
    +313    probe_bw_up_cwnd_gain: f64,
    +314    /// cwnd gain used when probing RTT <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.1>
    +315    probe_rtt_cwnd_gain: f64,
    +316    /// equivalent to BBR.state: The current state of a BBR flow in the BBR state machine. <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-3.3>
    +317    state: BbrState,
    +318    /// equivalent to BBR.undo_state: The state of a BBR flow in the BBR state machine saved in case a loss episode is later declared spurious. <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-3.3>
    +319    undo_state: BbrState,
    +320    /// equivalent to BBR.round_count: Count of packet-timed round trips elapsed so far.
    +321    round_count: u64,
    +322    /// equivalent to BBR.round_start: A boolean that BBR sets to true once per packet-timed round trip, on ACKs that advance BBR.round_count.
    +323    round_start: bool,
    +324    /// equivalent to BBR.next_round_delivered: P.delivered value denoting the end of a packet-timed round trip.
    +325    next_round_delivered: u64,
    +326    /// equivalent to BBR.idle_restart: A boolean that is true if and only if a connection is restarting after being idle.
    +327    idle_restart: bool,
    +328    /// equivalent to BBR.MinPipeCwnd: The minimal C.cwnd value BBR targets, to allow pipelining with endpoints that follow an "ACK every other packet" delayed-ACK policy: 4 * C.SMSS.
    +329    min_pipe_cwnd: u64,
    +330    /// equivalent to BBR.max_bw: The windowed maximum recent bandwidth sample, obtained using the BBR delivery rate sampling algorithm in
    +331    /// <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-4.1>,
    +332    /// measured during the current or previous bandwidth probing cycle (or during Startup, if the flow is still in that state). (Part of the long-term model.)
    +333    max_bw: f64,
    +334    /// equivalent to BBR.bw_shortterm: The short-term maximum sending bandwidth that the algorithm estimates is safe for matching the current network path delivery rate,
    +335    /// based on any loss signals in the current bandwidth probing cycle. This is generally lower than max_bw. (Part of the short-term model.)
    +336    bw_shortterm: f64,
    +337    /// equivalent to BBR.undo_bw_shortterm: The short-term maximum sending bandwidth that the algorithm estimates is safe for matching the current network path delivery rate,
    +338    /// based on any loss signals in the current bandwidth probing cycle. This is generally lower than max_bw. (Part of the short-term model.)
    +339    /// saved state in case a loss episode is later declared spurious
    +340    undo_bw_shortterm: f64,
    +341    /// equivalent to BBR.bw: The maximum sending bandwidth that the algorithm estimates is appropriate for matching the current network path delivery rate,
    +342    /// given all available signals in the model, at any time scale. It is the min() of max_bw and bw_shortterm.
    +343    bw: f64,
    +344    /// equivalent to BBR.min_rtt: The windowed minimum round-trip time sample measured over the last BBR.MinRTTFilterLen = 10 seconds.
    +345    /// This attempts to estimate the two-way propagation delay of the network path when all connections sharing a bottleneck are using BBR,
    +346    /// but also allows BBR to estimate the value required for a BBR.bdp estimate that allows full throughput if there are legacy loss-based Reno or CUBIC flows sharing the bottleneck.
    +347    min_rtt: Duration,
    +348    /// equivalent to BBR.bdp: The estimate of the network path's BDP (Bandwidth-Delay Product), computed as: BBR.bdp = BBR.bw * BBR.min_rtt.
    +349    bdp: u64,
    +350    /// equivalent to BBR.extra_acked: A volume of data that is the estimate of the recent degree of aggregation in the network path.
    +351    extra_acked: u64,
    +352    /// equivalent to BBR.offload_budget: The estimate of the minimum volume of data necessary to achieve full throughput when using sender
    +353    /// (TSO/GSO) and receiver (LRO, GRO) host offload mechanisms.
    +354    offload_budget: u64,
    +355    /// equivalent to BBR.max_inflight: The estimate of C.inflight required to fully utilize the bottleneck bandwidth available to the flow,
    +356    /// based on the BDP estimate (BBR.bdp), the aggregation estimate (BBR.extra_acked), the offload budget (BBR.offload_budget), and BBR.MinPipeCwnd.
    +357    max_inflight: u64,
    +358    /// equivalent to BBR.inflight_longterm: The long-term maximum inflight that the algorithm estimates will produce acceptable queue pressure,
    +359    /// based on signals in the current or previous bandwidth probing cycle, as measured by loss. That is, if a flow is probing for bandwidth,
    +360    /// and observes that sending a particular inflight causes a loss rate higher than the loss rate threshold,
    +361    /// it sets inflight_longterm to that volume of data. (Part of the long-term model.)
    +362    inflight_longterm: u64,
    +363    /// equivalent to BBR.inflight_longterm: The long-term maximum inflight that the algorithm estimates will produce acceptable queue pressure,
    +364    /// based on signals in the current or previous bandwidth probing cycle, as measured by loss. That is, if a flow is probing for bandwidth,
    +365    /// and observes that sending a particular inflight causes a loss rate higher than the loss rate threshold,
    +366    /// it sets inflight_longterm to that volume of data. (Part of the long-term model.)
    +367    /// saved state in case a loss episode is later declared spurious
    +368    undo_inflight_longterm: u64,
    +369    /// equivalent to BBR.inflight_shortterm: Analogous to BBR.bw_shortterm,
    +370    /// the short-term maximum inflight that the algorithm estimates is safe for matching the current network path delivery process,
    +371    /// based on any loss signals in the current bandwidth probing cycle. This is generally lower than max_inflight or inflight_longterm. (Part of the short-term model.)
    +372    inflight_shortterm: u64,
    +373    /// equivalent to BBR.undo_inflight_shortterm: Analogous to BBR.bw_shortterm,
    +374    /// the short-term maximum inflight that the algorithm estimates is safe for matching the current network path delivery process,
    +375    /// based on any loss signals in the current bandwidth probing cycle. This is generally lower than max_inflight or inflight_longterm. (Part of the short-term model.)
    +376    /// saved state in case a loss episode is later declared spurious
    +377    undo_inflight_shortterm: u64,
    +378    /// equivalent to BBR.bw_latest: a 1-round-trip max of delivered bandwidth (RS.delivery_rate).
    +379    bw_latest: f64,
    +380    /// equivalent to BBR.inflight_latest: a 1-round-trip max of delivered volume of data (RS.delivered).
    +381    inflight_latest: u64,
    +382    /// equivalent to BBR.max_bw_filter: A windowed max filter for RS.delivery_rate samples, for estimating BBR.max_bw.
    +383    max_bw_filter: MaxFilter,
    +384    /// equivalent to BBR.extra_acked_interval_start: The start of the time interval for estimating the excess amount of data acknowledged due to aggregation effects.
    +385    extra_acked_interval_start: Option<Instant>,
    +386    /// equivalent to BBR.extra_acked_delivered: The volume of data marked as delivered since BBR.extra_acked_interval_start.
    +387    extra_acked_delivered: u64,
    +388    /// equivalent to BBR.extra_acked_filter: A windowed max filter for tracking the degree of aggregation in the path.
    +389    extra_acked_filter: MaxFilter,
    +390    /// equivalent to BBR.full_bw_reached: A boolean that records whether BBR estimates that it has ever fully utilized its available bandwidth over the lifetime of the connection.
    +391    full_bw_reached: bool,
    +392    /// equivalent to BBR.full_bw_now: A boolean that records whether BBR estimates that it has fully utilized its available bandwidth since it most recetly started looking.
    +393    full_bw_now: bool,
    +394    /// equivalent to BBR.full_bw: A recent baseline BBR.max_bw to estimate if BBR has "filled the pipe" in Startup.
    +395    full_bw: f64,
    +396    /// equivalent to BBR.full_bw_count: The number of non-app-limited round trips without large increases in BBR.full_bw.
    +397    full_bw_count: u64,
    +398    /// equivalent to BBR.min_rtt_stamp: The wall clock time at which the current BBR.min_rtt sample was obtained.
    +399    min_rtt_stamp: Option<Instant>,
    +400    /// equivalent to BBR.ProbeRTTDuration: A constant specifying the minimum duration for which ProbeRTT state holds C.inflight to BBR.MinPipeCwnd or fewer packets: 200 ms.
    +401    probe_rtt_duration: Duration,
    +402    /// equivalent to BBR.ProbeRTTInterval: A constant specifying the minimum time interval between ProbeRTT states: 5 secs.
    +403    probe_rtt_interval: Duration,
    +404    /// equivalent to BBR.probe_rtt_min_delay: The minimum RTT sample recorded in the last ProbeRTTInterval.
    +405    probe_rtt_min_delay: Duration,
    +406    /// equivalent to BBR.probe_rtt_min_stamp: The wall clock time at which the current BBR.probe_rtt_min_delay sample was obtained.
    +407    probe_rtt_min_stamp: Option<Instant>,
    +408    /// equivalent to BBR.probe_rtt_expired: A boolean recording whether the BBR.probe_rtt_min_delay has expired and
    +409    /// is due for a refresh with an application idle period or a transition into ProbeRTT state.
    +410    probe_rtt_expired: bool,
    +411    /// equivalent to C.delivered_time: The wall clock time when C.delivered was last updated. <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-4.1.1.2.1>
    +412    delivered_time: Option<Instant>,
    +413    /// equivalent to C.first_send_time: If packets are in flight, then this holds the send time of the packet that was most recently marked as delivered.
    +414    /// Else, if the connection was recently idle, then this holds the send time of most recently sent packet.
    +415    first_send_time: Option<Instant>,
    +416    /// equivalent to C.app_limited: The index of the last transmitted packet marked as application-limited, or 0 if the connection is not currently application-limited.
    +417    app_limited: u64,
    +418    /// equivalent to C.lost: the number of bytes that have been lost during the lifetime of this connection
    +419    lost: u64,
    +420    /// equivalent to C.srtt: The smoothed RTT, an exponentially weighted moving average of the observed RTT of the connection.
    +421    srtt: Duration,
    +422    /// collection of packets in flight or just acknowledged / lost.
    +423    packets: VecDeque<BbrPacket>,
    +424    /// equivalent to RS: Per-ACK Rate Sample State <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-2.2>
    +425    rs: Option<BbrRateSample>,
    +426    /// equivalent to BBR.rounds_since_bw_probe: rounds since last bw probe state.
    +427    rounds_since_bw_probe: u64,
    +428    /// equivalent to BBR.bw_probe_wait: random wait time before entering probing state again
    +429    bw_probe_wait: Duration,
    +430    /// equivalent to BBR.bw_probe_up_rounds: number of rounds that have been executed in probe up state
    +431    bw_probe_up_rounds: u32,
    +432    /// equivalent to BBR.bw_probe_up_acks: volume of data in bytes that has been acknowledged during probe up state
    +433    bw_probe_up_acks: u64,
    +434    /// equivalent to BBR.probe_up_cnt: count of the number of times we've grown the cwnd during probe up state
    +435    probe_up_cnt: u64,
    +436    /// equivalent to BBR.cycle_stamp: timestamp when we start probing down state
    +437    cycle_stamp: Option<Instant>,
    +438    /// equivalent to BBR.ack_phase: ACK phase during probing states
    +439    ack_phase: AckPhase,
    +440    /// equivalent to BBR.bw_probe_samples: <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.10.2>
    +441    bw_probe_samples: bool,
    +442    /// equivalent to BBR.loss_round_delivered: C.delivered during the first loss of the round
    +443    loss_round_delivered: u64,
    +444    /// equivalent to BBR.loss_in_round: flag set to true when loss occurs during the round
    +445    loss_in_round: bool,
    +446    /// equivalent to BBR.probe_rtt_done_stamp: timestamp when probe RTT state is finished
    +447    probe_rtt_done_stamp: Option<Instant>,
    +448    /// equivalent to BBR.probe_rtt_round_done: set once per round when BBR.probe_rtt_done_stamp to check if we need to switch state
    +449    probe_rtt_round_done: bool,
    +450    /// equivalent to BBR.prior_cwnd: cwnd from last round
    +451    prior_cwnd: u64,
    +452    /// equivalent to BBR.loss_round_start: flag set to true at the very beginning of a round where loss occurred
    +453    loss_round_start: bool,
    +454    /// equivalent to BBR.drain_start_round: The value of round_count when Drain state started.
    +455    drain_start_round: u64,
    +456    /// Number of ack-eliciting packets the peer may receive before sending an immediate ACK,
    +457    /// as requested via the QUIC ACK frequency extension. Used when computing `offload_budget`
    +458    /// per <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.8.2>.
    +459    ack_eliciting_threshold: u64,
    +460    /// `max_ack_delay` we requested the peer to use via the QUIC ACK frequency extension.
    +461    /// Used when computing `offload_budget` per
    +462    /// <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.8.2>.
    +463    max_ack_delay: Duration,
    +464}
    +465
    +466impl Bbr3 {
    +467    fn new(config: Arc<Bbr3Config>, current_mtu: u16) -> Self {
    +468        let probe_rng: Pcg32;
    +469        if let Some(probe_seed) = config.probe_rng_seed {
    +470            probe_rng = Pcg32::from_seed(probe_seed);
    +471        } else {
    +472            probe_rng = Pcg32::from_rng(&mut rand::rng());
    +473        }
    +474        let smss = min(
    +475            max(MIN_MAX_DATAGRAM_SIZE, current_mtu) as u64,
    +476            MAX_DATAGRAM_SIZE,
    +477        );
    +478        let initial_cwnd = config.initial_window;
    +479        let startup_pacing_gain = config.startup_pacing_gain.unwrap_or(STARTUP_PACING_GAIN);
    +480        let default_pacing_gain = config.default_pacing_gain.unwrap_or(DEFAULT_PACING_GAIN);
    +481        let probe_bw_down_pacing_gain = config
    +482            .probe_bw_down_pacing_gain
    +483            .unwrap_or(PROBE_BW_DOWN_PACING_GAIN);
    +484        let probe_bw_up_pacing_gain = config
    +485            .probe_bw_up_pacing_gain
    +486            .unwrap_or(PROBE_BW_UP_PACING_GAIN);
    +487        let drain_pacing_gain = config.drain_pacing_gain.unwrap_or(DRAIN_PACING_GAIN);
    +488        let pacing_margin_percent = config
    +489            .pacing_margin_percent
    +490            .unwrap_or(PACING_MARGIN_PERCENT);
    +491        let default_cwnd_gain = config.default_cwnd_gain.unwrap_or(DEFAULT_CWND_GAIN);
    +492        let probe_bw_up_cwnd_gain = config
    +493            .probe_bw_up_cwnd_gain
    +494            .unwrap_or(PROBE_BW_UP_CWND_GAIN);
    +495        let probe_rtt_cwnd_gain = config.probe_rtt_cwnd_gain.unwrap_or(PROBE_RTT_CWND_GAIN);
    +496        // the calculation for initial pacing rate described here <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.2-5>
    +497        let nominal_bandwidth = initial_cwnd as f64 / 0.001;
    +498        let pacing_rate = startup_pacing_gain * nominal_bandwidth;
    +499        Self {
    +500            smss,
    +501            initial_cwnd,
    +502            delivered: 0,
    +503            inflight: 0,
    +504            is_cwnd_limited: false,
    +505            cycle_count: 0,
    +506            cwnd: initial_cwnd,
    +507            pacing_rate,
    +508            send_quantum: 2 * smss, // we start high, but it will be adjusted in set_send_quantum <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.3>
    +509            pacing_gain: startup_pacing_gain,
    +510            startup_pacing_gain,
    +511            default_pacing_gain,
    +512            probe_bw_down_pacing_gain,
    +513            probe_bw_up_pacing_gain,
    +514            drain_pacing_gain,
    +515            pacing_margin_percent,
    +516            cwnd_gain: default_cwnd_gain,
    +517            default_cwnd_gain,
    +518            probe_rng,
    +519            probe_bw_up_cwnd_gain,
    +520            state: BbrState::Startup,
    +521            undo_state: BbrState::Startup,
    +522            round_count: 0,
    +523            round_start: true,
    +524            next_round_delivered: 0,
    +525            idle_restart: false,
    +526            min_pipe_cwnd: 4 * smss, // 4 * C.SMSS as defined in <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-2.7-4>
    +527            max_bw: 0.0,
    +528            bw_shortterm: f64::INFINITY,
    +529            undo_bw_shortterm: f64::INFINITY,
    +530            bw: 0.0,
    +531            min_rtt: Duration::from_secs(u64::MAX),
    +532            bdp: 0,
    +533            extra_acked: 0,
    +534            offload_budget: 0,
    +535            max_inflight: 0,
    +536            inflight_longterm: u64::MAX,
    +537            undo_inflight_longterm: u64::MAX,
    +538            inflight_shortterm: u64::MAX,
    +539            undo_inflight_shortterm: u64::MAX,
    +540            bw_latest: 0.0,
    +541            inflight_latest: 0,
    +542            max_bw_filter: MaxFilter::new(MAX_BW_FILTER_LEN as u64),
    +543            extra_acked_interval_start: None,
    +544            extra_acked_delivered: 0,
    +545            extra_acked_filter: MaxFilter::new(EXTRA_ACKED_FILTER_LEN as u64),
    +546            full_bw_reached: false,
    +547            full_bw_now: false,
    +548            full_bw: 0.0,
    +549            full_bw_count: 0,
    +550            min_rtt_stamp: None,
    +551            probe_rtt_cwnd_gain,
    +552            probe_rtt_duration: Duration::from_millis(PROBE_RTT_DURATION_MS),
    +553            probe_rtt_interval: Duration::from_secs(PROBE_RTT_INTERVAL_SEC),
    +554            probe_rtt_min_delay: Duration::ZERO,
    +555            probe_rtt_min_stamp: None,
    +556            probe_rtt_expired: false,
    +557            delivered_time: None,
    +558            first_send_time: None,
    +559            app_limited: 0,
    +560            lost: 0,
    +561            srtt: Duration::ZERO,
    +562            rs: None,
    +563            packets: VecDeque::new(),
    +564            rounds_since_bw_probe: 0,
    +565            bw_probe_wait: Duration::ZERO,
    +566            bw_probe_up_rounds: 0,
    +567            bw_probe_up_acks: 0,
    +568            probe_up_cnt: 0,
    +569            cycle_stamp: None,
    +570            ack_phase: AckPhase::ProbeStarting,
    +571            bw_probe_samples: false,
    +572            loss_round_delivered: 0,
    +573            loss_in_round: false,
    +574            probe_rtt_done_stamp: None,
    +575            probe_rtt_round_done: false,
    +576            prior_cwnd: 0,
    +577            loss_round_start: false,
    +578            drain_start_round: 0,
    +579            // Conservative defaults that match RFC 9000 §13.2.2 behavior (ACK every other
    +580            // ack-eliciting packet) and the default QUIC `max_ack_delay` of 25ms. Overridden
    +581            // when the connection supplies peer ACK-frequency parameters.
    +582            ack_eliciting_threshold: 1,
    +583            max_ack_delay: Duration::from_millis(25),
    +584        }
    +585    }
    +586
    +587    /// equivalent to BBREnterStartup <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.1.1-3>
    +588    fn enter_startup(&mut self) {
    +589        self.state = BbrState::Startup;
    +590        self.pacing_gain = self.startup_pacing_gain;
    +591        self.cwnd_gain = self.default_cwnd_gain;
    +592    }
    +593
    +594    /// equivalent to BBRResetFullBW <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.1.2-4>
    +595    fn reset_full_bw(&mut self) {
    +596        self.full_bw = 0.0;
    +597        self.full_bw_count = 0;
    +598        self.full_bw_now = false;
    +599    }
    +600
    +601    /// equivalent to BBRNoteLoss <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.10.2-11>
    +602    fn note_loss(&mut self) {
    +603        if !self.loss_in_round {
    +604            self.loss_round_delivered = self.delivered;
    +605        }
    +606        self.save_state_upon_loss();
    +607        self.loss_in_round = true;
    +608    }
    +609
    +610    /// equivalent to BBRSaveStateUponLoss <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.11.1>
    +611    /// Save state in case a loss episode is later declared spurious
    +612    fn save_state_upon_loss(&mut self) {
    +613        self.undo_state = self.state;
    +614        self.undo_bw_shortterm = self.bw_shortterm;
    +615        self.undo_inflight_shortterm = self.inflight_shortterm;
    +616        self.undo_inflight_longterm = self.inflight_longterm;
    +617    }
    +618
    +619    /// equivalent to BBRInflightAtLoss <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.10.2-11>
    +620    /// We check at what prefix of packet did losses exceed `loss_thresh`
    +621    fn inflight_at_loss(&mut self, packet_size: u64) -> u64 {
    +622        if let Some(rate_sample) = self.rs {
    +623            let inflight_prev = rate_sample.tx_in_flight.saturating_sub(packet_size);
    +624            let inflight_prev_threshold = LOSS_THRESH * inflight_prev as f64;
    +625            let lost_prev = rate_sample.lost.saturating_sub(packet_size);
    +626            let compared_loss = (inflight_prev_threshold.round() as u64) - lost_prev;
    +627            let lost_prefix = compared_loss as f64 / (1.0 - LOSS_THRESH);
    +628            let inflight_at_loss = inflight_prev + lost_prefix as u64;
    +629            return inflight_at_loss;
    +630        }
    +631        0
    +632    }
    +633
    +634    /// equivalent to BBRSaveCwnd <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.4.4-13>
    +635    fn save_cwnd(&mut self) {
    +636        if !self.loss_in_round && self.state != BbrState::ProbeRtt {
    +637            self.prior_cwnd = self.cwnd;
    +638        } else {
    +639            self.prior_cwnd = max(self.prior_cwnd, self.cwnd);
    +640        }
    +641    }
    +642
    +643    /// equivalent to BBRRestoreCwnd <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.4.4-13>
    +644    fn restore_cwnd(&mut self) {
    +645        self.cwnd = max(self.cwnd, self.prior_cwnd);
    +646    }
    +647
    +648    /// equivalent to BBRProbeRTTCwnd <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.4.5-1>
    +649    fn probe_rtt_cwnd(&mut self) -> u64 {
    +650        let mut probe_rtt_cwnd = self.bdp_multiple(self.bw, self.probe_rtt_cwnd_gain);
    +651        probe_rtt_cwnd = max(probe_rtt_cwnd, self.min_pipe_cwnd);
    +652        probe_rtt_cwnd
    +653    }
    +654
    +655    /// equivalent to BBRBoundCwndForProbeRTT <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.4.5-1>
    +656    fn bound_cwnd_for_probe_rtt(&mut self) {
    +657        if self.state == BbrState::ProbeRtt {
    +658            self.cwnd = min(self.cwnd, self.probe_rtt_cwnd());
    +659        }
    +660    }
    +661
    +662    /// equivalent to BBRTargetInflight <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.5.3-6>
    +663    fn target_inflight(&self) -> u64 {
    +664        min(self.bdp, self.cwnd)
    +665    }
    +666
    +667    /// equivalent to BBRHandleInflightTooHigh <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.10.2-1>
    +668    fn handle_inflight_too_high(&mut self, now: Instant) {
    +669        self.bw_probe_samples = false;
    +670        if let Some(rate_sample) = self.rs
    +671            && !rate_sample.is_app_limited
    +672        {
    +673            self.inflight_longterm = max(
    +674                rate_sample.tx_in_flight,
    +675                (self.target_inflight() as f64 * BETA) as u64,
    +676            );
    +677        }
    +678
    +679        if self.state == BbrState::ProbeBw(ProbeBwSubstate::Up) {
    +680            self.start_probe_bw_down(now);
    +681        }
    +682    }
    +683
    +684    /// equivalent to IsInflightTooHigh <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.10.2-1>
    +685    fn is_inflight_too_high(&self) -> bool {
    +686        if let Some(rate_sample) = self.rs {
    +687            return rate_sample.lost as f64 > rate_sample.tx_in_flight as f64 * LOSS_THRESH;
    +688        }
    +689        false
    +690    }
    +691
    +692    /// equivalent to BBRCheckStartupHighLoss <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.1.3>
    +693    fn check_startup_high_loss(&mut self) {
    +694        if self.full_bw_reached {
    +695            return;
    +696        }
    +697
    +698        if self.is_inflight_too_high() {
    +699            let mut new_inflight_hi = self.bdp.max(self.inflight_latest);
    +700            if let Some(rate_sample) = self.rs
    +701                && new_inflight_hi < rate_sample.delivered
    +702            {
    +703                new_inflight_hi = rate_sample.delivered;
    +704            }
    +705            self.inflight_longterm = new_inflight_hi;
    +706            self.full_bw_reached = true;
    +707        }
    +708    }
    +709
    +710    /// equivalent to BBREnterProbeBW <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.6>
    +711    fn enter_probe_bw(&mut self, now: Instant) {
    +712        self.cwnd_gain = self.default_cwnd_gain;
    +713        self.start_probe_bw_down(now);
    +714    }
    +715
    +716    /// equivalent to BBRPickProbeWait <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.5.3-6>
    +717    fn pick_probe_wait(&mut self) {
    +718        // 0 or 1
    +719        self.rounds_since_bw_probe = self.probe_rng.random_bool(0.5) as u64;
    +720        self.bw_probe_wait = Duration::from_millis(
    +721            MIN_PROBE_WAIT_MS + self.probe_rng.random_range(0..=MAX_ADDED_PROBE_WAIT_MS),
    +722        );
    +723    }
    +724
    +725    /// equivalent to BBRHasElapsedInPhase <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.6-8>
    +726    fn has_elapsed_in_phase(&mut self, interval: Duration, now: Instant) -> bool {
    +727        if let Some(cycle_stamp) = self.cycle_stamp {
    +728            now > cycle_stamp.checked_add(interval).unwrap_or(cycle_stamp)
    +729        } else {
    +730            true
    +731        }
    +732    }
    +733
    +734    /// equivalent to BBRExitProbeRTT <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.4.4>
    +735    fn exit_probe_rtt(&mut self, now: Instant) {
    +736        self.reset_short_term_model();
    +737        if self.full_bw_reached {
    +738            self.start_probe_bw_down(now);
    +739            self.start_probe_bw_cruise();
    +740        } else {
    +741            self.enter_startup();
    +742        }
    +743    }
    +744
    +745    /// equivalent to BBRCheckProbeRTTDone <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.4.3-4>
    +746    fn check_probe_rtt_done(&mut self, now: Instant) {
    +747        if let Some(probe_rtt_done_stamp) = self.probe_rtt_done_stamp
    +748            && now > probe_rtt_done_stamp
    +749        {
    +750            self.probe_rtt_min_stamp = Some(now);
    +751            self.restore_cwnd();
    +752            self.exit_probe_rtt(now);
    +753        }
    +754    }
    +755
    +756    /// equivalent to BBRIsTimeToProbeBW <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.5.3-6>
    +757    fn maybe_enter_probe_bw_refill(&mut self, now: Instant) -> bool {
    +758        if self.has_elapsed_in_phase(self.bw_probe_wait, now)
    +759            || self.is_reno_coexistence_probe_time()
    +760        {
    +761            self.start_probe_bw_refill();
    +762            return true;
    +763        }
    +764        false
    +765    }
    +766
    +767    /// equivalent to BBRIsTimeToGoDown <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.6-6>
    +768    fn maybe_go_down(&mut self) -> bool {
    +769        if self.is_cwnd_limited && self.cwnd >= self.inflight_longterm {
    +770            self.reset_full_bw();
    +771            if let Some(rate_sample) = self.rs {
    +772                self.full_bw = rate_sample.delivery_rate;
    +773            }
    +774        } else if self.full_bw_now {
    +775            return true;
    +776        }
    +777        false
    +778    }
    +779
    +780    /// equivalent to BBRIsRenoCoexistenceProbeTime <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.5.3-6>
    +781    fn is_reno_coexistence_probe_time(&self) -> bool {
    +782        let reno_rounds = self.target_inflight();
    +783        let rounds = min(reno_rounds, MAX_RENO_ROUNDS);
    +784        self.rounds_since_bw_probe >= rounds
    +785    }
    +786
    +787    /// equivalent to BBRBDPMultiple <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.4.2-2>
    +788    fn bdp_multiple(&mut self, bw: f64, gain: f64) -> u64 {
    +789        if self.min_rtt == Duration::from_secs(u64::MAX) {
    +790            return self.initial_cwnd;
    +791        }
    +792        self.bdp = (bw * self.min_rtt.as_secs_f64()).round() as u64;
    +793        (gain * self.bdp as f64) as u64
    +794    }
    +795
    +796    /// equivalent to BBRUpdateOffloadBudget for QUIC per
    +797    /// <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.8.2>.
    +798    ///
    +799    /// The delayed-ACK term accounts for the QUIC ACK frequency extension:
    +800    /// `min(Ack-Eliciting Threshold, Requested Max Ack Delay * BBR.max_bw)`.
    +801    fn update_offload_budget(&mut self) {
    +802        let base = self.send_quantum;
    +803
    +804        // Ack-Eliciting Threshold is a packet count in the ACK_FREQUENCY frame; convert to
    +805        // bytes using the current SMSS. A threshold of 0 requires an immediate ACK per packet,
    +806        // so the delayed-ACK term contributes nothing in that case.
    +807        let threshold_bytes = self.ack_eliciting_threshold.saturating_mul(self.smss);
    +808        let delay_bytes = (self.max_ack_delay.as_secs_f64() * self.max_bw).round() as u64;
    +809        let delayed_ack_term = min(threshold_bytes, delay_bytes);
    +810
    +811        self.offload_budget = base.saturating_add(delayed_ack_term);
    +812    }
    +813
    +814    /// equivalent to BBRQuantizationBudget <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.4.2-2>
    +815    fn quantization_budget(&mut self, inflight_cap: u64) -> u64 {
    +816        self.update_offload_budget();
    +817        let mut inflight_cap = max(inflight_cap, self.offload_budget);
    +818        inflight_cap = max(inflight_cap, self.min_pipe_cwnd);
    +819        if self.state == BbrState::ProbeBw(ProbeBwSubstate::Up) {
    +820            inflight_cap += 2 * self.smss;
    +821        }
    +822        inflight_cap
    +823    }
    +824
    +825    /// equivalent to BBRInflight <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.4.2-2>
    +826    fn get_inflight(&mut self, gain: f64) -> u64 {
    +827        let inflight_cap = self.bdp_multiple(self.max_bw, gain);
    +828        self.quantization_budget(inflight_cap)
    +829    }
    +830
    +831    /// equivalent to BBRUpdateMaxInflight <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.4.2-2>
    +832    fn update_max_inflight(&mut self) {
    +833        let mut inflight_cap = self.bdp_multiple(self.max_bw, self.cwnd_gain);
    +834        inflight_cap += self.extra_acked;
    +835        self.max_inflight = self.quantization_budget(inflight_cap);
    +836    }
    +837
    +838    /// equivalent to BBRResetCongestionSignals <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.10.3-8>
    +839    fn reset_congestion_signals(&mut self) {
    +840        self.loss_in_round = false;
    +841        self.bw_latest = 0.0;
    +842        self.inflight_latest = 0;
    +843    }
    +844
    +845    /// equivalent to BBRStartRound <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.1-9>
    +846    fn start_round(&mut self) {
    +847        self.next_round_delivered = self.delivered;
    +848        self.is_cwnd_limited = false;
    +849    }
    +850
    +851    /// equivalent to BBRUpdateRound <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.1-9>
    +852    fn update_round(&mut self, packet: BbrPacket) {
    +853        if packet.delivered >= self.next_round_delivered {
    +854            self.start_round();
    +855            self.round_count += 1;
    +856            self.rounds_since_bw_probe += 1;
    +857            self.round_start = true;
    +858        } else {
    +859            self.round_start = false;
    +860        }
    +861    }
    +862
    +863    /// equivalent to BBRStartProbeBW_DOWN <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.6-4>
    +864    fn start_probe_bw_down(&mut self, now: Instant) {
    +865        self.reset_congestion_signals();
    +866        self.probe_up_cnt = u64::MAX;
    +867        self.pick_probe_wait();
    +868        self.cycle_stamp = Some(now);
    +869        self.ack_phase = AckPhase::ProbeStopping;
    +870        self.start_round();
    +871        self.pacing_gain = self.probe_bw_down_pacing_gain;
    +872        self.cwnd_gain = self.default_cwnd_gain;
    +873        self.state = BbrState::ProbeBw(ProbeBwSubstate::Down);
    +874    }
    +875
    +876    /// equivalent to BBRInflightWithHeadroom <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.6-8>
    +877    fn inflight_with_headroom(&self) -> u64 {
    +878        if self.inflight_longterm == u64::MAX {
    +879            return u64::MAX;
    +880        }
    +881        let total_headroom = max(self.smss, (HEADROOM * self.inflight_longterm as f64) as u64);
    +882        if let Some(inflight_with_headroom) = self.inflight_longterm.checked_sub(total_headroom) {
    +883            max(inflight_with_headroom, self.min_pipe_cwnd)
    +884        } else {
    +885            self.min_pipe_cwnd
    +886        }
    +887    }
    +888
    +889    /// equivalent to BBRSetPacingRateWithGain <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.2-7>
    +890    fn set_pacing_rate_with_gain(&mut self, gain: f64) {
    +891        let rate = gain * self.bw * (100.0 - self.pacing_margin_percent) / 100.0;
    +892        if self.full_bw_reached || rate > self.pacing_rate {
    +893            self.pacing_rate = rate;
    +894        }
    +895    }
    +896
    +897    /// equivalent to BBRRaiseInflightLongtermSlope <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.6-8>
    +898    fn raise_inflight_long_term_slope(&mut self) {
    +899        let growth_this_round = self
    +900            .smss
    +901            .checked_shl(self.bw_probe_up_rounds)
    +902            .unwrap_or(u64::MAX);
    +903        self.bw_probe_up_rounds = min(self.bw_probe_up_rounds + 1, MAX_LONG_TERM_PROBE_UP_ROUNDS);
    +904        self.probe_up_cnt = max(self.cwnd / growth_this_round, 1);
    +905    }
    +906
    +907    /// equivalent to BBRProbeInflightLongtermUpward <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.6-8>
    +908    fn probe_inflight_long_term_upward(&mut self) {
    +909        if !self.is_cwnd_limited || self.cwnd < self.inflight_longterm {
    +910            return;
    +911        }
    +912        if let Some(rate_sample) = self.rs {
    +913            self.bw_probe_up_acks += rate_sample.newly_acked;
    +914        }
    +915        if self.bw_probe_up_acks >= self.probe_up_cnt && self.probe_up_cnt > 0 {
    +916            let delta = self.bw_probe_up_acks / self.probe_up_cnt;
    +917            self.bw_probe_up_acks -= delta * self.probe_up_cnt;
    +918            self.inflight_longterm += delta;
    +919            if self.round_start {
    +920                self.raise_inflight_long_term_slope();
    +921            }
    +922        }
    +923    }
    +924
    +925    /// equivalent to BBRAdvanceMaxBwFilter <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.6>
    +926    fn advance_max_bw_filter(&mut self) {
    +927        self.cycle_count = self.cycle_count.saturating_add(1);
    +928    }
    +929
    +930    /// equivalent to BBRAdaptLongTermModel <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.6-8>
    +931    fn adapt_long_term_model(&mut self) {
    +932        if self.ack_phase == AckPhase::ProbeStarting && self.round_start {
    +933            self.ack_phase = AckPhase::ProbeFeedback;
    +934        }
    +935        if self.ack_phase == AckPhase::ProbeStopping
    +936            && self.round_start
    +937            && let BbrState::ProbeBw(_) = self.state
    +938            && let Some(rate_sample) = self.rs
    +939            && !rate_sample.is_app_limited
    +940        {
    +941            self.advance_max_bw_filter();
    +942        }
    +943        if !self.is_inflight_too_high() {
    +944            if self.inflight_longterm == u64::MAX {
    +945                return;
    +946            }
    +947            if let Some(rate_sample) = self.rs
    +948                && rate_sample.tx_in_flight > self.inflight_longterm
    +949            {
    +950                self.inflight_longterm = rate_sample.tx_in_flight;
    +951            }
    +952            if self.state == BbrState::ProbeBw(ProbeBwSubstate::Up) {
    +953                self.probe_inflight_long_term_upward();
    +954            }
    +955        }
    +956    }
    +957
    +958    /// equivalent to BBRIsTimeToCruise <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.6-8>
    +959    fn maybe_update_budget_and_time_to_cruise(&mut self) -> bool {
    +960        if self.inflight > self.inflight_with_headroom() {
    +961            return false;
    +962        }
    +963        if self.inflight > self.get_inflight(1.0) {
    +964            return false;
    +965        }
    +966        true
    +967    }
    +968
    +969    /// equivalent to BBRStartProbeBW_CRUISE <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.4.4-4>
    +970    fn start_probe_bw_cruise(&mut self) {
    +971        self.state = BbrState::ProbeBw(ProbeBwSubstate::Cruise);
    +972        self.pacing_gain = self.default_pacing_gain;
    +973        self.cwnd_gain = self.default_cwnd_gain;
    +974    }
    +975
    +976    /// equivalent to BBRResetShortTermModel <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.10.3-8>
    +977    fn reset_short_term_model(&mut self) {
    +978        self.bw_shortterm = f64::INFINITY;
    +979        self.inflight_shortterm = u64::MAX;
    +980    }
    +981
    +982    /// equivalent to BBRInitLowerBounds <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.10.3-8>
    +983    fn init_lower_bounds(&mut self) {
    +984        if self.bw_shortterm == f64::INFINITY {
    +985            self.bw_shortterm = self.max_bw;
    +986        }
    +987        if self.inflight_shortterm == u64::MAX {
    +988            self.inflight_shortterm = self.cwnd;
    +989        }
    +990    }
    +991
    +992    /// equivalent to BBRLossLowerBounds <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.10.3-8>
    +993    fn loss_lower_bounds(&mut self) {
    +994        // gives max of both f64
    +995        self.bw_shortterm = [self.bw_latest, BETA * self.bw_shortterm]
    +996            .iter()
    +997            .copied()
    +998            .fold(f64::NAN, f64::max);
    +999        self.inflight_shortterm = max(
    +1000            self.inflight_latest,
    +1001            (BETA * self.inflight_shortterm as f64) as u64,
    +1002        );
    +1003    }
    +1004
    +1005    /// equivalent to BBRBoundBWForModel <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.10.3-8>
    +1006    fn bound_bw_for_model(&mut self) {
    +1007        // gives min of both f64
    +1008        self.bw = [self.max_bw, self.bw_shortterm]
    +1009            .iter()
    +1010            .copied()
    +1011            .fold(f64::NAN, f64::min);
    +1012    }
    +1013
    +1014    /// equivalent to BBRStartProbeBW_REFILL <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.6-4>
    +1015    fn start_probe_bw_refill(&mut self) {
    +1016        self.reset_short_term_model();
    +1017        self.bw_probe_up_rounds = 0;
    +1018        self.bw_probe_up_acks = 0;
    +1019        self.ack_phase = AckPhase::Refilling;
    +1020        self.start_round();
    +1021        self.cwnd_gain = self.default_cwnd_gain;
    +1022        self.pacing_gain = self.default_pacing_gain;
    +1023        self.state = BbrState::ProbeBw(ProbeBwSubstate::Refill);
    +1024    }
    +1025
    +1026    /// equivalent to BBRStartProbeBW_UP <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.6-4>
    +1027    fn start_probe_bw_up(&mut self) {
    +1028        self.ack_phase = AckPhase::ProbeStarting;
    +1029        self.start_round();
    +1030        self.reset_full_bw();
    +1031        if let Some(rate_sample) = self.rs {
    +1032            self.full_bw = rate_sample.delivery_rate;
    +1033        }
    +1034        self.state = BbrState::ProbeBw(ProbeBwSubstate::Up);
    +1035        self.pacing_gain = self.probe_bw_up_pacing_gain;
    +1036        self.cwnd_gain = self.probe_bw_up_cwnd_gain;
    +1037        self.raise_inflight_long_term_slope();
    +1038    }
    +1039
    +1040    /// equivalent to BBREnterProbeRTT <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.4.3-4>
    +1041    fn enter_probe_rtt(&mut self) {
    +1042        self.state = BbrState::ProbeRtt;
    +1043        self.pacing_gain = self.default_pacing_gain;
    +1044        self.cwnd_gain = self.probe_rtt_cwnd_gain;
    +1045    }
    +1046
    +1047    /// equivalent to BBRHandleRestartFromIdle <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.4.1>
    +1048    fn handle_restart_from_idle(&mut self, now: Instant) {
    +1049        if self.inflight == 0 && self.app_limited != 0 {
    +1050            self.idle_restart = true;
    +1051            self.extra_acked_interval_start = Some(now);
    +1052            match self.state {
    +1053                BbrState::ProbeBw(_) => {
    +1054                    self.set_pacing_rate_with_gain(1.0);
    +1055                }
    +1056                BbrState::ProbeRtt => {
    +1057                    self.check_probe_rtt_done(now);
    +1058                }
    +1059                _ => {}
    +1060            }
    +1061        }
    +1062    }
    +1063
    +1064    /// equivalent to BBRUpdateProbeBWCyclePhase <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.6-6>
    +1065    fn update_probe_bw_cycle_phase(&mut self, now: Instant) {
    +1066        if !self.full_bw_reached {
    +1067            return;
    +1068        }
    +1069        self.adapt_long_term_model();
    +1070        let state = self.state;
    +1071        match state {
    +1072            BbrState::ProbeBw(ProbeBwSubstate::Down) => {
    +1073                if self.maybe_enter_probe_bw_refill(now) {
    +1074                    return;
    +1075                }
    +1076                if self.maybe_update_budget_and_time_to_cruise() {
    +1077                    self.start_probe_bw_cruise();
    +1078                }
    +1079            }
    +1080            BbrState::ProbeBw(ProbeBwSubstate::Cruise) if self.maybe_enter_probe_bw_refill(now) => {
    +1081            }
    +1082            BbrState::ProbeBw(ProbeBwSubstate::Refill) if self.round_start => {
    +1083                self.bw_probe_samples = true;
    +1084                self.start_probe_bw_up();
    +1085            }
    +1086            BbrState::ProbeBw(ProbeBwSubstate::Up) if self.maybe_go_down() => {
    +1087                self.start_probe_bw_down(now);
    +1088            }
    +1089            _ => {}
    +1090        }
    +1091    }
    +1092
    +1093    /// equivalent to BBRUpdateLatestDeliverySignals <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.10.3-8>
    +1094    fn update_latest_delivery_signals(&mut self) {
    +1095        self.loss_round_start = false;
    +1096        if let Some(rate_sample) = self.rs {
    +1097            self.bw_latest = [self.bw_latest, rate_sample.delivery_rate]
    +1098                .iter()
    +1099                .copied()
    +1100                .fold(f64::NAN, f64::max);
    +1101            self.inflight_latest = max(self.inflight_latest, rate_sample.delivered);
    +1102
    +1103            if rate_sample.prior_delivered >= self.loss_round_delivered {
    +1104                self.loss_round_delivered = self.delivered;
    +1105                self.loss_round_start = true;
    +1106            }
    +1107        }
    +1108    }
    +1109
    +1110    /// equivalent to BBRAdaptLowerBoundsFromCongestion <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.10.3-8>
    +1111    fn adapt_lower_bounds_from_congestion(&mut self) {
    +1112        match self.state {
    +1113            BbrState::ProbeBw(ProbeBwSubstate::Refill)
    +1114            | BbrState::ProbeBw(ProbeBwSubstate::Up)
    +1115            | BbrState::Startup => {}
    +1116            _ => {
    +1117                if self.loss_in_round {
    +1118                    self.init_lower_bounds();
    +1119                    self.loss_lower_bounds();
    +1120                }
    +1121            }
    +1122        }
    +1123    }
    +1124
    +1125    /// equivalent to BBRUpdateMaxBw <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.5>
    +1126    fn update_max_bw(&mut self, p: BbrPacket) {
    +1127        self.update_round(p);
    +1128        if let Some(rate_sample) = self.rs
    +1129            && rate_sample.delivery_rate > 0.0
    +1130            && (rate_sample.delivery_rate >= self.max_bw || !rate_sample.is_app_limited)
    +1131        {
    +1132            self.max_bw_filter
    +1133                .update_max(self.cycle_count, rate_sample.delivery_rate.round() as u64);
    +1134
    +1135            self.max_bw = self.max_bw_filter.get_max() as f64;
    +1136        }
    +1137    }
    +1138
    +1139    /// equivalent to BBRUpdateCongestionSignals <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.10.3-8>
    +1140    fn update_congestion_signals(&mut self, p: BbrPacket) {
    +1141        self.update_max_bw(p);
    +1142        if !self.loss_round_start {
    +1143            return;
    +1144        }
    +1145        self.adapt_lower_bounds_from_congestion();
    +1146        self.loss_in_round = false;
    +1147    }
    +1148
    +1149    /// equivalent to BBRUpdateACKAggregation <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.9>
    +1150    fn update_ack_aggregation(&mut self, now: Instant) {
    +1151        let interval;
    +1152        if let Some(extra_acked_interval_start) = self.extra_acked_interval_start {
    +1153            interval = now - extra_acked_interval_start;
    +1154        } else {
    +1155            interval = Duration::from_secs(0);
    +1156        }
    +1157        let mut expected_delivered = (self.bw * interval.as_secs_f64()) as u64;
    +1158        if self.extra_acked_delivered <= expected_delivered {
    +1159            self.extra_acked_delivered = 0;
    +1160            self.extra_acked_interval_start = Some(now);
    +1161            expected_delivered = 0;
    +1162        }
    +1163        if let Some(rate_sample) = self.rs {
    +1164            self.extra_acked_delivered += rate_sample.newly_acked;
    +1165        }
    +1166
    +1167        let mut extra = self
    +1168            .extra_acked_delivered
    +1169            .saturating_sub(expected_delivered);
    +1170        extra = min(extra, self.cwnd);
    +1171        if self.full_bw_reached {
    +1172            self.extra_acked_filter.update_max(self.round_count, extra);
    +1173            self.extra_acked = self.extra_acked_filter.get_max();
    +1174        } else {
    +1175            self.extra_acked = extra; // In startup, just remember 1 round
    +1176        }
    +1177    }
    +1178
    +1179    /// equivalent to BBRCheckFullBWReached <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.1.2-6>
    +1180    fn check_full_bw_reached(&mut self) {
    +1181        if self.full_bw_now || !self.round_start {
    +1182            return;
    +1183        }
    +1184        if let Some(rate_sample) = self.rs {
    +1185            if rate_sample.is_app_limited {
    +1186                return;
    +1187            }
    +1188            if rate_sample.delivery_rate >= self.full_bw * FULL_BW_GROWTH {
    +1189                self.reset_full_bw();
    +1190                self.full_bw = rate_sample.delivery_rate;
    +1191                return;
    +1192            }
    +1193        }
    +1194        self.full_bw_count += 1;
    +1195        self.full_bw_now = self.full_bw_count >= MAX_FULL_BW_COUNT;
    +1196        if self.full_bw_now {
    +1197            self.full_bw_reached = true;
    +1198        }
    +1199    }
    +1200
    +1201    /// equivalent to BBREnterDrain <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.2>
    +1202    fn enter_drain(&mut self) {
    +1203        self.state = BbrState::Drain;
    +1204        self.pacing_gain = self.drain_pacing_gain;
    +1205        self.cwnd_gain = self.default_cwnd_gain;
    +1206        self.drain_start_round = self.round_count;
    +1207    }
    +1208
    +1209    /// equivalent to BBRCheckStartupDone <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.1.1-6>
    +1210    fn check_startup_done(&mut self) {
    +1211        self.check_startup_high_loss();
    +1212        if self.state == BbrState::Startup && self.full_bw_reached {
    +1213            self.enter_drain();
    +1214        }
    +1215    }
    +1216
    +1217    /// equivalent to BBRCheckDrainDone <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.2-3>
    +1218    fn check_drain_done(&mut self, now: Instant) {
    +1219        if self.state == BbrState::Drain
    +1220            && (self.inflight <= self.get_inflight(1.0)
    +1221                || self.round_count > self.drain_start_round + 3)
    +1222        {
    +1223            self.enter_probe_bw(now);
    +1224        }
    +1225    }
    +1226
    +1227    /// equivalent to BBRUpdateMinRTT <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.4.3>
    +1228    fn update_min_rtt(&mut self, now: Instant) {
    +1229        if let Some(probe_rtt_min_stamp) = self.probe_rtt_min_stamp {
    +1230            self.probe_rtt_expired = now
    +1231                > probe_rtt_min_stamp
    +1232                    .checked_add(self.probe_rtt_interval)
    +1233                    .unwrap_or(probe_rtt_min_stamp);
    +1234        } else {
    +1235            self.probe_rtt_expired = true;
    +1236        }
    +1237        if let Some(rate_sample) = self.rs
    +1238            && rate_sample.rtt >= Duration::from_secs(0)
    +1239            && (rate_sample.rtt < self.probe_rtt_min_delay || self.probe_rtt_expired)
    +1240        {
    +1241            self.probe_rtt_min_delay = rate_sample.rtt;
    +1242            self.probe_rtt_min_stamp = Some(now);
    +1243        }
    +1244
    +1245        let min_rtt_expired;
    +1246        if let Some(min_rtt_stamp) = self.min_rtt_stamp {
    +1247            min_rtt_expired = now
    +1248                > min_rtt_stamp
    +1249                    .checked_add(Duration::from_secs(MIN_RTT_FILTER_LEN))
    +1250                    .unwrap_or(min_rtt_stamp);
    +1251        } else {
    +1252            min_rtt_expired = true;
    +1253        }
    +1254        if self.probe_rtt_min_delay < self.min_rtt || min_rtt_expired {
    +1255            self.min_rtt = self.probe_rtt_min_delay;
    +1256            self.min_rtt_stamp = self.probe_rtt_min_stamp;
    +1257        }
    +1258    }
    +1259
    +1260    /// equivalent to BBRHandleProbeRTT <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.4.3-4>
    +1261    fn handle_probe_rtt(&mut self, now: Instant) {
    +1262        if self.probe_rtt_done_stamp.is_none() && self.inflight <= self.probe_rtt_cwnd() {
    +1263            self.probe_rtt_done_stamp =
    +1264                Some(now.checked_add(self.probe_rtt_duration).unwrap_or(now));
    +1265            self.probe_rtt_round_done = false;
    +1266            self.start_round();
    +1267        } else if self.probe_rtt_done_stamp.is_some() {
    +1268            if self.round_start {
    +1269                self.probe_rtt_round_done = true;
    +1270            }
    +1271            if self.probe_rtt_round_done {
    +1272                self.check_probe_rtt_done(now);
    +1273            }
    +1274        }
    +1275    }
    +1276
    +1277    /// equivalent to BBRCheckProbeRTT <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.4.3-4>
    +1278    fn check_probe_rtt(&mut self, now: Instant) {
    +1279        match self.state {
    +1280            BbrState::ProbeRtt => {
    +1281                self.handle_probe_rtt(now);
    +1282            }
    +1283            _ => {
    +1284                if self.probe_rtt_expired && !self.idle_restart {
    +1285                    self.enter_probe_rtt();
    +1286                    self.save_cwnd();
    +1287                    self.probe_rtt_done_stamp = None;
    +1288                    self.ack_phase = AckPhase::ProbeStopping;
    +1289                    self.start_round();
    +1290                }
    +1291            }
    +1292        }
    +1293        if let Some(rate_sample) = self.rs
    +1294            && rate_sample.delivered > 0
    +1295        {
    +1296            self.idle_restart = false;
    +1297        }
    +1298    }
    +1299
    +1300    /// equivalent to BBRAdvanceLatestDeliverySignals <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.10.3-8>
    +1301    fn advance_latest_delivery_signals(&mut self) {
    +1302        if self.loss_round_start
    +1303            && let Some(rate_sample) = self.rs
    +1304        {
    +1305            self.bw_latest = rate_sample.delivery_rate;
    +1306            self.inflight_latest = rate_sample.delivered;
    +1307        }
    +1308    }
    +1309
    +1310    /// equivalent to BBRUpdateModelAndState <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.2.3>
    +1311    fn update_model_and_state(&mut self, p: BbrPacket, now: Instant) {
    +1312        self.update_latest_delivery_signals();
    +1313        self.reset_congestion_signals();
    +1314        self.update_congestion_signals(p);
    +1315        self.update_ack_aggregation(now);
    +1316        self.check_full_bw_reached();
    +1317        self.check_startup_done();
    +1318        self.check_drain_done(now);
    +1319        self.update_probe_bw_cycle_phase(now);
    +1320        self.update_min_rtt(now);
    +1321        self.check_probe_rtt(now);
    +1322        self.advance_latest_delivery_signals();
    +1323        self.bound_bw_for_model();
    +1324    }
    +1325
    +1326    /// equivalent to BBRSetPacingRate <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.2-7>
    +1327    fn set_pacing_rate(&mut self) {
    +1328        self.set_pacing_rate_with_gain(self.pacing_gain);
    +1329    }
    +1330
    +1331    /// equivalent to BBRSetSendQuantum <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.3>
    +1332    /// this version is based on a version of bbr2 from quiche
    +1333    fn set_send_quantum(&mut self) {
    +1334        self.send_quantum = match self.pacing_rate {
    +1335            rate if rate < PACING_RATE_1_2MBPS => MAX_DATAGRAM_SIZE,
    +1336            rate if rate < PACING_RATE_24MBPS => 2 * MAX_DATAGRAM_SIZE,
    +1337            _ => min((self.pacing_rate / 1000.0) as u64, HIGH_PACE_MAX_QUANTUM),
    +1338        };
    +1339    }
    +1340
    +1341    /// equivalent to BBRBoundCwndForModel <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.4.7>
    +1342    fn bound_cwnd_for_model(&mut self) {
    +1343        let mut cap = u64::MAX;
    +1344        match self.state {
    +1345            BbrState::ProbeRtt => {
    +1346                cap = self.inflight_with_headroom();
    +1347            }
    +1348            BbrState::ProbeBw(ProbeBwSubstate::Cruise) => {
    +1349                cap = self.inflight_with_headroom();
    +1350            }
    +1351            BbrState::ProbeBw(_) => {
    +1352                cap = self.inflight_longterm;
    +1353            }
    +1354            _ => {}
    +1355        }
    +1356        cap = min(cap, self.inflight_shortterm);
    +1357        cap = max(cap, self.min_pipe_cwnd);
    +1358        self.cwnd = min(self.cwnd, cap);
    +1359    }
    +1360
    +1361    /// equivalent to BBRSetCwnd <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.4.6>
    +1362    fn set_cwnd(&mut self) {
    +1363        self.update_max_inflight();
    +1364        if self.full_bw_reached {
    +1365            if let Some(rate_sample) = self.rs {
    +1366                self.cwnd = min(self.cwnd + rate_sample.newly_acked, self.max_inflight);
    +1367            } else {
    +1368                self.cwnd = min(self.cwnd, self.max_inflight);
    +1369            }
    +1370        } else if (self.cwnd < self.max_inflight || self.delivered < self.initial_cwnd)
    +1371            && let Some(rate_sample) = self.rs
    +1372        {
    +1373            self.cwnd += rate_sample.newly_acked;
    +1374        }
    +1375        self.cwnd = max(self.cwnd, self.min_pipe_cwnd);
    +1376        self.bound_cwnd_for_probe_rtt();
    +1377        self.bound_cwnd_for_model();
    +1378    }
    +1379
    +1380    /// equivalent to BBRUpdateControlParameters <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.2.3>
    +1381    fn update_control_parameters(&mut self) {
    +1382        self.set_pacing_rate();
    +1383        self.set_send_quantum();
    +1384        self.set_cwnd();
    +1385    }
    +1386
    +1387    /// equivalent to IsNewestPacket <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-4.1.2.3-3>
    +1388    fn is_newest_packet(&self, send_time: Instant, end_seq: u64) -> bool {
    +1389        if let Some(first_send_time) = self.first_send_time {
    +1390            if send_time > first_send_time {
    +1391                return true;
    +1392            }
    +1393            if let Some(rate_sample) = self.rs
    +1394                && end_seq > rate_sample.last_end_seq
    +1395            {
    +1396                return true;
    +1397            }
    +1398        }
    +1399        false
    +1400    }
    +1401
    +1402    /// equivalent to BBRHandleLostPacket <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.10.2-11>
    +1403    fn process_lost_packet(&mut self, lost_bytes: u64, packet_index: usize, now: Instant) {
    +1404        let p = self.packets[packet_index];
    +1405        self.note_loss();
    +1406        if !self.bw_probe_samples {
    +1407            self.packets.remove(packet_index);
    +1408            return;
    +1409        }
    +1410        if let Some(mut rate_sample) = self.rs {
    +1411            rate_sample.newly_lost += lost_bytes;
    +1412            rate_sample.tx_in_flight = p.tx_in_flight;
    +1413            rate_sample.lost = self.lost.saturating_sub(p.lost);
    +1414            rate_sample.is_app_limited = p.is_app_limited;
    +1415            if self.is_inflight_too_high() {
    +1416                rate_sample.tx_in_flight = self.inflight_at_loss(p.size as u64);
    +1417                self.handle_inflight_too_high(now);
    +1418            }
    +1419            self.rs = Some(rate_sample);
    +1420        }
    +1421        self.packets.remove(packet_index);
    +1422    }
    +1423}
    +1424impl Controller for Bbr3 {
    +1425    fn on_packet_sent(&mut self, now: Instant, bytes: u16, pn: u64) {
    +1426        if self.inflight == 0 {
    +1427            self.first_send_time = Some(now);
    +1428            self.delivered_time = Some(now);
    +1429        }
    +1430        let added_bytes = bytes as u64;
    +1431        self.inflight += added_bytes;
    +1432        self.packets.push_back(BbrPacket {
    +1433            delivered: self.delivered,
    +1434            delivered_time: self.delivered_time.unwrap_or(now),
    +1435            first_send_time: self.first_send_time.unwrap_or(now),
    +1436            send_time: now,
    +1437            is_app_limited: self.app_limited != 0,
    +1438            tx_in_flight: self.inflight,
    +1439            packet_number: pn,
    +1440            size: bytes,
    +1441            lost: self.lost,
    +1442            acknowledged: false,
    +1443            stale: false,
    +1444            round_count: self.round_count,
    +1445        });
    +1446        self.handle_restart_from_idle(now);
    +1447    }
    +1448
    +1449    fn on_ack(
    +1450        &mut self,
    +1451        now: Instant,
    +1452        sent: Instant,
    +1453        bytes: u64,
    +1454        pn: u64,
    +1455        _app_limited: bool,
    +1456        rtt: &RttEstimator,
    +1457    ) {
    +1458        if let Some(mut rate_sample) = self.rs {
    +1459            rate_sample.newly_acked += bytes;
    +1460            self.rs = Some(rate_sample);
    +1461            self.delivered += bytes;
    +1462            self.delivered_time = Some(now);
    +1463        }
    +1464        let p_index_result = self.packets.binary_search_by_key(&pn, |p| p.packet_number);
    +1465        let is_newest_packet = self.is_newest_packet(sent, pn);
    +1466        if let Ok(p_index) = p_index_result
    +1467            && let Some(p) = self.packets.get_mut(p_index)
    +1468        {
    +1469            p.acknowledged = true;
    +1470            if let Some(mut rate_sample) = self.rs {
    +1471                rate_sample.rtt = now - p.send_time;
    +1472                if is_newest_packet {
    +1473                    self.srtt = rtt.get();
    +1474                    rate_sample.prior_delivered = p.delivered;
    +1475                    rate_sample.prior_time = p.delivered_time;
    +1476                    rate_sample.is_app_limited = p.is_app_limited;
    +1477                    rate_sample.tx_in_flight = p.tx_in_flight;
    +1478                    rate_sample.send_elapsed = p.send_time - p.first_send_time;
    +1479                    rate_sample.ack_elapsed = self.delivered_time.unwrap_or(now) - p.delivered_time;
    +1480                    rate_sample.last_end_seq = pn;
    +1481                    self.first_send_time = Some(p.send_time);
    +1482                    rate_sample.last_packet = *p;
    +1483                    self.rs = Some(rate_sample);
    +1484                    self.update_model_and_state(rate_sample.last_packet, now);
    +1485                    self.update_control_parameters();
    +1486                }
    +1487            } else {
    +1488                let rate_sample = BbrRateSample {
    +1489                    rtt: rtt.get(),
    +1490                    prior_time: p.delivered_time,
    +1491                    interval: Duration::ZERO,
    +1492                    delivery_rate: 0.0,
    +1493                    is_app_limited: p.is_app_limited,
    +1494                    delivered: 0,
    +1495                    prior_delivered: p.delivered,
    +1496                    tx_in_flight: p.tx_in_flight,
    +1497                    send_elapsed: p.send_time - p.first_send_time,
    +1498                    ack_elapsed: self.delivered_time.unwrap_or(now) - p.delivered_time,
    +1499                    newly_acked: bytes,
    +1500                    newly_lost: 0,
    +1501                    lost: 0,
    +1502                    last_end_seq: pn,
    +1503                    last_packet: *p,
    +1504                };
    +1505                self.rs = Some(rate_sample);
    +1506                self.first_send_time = Some(p.send_time);
    +1507                self.srtt = rate_sample.rtt;
    +1508                self.update_model_and_state(rate_sample.last_packet, now);
    +1509                self.update_control_parameters();
    +1510            }
    +1511        }
    +1512    }
    +1513
    +1514    fn on_end_acks(
    +1515        &mut self,
    +1516        _now: Instant,
    +1517        in_flight: u64,
    +1518        app_limited: bool,
    +1519        largest_packet_num_acked: Option<u64>,
    +1520    ) {
    +1521        self.inflight = in_flight;
    +1522        if let Some(largest_packet_num) = largest_packet_num_acked {
    +1523            if self.app_limited != 0 && largest_packet_num > self.app_limited {
    +1524                self.app_limited = 0;
    +1525            } else if app_limited {
    +1526                self.app_limited = self.app_limited.max(largest_packet_num);
    +1527            }
    +1528            self.packets.retain(|&p| !p.stale);
    +1529            for p in self.packets.iter_mut() {
    +1530                if p.acknowledged || self.round_count - p.round_count > ROUND_COUNT_WINDOW {
    +1531                    p.stale = true;
    +1532                }
    +1533            }
    +1534            if let Some(mut rate_sample) = self.rs {
    +1535                if rate_sample.prior_delivered == 0 {
    +1536                    return;
    +1537                }
    +1538                rate_sample.interval = max(rate_sample.send_elapsed, rate_sample.ack_elapsed);
    +1539                rate_sample.delivered = self.delivered.saturating_sub(rate_sample.prior_delivered);
    +1540                // ignore this condition on an initially high min rtt as per <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-4.1.2.3-5>
    +1541                if rate_sample.interval < self.min_rtt
    +1542                    && self.min_rtt != Duration::from_secs(u64::MAX)
    +1543                {
    +1544                    return;
    +1545                }
    +1546                if rate_sample.interval != Duration::ZERO {
    +1547                    rate_sample.delivery_rate =
    +1548                        rate_sample.delivered as f64 / rate_sample.interval.as_secs_f64();
    +1549                }
    +1550                if rate_sample.delivered >= self.cwnd {
    +1551                    self.is_cwnd_limited = true;
    +1552                }
    +1553                self.rs = Some(rate_sample);
    +1554                rate_sample.newly_acked = 0;
    +1555                rate_sample.lost = 0;
    +1556                rate_sample.newly_lost = 0;
    +1557                self.rs = Some(rate_sample);
    +1558            }
    +1559        }
    +1560    }
    +1561
    +1562    fn on_congestion_event(
    +1563        &mut self,
    +1564        now: Instant,
    +1565        _sent: Instant,
    +1566        is_persistent_congestion: bool,
    +1567        is_ecn: bool,
    +1568        lost_bytes: u64,
    +1569        largest_lost_pn: u64,
    +1570    ) {
    +1571        // only process ecn here, regular packet loss is detected per packet in on_packet_lost.
    +1572        if is_ecn {
    +1573            self.lost += lost_bytes;
    +1574            let p_index_result = self
    +1575                .packets
    +1576                .binary_search_by_key(&largest_lost_pn, |p| p.packet_number);
    +1577            if let Ok(p_index) = p_index_result {
    +1578                self.process_lost_packet(lost_bytes, p_index, now);
    +1579            }
    +1580            if is_persistent_congestion {
    +1581                self.cwnd = self.min_pipe_cwnd;
    +1582            }
    +1583        }
    +1584    }
    +1585
    +1586    fn on_packet_lost(&mut self, lost_bytes: u16, pn: u64, now: Instant) {
    +1587        let lost_bytes_64 = lost_bytes as u64;
    +1588        self.lost += lost_bytes_64;
    +1589        let p_index_result = self.packets.binary_search_by_key(&pn, |p| p.packet_number);
    +1590        if let Ok(p_index) = p_index_result {
    +1591            self.process_lost_packet(lost_bytes_64, p_index, now);
    +1592        }
    +1593    }
    +1594
    +1595    /// equivalent to BBRHandleSpuriousLossDetection: <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.11.2>
    +1596    fn on_spurious_congestion_event(&mut self) {
    +1597        self.loss_in_round = false;
    +1598        self.reset_full_bw();
    +1599        self.bw_shortterm = [self.bw_shortterm, self.undo_bw_shortterm]
    +1600            .iter()
    +1601            .copied()
    +1602            .fold(f64::NAN, f64::max);
    +1603        self.inflight_shortterm = max(self.inflight_shortterm, self.undo_inflight_shortterm);
    +1604        self.inflight_longterm = max(self.inflight_longterm, self.undo_inflight_longterm);
    +1605        if self.state != BbrState::ProbeRtt && self.state != self.undo_state {
    +1606            if self.undo_state == BbrState::Startup {
    +1607                self.enter_startup();
    +1608            } else if self.undo_state == BbrState::ProbeBw(ProbeBwSubstate::Up) {
    +1609                self.start_probe_bw_up();
    +1610            }
    +1611        }
    +1612    }
    +1613
    +1614    fn on_mtu_update(&mut self, new_mtu: u16) {
    +1615        self.smss = min(
    +1616            max(MIN_MAX_DATAGRAM_SIZE, new_mtu) as u64,
    +1617            MAX_DATAGRAM_SIZE,
    +1618        );
    +1619        self.set_cwnd();
    +1620    }
    +1621
    +1622    fn on_ack_frequency_update(
    +1623        &mut self,
    +1624        ack_eliciting_threshold: u64,
    +1625        requested_max_ack_delay: Duration,
    +1626    ) {
    +1627        self.ack_eliciting_threshold = ack_eliciting_threshold;
    +1628        self.max_ack_delay = requested_max_ack_delay;
    +1629    }
    +1630
    +1631    fn window(&self) -> u64 {
    +1632        self.cwnd
    +1633    }
    +1634
    +1635    fn metrics(&self) -> ControllerMetrics {
    +1636        ControllerMetrics {
    +1637            congestion_window: self.window(),
    +1638            ssthresh: None,
    +1639            pacing_rate: Some(self.pacing_rate.round() as u64),
    +1640            send_quantum: Some(self.send_quantum),
    +1641        }
    +1642    }
    +1643
    +1644    fn clone_box(&self) -> Box<dyn Controller> {
    +1645        Box::new(self.clone())
    +1646    }
    +1647
    +1648    fn initial_window(&self) -> u64 {
    +1649        self.initial_cwnd
    +1650    }
    +1651
    +1652    fn into_any(self: Box<Self>) -> Box<dyn Any> {
    +1653        self
    +1654    }
    +1655}
    +1656
    +1657/// Configuration for the `Bbr3` congestion controller
    +1658///
    +1659/// Different pacing_gains can be set to modify the multiplier used to
    +1660/// increase the sending rates.
    +1661/// Different cwnd_gains can be set to modify the multiplier used to increase
    +1662/// the congestion windows.
    +1663/// All of these parameters are specific to different states of the algorithm: see `BbrState`
    +1664/// `pacing_margin_percent` is used to set a margin when calculating the `pacing_rate` in order
    +1665/// to not send at 100% capacity when calculating pacing.
    +1666#[derive(Debug, Clone)]
    +1667pub struct Bbr3Config {
    +1668    initial_window: u64,
    +1669    probe_rng_seed: Option<[u8; 16]>,
    +1670    startup_pacing_gain: Option<f64>,
    +1671    default_pacing_gain: Option<f64>,
    +1672    probe_bw_down_pacing_gain: Option<f64>,
    +1673    probe_bw_up_pacing_gain: Option<f64>,
    +1674    probe_bw_up_cwnd_gain: Option<f64>,
    +1675    probe_rtt_cwnd_gain: Option<f64>,
    +1676    drain_pacing_gain: Option<f64>,
    +1677    pacing_margin_percent: Option<f64>,
    +1678    default_cwnd_gain: Option<f64>,
    +1679}
    +1680
    +1681impl Bbr3Config {
    +1682    /// Default limit on the amount of outstanding data in bytes.
    +1683    ///
    +1684    /// Recommended value: `min(10 * max_datagram_size, max(2 * max_datagram_size, 14720))`
    +1685    pub fn initial_window(&mut self, value: u64) -> &mut Self {
    +1686        self.initial_window = value;
    +1687        self
    +1688    }
    +1689}
    +1690
    +1691impl Default for Bbr3Config {
    +1692    fn default() -> Self {
    +1693        Self {
    +1694            initial_window: 14720.clamp(2 * MAX_DATAGRAM_SIZE, 10 * MAX_DATAGRAM_SIZE),
    +1695            probe_rng_seed: None,
    +1696            startup_pacing_gain: None,
    +1697            default_pacing_gain: None,
    +1698            probe_bw_down_pacing_gain: None,
    +1699            probe_bw_up_pacing_gain: None,
    +1700            probe_bw_up_cwnd_gain: None,
    +1701            probe_rtt_cwnd_gain: None,
    +1702            drain_pacing_gain: None,
    +1703            pacing_margin_percent: None,
    +1704            default_cwnd_gain: None,
    +1705        }
    +1706    }
    +1707}
    +1708
    +1709impl ControllerFactory for Bbr3Config {
    +1710    fn build(self: Arc<Self>, _now: Instant, current_mtu: u16) -> Box<dyn Controller> {
    +1711        Box::new(Bbr3::new(self, current_mtu))
    +1712    }
    +1713}
    +1714
    +1715#[cfg(test)]
    +1716mod test {
    +1717    use super::*;
    +1718
    +1719    #[test]
    +1720    fn test_probe_rng() {
    +1721        let seed: [u8; 16] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
    +1722        let config = Bbr3Config {
    +1723            initial_window: 14720.clamp(2 * MAX_DATAGRAM_SIZE, 10 * MAX_DATAGRAM_SIZE),
    +1724            probe_rng_seed: Some(seed),
    +1725            startup_pacing_gain: None,
    +1726            default_pacing_gain: None,
    +1727            probe_bw_down_pacing_gain: None,
    +1728            probe_bw_up_pacing_gain: None,
    +1729            probe_bw_up_cwnd_gain: None,
    +1730            probe_rtt_cwnd_gain: None,
    +1731            drain_pacing_gain: None,
    +1732            pacing_margin_percent: None,
    +1733            default_cwnd_gain: None,
    +1734        };
    +1735        let mut bbr3 = Bbr3::new(Arc::new(config), 2500);
    +1736        bbr3.pick_probe_wait();
    +1737        assert_eq!(bbr3.rounds_since_bw_probe, 1);
    +1738        assert_eq!(bbr3.bw_probe_wait, Duration::from_millis(2652));
    +1739        bbr3.pick_probe_wait();
    +1740        assert_eq!(bbr3.rounds_since_bw_probe, 1);
    +1741        assert_eq!(bbr3.bw_probe_wait, Duration::from_millis(2570));
    +1742    }
    +1743}
    \ No newline at end of file diff --git a/pr/451/docs/src/noq_proto/congestion/cubic.rs.html b/pr/451/docs/src/noq_proto/congestion/cubic.rs.html index db23f47ef..5a1e6f3b5 100644 --- a/pr/451/docs/src/noq_proto/congestion/cubic.rs.html +++ b/pr/451/docs/src/noq_proto/congestion/cubic.rs.html @@ -107,193 +107,196 @@ 107 now: Instant, 108 sent: Instant, 109 bytes: u64, -110 app_limited: bool, -111 rtt: &RttEstimator, -112 ) { -113 if app_limited -114 || self -115 .state -116 .recovery_start_time -117 .map(|recovery_start_time| sent <= recovery_start_time) -118 .unwrap_or(false) -119 { -120 return; -121 } -122 -123 if self.state.window < self.state.ssthresh { -124 // Slow start -125 self.state.window += bytes; -126 } else { -127 // Congestion avoidance. -128 let ca_start_time; -129 -130 match self.state.recovery_start_time { -131 Some(t) => ca_start_time = t, -132 None => { -133 // When we come here without congestion_event() triggered, -134 // initialize congestion_recovery_start_time, w_max and k. -135 ca_start_time = now; -136 self.state.recovery_start_time = Some(now); -137 -138 self.state.w_max = self.state.window as f64; -139 self.state.k = 0.0; -140 } -141 } -142 -143 let t = now - ca_start_time; -144 -145 // w_cubic(t + rtt) -146 let w_cubic = self.state.w_cubic(t + rtt.get(), self.current_mtu); -147 -148 // w_est(t) -149 let w_est = self.state.w_est(t, rtt.get(), self.current_mtu); -150 -151 let mut cubic_cwnd = self.state.window; -152 -153 if w_cubic < w_est { -154 // TCP friendly region. -155 cubic_cwnd = cmp::max(cubic_cwnd, w_est as u64); -156 } else if cubic_cwnd < w_cubic as u64 { -157 // Concave region or convex region use same increment. -158 let cubic_inc = -159 (w_cubic - cubic_cwnd as f64) / cubic_cwnd as f64 * self.current_mtu as f64; -160 -161 cubic_cwnd += cubic_inc as u64; -162 } -163 -164 // Update the increment and increase cwnd by MSS. -165 self.state.cwnd_inc += cubic_cwnd - self.state.window; -166 -167 // cwnd_inc can be more than 1 MSS in the late stage of max probing. -168 // however RFC9002 §7.3.3 (Congestion Avoidance) limits -169 // the increase of cwnd to 1 max_datagram_size per cwnd acknowledged. -170 if self.state.cwnd_inc >= self.current_mtu { -171 self.state.window += self.current_mtu; -172 self.state.cwnd_inc = 0; -173 } -174 } -175 } -176 -177 fn on_congestion_event( -178 &mut self, -179 now: Instant, -180 sent: Instant, -181 is_persistent_congestion: bool, -182 is_ecn: bool, -183 _lost_bytes: u64, -184 ) { -185 if self -186 .state -187 .recovery_start_time -188 .map(|recovery_start_time| sent <= recovery_start_time) -189 .unwrap_or(false) -190 { -191 return; -192 } -193 -194 // Save state in case this event ends up being spurious -195 if !is_ecn { -196 self.pre_congestion_state = Some(self.state.clone()); -197 } -198 -199 self.state.recovery_start_time = Some(now); +110 _pn: u64, +111 app_limited: bool, +112 rtt: &RttEstimator, +113 ) { +114 if app_limited +115 || self +116 .state +117 .recovery_start_time +118 .map(|recovery_start_time| sent <= recovery_start_time) +119 .unwrap_or(false) +120 { +121 return; +122 } +123 +124 if self.state.window < self.state.ssthresh { +125 // Slow start +126 self.state.window += bytes; +127 } else { +128 // Congestion avoidance. +129 let ca_start_time; +130 +131 match self.state.recovery_start_time { +132 Some(t) => ca_start_time = t, +133 None => { +134 // When we come here without congestion_event() triggered, +135 // initialize congestion_recovery_start_time, w_max and k. +136 ca_start_time = now; +137 self.state.recovery_start_time = Some(now); +138 +139 self.state.w_max = self.state.window as f64; +140 self.state.k = 0.0; +141 } +142 } +143 +144 let t = now - ca_start_time; +145 +146 // w_cubic(t + rtt) +147 let w_cubic = self.state.w_cubic(t + rtt.get(), self.current_mtu); +148 +149 // w_est(t) +150 let w_est = self.state.w_est(t, rtt.get(), self.current_mtu); +151 +152 let mut cubic_cwnd = self.state.window; +153 +154 if w_cubic < w_est { +155 // TCP friendly region. +156 cubic_cwnd = cmp::max(cubic_cwnd, w_est as u64); +157 } else if cubic_cwnd < w_cubic as u64 { +158 // Concave region or convex region use same increment. +159 let cubic_inc = +160 (w_cubic - cubic_cwnd as f64) / cubic_cwnd as f64 * self.current_mtu as f64; +161 +162 cubic_cwnd += cubic_inc as u64; +163 } +164 +165 // Update the increment and increase cwnd by MSS. +166 self.state.cwnd_inc += cubic_cwnd - self.state.window; +167 +168 // cwnd_inc can be more than 1 MSS in the late stage of max probing. +169 // however RFC9002 §7.3.3 (Congestion Avoidance) limits +170 // the increase of cwnd to 1 max_datagram_size per cwnd acknowledged. +171 if self.state.cwnd_inc >= self.current_mtu { +172 self.state.window += self.current_mtu; +173 self.state.cwnd_inc = 0; +174 } +175 } +176 } +177 +178 fn on_congestion_event( +179 &mut self, +180 now: Instant, +181 sent: Instant, +182 is_persistent_congestion: bool, +183 is_ecn: bool, +184 _lost_bytes: u64, +185 _largest_lost_pn: u64, +186 ) { +187 if self +188 .state +189 .recovery_start_time +190 .map(|recovery_start_time| sent <= recovery_start_time) +191 .unwrap_or(false) +192 { +193 return; +194 } +195 +196 // Save state in case this event ends up being spurious +197 if !is_ecn { +198 self.pre_congestion_state = Some(self.state.clone()); +199 } 200 -201 // Fast convergence -202 if (self.state.window as f64) < self.state.w_max { -203 self.state.w_max = self.state.window as f64 * (1.0 + BETA_CUBIC) / 2.0; -204 } else { -205 self.state.w_max = self.state.window as f64; -206 } -207 -208 self.state.ssthresh = cmp::max( -209 (self.state.w_max * BETA_CUBIC) as u64, -210 self.minimum_window(), -211 ); -212 self.state.window = self.state.ssthresh; -213 self.state.k = self.state.cubic_k(self.current_mtu); -214 -215 self.state.cwnd_inc = (self.state.cwnd_inc as f64 * BETA_CUBIC) as u64; +201 self.state.recovery_start_time = Some(now); +202 +203 // Fast convergence +204 if (self.state.window as f64) < self.state.w_max { +205 self.state.w_max = self.state.window as f64 * (1.0 + BETA_CUBIC) / 2.0; +206 } else { +207 self.state.w_max = self.state.window as f64; +208 } +209 +210 self.state.ssthresh = cmp::max( +211 (self.state.w_max * BETA_CUBIC) as u64, +212 self.minimum_window(), +213 ); +214 self.state.window = self.state.ssthresh; +215 self.state.k = self.state.cubic_k(self.current_mtu); 216 -217 if is_persistent_congestion { -218 self.state.recovery_start_time = None; -219 self.state.w_max = self.state.window as f64; -220 -221 // 4.7 Timeout - reduce ssthresh based on BETA_CUBIC -222 self.state.ssthresh = cmp::max( -223 (self.state.window as f64 * BETA_CUBIC) as u64, -224 self.minimum_window(), -225 ); -226 -227 self.state.cwnd_inc = 0; +217 self.state.cwnd_inc = (self.state.cwnd_inc as f64 * BETA_CUBIC) as u64; +218 +219 if is_persistent_congestion { +220 self.state.recovery_start_time = None; +221 self.state.w_max = self.state.window as f64; +222 +223 // 4.7 Timeout - reduce ssthresh based on BETA_CUBIC +224 self.state.ssthresh = cmp::max( +225 (self.state.window as f64 * BETA_CUBIC) as u64, +226 self.minimum_window(), +227 ); 228 -229 self.state.window = self.minimum_window(); -230 } -231 } -232 -233 fn on_spurious_congestion_event(&mut self) { -234 if let Some(prior_state) = self.pre_congestion_state.take() -235 && self.state.window < prior_state.window -236 { -237 self.state = prior_state; -238 } -239 } -240 -241 fn on_mtu_update(&mut self, new_mtu: u16) { -242 self.current_mtu = new_mtu as u64; -243 self.state.window = self.state.window.max(self.minimum_window()); -244 } -245 -246 fn window(&self) -> u64 { -247 self.state.window -248 } -249 -250 fn metrics(&self) -> super::ControllerMetrics { -251 super::ControllerMetrics { -252 congestion_window: self.window(), -253 ssthresh: Some(self.state.ssthresh), -254 pacing_rate: None, -255 } -256 } -257 -258 fn clone_box(&self) -> Box<dyn Controller> { -259 Box::new(self.clone()) -260 } -261 -262 fn initial_window(&self) -> u64 { -263 self.config.initial_window -264 } -265 -266 fn into_any(self: Box<Self>) -> Box<dyn Any> { -267 self -268 } -269} -270 -271/// Configuration for the `Cubic` congestion controller -272#[derive(Debug, Clone)] -273pub struct CubicConfig { -274 initial_window: u64, -275} -276 -277impl CubicConfig { -278 /// Default limit on the amount of outstanding data in bytes. -279 /// -280 /// Recommended value: `min(10 * max_datagram_size, max(2 * max_datagram_size, 14720))` -281 pub fn initial_window(&mut self, value: u64) -> &mut Self { -282 self.initial_window = value; -283 self -284 } -285} -286 -287impl Default for CubicConfig { -288 fn default() -> Self { -289 Self { -290 initial_window: 14720.clamp(2 * BASE_DATAGRAM_SIZE, 10 * BASE_DATAGRAM_SIZE), -291 } -292 } -293} -294 -295impl ControllerFactory for CubicConfig { -296 fn build(self: Arc<Self>, now: Instant, current_mtu: u16) -> Box<dyn Controller> { -297 Box::new(Cubic::new(self, now, current_mtu)) -298 } -299}

    \ No newline at end of file +229 self.state.cwnd_inc = 0; +230 +231 self.state.window = self.minimum_window(); +232 } +233 } +234 +235 fn on_spurious_congestion_event(&mut self) { +236 if let Some(prior_state) = self.pre_congestion_state.take() +237 && self.state.window < prior_state.window +238 { +239 self.state = prior_state; +240 } +241 } +242 +243 fn on_mtu_update(&mut self, new_mtu: u16) { +244 self.current_mtu = new_mtu as u64; +245 self.state.window = self.state.window.max(self.minimum_window()); +246 } +247 +248 fn window(&self) -> u64 { +249 self.state.window +250 } +251 +252 fn metrics(&self) -> super::ControllerMetrics { +253 super::ControllerMetrics { +254 congestion_window: self.window(), +255 ssthresh: Some(self.state.ssthresh), +256 pacing_rate: None, +257 send_quantum: None, +258 } +259 } +260 +261 fn clone_box(&self) -> Box<dyn Controller> { +262 Box::new(self.clone()) +263 } +264 +265 fn initial_window(&self) -> u64 { +266 self.config.initial_window +267 } +268 +269 fn into_any(self: Box<Self>) -> Box<dyn Any> { +270 self +271 } +272} +273 +274/// Configuration for the `Cubic` congestion controller +275#[derive(Debug, Clone)] +276pub struct CubicConfig { +277 initial_window: u64, +278} +279 +280impl CubicConfig { +281 /// Default limit on the amount of outstanding data in bytes. +282 /// +283 /// Recommended value: `min(10 * max_datagram_size, max(2 * max_datagram_size, 14720))` +284 pub fn initial_window(&mut self, value: u64) -> &mut Self { +285 self.initial_window = value; +286 self +287 } +288} +289 +290impl Default for CubicConfig { +291 fn default() -> Self { +292 Self { +293 initial_window: 14720.clamp(2 * BASE_DATAGRAM_SIZE, 10 * BASE_DATAGRAM_SIZE), +294 } +295 } +296} +297 +298impl ControllerFactory for CubicConfig { +299 fn build(self: Arc<Self>, now: Instant, current_mtu: u16) -> Box<dyn Controller> { +300 Box::new(Cubic::new(self, now, current_mtu)) +301 } +302} \ No newline at end of file diff --git a/pr/451/docs/src/noq_proto/congestion/new_reno.rs.html b/pr/451/docs/src/noq_proto/congestion/new_reno.rs.html index 3153b27f9..077af3478 100644 --- a/pr/451/docs/src/noq_proto/congestion/new_reno.rs.html +++ b/pr/451/docs/src/noq_proto/congestion/new_reno.rs.html @@ -46,128 +46,131 @@ 46 _now: Instant, 47 sent: Instant, 48 bytes: u64, -49 app_limited: bool, -50 _rtt: &RttEstimator, -51 ) { -52 if app_limited || sent <= self.recovery_start_time { -53 return; -54 } -55 -56 if self.window < self.ssthresh { -57 // Slow start -58 self.window += bytes; -59 -60 if self.window >= self.ssthresh { -61 // Exiting slow start -62 // Initialize `bytes_acked` for congestion avoidance. The idea -63 // here is that any bytes over `sshthresh` will already be counted -64 // towards the congestion avoidance phase - independent of when -65 // how close to `sshthresh` the `window` was when switching states, -66 // and independent of datagram sizes. -67 self.bytes_acked = self.window - self.ssthresh; -68 } -69 } else { -70 // Congestion avoidance -71 // This implementation uses the method which does not require -72 // floating point math, which also increases the window by 1 datagram -73 // for every round trip. -74 // This mechanism is called Appropriate Byte Counting in -75 // https://tools.ietf.org/html/rfc3465 -76 self.bytes_acked += bytes; -77 -78 if self.bytes_acked >= self.window { -79 self.bytes_acked -= self.window; -80 self.window += self.current_mtu; -81 } -82 } -83 } -84 -85 fn on_congestion_event( -86 &mut self, -87 now: Instant, -88 sent: Instant, -89 is_persistent_congestion: bool, -90 _is_ecn: bool, -91 _lost_bytes: u64, -92 ) { -93 if sent <= self.recovery_start_time { -94 return; -95 } -96 -97 self.recovery_start_time = now; -98 self.window = (self.window as f32 * self.config.loss_reduction_factor) as u64; -99 self.window = self.window.max(self.minimum_window()); -100 self.ssthresh = self.window; -101 -102 if is_persistent_congestion { -103 self.window = self.minimum_window(); -104 } -105 } -106 -107 fn on_mtu_update(&mut self, new_mtu: u16) { -108 self.current_mtu = new_mtu as u64; -109 self.window = self.window.max(self.minimum_window()); -110 } -111 -112 fn window(&self) -> u64 { -113 self.window -114 } -115 -116 fn metrics(&self) -> super::ControllerMetrics { -117 super::ControllerMetrics { -118 congestion_window: self.window(), -119 ssthresh: Some(self.ssthresh), -120 pacing_rate: None, -121 } -122 } -123 -124 fn clone_box(&self) -> Box<dyn Controller> { -125 Box::new(self.clone()) -126 } -127 -128 fn initial_window(&self) -> u64 { -129 self.config.initial_window -130 } -131 -132 fn into_any(self: Box<Self>) -> Box<dyn Any> { -133 self -134 } -135} -136 -137/// Configuration for the `NewReno` congestion controller -138#[derive(Debug, Clone)] -139pub struct NewRenoConfig { -140 initial_window: u64, -141 loss_reduction_factor: f32, -142} -143 -144impl NewRenoConfig { -145 /// Default limit on the amount of outstanding data in bytes. -146 /// -147 /// Recommended value: `min(10 * max_datagram_size, max(2 * max_datagram_size, 14720))` -148 pub fn initial_window(&mut self, value: u64) -> &mut Self { -149 self.initial_window = value; -150 self -151 } -152 -153 /// Reduction in congestion window when a new loss event is detected. -154 pub fn loss_reduction_factor(&mut self, value: f32) -> &mut Self { -155 self.loss_reduction_factor = value; -156 self -157 } -158} -159 -160impl Default for NewRenoConfig { -161 fn default() -> Self { -162 Self { -163 initial_window: 14720.clamp(2 * BASE_DATAGRAM_SIZE, 10 * BASE_DATAGRAM_SIZE), -164 loss_reduction_factor: 0.5, -165 } -166 } -167} -168 -169impl ControllerFactory for NewRenoConfig { -170 fn build(self: Arc<Self>, now: Instant, current_mtu: u16) -> Box<dyn Controller> { -171 Box::new(NewReno::new(self, now, current_mtu)) -172 } -173} \ No newline at end of file +49 _pn: u64, +50 app_limited: bool, +51 _rtt: &RttEstimator, +52 ) { +53 if app_limited || sent <= self.recovery_start_time { +54 return; +55 } +56 +57 if self.window < self.ssthresh { +58 // Slow start +59 self.window += bytes; +60 +61 if self.window >= self.ssthresh { +62 // Exiting slow start +63 // Initialize `bytes_acked` for congestion avoidance. The idea +64 // here is that any bytes over `sshthresh` will already be counted +65 // towards the congestion avoidance phase - independent of when +66 // how close to `sshthresh` the `window` was when switching states, +67 // and independent of datagram sizes. +68 self.bytes_acked = self.window - self.ssthresh; +69 } +70 } else { +71 // Congestion avoidance +72 // This implementation uses the method which does not require +73 // floating point math, which also increases the window by 1 datagram +74 // for every round trip. +75 // This mechanism is called Appropriate Byte Counting in +76 // https://tools.ietf.org/html/rfc3465 +77 self.bytes_acked += bytes; +78 +79 if self.bytes_acked >= self.window { +80 self.bytes_acked -= self.window; +81 self.window += self.current_mtu; +82 } +83 } +84 } +85 +86 fn on_congestion_event( +87 &mut self, +88 now: Instant, +89 sent: Instant, +90 is_persistent_congestion: bool, +91 _is_ecn: bool, +92 _lost_bytes: u64, +93 _largest_lost_pn: u64, +94 ) { +95 if sent <= self.recovery_start_time { +96 return; +97 } +98 +99 self.recovery_start_time = now; +100 self.window = (self.window as f32 * self.config.loss_reduction_factor) as u64; +101 self.window = self.window.max(self.minimum_window()); +102 self.ssthresh = self.window; +103 +104 if is_persistent_congestion { +105 self.window = self.minimum_window(); +106 } +107 } +108 +109 fn on_mtu_update(&mut self, new_mtu: u16) { +110 self.current_mtu = new_mtu as u64; +111 self.window = self.window.max(self.minimum_window()); +112 } +113 +114 fn window(&self) -> u64 { +115 self.window +116 } +117 +118 fn metrics(&self) -> super::ControllerMetrics { +119 super::ControllerMetrics { +120 congestion_window: self.window(), +121 ssthresh: Some(self.ssthresh), +122 pacing_rate: None, +123 send_quantum: None, +124 } +125 } +126 +127 fn clone_box(&self) -> Box<dyn Controller> { +128 Box::new(self.clone()) +129 } +130 +131 fn initial_window(&self) -> u64 { +132 self.config.initial_window +133 } +134 +135 fn into_any(self: Box<Self>) -> Box<dyn Any> { +136 self +137 } +138} +139 +140/// Configuration for the `NewReno` congestion controller +141#[derive(Debug, Clone)] +142pub struct NewRenoConfig { +143 initial_window: u64, +144 loss_reduction_factor: f32, +145} +146 +147impl NewRenoConfig { +148 /// Default limit on the amount of outstanding data in bytes. +149 /// +150 /// Recommended value: `min(10 * max_datagram_size, max(2 * max_datagram_size, 14720))` +151 pub fn initial_window(&mut self, value: u64) -> &mut Self { +152 self.initial_window = value; +153 self +154 } +155 +156 /// Reduction in congestion window when a new loss event is detected. +157 pub fn loss_reduction_factor(&mut self, value: f32) -> &mut Self { +158 self.loss_reduction_factor = value; +159 self +160 } +161} +162 +163impl Default for NewRenoConfig { +164 fn default() -> Self { +165 Self { +166 initial_window: 14720.clamp(2 * BASE_DATAGRAM_SIZE, 10 * BASE_DATAGRAM_SIZE), +167 loss_reduction_factor: 0.5, +168 } +169 } +170} +171 +172impl ControllerFactory for NewRenoConfig { +173 fn build(self: Arc<Self>, now: Instant, current_mtu: u16) -> Box<dyn Controller> { +174 Box::new(NewReno::new(self, now, current_mtu)) +175 } +176} \ 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 7e858ed3e..70f3e2465 100644 --- a/pr/451/docs/src/noq_proto/connection/mod.rs.html +++ b/pr/451/docs/src/noq_proto/connection/mod.rs.html @@ -3,7 +3,7 @@ 3 collections::{BTreeMap, VecDeque, btree_map}, 4 convert::TryFrom, 5 fmt, io, mem, -6 net::{IpAddr, SocketAddr}, +6 net::SocketAddr, 7 num::{NonZeroU32, NonZeroUsize}, 8 sync::Arc, 9}; @@ -81,7 +81,7 @@ 81pub(crate) mod qlog; 82pub(crate) mod send_buffer; 83 -84mod spaces; +84pub(crate) mod spaces; 85#[cfg(fuzzing)] 86pub use spaces::Retransmits; 87#[cfg(not(fuzzing))] @@ -99,7 +99,7 @@ 99use streams::StreamsState; 100pub use streams::{ 101 Chunks, ClosedStream, FinishError, ReadError, ReadableError, RecvStream, SendStream, -102 ShouldTransmit, StreamEvent, Streams, WriteError, Written, +102 ShouldTransmit, StreamEvent, Streams, WriteError, 103}; 104 105mod timer; @@ -206,1917 +206,1917 @@ 206 spaces: [PacketSpace; 3], 207 /// Highest usable packet space. 208 highest_space: SpaceKind, -209 /// Whether the idle timer should be reset the next time an ack-eliciting packet is transmitted. -210 permit_idle_reset: bool, -211 /// Negotiated idle timeout -212 idle_timeout: Option<Duration>, -213 timers: TimerTable, -214 /// Number of packets received which could not be authenticated -215 authentication_failures: u64, -216 -217 // -218 // Queued non-retransmittable 1-RTT data -219 // -220 /// If the CONNECTION_CLOSE frame needs to be sent -221 connection_close_pending: bool, -222 -223 // -224 // ACK frequency -225 // -226 ack_frequency: AckFrequencyState, -227 -228 // -229 // Congestion Control -230 // -231 /// Whether the most recently received packet had an ECN codepoint set -232 receiving_ecn: bool, -233 /// Number of packets authenticated -234 total_authed_packets: u64, -235 /// Whether the last `poll_transmit` call yielded no data because there was -236 /// no outgoing application data. -237 app_limited: bool, -238 -239 // -240 // ObservedAddr -241 // -242 /// Sequence number for the next observed address frame sent to the peer. -243 next_observed_addr_seq_no: VarInt, -244 -245 streams: StreamsState, -246 /// Active and surplus CIDs issued by the remote, for future use on new paths. -247 /// -248 /// These are given out before multiple paths exist, also for paths that will never -249 /// exist. So if multipath is supported the number of paths here will be higher than -250 /// the actual number of paths in use. -251 remote_cids: FxHashMap<PathId, CidQueue>, -252 /// Attributes of CIDs generated by local endpoint -253 /// -254 /// Any path that is allowed to be opened is present in this map, as well as the already -255 /// opened paths. However since CIDs are issued async by the endpoint driver via -256 /// connection events it can not be used to know if CIDs have been issued for a path or -257 /// not. See [`Connection::max_path_id_with_cids`] for this. -258 local_cid_state: FxHashMap<PathId, CidState>, -259 /// State of the unreliable datagram extension -260 datagrams: DatagramState, -261 /// Path level statistics. -262 path_stats: PathStatsMap, -263 /// Accumulated stats of all discarded paths. -264 /// -265 /// The connection-level stats returned by [`Self::stats`] are the sum of the stats of -266 /// all the paths. However once a path is discarded it gets added to this field instead -267 /// so we do not have to keep an ever growing number of paths stats in memory. -268 partial_stats: ConnectionStats, -269 /// QUIC version used for the connection. -270 version: u32, -271 -272 // -273 // Multipath -274 // -275 /// Maximum number of concurrent paths -276 /// -277 /// Initially set from the [`TransportConfig::max_concurrent_multipath_paths`]. Even -278 /// when multipath is disabled this will be set to 1, it is not used in that case -279 /// though. -280 max_concurrent_paths: NonZeroU32, -281 /// Local maximum [`PathId`] to be used -282 /// -283 /// This is initially set to [`TransportConfig::get_initial_max_path_id`] when multipath -284 /// is negotiated, or to [`PathId::ZERO`] otherwise. This is essentially the value of -285 /// the highest MAX_PATH_ID frame sent. -286 /// -287 /// Any path with an ID equal or below this [`PathId`] is either: -288 /// -289 /// - Abandoned, if it is also in [`Connection::abandoned_paths`]. -290 /// - Open, in this case it is present in [`Connection::paths`] -291 /// - Not yet opened, if it is in neither of these two places. +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 /// Note that for not-yet-open there may or may not be any CIDs issued. See -294 /// [`Connection::max_path_id_with_cids`]. -295 local_max_path_id: PathId, -296 /// Remote's maximum [`PathId`] to be used -297 /// -298 /// This is initially set to the peer's [`TransportParameters::initial_max_path_id`] when -299 /// multipath is negotiated, or to [`PathId::ZERO`] otherwise. A peer may increase this limit -300 /// by sending [`Frame::MaxPathId`] frames. -301 remote_max_path_id: PathId, -302 /// The greatest [`PathId`] we have issued CIDs for -303 /// -304 /// CIDs are only issued for `min(local_max_path_id, remote_max_path_id)`. It is not -305 /// possible to use [`Connection::local_cid_state`] to know if CIDs have been issued -306 /// since they are issued asynchronously by the endpoint driver. -307 max_path_id_with_cids: PathId, -308 /// The paths already abandoned -309 /// -310 /// They may still have some state left in [`Connection::paths`] or -311 /// [`Connection::local_cid_state`] since some of this has to be kept around for some -312 /// time after a path is abandoned. -313 // TODO(flub): Make this a more efficient data structure. Like ranges of abandoned -314 // paths. Or a set together with a minimum. Or something. -315 abandoned_paths: FxHashSet<PathId>, +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 -317 /// State for n0's (<https://n0.computer>) nat traversal protocol. -318 n0_nat_traversal: n0_nat_traversal::State, -319 qlog: QlogSink, -320} -321 -322impl Connection { -323 pub(crate) fn new( -324 endpoint_config: Arc<EndpointConfig>, -325 config: Arc<TransportConfig>, -326 init_cid: ConnectionId, -327 local_cid: ConnectionId, -328 remote_cid: ConnectionId, -329 network_path: FourTuple, -330 crypto: Box<dyn crypto::Session>, -331 cid_gen: &dyn ConnectionIdGenerator, -332 now: Instant, -333 version: u32, -334 allow_mtud: bool, -335 rng_seed: [u8; 32], -336 side_args: SideArgs, -337 qlog: QlogSink, -338 ) -> Self { -339 let pref_addr_cid = side_args.pref_addr_cid(); -340 let path_validated = side_args.path_validated(); -341 let connection_side = ConnectionSide::from(side_args); -342 let side = connection_side.side(); -343 let mut rng = StdRng::from_seed(rng_seed); -344 let initial_space = PacketSpace::new(now, SpaceId::Initial, &mut rng); -345 let handshake_space = PacketSpace::new(now, SpaceId::Handshake, &mut rng); -346 #[cfg(test)] -347 let data_space = match config.deterministic_packet_numbers { -348 true => PacketSpace::new_deterministic(now, SpaceId::Data), -349 false => PacketSpace::new(now, SpaceId::Data, &mut rng), -350 }; -351 #[cfg(not(test))] -352 let data_space = PacketSpace::new(now, SpaceId::Data, &mut rng); -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(), -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 path = PathData::new(network_path, allow_mtud, None, 0, now, &config); -370 path.open_status = paths::OpenStatus::Informed; -371 let mut this = Self { -372 endpoint_config, -373 crypto_state: CryptoState::new(crypto, init_cid, side, &mut rng), -374 handshake_cid: local_cid, -375 remote_handshake_cid: remote_cid, -376 local_cid_state, -377 paths: BTreeMap::from_iter([( -378 PathId::ZERO, -379 PathState { -380 data: path, -381 prev: None, -382 }, -383 )]), -384 path_generation_counter: 0, -385 allow_mtud, -386 state, -387 side: connection_side, -388 peer_params: TransportParameters::default(), -389 original_remote_cid: remote_cid, -390 initial_dst_cid: init_cid, -391 retry_src_cid: None, -392 events: VecDeque::new(), -393 endpoint_events: VecDeque::new(), -394 spin_enabled: config.allow_spin && rng.random_ratio(7, 8), -395 spin: false, -396 spaces: [initial_space, handshake_space, data_space], -397 highest_space: SpaceKind::Initial, -398 permit_idle_reset: true, -399 idle_timeout: match config.max_idle_timeout { -400 None | Some(VarInt(0)) => None, -401 Some(dur) => Some(Duration::from_millis(dur.0)), -402 }, -403 timers: TimerTable::default(), -404 authentication_failures: 0, -405 connection_close_pending: false, -406 -407 ack_frequency: AckFrequencyState::new(get_max_ack_delay( -408 &TransportParameters::default(), -409 )), -410 -411 app_limited: false, -412 receiving_ecn: false, -413 total_authed_packets: 0, -414 -415 next_observed_addr_seq_no: 0u32.into(), -416 -417 streams: StreamsState::new( -418 side, -419 config.max_concurrent_uni_streams, -420 config.max_concurrent_bidi_streams, -421 config.send_window, -422 config.receive_window, -423 config.stream_receive_window, -424 ), -425 datagrams: DatagramState::default(), -426 config, -427 remote_cids: FxHashMap::from_iter([(PathId::ZERO, CidQueue::new(remote_cid))]), -428 rng, -429 path_stats: Default::default(), -430 partial_stats: ConnectionStats::default(), -431 version, +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, +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 // peer params are not yet known, so multipath is not enabled -434 max_concurrent_paths: NonZeroU32::MIN, -435 local_max_path_id: PathId::ZERO, -436 remote_max_path_id: PathId::ZERO, -437 max_path_id_with_cids: PathId::ZERO, -438 abandoned_paths: Default::default(), -439 -440 n0_nat_traversal: Default::default(), -441 qlog, -442 }; -443 if path_validated { -444 this.on_path_validated(PathId::ZERO); -445 } -446 if side.is_client() { -447 // Kick off the connection -448 this.write_crypto(); -449 this.init_0rtt(now); -450 } -451 this.qlog -452 .emit_tuple_assigned(PathId::ZERO, network_path, now); -453 this -454 } -455 -456 /// Returns the next time at which `handle_timeout` should be called -457 /// -458 /// The value returned may change after: -459 /// - the application performed some I/O on the connection -460 /// - a call was made to `handle_event` -461 /// - a call to `poll_transmit` returned `Some` -462 /// - a call was made to `handle_timeout` -463 #[must_use] -464 pub fn poll_timeout(&mut self) -> Option<Instant> { -465 self.timers.peek() -466 } -467 -468 /// Returns application-facing events -469 /// -470 /// Connections should be polled for events after: -471 /// - a call was made to `handle_event` -472 /// - a call was made to `handle_timeout` -473 #[must_use] -474 pub fn poll(&mut self) -> Option<Event> { -475 if let Some(x) = self.events.pop_front() { -476 return Some(x); -477 } -478 -479 if let Some(event) = self.streams.poll() { -480 return Some(Event::Stream(event)); -481 } +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 } 482 -483 if let Some(reason) = self.state.take_error() { -484 return Some(Event::ConnectionLost { reason }); -485 } -486 -487 None -488 } -489 -490 /// Return endpoint-facing events -491 #[must_use] -492 pub fn poll_endpoint_events(&mut self) -> Option<EndpointEvent> { -493 self.endpoint_events.pop_front().map(EndpointEvent) -494 } -495 -496 /// Provide control over streams -497 #[must_use] -498 pub fn streams(&mut self) -> Streams<'_> { -499 Streams { -500 state: &mut self.streams, -501 conn_state: &self.state, -502 } -503 } -504 -505 /// Provide control over streams -506 #[must_use] -507 pub fn recv_stream(&mut self, id: StreamId) -> RecvStream<'_> { -508 assert!(id.dir() == Dir::Bi || id.initiator() != self.side.side()); -509 RecvStream { -510 id, -511 state: &mut self.streams, -512 pending: &mut self.spaces[SpaceId::Data].pending, -513 } -514 } -515 -516 /// Provide control over streams -517 #[must_use] -518 pub fn send_stream(&mut self, id: StreamId) -> SendStream<'_> { -519 assert!(id.dir() == Dir::Bi || id.initiator() == self.side.side()); -520 SendStream { -521 id, -522 state: &mut self.streams, -523 pending: &mut self.spaces[SpaceId::Data].pending, -524 conn_state: &self.state, -525 } -526 } -527 -528 /// Opens a new path only if no path on the same network path currently exists. -529 /// -530 /// This comparison will use [`FourTuple::is_probably_same_path`] on the given `network_path` -531 /// and pass it existing path's network paths. +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. +525 /// +526 /// This means that you can pass `local_ip: None` to make the comparison only compare +527 /// remote addresses. +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 /// This means that you can pass `local_ip: None` to make the comparison only compare -534 /// remote addresses. +533 /// See also [`open_path`]. Returns `(path_id, true)` if the path already existed. `(path_id, +534 /// false)` if was opened. 535 /// -536 /// This avoids having to guess which local interface will be used to communicate with the -537 /// remote, should it not be known yet. We assume that if we already have a path to the remote, -538 /// the OS is likely to use the same interface to talk to said remote. -539 /// -540 /// See also [`open_path`]. Returns `(path_id, true)` if the path already existed. `(path_id, -541 /// false)` if was opened. -542 /// -543 /// [`open_path`]: Connection::open_path -544 pub fn open_path_ensure( -545 &mut self, -546 network_path: FourTuple, -547 initial_status: PathStatus, -548 now: Instant, -549 ) -> Result<(PathId, bool), PathError> { -550 let existing_open_path = self.paths.iter().find(|(id, path)| { -551 network_path.is_probably_same_path(&path.data.network_path) -552 && !self.abandoned_paths.contains(*id) -553 }); -554 match existing_open_path { -555 Some((path_id, _state)) => Ok((*path_id, true)), -556 None => Ok((self.open_path(network_path, initial_status, now)?, false)), -557 } -558 } -559 -560 /// Opens a new path -561 /// -562 /// Further errors might occur and they will be emitted in [`PathEvent::Abandoned`] events with this path id. -563 /// When the path is opened it will be reported as an [`PathEvent::Opened`]. -564 pub fn open_path( -565 &mut self, -566 network_path: FourTuple, -567 initial_status: PathStatus, -568 now: Instant, -569 ) -> Result<PathId, PathError> { -570 if !self.is_multipath_negotiated() { -571 return Err(PathError::MultipathNotNegotiated); -572 } -573 if self.side().is_server() { -574 return Err(PathError::ServerSideNotAllowed); -575 } -576 -577 let max_abandoned = self.abandoned_paths.iter().max().copied(); -578 let max_used = self.paths.keys().last().copied(); -579 let path_id = max_abandoned -580 .max(max_used) -581 .unwrap_or(PathId::ZERO) -582 .saturating_add(1u8); -583 -584 if Some(path_id) > self.max_path_id() { -585 return Err(PathError::MaxPathIdReached); -586 } -587 if path_id > self.remote_max_path_id { -588 self.spaces[SpaceId::Data].pending.paths_blocked = true; -589 return Err(PathError::MaxPathIdReached); -590 } -591 if self -592 .remote_cids -593 .get(&path_id) -594 .map(CidQueue::active) -595 .is_none() -596 { -597 self.spaces[SpaceId::Data] -598 .pending -599 .path_cids_blocked -600 .insert(path_id); -601 return Err(PathError::RemoteCidsExhausted); -602 } +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; +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 let path = self.ensure_path(path_id, network_path, now, None); -605 path.status.local_update(initial_status); -606 -607 Ok(path_id) -608 } -609 -610 /// Closes a path and sends a PATH_ABANDON frame with the passed error code. -611 /// -612 /// Returns [`ClosePathError::LastOpenPath`] if this is the last open path. -613 /// It does allow closing paths which have not yet been opened, as e.g. is the case -614 /// when receiving a PATH_ABANDON from the peer for a path that was never opened locally. -615 pub fn close_path( -616 &mut self, -617 now: Instant, -618 path_id: PathId, -619 error_code: VarInt, -620 ) -> Result<(), ClosePathError> { -621 self.close_path_inner( -622 now, -623 path_id, -624 PathAbandonReason::ApplicationClosed { error_code }, -625 ) -626 } -627 -628 /// Closes a path and sends a PATH_ABANDON frame. -629 /// -630 /// Other than [`Self::close_path`] this allows to specify the reason for the path being closed. -631 /// Internally, this should be used over [`Self::close_path`]. -632 pub(crate) fn close_path_inner( -633 &mut self, -634 now: Instant, -635 path_id: PathId, -636 reason: PathAbandonReason, -637 ) -> Result<(), ClosePathError> { -638 if self.state.is_drained() { -639 return Ok(()); -640 } -641 -642 if !self.is_multipath_negotiated() { -643 return Err(ClosePathError::MultipathNotNegotiated); +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 } -645 if self.abandoned_paths.contains(&path_id) -646 || Some(path_id) > self.max_path_id() -647 || !self.paths.contains_key(&path_id) -648 { -649 return Err(ClosePathError::ClosedPath); -650 } -651 -652 let is_last_path = !self -653 .paths -654 .keys() -655 .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 if is_last_path && !reason.is_remote() { -658 return Err(ClosePathError::LastOpenPath); -659 } -660 -661 self.abandon_path(now, path_id, reason); -662 -663 // When the remote abandons our last path, start a grace timer to allow -664 // the application to open a replacement path. -665 // https://www.ietf.org/archive/id/draft-ietf-quic-multipath-21.html#section-3.4-8 -666 if is_last_path { -667 // The spec suggests 1 PTO, but we use 3 * PTO to account for -668 // packet loss when opening a replacement path. Uses initial RTT -669 // since the abandoned path's RTT estimate is no longer valid. -670 let rtt = RttEstimator::new(self.config.initial_rtt); -671 let pto = rtt.pto_base() + self.ack_frequency.max_ack_delay_for_pto(); -672 let grace = pto * 3; -673 self.timers.set( -674 Timer::Conn(ConnTimer::NoAvailablePath), -675 now + grace, -676 self.qlog.with_time(now), -677 ); -678 } -679 -680 Ok(()) -681 } -682 -683 /// Unconditionally abandon a path. -684 /// -685 /// Only to be called once sure this path should be abandoned, all checks -686 /// should have happened before calling this. -687 fn abandon_path(&mut self, now: Instant, path_id: PathId, reason: PathAbandonReason) { -688 trace!(%path_id, ?reason, "closing path"); +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 // Send PATH_ABANDON -691 self.spaces[SpaceId::Data] -692 .pending -693 .path_abandon -694 .insert(path_id, reason.error_code()); -695 -696 // Remove pending NEW CIDs for this path -697 let pending_space = &mut self.spaces[SpaceId::Data].pending; -698 pending_space.new_cids.retain(|cid| cid.path_id != path_id); -699 pending_space.path_cids_blocked.retain(|&id| id != path_id); -700 pending_space.path_status.retain(|&id| id != path_id); -701 -702 // Cleanup retransmits across ALL paths (CIDs for path_id may have been transmitted on other paths) -703 for space in self.spaces[SpaceId::Data].iter_paths_mut() { -704 for sent_packet in space.sent_packets.values_mut() { -705 if let Some(retransmits) = sent_packet.retransmits.get_mut() { -706 retransmits.new_cids.retain(|cid| cid.path_id != path_id); -707 retransmits.path_cids_blocked.retain(|&id| id != path_id); -708 retransmits.path_status.retain(|&id| id != path_id); -709 } -710 } -711 } -712 -713 // Note: remote CIDs are NOT removed here. They are removed when the PATH_ABANDON -714 // frame is actually written to a packet (in populate_packet). This allows sending -715 // PATH_ABANDON on the abandoned path itself when no other path exists (#509). -716 debug_assert!(!self.state.is_drained()); // requirement for endpoint_events, checked above -717 self.endpoint_events -718 .push_back(EndpointEventInner::RetireResetToken(path_id)); -719 -720 trace!(%path_id, "abandoning path"); -721 self.abandoned_paths.insert(path_id); -722 -723 for timer in timer::PathTimer::VALUES { -724 // match for completeness -725 let keep_timer = match timer { -726 // These timers deal with sending and receiving PATH_CHALLENGE and -727 // PATH_RESPONSE, but now that the path is abandoned, we no longer care about -728 // these frames or their timing -729 PathTimer::PathValidationFailed -730 | PathTimer::PathChallengeLost -731 | PathTimer::PathOpenFailed => false, -732 // These timers deal with the lifetime of the path. Now that the path is abandoned, -733 // these are not relevant. -734 PathTimer::PathKeepAlive | PathTimer::PathIdle => false, -735 // The path has already been informed that outstanding acks should be sent -736 // immediately -737 PathTimer::MaxAckDelay => false, -738 // This timer should not be set, for completeness it's not kept as it's set when -739 // the PATH_ABANDON frame is sent. -740 PathTimer::PathDrained => false, -741 // Sent packets still need to be identified as lost to trigger timely -742 // retransmission. -743 PathTimer::LossDetection => true, -744 // This path should not be used for sending after the PATH_ABANDON frame is sent. -745 // However, any outstanding data that should be sent before PATH_ABANDON, should -746 // still respect pacing. -747 PathTimer::Pacing => true, -748 }; -749 -750 if !keep_timer { -751 let qlog = self.qlog.with_time(now); -752 self.timers.stop(Timer::PerPath(path_id, timer), qlog); -753 } -754 } -755 -756 // Emit event to the application. -757 self.events.push_back(Event::Path(PathEvent::Abandoned { -758 id: path_id, -759 reason, -760 })); -761 } -762 -763 /// Gets the [`PathData`] for a known [`PathId`]. -764 /// -765 /// Will panic if the path_id does not reference any known path. -766 #[track_caller] -767 fn path_data(&self, path_id: PathId) -> &PathData { -768 if let Some(data) = self.paths.get(&path_id) { -769 &data.data -770 } else { -771 panic!( -772 "unknown path: {path_id}, currently known paths: {:?}", -773 self.paths.keys().collect::<Vec<_>>() -774 ); -775 } -776 } -777 -778 /// Gets a reference to the [`PathData`] for a [`PathId`] -779 fn path(&self, path_id: PathId) -> Option<&PathData> { -780 self.paths.get(&path_id).map(|path_state| &path_state.data) -781 } -782 -783 /// Gets a mutable reference to the [`PathData`] for a [`PathId`] -784 fn path_mut(&mut self, path_id: PathId) -> Option<&mut PathData> { -785 self.paths -786 .get_mut(&path_id) -787 .map(|path_state| &mut path_state.data) +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; +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); +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 /// Returns all known paths. -791 /// -792 /// There is no guarantee any of these paths are open or usable. -793 pub fn paths(&self) -> Vec<PathId> { -794 self.paths.keys().copied().collect() -795 } -796 -797 /// Gets the local [`PathStatus`] for a known [`PathId`] -798 pub fn path_status(&self, path_id: PathId) -> Result<PathStatus, ClosedPath> { -799 self.path(path_id) -800 .map(PathData::local_status) -801 .ok_or(ClosedPath { _private: () }) -802 } -803 -804 /// Returns the path's network path represented as a 4-tuple. -805 pub fn network_path(&self, path_id: PathId) -> Result<FourTuple, ClosedPath> { -806 self.path(path_id) -807 .map(|path| path.network_path) -808 .ok_or(ClosedPath { _private: () }) -809 } -810 -811 /// Sets the [`PathStatus`] for a known [`PathId`] -812 /// -813 /// Returns the previous path status on success. -814 pub fn set_path_status( -815 &mut self, -816 path_id: PathId, -817 status: PathStatus, -818 ) -> Result<PathStatus, SetPathStatusError> { -819 if !self.is_multipath_negotiated() { -820 return Err(SetPathStatusError::MultipathNotNegotiated); -821 } -822 let path = self -823 .path_mut(path_id) -824 .ok_or(SetPathStatusError::ClosedPath)?; -825 let prev = match path.status.local_update(status) { -826 Some(prev) => { -827 self.spaces[SpaceId::Data] -828 .pending -829 .path_status -830 .insert(path_id); -831 prev -832 } -833 None => path.local_status(), -834 }; -835 Ok(prev) -836 } -837 -838 /// Returns the remote path status -839 // TODO(flub): Probably should also be some kind of path event? Not even sure if I like -840 // this as an API, but for now it allows me to write a test easily. -841 // TODO(flub): Technically this should be a Result<Option<PathSTatus>>? -842 pub fn remote_path_status(&self, path_id: PathId) -> Option<PathStatus> { -843 self.path(path_id).and_then(|path| path.remote_status()) -844 } -845 -846 /// Sets the max_idle_timeout for a specific path. -847 /// -848 /// The PathIdle timer is immediately re-armed accounting for already-elapsed -849 /// idle time. Setting `None` disables the timeout and stops the timer. -850 /// -851 /// See [`TransportConfig::default_path_max_idle_timeout`] for details. -852 /// -853 /// Returns the previous value of the setting. -854 pub fn set_path_max_idle_timeout( -855 &mut self, -856 now: Instant, -857 path_id: PathId, -858 timeout: Option<Duration>, -859 ) -> Result<Option<Duration>, ClosedPath> { -860 let path = self -861 .paths -862 .get_mut(&path_id) -863 .ok_or(ClosedPath { _private: () })?; -864 let prev = std::mem::replace(&mut path.data.idle_timeout, timeout); -865 -866 // Adjust the PathIdle timer, accounting for already-elapsed idle time. -867 if !self.state.is_closed() { -868 if let Some(new_timeout) = timeout { -869 let timer = Timer::PerPath(path_id, PathTimer::PathIdle); -870 let deadline = match (prev, self.timers.get(timer)) { -871 (Some(old_timeout), Some(old_deadline)) => { -872 let last_activity = old_deadline.checked_sub(old_timeout).unwrap_or(now); -873 last_activity + new_timeout -874 } -875 _ => now + new_timeout, -876 }; -877 self.timers.set(timer, deadline, self.qlog.with_time(now)); -878 } else { -879 self.timers.stop( -880 Timer::PerPath(path_id, PathTimer::PathIdle), -881 self.qlog.with_time(now), -882 ); -883 } -884 } -885 -886 Ok(prev) -887 } -888 -889 /// Sets the keep_alive_interval for a specific path -890 /// -891 /// See [`TransportConfig::default_path_keep_alive_interval`] for details. -892 /// -893 /// Returns the previous value of the setting. -894 pub fn set_path_keep_alive_interval( -895 &mut self, -896 path_id: PathId, -897 interval: Option<Duration>, -898 ) -> Result<Option<Duration>, ClosedPath> { -899 let path = self -900 .paths -901 .get_mut(&path_id) -902 .ok_or(ClosedPath { _private: () })?; -903 Ok(std::mem::replace(&mut path.data.keep_alive, interval)) -904 } -905 -906 /// Gets the [`PathData`] for a known [`PathId`]. -907 /// -908 /// Will panic if the path_id does not reference any known path. -909 #[track_caller] -910 fn path_data_mut(&mut self, path_id: PathId) -> &mut PathData { -911 &mut self.paths.get_mut(&path_id).expect("known path").data -912 } -913 -914 /// Find an open, validated path that's on the same network path as the given network path. -915 /// -916 /// Returns the first path matching, even if there's multiple. -917 fn find_validated_path_on_network_path( -918 &self, -919 network_path: FourTuple, -920 ) -> Option<(&PathId, &PathState)> { -921 self.paths.iter().find(|(path_id, path_state)| { -922 path_state.data.validated -923 // Would this use the same network path, if network_path were used to send right now? -924 && network_path.is_probably_same_path(&path_state.data.network_path) -925 && !self.abandoned_paths.contains(path_id) -926 }) -927 // TODO(@divma): we might want to ensure the path has been recently active to consider the -928 // address validated -929 // matheus23: Perhaps looking at !self.abandoned_paths.contains(path_id) is enough, given keep-alives? -930 } -931 -932 /// Creates the [`PathData`] for a new [`PathId`]. -933 /// -934 /// Called for incoming packets as well as when opening a new path locally. -935 fn ensure_path( -936 &mut self, -937 path_id: PathId, -938 network_path: FourTuple, -939 now: Instant, -940 pn: Option<u64>, -941 ) -> &mut PathData { -942 let valid_path = self.find_validated_path_on_network_path(network_path); -943 let validated = valid_path.is_some(); -944 let initial_rtt = valid_path.map(|(_, path)| path.data.rtt.conservative()); -945 let vacant_entry = match self.paths.entry(path_id) { -946 btree_map::Entry::Vacant(vacant_entry) => vacant_entry, -947 btree_map::Entry::Occupied(occupied_entry) => { -948 return &mut occupied_entry.into_mut().data; -949 } -950 }; -951 -952 debug!(%validated, %path_id, %network_path, "path added"); -953 -954 // A new path was added. Cancel any pending NoAvailablePath grace timer. -955 self.timers.stop( -956 Timer::Conn(ConnTimer::NoAvailablePath), -957 self.qlog.with_time(now), -958 ); -959 let peer_max_udp_payload_size = -960 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()).unwrap_or(u16::MAX); -961 self.path_generation_counter = self.path_generation_counter.wrapping_add(1); -962 let mut data = PathData::new( -963 network_path, -964 self.allow_mtud, -965 Some(peer_max_udp_payload_size), -966 self.path_generation_counter, -967 now, -968 &self.config, -969 ); -970 -971 data.validated = validated; -972 if let Some(initial_rtt) = initial_rtt { -973 data.rtt.reset_initial_rtt(initial_rtt); -974 } -975 -976 // To open a path locally we need to send a packet on the path. Sending a challenge -977 // guarantees this. -978 data.pending_on_path_challenge = true; +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) +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) +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() +807 } +808 +809 /// Gets the local [`PathStatus`] for a known [`PathId`] +810 pub fn path_status(&self, path_id: PathId) -> Result<PathStatus, ClosedPath> { +811 self.path(path_id) +812 .map(PathData::local_status) +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 let path = vacant_entry.insert(PathState { data, prev: None }); -981 -982 let mut pn_space = spaces::PacketNumberSpace::new(now, SpaceId::Data, &mut self.rng); -983 if let Some(pn) = pn { -984 pn_space.dedup.insert(pn); -985 } -986 self.spaces[SpaceId::Data] -987 .number_spaces -988 .insert(path_id, pn_space); -989 self.qlog.emit_tuple_assigned(path_id, network_path, now); -990 -991 // If the remote opened this path we may not have CIDs for it. For locally opened -992 // paths the caller should have already made sure we have CIDs and refused to open -993 // it if there were none. -994 if !self.remote_cids.contains_key(&path_id) { -995 debug!(%path_id, "Remote opened path without issuing CIDs"); -996 self.spaces[SpaceId::Data] -997 .pending -998 .path_cids_blocked -999 .insert(path_id); -1000 // Do not abandon this path right away. CIDs might be in-flight still and arrive -1001 // soon. It is up to the remote to handle this situation. -1002 } -1003 -1004 &mut path.data -1005 } -1006 -1007 /// Returns packets to transmit -1008 /// -1009 /// Connections should be polled for transmit after: -1010 /// - the application performed some I/O on the connection -1011 /// - a call was made to `handle_event` -1012 /// - a call was made to `handle_timeout` -1013 /// -1014 /// `max_datagrams` specifies how many datagrams can be returned inside a -1015 /// single Transmit using GSO. This must be at least 1. -1016 #[must_use] -1017 pub fn poll_transmit( -1018 &mut self, -1019 now: Instant, -1020 max_datagrams: NonZeroUsize, -1021 buf: &mut Vec<u8>, -1022 ) -> Option<Transmit> { -1023 let max_datagrams = match self.config.enable_segmentation_offload { -1024 false => NonZeroUsize::MIN, -1025 true => max_datagrams, -1026 }; -1027 -1028 // Each call to poll_transmit can only send datagrams to one destination, because -1029 // all datagrams in a GSO batch are for the same destination. Therefore only -1030 // datagrams for one destination address are produced for each poll_transmit call. +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 } +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 // Check whether we need to send a close message -1033 let connection_close_pending = match self.state.as_type() { -1034 StateType::Drained => { -1035 self.app_limited = true; -1036 return None; -1037 } -1038 StateType::Draining | StateType::Closed => { -1039 // self.connection_close_pending is only reset once the associated packet -1040 // had been encoded successfully -1041 if !self.connection_close_pending { -1042 self.app_limited = true; -1043 return None; -1044 } -1045 true -1046 } -1047 _ => false, -1048 }; -1049 -1050 // Schedule an ACK_FREQUENCY frame if a new one needs to be sent. -1051 if let Some(config) = &self.config.ack_frequency_config { -1052 let rtt = self -1053 .paths -1054 .values() -1055 .map(|p| p.data.rtt.get()) -1056 .min() -1057 .expect("one path exists"); -1058 self.spaces[SpaceId::Data].pending.ack_frequency = self -1059 .ack_frequency -1060 .should_send_ack_frequency(rtt, config, &self.peer_params) -1061 && self.highest_space == SpaceKind::Data -1062 && self.peer_supports_ack_frequency(); -1063 } -1064 -1065 // If we end up not sending anything, we need to know if that was because there was -1066 // nothing to send or because we were congestion blocked. -1067 let mut congestion_blocked = false; -1068 -1069 let mut next_path_id = self.paths.first_entry().map(|e| *e.key()); -1070 while let Some(path_id) = next_path_id { -1071 if !connection_close_pending -1072 && let Some(transmit) = self.poll_transmit_off_path(now, buf, path_id) -1073 { -1074 return Some(transmit); -1075 } -1076 -1077 let info = self.scheduling_info(path_id); -1078 match self.poll_transmit_on_path( -1079 now, -1080 buf, -1081 path_id, -1082 max_datagrams, -1083 &info, -1084 connection_close_pending, -1085 ) { -1086 PollPathStatus::Send(transmit) => { -1087 return Some(transmit); -1088 } -1089 PollPathStatus::NothingToSend { -1090 congestion_blocked: cb, -1091 } => { -1092 congestion_blocked |= cb; -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 } -1101 -1102 next_path_id = self.paths.keys().find(|i| **i > path_id).copied(); -1103 } -1104 -1105 // We didn't produce any application data packet -1106 debug_assert!( -1107 buf.is_empty(), -1108 "there was data in the buffer, but it was not sent" -1109 ); -1110 -1111 self.app_limited = !congestion_blocked; -1112 -1113 if self.state.is_established() { -1114 // Try MTU probing now -1115 let mut next_path_id = self.paths.first_entry().map(|e| *e.key()); -1116 while let Some(path_id) = next_path_id { -1117 if let Some(transmit) = self.poll_transmit_mtu_probe(now, buf, path_id) { -1118 return Some(transmit); -1119 } -1120 next_path_id = self.paths.keys().find(|i| **i > path_id).copied(); -1121 } -1122 } -1123 -1124 None -1125 } -1126 -1127 /// Computes the packet scheduling information for this path. -1128 /// -1129 /// While this information is only returned for a single path, it is important to know -1130 /// that this information remains static for the entire span of a single -1131 /// [`Connection::poll_transmit`] call. In other words, the return value is purely -1132 /// functional and only depends on the [`PathId`] **during a single** `poll_transmit` -1133 /// call. It can be computed up-front for all paths but we don't do that because it -1134 /// involves an allocation. +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. +1133 /// +1134 /// # Panics 1135 /// -1136 /// See the inline comments for how the packet scheduling works. -1137 /// -1138 /// # Panics -1139 /// -1140 /// This will panic if called for a path for which we do not have any [`PathData`], like -1141 /// so many other functions we have. But this is the only one to document this in its -1142 /// doc comment. Maybe that should change. Eventually we'll refactor things for this -1143 /// panic to go away. -1144 fn scheduling_info(&self, path_id: PathId) -> PathSchedulingInfo { -1145 // Such a space is preferred for SpaceKind::Data frames. -1146 let have_validated_status_available_space = self.paths.iter().any(|(path_id, path)| { -1147 self.remote_cids.contains_key(path_id) -1148 && !self.abandoned_paths.contains(path_id) -1149 && path.data.validated -1150 && path.data.local_status() == PathStatus::Available -1151 }); -1152 -1153 // Such a space is able to send SpaceKind::Data frames. -1154 let have_validated_space = self.paths.iter().any(|(path_id, path)| { -1155 self.remote_cids.contains_key(path_id) -1156 && !self.abandoned_paths.contains(path_id) -1157 && path.data.validated -1158 }); -1159 -1160 let is_handshaking = self.is_handshaking(); -1161 let has_cids = self.remote_cids.contains_key(&path_id); -1162 let is_abandoned = self.abandoned_paths.contains(&path_id); -1163 let path_data = self.path_data(path_id); -1164 let validated = path_data.validated; -1165 let status = path_data.local_status(); -1166 -1167 // This is the core packet scheduling, whether this space ID may send -1168 // SpaceKind::Data frames. -1169 let may_send_data = has_cids -1170 && !is_abandoned -1171 && if is_handshaking { -1172 // There is only one path during the handshake. We want to -1173 // already send 0-RTT and 0.5-RTT (permitting anti-amplification -1174 // limit) data. -1175 true -1176 } else if !validated { -1177 // TODO(flub): When we have a network change we might end up -1178 // having to abandon all paths and re-open new ones to the -1179 // same remotes. This leaves us without any validated -1180 // path. Perhaps we should have a way to figure out if the -1181 // path is to a previously-validated remote address and allow -1182 // sending data to such remotes immediately. -1183 false -1184 } else { -1185 match status { -1186 PathStatus::Available => { -1187 // Best possible space to send data on. -1188 true -1189 } -1190 PathStatus::Backup => { -1191 // If there is a status-available path we prefer that. -1192 !have_validated_status_available_space -1193 } -1194 } -1195 }; -1196 -1197 // CONNECTION_CLOSE is allowed to be sent on a non-validated -1198 // path. Particularly during the handshake we want to send it before the -1199 // path is validated. Later if there is no validated path available we -1200 // will also accept sending it on an un-validated path. -1201 let may_send_close = has_cids -1202 && !is_abandoned -1203 && if !validated && have_validated_status_available_space { -1204 // We have a better space to send on. -1205 false -1206 } else { -1207 // No other validated space, this is as good as it gets. -1208 true -1209 }; -1210 -1211 // PATH_ABANDON is normally sent together with other SpaceKind::Data frames. But if -1212 // there is no other validated space to send it on, it can be sent on the path to be -1213 // abandoned itself if that was validated. -1214 let may_self_abandon = has_cids && validated && !have_validated_space; -1215 -1216 PathSchedulingInfo { -1217 is_abandoned, -1218 may_send_data, -1219 may_send_close, -1220 may_self_abandon, -1221 } -1222 } -1223 -1224 fn build_transmit(&mut self, path_id: PathId, transmit: TransmitBuf<'_>) -> Transmit { -1225 debug_assert!( -1226 !transmit.is_empty(), -1227 "must not be called with an empty transmit buffer" -1228 ); -1229 -1230 let network_path = self.path_data(path_id).network_path; -1231 trace!( -1232 segment_size = transmit.segment_size(), -1233 last_datagram_len = transmit.len() % transmit.segment_size(), -1234 %network_path, -1235 "sending {} bytes in {} datagrams", -1236 transmit.len(), -1237 transmit.num_datagrams() -1238 ); -1239 self.path_data_mut(path_id) -1240 .inc_total_sent(transmit.len() as u64); -1241 -1242 self.path_stats -1243 .for_path(path_id) -1244 .udp_tx -1245 .on_sent(transmit.num_datagrams() as u64, transmit.len()); -1246 -1247 Transmit { -1248 destination: network_path.remote, -1249 size: transmit.len(), -1250 ecn: if self.path_data(path_id).sending_ecn { -1251 Some(EcnCodepoint::Ect0) -1252 } else { -1253 None -1254 }, -1255 segment_size: match transmit.num_datagrams() { -1256 1 => None, -1257 _ => Some(transmit.segment_size()), -1258 }, -1259 src_ip: network_path.local_ip, -1260 } -1261 } -1262 -1263 /// poll_transmit logic for off-path data. -1264 fn poll_transmit_off_path( -1265 &mut self, -1266 now: Instant, -1267 buf: &mut Vec<u8>, -1268 path_id: PathId, -1269 ) -> Option<Transmit> { -1270 if let Some(challenge) = self.send_prev_path_challenge(now, buf, path_id) { -1271 return Some(challenge); -1272 } -1273 if let Some(response) = self.send_off_path_path_response(now, buf, path_id) { -1274 return Some(response); -1275 } -1276 if let Some(challenge) = self.send_nat_traversal_path_challenge(now, buf, path_id) { -1277 return Some(challenge); -1278 } -1279 None -1280 } -1281 -1282 /// poll_transmit logic for on-path data. -1283 /// -1284 /// This is not quite the same as for a multipath packet space, since [`PathId::ZERO`] -1285 /// has 3 packet spaces, which this handles. -1286 /// -1287 /// See [`Self::poll_transmit_off_path`] for off-path data. -1288 #[must_use] -1289 fn poll_transmit_on_path( -1290 &mut self, -1291 now: Instant, -1292 buf: &mut Vec<u8>, -1293 path_id: PathId, -1294 max_datagrams: NonZeroUsize, -1295 scheduling_info: &PathSchedulingInfo, -1296 connection_close_pending: bool, -1297 ) -> PollPathStatus { -1298 // Check if there is at least one active CID to use for sending -1299 let Some(remote_cid) = self.remote_cids.get(&path_id).map(CidQueue::active) else { -1300 if !self.abandoned_paths.contains(&path_id) { -1301 debug!(%path_id, "no remote CIDs for path"); -1302 } -1303 return PollPathStatus::NothingToSend { -1304 congestion_blocked: false, -1305 }; -1306 }; -1307 -1308 // Whether the last packet in the datagram must be padded so the datagram takes up -1309 // an exact size. An earlier space can decide to not fill an entire datagram and -1310 // require the next space to fill it further. But may need a specific size of the -1311 // datagram containing the packet. The final packet built in the datagram must pad -1312 // to this size. -1313 let mut pad_datagram = PadDatagram::No; -1314 -1315 // The packet number of the last built packet. This is kept kept across spaces. -1316 // QUIC is supposed to have a single congestion controller for the Initial, -1317 // Handshake and Data(PathId::ZERO) spaces. -1318 let mut last_packet_number = None; -1319 -1320 // If we end up not sending anything, we need to know if that was because there was -1321 // nothing to send or because we were congestion blocked. -1322 let mut congestion_blocked = false; -1323 -1324 // Set the segment size to this path's MTU for on-path data. -1325 let pmtu = self.path_data(path_id).current_mtu().into(); -1326 let mut transmit = TransmitBuf::new(buf, max_datagrams, pmtu); -1327 -1328 // Iterate over the available spaces. -1329 for space_id in SpaceId::iter() { -1330 // Only PathId::ZERO uses non Data space ids. -1331 if path_id != PathId::ZERO && space_id != SpaceId::Data { -1332 continue; -1333 } -1334 match self.poll_transmit_path_space( -1335 now, -1336 &mut transmit, -1337 path_id, -1338 space_id, -1339 remote_cid, -1340 scheduling_info, -1341 connection_close_pending, -1342 pad_datagram, -1343 ) { -1344 PollPathSpaceStatus::NothingToSend { -1345 congestion_blocked: cb, -1346 } => { -1347 congestion_blocked |= cb; -1348 // Continue checking other spaces, tail-loss probes may need to be sent -1349 // in all spaces. -1350 } -1351 PollPathSpaceStatus::WrotePacket { -1352 last_packet_number: pn, -1353 pad_datagram: pad, -1354 } => { -1355 debug_assert!(!transmit.is_empty(), "transmit must contain packets"); -1356 last_packet_number = Some(pn); -1357 pad_datagram = pad; -1358 // Always check higher spaces. If the transmit is full or they have -1359 // nothing to send they will not write packets. But if they can, they -1360 // must always be allowed to add to this transmit because coalescing may -1361 // be required. -1362 continue; -1363 } -1364 PollPathSpaceStatus::Send { -1365 last_packet_number: pn, -1366 } => { -1367 debug_assert!(!transmit.is_empty(), "transmit must contain packets"); -1368 last_packet_number = Some(pn); -1369 break; -1370 } -1371 } -1372 } -1373 -1374 if last_packet_number.is_some() || congestion_blocked { -1375 self.qlog.emit_recovery_metrics( -1376 path_id, -1377 &mut self.paths.get_mut(&path_id).unwrap().data, -1378 now, -1379 ); -1380 } -1381 -1382 match last_packet_number { -1383 Some(last_packet_number) => { -1384 // Note that when sending in multiple spaces the last packet number will be -1385 // the one from the highest space. -1386 self.path_data_mut(path_id).congestion.on_sent( -1387 now, -1388 transmit.len() as u64, -1389 last_packet_number, -1390 ); -1391 PollPathStatus::Send(self.build_transmit(path_id, transmit)) -1392 } -1393 None => PollPathStatus::NothingToSend { congestion_blocked }, -1394 } -1395 } -1396 -1397 /// poll_transmit logic for a QUIC-MULTIPATH packet number space (PathID + SpaceId). -1398 #[must_use] -1399 fn poll_transmit_path_space( -1400 &mut self, -1401 now: Instant, -1402 transmit: &mut TransmitBuf<'_>, -1403 path_id: PathId, -1404 space_id: SpaceId, -1405 remote_cid: ConnectionId, -1406 scheduling_info: &PathSchedulingInfo, -1407 // If we need to send a CONNECTION_CLOSE frame. -1408 connection_close_pending: bool, -1409 // Whether the current datagram needs to be padded to a certain size. -1410 mut pad_datagram: PadDatagram, -1411 ) -> PollPathSpaceStatus { -1412 // Keep track of the last packet number we wrote. If None we did not write any -1413 // packets. -1414 let mut last_packet_number = None; -1415 -1416 // Each loop of this may build one packet. It works logically as follows: -1417 // -1418 // - Check if something *needs* to be sent in this space and *can* be sent. -1419 // - If not, return to the caller who will call us again for the next space. -1420 // - Start a new datagram. -1421 // - Unless coalescing the packet into an existing datagram. -1422 // - Write the packet header and payload. -1423 // - Check if coalescing a next packet into the datagram is possible. -1424 // - If coalescing, finish packet without padding to leave space in the datagram. -1425 // - If not coalescing, complete the datagram: -1426 // - Finish packet with padding. -1427 // - Set the transmit segment size if this is the first datagram. -1428 // - Loop: next iteration will exit the loop if nothing more to send in this -1429 // space. The TransmitBuf will contain a started datagram with space if -1430 // coalescing, or completely filled datagram if not coalescing. -1431 loop { -1432 // Determine if anything can be sent in this packet number space. -1433 let max_packet_size = if transmit.datagram_remaining_mut() > 0 { -1434 // A datagram is started already, we are coalescing another packet into it. -1435 transmit.datagram_remaining_mut() -1436 } else { -1437 // A new datagram needs to be started. -1438 transmit.segment_size() -1439 }; -1440 let can_send = -1441 self.space_can_send(space_id, path_id, max_packet_size, connection_close_pending); -1442 let needs_loss_probe = self.spaces[space_id].for_path(path_id).loss_probes > 0; -1443 let space_will_send = { -1444 if scheduling_info.is_abandoned { -1445 // If this path is abandoned then we might still have to send -1446 // PATH_ABANDON itself on it if there was no better space -1447 // available. Otherwise we want to send the PATH_ABANDON as permitted by -1448 // may_send_data however. -1449 scheduling_info.may_self_abandon -1450 && self.spaces[space_id] -1451 .pending -1452 .path_abandon -1453 .contains_key(&path_id) -1454 } else if can_send.close && scheduling_info.may_send_close { -1455 // This is the best path to send a CONNECTION_CLOSE on. -1456 true -1457 } else if needs_loss_probe || can_send.space_specific { -1458 // We always send a loss probe or space-specific frames if the path is -1459 // not abandoned. -1460 true -1461 } else { -1462 // Anything else we only send if we're the best path for SpaceKind::Data -1463 // frames. -1464 !can_send.is_empty() && scheduling_info.may_send_data -1465 } -1466 }; -1467 -1468 if !space_will_send { -1469 // Nothing more to send. Previous iterations of this loop may have built -1470 // packets already. -1471 return match last_packet_number { -1472 Some(pn) => PollPathSpaceStatus::WrotePacket { -1473 last_packet_number: pn, -1474 pad_datagram, -1475 }, -1476 None => { -1477 // Only log for spaces which have crypto. -1478 if self.crypto_state.has_keys(space_id.encryption_level()) -1479 || (space_id == SpaceId::Data -1480 && self.crypto_state.has_keys(EncryptionLevel::ZeroRtt)) -1481 { -1482 trace!(?space_id, %path_id, "nothing to send in space"); -1483 } -1484 PollPathSpaceStatus::NothingToSend { -1485 congestion_blocked: false, -1486 } -1487 } -1488 }; -1489 } -1490 -1491 // We want to send on this space, check congestion control if we can. But only -1492 // if we will need to start a new datagram. If we are coalescing into an already -1493 // started datagram we do not need to check congestion control again. -1494 if transmit.datagram_remaining_mut() == 0 { -1495 let congestion_blocked = -1496 self.path_congestion_check(space_id, path_id, transmit, &can_send, now); -1497 if congestion_blocked != PathBlocked::No { -1498 // Previous iterations of this loop may have built packets already. -1499 return match last_packet_number { -1500 Some(pn) => PollPathSpaceStatus::WrotePacket { -1501 last_packet_number: pn, -1502 pad_datagram, -1503 }, -1504 None => { -1505 return PollPathSpaceStatus::NothingToSend { -1506 congestion_blocked: true, -1507 }; -1508 } -1509 }; -1510 } -1511 } +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; +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.datagram_remaining_mut() == 0 { -1516 if transmit.num_datagrams() >= transmit.max_datagrams().get() { -1517 // No more datagrams allowed. -1518 // Previous iterations of this loop may have built packets already. -1519 return match last_packet_number { -1520 Some(pn) => PollPathSpaceStatus::WrotePacket { -1521 last_packet_number: pn, -1522 pad_datagram, -1523 }, -1524 None => { -1525 return PollPathSpaceStatus::NothingToSend { -1526 congestion_blocked: false, -1527 }; -1528 } -1529 }; -1530 } -1531 -1532 match self.spaces[space_id].for_path(path_id).loss_probes { -1533 0 => transmit.start_new_datagram(), -1534 _ => { -1535 // We need something to send for a tail-loss probe. -1536 let request_immediate_ack = -1537 space_id == SpaceId::Data && self.peer_supports_ack_frequency(); -1538 self.spaces[space_id].maybe_queue_probe( -1539 path_id, -1540 request_immediate_ack, -1541 &self.streams, -1542 ); -1543 -1544 self.spaces[space_id].for_path(path_id).loss_probes -= 1; -1545 -1546 // Clamp the datagram to at most the minimum MTU to ensure that loss -1547 // probes can get through and enable recovery even if the path MTU -1548 // has shrank unexpectedly. -1549 transmit.start_new_datagram_with_size(std::cmp::min( -1550 usize::from(INITIAL_MTU), -1551 transmit.segment_size(), -1552 )); -1553 } -1554 } -1555 trace!(count = transmit.num_datagrams(), "new datagram started"); -1556 -1557 // We started a new datagram, we decide later if it needs padding. -1558 pad_datagram = PadDatagram::No; -1559 } -1560 -1561 // If coalescing another packet into the existing datagram, there should -1562 // still be enough space for a whole packet. -1563 if transmit.datagram_start_offset() < transmit.len() { -1564 debug_assert!(transmit.datagram_remaining_mut() >= MIN_PACKET_SPACE); -1565 } -1566 -1567 // -1568 // From here on, we've determined that a packet will definitely be sent. -1569 // -1570 -1571 if self.crypto_state.has_keys(EncryptionLevel::Initial) -1572 && space_id == SpaceId::Handshake -1573 && self.side.is_client() -1574 { -1575 // A client stops both sending and processing Initial packets when it -1576 // sends its first Handshake packet. -1577 self.discard_space(now, SpaceKind::Initial); -1578 } -1579 if let Some(ref mut prev) = self.crypto_state.prev_crypto { -1580 prev.update_unacked = false; -1581 } -1582 -1583 let Some(mut builder) = PacketBuilder::new( -1584 now, -1585 space_id, -1586 path_id, -1587 remote_cid, -1588 transmit, -1589 can_send.is_ack_eliciting(), -1590 self, -1591 ) else { -1592 // Confidentiality limit is exceeded and the connection has been killed. We -1593 // should not send any other packets. This works in a roundabout way: We -1594 // have started a datagram but not written anything into it. So even if we -1595 // get called again for another space we will see an already started -1596 // datagram and try and start another packet here. Then be stopped by the -1597 // same confidentiality limit. -1598 return PollPathSpaceStatus::NothingToSend { -1599 congestion_blocked: false, -1600 }; -1601 }; -1602 last_packet_number = Some(builder.packet_number); -1603 -1604 if space_id == SpaceId::Initial -1605 && (self.side.is_client() || can_send.is_ack_eliciting()) -1606 { -1607 // https://www.rfc-editor.org/rfc/rfc9000.html#section-14.1 -1608 pad_datagram |= PadDatagram::ToMinMtu; -1609 } -1610 if space_id == SpaceId::Data && self.config.pad_to_mtu { -1611 pad_datagram |= PadDatagram::ToSegmentSize; -1612 } -1613 -1614 if scheduling_info.may_send_close && can_send.close { -1615 trace!("sending CONNECTION_CLOSE"); -1616 // Encode ACKs before the ConnectionClose message, to give the receiver -1617 // a better approximate on what data has been processed. This is -1618 // especially important with ack delay, since the peer might not -1619 // have gotten any other ACK for the data earlier on. -1620 let is_multipath_negotiated = self.is_multipath_negotiated(); -1621 for path_id in self.spaces[space_id] -1622 .number_spaces -1623 .iter() -1624 .filter(|(_, pns)| !pns.pending_acks.ranges().is_empty()) -1625 .map(|(&path_id, _)| path_id) -1626 .collect::<Vec<_>>() -1627 { -1628 Self::populate_acks( -1629 now, -1630 self.receiving_ecn, -1631 path_id, -1632 space_id, -1633 &mut self.spaces[space_id], -1634 is_multipath_negotiated, -1635 &mut builder, -1636 &mut self.path_stats.for_path(path_id).frame_tx, -1637 self.crypto_state.has_keys(space_id.encryption_level()), -1638 ); -1639 } -1640 -1641 // Since there only 64 ACK frames there will always be enough space -1642 // to encode the ConnectionClose frame too. However we still have the -1643 // check here to prevent crashes if something changes. -1644 -1645 // TODO(flub): This needs fixing for multipath, to ensure we can always -1646 // write the CONNECTION_CLOSE even if we have many PATH_ACKs to send: -1647 // https://github.com/n0-computer/noq/issues/367. -1648 debug_assert!( -1649 builder.frame_space_remaining() > frame::ConnectionClose::SIZE_BOUND, -1650 "ACKs should leave space for ConnectionClose" -1651 ); -1652 let stats = &mut self.path_stats.for_path(path_id).frame_tx; -1653 if frame::ConnectionClose::SIZE_BOUND < builder.frame_space_remaining() { -1654 let max_frame_size = builder.frame_space_remaining(); -1655 let close: Close = match self.state.as_type() { -1656 StateType::Closed => { -1657 let reason: Close = -1658 self.state.as_closed().expect("checked").clone().into(); -1659 if space_id == SpaceId::Data || reason.is_transport_layer() { -1660 reason -1661 } else { -1662 TransportError::APPLICATION_ERROR("").into() -1663 } -1664 } -1665 StateType::Draining => TransportError::NO_ERROR("").into(), -1666 _ => unreachable!( -1667 "tried to make a close packet when the connection wasn't closed" -1668 ), -1669 }; -1670 builder.write_frame(close.encoder(max_frame_size), stats); -1671 } -1672 let last_pn = builder.packet_number; -1673 builder.finish_and_track(now, self, path_id, pad_datagram); -1674 if space_id.kind() == self.highest_space { -1675 // Don't send another close packet. Even with multipath we only send -1676 // CONNECTION_CLOSE on a single path since we expect our paths to work. -1677 self.connection_close_pending = false; -1678 } -1679 // Send a close frame in every possible space for robustness, per -1680 // RFC9000 "Immediate Close during the Handshake". Don't bother trying -1681 // to send anything else. -1682 // TODO(flub): This breaks during the handshake if we can not coalesce -1683 // packets due to space reasons: the next space would either fail a -1684 // debug_assert checking for enough packet space or produce an invalid -1685 // packet. We need to keep track of per-space pending CONNECTION_CLOSE to -1686 // be able to send these across multiple calls to poll_transmit. Then -1687 // check for coalescing space here because initial packets need to be in -1688 // padded datagrams. And also add space checks for CONNECTION_CLOSE in -1689 // space_can_send so it would stop a GSO batch if the datagram is too -1690 // small for another CONNECTION_CLOSE packet. -1691 return PollPathSpaceStatus::WrotePacket { -1692 last_packet_number: last_pn, -1693 pad_datagram, -1694 }; -1695 } -1696 -1697 self.populate_packet(now, space_id, path_id, scheduling_info, &mut builder); -1698 -1699 // ACK-only packets should only be sent when explicitly allowed. If we write them due to -1700 // any other reason, there is a bug which leads to one component announcing write -1701 // readiness while not writing any data. This degrades performance. The condition is -1702 // only checked if the full MTU is available and when potentially large fixed-size -1703 // frames aren't queued, so that lack of space in the datagram isn't the reason for just -1704 // writing ACKs. -1705 debug_assert!( -1706 !(builder.sent_frames().is_ack_only(&self.streams) -1707 && !can_send.acks -1708 && (can_send.other || can_send.space_specific) -1709 && builder.buf.segment_size() -1710 == self.path_data(path_id).current_mtu() as usize -1711 && self.datagrams.outgoing.is_empty()), -1712 "SendableFrames was {can_send:?}, but only ACKs have been written" -1713 ); -1714 if builder.sent_frames().requires_padding { -1715 pad_datagram |= PadDatagram::ToMinMtu; -1716 } -1717 -1718 for (path_id, _pn) in builder.sent_frames().largest_acked.iter() { -1719 self.spaces[space_id] -1720 .for_path(*path_id) -1721 .pending_acks -1722 .acks_sent(); -1723 self.timers.stop( -1724 Timer::PerPath(*path_id, PathTimer::MaxAckDelay), -1725 self.qlog.with_time(now), -1726 ); -1727 } -1728 -1729 // Now we need to finish the packet. Before we do so we need to know if we will -1730 // be coalescing the next packet into this one, or will be ending the datagram -1731 // as well. Because if this is the last packet in the datagram more padding -1732 // might be needed because of the packet type, or to fill the GSO segment size. -1733 -1734 // Are we allowed to coalesce AND is there enough space for another *packet* in -1735 // this datagram AND will we definitely send another packet? -1736 if builder.can_coalesce && path_id == PathId::ZERO && { -1737 let max_packet_size = builder -1738 .buf -1739 .datagram_remaining_mut() -1740 .saturating_sub(builder.predict_packet_end()); -1741 max_packet_size > MIN_PACKET_SPACE -1742 && self.has_pending_packet(space_id, max_packet_size, connection_close_pending) -1743 } { -1744 // We can append/coalesce the next packet into the current -1745 // datagram. Finish the current packet without adding extra padding. -1746 trace!("will coalesce with next packet"); -1747 builder.finish_and_track(now, self, path_id, PadDatagram::No); -1748 } else { -1749 // We need a new datagram for the next packet. Finish the current -1750 // packet with padding. -1751 // TODO(flub): if there isn't any more data to be sent, this will still pad -1752 // to the segment size and only discover there is nothing to send before -1753 // starting the next packet. That is wasting up to 32 bytes. -1754 if builder.buf.num_datagrams() > 1 && matches!(pad_datagram, PadDatagram::No) { -1755 // If too many padding bytes would be required to continue the -1756 // GSO batch after this packet, end the GSO batch here. Ensures -1757 // that fixed-size frames with heterogeneous sizes -1758 // (e.g. application datagrams) won't inadvertently waste large -1759 // amounts of bandwidth. The exact threshold is a bit arbitrary -1760 // and might benefit from further tuning, though there's no -1761 // universally optimal value. -1762 const MAX_PADDING: usize = 32; -1763 if builder.buf.datagram_remaining_mut() -1764 > builder.predict_packet_end() + MAX_PADDING -1765 { -1766 trace!( -1767 "GSO truncated by demand for {} padding bytes", -1768 builder.buf.datagram_remaining_mut() - builder.predict_packet_end() -1769 ); -1770 let last_pn = builder.packet_number; -1771 builder.finish_and_track(now, self, path_id, PadDatagram::No); -1772 return PollPathSpaceStatus::Send { -1773 last_packet_number: last_pn, -1774 }; -1775 } -1776 -1777 // Pad the current datagram to GSO segment size so it can be -1778 // included in the GSO batch. -1779 builder.finish_and_track(now, self, path_id, PadDatagram::ToSegmentSize); -1780 } else { -1781 builder.finish_and_track(now, self, path_id, pad_datagram); -1782 } -1783 -1784 // If this is the first datagram we set the segment size to the size of the -1785 // first datagram. -1786 if transmit.num_datagrams() == 1 { -1787 transmit.clip_segment_size(); -1788 } -1789 } -1790 } -1791 } +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 } +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 fn poll_transmit_mtu_probe( -1794 &mut self, -1795 now: Instant, -1796 buf: &mut Vec<u8>, -1797 path_id: PathId, -1798 ) -> Option<Transmit> { -1799 let (active_cid, probe_size) = self.get_mtu_probe_data(now, path_id)?; -1800 -1801 // We are definitely sending a DPLPMTUD probe. -1802 let mut transmit = TransmitBuf::new(buf, NonZeroUsize::MIN, probe_size as usize); -1803 transmit.start_new_datagram_with_size(probe_size as usize); -1804 -1805 let mut builder = PacketBuilder::new( -1806 now, -1807 SpaceId::Data, -1808 path_id, -1809 active_cid, -1810 &mut transmit, -1811 true, -1812 self, -1813 )?; -1814 -1815 // We implement MTU probes as ping packets padded up to the probe size -1816 trace!(?probe_size, "writing MTUD probe"); -1817 builder.write_frame(frame::Ping, &mut self.path_stats.for_path(path_id).frame_tx); +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 // If supported by the peer, we want no delays to the probe's ACK -1820 if self.peer_supports_ack_frequency() { -1821 builder.write_frame( -1822 frame::ImmediateAck, -1823 &mut self.path_stats.for_path(path_id).frame_tx, -1824 ); -1825 } -1826 -1827 builder.finish_and_track(now, self, path_id, PadDatagram::ToSize(probe_size)); -1828 -1829 self.path_stats.for_path(path_id).sent_plpmtud_probes += 1; -1830 -1831 Some(self.build_transmit(path_id, transmit)) -1832 } -1833 -1834 /// Returns the CID and probe size if a DPLPMTUD probe is needed. -1835 /// -1836 /// We MTU probe all paths for which all of the following is true: -1837 /// - We have an active destination CID for the path. -1838 /// - The remote address *and* path are validated. -1839 /// - The path is not abandoned. -1840 /// - The MTU Discovery subsystem wants to probe the path. -1841 fn get_mtu_probe_data(&mut self, now: Instant, path_id: PathId) -> Option<(ConnectionId, u16)> { -1842 let active_cid = self.remote_cids.get(&path_id).map(CidQueue::active)?; -1843 let is_eligible = self.path_data(path_id).validated -1844 && !self.path_data(path_id).is_validating_path() -1845 && !self.abandoned_paths.contains(&path_id); -1846 -1847 if !is_eligible { -1848 return None; -1849 } -1850 let next_pn = self.spaces[SpaceId::Data] -1851 .for_path(path_id) -1852 .peek_tx_number(); -1853 let probe_size = self -1854 .path_data_mut(path_id) -1855 .mtud -1856 .poll_transmit(now, next_pn)?; -1857 -1858 Some((active_cid, probe_size)) -1859 } -1860 -1861 /// Returns true if there is a further packet to send on [`PathId::ZERO`]. -1862 /// -1863 /// In other words this is predicting whether the next call to -1864 /// [`Connection::space_can_send`] issued will return some frames to be sent. Including -1865 /// having to predict which packet number space it will be invoked with. This depends on -1866 /// how both [`Connection::poll_transmit_on_path`] and -1867 /// [`Connection::poll_transmit_path_space`] behave. -1868 /// -1869 /// This is needed to determine if packet coalescing can happen. Because the last packet -1870 /// in a datagram may need to be padded and thus we must know if another packet will -1871 /// follow or not. -1872 /// -1873 /// The next packet can be either in the same space, or in one of the following spaces -1874 /// on the same path. Because a 0-RTT packet can be coalesced with a 1-RTT packet and -1875 /// both are in the Data(PathId::ZERO) space. Previous spaces are not checked, because -1876 /// packets are built from Initial to Handshake to Data spaces. -1877 fn has_pending_packet( -1878 &mut self, -1879 current_space_id: SpaceId, -1880 max_packet_size: usize, -1881 connection_close_pending: bool, -1882 ) -> bool { -1883 let mut space_id = current_space_id; -1884 loop { -1885 let can_send = self.space_can_send( -1886 space_id, -1887 PathId::ZERO, -1888 max_packet_size, -1889 connection_close_pending, -1890 ); -1891 if !can_send.is_empty() { -1892 return true; -1893 } -1894 match space_id.next() { -1895 Some(next_space_id) => space_id = next_space_id, -1896 None => break, -1897 } -1898 } -1899 false -1900 } -1901 -1902 /// Checks if creating a new datagram would be blocked by congestion control -1903 fn path_congestion_check( -1904 &mut self, -1905 space_id: SpaceId, -1906 path_id: PathId, -1907 transmit: &TransmitBuf<'_>, -1908 can_send: &SendableFrames, -1909 now: Instant, -1910 ) -> PathBlocked { -1911 // Anti-amplification is only based on `total_sent`, which gets updated after -1912 // the transmit is sent. Therefore we pass the amount of bytes for datagrams -1913 // that are already created, as well as 1 byte for starting another datagram. If -1914 // there is any anti-amplification budget left, we always allow a full MTU to be -1915 // sent (see https://github.com/quinn-rs/quinn/issues/1082). -1916 if self.side().is_server() -1917 && self -1918 .path_data(path_id) -1919 .anti_amplification_blocked(transmit.len() as u64 + 1) -1920 { -1921 trace!(?space_id, %path_id, "blocked by anti-amplification"); -1922 return PathBlocked::AntiAmplification; -1923 } -1924 -1925 // Congestion control check. -1926 // Tail loss probes must not be blocked by congestion, or a deadlock could arise. -1927 let bytes_to_send = transmit.segment_size() as u64; -1928 let need_loss_probe = self.spaces[space_id].for_path(path_id).loss_probes > 0; -1929 -1930 if can_send.other && !need_loss_probe && !can_send.close { -1931 let path = self.path_data(path_id); -1932 if path.in_flight.bytes + bytes_to_send >= path.congestion.window() { -1933 trace!( -1934 ?space_id, -1935 %path_id, -1936 in_flight=%path.in_flight.bytes, -1937 congestion_window=%path.congestion.window(), -1938 "blocked by congestion control", -1939 ); -1940 return PathBlocked::Congestion; -1941 } -1942 } -1943 -1944 // Pacing check. -1945 if let Some(delay) = self.path_data_mut(path_id).pacing_delay(bytes_to_send, now) { -1946 self.timers.set( -1947 Timer::PerPath(path_id, PathTimer::Pacing), -1948 delay, -1949 self.qlog.with_time(now), -1950 ); -1951 // Loss probes and CONNECTION_CLOSE should be subject to pacing, even though -1952 // they are not congestion controlled. -1953 trace!(?space_id, %path_id, "blocked by pacing"); -1954 return PathBlocked::Pacing; -1955 } -1956 -1957 PathBlocked::No -1958 } -1959 -1960 /// Send PATH_CHALLENGE for a previous path if necessary -1961 /// -1962 /// QUIC-TRANSPORT section 9.3.3 -1963 /// <https://www.rfc-editor.org/rfc/rfc9000.html#name-off-path-packet-forwarding> -1964 fn send_prev_path_challenge( -1965 &mut self, -1966 now: Instant, -1967 buf: &mut Vec<u8>, -1968 path_id: PathId, -1969 ) -> Option<Transmit> { -1970 let (prev_cid, prev_path) = self.paths.get_mut(&path_id)?.prev.as_mut()?; -1971 // TODO (matheus23): We could use !prev_path.is_validating() here instead to -1972 // (possibly) also re-send challenges when they get lost. -1973 if !prev_path.pending_on_path_challenge { -1974 return None; -1975 }; -1976 prev_path.pending_on_path_challenge = false; -1977 let token = self.rng.random(); -1978 let network_path = prev_path.network_path; -1979 prev_path.record_path_challenge_sent(now, token, network_path); -1980 -1981 debug_assert_eq!( -1982 self.highest_space, -1983 SpaceKind::Data, -1984 "PATH_CHALLENGE queued without 1-RTT keys" -1985 ); -1986 let buf = &mut TransmitBuf::new(buf, NonZeroUsize::MIN, MIN_INITIAL_SIZE.into()); -1987 buf.start_new_datagram(); +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 // Use the previous CID to avoid linking the new path with the previous path. We -1990 // don't bother accounting for possible retirement of that prev_cid because this is -1991 // sent once, immediately after migration, when the CID is known to be valid. Even -1992 // if a post-migration packet caused the CID to be retired, it's fair to pretend -1993 // this is sent first. -1994 let mut builder = -1995 PacketBuilder::new(now, SpaceId::Data, path_id, *prev_cid, buf, false, self)?; -1996 let challenge = frame::PathChallenge(token); -1997 let stats = &mut self.path_stats.for_path(path_id).frame_tx; -1998 builder.write_frame_with_log_msg(challenge, stats, Some("validating previous path")); -1999 -2000 // An endpoint MUST expand datagrams that contain a PATH_CHALLENGE frame -2001 // to at least the smallest allowed maximum datagram size of 1200 bytes, -2002 // unless the anti-amplification limit for the path does not permit -2003 // sending a datagram of this size -2004 builder.pad_to(MIN_INITIAL_SIZE); -2005 -2006 builder.finish(self, now); -2007 self.path_stats -2008 .for_path(path_id) -2009 .udp_tx -2010 .on_sent(1, buf.len()); -2011 -2012 Some(Transmit { -2013 destination: network_path.remote, -2014 size: buf.len(), -2015 ecn: None, -2016 segment_size: None, -2017 src_ip: network_path.local_ip, -2018 }) -2019 } -2020 -2021 fn send_off_path_path_response( -2022 &mut self, -2023 now: Instant, -2024 buf: &mut Vec<u8>, -2025 path_id: PathId, -2026 ) -> Option<Transmit> { -2027 let path = self.paths.get_mut(&path_id).map(|state| &mut state.data)?; -2028 let cid_queue = self.remote_cids.get_mut(&path_id)?; -2029 let (token, network_path) = path.path_responses.pop_off_path(path.network_path)?; -2030 -2031 let cid = cid_queue -2032 .next_reserved() -2033 .unwrap_or_else(|| cid_queue.active()); -2034 // TODO(@divma): we should take a different approach when there is no fresh CID to use. -2035 // https://github.com/quinn-rs/quinn/issues/2184 -2036 -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, false, self)?; -2043 let stats = &mut self.path_stats.for_path(path_id).frame_tx; -2044 builder.write_frame_with_log_msg(frame, stats, Some("(off-path)")); -2045 // Off-path: not tracked in congestion control. The packet is sent to a -2046 // different destination than path_id's network path. -2047 builder.pad_to(MIN_INITIAL_SIZE); -2048 builder.finish(self, now); -2049 -2050 let size = buf.len(); -2051 -2052 self.path_stats.for_path(path_id).udp_tx.on_sent(1, size); -2053 Some(Transmit { -2054 destination: network_path.remote, -2055 size, -2056 ecn: None, -2057 segment_size: None, -2058 src_ip: network_path.local_ip, -2059 }) -2060 } -2061 -2062 /// Send a nat traversal challenge (off-path) on this path if possible. -2063 /// -2064 /// This will ensure the path still has a remaining CID to use if the active one should be -2065 /// retired. -2066 fn send_nat_traversal_path_challenge( -2067 &mut self, -2068 now: Instant, -2069 buf: &mut Vec<u8>, -2070 path_id: PathId, -2071 ) -> Option<Transmit> { -2072 let server_side = self.n0_nat_traversal.server_side_mut().ok()?; -2073 let probe = server_side.next_probe()?; -2074 if !self.paths.get(&path_id)?.data.validated { -2075 // Path is not usable for probing -2076 return None; -2077 } -2078 -2079 let remote_cids = self.remote_cids.get_mut(&path_id)?; -2080 -2081 // Check if this path has enough CIDs to send a probe. One to be reserved, one in case the -2082 // active CID needs to be retired. -2083 if remote_cids.remaining() < 2 { -2084 return None; -2085 } -2086 -2087 let cid = remote_cids.next_reserved()?; -2088 let remote = probe.remote(); -2089 let token = self.rng.random(); -2090 probe.mark_as_sent(); -2091 -2092 let frame = frame::PathChallenge(token); -2093 -2094 let mut buf = TransmitBuf::new(buf, NonZeroUsize::MIN, MIN_INITIAL_SIZE.into()); -2095 buf.start_new_datagram(); -2096 -2097 let mut builder = -2098 PacketBuilder::new(now, SpaceId::Data, path_id, cid, &mut buf, false, self)?; -2099 let stats = &mut self.path_stats.for_path(path_id).frame_tx; -2100 builder.write_frame_with_log_msg(frame, stats, Some("(nat-traversal)")); -2101 // Off-path: not tracked in congestion control. The packet is sent to a -2102 // different destination than path_id's network path. -2103 builder.pad_to(MIN_INITIAL_SIZE); -2104 builder.finish(self, now); -2105 -2106 let path = &mut self.paths.get_mut(&path_id).expect("checked").data; -2107 let network_path = FourTuple { -2108 remote, -2109 local_ip: None, -2110 }; -2111 -2112 path.record_path_challenge_sent(now, token, network_path); +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); +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 -2116 self.path_stats.for_path(path_id).udp_tx.on_sent(1, size); -2117 +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, +2119 destination: remote.into(), 2120 size, 2121 ecn: None, 2122 segment_size: None, @@ -2189,7 +2189,7 @@ 2189 let span = trace_span!("pkt", %path_id); 2190 let _guard = span.enter(); 2191 -2192 if self.update_network_path_or_discard(network_path, path_id) { +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 } @@ -2235,5342 +2235,5470 @@ 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 let cid_state = self -2239 .local_cid_state -2240 .entry(path_id) -2241 .or_insert_with(|| CidState::new(cid_len, cid_lifetime, now, 0)); -2242 cid_state.new_cids(&ids, now); -2243 -2244 ids.into_iter().rev().for_each(|frame| { -2245 self.spaces[SpaceId::Data].pending.new_cids.push(frame); -2246 }); -2247 // Always update Timer::PushNewCid -2248 self.reset_cid_retirement(now); -2249 } -2250 } -2251 } -2252 -2253 /// Updates the network path for `path_id`. -2254 /// -2255 /// Returns true if a packet coming in for this `path_id` over given `network_path` should be discarded. -2256 /// Returns false if the path was updated and the packet doesn't need to be discarded. -2257 fn update_network_path_or_discard(&mut self, network_path: FourTuple, path_id: PathId) -> bool { -2258 let remote_may_migrate = self.side.remote_may_migrate(&self.state); -2259 let local_ip_may_migrate = self.side.is_client(); -2260 // If this packet could initiate a migration and we're a client or a server that -2261 // forbids migration, drop the datagram. This could be relaxed to heuristically -2262 // permit NAT-rebinding-like migration. -2263 if let Some(known_path) = self.path_mut(path_id) { -2264 if network_path.remote != known_path.network_path.remote && !remote_may_migrate { -2265 trace!( -2266 %path_id, -2267 %network_path, -2268 %known_path.network_path, -2269 "discarding packet from unrecognized peer" -2270 ); -2271 return true; -2272 } -2273 -2274 if known_path.network_path.local_ip.is_some() -2275 && network_path.local_ip.is_some() -2276 && known_path.network_path.local_ip != network_path.local_ip -2277 && !local_ip_may_migrate -2278 { -2279 trace!( -2280 %path_id, -2281 %network_path, -2282 %known_path.network_path, -2283 "discarding packet sent to incorrect interface" -2284 ); -2285 return true; -2286 } -2287 // If the datagram indicates that we've changed our local IP, we update it. -2288 // This is alluded to in Section 5.2 of the Multipath RFC draft 18: -2289 // https://www.ietf.org/archive/id/draft-ietf-quic-multipath-18.html#name-using-multiple-paths-on-the -2290 // > Client receives the packet, recognizes a path migration, updates the source address of path 2 to 192.0.2.1. -2291 if let Some(local_ip) = network_path.local_ip { -2292 if known_path -2293 .network_path -2294 .local_ip -2295 .is_some_and(|ip| ip != local_ip) -2296 { -2297 debug!( -2298 %path_id, -2299 %network_path, -2300 %known_path.network_path, -2301 "path's local address seemingly migrated" -2302 ); -2303 } -2304 // We update the address without path validation on the client side. -2305 // https://www.ietf.org/archive/id/draft-ietf-quic-multipath-18.html#section-5.1 -2306 // > Servers observing a 4-tuple change will perform path validation (see Section 9 of [QUIC-TRANSPORT]). -2307 // This sounds like it's *only* the server endpoints that do this. -2308 // TODO(matheus23): We should still consider doing a proper migration on the client side in the future. -2309 // For now, this preserves the behavior of this code pre 4-tuple tracking. -2310 known_path.network_path.local_ip = Some(local_ip); -2311 } -2312 } -2313 false -2314 } -2315 -2316 /// Process timer expirations -2317 /// -2318 /// Executes protocol logic, potentially preparing signals (including application `Event`s, -2319 /// `EndpointEvent`s and outgoing datagrams) that should be extracted through the relevant -2320 /// methods. -2321 /// -2322 /// It is most efficient to call this immediately after the system clock reaches the latest -2323 /// `Instant` that was output by `poll_timeout`; however spurious extra calls will simply -2324 /// no-op and therefore are safe. -2325 pub fn handle_timeout(&mut self, now: Instant) { -2326 while let Some((timer, _time)) = self.timers.expire_before(now, &self.qlog) { -2327 // TODO(@divma): remove `at` when the unicorn is born -2328 trace!(?timer, at=?now, "timeout"); -2329 match timer { -2330 Timer::Conn(timer) => match timer { -2331 ConnTimer::Close => { -2332 self.state.move_to_drained(None); -2333 // move_to_drained checks that we weren't in drained before. -2334 // Adding events to endpoint_events is only legal if `Drained` was never queued before. -2335 self.endpoint_events.push_back(EndpointEventInner::Drained); -2336 } -2337 ConnTimer::Idle => { -2338 self.kill(ConnectionError::TimedOut); -2339 } -2340 ConnTimer::KeepAlive => { -2341 trace!("sending keep-alive"); -2342 self.ping(); -2343 } -2344 ConnTimer::KeyDiscard => { -2345 self.crypto_state.discard_temporary_keys(); -2346 } -2347 ConnTimer::PushNewCid => { -2348 while let Some((path_id, when)) = self.next_cid_retirement() { -2349 if when > now { -2350 break; -2351 } -2352 match self.local_cid_state.get_mut(&path_id) { -2353 None => error!(%path_id, "No local CID state for path"), -2354 Some(cid_state) => { -2355 // Update `retire_prior_to` field in NEW_CONNECTION_ID frame -2356 let num_new_cid = cid_state.on_cid_timeout().into(); -2357 if !self.state.is_closed() { -2358 trace!( -2359 "push a new CID to peer RETIRE_PRIOR_TO field {}", -2360 cid_state.retire_prior_to() -2361 ); -2362 self.endpoint_events.push_back( -2363 EndpointEventInner::NeedIdentifiers( -2364 path_id, -2365 now, -2366 num_new_cid, -2367 ), -2368 ); -2369 } -2370 } -2371 } -2372 } -2373 } -2374 ConnTimer::NoAvailablePath => { -2375 // Grace period expired: all paths were abandoned and no new path -2376 // was opened. Close the connection. There are no paths left to -2377 // send CONNECTION_CLOSE on, so this is a silent close. -2378 // https://www.ietf.org/archive/id/draft-ietf-quic-multipath-21.html#section-3.4-8 -2379 if self.state.is_closed() || self.state.is_drained() { -2380 // Connection already closing/drained (e.g. application called -2381 // close() before the grace timer fired). Nothing to do. -2382 error!("no viable path timer fired, but connection already closing"); -2383 } else { -2384 trace!("no viable path grace period expired, closing connection"); -2385 let err = TransportError::NO_VIABLE_PATH( -2386 "last path abandoned, no new path opened", -2387 ); -2388 self.close_common(); -2389 self.set_close_timer(now); -2390 self.connection_close_pending = true; -2391 self.state.move_to_closed(err); -2392 } +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 } +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 } +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 } +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 }, -2395 Timer::PerPath(path_id, timer) => { -2396 let span = trace_span!("per-path timer fired", %path_id, ?timer); -2397 let _guard = span.enter(); -2398 match timer { -2399 PathTimer::PathIdle => { -2400 if let Err(err) = -2401 self.close_path_inner(now, path_id, PathAbandonReason::TimedOut) -2402 { -2403 warn!(?err, "failed closing path"); -2404 } -2405 } -2406 -2407 PathTimer::PathKeepAlive => { -2408 trace!("sending keep-alive on path"); -2409 self.ping_path(path_id).ok(); -2410 } -2411 PathTimer::LossDetection => { -2412 self.on_loss_detection_timeout(now, path_id); -2413 self.qlog.emit_recovery_metrics( -2414 path_id, -2415 &mut self.paths.get_mut(&path_id).unwrap().data, -2416 now, -2417 ); -2418 } -2419 PathTimer::PathValidationFailed => { -2420 let Some(path) = self.paths.get_mut(&path_id) else { -2421 continue; -2422 }; -2423 self.timers.stop( -2424 Timer::PerPath(path_id, PathTimer::PathChallengeLost), -2425 self.qlog.with_time(now), -2426 ); -2427 debug!("path migration validation failed"); -2428 if let Some((_, prev)) = path.prev.take() { -2429 path.data = prev; -2430 } -2431 path.data.reset_on_path_challenges(); -2432 } -2433 PathTimer::PathChallengeLost => { -2434 let Some(path) = self.paths.get_mut(&path_id) else { -2435 continue; -2436 }; -2437 trace!("path challenge deemed lost"); -2438 path.data.pending_on_path_challenge = true; -2439 } -2440 PathTimer::PathOpenFailed => { -2441 let Some(path) = self.paths.get_mut(&path_id) else { -2442 continue; -2443 }; -2444 path.data.reset_on_path_challenges(); -2445 self.timers.stop( -2446 Timer::PerPath(path_id, PathTimer::PathChallengeLost), -2447 self.qlog.with_time(now), -2448 ); -2449 debug!("new path validation failed"); -2450 if let Err(err) = self.close_path_inner( -2451 now, -2452 path_id, -2453 PathAbandonReason::ValidationFailed, -2454 ) { -2455 warn!(?err, "failed closing path"); -2456 } -2457 } -2458 PathTimer::Pacing => trace!("pacing timer expired"), -2459 PathTimer::MaxAckDelay => { -2460 trace!("max ack delay reached"); -2461 // This timer is only armed in the Data space -2462 self.spaces[SpaceId::Data] -2463 .for_path(path_id) -2464 .pending_acks -2465 .on_max_ack_delay_timeout() -2466 } -2467 PathTimer::PathDrained => { -2468 // The path was abandoned and 3*PTO has expired since. Clean up all -2469 // remaining state and install stateless reset token. -2470 self.timers.stop_per_path(path_id, self.qlog.with_time(now)); -2471 if let Some(local_cid_state) = self.local_cid_state.remove(&path_id) { -2472 debug_assert!(!self.state.is_drained()); // requirement for endpoint_events. All timers should be cleared in drained connections. -2473 let (min_seq, max_seq) = local_cid_state.active_seq(); -2474 for seq in min_seq..=max_seq { -2475 self.endpoint_events.push_back( -2476 EndpointEventInner::RetireConnectionId( -2477 now, path_id, seq, false, -2478 ), -2479 ); -2480 } -2481 } -2482 self.discard_path(path_id, now); -2483 } -2484 } -2485 } -2486 } -2487 } -2488 } -2489 -2490 /// Close a connection immediately -2491 /// -2492 /// This does not ensure delivery of outstanding data. It is the application's responsibility to -2493 /// call this only when all important communications have been completed, e.g. by calling -2494 /// [`SendStream::finish`] on outstanding streams and waiting for the corresponding -2495 /// [`StreamEvent::Finished`] event. -2496 /// -2497 /// If [`Streams::send_streams`] returns 0, all outstanding stream data has been -2498 /// delivered. There may still be data from the peer that has not been received. -2499 /// -2500 /// [`StreamEvent::Finished`]: crate::StreamEvent::Finished -2501 pub fn close(&mut self, now: Instant, error_code: VarInt, reason: Bytes) { -2502 self.close_inner( -2503 now, -2504 Close::Application(frame::ApplicationClose { error_code, reason }), -2505 ) -2506 } -2507 -2508 /// Close the connection immediately, initiated by an API call. -2509 /// -2510 /// This will not produce a [`ConnectionLost`] event propagated by the -2511 /// [`Connection::poll`] call, because the API call already propagated the error to the -2512 /// user. -2513 /// -2514 /// Not to be used when entering immediate close due to an internal state change based -2515 /// on an event. See [`State::move_to_closed_local`] for details. -2516 /// -2517 /// This initiates immediate close from -2518 /// <https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2>, moving to the closed -2519 /// state. -2520 /// -2521 /// [`ConnectionLost`]: crate::Event::ConnectionLost -2522 /// [`Connection::poll`]: super::Connection::poll -2523 fn close_inner(&mut self, now: Instant, reason: Close) { -2524 let was_closed = self.state.is_closed(); -2525 if !was_closed { -2526 self.close_common(); -2527 self.set_close_timer(now); -2528 self.connection_close_pending = true; -2529 self.state.move_to_closed_local(reason); -2530 } -2531 } -2532 -2533 /// Control datagrams -2534 pub fn datagrams(&mut self) -> Datagrams<'_> { -2535 Datagrams { conn: self } -2536 } -2537 -2538 /// Returns connection statistics -2539 pub fn stats(&mut self) -> ConnectionStats { -2540 let mut stats = self.partial_stats.clone(); -2541 -2542 for path_stats in self.path_stats.iter_stats() { -2543 // Self::path_stats() computes the path rtt, cwnd and current_mtu on access -2544 // because they are not simple counters. When computing the connection stats we -2545 // can skip that effort since those fields are not used in the `impl -2546 // Add<PathStats> for ConnectionStats`. -2547 stats += *path_stats; -2548 } -2549 -2550 stats -2551 } -2552 -2553 /// Returns path statistics -2554 pub fn path_stats(&mut self, path_id: PathId) -> Option<PathStats> { -2555 let path = self.paths.get(&path_id)?; -2556 let stats = self.path_stats.for_path(path_id); -2557 stats.rtt = path.data.rtt.get(); -2558 stats.cwnd = path.data.congestion.window(); -2559 stats.current_mtu = path.data.mtud.current_mtu(); -2560 Some(*stats) -2561 } -2562 -2563 /// Ping the remote endpoint -2564 /// -2565 /// Causes an ACK-eliciting packet to be transmitted on the connection. -2566 pub fn ping(&mut self) { -2567 // TODO(flub): This is very brute-force: it pings *all* the paths. Instead it would -2568 // be nice if we could only send a single packet for this. -2569 for path_data in self.spaces[self.highest_space].number_spaces.values_mut() { -2570 path_data.ping_pending = true; -2571 } -2572 } -2573 -2574 /// Ping the remote endpoint over a specific path -2575 /// -2576 /// Causes an ACK-eliciting packet to be transmitted on the path. -2577 pub fn ping_path(&mut self, path: PathId) -> Result<(), ClosedPath> { -2578 let path_data = self.spaces[self.highest_space] -2579 .number_spaces -2580 .get_mut(&path) -2581 .ok_or(ClosedPath { _private: () })?; -2582 path_data.ping_pending = true; -2583 Ok(()) -2584 } -2585 -2586 /// Update traffic keys spontaneously +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 can be useful for testing key updates, as they otherwise only happen infrequently. -2589 pub fn force_key_update(&mut self) { -2590 if !self.state.is_established() { -2591 debug!("ignoring forced key update in illegal state"); -2592 return; -2593 } -2594 if self.crypto_state.prev_crypto.is_some() { -2595 // We already just updated, or are currently updating, the keys. Concurrent key updates -2596 // are illegal. -2597 debug!("ignoring redundant forced key update"); -2598 return; -2599 } -2600 self.crypto_state.update_keys(None, false); -2601 } -2602 -2603 /// Get a session reference -2604 pub fn crypto_session(&self) -> &dyn crypto::Session { -2605 self.crypto_state.session.as_ref() -2606 } -2607 -2608 /// Whether the connection is in the process of being established -2609 /// -2610 /// If this returns `false`, the connection may be either established or closed, signaled by the -2611 /// emission of a `Connected` or `ConnectionLost` message respectively. -2612 pub fn is_handshaking(&self) -> bool { -2613 self.state.is_handshake() -2614 } -2615 -2616 /// Whether the connection is closed -2617 /// -2618 /// Closed connections cannot transport any further data. A connection becomes closed when -2619 /// either peer application intentionally closes it, or when either transport layer detects an -2620 /// error such as a time-out or certificate validation failure. -2621 /// -2622 /// A `ConnectionLost` event is emitted with details when the connection becomes closed. -2623 pub fn is_closed(&self) -> bool { -2624 self.state.is_closed() -2625 } -2626 -2627 /// Whether there is no longer any need to keep the connection around -2628 /// -2629 /// Closed connections become drained after a brief timeout to absorb any remaining in-flight -2630 /// packets from the peer. All drained connections have been closed. -2631 pub fn is_drained(&self) -> bool { -2632 self.state.is_drained() -2633 } -2634 -2635 /// For clients, if the peer accepted the 0-RTT data packets -2636 /// -2637 /// The value is meaningless until after the handshake completes. -2638 pub fn accepted_0rtt(&self) -> bool { -2639 self.crypto_state.accepted_0rtt -2640 } -2641 -2642 /// Whether 0-RTT is/was possible during the handshake -2643 pub fn has_0rtt(&self) -> bool { -2644 self.crypto_state.zero_rtt_enabled -2645 } -2646 -2647 /// Whether there are any pending retransmits -2648 pub fn has_pending_retransmits(&self) -> bool { -2649 !self.spaces[SpaceId::Data].pending.is_empty(&self.streams) -2650 } -2651 -2652 /// Look up whether we're the client or server of this Connection -2653 pub fn side(&self) -> Side { -2654 self.side.side() +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 } +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 } 2656 -2657 /// Get the address observed by the remote over the given path -2658 pub fn path_observed_address(&self, path_id: PathId) -> Result<Option<SocketAddr>, ClosedPath> { -2659 self.path(path_id) -2660 .map(|path_data| { -2661 path_data -2662 .last_observed_addr_report -2663 .as_ref() -2664 .map(|observed| observed.socket_addr()) -2665 }) -2666 .ok_or(ClosedPath { _private: () }) -2667 } -2668 -2669 /// Current best estimate of this connection's latency (round-trip-time) -2670 pub fn rtt(&self, path_id: PathId) -> Option<Duration> { -2671 self.path(path_id).map(|d| d.rtt.get()) +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 /// Current state of this connection's congestion controller, for debugging purposes -2675 pub fn congestion_state(&self, path_id: PathId) -> Option<&dyn Controller> { -2676 self.path(path_id).map(|d| d.congestion.as_ref()) +2674 /// Get a session reference +2675 pub fn crypto_session(&self) -> &dyn crypto::Session { +2676 self.crypto_state.session.as_ref() 2677 } 2678 -2679 /// Modify the number of remotely initiated streams that may be concurrently open +2679 /// Whether the connection is in the process of being established 2680 /// -2681 /// No streams may be opened by the peer unless fewer than `count` are already open. Large -2682 /// `count`s increase both minimum and worst-case memory consumption. -2683 pub fn set_max_concurrent_streams(&mut self, dir: Dir, count: VarInt) { -2684 self.streams.set_max_concurrent(dir, count); -2685 // If the limit was reduced, then a flow control update previously deemed insignificant may -2686 // now be significant. -2687 let pending = &mut self.spaces[SpaceId::Data].pending; -2688 self.streams.queue_max_stream_id(pending); -2689 } -2690 -2691 /// Modify the number of open paths allowed when multipath is enabled -2692 /// -2693 /// When reducing the number of concurrent paths this will only affect delaying sending -2694 /// new MAX_PATH_ID frames until fewer than this number of paths are possible. To -2695 /// actively reduce paths they must be closed using [`Connection::close_path`], which -2696 /// can also be used to close not-yet-opened paths. +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 /// If multipath is not negotiated (see the [`TransportConfig`]) this can not enable -2699 /// multipath and will fail. -2700 pub fn set_max_concurrent_paths( -2701 &mut self, -2702 now: Instant, -2703 count: NonZeroU32, -2704 ) -> Result<(), MultipathNotNegotiated> { -2705 if !self.is_multipath_negotiated() { -2706 return Err(MultipathNotNegotiated { _private: () }); -2707 } -2708 self.max_concurrent_paths = count; -2709 -2710 let in_use_count = self -2711 .local_max_path_id -2712 .next() -2713 .saturating_sub(self.abandoned_paths.len() as u32) -2714 .as_u32(); -2715 let extra_needed = count.get().saturating_sub(in_use_count); -2716 let new_max_path_id = self.local_max_path_id.saturating_add(extra_needed); -2717 -2718 self.set_max_path_id(now, new_max_path_id); -2719 -2720 Ok(()) -2721 } -2722 -2723 /// If needed, issues a new MAX_PATH_ID frame and new CIDs for any newly allowed paths -2724 fn set_max_path_id(&mut self, now: Instant, max_path_id: PathId) { -2725 if max_path_id <= self.local_max_path_id { -2726 return; -2727 } -2728 -2729 self.local_max_path_id = max_path_id; -2730 self.spaces[SpaceId::Data].pending.max_path_id = true; -2731 -2732 self.issue_first_path_cids(now); -2733 } -2734 -2735 /// Current number of remotely initiated streams that may be concurrently open -2736 /// -2737 /// If the target for this limit is reduced using [`set_max_concurrent_streams`](Self::set_max_concurrent_streams), -2738 /// it will not change immediately, even if fewer streams are open. Instead, it will -2739 /// decrement by one for each time a remotely initiated stream of matching directionality is closed. -2740 pub fn max_concurrent_streams(&self, dir: Dir) -> u64 { -2741 self.streams.max_concurrent(dir) -2742 } -2743 -2744 /// See [`TransportConfig::send_window()`] -2745 pub fn set_send_window(&mut self, send_window: u64) { -2746 self.streams.set_send_window(send_window); -2747 } -2748 -2749 /// See [`TransportConfig::receive_window()`] -2750 pub fn set_receive_window(&mut self, receive_window: VarInt) { -2751 if self.streams.set_receive_window(receive_window) { -2752 self.spaces[SpaceId::Data].pending.max_data = true; -2753 } -2754 } -2755 -2756 /// Whether the Multipath for QUIC extension is enabled. -2757 /// -2758 /// Multipath is only enabled after the handshake is completed and if it was enabled by both -2759 /// peers. -2760 pub fn is_multipath_negotiated(&self) -> bool { -2761 !self.is_handshaking() -2762 && self.config.max_concurrent_multipath_paths.is_some() -2763 && self.peer_params.initial_max_path_id.is_some() -2764 } -2765 -2766 fn on_ack_received( -2767 &mut self, -2768 now: Instant, -2769 space: SpaceId, -2770 ack: frame::Ack, -2771 ) -> Result<(), TransportError> { -2772 // All ACKs are referencing path 0 -2773 let path = PathId::ZERO; -2774 self.inner_on_ack_received(now, space, path, ack) -2775 } -2776 -2777 fn on_path_ack_received( -2778 &mut self, -2779 now: Instant, -2780 space: SpaceId, -2781 path_ack: frame::PathAck, -2782 ) -> Result<(), TransportError> { -2783 let (ack, path) = path_ack.into_ack(); -2784 self.inner_on_ack_received(now, space, path, ack) -2785 } -2786 -2787 /// Handles an ACK frame acknowledging packets sent on *path*. -2788 fn inner_on_ack_received( -2789 &mut self, -2790 now: Instant, -2791 space: SpaceId, -2792 path: PathId, -2793 ack: frame::Ack, -2794 ) -> Result<(), TransportError> { -2795 if !self.spaces[space].number_spaces.contains_key(&path) -2796 && self.abandoned_paths.contains(&path) -2797 { -2798 // See also -2799 // https://www.ietf.org/archive/id/draft-ietf-quic-multipath-21.html#section-3.4.3-3 -2800 // > When an endpoint finally deletes all state associated with the path [...] -2801 // > PATH_ACK frames received with an abandoned path ID are silently ignored, -2802 // > as specified in Section 4. -2803 trace!("silently ignoring PATH_ACK on discarded path"); -2804 return Ok(()); -2805 } -2806 if ack.largest >= self.spaces[space].for_path(path).next_packet_number { -2807 return Err(TransportError::PROTOCOL_VIOLATION("unsent packet acked")); -2808 } -2809 let new_largest = { -2810 let space = &mut self.spaces[space].for_path(path); -2811 if space.largest_acked_packet.is_none_or(|pn| ack.largest > pn) { -2812 space.largest_acked_packet = Some(ack.largest); -2813 if let Some(info) = space.sent_packets.get(ack.largest) { -2814 // This should always succeed, but a misbehaving peer might ACK a packet we -2815 // haven't sent. At worst, that will result in us spuriously reducing the -2816 // congestion window. -2817 space.largest_acked_packet_sent = info.time_sent; -2818 } -2819 true -2820 } else { -2821 false -2822 } -2823 }; -2824 -2825 if self.detect_spurious_loss(&ack, space, path) { -2826 self.path_data_mut(path) -2827 .congestion -2828 .on_spurious_congestion_event(); -2829 } -2830 -2831 // Avoid DoS from unreasonably huge ack ranges by filtering out just the new acks. -2832 let mut newly_acked = ArrayRangeSet::new(); -2833 for range in ack.iter() { -2834 self.spaces[space].for_path(path).check_ack(range.clone())?; -2835 for (pn, _) in self.spaces[space] -2836 .for_path(path) -2837 .sent_packets -2838 .iter_range(range) -2839 { -2840 newly_acked.insert_one(pn); -2841 } -2842 } -2843 -2844 if newly_acked.is_empty() { -2845 return Ok(()); -2846 } -2847 -2848 let mut ack_eliciting_acked = false; -2849 for packet in newly_acked.elts() { -2850 if let Some(info) = self.spaces[space].for_path(path).take(packet) { -2851 for (acked_path_id, acked_pn) in info.largest_acked.iter() { -2852 // Assume ACKs for all packets below the largest acknowledged in -2853 // `packet` have been received. This can cause the peer to spuriously -2854 // retransmit if some of our earlier ACKs were lost, but allows for -2855 // simpler state tracking. See discussion at -2856 // https://www.rfc-editor.org/rfc/rfc9000.html#name-limiting-ranges-by-tracking -2857 if let Some(pns) = self.spaces[space].path_space_mut(*acked_path_id) { -2858 pns.pending_acks.subtract_below(*acked_pn); -2859 } -2860 } -2861 ack_eliciting_acked |= info.ack_eliciting; -2862 -2863 // Notify MTU discovery that a packet was acked, because it might be an MTU probe -2864 let path_data = self.path_data_mut(path); -2865 let mtu_updated = path_data.mtud.on_acked(space.kind(), packet, info.size); -2866 if mtu_updated { -2867 path_data -2868 .congestion -2869 .on_mtu_update(path_data.mtud.current_mtu()); -2870 } -2871 -2872 // Notify ack frequency that a packet was acked, because it might contain an ACK_FREQUENCY frame -2873 self.ack_frequency.on_acked(path, packet); -2874 -2875 self.on_packet_acked(now, path, info); -2876 } -2877 } -2878 -2879 let largest_ackd = self.spaces[space].for_path(path).largest_acked_packet; -2880 let app_limited = self.app_limited; -2881 let path_data = self.path_data_mut(path); -2882 let in_flight = path_data.in_flight.bytes; -2883 -2884 path_data -2885 .congestion -2886 .on_end_acks(now, in_flight, app_limited, largest_ackd); -2887 -2888 if new_largest && ack_eliciting_acked { -2889 let ack_delay = if space != SpaceId::Data { -2890 Duration::from_micros(0) -2891 } else { -2892 cmp::min( -2893 self.ack_frequency.peer_max_ack_delay, -2894 Duration::from_micros(ack.delay << self.peer_params.ack_delay_exponent.0), -2895 ) -2896 }; -2897 let rtt = now.saturating_duration_since( -2898 self.spaces[space].for_path(path).largest_acked_packet_sent, -2899 ); -2900 -2901 let next_pn = self.spaces[space].for_path(path).next_packet_number; -2902 let path_data = self.path_data_mut(path); -2903 // TODO(@divma): should be a method of path, should be contained in a single place -2904 path_data.rtt.update(ack_delay, rtt); -2905 if path_data.first_packet_after_rtt_sample.is_none() { -2906 path_data.first_packet_after_rtt_sample = Some((space.kind(), next_pn)); -2907 } -2908 } -2909 -2910 // Must be called before crypto/pto_count are clobbered -2911 self.detect_lost_packets(now, space, path, true); -2912 -2913 // If the peer did not complete the handshake address validation the ACK could be -2914 // spoofed, e.g. in the Initial space. Setting the pto_count back to 0 removes the -2915 // exponential backoff from the PTO timer and would result in too many tail-loss -2916 // probes being sent. -2917 if self.peer_completed_handshake_address_validation() { -2918 self.path_data_mut(path).pto_count = 0; -2919 } -2920 -2921 // Explicit congestion notification -2922 // TODO(@divma): this code is a good example of logic that should be contained in a single -2923 // place but it's split between the path data and the packet number space data, we should -2924 // find a way to make this work without two lookups -2925 if self.path_data(path).sending_ecn { -2926 if let Some(ecn) = ack.ecn { -2927 // We only examine ECN counters from ACKs that we are certain we received in transmit -2928 // order, allowing us to compute an increase in ECN counts to compare against the number -2929 // of newly acked packets that remains well-defined in the presence of arbitrary packet -2930 // reordering. -2931 if new_largest { -2932 let sent = self.spaces[space].for_path(path).largest_acked_packet_sent; -2933 self.process_ecn(now, space, path, newly_acked.len() as u64, ecn, sent); -2934 } -2935 } else { -2936 // We always start out sending ECN, so any ack that doesn't acknowledge it disables it. -2937 debug!("ECN not acknowledged by peer"); -2938 self.path_data_mut(path).sending_ecn = false; -2939 } -2940 } -2941 -2942 self.set_loss_detection_timer(now, path); -2943 Ok(()) -2944 } -2945 -2946 fn detect_spurious_loss(&mut self, ack: &frame::Ack, space: SpaceId, path: PathId) -> bool { -2947 let lost_packets = &mut self.spaces[space].for_path(path).lost_packets; -2948 -2949 if lost_packets.is_empty() { -2950 return false; -2951 } -2952 -2953 for range in ack.iter() { -2954 let spurious_losses: Vec<u64> = lost_packets -2955 .iter_range(range.clone()) -2956 .map(|(pn, _info)| pn) -2957 .collect(); -2958 -2959 for pn in spurious_losses { -2960 lost_packets.remove(pn); -2961 } -2962 } -2963 -2964 // If this ACK frame acknowledged all deemed lost packets, -2965 // then we have raised a spurious congestion event in the past. -2966 // We cannot conclude when there are remaining packets, -2967 // but future ACK frames might indicate a spurious loss detection. -2968 lost_packets.is_empty() -2969 } -2970 -2971 /// Drain lost packets that we reasonably think will never arrive -2972 /// -2973 /// The current criterion is copied from `msquic`: -2974 /// discard packets that were sent earlier than 2 probe timeouts ago. -2975 fn drain_lost_packets(&mut self, now: Instant, space: SpaceId, path: PathId) { -2976 let two_pto = 2 * self.path_data(path).rtt.pto_base(); -2977 -2978 let lost_packets = &mut self.spaces[space].for_path(path).lost_packets; -2979 lost_packets.retain(|_pn, info| now.saturating_duration_since(info.time_sent) <= two_pto); -2980 } -2981 -2982 /// Process a new ECN block from an in-order ACK -2983 fn process_ecn( -2984 &mut self, -2985 now: Instant, -2986 space: SpaceId, -2987 path: PathId, -2988 newly_acked: u64, -2989 ecn: frame::EcnCounts, -2990 largest_sent_time: Instant, -2991 ) { -2992 match self.spaces[space] -2993 .for_path(path) -2994 .detect_ecn(newly_acked, ecn) -2995 { -2996 Err(e) => { -2997 debug!("halting ECN due to verification failure: {}", e); -2998 -2999 self.path_data_mut(path).sending_ecn = false; -3000 // Wipe out the existing value because it might be garbage and could interfere with -3001 // future attempts to use ECN on new paths. -3002 self.spaces[space].for_path(path).ecn_feedback = frame::EcnCounts::ZERO; -3003 } -3004 Ok(false) => {} -3005 Ok(true) => { -3006 self.path_stats.for_path(path).congestion_events += 1; -3007 self.path_data_mut(path).congestion.on_congestion_event( -3008 now, -3009 largest_sent_time, -3010 false, -3011 true, -3012 0, -3013 ); -3014 } -3015 } -3016 } -3017 -3018 // Not timing-aware, so it's safe to call this for inferred acks, such as arise from -3019 // high-latency handshakes -3020 fn on_packet_acked(&mut self, now: Instant, path_id: PathId, info: SentPacket) { -3021 let app_limited = self.app_limited; -3022 let path = self.path_data_mut(path_id); -3023 path.remove_in_flight(&info); -3024 if info.ack_eliciting && info.path_generation == path.generation() { -3025 // Only pass ACKs to the congestion controller if it belongs to this exact -3026 // generation of the path. Otherwise we might be feeding ACKs from the previous -3027 // 4-tuple into our congestion controller. -3028 let rtt = path.rtt; -3029 path.congestion -3030 .on_ack(now, info.time_sent, info.size.into(), app_limited, &rtt); -3031 } -3032 -3033 // Update state for confirmed delivery of frames -3034 if let Some(retransmits) = info.retransmits.get() { -3035 for (id, _) in retransmits.reset_stream.iter() { -3036 self.streams.reset_acked(*id); -3037 } -3038 } -3039 -3040 for frame in info.stream_frames { -3041 self.streams.received_ack_of(frame); -3042 } -3043 } -3044 -3045 fn set_key_discard_timer(&mut self, now: Instant, space: SpaceKind) { -3046 let start = if self.crypto_state.has_keys(EncryptionLevel::ZeroRtt) { -3047 now -3048 } else { -3049 self.crypto_state -3050 .prev_crypto -3051 .as_ref() -3052 .expect("no previous keys") -3053 .end_packet -3054 .as_ref() -3055 .expect("update not acknowledged yet") -3056 .1 -3057 }; +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; +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 } +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; +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); +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 // QUIC-MULTIPATH § 2.5 Key Phase Update Process: use largest PTO of all paths. -3060 self.timers.set( -3061 Timer::Conn(ConnTimer::KeyDiscard), -3062 start + self.max_pto_for_space(space) * 3, -3063 self.qlog.with_time(now), -3064 ); -3065 } -3066 -3067 /// Handle a [`PathTimer::LossDetection`] timeout. -3068 /// -3069 /// This timer expires for two reasons: -3070 /// - An ACK-eliciting packet we sent should be considered lost. -3071 /// - The PTO may have expired and a tail-loss probe needs to be scheduled. +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 former needs us to schedule re-transmission of the lost data. -3074 /// -3075 /// The latter means we have not received an ACK for an ack-eliciting packet we sent -3076 /// within the PTO time-window. We need to schedule a tail-loss probe, an ack-eliciting -3077 /// packet, to try and elicit new acknowledgements. These new acknowledgements will -3078 /// indicate whether the previously sent packets were lost or not. -3079 fn on_loss_detection_timeout(&mut self, now: Instant, path_id: PathId) { -3080 if let Some((_, pn_space)) = self.loss_time_and_space(path_id) { -3081 // Time threshold loss Detection -3082 self.detect_lost_packets(now, pn_space, path_id, false); -3083 self.set_loss_detection_timer(now, path_id); -3084 return; -3085 } -3086 -3087 let Some((_, space)) = self.pto_time_and_space(now, path_id) else { -3088 error!(%path_id, "PTO expired while unset"); -3089 return; -3090 }; -3091 trace!( -3092 in_flight = self.path_data(path_id).in_flight.bytes, -3093 count = self.path_data(path_id).pto_count, -3094 ?space, -3095 %path_id, -3096 "PTO fired" -3097 ); -3098 -3099 let count = match self.path_data(path_id).in_flight.ack_eliciting { -3100 // A PTO when we're not expecting any ACKs must be due to handshake -3101 // anti-amplification deadlock prevention. -3102 0 => { -3103 debug_assert!(!self.peer_completed_handshake_address_validation()); -3104 1 -3105 } -3106 // Conventional loss probe -3107 _ => 2, -3108 }; -3109 let pns = self.spaces[space].for_path(path_id); -3110 pns.loss_probes = pns.loss_probes.saturating_add(count); -3111 let path_data = self.path_data_mut(path_id); -3112 path_data.pto_count = path_data.pto_count.saturating_add(1); -3113 self.set_loss_detection_timer(now, path_id); -3114 } -3115 -3116 /// Detect any lost packets -3117 /// -3118 /// There are two cases in which we detects lost packets: -3119 /// -3120 /// - We received an ACK packet. -3121 /// - The [`PathTimer::LossDetection`] timer expired. So there is an un-acknowledged packet -3122 /// that was followed by an acknowledged packet. The loss timer for this -3123 /// un-acknowledged packet expired and we need to detect that packet as lost. -3124 /// -3125 /// Packets are lost if they are both (See RFC9002 §6.1): -3126 /// -3127 /// - Unacknowledged, in flight and sent prior to an acknowledged packet. -3128 /// - Old enough by either: -3129 /// - Having a packet number [`TransportConfig::packet_threshold`] lower then the last -3130 /// acknowledged packet. -3131 /// - Being sent [`TransportConfig::time_threshold`] * RTT in the past. -3132 fn detect_lost_packets( -3133 &mut self, -3134 now: Instant, -3135 pn_space: SpaceId, -3136 path_id: PathId, -3137 due_to_ack: bool, -3138 ) { -3139 let mut lost_packets = Vec::<u64>::new(); -3140 let mut lost_mtu_probe = None; -3141 let mut in_persistent_congestion = false; -3142 let mut size_of_lost_packets = 0u64; -3143 self.spaces[pn_space].for_path(path_id).loss_time = None; -3144 -3145 // Find all the lost packets, populating all variables initialised above. +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(); +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); +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); +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 let path = self.path_data(path_id); -3148 let in_flight_mtu_probe = path.mtud.in_flight_mtu_probe(); -3149 let loss_delay = path -3150 .rtt -3151 .conservative() -3152 .mul_f32(self.config.time_threshold) -3153 .max(TIMER_GRANULARITY); -3154 let first_packet_after_rtt_sample = path.first_packet_after_rtt_sample; -3155 -3156 let largest_acked_packet = self.spaces[pn_space] -3157 .for_path(path_id) -3158 .largest_acked_packet -3159 .expect("detect_lost_packets only to be called if path received at least one ACK"); -3160 let packet_threshold = self.config.packet_threshold as u64; -3161 -3162 // InPersistentCongestion: Determine if all packets in the time period before the newest -3163 // lost packet, including the edges, are marked lost. PTO computation must always -3164 // include max ACK delay, i.e. operate as if in Data space (see RFC9001 §7.6.1). -3165 let congestion_period = self -3166 .pto(SpaceKind::Data, path_id) -3167 .saturating_mul(self.config.persistent_congestion_threshold); -3168 let mut persistent_congestion_start: Option<Instant> = None; -3169 let mut prev_packet = None; -3170 let space = self.spaces[pn_space].for_path(path_id); -3171 -3172 for (packet, info) in space.sent_packets.iter_range(0..largest_acked_packet) { -3173 if prev_packet != Some(packet.wrapping_sub(1)) { -3174 // An intervening packet was acknowledged -3175 persistent_congestion_start = None; -3176 } -3177 -3178 // Packets sent before now - loss_delay are deemed lost. -3179 // However, we avoid subtraction as it can panic and there's no -3180 // saturating equivalent of this subtraction operation with a Duration. -3181 let packet_too_old = now.saturating_duration_since(info.time_sent) >= loss_delay; -3182 if packet_too_old || largest_acked_packet >= packet + packet_threshold { -3183 // The packet should be declared lost. -3184 if Some(packet) == in_flight_mtu_probe { -3185 // Lost MTU probes are not included in `lost_packets`, because they -3186 // should not trigger a congestion control response -3187 lost_mtu_probe = in_flight_mtu_probe; -3188 } else { -3189 lost_packets.push(packet); -3190 size_of_lost_packets += info.size as u64; -3191 if info.ack_eliciting && due_to_ack { -3192 match persistent_congestion_start { -3193 // Two ACK-eliciting packets lost more than -3194 // congestion_period apart, with no ACKed packets in between -3195 Some(start) if info.time_sent - start > congestion_period => { -3196 in_persistent_congestion = true; -3197 } -3198 // Persistent congestion must start after the first RTT sample -3199 None if first_packet_after_rtt_sample -3200 .is_some_and(|x| x < (pn_space.kind(), packet)) => -3201 { -3202 persistent_congestion_start = Some(info.time_sent); -3203 } -3204 _ => {} -3205 } -3206 } -3207 } -3208 } else { -3209 // The packet should not yet be declared lost. -3210 if space.loss_time.is_none() { -3211 // Since we iterate in order the lowest packet number's loss time will -3212 // always be the earliest. -3213 space.loss_time = Some(info.time_sent + loss_delay); -3214 } -3215 persistent_congestion_start = None; -3216 } +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 prev_packet = Some(packet); -3219 } -3220 -3221 self.handle_lost_packets( -3222 pn_space, -3223 path_id, -3224 now, -3225 lost_packets, -3226 lost_mtu_probe, -3227 loss_delay, -3228 in_persistent_congestion, -3229 size_of_lost_packets, -3230 ); -3231 } -3232 -3233 /// Drops the path state, declaring any remaining in-flight packets as lost -3234 fn discard_path(&mut self, path_id: PathId, now: Instant) { -3235 trace!(%path_id, "dropping path state"); -3236 let path = self.path_data(path_id); -3237 let in_flight_mtu_probe = path.mtud.in_flight_mtu_probe(); -3238 -3239 let mut size_of_lost_packets = 0u64; // add to path_stats.lost_bytes; -3240 let lost_pns: Vec<_> = self.spaces[SpaceId::Data] -3241 .for_path(path_id) -3242 .sent_packets -3243 .iter() -3244 .filter(|(pn, _info)| Some(*pn) != in_flight_mtu_probe) -3245 .map(|(pn, info)| { -3246 size_of_lost_packets += info.size as u64; -3247 pn -3248 }) -3249 .collect(); -3250 -3251 if !lost_pns.is_empty() { -3252 trace!( -3253 %path_id, -3254 count = lost_pns.len(), -3255 lost_bytes = size_of_lost_packets, -3256 "packets lost on path abandon" -3257 ); -3258 self.handle_lost_packets( -3259 SpaceId::Data, -3260 path_id, -3261 now, -3262 lost_pns, -3263 in_flight_mtu_probe, -3264 Duration::ZERO, -3265 false, -3266 size_of_lost_packets, -3267 ); -3268 } -3269 // Before removing the path, we fetch the final path stats via `Self::path_stats`. -3270 // This updates some values for the last time. -3271 let path_stats = self.path_stats.discard(&path_id); -3272 self.partial_stats += path_stats; -3273 self.paths.remove(&path_id); -3274 self.spaces[SpaceId::Data].number_spaces.remove(&path_id); -3275 -3276 self.events.push_back( -3277 PathEvent::Discarded { -3278 id: path_id, -3279 path_stats: Box::new(path_stats), -3280 } -3281 .into(), -3282 ); -3283 } -3284 -3285 fn handle_lost_packets( -3286 &mut self, -3287 pn_space: SpaceId, -3288 path_id: PathId, -3289 now: Instant, -3290 lost_packets: Vec<u64>, -3291 lost_mtu_probe: Option<u64>, -3292 loss_delay: Duration, -3293 in_persistent_congestion: bool, -3294 size_of_lost_packets: u64, -3295 ) { -3296 debug_assert!( -3297 { -3298 let mut sorted = lost_packets.clone(); -3299 sorted.sort(); -3300 sorted == lost_packets -3301 }, -3302 "lost_packets must be sorted" -3303 ); -3304 -3305 self.drain_lost_packets(now, pn_space, path_id); -3306 -3307 // OnPacketsLost -3308 if let Some(largest_lost) = lost_packets.last().cloned() { -3309 let old_bytes_in_flight = self.path_data_mut(path_id).in_flight.bytes; -3310 let largest_lost_sent = self.spaces[pn_space] -3311 .for_path(path_id) -3312 .sent_packets -3313 .get(largest_lost) -3314 .unwrap() -3315 .time_sent; -3316 let path_stats = self.path_stats.for_path(path_id); -3317 path_stats.lost_packets += lost_packets.len() as u64; -3318 path_stats.lost_bytes += size_of_lost_packets; -3319 trace!( -3320 %path_id, -3321 count = lost_packets.len(), -3322 lost_bytes = size_of_lost_packets, -3323 "packets lost", -3324 ); -3325 -3326 for &packet in &lost_packets { -3327 let Some(info) = self.spaces[pn_space].for_path(path_id).take(packet) else { -3328 continue; -3329 }; -3330 self.qlog -3331 .emit_packet_lost(packet, &info, loss_delay, pn_space.kind(), now); -3332 self.paths -3333 .get_mut(&path_id) -3334 .unwrap() -3335 .remove_in_flight(&info); -3336 -3337 for frame in info.stream_frames { -3338 self.streams.retransmit(frame); -3339 } -3340 self.spaces[pn_space].pending |= info.retransmits; -3341 self.path_data_mut(path_id) -3342 .mtud -3343 .on_non_probe_lost(packet, info.size); -3344 -3345 self.spaces[pn_space].for_path(path_id).lost_packets.insert( -3346 packet, -3347 LostPacket { -3348 time_sent: info.time_sent, -3349 }, -3350 ); -3351 } +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 let path = self.path_data_mut(path_id); -3354 if path.mtud.black_hole_detected(now) { -3355 path.congestion.on_mtu_update(path.mtud.current_mtu()); -3356 if let Some(max_datagram_size) = self.datagrams().max_size() -3357 && self.datagrams.drop_oversized(max_datagram_size) -3358 && self.datagrams.send_blocked -3359 { -3360 self.datagrams.send_blocked = false; -3361 self.events.push_back(Event::DatagramsUnblocked); -3362 } -3363 self.path_stats.for_path(path_id).black_holes_detected += 1; -3364 } -3365 -3366 // Don't apply congestion penalty for lost ack-only packets -3367 let lost_ack_eliciting = -3368 old_bytes_in_flight != self.path_data_mut(path_id).in_flight.bytes; -3369 -3370 if lost_ack_eliciting { -3371 self.path_stats.for_path(path_id).congestion_events += 1; -3372 self.path_data_mut(path_id).congestion.on_congestion_event( -3373 now, -3374 largest_lost_sent, -3375 in_persistent_congestion, -3376 false, -3377 size_of_lost_packets, -3378 ); -3379 } -3380 } -3381 -3382 // Handle a lost MTU probe -3383 if let Some(packet) = lost_mtu_probe { -3384 let info = self.spaces[SpaceId::Data] -3385 .for_path(path_id) -3386 .take(packet) -3387 .unwrap(); // safe: lost_mtu_probe is omitted from lost_packets, and -3388 // therefore must not have been removed yet -3389 self.paths -3390 .get_mut(&path_id) -3391 .unwrap() -3392 .remove_in_flight(&info); -3393 self.path_data_mut(path_id).mtud.on_probe_lost(); -3394 self.path_stats.for_path(path_id).lost_plpmtud_probes += 1; -3395 } -3396 } -3397 -3398 /// Returns the earliest time packets should be declared lost for all spaces on a path. -3399 /// -3400 /// If a path has an acknowledged packet with any prior un-acknowledged packets, the -3401 /// earliest un-acknowledged packet can be declared lost after a timeout has elapsed. -3402 /// The time returned is when this packet should be declared lost. -3403 fn loss_time_and_space(&self, path_id: PathId) -> Option<(Instant, SpaceId)> { -3404 SpaceId::iter() -3405 .filter_map(|id| { -3406 self.spaces[id] -3407 .number_spaces -3408 .get(&path_id) -3409 .and_then(|pns| pns.loss_time) -3410 .map(|time| (time, id)) -3411 }) -3412 .min_by_key(|&(time, _)| time) -3413 } -3414 -3415 /// Returns the earliest next PTO should fire for all spaces on a path. -3416 /// -3417 /// This needs to be fully deterministic because it is also used to determine the PTO -3418 /// that fired, not just to set the next timer. So if it fired in the past it needs to -3419 /// return the time from the past at which it fired. -3420 /// -3421 /// This is the next time a tail-loss probe should be sent. -3422 fn pto_time_and_space(&mut self, now: Instant, path_id: PathId) -> Option<(Instant, SpaceId)> { -3423 let path = self.path(path_id)?; -3424 let pto_count = path.pto_count; -3425 -3426 // Cap the maximum interval between two tail-loss probes. -3427 let max_interval = if path.rtt.get() > SLOW_RTT_THRESHOLD { -3428 // For slow links we want to increase the interval beyond 2s. -3429 (path.rtt.get() * 3) / 2 -3430 } else if let Some(idle) = path.idle_timeout.or(self.idle_timeout) -3431 && idle <= MIN_IDLE_FOR_FAST_PTO -3432 { -3433 // If the idle timeout is relatively low, cap at 1s so we get plenty of retries -3434 // before the idle timeout fires. -3435 MAX_PTO_FAST_INTERVAL -3436 } else { -3437 // Otherwise cap to 2s. -3438 MAX_PTO_INTERVAL -3439 }; -3440 -3441 if path_id == PathId::ZERO -3442 && path.in_flight.ack_eliciting == 0 -3443 && !self.peer_completed_handshake_address_validation() -3444 { -3445 // Address Validation during Connection Establishment: -3446 // https://www.rfc-editor.org/rfc/rfc9000.html#section-8.1. To prevent a -3447 // deadlock if an Initial or Handshake packet from the server is lost and the -3448 // server can not send more due to its anti-amplification limit the client must -3449 // send another packet on PTO. -3450 let space = match self.highest_space { -3451 SpaceKind::Handshake => SpaceId::Handshake, -3452 _ => SpaceId::Initial, -3453 }; -3454 -3455 let backoff = 2u32.pow(path.pto_count.min(MAX_BACKOFF_EXPONENT)); -3456 let duration = path.rtt.pto_base() * backoff; -3457 let duration = duration.min(max_interval); -3458 return Some((now + duration, space)); -3459 } +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 let mut result = None; -3462 for space in SpaceId::iter() { -3463 let Some(pns) = self.spaces[space].number_spaces.get(&path_id) else { -3464 continue; -3465 }; -3466 -3467 if !pns.has_in_flight() { -3468 continue; -3469 } -3470 -3471 // Compute the PTO duration for this space, we want to cap the maximum interval -3472 // between two tail-loss probes so to not do a simple exponential backoff but -3473 // rather iterate through the probes to compute the capped increment for an -3474 // exponential backoff at each step. -3475 let duration = { -3476 let pto_base = path.rtt.pto_base() -3477 + if space == SpaceId::Data { -3478 self.ack_frequency.max_ack_delay_for_pto() -3479 } else { -3480 Duration::ZERO -3481 }; -3482 let mut duration = pto_base; -3483 for i in 1..=pto_count { -3484 let exponential_duration = pto_base * 2u32.pow(i.min(MAX_BACKOFF_EXPONENT)); -3485 let max_duration = duration + max_interval; -3486 duration = exponential_duration.min(max_duration); -3487 } -3488 duration -3489 }; -3490 -3491 let Some(last_ack_eliciting) = pns.time_of_last_ack_eliciting_packet else { -3492 continue; -3493 }; -3494 // Base the deadline on when the last probe was sent, so the PTO -3495 // doesn't fire before the response has had time to arrive. -3496 let pto = last_ack_eliciting + duration; -3497 if result.is_none_or(|(earliest_pto, _)| pto < earliest_pto) { -3498 if path.anti_amplification_blocked(1) { -3499 // Nothing would be able to be sent. -3500 continue; -3501 } -3502 if path.in_flight.ack_eliciting == 0 { -3503 // Nothing ack-eliciting, no PTO to arm/fire. -3504 continue; -3505 } -3506 result = Some((pto, space)); -3507 } -3508 } -3509 result -3510 } -3511 -3512 /// Whether the peer validated our address in the connection handshake. -3513 fn peer_completed_handshake_address_validation(&self) -> bool { -3514 if self.side.is_server() || self.state.is_closed() { -3515 return true; -3516 } -3517 // The server is guaranteed to have validated our address if any of our handshake or -3518 // 1-RTT packets are acknowledged or we've seen HANDSHAKE_DONE and discarded -3519 // handshake keys. -3520 self.spaces[SpaceId::Handshake] -3521 .path_space(PathId::ZERO) -3522 .and_then(|pns| pns.largest_acked_packet) -3523 .is_some() -3524 || self.spaces[SpaceId::Data] -3525 .path_space(PathId::ZERO) -3526 .and_then(|pns| pns.largest_acked_packet) -3527 .is_some() -3528 || (self.crypto_state.has_keys(EncryptionLevel::OneRtt) -3529 && !self.crypto_state.has_keys(EncryptionLevel::Handshake)) -3530 } -3531 -3532 /// Resets the the [`PathTimer::LossDetection`] timer to the next instant it may be needed -3533 /// -3534 /// The timer must fire if either: -3535 /// - An ack-eliciting packet we sent needs to be declared lost. -3536 /// - A tail-loss probe needs to be sent. -3537 /// -3538 /// See [`Connection::on_loss_detection_timeout`] for details. -3539 fn set_loss_detection_timer(&mut self, now: Instant, path_id: PathId) { -3540 if self.state.is_closed() { -3541 // No loss detection takes place on closed connections, and `close_common` already -3542 // stopped time timer. Ensure we don't restart it inadvertently, e.g. in response to a -3543 // reordered packet being handled by state-insensitive code. -3544 return; -3545 } -3546 -3547 if let Some((loss_time, _)) = self.loss_time_and_space(path_id) { -3548 // Time threshold loss detection. -3549 self.timers.set( -3550 Timer::PerPath(path_id, PathTimer::LossDetection), -3551 loss_time, -3552 self.qlog.with_time(now), -3553 ); -3554 return; +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 } +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 // Determine which PN space to arm PTO for. -3558 // Calculate PTO duration -3559 if let Some((timeout, _)) = self.pto_time_and_space(now, path_id) { -3560 self.timers.set( -3561 Timer::PerPath(path_id, PathTimer::LossDetection), -3562 timeout, -3563 self.qlog.with_time(now), -3564 ); -3565 } else { -3566 self.timers.stop( -3567 Timer::PerPath(path_id, PathTimer::LossDetection), -3568 self.qlog.with_time(now), -3569 ); -3570 } -3571 } -3572 -3573 /// The maximum probe timeout across all paths -3574 /// -3575 /// See [`Connection::pto`] -3576 fn max_pto_for_space(&self, space: SpaceKind) -> Duration { -3577 self.paths -3578 .keys() -3579 .map(|path_id| self.pto(space, *path_id)) -3580 .max() -3581 .unwrap_or_else(|| { -3582 // No paths remain (e.g. last path was abandoned and the NoAvailablePath grace timer -3583 // fired before any new path was opened). Fall back to a PTO derived from the -3584 // configured initial RTT, matching RFC 9002 §6.2.2 initial values. -3585 let rtt = self.config.initial_rtt; -3586 let max_ack_delay = match space { -3587 SpaceKind::Initial | SpaceKind::Handshake => Duration::ZERO, -3588 SpaceKind::Data => self.ack_frequency.max_ack_delay_for_pto(), -3589 }; -3590 rtt + cmp::max(4 * (rtt / 2), TIMER_GRANULARITY) + max_ack_delay -3591 }) -3592 } +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 /// Probe Timeout -3595 /// -3596 /// The PTO is logically the time in which you'd expect to receive an acknowledgement -3597 /// for a packet. So approximately RTT + max_ack_delay. -3598 fn pto(&self, space: SpaceKind, path_id: PathId) -> Duration { -3599 let max_ack_delay = match space { -3600 SpaceKind::Initial | SpaceKind::Handshake => Duration::ZERO, -3601 SpaceKind::Data => self.ack_frequency.max_ack_delay_for_pto(), -3602 }; -3603 self.path_data(path_id).rtt.pto_base() + max_ack_delay -3604 } -3605 -3606 fn on_packet_authenticated( -3607 &mut self, -3608 now: Instant, -3609 space_id: SpaceKind, -3610 path_id: PathId, -3611 ecn: Option<EcnCodepoint>, -3612 packet: Option<u64>, -3613 spin: bool, -3614 is_1rtt: bool, -3615 ) { -3616 self.total_authed_packets += 1; -3617 self.reset_keep_alive(path_id, now); -3618 self.reset_idle_timeout(now, space_id, path_id); -3619 self.permit_idle_reset = true; -3620 self.receiving_ecn |= ecn.is_some(); -3621 if let Some(x) = ecn { -3622 let space = &mut self.spaces[space_id]; -3623 space.for_path(path_id).ecn_counters += x; -3624 -3625 if x.is_ce() { -3626 space -3627 .for_path(path_id) -3628 .pending_acks -3629 .set_immediate_ack_required(); -3630 } -3631 } -3632 -3633 let Some(packet) = packet else { -3634 return; -3635 }; -3636 match &self.side { -3637 ConnectionSide::Client { .. } => { -3638 // If we received a handshake packet that authenticated, then we're talking to -3639 // the real server. From now on we should no longer allow the server to migrate -3640 // its address. -3641 if space_id == SpaceKind::Handshake -3642 && let Some(hs) = self.state.as_handshake_mut() -3643 { -3644 hs.allow_server_migration = false; -3645 } -3646 } -3647 ConnectionSide::Server { .. } => { -3648 if self.crypto_state.has_keys(EncryptionLevel::Initial) -3649 && space_id == SpaceKind::Handshake -3650 { -3651 // A server stops sending and processing Initial packets when it receives its first Handshake packet. -3652 self.discard_space(now, SpaceKind::Initial); -3653 } -3654 if self.crypto_state.has_keys(EncryptionLevel::ZeroRtt) && is_1rtt { -3655 // Discard 0-RTT keys soon after receiving a 1-RTT packet -3656 self.set_key_discard_timer(now, space_id) -3657 } -3658 } -3659 } -3660 let space = self.spaces[space_id].for_path(path_id); -3661 space.pending_acks.insert_one(packet, now); -3662 if packet >= space.rx_packet.unwrap_or_default() { -3663 space.rx_packet = Some(packet); -3664 // Update outgoing spin bit, inverting iff we're the client -3665 self.spin = self.side.is_client() ^ spin; -3666 } -3667 } -3668 -3669 /// Resets the idle timeout timers -3670 /// -3671 /// Without multipath there is only the connection-wide idle timeout. When multipath is -3672 /// enabled there is an additional per-path idle timeout. -3673 fn reset_idle_timeout(&mut self, now: Instant, space: SpaceKind, path_id: PathId) { -3674 // First reset the global idle timeout. -3675 if let Some(timeout) = self.idle_timeout { -3676 if self.state.is_closed() { -3677 self.timers -3678 .stop(Timer::Conn(ConnTimer::Idle), self.qlog.with_time(now)); -3679 } else { -3680 let dt = cmp::max(timeout, 3 * self.max_pto_for_space(space)); -3681 self.timers.set( -3682 Timer::Conn(ConnTimer::Idle), -3683 now + dt, -3684 self.qlog.with_time(now), -3685 ); -3686 } -3687 } -3688 -3689 // Now handle the per-path state -3690 if let Some(timeout) = self.path_data(path_id).idle_timeout { -3691 if self.state.is_closed() { -3692 self.timers.stop( -3693 Timer::PerPath(path_id, PathTimer::PathIdle), -3694 self.qlog.with_time(now), -3695 ); -3696 } else { -3697 let dt = cmp::max(timeout, 3 * self.pto(space, path_id)); -3698 self.timers.set( -3699 Timer::PerPath(path_id, PathTimer::PathIdle), -3700 now + dt, -3701 self.qlog.with_time(now), -3702 ); -3703 } -3704 } -3705 } -3706 -3707 /// Resets both the [`ConnTimer::KeepAlive`] and [`PathTimer::PathKeepAlive`] timers -3708 fn reset_keep_alive(&mut self, path_id: PathId, now: Instant) { -3709 if !self.state.is_established() { -3710 return; -3711 } -3712 -3713 if let Some(interval) = self.config.keep_alive_interval { -3714 self.timers.set( -3715 Timer::Conn(ConnTimer::KeepAlive), -3716 now + interval, -3717 self.qlog.with_time(now), -3718 ); -3719 } -3720 -3721 if let Some(interval) = self.path_data(path_id).keep_alive { -3722 self.timers.set( -3723 Timer::PerPath(path_id, PathTimer::PathKeepAlive), -3724 now + interval, -3725 self.qlog.with_time(now), -3726 ); -3727 } -3728 } -3729 -3730 /// Sets the timer for when a previously issued CID should be retired next -3731 fn reset_cid_retirement(&mut self, now: Instant) { -3732 if let Some((_path, t)) = self.next_cid_retirement() { -3733 self.timers.set( -3734 Timer::Conn(ConnTimer::PushNewCid), -3735 t, -3736 self.qlog.with_time(now), -3737 ); -3738 } -3739 } -3740 -3741 /// The next time when a previously issued CID should be retired -3742 fn next_cid_retirement(&self) -> Option<(PathId, Instant)> { -3743 self.local_cid_state -3744 .iter() -3745 .filter_map(|(path_id, cid_state)| cid_state.next_timeout().map(|t| (*path_id, t))) -3746 .min_by_key(|(_path_id, timeout)| *timeout) -3747 } -3748 -3749 /// Handle the already-decrypted first packet from the client -3750 /// -3751 /// Decrypting the first packet in the `Endpoint` allows stateless packet handling to be more -3752 /// efficient. -3753 pub(crate) fn handle_first_packet( -3754 &mut self, -3755 now: Instant, -3756 network_path: FourTuple, -3757 ecn: Option<EcnCodepoint>, -3758 packet_number: u64, -3759 packet: InitialPacket, -3760 remaining: Option<BytesMut>, -3761 ) -> Result<(), ConnectionError> { -3762 let span = trace_span!("first recv"); -3763 let _guard = span.enter(); -3764 debug_assert!(self.side.is_server()); -3765 let len = packet.header_data.len() + packet.payload.len(); -3766 let path_id = PathId::ZERO; -3767 self.path_data_mut(path_id).total_recvd = len as u64; -3768 -3769 if let Some(hs) = self.state.as_handshake_mut() { -3770 hs.expected_token = packet.header.token.clone(); -3771 } else { -3772 unreachable!("first packet must be delivered in Handshake state"); -3773 } -3774 -3775 // The first packet is always on PathId::ZERO -3776 self.on_packet_authenticated( -3777 now, -3778 SpaceKind::Initial, -3779 path_id, -3780 ecn, -3781 Some(packet_number), -3782 false, -3783 false, -3784 ); -3785 -3786 let packet: Packet = packet.into(); +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)); +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); +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 let mut qlog = QlogRecvPacket::new(len); -3789 qlog.header(&packet.header, Some(packet_number), path_id); -3790 -3791 self.process_decrypted_packet( -3792 now, -3793 network_path, -3794 path_id, -3795 Some(packet_number), -3796 packet, -3797 &mut qlog, -3798 )?; -3799 self.qlog.emit_packet_received(qlog, now); -3800 if let Some(data) = remaining { -3801 self.handle_coalesced(now, network_path, path_id, ecn, data); -3802 } -3803 -3804 self.qlog.emit_recovery_metrics( -3805 path_id, -3806 &mut self.paths.get_mut(&path_id).unwrap().data, -3807 now, -3808 ); -3809 -3810 Ok(()) -3811 } -3812 -3813 fn init_0rtt(&mut self, now: Instant) { -3814 let Some((header, packet)) = self.crypto_state.session.early_crypto() else { -3815 return; -3816 }; -3817 if self.side.is_client() { -3818 match self.crypto_state.session.transport_parameters() { -3819 Ok(params) => { -3820 let params = params -3821 .expect("crypto layer didn't supply transport parameters with ticket"); -3822 // Certain values must not be cached -3823 let params = TransportParameters { -3824 initial_src_cid: None, -3825 original_dst_cid: None, -3826 preferred_address: None, -3827 retry_src_cid: None, -3828 stateless_reset_token: None, -3829 min_ack_delay: None, -3830 ack_delay_exponent: TransportParameters::default().ack_delay_exponent, -3831 max_ack_delay: TransportParameters::default().max_ack_delay, -3832 initial_max_path_id: None, -3833 ..params -3834 }; -3835 self.set_peer_params(params); -3836 self.qlog.emit_peer_transport_params_restored(self, now); -3837 } -3838 Err(e) => { -3839 error!("session ticket has malformed transport parameters: {}", e); -3840 return; -3841 } -3842 } -3843 } -3844 trace!("0-RTT enabled"); -3845 self.crypto_state.enable_zero_rtt(header, packet); -3846 } -3847 -3848 fn read_crypto( -3849 &mut self, -3850 space: SpaceId, -3851 crypto: &frame::Crypto, -3852 payload_len: usize, -3853 ) -> Result<(), TransportError> { -3854 let expected = if !self.state.is_handshake() { -3855 SpaceId::Data -3856 } else if self.highest_space == SpaceKind::Initial { -3857 SpaceId::Initial -3858 } else { -3859 // On the server, self.highest_space can be Data after receiving the client's first -3860 // flight, but we expect Handshake CRYPTO until the handshake is complete. -3861 SpaceId::Handshake -3862 }; -3863 // We can't decrypt Handshake packets when highest_space is Initial, CRYPTO frames in 0-RTT -3864 // packets are illegal, and we don't process 1-RTT packets until the handshake is -3865 // complete. Therefore, we will never see CRYPTO data from a later-than-expected space. -3866 debug_assert!(space <= expected, "received out-of-order CRYPTO data"); -3867 -3868 let end = crypto.offset + crypto.data.len() as u64; -3869 if space < expected -3870 && end -3871 > self.crypto_state.spaces[space.kind()] -3872 .crypto_stream -3873 .bytes_read() -3874 { -3875 warn!( -3876 "received new {:?} CRYPTO data when expecting {:?}", -3877 space, expected -3878 ); -3879 return Err(TransportError::PROTOCOL_VIOLATION( -3880 "new data at unexpected encryption level", -3881 )); -3882 } -3883 -3884 let crypto_space = &mut self.crypto_state.spaces[space.kind()]; -3885 let max = end.saturating_sub(crypto_space.crypto_stream.bytes_read()); -3886 if max > self.config.crypto_buffer_size as u64 { -3887 return Err(TransportError::CRYPTO_BUFFER_EXCEEDED("")); -3888 } -3889 -3890 crypto_space -3891 .crypto_stream -3892 .insert(crypto.offset, crypto.data.clone(), payload_len); -3893 while let Some(chunk) = crypto_space.crypto_stream.read(usize::MAX, true) { -3894 trace!("consumed {} CRYPTO bytes", chunk.bytes.len()); -3895 if self.crypto_state.session.read_handshake(&chunk.bytes)? { -3896 self.events.push_back(Event::HandshakeDataReady); -3897 } -3898 } -3899 -3900 Ok(()) -3901 } -3902 -3903 fn write_crypto(&mut self) { -3904 loop { -3905 let space = self.highest_space; -3906 let mut outgoing = Vec::new(); -3907 if let Some(crypto) = self.crypto_state.session.write_handshake(&mut outgoing) { -3908 match space { -3909 SpaceKind::Initial => { -3910 self.upgrade_crypto(SpaceKind::Handshake, crypto); -3911 } -3912 SpaceKind::Handshake => { -3913 self.upgrade_crypto(SpaceKind::Data, crypto); -3914 } -3915 SpaceKind::Data => unreachable!("got updated secrets during 1-RTT"), -3916 } -3917 } -3918 if outgoing.is_empty() { -3919 if space == self.highest_space { -3920 break; -3921 } else { -3922 // Keys updated, check for more data to send -3923 continue; -3924 } -3925 } -3926 let offset = self.crypto_state.spaces[space].crypto_offset; -3927 let outgoing = Bytes::from(outgoing); -3928 if let Some(hs) = self.state.as_handshake_mut() -3929 && space == SpaceKind::Initial -3930 && offset == 0 -3931 && self.side.is_client() -3932 { -3933 hs.client_hello = Some(outgoing.clone()); -3934 } -3935 self.crypto_state.spaces[space].crypto_offset += outgoing.len() as u64; -3936 trace!("wrote {} {:?} CRYPTO bytes", outgoing.len(), space); -3937 self.spaces[space].pending.crypto.push_back(frame::Crypto { -3938 offset, -3939 data: outgoing, -3940 }); -3941 } +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 ); +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 ); +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 /// Switch to stronger cryptography during handshake -3945 fn upgrade_crypto(&mut self, space: SpaceKind, crypto: Keys) { -3946 debug_assert!( -3947 !self.crypto_state.has_keys(space.encryption_level()), -3948 "already reached packet space {space:?}" -3949 ); -3950 trace!("{:?} keys ready", space); -3951 if space == SpaceKind::Data { -3952 // Precompute the first key update -3953 self.crypto_state.next_crypto = Some( -3954 self.crypto_state -3955 .session -3956 .next_1rtt_keys() -3957 .expect("handshake should be complete"), -3958 ); -3959 } -3960 -3961 self.crypto_state.spaces[space].keys = Some(crypto); -3962 debug_assert!(space > self.highest_space); -3963 self.highest_space = space; -3964 if space == SpaceKind::Data && self.side.is_client() { -3965 // Discard 0-RTT keys because 1-RTT keys are available. -3966 self.crypto_state.discard_zero_rtt(); -3967 } -3968 } -3969 -3970 fn discard_space(&mut self, now: Instant, space: SpaceKind) { -3971 debug_assert!(space != SpaceKind::Data); -3972 trace!("discarding {:?} keys", space); -3973 if space == SpaceKind::Initial { -3974 // No longer needed -3975 if let ConnectionSide::Client { token, .. } = &mut self.side { -3976 *token = Bytes::new(); -3977 } -3978 } -3979 self.crypto_state.spaces[space].keys = None; -3980 let space = &mut self.spaces[space]; -3981 let pns = space.for_path(PathId::ZERO); -3982 pns.time_of_last_ack_eliciting_packet = None; -3983 pns.loss_time = None; -3984 pns.loss_probes = 0; -3985 let sent_packets = mem::take(&mut pns.sent_packets); -3986 let path = self.paths.get_mut(&PathId::ZERO).unwrap(); -3987 for (_, packet) in sent_packets.into_iter() { -3988 path.data.remove_in_flight(&packet); -3989 } -3990 -3991 self.set_loss_detection_timer(now, PathId::ZERO) -3992 } -3993 -3994 fn handle_coalesced( -3995 &mut self, -3996 now: Instant, -3997 network_path: FourTuple, -3998 path_id: PathId, -3999 ecn: Option<EcnCodepoint>, -4000 data: BytesMut, -4001 ) { -4002 self.path_data_mut(path_id) -4003 .inc_total_recvd(data.len() as u64); -4004 let mut remaining = Some(data); -4005 let cid_len = self -4006 .local_cid_state -4007 .values() -4008 .map(|cid_state| cid_state.cid_len()) -4009 .next() -4010 .expect("one cid_state must exist"); -4011 while let Some(data) = remaining { -4012 match PartialDecode::new( -4013 data, -4014 &FixedLengthConnectionIdParser::new(cid_len), -4015 &[self.version], -4016 self.endpoint_config.grease_quic_bit, -4017 ) { -4018 Ok((partial_decode, rest)) => { -4019 remaining = rest; -4020 self.handle_decode(now, network_path, path_id, ecn, partial_decode); -4021 } -4022 Err(e) => { -4023 trace!("malformed header: {}", e); -4024 return; -4025 } -4026 } -4027 } -4028 } -4029 -4030 fn handle_decode( -4031 &mut self, -4032 now: Instant, -4033 network_path: FourTuple, -4034 path_id: PathId, -4035 ecn: Option<EcnCodepoint>, -4036 partial_decode: PartialDecode, -4037 ) { -4038 let qlog = QlogRecvPacket::new(partial_decode.len()); -4039 if let Some(decoded) = self -4040 .crypto_state -4041 .unprotect_header(partial_decode, self.peer_params.stateless_reset_token) -4042 { -4043 self.handle_packet( -4044 now, -4045 network_path, -4046 path_id, -4047 ecn, -4048 decoded.packet, -4049 decoded.stateless_reset, -4050 qlog, -4051 ); -4052 } -4053 } -4054 -4055 fn handle_packet( -4056 &mut self, -4057 now: Instant, -4058 network_path: FourTuple, -4059 path_id: PathId, -4060 ecn: Option<EcnCodepoint>, -4061 packet: Option<Packet>, -4062 stateless_reset: bool, -4063 mut qlog: QlogRecvPacket, -4064 ) { -4065 self.path_stats.for_path(path_id).udp_rx.ios += 1; -4066 -4067 if let Some(ref packet) = packet { -4068 trace!( -4069 "got {:?} packet ({} bytes) from {} using id {}", -4070 packet.header.space(), -4071 packet.payload.len() + packet.header_data.len(), -4072 network_path, -4073 packet.header.dst_cid(), -4074 ); -4075 } -4076 -4077 if self.is_handshaking() { -4078 if path_id != PathId::ZERO { -4079 debug!(%network_path, %path_id, "discarding multipath packet during handshake"); -4080 return; -4081 } -4082 if network_path != self.path_data_mut(path_id).network_path { -4083 if let Some(hs) = self.state.as_handshake() { -4084 if hs.allow_server_migration { -4085 trace!(%network_path, prev = %self.path_data(path_id).network_path, "server migrated to new remote"); -4086 self.path_data_mut(path_id).network_path = network_path; -4087 self.qlog.emit_tuple_assigned(path_id, network_path, now); -4088 } else { -4089 debug!("discarding packet with unexpected remote during handshake"); -4090 return; -4091 } -4092 } else { -4093 debug!("discarding packet with unexpected remote during handshake"); -4094 return; -4095 } -4096 } -4097 } -4098 -4099 let was_closed = self.state.is_closed(); -4100 let was_drained = self.state.is_drained(); -4101 -4102 let decrypted = match packet { -4103 None => Err(None), -4104 Some(mut packet) => self -4105 .decrypt_packet(now, path_id, &mut packet) -4106 .map(move |number| (packet, number)), -4107 }; -4108 let result = match decrypted { -4109 _ if stateless_reset => { -4110 debug!("got stateless reset"); -4111 Err(ConnectionError::Reset) -4112 } -4113 Err(Some(e)) => { -4114 warn!("illegal packet: {}", e); -4115 Err(e.into()) -4116 } -4117 Err(None) => { -4118 debug!("failed to authenticate packet"); -4119 self.authentication_failures += 1; -4120 let integrity_limit = self -4121 .crypto_state -4122 .integrity_limit(self.highest_space) -4123 .unwrap(); -4124 if self.authentication_failures > integrity_limit { -4125 Err(TransportError::AEAD_LIMIT_REACHED("integrity limit violated").into()) -4126 } else { -4127 return; -4128 } -4129 } -4130 Ok((packet, number)) => { -4131 qlog.header(&packet.header, number, path_id); -4132 let span = match number { -4133 Some(pn) => trace_span!("recv", space = ?packet.header.space(), pn), -4134 None => trace_span!("recv", space = ?packet.header.space()), -4135 }; -4136 let _guard = span.enter(); -4137 -4138 let dedup = self.spaces[packet.header.space()] -4139 .path_space_mut(path_id) -4140 .map(|pns| &mut pns.dedup); -4141 if number.zip(dedup).is_some_and(|(n, d)| d.insert(n)) { -4142 debug!("discarding possible duplicate packet"); -4143 self.qlog.emit_packet_received(qlog, now); -4144 return; -4145 } else if self.state.is_handshake() && packet.header.is_short() { -4146 // TODO: SHOULD buffer these to improve reordering tolerance. -4147 trace!("dropping short packet during handshake"); -4148 self.qlog.emit_packet_received(qlog, now); -4149 return; -4150 } else { -4151 if let Header::Initial(InitialHeader { ref token, .. }) = packet.header -4152 && let Some(hs) = self.state.as_handshake() -4153 && self.side.is_server() -4154 && token != &hs.expected_token -4155 { -4156 // Clients must send the same retry token in every Initial. Initial -4157 // packets can be spoofed, so we discard rather than killing the -4158 // connection. -4159 warn!("discarding Initial with invalid retry token"); -4160 self.qlog.emit_packet_received(qlog, now); -4161 return; -4162 } +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 } +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 } +4161 } +4162 } 4163 -4164 if !self.state.is_closed() { -4165 let spin = match packet.header { -4166 Header::Short { spin, .. } => spin, -4167 _ => false, -4168 }; -4169 -4170 if self.side().is_server() && !self.abandoned_paths.contains(&path_id) { -4171 // Only the client is allowed to open paths -4172 self.ensure_path(path_id, network_path, now, number); -4173 } -4174 if self.paths.contains_key(&path_id) { -4175 self.on_packet_authenticated( -4176 now, -4177 packet.header.space(), -4178 path_id, -4179 ecn, -4180 number, -4181 spin, -4182 packet.header.is_1rtt(), -4183 ); -4184 } -4185 } -4186 -4187 let res = self.process_decrypted_packet( -4188 now, -4189 network_path, -4190 path_id, -4191 number, -4192 packet, -4193 &mut qlog, -4194 ); -4195 -4196 self.qlog.emit_packet_received(qlog, now); -4197 res -4198 } -4199 } -4200 }; -4201 -4202 // State transitions for error cases -4203 if let Err(conn_err) = result { -4204 match conn_err { -4205 ConnectionError::ApplicationClosed(reason) => self.state.move_to_closed(reason), -4206 ConnectionError::ConnectionClosed(reason) => self.state.move_to_closed(reason), -4207 ConnectionError::Reset -4208 | ConnectionError::TransportError(TransportError { -4209 code: TransportErrorCode::AEAD_LIMIT_REACHED, -4210 .. -4211 }) => { -4212 self.state.move_to_drained(Some(conn_err)); -4213 } -4214 ConnectionError::TimedOut => { -4215 unreachable!("timeouts aren't generated by packet processing"); -4216 } -4217 ConnectionError::TransportError(err) => { -4218 debug!("closing connection due to transport error: {}", err); -4219 self.state.move_to_closed(err); -4220 } -4221 ConnectionError::VersionMismatch => { -4222 self.state.move_to_draining(Some(conn_err)); -4223 } -4224 ConnectionError::LocallyClosed => { -4225 unreachable!("LocallyClosed isn't generated by packet processing"); -4226 } -4227 ConnectionError::CidsExhausted => { -4228 unreachable!("CidsExhausted isn't generated by packet processing"); -4229 } -4230 }; -4231 } -4232 -4233 if !was_closed && self.state.is_closed() { -4234 self.close_common(); -4235 if !self.state.is_drained() { -4236 self.set_close_timer(now); -4237 } -4238 } -4239 if !was_drained && self.state.is_drained() { -4240 self.endpoint_events.push_back(EndpointEventInner::Drained); -4241 // Close timer may have been started previously, e.g. if we sent a close and got a -4242 // stateless reset in response -4243 self.timers -4244 .stop(Timer::Conn(ConnTimer::Close), self.qlog.with_time(now)); -4245 } -4246 -4247 // Transmit CONNECTION_CLOSE if necessary. -4248 // -4249 // If we received a valid packet and we are in the closed state we should respond -4250 // with a CONNECTION_CLOSE frame. -4251 // TODO: This SHOULD be rate-limited according to §10.2.1 of QUIC-TRANSPORT, but -4252 // that does not yet happen. This is triggered by each received packet. -4253 if matches!(self.state.as_type(), StateType::Closed) { -4254 // From https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2.1-7 -4255 // -4256 // While in the closing state we must either: -4257 // - discard packets coming from an un-validated remote OR -4258 // - ensure we do not send more than 3 times the received data -4259 // -4260 // Doing the 2nd would mean we would be able to send CONNECTION_CLOSE to a peer -4261 // who was (involuntary) migrated just at the time we initiated immediate -4262 // close. It is a lot more work though. So while we would like to do this for -4263 // now we only do 1. -4264 // -4265 // Another shortcoming of the current implementation is that when we have a -4266 // previous PathData which is validated and the remote matches that path, we -4267 // should schedule CONNECTION_CLOSE on that path. However currently we can not -4268 // schedule such a packet. We should also fix this some day. This makes us -4269 // vulnerable to an attacker faking a migration at the right time and then we'd -4270 // be unable to send the CONNECTION_CLOSE to the real remote. -4271 if self -4272 .paths -4273 .get(&path_id) -4274 .map(|p| p.data.validated && p.data.network_path == network_path) -4275 .unwrap_or(false) -4276 { -4277 self.connection_close_pending = true; -4278 } -4279 } -4280 } -4281 -4282 fn process_decrypted_packet( -4283 &mut self, -4284 now: Instant, -4285 network_path: FourTuple, -4286 path_id: PathId, -4287 number: Option<u64>, -4288 packet: Packet, -4289 qlog: &mut QlogRecvPacket, -4290 ) -> Result<(), ConnectionError> { -4291 if !self.paths.contains_key(&path_id) { -4292 // There is a chance this is a server side, first (for this path) packet, which would -4293 // be a protocol violation. It's more likely, however, that this is a packet of a -4294 // pruned path -4295 trace!(%path_id, ?number, "discarding packet for unknown path"); -4296 return Ok(()); -4297 } -4298 let state = match self.state.as_type() { -4299 StateType::Established => { -4300 match packet.header.space() { -4301 SpaceKind::Data => self.process_payload( -4302 now, -4303 network_path, -4304 path_id, -4305 number.unwrap(), -4306 packet, -4307 qlog, -4308 )?, -4309 _ if packet.header.has_frames() => { -4310 self.process_early_payload(now, path_id, packet, qlog)? -4311 } -4312 _ => { -4313 trace!("discarding unexpected pre-handshake packet"); -4314 } -4315 } -4316 return Ok(()); -4317 } -4318 StateType::Closed => { -4319 for result in frame::Iter::new(packet.payload.freeze())? { -4320 let frame = match result { -4321 Ok(frame) => frame, -4322 Err(err) => { -4323 debug!("frame decoding error: {err:?}"); -4324 continue; -4325 } -4326 }; -4327 qlog.frame(&frame); -4328 -4329 if let Frame::Padding = frame { -4330 continue; -4331 }; -4332 -4333 self.path_stats -4334 .for_path(path_id) -4335 .frame_rx -4336 .record(frame.ty()); -4337 -4338 if let Frame::Close(_error) = frame { -4339 self.state.move_to_draining(None); -4340 break; -4341 } -4342 } -4343 return Ok(()); -4344 } -4345 StateType::Draining | StateType::Drained => return Ok(()), -4346 StateType::Handshake => self.state.as_handshake_mut().expect("checked"), -4347 }; -4348 -4349 match packet.header { -4350 Header::Retry { -4351 src_cid: remote_cid, -4352 .. -4353 } => { -4354 debug_assert_eq!(path_id, PathId::ZERO); -4355 if self.side.is_server() { -4356 return Err(TransportError::PROTOCOL_VIOLATION("client sent Retry").into()); -4357 } -4358 -4359 let is_valid_retry = self -4360 .remote_cids -4361 .get(&path_id) -4362 .map(|cids| cids.active()) -4363 .map(|orig_dst_cid| { -4364 self.crypto_state.session.is_valid_retry( -4365 orig_dst_cid, -4366 &packet.header_data, -4367 &packet.payload, -4368 ) -4369 }) -4370 .unwrap_or_default(); -4371 if self.total_authed_packets > 1 -4372 || packet.payload.len() <= 16 // token + 16 byte tag -4373 || !is_valid_retry -4374 { -4375 trace!("discarding invalid Retry"); -4376 // - After the client has received and processed an Initial or Retry -4377 // packet from the server, it MUST discard any subsequent Retry -4378 // packets that it receives. -4379 // - A client MUST discard a Retry packet with a zero-length Retry Token -4380 // field. -4381 // - Clients MUST discard Retry packets that have a Retry Integrity Tag -4382 // that cannot be validated -4383 return Ok(()); +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 -4386 trace!("retrying with CID {}", remote_cid); -4387 let client_hello = state.client_hello.take().unwrap(); -4388 self.retry_src_cid = Some(remote_cid); -4389 self.remote_cids -4390 .get_mut(&path_id) -4391 .expect("PathId::ZERO not yet abandoned, is_valid_retry would have been false") -4392 .update_initial_cid(remote_cid); -4393 self.remote_handshake_cid = remote_cid; -4394 -4395 let space = &mut self.spaces[SpaceId::Initial]; -4396 if let Some(info) = space.for_path(PathId::ZERO).take(0) { -4397 self.on_packet_acked(now, PathId::ZERO, info); -4398 }; -4399 -4400 self.discard_space(now, SpaceKind::Initial); // Make sure we clean up after -4401 // any retransmitted Initials -4402 let crypto_space = &mut self.crypto_state.spaces[SpaceKind::Initial]; -4403 crypto_space.keys = Some( -4404 self.crypto_state -4405 .session -4406 .initial_keys(remote_cid, self.side.side()), -4407 ); -4408 crypto_space.crypto_offset = client_hello.len() as u64; -4409 -4410 let next_pn = self.spaces[SpaceId::Initial] -4411 .for_path(path_id) -4412 .next_packet_number; -4413 self.spaces[SpaceId::Initial] = { -4414 let mut space = PacketSpace::new(now, SpaceId::Initial, &mut self.rng); -4415 space.for_path(path_id).next_packet_number = next_pn; -4416 space.pending.crypto.push_back(frame::Crypto { -4417 offset: 0, -4418 data: client_hello, -4419 }); -4420 space -4421 }; -4422 -4423 // Retransmit all 0-RTT data -4424 let zero_rtt = mem::take( -4425 &mut self.spaces[SpaceId::Data] -4426 .for_path(PathId::ZERO) -4427 .sent_packets, -4428 ); -4429 for (_, info) in zero_rtt.into_iter() { -4430 self.paths -4431 .get_mut(&PathId::ZERO) -4432 .unwrap() -4433 .remove_in_flight(&info); -4434 self.spaces[SpaceId::Data].pending |= info.retransmits; -4435 } -4436 self.streams.retransmit_all_for_0rtt(); -4437 -4438 let token_len = packet.payload.len() - 16; -4439 let ConnectionSide::Client { ref mut token, .. } = self.side else { -4440 unreachable!("we already short-circuited if we're server"); -4441 }; -4442 *token = packet.payload.freeze().split_to(token_len); -4443 -4444 self.state = State::handshake(state::Handshake { -4445 expected_token: Bytes::new(), -4446 remote_cid_set: false, -4447 client_hello: None, -4448 allow_server_migration: true, -4449 }); -4450 Ok(()) -4451 } -4452 Header::Long { -4453 ty: LongType::Handshake, -4454 src_cid: remote_cid, -4455 dst_cid: local_cid, -4456 .. -4457 } => { -4458 debug_assert_eq!(path_id, PathId::ZERO); -4459 if remote_cid != self.remote_handshake_cid { -4460 debug!( -4461 "discarding packet with mismatched remote CID: {} != {}", -4462 self.remote_handshake_cid, remote_cid -4463 ); -4464 return Ok(()); -4465 } -4466 self.on_path_validated(path_id); -4467 -4468 self.process_early_payload(now, path_id, packet, qlog)?; -4469 if self.state.is_closed() { -4470 return Ok(()); -4471 } -4472 -4473 if self.crypto_state.session.is_handshaking() { -4474 trace!("handshake ongoing"); -4475 return Ok(()); +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 -4478 if self.side.is_client() { -4479 // Client-only because server params were set from the client's Initial -4480 let params = self -4481 .crypto_state -4482 .session -4483 .transport_parameters()? -4484 .ok_or_else(|| { -4485 TransportError::new( -4486 TransportErrorCode::crypto(0x6d), -4487 "transport parameters missing".to_owned(), -4488 ) -4489 })?; -4490 -4491 if self.has_0rtt() { -4492 if !self.crypto_state.session.early_data_accepted().unwrap() { -4493 debug_assert!(self.side.is_client()); -4494 debug!("0-RTT rejected"); -4495 self.crypto_state.accepted_0rtt = false; -4496 self.streams.zero_rtt_rejected(); -4497 -4498 // Discard already-queued frames -4499 self.spaces[SpaceId::Data].pending = Retransmits::default(); -4500 -4501 // Discard 0-RTT packets -4502 let sent_packets = mem::take( -4503 &mut self.spaces[SpaceId::Data].for_path(path_id).sent_packets, -4504 ); -4505 for (_, packet) in sent_packets.into_iter() { -4506 self.paths -4507 .get_mut(&path_id) -4508 .unwrap() -4509 .remove_in_flight(&packet); -4510 } -4511 } else { -4512 self.crypto_state.accepted_0rtt = true; -4513 params.validate_resumption_from(&self.peer_params)?; -4514 } -4515 } -4516 if let Some(token) = params.stateless_reset_token { -4517 let remote = self.path_data(path_id).network_path.remote; -4518 debug_assert!(!self.state.is_drained()); // requirement for endpoint events, checked above -4519 self.endpoint_events -4520 .push_back(EndpointEventInner::ResetToken(path_id, remote, token)); -4521 } -4522 self.handle_peer_params(params, local_cid, remote_cid, now)?; -4523 self.issue_first_cids(now); -4524 } else { -4525 // Server-only -4526 self.spaces[SpaceId::Data].pending.handshake_done = true; -4527 self.discard_space(now, SpaceKind::Handshake); -4528 self.events.push_back(Event::HandshakeConfirmed); -4529 trace!("handshake confirmed"); -4530 } -4531 -4532 self.events.push_back(Event::Connected); -4533 self.state.move_to_established(); -4534 trace!("established"); -4535 -4536 // Multipath can only be enabled after the state has reached Established. -4537 // So this can not happen any earlier. -4538 self.issue_first_path_cids(now); -4539 Ok(()) -4540 } -4541 Header::Initial(InitialHeader { -4542 src_cid: remote_cid, -4543 dst_cid: local_cid, -4544 .. -4545 }) => { -4546 debug_assert_eq!(path_id, PathId::ZERO); -4547 if !state.remote_cid_set { -4548 trace!("switching remote CID to {}", remote_cid); -4549 let mut state = state.clone(); -4550 self.remote_cids -4551 .get_mut(&path_id) -4552 .expect("PathId::ZERO not yet abandoned") -4553 .update_initial_cid(remote_cid); -4554 self.remote_handshake_cid = remote_cid; -4555 self.original_remote_cid = remote_cid; -4556 state.remote_cid_set = true; -4557 self.state.move_to_handshake(state); -4558 } else if remote_cid != self.remote_handshake_cid { -4559 debug!( -4560 "discarding packet with mismatched remote CID: {} != {}", -4561 self.remote_handshake_cid, remote_cid -4562 ); -4563 return Ok(()); -4564 } -4565 -4566 let starting_space = self.highest_space; -4567 self.process_early_payload(now, path_id, packet, qlog)?; -4568 -4569 if self.side.is_server() -4570 && starting_space == SpaceKind::Initial -4571 && self.highest_space != SpaceKind::Initial -4572 { -4573 let params = self -4574 .crypto_state -4575 .session -4576 .transport_parameters()? -4577 .ok_or_else(|| { -4578 TransportError::new( -4579 TransportErrorCode::crypto(0x6d), -4580 "transport parameters missing".to_owned(), -4581 ) -4582 })?; -4583 self.handle_peer_params(params, local_cid, remote_cid, now)?; -4584 self.issue_first_cids(now); -4585 self.init_0rtt(now); -4586 } -4587 Ok(()) -4588 } -4589 Header::Long { -4590 ty: LongType::ZeroRtt, -4591 .. -4592 } => { -4593 self.process_payload(now, network_path, path_id, number.unwrap(), packet, qlog)?; -4594 Ok(()) -4595 } -4596 Header::VersionNegotiate { .. } => { -4597 if self.total_authed_packets > 1 { -4598 return Ok(()); -4599 } -4600 let supported = packet -4601 .payload -4602 .chunks(4) -4603 .any(|x| match <[u8; 4]>::try_from(x) { -4604 Ok(version) => self.version == u32::from_be_bytes(version), -4605 Err(_) => false, -4606 }); -4607 if supported { -4608 return Ok(()); -4609 } -4610 debug!("remote doesn't support our version"); -4611 Err(ConnectionError::VersionMismatch) +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::Short { .. } => unreachable!( -4614 "short packets received during handshake are discarded in handle_packet" -4615 ), -4616 } -4617 } -4618 -4619 /// Process an Initial or Handshake packet payload -4620 fn process_early_payload( -4621 &mut self, -4622 now: Instant, -4623 path_id: PathId, -4624 packet: Packet, -4625 #[allow(unused)] qlog: &mut QlogRecvPacket, -4626 ) -> Result<(), TransportError> { -4627 debug_assert_ne!(packet.header.space(), SpaceKind::Data); -4628 debug_assert_eq!(path_id, PathId::ZERO); -4629 let payload_len = packet.payload.len(); -4630 let mut ack_eliciting = false; -4631 for result in frame::Iter::new(packet.payload.freeze())? { -4632 let frame = result?; -4633 qlog.frame(&frame); -4634 let span = match frame { -4635 Frame::Padding => continue, -4636 _ => Some(trace_span!("frame", ty = %frame.ty(), path = tracing::field::Empty)), -4637 }; +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 } 4638 -4639 self.path_stats -4640 .for_path(path_id) -4641 .frame_rx -4642 .record(frame.ty()); -4643 -4644 let _guard = span.as_ref().map(|x| x.enter()); -4645 ack_eliciting |= frame.is_ack_eliciting(); -4646 -4647 // Process frames -4648 if frame.is_1rtt() && packet.header.space() != SpaceKind::Data { -4649 return Err(TransportError::PROTOCOL_VIOLATION( -4650 "illegal frame type in handshake", -4651 )); -4652 } -4653 -4654 match frame { -4655 Frame::Padding | Frame::Ping => {} -4656 Frame::Crypto(frame) => { -4657 self.read_crypto(packet.header.space().into(), &frame, payload_len)?; -4658 } -4659 Frame::Ack(ack) => { -4660 self.on_ack_received(now, packet.header.space().into(), ack)?; -4661 } -4662 Frame::PathAck(ack) => { -4663 span.as_ref() -4664 .map(|span| span.record("path", tracing::field::display(&ack.path_id))); -4665 self.on_path_ack_received(now, packet.header.space().into(), ack)?; -4666 } -4667 Frame::Close(reason) => { -4668 self.state.move_to_draining(Some(reason.into())); -4669 return Ok(()); -4670 } -4671 _ => { -4672 let mut err = -4673 TransportError::PROTOCOL_VIOLATION("illegal frame type in handshake"); -4674 err.frame = frame::MaybeFrame::Known(frame.ty()); -4675 return Err(err); -4676 } -4677 } -4678 } -4679 -4680 if ack_eliciting { -4681 // In the initial and handshake spaces, ACKs must be sent immediately -4682 self.spaces[packet.header.space()] -4683 .for_path(path_id) -4684 .pending_acks -4685 .set_immediate_ack_required(); -4686 } -4687 -4688 self.write_crypto(); -4689 Ok(()) -4690 } -4691 -4692 /// Processes the packet payload, always in the data space. -4693 fn process_payload( -4694 &mut self, -4695 now: Instant, -4696 network_path: FourTuple, -4697 path_id: PathId, -4698 number: u64, -4699 packet: Packet, -4700 #[allow(unused)] qlog: &mut QlogRecvPacket, -4701 ) -> Result<(), TransportError> { -4702 let is_multipath_negotiated = self.is_multipath_negotiated(); -4703 let payload = packet.payload.freeze(); -4704 let mut is_probing_packet = true; -4705 let mut close = None; -4706 let payload_len = payload.len(); -4707 let mut ack_eliciting = false; -4708 // if this packet triggers a path migration and includes a observed address frame, it's -4709 // stored here -4710 let mut migration_observed_addr = None; -4711 for result in frame::Iter::new(payload)? { -4712 let frame = result?; -4713 qlog.frame(&frame); -4714 let span = match frame { -4715 Frame::Padding => continue, -4716 _ => trace_span!("frame", ty = %frame.ty(), path = tracing::field::Empty), -4717 }; -4718 -4719 self.path_stats -4720 .for_path(path_id) -4721 .frame_rx -4722 .record(frame.ty()); -4723 // Crypto, Stream and Datagram frames are special cased in order no pollute -4724 // the log with payload data -4725 match &frame { -4726 Frame::Crypto(f) => { -4727 trace!(offset = f.offset, len = f.data.len(), "got frame CRYPTO"); -4728 } -4729 Frame::Stream(f) => { -4730 trace!(id = %f.id, offset = f.offset, len = f.data.len(), fin = f.fin, "got frame STREAM"); -4731 } -4732 Frame::Datagram(f) => { -4733 trace!(len = f.data.len(), "got frame DATAGRAM"); -4734 } -4735 f => { -4736 trace!("got frame {f}"); -4737 } -4738 } -4739 -4740 let _guard = span.enter(); -4741 if packet.header.is_0rtt() { -4742 match frame { -4743 Frame::Crypto(_) | Frame::Close(Close::Application(_)) => { -4744 return Err(TransportError::PROTOCOL_VIOLATION( -4745 "illegal frame type in 0-RTT", -4746 )); -4747 } -4748 _ => { -4749 if frame.is_1rtt() { -4750 return Err(TransportError::PROTOCOL_VIOLATION( -4751 "illegal frame type in 0-RTT", -4752 )); -4753 } -4754 } -4755 } +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 ack_eliciting |= frame.is_ack_eliciting(); -4758 -4759 // Check whether this could be a probing packet -4760 match frame { -4761 Frame::Padding -4762 | Frame::PathChallenge(_) -4763 | Frame::PathResponse(_) -4764 | Frame::NewConnectionId(_) -4765 | Frame::ObservedAddr(_) => {} -4766 _ => { -4767 is_probing_packet = false; -4768 } -4769 } -4770 -4771 match frame { -4772 Frame::Crypto(frame) => { -4773 self.read_crypto(SpaceId::Data, &frame, payload_len)?; -4774 } -4775 Frame::Stream(frame) => { -4776 if self.streams.received(frame, payload_len)?.should_transmit() { -4777 self.spaces[SpaceId::Data].pending.max_data = true; -4778 } -4779 } -4780 Frame::Ack(ack) => { -4781 self.on_ack_received(now, SpaceId::Data, ack)?; -4782 } -4783 Frame::PathAck(ack) => { -4784 span.record("path", tracing::field::display(&ack.path_id)); -4785 self.on_path_ack_received(now, SpaceId::Data, ack)?; -4786 } -4787 Frame::Padding | Frame::Ping => {} -4788 Frame::Close(reason) => { -4789 close = Some(reason); -4790 } -4791 Frame::PathChallenge(challenge) => { -4792 let path = &mut self -4793 .path_mut(path_id) -4794 .expect("payload is processed only after the path becomes known"); -4795 path.path_responses.push(number, challenge.0, network_path); -4796 // At this point, update_network_path_or_discard was already called, so -4797 // we don't need to be lenient about `local_ip` possibly mis-matching. -4798 if network_path == path.network_path { -4799 // PATH_CHALLENGE on active path, possible off-path packet forwarding -4800 // attack. Send a non-probing packet to recover the active path. -4801 // TODO(flub): No longer true! We now path_challege also to validate -4802 // the path if the path is new, without an RFC9000-style -4803 // migration involved. This means we add in an extra -4804 // IMMEDIATE_ACK on some challenges. It isn't really wrong to do -4805 // so, but it still is something untidy. We should instead -4806 // suppress this when we know the remote is still validating the -4807 // path. -4808 match self.peer_supports_ack_frequency() { -4809 true => self.immediate_ack(path_id), -4810 false => { -4811 self.ping_path(path_id).ok(); -4812 } -4813 } -4814 } -4815 } -4816 Frame::PathResponse(response) => { -4817 let path = self -4818 .paths -4819 .get_mut(&path_id) -4820 .expect("payload is processed only after the path becomes known"); -4821 -4822 use PathTimer::*; -4823 use paths::OnPathResponseReceived::*; -4824 match path -4825 .data -4826 .on_path_response_received(now, response.0, network_path) -4827 { -4828 OnPath { was_open } => { -4829 let qlog = self.qlog.with_time(now); -4830 -4831 self.timers -4832 .stop(Timer::PerPath(path_id, PathValidationFailed), qlog.clone()); -4833 self.timers -4834 .stop(Timer::PerPath(path_id, PathOpenFailed), qlog.clone()); -4835 -4836 let next_challenge = path -4837 .data -4838 .earliest_on_path_expiring_challenge() -4839 .map(|time| time + self.ack_frequency.max_ack_delay_for_pto()); -4840 self.timers.set_or_stop( -4841 Timer::PerPath(path_id, PathChallengeLost), -4842 next_challenge, -4843 qlog, -4844 ); -4845 -4846 if !was_open { -4847 if is_multipath_negotiated { -4848 self.events -4849 .push_back(Event::Path(PathEvent::Opened { id: path_id })); -4850 } -4851 if let Some(observed) = path.data.last_observed_addr_report.as_ref() -4852 { -4853 self.events.push_back(Event::Path(PathEvent::ObservedAddr { -4854 id: path_id, -4855 addr: observed.socket_addr(), -4856 })); -4857 } -4858 } -4859 if let Some((_, ref mut prev)) = path.prev { -4860 prev.reset_on_path_challenges(); -4861 } -4862 } -4863 OffPath => { -4864 debug!(%response, "Valid response to off-path PATH_CHALLENGE"); -4865 } -4866 Ignored { -4867 sent_on, -4868 current_path, -4869 } => { -4870 debug!(%sent_on, %current_path, %response, "ignoring valid PATH_RESPONSE") -4871 } -4872 Unknown => debug!(%response, "ignoring invalid PATH_RESPONSE"), -4873 } -4874 } -4875 Frame::MaxData(frame::MaxData(bytes)) => { -4876 self.streams.received_max_data(bytes); -4877 } -4878 Frame::MaxStreamData(frame::MaxStreamData { id, offset }) => { -4879 self.streams.received_max_stream_data(id, offset)?; -4880 } -4881 Frame::MaxStreams(frame::MaxStreams { dir, count }) => { -4882 self.streams.received_max_streams(dir, count)?; -4883 } -4884 Frame::ResetStream(frame) => { -4885 if self.streams.received_reset(frame)?.should_transmit() { -4886 self.spaces[SpaceId::Data].pending.max_data = true; -4887 } -4888 } -4889 Frame::DataBlocked(DataBlocked(offset)) => { -4890 debug!(offset, "peer claims to be blocked at connection level"); -4891 } -4892 Frame::StreamDataBlocked(StreamDataBlocked { id, offset }) => { -4893 if id.initiator() == self.side.side() && id.dir() == Dir::Uni { -4894 debug!("got STREAM_DATA_BLOCKED on send-only {}", id); -4895 return Err(TransportError::STREAM_STATE_ERROR( -4896 "STREAM_DATA_BLOCKED on send-only stream", -4897 )); -4898 } -4899 debug!( -4900 stream = %id, -4901 offset, "peer claims to be blocked at stream level" -4902 ); -4903 } -4904 Frame::StreamsBlocked(StreamsBlocked { dir, limit }) => { -4905 if limit > MAX_STREAM_COUNT { -4906 return Err(TransportError::FRAME_ENCODING_ERROR( -4907 "unrepresentable stream limit", -4908 )); -4909 } -4910 debug!( -4911 "peer claims to be blocked opening more than {} {} streams", -4912 limit, dir -4913 ); -4914 } -4915 Frame::StopSending(frame::StopSending { id, error_code }) => { -4916 if id.initiator() != self.side.side() { -4917 if id.dir() == Dir::Uni { -4918 debug!("got STOP_SENDING on recv-only {}", id); -4919 return Err(TransportError::STREAM_STATE_ERROR( -4920 "STOP_SENDING on recv-only stream", -4921 )); -4922 } -4923 } else if self.streams.is_local_unopened(id) { -4924 return Err(TransportError::STREAM_STATE_ERROR( -4925 "STOP_SENDING on unopened stream", -4926 )); -4927 } -4928 self.streams.received_stop_sending(id, error_code); +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 )); +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; 4929 } -4930 Frame::RetireConnectionId(frame::RetireConnectionId { path_id, sequence }) => { -4931 if let Some(ref path_id) = path_id { -4932 span.record("path", tracing::field::display(&path_id)); -4933 } -4934 let path_id = path_id.unwrap_or_default(); -4935 match self.local_cid_state.get_mut(&path_id) { -4936 None => error!(?path_id, "RETIRE_CONNECTION_ID for unknown path"), -4937 Some(cid_state) => { -4938 let allow_more_cids = cid_state -4939 .on_cid_retirement(sequence, self.peer_params.issue_cids_limit())?; -4940 -4941 // If the path has closed, we do not issue more CIDs for this path -4942 // For details see https://www.ietf.org/archive/id/draft-ietf-quic-multipath-17.html#section-3.2.2 -4943 // > an endpoint SHOULD provide new connection IDs for that path, if still open, using PATH_NEW_CONNECTION_ID frames. -4944 let has_path = !self.abandoned_paths.contains(&path_id); -4945 let allow_more_cids = allow_more_cids && has_path; -4946 -4947 debug_assert!(!self.state.is_drained()); // required for adding endpoint events, process_payload is never called for drained connections -4948 self.endpoint_events -4949 .push_back(EndpointEventInner::RetireConnectionId( -4950 now, -4951 path_id, -4952 sequence, -4953 allow_more_cids, -4954 )); -4955 } -4956 } -4957 } -4958 Frame::NewConnectionId(frame) => { -4959 let path_id = if let Some(path_id) = frame.path_id { -4960 if !self.is_multipath_negotiated() { -4961 return Err(TransportError::PROTOCOL_VIOLATION( -4962 "received PATH_NEW_CONNECTION_ID frame when multipath was not negotiated", -4963 )); -4964 } -4965 if path_id > self.local_max_path_id { -4966 return Err(TransportError::PROTOCOL_VIOLATION( -4967 "PATH_NEW_CONNECTION_ID contains path_id exceeding current max", -4968 )); -4969 } -4970 path_id -4971 } else { -4972 PathId::ZERO -4973 }; -4974 -4975 if let Some(ref path_id) = frame.path_id { -4976 span.record("path", tracing::field::display(&path_id)); -4977 } -4978 -4979 if self.abandoned_paths.contains(&path_id) { -4980 trace!("ignoring issued CID for abandoned path"); -4981 continue; -4982 } -4983 let remote_cids = self -4984 .remote_cids -4985 .entry(path_id) -4986 .or_insert_with(|| CidQueue::new(frame.id)); -4987 if remote_cids.active().is_empty() { -4988 return Err(TransportError::PROTOCOL_VIOLATION( -4989 "NEW_CONNECTION_ID when CIDs aren't in use", -4990 )); -4991 } -4992 if frame.retire_prior_to > frame.sequence { -4993 return Err(TransportError::PROTOCOL_VIOLATION( -4994 "NEW_CONNECTION_ID retiring unissued CIDs", -4995 )); -4996 } -4997 -4998 use crate::cid_queue::InsertError; -4999 match remote_cids.insert(frame) { -5000 Ok(None) if self.path(path_id).is_none() => { -5001 // if this gives us CIDs to open a new path and a nat traversal attempt -5002 // is underway we could try to probe a pending remote -5003 self.continue_nat_traversal_round(now); -5004 } -5005 Ok(None) => {} -5006 Ok(Some((retired, reset_token))) => { -5007 let pending_retired = -5008 &mut self.spaces[SpaceId::Data].pending.retire_cids; -5009 /// Ensure `pending_retired` cannot grow without bound. Limit is -5010 /// somewhat arbitrary but very permissive. -5011 const MAX_PENDING_RETIRED_CIDS: u64 = CidQueue::LEN as u64 * 10; -5012 // We don't bother counting in-flight frames because those are bounded -5013 // by congestion control. -5014 if (pending_retired.len() as u64) -5015 .saturating_add(retired.end.saturating_sub(retired.start)) -5016 > MAX_PENDING_RETIRED_CIDS -5017 { -5018 return Err(TransportError::CONNECTION_ID_LIMIT_ERROR( -5019 "queued too many retired CIDs", -5020 )); -5021 } -5022 pending_retired.extend(retired.map(|seq| (path_id, seq))); -5023 self.set_reset_token(path_id, network_path.remote, reset_token); -5024 } -5025 Err(InsertError::ExceedsLimit) => { -5026 return Err(TransportError::CONNECTION_ID_LIMIT_ERROR("")); -5027 } -5028 Err(InsertError::Retired) => { -5029 trace!("discarding already-retired"); -5030 // RETIRE_CONNECTION_ID might not have been previously sent if e.g. a -5031 // range of connection IDs larger than the active connection ID limit -5032 // was retired all at once via retire_prior_to. -5033 self.spaces[SpaceId::Data] -5034 .pending -5035 .retire_cids -5036 .push((path_id, frame.sequence)); -5037 continue; -5038 } -5039 }; -5040 -5041 if self.side.is_server() -5042 && path_id == PathId::ZERO -5043 && self -5044 .remote_cids -5045 .get(&PathId::ZERO) -5046 .map(|cids| cids.active_seq() == 0) -5047 .unwrap_or_default() -5048 { -5049 // We're a server still using the initial remote CID for the client, so -5050 // let's switch immediately to enable clientside stateless resets. -5051 self.update_remote_cid(PathId::ZERO); -5052 } -5053 } -5054 Frame::NewToken(NewToken { token }) => { -5055 let ConnectionSide::Client { -5056 token_store, -5057 server_name, -5058 .. -5059 } = &self.side -5060 else { -5061 return Err(TransportError::PROTOCOL_VIOLATION("client sent NEW_TOKEN")); -5062 }; -5063 if token.is_empty() { -5064 return Err(TransportError::FRAME_ENCODING_ERROR("empty token")); -5065 } -5066 trace!("got new token"); -5067 token_store.insert(server_name, token); -5068 } -5069 Frame::Datagram(datagram) => { -5070 if self -5071 .datagrams -5072 .received(datagram, &self.config.datagram_receive_buffer_size)? -5073 { -5074 self.events.push_back(Event::DatagramReceived); -5075 } -5076 } -5077 Frame::AckFrequency(ack_frequency) => { -5078 // This frame can only be sent in the Data space -5079 -5080 if !self.ack_frequency.ack_frequency_received(&ack_frequency)? { -5081 // The AckFrequency frame is stale (we have already received a more -5082 // recent one) -5083 continue; -5084 } -5085 -5086 // Update the params for all of our paths -5087 for (path_id, space) in self.spaces[SpaceId::Data].number_spaces.iter_mut() { -5088 space.pending_acks.set_ack_frequency_params(&ack_frequency); -5089 -5090 // Our `max_ack_delay` has been updated, so we may need to adjust -5091 // its associated timeout -5092 if let Some(timeout) = space -5093 .pending_acks -5094 .max_ack_delay_timeout(self.ack_frequency.max_ack_delay) -5095 { -5096 self.timers.set( -5097 Timer::PerPath(*path_id, PathTimer::MaxAckDelay), -5098 timeout, -5099 self.qlog.with_time(now), -5100 ); -5101 } -5102 } -5103 } -5104 Frame::ImmediateAck => { -5105 // This frame can only be sent in the Data space -5106 for pns in self.spaces[SpaceId::Data].iter_paths_mut() { -5107 pns.pending_acks.set_immediate_ack_required(); -5108 } -5109 } -5110 Frame::HandshakeDone => { -5111 if self.side.is_server() { -5112 return Err(TransportError::PROTOCOL_VIOLATION( -5113 "client sent HANDSHAKE_DONE", -5114 )); -5115 } -5116 if self.crypto_state.has_keys(EncryptionLevel::Handshake) { -5117 self.discard_space(now, SpaceKind::Handshake); -5118 } -5119 self.events.push_back(Event::HandshakeConfirmed); -5120 trace!("handshake confirmed"); -5121 } -5122 Frame::ObservedAddr(observed) => { -5123 // check if params allows the peer to send report and this node to receive it -5124 trace!(seq_no = %observed.seq_no, ip = %observed.ip, port = observed.port); -5125 if !self -5126 .peer_params -5127 .address_discovery_role -5128 .should_report(&self.config.address_discovery_role) -5129 { -5130 return Err(TransportError::PROTOCOL_VIOLATION( -5131 "received OBSERVED_ADDRESS frame when not negotiated", -5132 )); -5133 } -5134 // must only be sent in data space -5135 if packet.header.space() != SpaceKind::Data { -5136 return Err(TransportError::PROTOCOL_VIOLATION( -5137 "OBSERVED_ADDRESS frame outside data space", -5138 )); -5139 } -5140 -5141 let path = self.path_data_mut(path_id); -5142 if network_path == path.network_path { -5143 if let Some(updated) = path.update_observed_addr_report(observed) -5144 && path.open_status == paths::OpenStatus::Informed -5145 { -5146 self.events.push_back(Event::Path(PathEvent::ObservedAddr { -5147 id: path_id, -5148 addr: updated, -5149 })); -5150 // otherwise the event is reported when the path is deemed open -5151 } -5152 } else { -5153 // include in migration -5154 migration_observed_addr = Some(observed) -5155 } -5156 } -5157 Frame::PathAbandon(frame::PathAbandon { -5158 path_id, -5159 error_code, -5160 }) => { -5161 span.record("path", tracing::field::display(&path_id)); -5162 match self.close_path_inner( -5163 now, -5164 path_id, -5165 PathAbandonReason::RemoteAbandoned { -5166 error_code: error_code.into(), -5167 }, -5168 ) { -5169 Ok(()) => { -5170 trace!("peer abandoned path"); -5171 } -5172 Err(ClosePathError::ClosedPath) => { -5173 trace!("peer abandoned already closed path"); -5174 } -5175 Err(ClosePathError::MultipathNotNegotiated) => { -5176 return Err(TransportError::PROTOCOL_VIOLATION( -5177 "received PATH_ABANDON frame when multipath was not negotiated", -5178 )); -5179 } -5180 Err(ClosePathError::LastOpenPath) => { -5181 // Not reachable: close_path_inner allows remote abandons -5182 // for the last path. But handle gracefully just in case. -5183 error!( -5184 "peer abandoned last path but close_path_inner returned LastOpenPath" -5185 ); +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 )); +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 } +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); +5079 return Err(TransportError::STREAM_STATE_ERROR( +5080 "STREAM_DATA_BLOCKED on send-only 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 )); +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 }; -5188 -5189 // Start draining the path if it still exists and hasn't started draining yet. -5190 if let Some(path) = self.paths.get_mut(&path_id) -5191 && !mem::replace(&mut path.data.draining, true) -5192 { -5193 let ack_delay = self.ack_frequency.max_ack_delay_for_pto(); -5194 let pto = path.data.rtt.pto_base() + ack_delay; -5195 self.timers.set( -5196 Timer::PerPath(path_id, PathTimer::PathDrained), -5197 now + 3 * pto, -5198 self.qlog.with_time(now), -5199 ); -5200 -5201 self.set_max_path_id(now, self.local_max_path_id.saturating_add(1u8)); -5202 } -5203 } -5204 Frame::PathStatusAvailable(info) => { -5205 span.record("path", tracing::field::display(&info.path_id)); -5206 if self.is_multipath_negotiated() { -5207 self.on_path_status( -5208 info.path_id, -5209 PathStatus::Available, -5210 info.status_seq_no, -5211 ); -5212 } else { -5213 return Err(TransportError::PROTOCOL_VIOLATION( -5214 "received PATH_STATUS_AVAILABLE frame when multipath was not negotiated", -5215 )); -5216 } -5217 } -5218 Frame::PathStatusBackup(info) => { -5219 span.record("path", tracing::field::display(&info.path_id)); -5220 if self.is_multipath_negotiated() { -5221 self.on_path_status(info.path_id, PathStatus::Backup, info.status_seq_no); -5222 } else { -5223 return Err(TransportError::PROTOCOL_VIOLATION( -5224 "received PATH_STATUS_BACKUP frame when multipath was not negotiated", -5225 )); -5226 } -5227 } -5228 Frame::MaxPathId(frame::MaxPathId(path_id)) => { -5229 span.record("path", tracing::field::display(&path_id)); -5230 if !self.is_multipath_negotiated() { -5231 return Err(TransportError::PROTOCOL_VIOLATION( -5232 "received MAX_PATH_ID frame when multipath was not negotiated", -5233 )); +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 // frames that do not increase the path id are ignored -5236 if path_id > self.remote_max_path_id { -5237 self.remote_max_path_id = path_id; -5238 self.issue_first_path_cids(now); -5239 while let Some(true) = self.continue_nat_traversal_round(now) {} -5240 } -5241 } -5242 Frame::PathsBlocked(frame::PathsBlocked(max_path_id)) => { -5243 // Receipt of a value of Maximum Path Identifier or Path Identifier that is higher than the local maximum value MUST -5244 // be treated as a connection error of type PROTOCOL_VIOLATION. -5245 // Ref <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-paths_blocked-and-path_cids> -5246 if self.is_multipath_negotiated() { -5247 if max_path_id > self.local_max_path_id { -5248 return Err(TransportError::PROTOCOL_VIOLATION( -5249 "PATHS_BLOCKED maximum path identifier was larger than local maximum", -5250 )); -5251 } -5252 debug!("received PATHS_BLOCKED({:?})", max_path_id); -5253 // TODO(@divma): ensure max concurrent paths -5254 } else { -5255 return Err(TransportError::PROTOCOL_VIOLATION( -5256 "received PATHS_BLOCKED frame when not multipath was not negotiated", -5257 )); -5258 } -5259 } -5260 Frame::PathCidsBlocked(frame::PathCidsBlocked { path_id, next_seq }) => { -5261 // Nothing to do. This is recorded in the frame stats, but otherwise we -5262 // always issue all CIDs we're allowed to issue, so either this is an -5263 // impatient peer or a bug on our side. -5264 -5265 // Receipt of a value of Maximum Path Identifier or Path Identifier that is higher than the local maximum value MUST -5266 // be treated as a connection error of type PROTOCOL_VIOLATION. -5267 // Ref <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-paths_blocked-and-path_cids> -5268 if self.is_multipath_negotiated() { -5269 if path_id > self.local_max_path_id { -5270 return Err(TransportError::PROTOCOL_VIOLATION( -5271 "PATH_CIDS_BLOCKED path identifier was larger than local maximum", -5272 )); -5273 } -5274 if next_seq.0 -5275 > self -5276 .local_cid_state -5277 .get(&path_id) -5278 .map(|cid_state| cid_state.active_seq().1 + 1) -5279 .unwrap_or_default() -5280 { -5281 return Err(TransportError::PROTOCOL_VIOLATION( -5282 "PATH_CIDS_BLOCKED next sequence number larger than in local state", -5283 )); -5284 } -5285 debug!(%path_id, %next_seq, "received PATH_CIDS_BLOCKED"); -5286 } else { -5287 return Err(TransportError::PROTOCOL_VIOLATION( -5288 "received PATH_CIDS_BLOCKED frame when not multipath was not negotiated", -5289 )); -5290 } -5291 } -5292 Frame::AddAddress(addr) => { -5293 let client_state = match self.n0_nat_traversal.client_side_mut() { -5294 Ok(state) => state, -5295 Err(err) => { -5296 return Err(TransportError::PROTOCOL_VIOLATION(format!( -5297 "Nat traversal(ADD_ADDRESS): {err}" -5298 ))); -5299 } -5300 }; -5301 -5302 if !client_state.check_remote_address(&addr) { -5303 // if the address is not valid we flag it, but update anyway -5304 warn!(?addr, "server sent illegal ADD_ADDRESS frame"); -5305 } -5306 -5307 match client_state.add_remote_address(addr) { -5308 Ok(maybe_added) => { -5309 if let Some(added) = maybe_added { -5310 self.events.push_back(Event::NatTraversal( -5311 n0_nat_traversal::Event::AddressAdded(added), -5312 )); -5313 } -5314 } -5315 Err(e) => { -5316 warn!(%e, "failed to add remote address") -5317 } -5318 } -5319 } -5320 Frame::RemoveAddress(addr) => { -5321 let client_state = match self.n0_nat_traversal.client_side_mut() { -5322 Ok(state) => state, -5323 Err(err) => { -5324 return Err(TransportError::PROTOCOL_VIOLATION(format!( -5325 "Nat traversal(REMOVE_ADDRESS): {err}" -5326 ))); -5327 } -5328 }; -5329 if let Some(removed_addr) = client_state.remove_remote_address(addr) { -5330 self.events.push_back(Event::NatTraversal( -5331 n0_nat_traversal::Event::AddressRemoved(removed_addr), -5332 )); -5333 } -5334 } -5335 Frame::ReachOut(reach_out) => { -5336 let ipv6 = self.is_ipv6(); -5337 let server_state = match self.n0_nat_traversal.server_side_mut() { -5338 Ok(state) => state, -5339 Err(err) => { -5340 return Err(TransportError::PROTOCOL_VIOLATION(format!( -5341 "Nat traversal(REACH_OUT): {err}" -5342 ))); -5343 } -5344 }; -5345 -5346 if let Err(err) = server_state.handle_reach_out(reach_out, ipv6) { -5347 return Err(TransportError::PROTOCOL_VIOLATION(format!( -5348 "Nat traversal(REACH_OUT): {err}" -5349 ))); -5350 } -5351 } -5352 } -5353 } -5354 -5355 let space = self.spaces[SpaceId::Data].for_path(path_id); -5356 if space -5357 .pending_acks -5358 .packet_received(now, number, ack_eliciting, &space.dedup) -5359 { -5360 if self.abandoned_paths.contains(&path_id) { -5361 // § 3.4.3 QUIC-MULTIPATH: promptly send ACKs for packets received from -5362 // abandoned paths. -5363 space.pending_acks.set_immediate_ack_required(); -5364 } else { -5365 self.timers.set( -5366 Timer::PerPath(path_id, PathTimer::MaxAckDelay), -5367 now + self.ack_frequency.max_ack_delay, -5368 self.qlog.with_time(now), -5369 ); -5370 } -5371 } +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); +5257 } +5258 } +5259 Frame::AckFrequency(ack_frequency) => { +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 // Issue stream ID credit due to ACKs of outgoing finish/resets and incoming finish/resets -5374 // on stopped streams. Incoming finishes/resets on open streams are not handled here as they -5375 // are only freed, and hence only issue credit, once the application has been notified -5376 // during a read on the stream. -5377 let pending = &mut self.spaces[SpaceId::Data].pending; -5378 self.streams.queue_max_stream_id(pending); -5379 -5380 if let Some(reason) = close { -5381 self.state.move_to_draining(Some(reason.into())); -5382 self.connection_close_pending = true; -5383 } +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 if Some(number) == self.spaces[SpaceId::Data].for_path(path_id).rx_packet -5386 && !is_probing_packet -5387 && network_path != self.path_data(path_id).network_path -5388 { -5389 let ConnectionSide::Server { ref server_config } = self.side else { -5390 panic!("packets from unknown remote should be dropped by clients"); -5391 }; -5392 debug_assert!( -5393 server_config.migration, -5394 "migration-initiating packets should have been dropped immediately" -5395 ); -5396 self.migrate(path_id, now, network_path, migration_observed_addr); -5397 // Break linkability, if possible -5398 self.update_remote_cid(path_id); -5399 self.spin = false; -5400 } -5401 -5402 Ok(()) -5403 } -5404 -5405 fn migrate( -5406 &mut self, -5407 path_id: PathId, -5408 now: Instant, -5409 network_path: FourTuple, -5410 observed_addr: Option<ObservedAddr>, -5411 ) { -5412 trace!(%network_path, %path_id, "migration initiated"); -5413 self.path_generation_counter = self.path_generation_counter.wrapping_add(1); -5414 // TODO(@divma): conditions for path migration in multipath are very specific, check them -5415 // again to prevent path migrations that should actually create a new path -5416 -5417 // Reset rtt/congestion state for new path unless it looks like a NAT rebinding. -5418 // Note that the congestion window will not grow until validation terminates. Helps mitigate -5419 // amplification attacks performed by spoofing source addresses. -5420 let prev_pto = self.pto(SpaceKind::Data, path_id); -5421 let path = self.paths.get_mut(&path_id).expect("known path"); -5422 let mut new_path_data = if network_path.remote.is_ipv4() -5423 && network_path.remote.ip() == path.data.network_path.remote.ip() -5424 { -5425 PathData::from_previous(network_path, &path.data, self.path_generation_counter, now) -5426 } else { -5427 let peer_max_udp_payload_size = -5428 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()) -5429 .unwrap_or(u16::MAX); -5430 PathData::new( -5431 network_path, -5432 self.allow_mtud, -5433 Some(peer_max_udp_payload_size), -5434 self.path_generation_counter, -5435 now, -5436 &self.config, -5437 ) -5438 }; -5439 new_path_data.last_observed_addr_report = path.data.last_observed_addr_report.clone(); -5440 if let Some(report) = observed_addr -5441 && let Some(updated) = new_path_data.update_observed_addr_report(report) -5442 { -5443 tracing::info!("adding observed addr event from migration"); -5444 self.events.push_back(Event::Path(PathEvent::ObservedAddr { -5445 id: path_id, -5446 addr: updated, -5447 })); -5448 } -5449 new_path_data.pending_on_path_challenge = true; -5450 -5451 let mut prev_path_data = mem::replace(&mut path.data, new_path_data); -5452 -5453 // Only store this as previous path if it was validated. For all we know there could -5454 // already be a previous path stored which might have been validated in the past, -5455 // which is more valuable than one that's not yet validated. -5456 // -5457 // With multipath it is possible that there are no remote CIDs for the path ID -5458 // yet. In this case we would never have sent on this path yet and would not be able -5459 // to send a PATH_CHALLENGE either, which is currently a fire-and-forget affair -5460 // anyway. So don't store such a path either. -5461 if !prev_path_data.validated -5462 && let Some(cid) = self.remote_cids.get(&path_id).map(CidQueue::active) -5463 { -5464 prev_path_data.pending_on_path_challenge = true; -5465 // We haven't updated the remote CID yet, this captures the remote CID we were using on -5466 // the previous path. -5467 path.prev = Some((cid, prev_path_data)); -5468 } -5469 -5470 // We need to re-assign the correct remote to this path in qlog -5471 self.qlog.emit_tuple_assigned(path_id, network_path, now); -5472 -5473 self.timers.set( -5474 Timer::PerPath(path_id, PathTimer::PathValidationFailed), -5475 now + 3 * cmp::max(self.pto(SpaceKind::Data, path_id), prev_pto), -5476 self.qlog.with_time(now), -5477 ); -5478 } -5479 -5480 /// Handle a change in the local address, i.e. an active migration -5481 /// -5482 /// In the general (non-multipath) case, paths will perform a RFC9000 migration and be pinged -5483 /// for a liveness check. This is the behaviour of a path assumed to be recoverable, even if -5484 /// this is not the case. -5485 /// -5486 /// Clients in a connection in which multipath has been negotiated should migrate paths to new -5487 /// [`PathId`]s. For paths that are known to be non-recoverable can be migrated to a new -5488 /// [`PathId`] by closing the current path, and opening a new one to the same remote. Treating -5489 /// paths as non recoverable when necessary accelerates connectivity re-establishment, or might -5490 /// allow it altogether. -5491 /// -5492 /// The optional `hint` allows callers to indicate when paths are non-recoverable and should be -5493 /// migrated to new a [`PathId`]. -5494 // NOTE: only clients are allowed to migrate, but generally dealing with RFC9000 migrations is -5495 // lacking <https://github.com/n0-computer/noq/issues/364> -5496 pub fn handle_network_change(&mut self, hint: Option<&dyn NetworkChangeHint>, now: Instant) { -5497 debug!("network changed"); -5498 if self.state.is_drained() { -5499 return; -5500 } -5501 if self.highest_space < SpaceKind::Data { -5502 for path in self.paths.values_mut() { -5503 // Clear the local address for it to be obtained from the socket again. -5504 path.data.network_path.local_ip = None; -5505 } -5506 -5507 self.update_remote_cid(PathId::ZERO); -5508 self.ping(); -5509 -5510 return; -5511 } -5512 -5513 // Paths that can't recover so a new path should be open instead. If multipath is not -5514 // negotiated, this will be empty. -5515 let mut non_recoverable_paths = Vec::default(); -5516 let mut recoverable_paths = Vec::default(); -5517 let mut open_paths = 0; -5518 -5519 let is_multipath_negotiated = self.is_multipath_negotiated(); -5520 let is_client = self.side().is_client(); -5521 let immediate_ack_allowed = self.peer_supports_ack_frequency(); -5522 -5523 for (path_id, path) in self.paths.iter_mut() { -5524 if self.abandoned_paths.contains(path_id) { -5525 continue; -5526 } -5527 open_paths += 1; -5528 -5529 // Read the network path BEFORE clearing local_ip, so the hint can -5530 // check which interface the path was using. -5531 let network_path = path.data.network_path; -5532 -5533 // Clear the local address for it to be obtained from the socket again. This applies to -5534 // all paths, regardless of being considered recoverable or not -5535 path.data.network_path.local_ip = None; -5536 let remote = network_path.remote; +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 } +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 // Without multipath, the connection tries to recover the single path, whereas with -5539 // multipath, even in a single-path scenario, we attempt to migrate the path to a new -5540 // PathId. -5541 let attempt_to_recover = if is_multipath_negotiated { -5542 if is_client { -5543 hint.map(|h| h.is_path_recoverable(*path_id, network_path)) -5544 .unwrap_or(false) -5545 } else { -5546 // Servers should have stable addresses so this scenario is generally discouraged. -5547 // There is no way to prevent this, so the best hope is to attempt to recover the -5548 // path -5549 true -5550 } -5551 } else { -5552 // In the non multipath case, we try to recover the single active path -5553 true -5554 }; -5555 -5556 if attempt_to_recover { -5557 recoverable_paths.push((*path_id, remote)); -5558 } else { -5559 non_recoverable_paths.push((*path_id, remote, path.data.local_status())) -5560 } -5561 } -5562 -5563 /* NON RECOVERABLE PATHS */ -5564 // This are handled first, so that in case the treatment intended for these fails, we can -5565 // go the recoverable route instead. -5566 -5567 // Decide if we need to close first or open first in the multipath case. -5568 // - Opening first has a higher risk of getting limited by the negotiated MAX_PATH_ID. -5569 // - Closing first risks this being the only open path. -5570 // We prefer closing paths first unless we identify this is the last open path. -5571 let open_first = open_paths == non_recoverable_paths.len(); -5572 -5573 for (path_id, remote, status) in non_recoverable_paths.into_iter() { -5574 let network_path = FourTuple { -5575 remote, -5576 local_ip: None, /* allow the local ip to be discovered */ -5577 }; +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 open_first && let Err(e) = self.open_path(network_path, status, now) { -5580 debug!(%e,"Failed to open new path for network change"); -5581 // if this fails, let the path try to recover itself -5582 recoverable_paths.push((path_id, remote)); -5583 continue; -5584 } -5585 -5586 if let Err(e) = -5587 self.close_path_inner(now, path_id, PathAbandonReason::UnusableAfterNetworkChange) -5588 { -5589 debug!(%e,"Failed to close unrecoverable path after network change"); -5590 recoverable_paths.push((path_id, remote)); -5591 continue; -5592 } -5593 -5594 if !open_first && let Err(e) = self.open_path(network_path, status, now) { -5595 // Path has already been closed if we got here. Since the path was not recoverable, -5596 // this might be desirable in any case, because other paths exist (!open_first) and -5597 // this was is considered non recoverable -5598 debug!(%e,"Failed to open new path for network change"); -5599 } -5600 } -5601 -5602 /* RECOVERABLE PATHS */ -5603 -5604 for (path_id, remote) in recoverable_paths.into_iter() { -5605 // Schedule a Ping for a liveness check. -5606 if let Some(path_space) = self.spaces[SpaceId::Data].number_spaces.get_mut(&path_id) { -5607 path_space.ping_pending = true; -5608 -5609 if immediate_ack_allowed { -5610 path_space.immediate_ack_pending = true; -5611 } -5612 } -5613 -5614 // Reset PTO backoff so retransmits resume promptly. Congestion controller and -5615 // RTT are intentionally preserved for recoverable paths. We explicitly allow -5616 // this reset also during the handshake, so do not check -5617 // Self::peer_competed_handshake_address_validation. -5618 if let Some(path) = self.paths.get_mut(&path_id) { -5619 path.data.pto_count = 0; -5620 } -5621 self.set_loss_detection_timer(now, path_id); -5622 -5623 let Some((reset_token, retired)) = -5624 self.remote_cids.get_mut(&path_id).and_then(CidQueue::next) -5625 else { -5626 continue; -5627 }; -5628 -5629 // Retire the current remote CID and any CIDs we had to skip. -5630 self.spaces[SpaceId::Data] -5631 .pending -5632 .retire_cids -5633 .extend(retired.map(|seq| (path_id, seq))); +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 debug_assert!(!self.state.is_drained()); // required for endpoint_events, checked above -5636 self.endpoint_events -5637 .push_back(EndpointEventInner::ResetToken(path_id, remote, reset_token)); -5638 } -5639 } -5640 -5641 /// Switch to a previously unused remote connection ID, if possible -5642 fn update_remote_cid(&mut self, path_id: PathId) { -5643 let Some((reset_token, retired)) = self -5644 .remote_cids -5645 .get_mut(&path_id) -5646 .and_then(|cids| cids.next()) -5647 else { -5648 return; -5649 }; -5650 -5651 // Retire the current remote CID and any CIDs we had to skip. -5652 self.spaces[SpaceId::Data] -5653 .pending -5654 .retire_cids -5655 .extend(retired.map(|seq| (path_id, seq))); -5656 let remote = self.path_data(path_id).network_path.remote; -5657 self.set_reset_token(path_id, remote, reset_token); -5658 } -5659 -5660 /// Sends this reset token to the endpoint -5661 /// -5662 /// The endpoint needs to know the reset tokens issued by the peer, so that if the peer -5663 /// sends a reset token it knows to route it to this connection. See RFC 9000 section -5664 /// 10.3. Stateless Reset. -5665 /// -5666 /// Reset tokens are different for each path, the endpoint identifies paths by peer -5667 /// socket address however, not by path ID. -5668 fn set_reset_token(&mut self, path_id: PathId, remote: SocketAddr, reset_token: ResetToken) { -5669 debug_assert!(!self.state.is_drained()); // required for endpoint events, set_reset_token is never called for drained connections -5670 self.endpoint_events -5671 .push_back(EndpointEventInner::ResetToken(path_id, remote, reset_token)); -5672 -5673 // During the handshake the server sends a reset token in the transport -5674 // parameters. When we are the client and we receive the reset token during the -5675 // handshake we want this to affect our peer transport parameters. -5676 // TODO(flub): Pretty sure this is pointless, the entire params is overwritten -5677 // shortly after this was called. And then the params don't have this anymore. -5678 if path_id == PathId::ZERO { -5679 self.peer_params.stateless_reset_token = Some(reset_token); -5680 } -5681 } -5682 -5683 /// Issue an initial set of connection IDs to the peer upon connection -5684 fn issue_first_cids(&mut self, now: Instant) { -5685 if self -5686 .local_cid_state -5687 .get(&PathId::ZERO) -5688 .expect("PathId::ZERO exists when the connection is created") -5689 .cid_len() -5690 == 0 -5691 { -5692 return; -5693 } -5694 -5695 // Subtract 1 to account for the CID we supplied while handshaking -5696 let mut n = self.peer_params.issue_cids_limit() - 1; -5697 if let ConnectionSide::Server { server_config } = &self.side -5698 && server_config.has_preferred_address() -5699 { -5700 // We also sent a CID in the transport parameters -5701 n -= 1; -5702 } -5703 debug_assert!(!self.state.is_drained()); // requirement for endpoint_events -5704 self.endpoint_events -5705 .push_back(EndpointEventInner::NeedIdentifiers(PathId::ZERO, now, n)); -5706 } -5707 -5708 /// Issues an initial set of CIDs for paths that have not yet had any CIDs issued -5709 /// -5710 /// Later CIDs are issued when CIDs expire or are retired by the peer. -5711 fn issue_first_path_cids(&mut self, now: Instant) { -5712 if let Some(max_path_id) = self.max_path_id() { -5713 let mut path_id = self.max_path_id_with_cids.next(); -5714 while path_id <= max_path_id { -5715 self.endpoint_events -5716 .push_back(EndpointEventInner::NeedIdentifiers( -5717 path_id, -5718 now, -5719 self.peer_params.issue_cids_limit(), -5720 )); -5721 path_id = path_id.next(); -5722 } -5723 self.max_path_id_with_cids = max_path_id; -5724 } -5725 } -5726 -5727 /// Populates a packet with frames -5728 /// -5729 /// This tries to fit as many frames as possible into the packet. -5730 /// -5731 /// *path_exclusive_only* means to only build frames which can only be sent on this -5732 /// *path. This is used in multipath for backup paths while there is still an active -5733 /// *path. -5734 fn populate_packet<'a, 'b>( -5735 &mut self, -5736 now: Instant, -5737 space_id: SpaceId, -5738 path_id: PathId, -5739 scheduling_info: &PathSchedulingInfo, -5740 builder: &mut PacketBuilder<'a, 'b>, -5741 ) { -5742 let is_multipath_negotiated = self.is_multipath_negotiated(); -5743 let space_has_keys = self.crypto_state.has_keys(space_id.encryption_level()); -5744 let is_0rtt = space_id == SpaceId::Data && !space_has_keys; -5745 let stats = &mut self.path_stats.for_path(path_id).frame_tx; -5746 let space = &mut self.spaces[space_id]; -5747 let path = &mut self.paths.get_mut(&path_id).expect("known path").data; -5748 space -5749 .for_path(path_id) -5750 .pending_acks -5751 .maybe_ack_non_eliciting(); -5752 -5753 // HANDSHAKE_DONE -5754 if !is_0rtt -5755 && !scheduling_info.is_abandoned -5756 && scheduling_info.may_send_data -5757 && mem::replace(&mut space.pending.handshake_done, false) -5758 { -5759 builder.write_frame(frame::HandshakeDone, stats); -5760 } -5761 -5762 // PING -5763 if !scheduling_info.is_abandoned -5764 && mem::replace(&mut space.for_path(path_id).ping_pending, false) -5765 { -5766 builder.write_frame(frame::Ping, stats); -5767 } -5768 -5769 // IMMEDIATE_ACK -5770 if !scheduling_info.is_abandoned -5771 && mem::replace(&mut space.for_path(path_id).immediate_ack_pending, false) -5772 { -5773 debug_assert_eq!( -5774 space_id, -5775 SpaceId::Data, -5776 "immediate acks must be sent in the data space" -5777 ); -5778 builder.write_frame(frame::ImmediateAck, stats); -5779 } -5780 -5781 // ACK -5782 if !scheduling_info.is_abandoned && scheduling_info.may_send_data { -5783 for path_id in space -5784 .number_spaces -5785 .iter_mut() -5786 .filter(|(_, pns)| pns.pending_acks.can_send()) -5787 .map(|(&path_id, _)| path_id) -5788 .collect::<Vec<_>>() -5789 { -5790 Self::populate_acks( -5791 now, -5792 self.receiving_ecn, -5793 path_id, -5794 space_id, -5795 space, -5796 is_multipath_negotiated, -5797 builder, -5798 stats, -5799 space_has_keys, -5800 ); -5801 } -5802 } +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 }, +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 // ACK_FREQUENCY -5805 if !scheduling_info.is_abandoned -5806 && scheduling_info.may_send_data -5807 && mem::replace(&mut space.pending.ack_frequency, false) -5808 { -5809 let sequence_number = self.ack_frequency.next_sequence_number(); -5810 -5811 // Safe to unwrap because this is always provided when ACK frequency is enabled -5812 let config = self.config.ack_frequency_config.as_ref().unwrap(); +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(); +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 // Ensure the delay is within bounds to avoid a PROTOCOL_VIOLATION error -5815 let max_ack_delay = self.ack_frequency.candidate_max_ack_delay( -5816 path.rtt.get(), -5817 config, -5818 &self.peer_params, -5819 ); -5820 -5821 let frame = frame::AckFrequency { -5822 sequence: sequence_number, -5823 ack_eliciting_threshold: config.ack_eliciting_threshold, -5824 request_max_ack_delay: max_ack_delay.as_micros().try_into().unwrap_or(VarInt::MAX), -5825 reordering_threshold: config.reordering_threshold, -5826 }; -5827 builder.write_frame(frame, stats); -5828 -5829 self.ack_frequency -5830 .ack_frequency_sent(path_id, builder.packet_number, max_ack_delay); -5831 } -5832 -5833 // PATH_CHALLENGE -5834 if !scheduling_info.is_abandoned -5835 && space_id == SpaceId::Data -5836 && path.pending_on_path_challenge -5837 && !self.state.is_closed() -5838 && builder.frame_space_remaining() > frame::PathChallenge::SIZE_BOUND -5839 // we don't want to send new challenges if we are already closing -5840 { -5841 path.pending_on_path_challenge = false; -5842 -5843 let token = self.rng.random(); -5844 path.record_path_challenge_sent(now, token, path.network_path); -5845 // Generate a new challenge every time we send a new PATH_CHALLENGE -5846 let challenge = frame::PathChallenge(token); -5847 builder.write_frame(challenge, stats); -5848 builder.require_padding(); -5849 let pto = self.ack_frequency.max_ack_delay_for_pto() + path.rtt.pto_base(); -5850 if path.open_status == paths::OpenStatus::Pending { -5851 path.open_status = paths::OpenStatus::Sent; -5852 self.timers.set( -5853 Timer::PerPath(path_id, PathTimer::PathOpenFailed), -5854 now + 3 * pto, -5855 self.qlog.with_time(now), -5856 ); -5857 } -5858 -5859 self.timers.set( -5860 Timer::PerPath(path_id, PathTimer::PathChallengeLost), -5861 now + pto, -5862 self.qlog.with_time(now), -5863 ); -5864 -5865 if is_multipath_negotiated && !path.validated && path.pending_on_path_challenge { -5866 // queue informing the path status along with the challenge -5867 space.pending.path_status.insert(path_id); -5868 } -5869 -5870 // Always include an OBSERVED_ADDR frame with a PATH_CHALLENGE, regardless -5871 // of whether one has already been sent on this path. -5872 if space_id == SpaceId::Data -5873 && self -5874 .config -5875 .address_discovery_role -5876 .should_report(&self.peer_params.address_discovery_role) -5877 { -5878 let frame = frame::ObservedAddr::new( -5879 path.network_path.remote, -5880 self.next_observed_addr_seq_no, -5881 ); -5882 if builder.frame_space_remaining() > frame.size() { -5883 builder.write_frame(frame, stats); +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. +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(); +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 self.next_observed_addr_seq_no = -5886 self.next_observed_addr_seq_no.saturating_add(1u8); -5887 path.observed_addr_sent = true; -5888 -5889 space.pending.observed_addr = false; -5890 } -5891 } -5892 } -5893 -5894 // PATH_RESPONSE -5895 if !scheduling_info.is_abandoned -5896 && space_id == SpaceId::Data -5897 && builder.frame_space_remaining() > frame::PathResponse::SIZE_BOUND -5898 && let Some(token) = path.path_responses.pop_on_path(path.network_path) -5899 { -5900 let response = frame::PathResponse(token); -5901 builder.write_frame(response, stats); -5902 builder.require_padding(); -5903 -5904 // NOTE: this is technically not required but might be useful to ride the -5905 // request/response nature of path challenges to refresh an observation -5906 // Since PATH_RESPONSE is a probing frame, this is allowed by the spec. -5907 if space_id == SpaceId::Data -5908 && self -5909 .config -5910 .address_discovery_role -5911 .should_report(&self.peer_params.address_discovery_role) -5912 { -5913 let frame = frame::ObservedAddr::new( -5914 path.network_path.remote, -5915 self.next_observed_addr_seq_no, -5916 ); -5917 if builder.frame_space_remaining() > frame.size() { -5918 builder.write_frame(frame, stats); -5919 -5920 self.next_observed_addr_seq_no = -5921 self.next_observed_addr_seq_no.saturating_add(1u8); -5922 path.observed_addr_sent = true; +5885 /* RECOVERABLE PATHS */ +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 space.pending.observed_addr = false; -5925 } -5926 } -5927 } -5928 -5929 // REACH_OUT -5930 if !scheduling_info.is_abandoned -5931 && scheduling_info.may_send_data -5932 && let Some((round, addresses)) = space.pending.reach_out.as_mut() -5933 { -5934 while let Some(local_addr) = addresses.iter().next().copied() { -5935 let local_addr = addresses.take(&local_addr).expect("found from iter"); -5936 let reach_out = frame::ReachOut::new(*round, local_addr); -5937 if builder.frame_space_remaining() > reach_out.size() { -5938 builder.write_frame(reach_out, stats); -5939 } else { -5940 addresses.insert(local_addr); -5941 break; -5942 } -5943 } -5944 if addresses.is_empty() { -5945 space.pending.reach_out = None; -5946 } -5947 } -5948 -5949 // PATH_ABANDON -5950 if space_id == SpaceId::Data -5951 && scheduling_info.is_abandoned -5952 && scheduling_info.may_self_abandon -5953 && frame::PathAbandon::SIZE_BOUND <= builder.frame_space_remaining() -5954 && let Some(error_code) = space.pending.path_abandon.remove(&path_id) -5955 { -5956 let frame = frame::PathAbandon { -5957 path_id, -5958 error_code, -5959 }; -5960 builder.write_frame(frame, stats); -5961 -5962 // Consider remotely issued CIDs as retired now that we have sent this frame at -5963 // least once. -5964 self.remote_cids.remove(&path_id); -5965 } -5966 while space_id == SpaceId::Data -5967 && scheduling_info.may_send_data -5968 && frame::PathAbandon::SIZE_BOUND <= builder.frame_space_remaining() -5969 && let Some((abandoned_path_id, error_code)) = space.pending.path_abandon.pop_first() -5970 { -5971 let frame = frame::PathAbandon { -5972 path_id: abandoned_path_id, -5973 error_code, -5974 }; -5975 builder.write_frame(frame, stats); -5976 -5977 // Consider remotely issued CIDs as retired now that we have sent this frame at -5978 // least once. -5979 self.remote_cids.remove(&abandoned_path_id); -5980 } -5981 -5982 // OBSERVED_ADDR -5983 if !scheduling_info.is_abandoned -5984 && scheduling_info.may_send_data -5985 && space_id == SpaceId::Data -5986 && self -5987 .config -5988 .address_discovery_role -5989 .should_report(&self.peer_params.address_discovery_role) -5990 && (!path.observed_addr_sent || space.pending.observed_addr) -5991 { -5992 let frame = -5993 frame::ObservedAddr::new(path.network_path.remote, self.next_observed_addr_seq_no); -5994 if builder.frame_space_remaining() > frame.size() { -5995 builder.write_frame(frame, stats); -5996 -5997 self.next_observed_addr_seq_no = self.next_observed_addr_seq_no.saturating_add(1u8); -5998 path.observed_addr_sent = true; -5999 -6000 space.pending.observed_addr = false; -6001 } -6002 } -6003 -6004 // CRYPTO -6005 while !is_0rtt -6006 && !scheduling_info.is_abandoned -6007 && scheduling_info.may_send_data -6008 && builder.frame_space_remaining() > frame::Crypto::SIZE_BOUND -6009 { -6010 let Some(mut frame) = space.pending.crypto.pop_front() else { -6011 break; -6012 }; -6013 -6014 // Calculate the maximum amount of crypto data we can store in the buffer. -6015 // Since the offset is known, we can reserve the exact size required to encode it. -6016 // For length we reserve 2bytes which allows to encode up to 2^14, -6017 // which is more than what fits into normally sized QUIC frames. -6018 let max_crypto_data_size = builder.frame_space_remaining() -6019 - 1 // Frame Type -6020 - VarInt::size(unsafe { VarInt::from_u64_unchecked(frame.offset) }) -6021 - 2; // Maximum encoded length for frame size, given we send less than 2^14 bytes -6022 -6023 let len = frame -6024 .data -6025 .len() -6026 .min(2usize.pow(14) - 1) -6027 .min(max_crypto_data_size); -6028 -6029 let data = frame.data.split_to(len); -6030 let offset = frame.offset; -6031 let truncated = frame::Crypto { offset, data }; -6032 builder.write_frame(truncated, stats); -6033 -6034 if !frame.data.is_empty() { -6035 frame.offset += len as u64; -6036 space.pending.crypto.push_front(frame); -6037 } -6038 } -6039 -6040 // PATH_STATUS_AVAILABLE & PATH_STATUS_BACKUP -6041 while space_id == SpaceId::Data -6042 && !scheduling_info.is_abandoned -6043 && scheduling_info.may_send_data -6044 && frame::PathStatusAvailable::SIZE_BOUND <= builder.frame_space_remaining() -6045 { -6046 let Some(path_id) = space.pending.path_status.pop_first() else { -6047 break; -6048 }; -6049 let Some(path) = self.paths.get(&path_id).map(|path_state| &path_state.data) else { -6050 trace!(%path_id, "discarding queued path status for unknown path"); -6051 continue; -6052 }; -6053 -6054 let seq = path.status.seq(); -6055 match path.local_status() { -6056 PathStatus::Available => { -6057 let frame = frame::PathStatusAvailable { -6058 path_id, -6059 status_seq_no: seq, -6060 }; -6061 builder.write_frame(frame, stats); -6062 } -6063 PathStatus::Backup => { -6064 let frame = frame::PathStatusBackup { -6065 path_id, -6066 status_seq_no: seq, -6067 }; -6068 builder.write_frame(frame, stats); -6069 } -6070 } -6071 } -6072 -6073 // MAX_PATH_ID -6074 if space_id == SpaceId::Data -6075 && !scheduling_info.is_abandoned -6076 && scheduling_info.may_send_data -6077 && space.pending.max_path_id -6078 && frame::MaxPathId::SIZE_BOUND <= builder.frame_space_remaining() -6079 { -6080 let frame = frame::MaxPathId(self.local_max_path_id); -6081 builder.write_frame(frame, stats); -6082 space.pending.max_path_id = false; -6083 } -6084 -6085 // PATHS_BLOCKED -6086 if space_id == SpaceId::Data -6087 && !scheduling_info.is_abandoned -6088 && scheduling_info.may_send_data -6089 && space.pending.paths_blocked -6090 && frame::PathsBlocked::SIZE_BOUND <= builder.frame_space_remaining() +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)); +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 +6089 && scheduling_info.may_send_data +6090 && mem::replace(&mut space.pending.ack_frequency, false) 6091 { -6092 let frame = frame::PathsBlocked(self.remote_max_path_id); -6093 builder.write_frame(frame, stats); -6094 space.pending.paths_blocked = false; -6095 } +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 // PATH_CIDS_BLOCKED -6098 while space_id == SpaceId::Data -6099 && !scheduling_info.is_abandoned -6100 && scheduling_info.may_send_data -6101 && frame::PathCidsBlocked::SIZE_BOUND <= builder.frame_space_remaining() -6102 { -6103 let Some(path_id) = space.pending.path_cids_blocked.pop_first() else { -6104 break; -6105 }; -6106 let next_seq = match self.remote_cids.get(&path_id) { -6107 Some(cid_queue) => VarInt(cid_queue.active_seq() + 1), -6108 None => VarInt(0), +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 let frame = frame::PathCidsBlocked { path_id, next_seq }; -6111 builder.write_frame(frame, stats); -6112 } -6113 -6114 // RESET_STREAM, STOP_SENDING, MAX_DATA, MAX_STREAM_DATA, MAX_STREAMS -6115 if space_id == SpaceId::Data -6116 && !scheduling_info.is_abandoned -6117 && scheduling_info.may_send_data -6118 { -6119 self.streams -6120 .write_control_frames(builder, &mut space.pending, stats); -6121 } -6122 -6123 // NEW_CONNECTION_ID -6124 let cid_len = self -6125 .local_cid_state -6126 .values() -6127 .map(|cid_state| cid_state.cid_len()) -6128 .max() -6129 .expect("some local CID state must exist"); -6130 let new_cid_size_bound = -6131 frame::NewConnectionId::size_bound(is_multipath_negotiated, cid_len); -6132 while !scheduling_info.is_abandoned -6133 && scheduling_info.may_send_data -6134 && builder.frame_space_remaining() > new_cid_size_bound -6135 { -6136 let Some(issued) = space.pending.new_cids.pop() else { -6137 break; -6138 }; -6139 let retire_prior_to = self -6140 .local_cid_state -6141 .get(&issued.path_id) -6142 .map(|cid_state| cid_state.retire_prior_to()) -6143 .unwrap_or_else(|| panic!("missing local CID state for path={}", issued.path_id)); +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 let cid_path_id = match is_multipath_negotiated { -6146 true => Some(issued.path_id), -6147 false => { -6148 debug_assert_eq!(issued.path_id, PathId::ZERO); -6149 None -6150 } -6151 }; -6152 let frame = frame::NewConnectionId { -6153 path_id: cid_path_id, -6154 sequence: issued.sequence, -6155 retire_prior_to, -6156 id: issued.id, -6157 reset_token: issued.reset_token, -6158 }; -6159 builder.write_frame(frame, stats); -6160 } -6161 -6162 // RETIRE_CONNECTION_ID -6163 let retire_cid_bound = frame::RetireConnectionId::size_bound(is_multipath_negotiated); -6164 while !scheduling_info.is_abandoned -6165 && scheduling_info.may_send_data -6166 && builder.frame_space_remaining() > retire_cid_bound -6167 { -6168 let (path_id, sequence) = match space.pending.retire_cids.pop() { -6169 Some((PathId::ZERO, seq)) if !is_multipath_negotiated => (None, seq), -6170 Some((path_id, seq)) => (Some(path_id), seq), -6171 None => break, -6172 }; -6173 let frame = frame::RetireConnectionId { path_id, sequence }; -6174 builder.write_frame(frame, stats); -6175 } -6176 -6177 // DATAGRAM -6178 let mut sent_datagrams = false; -6179 while !scheduling_info.is_abandoned -6180 && scheduling_info.may_send_data -6181 && builder.frame_space_remaining() > Datagram::SIZE_BOUND +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 { -6184 match self.datagrams.write(builder, stats) { -6185 true => { -6186 sent_datagrams = true; -6187 } -6188 false => break, -6189 } -6190 } -6191 if self.datagrams.send_blocked && sent_datagrams { -6192 self.events.push_back(Event::DatagramsUnblocked); -6193 self.datagrams.send_blocked = false; -6194 } -6195 -6196 let path = &mut self.paths.get_mut(&path_id).expect("known path").data; -6197 -6198 // NEW_TOKEN -6199 if !scheduling_info.is_abandoned && scheduling_info.may_send_data { -6200 while let Some(network_path) = space.pending.new_tokens.pop() { -6201 debug_assert_eq!(space_id, SpaceId::Data); -6202 let ConnectionSide::Server { server_config } = &self.side else { -6203 panic!("NEW_TOKEN frames should not be enqueued by clients"); -6204 }; +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 if !network_path.is_probably_same_path(&path.network_path) { -6207 // NEW_TOKEN frames contain tokens bound to a client's IP address, and are only -6208 // useful if used from the same IP address. Thus, we abandon enqueued NEW_TOKEN -6209 // frames upon an path change. Instead, when the new path becomes validated, -6210 // NEW_TOKEN frames may be enqueued for the new path instead. -6211 continue; -6212 } -6213 -6214 let token = Token::new( -6215 TokenPayload::Validation { -6216 ip: network_path.remote.ip(), -6217 issued: server_config.time_source.now(), -6218 }, -6219 &mut self.rng, -6220 ); -6221 let new_token = NewToken { -6222 token: token.encode(&*server_config.token_key).into(), -6223 }; -6224 -6225 if builder.frame_space_remaining() < new_token.size() { -6226 space.pending.new_tokens.push(network_path); -6227 break; -6228 } -6229 -6230 builder.write_frame(new_token, stats); -6231 builder.retransmits_mut().new_tokens.push(network_path); -6232 } -6233 } -6234 -6235 // ADD_ADDRESS -6236 while space_id == SpaceId::Data -6237 && !scheduling_info.is_abandoned -6238 && scheduling_info.may_send_data -6239 && frame::AddAddress::SIZE_BOUND <= builder.frame_space_remaining() -6240 { -6241 if let Some(added_address) = space.pending.add_address.pop_last() { -6242 builder.write_frame(added_address, stats); -6243 } else { -6244 break; -6245 } -6246 } -6247 -6248 // REMOVE_ADDRESS -6249 while space_id == SpaceId::Data -6250 && !scheduling_info.is_abandoned -6251 && scheduling_info.may_send_data -6252 && frame::RemoveAddress::SIZE_BOUND <= builder.frame_space_remaining() -6253 { -6254 if let Some(removed_address) = space.pending.remove_address.pop_last() { -6255 builder.write_frame(removed_address, stats); -6256 } else { -6257 break; -6258 } -6259 } -6260 -6261 // STREAM -6262 if !scheduling_info.is_abandoned -6263 && scheduling_info.may_send_data -6264 && space_id == SpaceId::Data -6265 { -6266 self.streams -6267 .write_stream_frames(builder, self.config.send_fairness, stats); -6268 } -6269 } -6270 -6271 /// Write pending ACKs into a buffer -6272 fn populate_acks<'a, 'b>( -6273 now: Instant, -6274 receiving_ecn: bool, -6275 path_id: PathId, -6276 space_id: SpaceId, -6277 space: &mut PacketSpace, -6278 is_multipath_negotiated: bool, -6279 builder: &mut PacketBuilder<'a, 'b>, -6280 stats: &mut FrameStats, -6281 space_has_keys: bool, -6282 ) { -6283 // 0-RTT packets must never carry acks (which would have to be of handshake packets) -6284 debug_assert!(space_has_keys, "tried to send ACK in 0-RTT"); -6285 -6286 debug_assert!( -6287 is_multipath_negotiated || path_id == PathId::ZERO, -6288 "Only PathId::ZERO allowed without multipath (have {path_id:?})" -6289 ); -6290 if is_multipath_negotiated { -6291 debug_assert!( -6292 space_id == SpaceId::Data || path_id == PathId::ZERO, -6293 "path acks must be sent in 1RTT space (have {space_id:?})" -6294 ); -6295 } -6296 -6297 let pns = space.for_path(path_id); -6298 let ranges = pns.pending_acks.ranges(); -6299 debug_assert!(!ranges.is_empty(), "can not send empty ACK range"); -6300 let ecn = if receiving_ecn { -6301 Some(&pns.ecn_counters) -6302 } else { -6303 None -6304 }; +6206 self.next_observed_addr_seq_no = +6207 self.next_observed_addr_seq_no.saturating_add(1u8); +6208 path.observed_addr_sent = true; +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; +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 delay_micros = pns.pending_acks.ack_delay(now).as_micros() as u64; -6307 // TODO: This should come from `TransportConfig` if that gets configurable. -6308 let ack_delay_exp = TransportParameters::default().ack_delay_exponent; -6309 let delay = delay_micros >> ack_delay_exp.into_inner(); +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 is_multipath_negotiated && space_id == SpaceId::Data { -6312 if !ranges.is_empty() { -6313 let frame = frame::PathAck::encoder(path_id, delay, ranges, ecn); -6314 builder.write_frame(frame, stats); -6315 } -6316 } else { -6317 builder.write_frame(frame::Ack::encoder(delay, ranges, ecn), stats); -6318 } -6319 } -6320 -6321 fn close_common(&mut self) { -6322 trace!("connection closed"); -6323 self.timers.reset(); -6324 } -6325 -6326 fn set_close_timer(&mut self, now: Instant) { -6327 // QUIC-MULTIPATH § 2.6 Connection Closure: draining for 3*PTO using the max PTO of -6328 // all paths. -6329 let pto_max = self.max_pto_for_space(self.highest_space); -6330 self.timers.set( -6331 Timer::Conn(ConnTimer::Close), -6332 now + 3 * pto_max, -6333 self.qlog.with_time(now), -6334 ); -6335 } -6336 -6337 /// Handle transport parameters received from the peer -6338 /// -6339 /// *remote_cid* and *local_cid* are the source and destination CIDs respectively of the -6340 /// *packet into which the transport parameters arrived. -6341 fn handle_peer_params( -6342 &mut self, -6343 params: TransportParameters, -6344 local_cid: ConnectionId, -6345 remote_cid: ConnectionId, -6346 now: Instant, -6347 ) -> Result<(), TransportError> { -6348 if Some(self.original_remote_cid) != params.initial_src_cid -6349 || (self.side.is_client() -6350 && (Some(self.initial_dst_cid) != params.original_dst_cid -6351 || self.retry_src_cid != params.retry_src_cid)) -6352 { -6353 return Err(TransportError::TRANSPORT_PARAMETER_ERROR( -6354 "CID authentication failure", -6355 )); -6356 } -6357 if params.initial_max_path_id.is_some() && (local_cid.is_empty() || remote_cid.is_empty()) { -6358 return Err(TransportError::PROTOCOL_VIOLATION( -6359 "multipath must not use zero-length CIDs", -6360 )); -6361 } -6362 -6363 self.set_peer_params(params); -6364 self.qlog.emit_peer_transport_params_received(self, now); -6365 -6366 Ok(()) -6367 } -6368 -6369 fn set_peer_params(&mut self, params: TransportParameters) { -6370 self.streams.set_params(&params); -6371 self.idle_timeout = -6372 negotiate_max_idle_timeout(self.config.max_idle_timeout, Some(params.max_idle_timeout)); -6373 trace!("negotiated max idle timeout {:?}", self.idle_timeout); -6374 -6375 if let Some(ref info) = params.preferred_address { -6376 // During the handshake PathId::ZERO exists. -6377 self.remote_cids.get_mut(&PathId::ZERO).expect("not yet abandoned").insert(frame::NewConnectionId { -6378 path_id: None, -6379 sequence: 1, -6380 id: info.connection_id, -6381 reset_token: info.stateless_reset_token, -6382 retire_prior_to: 0, -6383 }) -6384 .expect( -6385 "preferred address CID is the first received, and hence is guaranteed to be legal", -6386 ); -6387 let remote = self.path_data(PathId::ZERO).network_path.remote; -6388 self.set_reset_token(PathId::ZERO, remote, info.stateless_reset_token); +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 } +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 self.ack_frequency.peer_max_ack_delay = get_max_ack_delay(&params); -6391 -6392 let mut multipath_enabled = None; -6393 if let (Some(local_max_path_id), Some(remote_max_path_id)) = ( -6394 self.config.get_initial_max_path_id(), -6395 params.initial_max_path_id, -6396 ) { -6397 // multipath is enabled, register the local and remote maximums -6398 self.local_max_path_id = local_max_path_id; -6399 self.remote_max_path_id = remote_max_path_id; -6400 let initial_max_path_id = local_max_path_id.min(remote_max_path_id); -6401 debug!(%initial_max_path_id, "multipath negotiated"); -6402 multipath_enabled = Some(initial_max_path_id); -6403 } -6404 -6405 if let Some((max_locally_allowed_remote_addresses, max_remotely_allowed_remote_addresses)) = -6406 self.config -6407 .max_remote_nat_traversal_addresses -6408 .zip(params.max_remote_nat_traversal_addresses) -6409 { -6410 if let Some(max_initial_paths) = -6411 multipath_enabled.map(|path_id| path_id.saturating_add(1u8)) -6412 { -6413 let max_local_addresses = max_remotely_allowed_remote_addresses.get(); -6414 let max_remote_addresses = max_locally_allowed_remote_addresses.get(); -6415 self.n0_nat_traversal = n0_nat_traversal::State::new( -6416 max_remote_addresses, -6417 max_local_addresses, -6418 self.side(), -6419 ); -6420 debug!( -6421 %max_remote_addresses, %max_local_addresses, -6422 "n0's nat traversal negotiated" -6423 ); -6424 -6425 match self.side() { -6426 Side::Client => { -6427 if max_initial_paths.as_u32() < max_remote_addresses as u32 + 1 { -6428 // in this case the client might try to open `max_remote_addresses` new -6429 // paths, but the current multipath configuration will not allow it -6430 debug!(%max_initial_paths, %max_remote_addresses, "local client configuration might cause nat traversal issues") -6431 } else if max_local_addresses as u64 -6432 > params.active_connection_id_limit.into_inner() -6433 { -6434 // the server allows us to send at most `params.active_connection_id_limit` -6435 // but they might need at least `max_local_addresses` to effectively send -6436 // `PATH_CHALLENGE` frames to each advertised local address -6437 debug!(%max_local_addresses, remote_cid_limit=%params.active_connection_id_limit.into_inner(), "remote server configuration might cause nat traversal issues") -6438 } -6439 } -6440 Side::Server => { -6441 if (max_initial_paths.as_u32() as u64) < crate::LOCAL_CID_COUNT { -6442 debug!(%max_initial_paths, local_cid_limit=%crate::LOCAL_CID_COUNT, "local server configuration might cause nat traversal issues") -6443 } -6444 } -6445 } -6446 } else { -6447 debug!("n0 nat traversal enabled for both endpoints, but multipath is missing") -6448 } -6449 } -6450 -6451 self.peer_params = params; -6452 let peer_max_udp_payload_size = -6453 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()).unwrap_or(u16::MAX); -6454 self.path_data_mut(PathId::ZERO) -6455 .mtud -6456 .on_peer_max_udp_payload_size_received(peer_max_udp_payload_size); -6457 } -6458 -6459 /// Decrypts a packet, returning the packet number on success -6460 fn decrypt_packet( -6461 &mut self, -6462 now: Instant, -6463 path_id: PathId, -6464 packet: &mut Packet, -6465 ) -> Result<Option<u64>, Option<TransportError>> { -6466 let result = self -6467 .crypto_state -6468 .decrypt_packet_body(packet, path_id, &self.spaces)?; -6469 -6470 let Some(result) = result else { -6471 return Ok(None); -6472 }; -6473 -6474 if result.outgoing_key_update_acked -6475 && let Some(prev) = self.crypto_state.prev_crypto.as_mut() -6476 { -6477 prev.end_packet = Some((result.number, now)); -6478 self.set_key_discard_timer(now, packet.header.space()); -6479 } -6480 -6481 if result.incoming_key_update { -6482 trace!("key update authenticated"); -6483 self.crypto_state -6484 .update_keys(Some((result.number, now)), true); -6485 self.set_key_discard_timer(now, packet.header.space()); -6486 } -6487 -6488 Ok(Some(result.number)) -6489 } -6490 -6491 fn peer_supports_ack_frequency(&self) -> bool { -6492 self.peer_params.min_ack_delay.is_some() -6493 } +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 +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 /// Send an IMMEDIATE_ACK frame to the remote endpoint -6496 /// -6497 /// According to the spec, this will result in an error if the remote endpoint does not support -6498 /// the Acknowledgement Frequency extension -6499 pub(crate) fn immediate_ack(&mut self, path_id: PathId) { -6500 debug_assert_eq!( -6501 self.highest_space, -6502 SpaceKind::Data, -6503 "immediate ack must be written in the data space" -6504 ); -6505 self.spaces[SpaceId::Data] -6506 .for_path(path_id) -6507 .immediate_ack_pending = true; -6508 } -6509 -6510 /// Decodes a packet, returning its decrypted payload, so it can be inspected in tests -6511 #[cfg(test)] -6512 pub(crate) fn decode_packet(&self, event: &ConnectionEvent) -> Option<Vec<u8>> { -6513 let ConnectionEventInner::Datagram(DatagramConnectionEvent { -6514 path_id, -6515 first_decode, -6516 remaining, -6517 .. -6518 }) = &event.0 -6519 else { -6520 return None; -6521 }; -6522 -6523 if remaining.is_some() { -6524 panic!("Packets should never be coalesced in tests"); -6525 } -6526 -6527 let decrypted_header = self -6528 .crypto_state -6529 .unprotect_header(first_decode.clone(), self.peer_params.stateless_reset_token)?; -6530 -6531 let mut packet = decrypted_header.packet?; -6532 self.crypto_state -6533 .decrypt_packet_body(&mut packet, *path_id, &self.spaces) -6534 .ok()?; -6535 -6536 Some(packet.payload.to_vec()) -6537 } -6538 -6539 /// The number of bytes of packets containing retransmittable frames that have not been -6540 /// acknowledged or declared lost. -6541 #[cfg(test)] -6542 pub(crate) fn bytes_in_flight(&self) -> u64 { -6543 // TODO(@divma): consider including for multipath? -6544 self.path_data(PathId::ZERO).in_flight.bytes -6545 } -6546 -6547 /// Number of bytes worth of non-ack-only packets that may be sent -6548 #[cfg(test)] -6549 pub(crate) fn congestion_window(&self) -> u64 { -6550 let path = self.path_data(PathId::ZERO); -6551 path.congestion -6552 .window() -6553 .saturating_sub(path.in_flight.bytes) -6554 } -6555 -6556 /// Whether no timers but keepalive, idle, rtt, pushnewcid, and key discard are running -6557 #[cfg(test)] -6558 pub(crate) fn is_idle(&self) -> bool { -6559 let current_timers = self.timers.values(); -6560 current_timers -6561 .into_iter() -6562 .filter(|(timer, _)| { -6563 !matches!( -6564 timer, -6565 Timer::Conn(ConnTimer::KeepAlive) -6566 | Timer::PerPath(_, PathTimer::PathKeepAlive) -6567 | Timer::PerPath(_, PathTimer::PathIdle) -6568 | Timer::Conn(ConnTimer::PushNewCid) -6569 | Timer::Conn(ConnTimer::KeyDiscard) -6570 ) -6571 }) -6572 .min_by_key(|(_, time)| *time) -6573 .is_none_or(|(timer, _)| timer == Timer::Conn(ConnTimer::Idle)) -6574 } -6575 -6576 /// Whether explicit congestion notification is in use on outgoing packets. -6577 #[cfg(test)] -6578 pub(crate) fn using_ecn(&self) -> bool { -6579 self.path_data(PathId::ZERO).sending_ecn -6580 } -6581 -6582 /// The number of received bytes in the current path -6583 #[cfg(test)] -6584 pub(crate) fn total_recvd(&self) -> u64 { -6585 self.path_data(PathId::ZERO).total_recvd -6586 } -6587 -6588 #[cfg(test)] -6589 pub(crate) fn active_local_cid_seq(&self) -> (u64, u64) { -6590 self.local_cid_state -6591 .get(&PathId::ZERO) -6592 .unwrap() -6593 .active_seq() -6594 } -6595 -6596 #[cfg(test)] -6597 #[track_caller] -6598 pub(crate) fn active_local_path_cid_seq(&self, path_id: u32) -> (u64, u64) { -6599 self.local_cid_state -6600 .get(&PathId(path_id)) -6601 .unwrap() -6602 .active_seq() -6603 } -6604 -6605 /// Instruct the peer to replace previously issued CIDs by sending a NEW_CONNECTION_ID frame -6606 /// with updated `retire_prior_to` field set to `v` -6607 #[cfg(test)] -6608 pub(crate) fn rotate_local_cid(&mut self, v: u64, now: Instant) { -6609 let n = self -6610 .local_cid_state -6611 .get_mut(&PathId::ZERO) -6612 .unwrap() -6613 .assign_retire_seq(v); -6614 debug_assert!(!self.state.is_drained()); // requirement for endpoint_events -6615 self.endpoint_events -6616 .push_back(EndpointEventInner::NeedIdentifiers(PathId::ZERO, now, n)); -6617 } -6618 -6619 /// Check the current active remote CID sequence for `PathId::ZERO` -6620 #[cfg(test)] -6621 pub(crate) fn active_remote_cid_seq(&self) -> u64 { -6622 self.remote_cids.get(&PathId::ZERO).unwrap().active_seq() -6623 } -6624 -6625 /// Returns the detected maximum udp payload size for the current path -6626 #[cfg(test)] -6627 pub(crate) fn path_mtu(&self, path_id: PathId) -> u16 { -6628 self.path_data(path_id).current_mtu() -6629 } -6630 -6631 /// Triggers path validation on all paths -6632 #[cfg(test)] -6633 pub(crate) fn trigger_path_validation(&mut self) { -6634 for path in self.paths.values_mut() { -6635 path.data.pending_on_path_challenge = true; -6636 } -6637 } -6638 -6639 /// Simulates a protocol violation error for test purposes. -6640 #[cfg(test)] -6641 pub fn simulate_protocol_violation(&mut self, now: Instant) { -6642 if !self.state.is_closed() { -6643 self.state -6644 .move_to_closed(TransportError::PROTOCOL_VIOLATION("simulated violation")); -6645 self.close_common(); -6646 if !self.state.is_drained() { -6647 self.set_close_timer(now); -6648 } -6649 self.connection_close_pending = true; -6650 } -6651 } -6652 -6653 /// Whether we have on-path 1-RTT data to send. -6654 /// -6655 /// This checks for frames that can only be sent in the data space (1-RTT): -6656 /// - Pending PATH_CHALLENGE frames on the active and previous path if just migrated. -6657 /// - Pending PATH_RESPONSE frames. -6658 /// - Pending data to send in STREAM frames. -6659 /// - Pending DATAGRAM frames to send. -6660 /// -6661 /// See also [`PacketSpace::can_send`] which keeps track of all other frame types that -6662 /// may need to be sent. -6663 fn can_send_1rtt(&self, path_id: PathId, max_size: usize) -> SendableFrames { -6664 let space_specific = self.paths.get(&path_id).is_some_and(|path| { -6665 path.data.pending_on_path_challenge || !path.data.path_responses.is_empty() -6666 }); -6667 -6668 // Stream control frames are checked in PacketSpace::can_send, only check data here. -6669 let other = self.streams.can_send_stream_data() -6670 || self -6671 .datagrams -6672 .outgoing -6673 .front() -6674 .is_some_and(|x| x.size(true) <= max_size); -6675 -6676 // All `false` fields are set in PacketSpace::can_send. -6677 SendableFrames { -6678 acks: false, -6679 close: false, -6680 space_specific, -6681 other, -6682 } -6683 } -6684 -6685 /// Terminate the connection instantly, without sending a close packet -6686 fn kill(&mut self, reason: ConnectionError) { -6687 self.close_common(); -6688 self.state.move_to_drained(Some(reason)); -6689 // move_to_drained checks that we were never in drained before, so we -6690 // never sent a `Drained` event before (it's illegal to send more events after drained). -6691 self.endpoint_events.push_back(EndpointEventInner::Drained); -6692 } -6693 -6694 /// Storage size required for the largest packet that can be transmitted on all currently -6695 /// available paths -6696 /// -6697 /// Buffers passed to [`Connection::poll_transmit`] should be at least this large. -6698 /// -6699 /// When multipath is enabled, this value is the minimum MTU across all available paths. -6700 pub fn current_mtu(&self) -> u16 { -6701 self.paths -6702 .iter() -6703 .filter(|&(path_id, _path_state)| !self.abandoned_paths.contains(path_id)) -6704 .map(|(_path_id, path_state)| path_state.data.current_mtu()) -6705 .min() -6706 .unwrap_or(INITIAL_MTU) -6707 } -6708 -6709 /// Size of non-frame data for a 1-RTT packet -6710 /// -6711 /// Quantifies space consumed by the QUIC header and AEAD tag. All other bytes in a packet are -6712 /// frames. Changes if the length of the remote connection ID changes, which is expected to be -6713 /// rare. If `pn` is specified, may additionally change unpredictably due to variations in -6714 /// latency and packet loss. -6715 fn predict_1rtt_overhead(&mut self, pn: u64, path: PathId) -> usize { -6716 let pn_len = PacketNumber::new( -6717 pn, -6718 self.spaces[SpaceId::Data] -6719 .for_path(path) -6720 .largest_acked_packet -6721 .unwrap_or(0), -6722 ) -6723 .len(); -6724 -6725 // 1 byte for flags -6726 1 + self -6727 .remote_cids -6728 .get(&path) -6729 .map(|cids| cids.active().len()) -6730 .unwrap_or(20) // Max CID len in QUIC v1 -6731 + pn_len -6732 + self.tag_len_1rtt() -6733 } -6734 -6735 fn predict_1rtt_overhead_no_pn(&self) -> usize { -6736 let pn_len = 4; +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 } +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 } +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 let cid_len = self -6739 .remote_cids -6740 .values() -6741 .map(|cids| cids.active().len()) -6742 .max() -6743 .unwrap_or(20); // Max CID len in QUIC v1 +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 // 1 byte for flags -6746 1 + cid_len + pn_len + self.tag_len_1rtt() -6747 } -6748 -6749 fn tag_len_1rtt(&self) -> usize { -6750 // encryption_keys for Data space returns 1-RTT keys if available, otherwise 0-RTT keys -6751 let packet_crypto = self -6752 .crypto_state -6753 .encryption_keys(SpaceKind::Data, self.side.side()) -6754 .map(|(_header, packet, _level)| packet); -6755 // If neither Data nor 0-RTT keys are available, make a reasonable tag length guess. As of -6756 // this writing, all QUIC cipher suites use 16-byte tags. We could return `None` instead, -6757 // but that would needlessly prevent sending datagrams during 0-RTT. -6758 packet_crypto.map_or(16, |x| x.tag_len()) -6759 } -6760 -6761 /// Mark the path as validated, and enqueue NEW_TOKEN frames to be sent as appropriate -6762 fn on_path_validated(&mut self, path_id: PathId) { -6763 self.path_data_mut(path_id).validated = true; -6764 let ConnectionSide::Server { server_config } = &self.side else { -6765 return; -6766 }; -6767 let network_path = self.path_data(path_id).network_path; -6768 let new_tokens = &mut self.spaces[SpaceId::Data as usize].pending.new_tokens; -6769 new_tokens.clear(); -6770 for _ in 0..server_config.validation_token.sent { -6771 new_tokens.push(network_path); -6772 } -6773 } -6774 -6775 /// Handle new path status information: PATH_STATUS_AVAILABLE, PATH_STATUS_BACKUP -6776 fn on_path_status(&mut self, path_id: PathId, status: PathStatus, status_seq_no: VarInt) { -6777 if let Some(path) = self.paths.get_mut(&path_id) { -6778 path.data.status.remote_update(status, status_seq_no); -6779 } else { -6780 debug!("PATH_STATUS_AVAILABLE received unknown path {:?}", path_id); -6781 } -6782 self.events.push_back( -6783 PathEvent::RemoteStatus { -6784 id: path_id, -6785 status, -6786 } -6787 .into(), -6788 ); -6789 } -6790 -6791 /// Returns the maximum [`PathId`] to be used for sending in this connection. -6792 /// -6793 /// This is calculated as minimum between the local and remote's maximums when multipath is -6794 /// enabled, or `None` when disabled. -6795 /// -6796 /// For data that's received, we should use [`Self::local_max_path_id`] instead. -6797 /// The reasoning is that the remote might already have updated to its own newer -6798 /// [`Self::max_path_id`] after sending out a `MAX_PATH_ID` frame, but it got re-ordered. -6799 fn max_path_id(&self) -> Option<PathId> { -6800 if self.is_multipath_negotiated() { -6801 Some(self.remote_max_path_id.min(self.local_max_path_id)) -6802 } else { -6803 None -6804 } -6805 } -6806 -6807 /// Returns whether this connection has a socket that supports IPv6. -6808 /// -6809 /// TODO(matheus23): This is related to noq endpoint state's `ipv6` bool. We should move that info -6810 /// here instead of trying to hack around not knowing it exactly. -6811 fn is_ipv6(&self) -> bool { -6812 self.paths -6813 .values() -6814 .any(|p| p.data.network_path.remote.is_ipv6()) -6815 } -6816 -6817 /// Add addresses the local endpoint considers are reachable for nat traversal. -6818 pub fn add_nat_traversal_address( -6819 &mut self, -6820 address: SocketAddr, -6821 ) -> Result<(), n0_nat_traversal::Error> { -6822 if let Some(added) = self.n0_nat_traversal.add_local_address(address)? { -6823 self.spaces[SpaceId::Data].pending.add_address.insert(added); -6824 }; -6825 Ok(()) -6826 } -6827 -6828 /// Removes an address the endpoing no longer considers reachable for nat traversal -6829 /// -6830 /// Addresses not present in the set will be silently ignored. -6831 pub fn remove_nat_traversal_address( -6832 &mut self, -6833 address: SocketAddr, -6834 ) -> Result<(), n0_nat_traversal::Error> { -6835 if let Some(removed) = self.n0_nat_traversal.remove_local_address(address)? { -6836 self.spaces[SpaceId::Data] -6837 .pending -6838 .remove_address -6839 .insert(removed); -6840 } -6841 Ok(()) -6842 } -6843 -6844 /// Get the current local nat traversal addresses -6845 pub fn get_local_nat_traversal_addresses( -6846 &self, -6847 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { -6848 self.n0_nat_traversal.get_local_nat_traversal_addresses() -6849 } -6850 -6851 /// Get the currently advertised nat traversal addresses by the server -6852 pub fn get_remote_nat_traversal_addresses( -6853 &self, -6854 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { -6855 Ok(self -6856 .n0_nat_traversal -6857 .client_side()? -6858 .get_remote_nat_traversal_addresses()) -6859 } -6860 -6861 /// Attempts to open a path for nat traversal. -6862 /// -6863 /// On success returns the [`PathId`] and remote address of the path. -6864 fn open_nat_traversal_path( -6865 &mut self, -6866 now: Instant, -6867 ip_port: (IpAddr, u16), -6868 ) -> Result<Option<(PathId, SocketAddr)>, PathError> { -6869 let remote = ip_port.into(); -6870 // TODO(matheus23): Probe the correct 4-tuple, instead of only a remote address? -6871 // By specifying None for `local_ip`, we do two things: 1. open_path_ensure won't -6872 // generate two paths to the same remote and 2. we let the OS choose which -6873 // interface to use for sending on that path. -6874 let network_path = FourTuple { -6875 remote, -6876 local_ip: None, -6877 }; -6878 match self.open_path_ensure(network_path, PathStatus::Backup, now) { -6879 Ok((path_id, path_was_known)) => { -6880 if path_was_known { -6881 trace!(%path_id, %remote, "nat traversal: path existed for remote, revalidating"); -6882 if let Some(path) = self.paths.get_mut(&path_id) { -6883 path.data.pending_on_path_challenge = true; -6884 } -6885 } -6886 Ok(Some((path_id, remote))) -6887 } -6888 Err(e) => { -6889 debug!(%remote, %e, "nat traversal: failed to probe remote"); -6890 Err(e) -6891 } -6892 } -6893 } -6894 -6895 /// Initiates a new nat traversal round -6896 /// -6897 /// A nat traversal round involves advertising the client's local addresses in `REACH_OUT` -6898 /// frames, and initiating probing of the known remote addresses. When a new round is -6899 /// initiated, the previous one is cancelled, and paths that have not been opened are closed. -6900 /// -6901 /// Returns the server addresses that are now being probed. -6902 /// If addresses fail due to spurious errors, these might succeed later and not be returned in -6903 /// this set. -6904 pub fn initiate_nat_traversal_round( -6905 &mut self, -6906 now: Instant, -6907 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { -6908 if self.state.is_closed() { -6909 return Err(n0_nat_traversal::Error::Closed); -6910 } -6911 -6912 let ipv6 = self.is_ipv6(); -6913 let client_state = self.n0_nat_traversal.client_side_mut()?; -6914 let n0_nat_traversal::NatTraversalRound { -6915 new_round, -6916 reach_out_at, -6917 addresses_to_probe, -6918 prev_round_path_ids, -6919 } = client_state.initiate_nat_traversal_round(ipv6)?; -6920 -6921 trace!(%new_round, reach_out=reach_out_at.len(), to_probe=addresses_to_probe.len(), -6922 "initiating nat traversal round"); -6923 -6924 self.spaces[SpaceId::Data].pending.reach_out = Some((new_round, reach_out_at)); -6925 -6926 for path_id in prev_round_path_ids { -6927 let Some(path) = self.path(path_id) else { -6928 continue; -6929 }; -6930 let ip = path.network_path.remote.ip(); -6931 let port = path.network_path.remote.port(); -6932 -6933 // We only close paths that aren't validated (thus are working) that we opened -6934 // in a previous round. -6935 // And we only close paths that we don't want to probe anyways. -6936 if !addresses_to_probe -6937 .iter() -6938 .any(|(_, probe)| *probe == (ip, port)) -6939 && !path.validated -6940 && !self.abandoned_paths.contains(&path_id) -6941 { -6942 trace!(%path_id, "closing path from previous round"); -6943 let _ = -6944 self.close_path_inner(now, path_id, PathAbandonReason::NatTraversalRoundEnded); -6945 } -6946 } -6947 -6948 let mut err = None; -6949 -6950 let mut path_ids = Vec::with_capacity(addresses_to_probe.len()); -6951 let mut probed_addresses = Vec::with_capacity(addresses_to_probe.len()); -6952 -6953 for (id, address) in addresses_to_probe { -6954 match self.open_nat_traversal_path(now, address) { -6955 Ok(None) => {} -6956 Ok(Some((path_id, remote))) => { -6957 path_ids.push(path_id); -6958 probed_addresses.push(remote); -6959 } -6960 Err(e) => { -6961 self.n0_nat_traversal -6962 .client_side_mut() -6963 .expect("validated") -6964 .report_in_continuation(id, e); -6965 err.get_or_insert(e); -6966 } -6967 } -6968 } +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; +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 +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 if let Some(err) = err { -6971 // We failed to probe any addresses, bail out -6972 if probed_addresses.is_empty() { -6973 return Err(n0_nat_traversal::Error::Multipath(err)); -6974 } -6975 } -6976 -6977 self.n0_nat_traversal -6978 .client_side_mut() -6979 .expect("connection side validated") -6980 .set_round_path_ids(path_ids); -6981 -6982 Ok(probed_addresses) -6983 } -6984 -6985 /// Attempts to continue a nat traversal round by trying to open paths for pending client probes. -6986 /// -6987 /// If there was nothing to do, it returns `None`. Otherwise it returns whether the path was -6988 /// successfully open. -6989 fn continue_nat_traversal_round(&mut self, now: Instant) -> Option<bool> { -6990 let ipv6 = self.is_ipv6(); -6991 let client_state = self.n0_nat_traversal.client_side_mut().ok()?; -6992 let (id, address) = client_state.continue_nat_traversal_round(ipv6)?; -6993 let open_result = self.open_nat_traversal_path(now, address); -6994 let client_state = self.n0_nat_traversal.client_side_mut().expect("validated"); -6995 match open_result { -6996 Ok(None) => Some(true), -6997 Ok(Some((path_id, _remote))) => { -6998 client_state.add_round_path_id(path_id); -6999 Some(true) -7000 } -7001 Err(e) => { -7002 client_state.report_in_continuation(id, e); -7003 Some(false) -7004 } -7005 } -7006 } -7007} -7008 -7009impl fmt::Debug for Connection { -7010 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -7011 f.debug_struct("Connection") -7012 .field("handshake_cid", &self.handshake_cid) -7013 .finish() -7014 } -7015} -7016 -7017/// Hints when the caller identifies a network change. -7018pub trait NetworkChangeHint: std::fmt::Debug + 'static { -7019 /// Inform the connection if a path may recover after a network change. -7020 /// -7021 /// After network changes, paths may not be recoverable. In this case, waiting for the path to -7022 /// become idle may take longer than what is desirable. If [`Self::is_path_recoverable`] -7023 /// returns `false`, a multipath-enabled, client-side connection will establish a new path to -7024 /// the same remote, closing the current one, instead of migrating the path. -7025 /// -7026 /// Paths that are deemed recoverable will simply be sent a PING for a liveness check. -7027 fn is_path_recoverable(&self, path_id: PathId, network_path: FourTuple) -> bool; -7028} -7029 -7030/// Return value for [`Connection::poll_transmit_on_path`]. -7031#[derive(Debug)] -7032enum PollPathStatus { -7033 /// Nothing to send on the path, nothing was written into the [`TransmitBuf`]. -7034 NothingToSend { -7035 /// If true there was data to send but congestion control did not allow so. -7036 congestion_blocked: bool, -7037 }, -7038 /// The transmit is ready to be sent. -7039 Send(Transmit), -7040} -7041 -7042/// Return value for [`Connection::poll_transmit_path_space`]. -7043#[derive(Debug)] -7044enum PollPathSpaceStatus { -7045 /// Nothing to send in the space, nothing was written into the [`TransmitBuf`]. -7046 NothingToSend { -7047 /// If true there was data to send but congestion control did not allow so. -7048 congestion_blocked: bool, -7049 }, -7050 /// One or more packets have been written into the [`TransmitBuf`]. -7051 WrotePacket { -7052 /// The highest packet number. -7053 last_packet_number: u64, -7054 /// Whether to pad an already started datagram in the next packet. -7055 /// -7056 /// When packets in Initial, 0-RTT or Handshake packet do not fill the entire -7057 /// datagram they may decide to coalesce with the next packet from a higher -7058 /// encryption level on the same path. But the earlier packet may require specific -7059 /// size requirements for the datagram they are sent in. -7060 /// -7061 /// If a space did not complete the datagram, they use this to request the correct -7062 /// padding in the final packet of the datagram so that the final datagram will have -7063 /// the correct size. -7064 /// -7065 /// If a space did fill an entire datagram, it leaves this to the default of -7066 /// [`PadDatagram::No`]. -7067 pad_datagram: PadDatagram, -7068 }, -7069 /// Send the contents of the transmit immediately. -7070 /// -7071 /// Packets were written and the GSO batch must end now, regardless from whether higher -7072 /// spaces still have frames to write. This is used when the last datagram written would -7073 /// require too much padding to continue a GSO batch, which would waste space on the -7074 /// wire. -7075 Send { -7076 /// The highest packet number written into the transmit. -7077 last_packet_number: u64, -7078 }, -7079} -7080 -7081/// Information used to decide what frames to schedule into which packets. -7082/// -7083/// Primarily used by [`Connection::poll_transmit_on_path`] and the functions that help -7084/// building packets for it: [`Connection::poll_transmit_path_space`] and -7085/// [`Connection::populate_packet`]. -7086#[derive(Debug, Copy, Clone)] -7087struct PathSchedulingInfo { -7088 /// Whether the path is abandoned. -7089 /// -7090 /// Note that a path that is abandoned but still has CIDs can still send a packet. After -7091 /// sending that packet the CIDs issued by the remote have to be considered retired as -7092 /// well. -7093 is_abandoned: bool, -7094 /// Whether the path may send [`SpaceKind::Data`] frames. -7095 /// -7096 /// Some paths should only send frames from [`SendableFrames::space_specific`]. All other -7097 /// frames are essentially frames that can be sent on any [`SpaceKind::Data`] space. For -7098 /// those we want to respect packet scheduling rules however. -7099 /// -7100 /// Roughly speaking data frames are only sent on spaces that have CIDs, are not -7101 /// abandoned and have no *better* spaces. However see to comments where this is -7102 /// populated for the exact packet scheduling implementation. -7103 /// -7104 /// This essentially marks this paths as the best validated space ID. Except during -7105 /// the handshake in which case it does not need to be validated. Several paths could be -7106 /// equally good and all have this set to `true`, in that case packet scheduling can -7107 /// choose which path to use. Currently it chooses the lowest path that is not -7108 /// congestion blocked. -7109 /// -7110 /// Note that once in the closed or draining states this will never be true. -7111 may_send_data: bool, -7112 /// Whether the path may send a CONNECTION_CLOSE frame. -7113 /// -7114 /// This essentially marks this path as the best validated space ID with a fallback -7115 /// to unvalidated spaces if there are no validated spaces. Like for -7116 /// [`Self::may_send_data`] other paths could be equally good. -7117 may_send_close: bool, -7118 may_self_abandon: bool, -7119} -7120 -7121#[derive(Debug, Copy, Clone, PartialEq, Eq)] -7122enum PathBlocked { -7123 No, -7124 AntiAmplification, -7125 Congestion, -7126 Pacing, -7127} -7128 -7129/// Fields of `Connection` specific to it being client-side or server-side -7130enum ConnectionSide { -7131 Client { -7132 /// Sent in every outgoing Initial packet. Always empty after Initial keys are discarded -7133 token: Bytes, -7134 token_store: Arc<dyn TokenStore>, -7135 server_name: String, -7136 }, -7137 Server { -7138 server_config: Arc<ServerConfig>, -7139 }, -7140} -7141 -7142impl ConnectionSide { -7143 fn remote_may_migrate(&self, state: &State) -> bool { -7144 match self { -7145 Self::Server { server_config } => server_config.migration, -7146 Self::Client { .. } => { -7147 if let Some(hs) = state.as_handshake() { -7148 hs.allow_server_migration -7149 } else { -7150 false -7151 } -7152 } -7153 } -7154 } -7155 -7156 fn is_client(&self) -> bool { -7157 self.side().is_client() -7158 } -7159 -7160 fn is_server(&self) -> bool { -7161 self.side().is_server() -7162 } -7163 -7164 fn side(&self) -> Side { -7165 match *self { -7166 Self::Client { .. } => Side::Client, -7167 Self::Server { .. } => Side::Server, -7168 } -7169 } -7170} -7171 -7172impl From<SideArgs> for ConnectionSide { -7173 fn from(side: SideArgs) -> Self { -7174 match side { -7175 SideArgs::Client { -7176 token_store, -7177 server_name, -7178 } => Self::Client { -7179 token: token_store.take(&server_name).unwrap_or_default(), -7180 token_store, -7181 server_name, -7182 }, -7183 SideArgs::Server { -7184 server_config, -7185 pref_addr_cid: _, -7186 path_validated: _, -7187 } => Self::Server { server_config }, -7188 } -7189 } -7190} -7191 -7192/// Parameters to `Connection::new` specific to it being client-side or server-side -7193pub(crate) enum SideArgs { -7194 Client { -7195 token_store: Arc<dyn TokenStore>, -7196 server_name: String, -7197 }, -7198 Server { -7199 server_config: Arc<ServerConfig>, -7200 pref_addr_cid: Option<ConnectionId>, -7201 path_validated: bool, +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, +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. +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} -7204 -7205impl SideArgs { -7206 pub(crate) fn pref_addr_cid(&self) -> Option<ConnectionId> { -7207 match *self { -7208 Self::Client { .. } => None, -7209 Self::Server { pref_addr_cid, .. } => pref_addr_cid, -7210 } -7211 } -7212 -7213 pub(crate) fn path_validated(&self) -> bool { -7214 match *self { -7215 Self::Client { .. } => true, -7216 Self::Server { path_validated, .. } => path_validated, -7217 } -7218 } -7219 -7220 pub(crate) fn side(&self) -> Side { -7221 match *self { -7222 Self::Client { .. } => Side::Client, -7223 Self::Server { .. } => Side::Server, -7224 } -7225 } -7226} -7227 -7228/// Reasons why a connection might be lost -7229#[derive(Debug, Error, Clone, PartialEq, Eq)] -7230pub enum ConnectionError { -7231 /// The peer doesn't implement any supported version -7232 #[error("peer doesn't implement any supported version")] -7233 VersionMismatch, -7234 /// The peer violated the QUIC specification as understood by this implementation -7235 #[error(transparent)] -7236 TransportError(#[from] TransportError), -7237 /// The peer's QUIC stack aborted the connection automatically -7238 #[error("aborted by peer: {0}")] -7239 ConnectionClosed(frame::ConnectionClose), -7240 /// The peer closed the connection -7241 #[error("closed by peer: {0}")] -7242 ApplicationClosed(frame::ApplicationClose), -7243 /// The peer is unable to continue processing this connection, usually due to having restarted -7244 #[error("reset by peer")] -7245 Reset, -7246 /// Communication with the peer has lapsed for longer than the negotiated idle timeout -7247 /// -7248 /// If neither side is sending keep-alives, a connection will time out after a long enough idle -7249 /// period even if the peer is still reachable. See also [`TransportConfig::max_idle_timeout()`] -7250 /// and [`TransportConfig::keep_alive_interval()`]. -7251 #[error("timed out")] -7252 TimedOut, -7253 /// The local application closed the connection -7254 #[error("closed")] -7255 LocallyClosed, -7256 /// The connection could not be created because not enough of the CID space is available -7257 /// -7258 /// Try using longer connection IDs. -7259 #[error("CIDs exhausted")] -7260 CidsExhausted, -7261} -7262 -7263impl From<Close> for ConnectionError { -7264 fn from(x: Close) -> Self { -7265 match x { -7266 Close::Connection(reason) => Self::ConnectionClosed(reason), -7267 Close::Application(reason) => Self::ApplicationClosed(reason), -7268 } -7269 } -7270} -7271 -7272// For compatibility with API consumers -7273impl From<ConnectionError> for io::Error { -7274 fn from(x: ConnectionError) -> Self { -7275 use ConnectionError::*; -7276 let kind = match x { -7277 TimedOut => io::ErrorKind::TimedOut, -7278 Reset => io::ErrorKind::ConnectionReset, -7279 ApplicationClosed(_) | ConnectionClosed(_) => io::ErrorKind::ConnectionAborted, -7280 TransportError(_) | VersionMismatch | LocallyClosed | CidsExhausted => { -7281 io::ErrorKind::Other -7282 } -7283 }; -7284 Self::new(kind, x) -7285 } -7286} -7287 -7288/// Errors that might trigger a path being closed -7289// TODO(@divma): maybe needs to be reworked based on what we want to do with the public API -7290#[derive(Debug, Error, PartialEq, Eq, Clone, Copy)] -7291pub enum PathError { -7292 /// The extension was not negotiated with the peer -7293 #[error("multipath extension not negotiated")] -7294 MultipathNotNegotiated, -7295 /// Paths can only be opened client-side -7296 #[error("the server side may not open a path")] -7297 ServerSideNotAllowed, -7298 /// Current limits do not allow us to open more paths -7299 #[error("maximum number of concurrent paths reached")] -7300 MaxPathIdReached, -7301 /// No remote CIDs available to open a new path -7302 #[error("remoted CIDs exhausted")] -7303 RemoteCidsExhausted, -7304 /// Path could not be validated and will be abandoned -7305 #[error("path validation failed")] -7306 ValidationFailed, -7307 /// The remote address for the path is not supported by the endpoint -7308 #[error("invalid remote address")] -7309 InvalidRemoteAddress(SocketAddr), +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 -7312/// Errors triggered when abandoning a path -7313#[derive(Debug, Error, Clone, Eq, PartialEq)] -7314pub enum ClosePathError { -7315 /// Multipath is not negotiated -7316 #[error("Multipath extension not negotiated")] -7317 MultipathNotNegotiated, -7318 /// The path is already closed or was never opened -7319 #[error("closed path")] -7320 ClosedPath, -7321 /// Cannot close the last remaining open path via the local API. -7322 /// -7323 /// Use [`Connection::close`] to end the connection instead. -7324 #[error("last open path")] -7325 LastOpenPath, -7326} -7327 -7328/// Error when the multipath extension was not negotiated, but attempted to be used. -7329#[derive(Debug, Error, Clone, Copy)] -7330#[error("Multipath extension not negotiated")] -7331pub struct MultipathNotNegotiated { -7332 _private: (), -7333} -7334 -7335/// Events of interest to the application -7336#[derive(Debug)] -7337pub enum Event { -7338 /// The connection's handshake data is ready -7339 HandshakeDataReady, -7340 /// The connection was successfully established -7341 Connected, -7342 /// The TLS handshake was confirmed -7343 HandshakeConfirmed, -7344 /// The connection was lost -7345 /// -7346 /// Emitted if the peer closes the connection or an error is encountered. -7347 ConnectionLost { -7348 /// Reason that the connection was closed -7349 reason: ConnectionError, -7350 }, -7351 /// Stream events -7352 Stream(StreamEvent), -7353 /// One or more application datagrams have been received -7354 DatagramReceived, -7355 /// One or more application datagrams have been sent after blocking -7356 DatagramsUnblocked, -7357 /// (Multi)Path events -7358 Path(PathEvent), -7359 /// n0's nat traversal events -7360 NatTraversal(n0_nat_traversal::Event), -7361} -7362 -7363impl From<PathEvent> for Event { -7364 fn from(source: PathEvent) -> Self { -7365 Self::Path(source) -7366 } -7367} -7368 -7369fn get_max_ack_delay(params: &TransportParameters) -> Duration { -7370 Duration::from_micros(params.max_ack_delay.0 * 1000) -7371} -7372 -7373/// Prevents overflow and improves behavior in extreme circumstances. -7374const MAX_BACKOFF_EXPONENT: u32 = 16; -7375 -7376/// The max interval between successive tail-loss probes. -7377/// -7378/// This is the "normal" value we use. -7379const MAX_PTO_INTERVAL: Duration = Duration::from_secs(2); -7380 -7381/// The idle time, below which we use the shorter [`MAX_PTO_FAST_INTERVAL`]. -7382const MIN_IDLE_FOR_FAST_PTO: Duration = Duration::from_secs(25); -7383 -7384/// The max interval between successive tail-loss probes with short idle times. -7385/// -7386/// If the path or connection idle time is less than [`MIN_IDLE_FOR_FAST_PTO`] then we use -7387/// this value to ensure we have plenty of retransmits before we reach the idle time. -7388const MAX_PTO_FAST_INTERVAL: Duration = Duration::from_secs(1); -7389 -7390/// The RTT threshold above which we cap the PTO interval to 1.5 * smoothed_rtt -7391/// -7392/// This is RTT time above which 1.5 * RTT > [`MAX_PTO_INTERVAL`], for these links we want -7393/// to extend the interval between tail-loss probes to not fill the entire pipe with them. -7394const SLOW_RTT_THRESHOLD: Duration = -7395 Duration::from_millis((MAX_PTO_INTERVAL.as_millis() as u64 * 2) / 3); -7396 -7397/// Minimal remaining size to allow packet coalescing, excluding cryptographic tag -7398/// -7399/// This must be at least as large as the header for a well-formed empty packet to be coalesced, -7400/// plus some space for frames. We only care about handshake headers because short header packets -7401/// necessarily have smaller headers, and initial packets are only ever the first packet in a -7402/// datagram (because we coalesce in ascending packet space order and the only reason to split a -7403/// packet is when packet space changes). -7404const MIN_PACKET_SPACE: usize = MAX_HANDSHAKE_OR_0RTT_HEADER_SIZE + 32; -7405 -7406/// Largest amount of space that could be occupied by a Handshake or 0-RTT packet's header -7407/// -7408/// Excludes packet-type-specific fields such as packet number or Initial token -7409// https://www.rfc-editor.org/rfc/rfc9000.html#name-0-rtt: flags + version + dcid len + dcid + -7410// scid len + scid + length + pn -7411const MAX_HANDSHAKE_OR_0RTT_HEADER_SIZE: usize = -7412 1 + 4 + 1 + MAX_CID_SIZE + 1 + MAX_CID_SIZE + VarInt::from_u32(u16::MAX as u32).size() + 4; -7413 -7414#[derive(Default)] -7415struct SentFrames { -7416 retransmits: ThinRetransmits, -7417 /// The packet number of the largest acknowledged packet for each path -7418 largest_acked: FxHashMap<PathId, u64>, -7419 stream_frames: StreamMetaVec, -7420 /// Whether the packet contains non-retransmittable frames (like datagrams) -7421 non_retransmits: bool, -7422 /// If the datagram containing these frames should be padded to the min MTU -7423 requires_padding: bool, -7424} -7425 -7426impl SentFrames { -7427 /// Returns whether the packet contains only ACKs -7428 fn is_ack_only(&self, streams: &StreamsState) -> bool { -7429 !self.largest_acked.is_empty() -7430 && !self.non_retransmits -7431 && self.stream_frames.is_empty() -7432 && self.retransmits.is_empty(streams) -7433 } -7434 -7435 /// Returns whether the packet contains any frames at all -7436 fn is_empty(&self, streams: &StreamsState) -> bool { -7437 !self.non_retransmits && self.stream_frames.is_empty() && self.retransmits.is_empty(streams) -7438 } -7439 -7440 fn retransmits_mut(&mut self) -> &mut Retransmits { -7441 self.retransmits.get_or_create() -7442 } -7443 -7444 fn record_sent_frame(&mut self, frame: frame::EncodableFrame<'_>) { -7445 use frame::EncodableFrame::*; -7446 match frame { -7447 PathAck(path_ack_encoder) => { -7448 if let Some(max) = path_ack_encoder.ranges.max() { -7449 self.largest_acked.insert(path_ack_encoder.path_id, max); -7450 } -7451 } -7452 Ack(ack_encoder) => { -7453 if let Some(max) = ack_encoder.ranges.max() { -7454 self.largest_acked.insert(PathId::ZERO, max); -7455 } -7456 } -7457 Close(_) => { /* non retransmittable, but after this we don't really care */ } -7458 PathResponse(_) => self.non_retransmits = true, -7459 HandshakeDone(_) => self.retransmits_mut().handshake_done = true, -7460 ReachOut(frame::ReachOut { round, ip, port }) => { -7461 let (recorded_round, reach_outs) = self -7462 .retransmits_mut() -7463 .reach_out -7464 .get_or_insert_with(|| (round, FxHashSet::default())); -7465 // Only record reach outs for the current round or a newer than the recorded one. -7466 if *recorded_round == round { -7467 // Same round, simply append. -7468 reach_outs.insert((ip, port)); -7469 } else if *recorded_round < round { -7470 // New round. -7471 *recorded_round = round; -7472 reach_outs.drain(); -7473 reach_outs.insert((ip, port)); -7474 } else { -7475 // ignore old reach out that was sent -7476 } -7477 } -7478 -7479 ObservedAddr(_) => self.retransmits_mut().observed_addr = true, -7480 Ping(_) => self.non_retransmits = true, -7481 ImmediateAck(_) => self.non_retransmits = true, -7482 AckFrequency(_) => self.retransmits_mut().ack_frequency = true, -7483 PathChallenge(_) => self.non_retransmits = true, -7484 Crypto(crypto) => self.retransmits_mut().crypto.push_back(crypto), -7485 PathAbandon(path_abandon) => { -7486 self.retransmits_mut() -7487 .path_abandon -7488 .entry(path_abandon.path_id) -7489 .or_insert(path_abandon.error_code); -7490 } -7491 PathStatusAvailable(frame::PathStatusAvailable { path_id, .. }) -7492 | PathStatusBackup(frame::PathStatusBackup { path_id, .. }) => { -7493 self.retransmits_mut().path_status.insert(path_id); -7494 } -7495 MaxPathId(_) => self.retransmits_mut().max_path_id = true, -7496 PathsBlocked(_) => self.retransmits_mut().paths_blocked = true, -7497 PathCidsBlocked(path_cids_blocked) => { -7498 self.retransmits_mut() -7499 .path_cids_blocked -7500 .insert(path_cids_blocked.path_id); -7501 } -7502 ResetStream(reset) => self -7503 .retransmits_mut() -7504 .reset_stream -7505 .push((reset.id, reset.error_code)), -7506 StopSending(stop_sending) => self.retransmits_mut().stop_sending.push(stop_sending), -7507 NewConnectionId(new_cid) => self.retransmits_mut().new_cids.push(new_cid.issued()), -7508 RetireConnectionId(retire_cid) => self -7509 .retransmits_mut() -7510 .retire_cids -7511 .push((retire_cid.path_id.unwrap_or_default(), retire_cid.sequence)), -7512 Datagram(_) => self.non_retransmits = true, -7513 NewToken(_) => {} -7514 AddAddress(add_address) => { -7515 self.retransmits_mut().add_address.insert(add_address); -7516 } -7517 RemoveAddress(remove_address) => { -7518 self.retransmits_mut().remove_address.insert(remove_address); -7519 } -7520 StreamMeta(stream_meta_encoder) => self.stream_frames.push(stream_meta_encoder.meta), -7521 MaxData(_) => self.retransmits_mut().max_data = true, -7522 MaxStreamData(max) => { -7523 self.retransmits_mut().max_stream_data.insert(max.id); -7524 } -7525 MaxStreams(max_streams) => { -7526 self.retransmits_mut().max_stream_id[max_streams.dir as usize] = true -7527 } -7528 } -7529 } -7530} -7531 -7532/// Compute the negotiated idle timeout based on local and remote max_idle_timeout transport parameters. -7533/// -7534/// 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.> -7535/// -7536/// 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.> -7537/// -7538/// Returns the negotiated idle timeout as a `Duration`, or `None` when both endpoints have opted out of idle timeout. -7539fn negotiate_max_idle_timeout(x: Option<VarInt>, y: Option<VarInt>) -> Option<Duration> { -7540 match (x, y) { -7541 (Some(VarInt(0)) | None, Some(VarInt(0)) | None) => None, -7542 (Some(VarInt(0)) | None, Some(y)) => Some(Duration::from_millis(y.0)), -7543 (Some(x), Some(VarInt(0)) | None) => Some(Duration::from_millis(x.0)), -7544 (Some(x), Some(y)) => Some(Duration::from_millis(cmp::min(x, y).0)), -7545 } -7546} -7547 -7548#[cfg(test)] -7549mod tests { -7550 use super::*; -7551 -7552 #[test] -7553 fn negotiate_max_idle_timeout_commutative() { -7554 let test_params = [ -7555 (None, None, None), -7556 (None, Some(VarInt(0)), None), -7557 (None, Some(VarInt(2)), Some(Duration::from_millis(2))), -7558 (Some(VarInt(0)), Some(VarInt(0)), None), -7559 ( -7560 Some(VarInt(2)), -7561 Some(VarInt(0)), -7562 Some(Duration::from_millis(2)), -7563 ), -7564 ( -7565 Some(VarInt(1)), -7566 Some(VarInt(4)), -7567 Some(Duration::from_millis(1)), -7568 ), -7569 ]; -7570 -7571 for (left, right, result) in test_params { -7572 assert_eq!(negotiate_max_idle_timeout(left, right), result); -7573 assert_eq!(negotiate_max_idle_timeout(right, left), result); -7574 } -7575 } -7576} \ No newline at end of file +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} +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) +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 } +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 diff --git a/pr/451/docs/src/noq_proto/connection/pacing.rs.html b/pr/451/docs/src/noq_proto/connection/pacing.rs.html index 3dd43abfb..f3253fa9b 100644 --- a/pr/451/docs/src/noq_proto/connection/pacing.rs.html +++ b/pr/451/docs/src/noq_proto/connection/pacing.rs.html @@ -18,308 +18,427 @@ 18 last_window: u64, 19 last_mtu: u16, 20 tokens: u64, -21 prev: Instant, -22} -23 -24impl Pacer { -25 /// Obtains a new [`Pacer`]. -26 pub(super) fn new(smoothed_rtt: Duration, window: u64, mtu: u16, now: Instant) -> Self { -27 let capacity = optimal_capacity(smoothed_rtt, window, mtu); -28 Self { -29 capacity, -30 last_window: window, -31 last_mtu: mtu, -32 tokens: capacity, -33 prev: now, -34 } -35 } -36 -37 /// Record that a packet has been transmitted. -38 pub(super) fn on_transmit(&mut self, packet_length: u16) { -39 self.tokens = self.tokens.saturating_sub(packet_length.into()) -40 } -41 -42 /// Return how long we need to wait before sending `bytes_to_send` -43 /// -44 /// If we can send a packet right away, this returns `None`. Otherwise, returns `Some(d)`, -45 /// where `d` is the time before this function should be called again. -46 /// -47 /// The 5/4 ratio used here comes from the suggestion that N = 1.25 in the draft IETF RFC for -48 /// QUIC. -49 pub(super) fn delay( -50 &mut self, -51 smoothed_rtt: Duration, -52 bytes_to_send: u64, -53 mtu: u16, -54 window: u64, -55 now: Instant, -56 ) -> Option<Instant> { -57 debug_assert_ne!( -58 window, 0, -59 "zero-sized congestion control window is nonsense" -60 ); -61 -62 if window != self.last_window || mtu != self.last_mtu { -63 self.capacity = optimal_capacity(smoothed_rtt, window, mtu); -64 -65 // Clamp the tokens -66 self.tokens = self.capacity.min(self.tokens); -67 self.last_window = window; -68 self.last_mtu = mtu; -69 } -70 -71 // if we can already send a packet, there is no need for delay -72 if self.tokens >= bytes_to_send { -73 return None; -74 } -75 -76 // we disable pacing for extremely large windows -77 if window > u64::from(u32::MAX) { -78 return None; -79 } -80 -81 let window = window as u32; -82 -83 let time_elapsed = now.checked_duration_since(self.prev).unwrap_or_else(|| { -84 warn!("received a timestamp early than a previous recorded time, ignoring"); -85 Default::default() -86 }); +21 max_bytes_per_second: Option<u64>, +22 prev: Instant, +23} +24 +25impl Pacer { +26 /// Obtains a new [`Pacer`]. +27 pub(super) fn new( +28 smoothed_rtt: Duration, +29 window: u64, +30 mtu: u16, +31 max_bytes_per_second: Option<u64>, +32 now: Instant, +33 ) -> Self { +34 let window = rate_limited_window(smoothed_rtt, window, max_bytes_per_second); +35 let capacity = optimal_capacity(smoothed_rtt, window, mtu); +36 Self { +37 capacity, +38 last_window: window, +39 last_mtu: mtu, +40 tokens: capacity, +41 max_bytes_per_second, +42 prev: now, +43 } +44 } +45 +46 /// Obtains the `max_bytes_per_second` used when this [`Pacer`] was constructed. +47 pub(crate) fn max_bytes_per_second(&self) -> Option<u64> { +48 self.max_bytes_per_second +49 } +50 +51 /// Record that a packet has been transmitted. +52 pub(super) fn on_transmit(&mut self, packet_length: u16) { +53 self.tokens = self.tokens.saturating_sub(packet_length.into()) +54 } +55 +56 /// Return how long we need to wait before sending `bytes_to_send`. +57 /// +58 /// If we can send a packet right away, this returns `None`. Otherwise, returns +59 /// `Some(d)`, where `d` is the duration after which this function should be called +60 /// again. +61 /// +62 /// The 5/4 ratio used here comes from the suggestion that N = 1.25 in the draft IETF +63 /// RFC for QUIC. +64 /// +65 /// `capacity` (bytes) and `pacing_rate` (bytes/s) are optional overrides supplied by +66 /// the congestion controller (e.g. BBRv3's `send_quantum` / `pacing_rate`). They take +67 /// precedence over the window-derived defaults, but are still subject to the static +68 /// `max_bytes_per_second` cap configured at construction time. +69 pub(super) fn delay( +70 &mut self, +71 smoothed_rtt: Duration, +72 bytes_to_send: u64, +73 mtu: u16, +74 window: u64, +75 now: Instant, +76 capacity: Option<u64>, +77 pacing_rate: Option<u64>, +78 ) -> Option<Duration> { +79 debug_assert_ne!( +80 window, 0, +81 "zero-sized congestion control window is nonsense" +82 ); +83 +84 let window = rate_limited_window(smoothed_rtt, window, self.max_bytes_per_second); +85 if window != self.last_window || mtu != self.last_mtu { +86 self.capacity = optimal_capacity(smoothed_rtt, window, mtu); 87 -88 if smoothed_rtt.as_nanos() == 0 { -89 return None; -90 } -91 -92 let elapsed_rtts = time_elapsed.as_secs_f64() / smoothed_rtt.as_secs_f64(); -93 let new_tokens = (window as f64 * 1.25 * elapsed_rtts).round() as u64; -94 self.tokens = self.tokens.saturating_add(new_tokens).min(self.capacity); -95 -96 // In the unlikely event that we're getting polled faster than tokens are generated, ensure -97 // that `elapsed_rtts` can grow until we make progress. -98 if new_tokens > 0 { -99 self.prev = now; -100 } -101 -102 // if we can already send a packet, there is no need for delay -103 if self.tokens >= bytes_to_send { -104 return None; -105 } -106 -107 let unscaled_delay = smoothed_rtt -108 .checked_mul((bytes_to_send.max(self.capacity) - self.tokens) as _) -109 .unwrap_or(Duration::MAX) -110 / window; -111 -112 // divisions come before multiplications to prevent overflow -113 // this is the time at which the pacing window becomes empty -114 Some(now + (unscaled_delay / 5) * 4) -115 } -116} -117 -118/// Calculates a pacer capacity for a certain window and RTT -119/// -120/// The goal is to emit a burst (of size `capacity`) in timer intervals -121/// which compromise between -122/// - ideally distributing datagrams over time -123/// - constantly waking up the connection to produce additional datagrams -124/// -125/// Too short burst intervals means we will never meet them since the timer -126/// accuracy in user-space is not high enough. If we miss the interval by more -127/// than 25%, we will lose that part of the congestion window since no additional -128/// tokens for the extra-elapsed time can be stored. -129/// -130/// Too long burst intervals make pacing less effective. -131fn optimal_capacity(smoothed_rtt: Duration, window: u64, mtu: u16) -> u64 { -132 let rtt = smoothed_rtt.as_nanos().max(1); -133 let mtu = u64::from(mtu); -134 -135 let target_capacity = ((window as u128 * TARGET_BURST_INTERVAL.as_nanos()) / rtt) as u64; -136 // Never restrict capacity below one MTU. -137 let max_capacity = Ord::max( -138 ((window as u128 * MAX_BURST_INTERVAL.as_nanos()) / rtt) as u64, -139 mtu, -140 ); -141 -142 // Batch the greater of `TARGET_BURST_INTERVAL` or `MIN_BURST_SIZE` worth of traffic at a -143 // time. To avoid inducing excessive latency, limit that result to at most `MAX_BURST_INTERVAL` -144 // worth of traffic. -145 Ord::min( -146 max_capacity, -147 target_capacity.clamp(MIN_BURST_SIZE * mtu, MAX_BURST_SIZE * mtu), -148 ) -149} -150 -151/// Period of traffic to batch together on a reasonably fast connection -152const TARGET_BURST_INTERVAL: Duration = Duration::from_millis(2); -153 -154/// Maximum period of traffic to batch together on a slow connection -155/// -156/// Takes precedence over [`MIN_BURST_SIZE`]. -157const MAX_BURST_INTERVAL: Duration = Duration::from_millis(10); -158 -159/// Minimum number of datagrams to batch together, so long as we won't have to wait for more than -160/// [`MAX_BURST_INTERVAL`] -161const MIN_BURST_SIZE: u64 = 10; -162 -163/// Creating 256 packets took 1ms in a benchmark, so larger bursts don't make sense. -164const MAX_BURST_SIZE: u64 = 256; -165 -166#[cfg(test)] -167mod tests { -168 use super::*; -169 -170 #[test] -171 fn does_not_panic_on_bad_instant() { -172 let old_instant = Instant::now(); -173 let new_instant = old_instant + Duration::from_micros(15); -174 let rtt = Duration::from_micros(400); -175 -176 assert!( -177 Pacer::new(rtt, 30000, 1500, new_instant) -178 .delay(Duration::from_micros(0), 0, 1500, 1, old_instant) -179 .is_none() -180 ); -181 assert!( -182 Pacer::new(rtt, 30000, 1500, new_instant) -183 .delay(Duration::from_micros(0), 1600, 1500, 1, old_instant) -184 .is_none() -185 ); -186 assert!( -187 Pacer::new(rtt, 30000, 1500, new_instant) -188 .delay(Duration::from_micros(0), 1500, 1500, 3000, old_instant) -189 .is_none() -190 ); -191 } -192 -193 #[test] -194 fn derives_initial_capacity() { -195 let window = 2_000_000; -196 let mtu = 1500; -197 let rtt = Duration::from_millis(50); -198 let now = Instant::now(); -199 -200 let pacer = Pacer::new(rtt, window, mtu, now); -201 assert_eq!( -202 pacer.capacity, -203 (window as u128 * TARGET_BURST_INTERVAL.as_nanos() / rtt.as_nanos()) as u64 -204 ); -205 assert_eq!(pacer.tokens, pacer.capacity); +88 // Clamp the tokens +89 self.tokens = self.capacity.min(self.tokens); +90 self.last_window = window; +91 self.last_mtu = mtu; +92 } +93 +94 if let Some(capacity) = capacity { +95 self.capacity = capacity; +96 self.tokens = self.capacity.min(self.tokens); +97 } +98 +99 if let Some(pacing_rate) = pacing_rate +100 && bytes_to_send > self.capacity +101 { +102 // Pace at the controller-supplied rate; cap the static rate-limit through the +103 // `rate_limited_window` window above. +104 let capped_bytes_to_send = bytes_to_send.max(self.capacity); +105 let delay = Duration::from_secs_f64(capped_bytes_to_send as f64 / pacing_rate as f64); +106 return Some(delay); +107 } +108 +109 // if we can already send a packet, there is no need for delay +110 if self.tokens >= bytes_to_send { +111 return None; +112 } +113 +114 // we disable pacing for extremely large windows +115 if window > u64::from(u32::MAX) { +116 return None; +117 } +118 +119 let window = window as u32; +120 +121 let time_elapsed = now.checked_duration_since(self.prev).unwrap_or_else(|| { +122 warn!("received a timestamp early than a previous recorded time, ignoring"); +123 Default::default() +124 }); +125 +126 if smoothed_rtt.as_nanos() == 0 { +127 return None; +128 } +129 +130 let elapsed_rtts = time_elapsed.as_secs_f64() / smoothed_rtt.as_secs_f64(); +131 let new_tokens = (window as f64 * 1.25 * elapsed_rtts).round() as u64; +132 self.tokens = self.tokens.saturating_add(new_tokens).min(self.capacity); +133 +134 // In the unlikely event that we're getting polled faster than tokens are generated, ensure +135 // that `elapsed_rtts` can grow until we make progress. +136 if new_tokens > 0 { +137 self.prev = now; +138 } +139 +140 // if we can already send a packet, there is no need for delay +141 if self.tokens >= bytes_to_send { +142 return None; +143 } +144 +145 let unscaled_delay = smoothed_rtt +146 .checked_mul((bytes_to_send.max(self.capacity) - self.tokens) as _) +147 .unwrap_or(Duration::MAX) +148 / window; +149 +150 // divisions come before multiplications to prevent overflow +151 // this is the time at which the pacing window becomes empty +152 Some((unscaled_delay / 5) * 4) +153 } +154} +155 +156/// Calculates a pacer capacity for a certain window and RTT +157/// +158/// The goal is to emit a burst (of size `capacity`) in timer intervals +159/// which compromise between +160/// - ideally distributing datagrams over time +161/// - constantly waking up the connection to produce additional datagrams +162/// +163/// Too short burst intervals means we will never meet them since the timer +164/// accuracy in user-space is not high enough. If we miss the interval by more +165/// than 25%, we will lose that part of the congestion window since no additional +166/// tokens for the extra-elapsed time can be stored. +167/// +168/// Too long burst intervals make pacing less effective. +169fn optimal_capacity(smoothed_rtt: Duration, window: u64, mtu: u16) -> u64 { +170 let rtt = smoothed_rtt.as_nanos().max(1); +171 let mtu = u64::from(mtu); +172 +173 let target_capacity = ((window as u128 * TARGET_BURST_INTERVAL.as_nanos()) / rtt) as u64; +174 // Never restrict capacity below one MTU. +175 let max_capacity = Ord::max( +176 ((window as u128 * MAX_BURST_INTERVAL.as_nanos()) / rtt) as u64, +177 mtu, +178 ); +179 +180 // Batch the greater of `TARGET_BURST_INTERVAL` or `MIN_BURST_SIZE` worth of traffic at a +181 // time. To avoid inducing excessive latency, limit that result to at most `MAX_BURST_INTERVAL` +182 // worth of traffic. +183 Ord::min( +184 max_capacity, +185 target_capacity.clamp(MIN_BURST_SIZE * mtu, MAX_BURST_SIZE * mtu), +186 ) +187} +188 +189/// Clamps the window to limit the sending rate to `max_bytes_per_second`. +190/// +191/// If `max_bytes_per_second` is `None`, the original window is returned. +192fn rate_limited_window( +193 smoothed_rtt: Duration, +194 window: u64, +195 max_bytes_per_second: Option<u64>, +196) -> u64 { +197 let Some(max_bytes_per_second) = max_bytes_per_second else { +198 return window; +199 }; +200 +201 let rate_window = max_bytes_per_second as f64 * smoothed_rtt.as_secs_f64(); +202 +203 // the pacer refills tokens at x1.25 speed, so we shrink the window to cancel out the speedup +204 // (otherwise the actual sending rate could be higher than `max_bytes_per_second`) +205 let adjusted_rate_window = (rate_window / 1.25).round(); 206 -207 let pacer = Pacer::new(Duration::from_millis(0), window, mtu, now); -208 assert_eq!(pacer.capacity, MAX_BURST_SIZE * mtu as u64); -209 assert_eq!(pacer.tokens, pacer.capacity); -210 -211 let pacer = Pacer::new(rtt, 1, mtu, now); -212 assert_eq!(pacer.capacity, mtu as u64); -213 assert_eq!(pacer.tokens, pacer.capacity); -214 } -215 -216 #[test] -217 fn adjusts_capacity() { -218 let window = 2_000_000; -219 let mtu = 1500; -220 let rtt = Duration::from_millis(50); -221 let now = Instant::now(); -222 -223 let mut pacer = Pacer::new(rtt, window, mtu, now); -224 assert_eq!( -225 pacer.capacity, -226 (window as u128 * TARGET_BURST_INTERVAL.as_nanos() / rtt.as_nanos()) as u64 -227 ); -228 assert_eq!(pacer.tokens, pacer.capacity); -229 let initial_tokens = pacer.tokens; -230 -231 pacer.delay(rtt, mtu as u64, mtu, window * 2, now); -232 assert_eq!( -233 pacer.capacity, -234 (2 * window as u128 * TARGET_BURST_INTERVAL.as_nanos() / rtt.as_nanos()) as u64 -235 ); -236 assert_eq!(pacer.tokens, initial_tokens); -237 -238 pacer.delay(rtt, mtu as u64, mtu, window / 2, now); -239 assert_eq!( -240 pacer.capacity, -241 (window as u128 / 2 * TARGET_BURST_INTERVAL.as_nanos() / rtt.as_nanos()) as u64 -242 ); -243 assert_eq!(pacer.tokens, initial_tokens / 2); -244 -245 pacer.delay(rtt, mtu as u64, mtu * 2, window, now); -246 assert_eq!( -247 pacer.capacity, -248 (window as u128 * TARGET_BURST_INTERVAL.as_nanos() / rtt.as_nanos()) as u64 -249 ); -250 -251 pacer.delay(rtt, mtu as u64, 20_000, window, now); -252 assert_eq!(pacer.capacity, 20_000_u64 * MIN_BURST_SIZE); -253 } -254 -255 #[test] -256 fn computes_pause_correctly() { -257 let window = 2_000_000u64; -258 let mtu = 1000; -259 let rtt = Duration::from_millis(50); -260 let old_instant = Instant::now(); -261 -262 let mut pacer = Pacer::new(rtt, window, mtu, old_instant); -263 let packet_capacity = pacer.capacity / mtu as u64; -264 -265 for _ in 0..packet_capacity { -266 assert_eq!( -267 pacer.delay(rtt, mtu as u64, mtu, window, old_instant), -268 None, -269 "When capacity is available packets should be sent immediately" -270 ); -271 -272 pacer.on_transmit(mtu); -273 } -274 -275 let pace_duration = Duration::from_nanos((TARGET_BURST_INTERVAL.as_nanos() * 4 / 5) as u64); -276 -277 let actual_delay = pacer -278 .delay(rtt, mtu as u64, mtu, window, old_instant) -279 .expect("Send must be delayed") -280 .duration_since(old_instant); -281 -282 let diff = actual_delay.abs_diff(pace_duration); -283 -284 // Allow up to 2ns difference due to rounding -285 assert!( -286 diff < Duration::from_nanos(2), -287 "expected ≈ {pace_duration:?}, got {actual_delay:?} (diff {diff:?})" -288 ); -289 // Refill half of the tokens -290 assert_eq!( -291 pacer.delay( -292 rtt, -293 mtu as u64, -294 mtu, -295 window, -296 old_instant + pace_duration / 2 -297 ), -298 None -299 ); -300 assert_eq!(pacer.tokens, pacer.capacity / 2); -301 -302 for _ in 0..packet_capacity / 2 { -303 assert_eq!( -304 pacer.delay(rtt, mtu as u64, mtu, window, old_instant), -305 None, -306 "When capacity is available packets should be sent immediately" -307 ); -308 -309 pacer.on_transmit(mtu); -310 } -311 -312 // Refill all capacity by waiting more than the expected duration -313 assert_eq!( -314 pacer.delay( -315 rtt, -316 mtu as u64, -317 mtu, -318 window, -319 old_instant + pace_duration * 3 / 2 -320 ), -321 None -322 ); -323 assert_eq!(pacer.tokens, pacer.capacity); -324 } -325} \ No newline at end of file +207 Ord::min(window, Ord::max(adjusted_rate_window as u64, 1)) +208} +209 +210/// Period of traffic to batch together on a reasonably fast connection +211const TARGET_BURST_INTERVAL: Duration = Duration::from_millis(2); +212 +213/// Maximum period of traffic to batch together on a slow connection +214/// +215/// Takes precedence over [`MIN_BURST_SIZE`]. +216const MAX_BURST_INTERVAL: Duration = Duration::from_millis(10); +217 +218/// Minimum number of datagrams to batch together, so long as we won't have to wait for more than +219/// [`MAX_BURST_INTERVAL`] +220const MIN_BURST_SIZE: u64 = 10; +221 +222/// Creating 256 packets took 1ms in a benchmark, so larger bursts don't make sense. +223const MAX_BURST_SIZE: u64 = 256; +224 +225#[cfg(test)] +226mod tests { +227 use super::*; +228 +229 #[test] +230 fn does_not_panic_on_bad_instant() { +231 let old_instant = Instant::now(); +232 let new_instant = old_instant + Duration::from_micros(15); +233 let rtt = Duration::from_micros(400); +234 +235 assert!( +236 Pacer::new(rtt, 30000, 1500, None, new_instant) +237 .delay( +238 Duration::from_micros(0), +239 0, +240 1500, +241 1, +242 old_instant, +243 None, +244 None +245 ) +246 .is_none() +247 ); +248 assert!( +249 Pacer::new(rtt, 30000, 1500, None, new_instant) +250 .delay( +251 Duration::from_micros(0), +252 1600, +253 1500, +254 1, +255 old_instant, +256 None, +257 None +258 ) +259 .is_none() +260 ); +261 assert!( +262 Pacer::new(rtt, 30000, 1500, None, new_instant) +263 .delay( +264 Duration::from_micros(0), +265 1500, +266 1500, +267 3000, +268 old_instant, +269 None, +270 None +271 ) +272 .is_none() +273 ); +274 } +275 +276 #[test] +277 fn derives_initial_capacity() { +278 let window = 2_000_000; +279 let mtu = 1500; +280 let rtt = Duration::from_millis(50); +281 let now = Instant::now(); +282 +283 let pacer = Pacer::new(rtt, window, mtu, None, now); +284 assert_eq!( +285 pacer.capacity, +286 (window as u128 * TARGET_BURST_INTERVAL.as_nanos() / rtt.as_nanos()) as u64 +287 ); +288 assert_eq!(pacer.tokens, pacer.capacity); +289 +290 let pacer = Pacer::new(Duration::from_millis(0), window, mtu, None, now); +291 assert_eq!(pacer.capacity, MAX_BURST_SIZE * mtu as u64); +292 assert_eq!(pacer.tokens, pacer.capacity); +293 +294 let pacer = Pacer::new(rtt, 1, mtu, None, now); +295 assert_eq!(pacer.capacity, mtu as u64); +296 assert_eq!(pacer.tokens, pacer.capacity); +297 } +298 +299 #[test] +300 fn adjusts_capacity() { +301 let window = 2_000_000; +302 let mtu = 1500; +303 let rtt = Duration::from_millis(50); +304 let now = Instant::now(); +305 +306 let mut pacer = Pacer::new(rtt, window, mtu, None, now); +307 assert_eq!( +308 pacer.capacity, +309 (window as u128 * TARGET_BURST_INTERVAL.as_nanos() / rtt.as_nanos()) as u64 +310 ); +311 assert_eq!(pacer.tokens, pacer.capacity); +312 let initial_tokens = pacer.tokens; +313 +314 pacer.delay(rtt, mtu as u64, mtu, window * 2, now, None, None); +315 assert_eq!( +316 pacer.capacity, +317 (2 * window as u128 * TARGET_BURST_INTERVAL.as_nanos() / rtt.as_nanos()) as u64 +318 ); +319 assert_eq!(pacer.tokens, initial_tokens); +320 +321 pacer.delay(rtt, mtu as u64, mtu, window / 2, now, None, None); +322 assert_eq!( +323 pacer.capacity, +324 (window as u128 / 2 * TARGET_BURST_INTERVAL.as_nanos() / rtt.as_nanos()) as u64 +325 ); +326 assert_eq!(pacer.tokens, initial_tokens / 2); +327 +328 pacer.delay(rtt, mtu as u64, mtu * 2, window, now, None, None); +329 assert_eq!( +330 pacer.capacity, +331 (window as u128 * TARGET_BURST_INTERVAL.as_nanos() / rtt.as_nanos()) as u64 +332 ); +333 +334 pacer.delay(rtt, mtu as u64, 20_000, window, now, None, None); +335 assert_eq!(pacer.capacity, 20_000_u64 * MIN_BURST_SIZE); +336 } +337 +338 #[test] +339 fn computes_pause_correctly() { +340 let window = 2_000_000u64; +341 let mtu = 1000; +342 let rtt = Duration::from_millis(50); +343 let old_instant = Instant::now(); +344 +345 let mut pacer = Pacer::new(rtt, window, mtu, None, old_instant); +346 let packet_capacity = pacer.capacity / mtu as u64; +347 +348 for _ in 0..packet_capacity { +349 assert_eq!( +350 pacer.delay(rtt, mtu as u64, mtu, window, old_instant, None, None), +351 None, +352 "When capacity is available packets should be sent immediately" +353 ); +354 +355 pacer.on_transmit(mtu); +356 } +357 +358 let pace_duration = Duration::from_nanos((TARGET_BURST_INTERVAL.as_nanos() * 4 / 5) as u64); +359 +360 let actual_delay = pacer +361 .delay(rtt, mtu as u64, mtu, window, old_instant, None, None) +362 .expect("Send must be delayed"); +363 +364 let diff = actual_delay.abs_diff(pace_duration); +365 +366 // Allow up to 2ns difference due to rounding +367 assert!( +368 diff < Duration::from_nanos(2), +369 "expected ≈ {pace_duration:?}, got {actual_delay:?} (diff {diff:?})" +370 ); +371 // Refill half of the tokens +372 assert_eq!( +373 pacer.delay( +374 rtt, +375 mtu as u64, +376 mtu, +377 window, +378 old_instant + pace_duration / 2, +379 None, +380 None, +381 ), +382 None +383 ); +384 assert_eq!(pacer.tokens, pacer.capacity / 2); +385 +386 for _ in 0..packet_capacity / 2 { +387 assert_eq!( +388 pacer.delay(rtt, mtu as u64, mtu, window, old_instant, None, None), +389 None, +390 "When capacity is available packets should be sent immediately" +391 ); +392 +393 pacer.on_transmit(mtu); +394 } +395 +396 // Refill all capacity by waiting more than the expected duration +397 assert_eq!( +398 pacer.delay( +399 rtt, +400 mtu as u64, +401 mtu, +402 window, +403 old_instant + pace_duration * 3 / 2, +404 None, +405 None, +406 ), +407 None +408 ); +409 assert_eq!(pacer.tokens, pacer.capacity); +410 } +411 +412 #[test] +413 fn computes_pause_correctly_for_rate_limited() { +414 let window = 2_000_000u64; +415 let mtu = 1000; +416 let rtt = Duration::from_millis(50); +417 let old_instant = Instant::now(); +418 +419 let mut pacer = Pacer::new(rtt, window, mtu, Some(2_000), old_instant); +420 assert_eq!( +421 pacer.delay(rtt, 1_000, mtu, window, old_instant, None, None), +422 None, +423 "When capacity is available packets should be sent immediately" +424 ); +425 pacer.on_transmit(mtu); +426 +427 let actual_delay = pacer +428 .delay(rtt, 1_000, mtu, window, old_instant, None, None) +429 .expect("Send must be delayed"); +430 +431 let expected_delay = Duration::from_millis(500); +432 let diff = actual_delay.abs_diff(expected_delay); +433 +434 // Allow up to 2ns difference due to rounding +435 assert!( +436 diff < Duration::from_nanos(2), +437 "expected ≈ {expected_delay:?}, got {actual_delay:?} (diff {diff:?})" +438 ); +439 +440 // Should be able to send after a while +441 let now = old_instant + expected_delay / 2; +442 assert_eq!(pacer.delay(rtt, 500, mtu, window, now, None, None), None); +443 } +444} \ 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 d0f5bfba0..9247a8803 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 @@ -49,198 +49,198 @@ 49 path_id: PathId, 50 dst_cid: ConnectionId, 51 buffer: &'a mut TransmitBuf<'b>, -52 ack_eliciting: bool, -53 conn: &mut Connection, -54 ) -> Option<Self> -55 where -56 'b: 'a, -57 { -58 let mut qlog = QlogSentPacket::default(); -59 -60 let version = conn.version; -61 // Initiate key update if we're approaching the confidentiality limit -62 let (_header_crypto, _packet_crypto, level) = conn -63 .crypto_state -64 .encryption_keys(space_id.kind(), conn.side.side()) -65 .expect("tried to build packet without encryption keys"); -66 -67 let remaining_packet_budget = conn -68 .crypto_state -69 .remaining_packet_budget(level) -70 .expect("keys are installed"); -71 -72 if level == EncryptionLevel::OneRtt { -73 // 1-RTT keys can be rotated, so exhaustion just triggers a key update -74 if remaining_packet_budget == 0 { -75 debug!("routine key update due to phase exhaustion"); -76 conn.force_key_update(); -77 } -78 } else { -79 // Other encryption levels have a fixed key budget -80 if remaining_packet_budget == 0 { -81 let close = TransportError::AEAD_LIMIT_REACHED("confidentiality limit reached"); -82 conn.kill(close.into()); -83 return None; -84 } else if remaining_packet_budget == 1 { -85 let close = TransportError::AEAD_LIMIT_REACHED("confidentiality limit reached"); -86 conn.close_inner(now, close.into()); -87 } -88 } -89 -90 let (header_crypto, packet_crypto, level) = conn -91 .crypto_state -92 .encryption_keys(space_id.kind(), conn.side.side()) -93 .expect("verified"); -94 -95 let space = &mut conn.spaces[space_id]; -96 let packet_number = space.for_path(path_id).get_tx_number(&mut conn.rng); -97 let span = trace_span!("send", space = ?space_id, pn = packet_number, %path_id).entered(); -98 -99 let number = PacketNumber::new( -100 packet_number, -101 space.for_path(path_id).largest_acked_packet.unwrap_or(0), -102 ); -103 let header = match level { -104 EncryptionLevel::OneRtt => Header::Short { -105 dst_cid, -106 number, -107 spin: if conn.spin_enabled { -108 conn.spin -109 } else { -110 conn.rng.random() -111 }, -112 key_phase: conn.crypto_state.key_phase, -113 }, -114 EncryptionLevel::ZeroRtt => Header::Long { -115 ty: LongType::ZeroRtt, -116 src_cid: conn.handshake_cid, -117 dst_cid, -118 number, -119 version, -120 }, -121 EncryptionLevel::Handshake => Header::Long { -122 ty: LongType::Handshake, -123 src_cid: conn.handshake_cid, -124 dst_cid, -125 number, -126 version, -127 }, -128 EncryptionLevel::Initial => Header::Initial(InitialHeader { -129 src_cid: conn.handshake_cid, -130 dst_cid, -131 token: match &conn.side { -132 ConnectionSide::Client { token, .. } => token.clone(), -133 ConnectionSide::Server { .. } => Bytes::new(), -134 }, -135 number, -136 version, -137 }), -138 }; -139 -140 let partial_encode = header.encode(buffer); -141 if conn.peer_params.grease_quic_bit && conn.rng.random() { -142 buffer.as_mut_slice()[partial_encode.start] ^= FIXED_BIT; -143 } -144 -145 let (sample_size, tag_len) = (header_crypto.sample_size(), packet_crypto.tag_len()); -146 -147 // Each packet must be large enough for header protection sampling, i.e. the combined -148 // lengths of the encoded packet number and protected payload must be at least 4 bytes -149 // longer than the sample required for header protection. Further, each packet should be at -150 // least tag_len + 6 bytes larger than the destination CID on incoming packets so that the -151 // peer may send stateless resets that are indistinguishable from regular traffic. -152 -153 // pn_len + payload_len + tag_len >= sample_size + 4 -154 // payload_len >= sample_size + 4 - pn_len - tag_len -155 let min_size = Ord::max( -156 buffer.len() + (sample_size + 4).saturating_sub(number.len() + tag_len), -157 partial_encode.start + dst_cid.len() + 6, -158 ); -159 let max_size = buffer.datagram_max_offset() - tag_len; -160 debug_assert!(max_size >= min_size); -161 -162 qlog.header(&header, Some(packet_number), level, path_id); -163 -164 Some(Self { -165 buf: buffer, -166 space: space_id, -167 path: path_id, -168 partial_encode, -169 packet_number, -170 can_coalesce: header.can_coalesce(), -171 min_size, -172 tag_len, -173 level, -174 ack_eliciting, -175 qlog, -176 sent_frames: SentFrames::default(), -177 _span: span, -178 }) -179 } -180 -181 #[cfg(test)] -182 pub(crate) fn simple_data_buf(buf: &'a mut TransmitBuf<'b>) -> Self { -183 Self { -184 buf, -185 space: SpaceId::Data, -186 path: PathId::ZERO, -187 partial_encode: PartialEncode::no_header(), -188 ack_eliciting: true, -189 packet_number: 0, -190 can_coalesce: true, -191 min_size: 0, -192 tag_len: 0, -193 _span: trace_span!("test").entered(), -194 qlog: QlogSentPacket::default(), -195 sent_frames: SentFrames::default(), -196 level: EncryptionLevel::Initial, -197 } -198 } -199 -200 /// Append the minimum amount of padding to the packet such that, after encryption, the -201 /// enclosing datagram will occupy at least `min_size` bytes -202 pub(super) fn pad_to(&mut self, min_size: u16) { -203 // The datagram might already have a larger minimum size than the caller is requesting, if -204 // e.g. we're coalescing packets and have populated more than `min_size` bytes with packets -205 // already. -206 self.min_size = Ord::max( -207 self.min_size, -208 self.buf.datagram_start_offset() + (min_size as usize) - self.tag_len, -209 ); -210 } -211 -212 /// Writes a frame into the underlying buffer. -213 /// -214 /// It will also: -215 /// - Track the frame so that it's registered with the path once [`Self::finish_and_track`] is -216 /// called. -217 /// - Register the sent frame with the given [`FrameStats`]. -218 /// - If the qlog feature is enabled, register the frame. -219 /// - Log the frame. -220 pub(super) fn write_frame<'c>( -221 &mut self, -222 frame: impl Into<EncodableFrame<'c>>, -223 stats: &mut FrameStats, -224 ) { -225 self.write_frame_with_log_msg(frame, stats, None); -226 } -227 -228 /// Writes a frame into the underlying buffer. -229 /// -230 /// It will also: -231 /// - Track the frame so that it's registered with the path once [`Self::finish_and_track`] is -232 /// called. -233 /// - Register the sent frame with the given [`FrameStats`]. -234 /// - If the qlog feature is enabled, register the frame. -235 /// - Log the frame. If a `msg` is given, this will be added to the log. -236 pub(super) fn write_frame_with_log_msg<'c>( -237 &mut self, -238 frame: impl Into<EncodableFrame<'c>>, -239 stats: &mut FrameStats, -240 msg: Option<&'static str>, -241 ) { -242 let frame = frame.into(); -243 frame.encode(&mut self.frame_space_mut()); +52 conn: &mut Connection, +53 ) -> Option<Self> +54 where +55 'b: 'a, +56 { +57 let mut qlog = QlogSentPacket::default(); +58 +59 let version = conn.version; +60 // Initiate key update if we're approaching the confidentiality limit +61 let (_header_crypto, _packet_crypto, level) = conn +62 .crypto_state +63 .encryption_keys(space_id.kind(), conn.side.side()) +64 .expect("tried to build packet without encryption keys"); +65 +66 let remaining_packet_budget = conn +67 .crypto_state +68 .remaining_packet_budget(level) +69 .expect("keys are installed"); +70 +71 if level == EncryptionLevel::OneRtt { +72 // 1-RTT keys can be rotated, so exhaustion just triggers a key update +73 if remaining_packet_budget == 0 { +74 debug!("routine key update due to phase exhaustion"); +75 conn.force_key_update(); +76 } +77 } else { +78 // Other encryption levels have a fixed key budget +79 if remaining_packet_budget == 0 { +80 let close = TransportError::AEAD_LIMIT_REACHED("confidentiality limit reached"); +81 conn.kill(close.into()); +82 return None; +83 } else if remaining_packet_budget == 1 { +84 let close = TransportError::AEAD_LIMIT_REACHED("confidentiality limit reached"); +85 conn.close_inner(now, close.into()); +86 } +87 } +88 +89 let (header_crypto, packet_crypto, level) = conn +90 .crypto_state +91 .encryption_keys(space_id.kind(), conn.side.side()) +92 .expect("verified"); +93 +94 let space = &mut conn.spaces[space_id]; +95 let packet_number = space.for_path(path_id).get_tx_number(&mut conn.rng); +96 let span = trace_span!("send", space = ?space_id, pn = packet_number, %path_id).entered(); +97 +98 let number = PacketNumber::new( +99 packet_number, +100 space.for_path(path_id).largest_acked_packet_pn.unwrap_or(0), +101 ); +102 let header = match level { +103 EncryptionLevel::OneRtt => Header::Short { +104 dst_cid, +105 number, +106 spin: if conn.spin_enabled { +107 conn.spin +108 } else { +109 conn.rng.random() +110 }, +111 key_phase: conn.crypto_state.key_phase, +112 }, +113 EncryptionLevel::ZeroRtt => Header::Long { +114 ty: LongType::ZeroRtt, +115 src_cid: conn.handshake_cid, +116 dst_cid, +117 number, +118 version, +119 }, +120 EncryptionLevel::Handshake => Header::Long { +121 ty: LongType::Handshake, +122 src_cid: conn.handshake_cid, +123 dst_cid, +124 number, +125 version, +126 }, +127 EncryptionLevel::Initial => Header::Initial(InitialHeader { +128 src_cid: conn.handshake_cid, +129 dst_cid, +130 token: match &conn.side { +131 ConnectionSide::Client { token, .. } => token.clone(), +132 ConnectionSide::Server { .. } => Bytes::new(), +133 }, +134 number, +135 version, +136 }), +137 }; +138 +139 let partial_encode = header.encode(buffer); +140 if conn.peer_params.grease_quic_bit && conn.rng.random() { +141 buffer.as_mut_slice()[partial_encode.start] ^= FIXED_BIT; +142 } +143 +144 let (sample_size, tag_len) = (header_crypto.sample_size(), packet_crypto.tag_len()); +145 +146 // Each packet must be large enough for header protection sampling, i.e. the combined +147 // lengths of the encoded packet number and protected payload must be at least 4 bytes +148 // longer than the sample required for header protection. Further, each packet should be at +149 // least tag_len + 6 bytes larger than the destination CID on incoming packets so that the +150 // peer may send stateless resets that are indistinguishable from regular traffic. +151 +152 // pn_len + payload_len + tag_len >= sample_size + 4 +153 // payload_len >= sample_size + 4 - pn_len - tag_len +154 let min_size = Ord::max( +155 buffer.len() + (sample_size + 4).saturating_sub(number.len() + tag_len), +156 partial_encode.start + dst_cid.len() + 6, +157 ); +158 let max_size = buffer.datagram_max_offset() - tag_len; +159 debug_assert!(max_size >= min_size); +160 +161 qlog.header(&header, Some(packet_number), level, path_id); +162 +163 Some(Self { +164 buf: buffer, +165 space: space_id, +166 path: path_id, +167 partial_encode, +168 packet_number, +169 can_coalesce: header.can_coalesce(), +170 min_size, +171 tag_len, +172 level, +173 ack_eliciting: false, +174 qlog, +175 sent_frames: SentFrames::default(), +176 _span: span, +177 }) +178 } +179 +180 #[cfg(test)] +181 pub(crate) fn simple_data_buf(buf: &'a mut TransmitBuf<'b>) -> Self { +182 Self { +183 buf, +184 space: SpaceId::Data, +185 path: PathId::ZERO, +186 partial_encode: PartialEncode::no_header(), +187 ack_eliciting: true, +188 packet_number: 0, +189 can_coalesce: true, +190 min_size: 0, +191 tag_len: 0, +192 _span: trace_span!("test").entered(), +193 qlog: QlogSentPacket::default(), +194 sent_frames: SentFrames::default(), +195 level: EncryptionLevel::Initial, +196 } +197 } +198 +199 /// Append the minimum amount of padding to the packet such that, after encryption, the +200 /// enclosing datagram will occupy at least `min_size` bytes +201 pub(super) fn pad_to(&mut self, min_size: u16) { +202 // The datagram might already have a larger minimum size than the caller is requesting, if +203 // e.g. we're coalescing packets and have populated more than `min_size` bytes with packets +204 // already. +205 self.min_size = Ord::max( +206 self.min_size, +207 self.buf.datagram_start_offset() + (min_size as usize) - self.tag_len, +208 ); +209 } +210 +211 /// Writes a frame into the underlying buffer. +212 /// +213 /// It will also: +214 /// - Track the frame so that it's registered with the path once [`Self::finish_and_track`] is +215 /// called. +216 /// - Register the sent frame with the given [`FrameStats`]. +217 /// - If the qlog feature is enabled, register the frame. +218 /// - Log the frame. +219 pub(super) fn write_frame<'c>( +220 &mut self, +221 frame: impl Into<EncodableFrame<'c>>, +222 stats: &mut FrameStats, +223 ) { +224 self.write_frame_with_log_msg(frame, stats, None); +225 } +226 +227 /// Writes a frame into the underlying buffer. +228 /// +229 /// It will also: +230 /// - Track the frame so that it's registered with the path once [`Self::finish_and_track`] is +231 /// called. +232 /// - Register the sent frame with the given [`FrameStats`]. +233 /// - If the qlog feature is enabled, register the frame. +234 /// - Log the frame. If a `msg` is given, this will be added to the log. +235 pub(super) fn write_frame_with_log_msg<'c>( +236 &mut self, +237 frame: impl Into<EncodableFrame<'c>>, +238 stats: &mut FrameStats, +239 msg: Option<&'static str>, +240 ) { +241 let frame = frame.into(); +242 frame.encode(&mut self.frame_space_mut()); +243 self.ack_eliciting |= frame.is_ack_eliciting(); 244 stats.record(frame.get_type()); 245 self.qlog.record(&frame); 246 match msg { @@ -255,7 +255,7 @@ 255 /// The [`BufMut::remaining_mut`] call on the returned buffer indicates the amount of 256 /// space available to write QUIC frames into. 257 // In rust 1.82 we can use `-> impl BufMut + use<'_, 'a, 'b>` -258 pub(super) fn frame_space_mut(&mut self) -> bytes::buf::Limit<&mut TransmitBuf<'b>> { +258 fn frame_space_mut(&mut self) -> bytes::buf::Limit<&mut TransmitBuf<'b>> { 259 self.buf.limit(self.frame_space_remaining()) 260 } 261 @@ -307,10 +307,10 @@ 307 conn.spaces[space_id] 308 .for_path(path_id) 309 .time_of_last_ack_eliciting_packet = Some(now); -310 if conn.permit_idle_reset { +310 if conn.path_data(path_id).permit_idle_reset { 311 conn.reset_idle_timeout(now, space_id.kind(), path_id); 312 } -313 conn.permit_idle_reset = false; +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); diff --git a/pr/451/docs/src/noq_proto/connection/packet_crypto.rs.html b/pr/451/docs/src/noq_proto/connection/packet_crypto.rs.html index 7640d46ad..138025d42 100644 --- a/pr/451/docs/src/noq_proto/connection/packet_crypto.rs.html +++ b/pr/451/docs/src/noq_proto/connection/packet_crypto.rs.html @@ -31,7 +31,7 @@ 31 32pub(super) struct DecryptPacketResult { 33 /// The packet number -34 pub(super) number: u64, +34 pub(super) packet_number: u64, 35 /// Whether a locally initiated key update has been acknowledged by the peer 36 pub(super) outgoing_key_update_acked: bool, 37 /// Whether the peer has initiated a key update @@ -200,302 +200,310 @@ 200 // Packets that do not belong to known path ids are valid as long as they can be decrypted. 201 // If we didn't have a path, that's for the purposes of this function equivalent to not 202 // having received packets on that path yet. So both of these cases are represented by `None`. -203 let rx_packet = spaces[space].path_space(path_id).and_then(|s| s.rx_packet); -204 let number = packet -205 .header -206 .number() -207 .ok_or(None)? -208 .expand(rx_packet.map(|n| n + 1).unwrap_or_default()); -209 let packet_key_phase = packet.header.key_phase(); -210 -211 let mut crypto_update = false; -212 let crypto = if packet.header.is_0rtt() { -213 let (_, packet) = self.remote_crypto(EncryptionLevel::ZeroRtt).unwrap(); -214 packet -215 } else if packet_key_phase == conn_key_phase || space != SpaceKind::Data { -216 let (_, packet) = self.remote_crypto(space.encryption_level()).unwrap(); -217 packet -218 } else if let Some(prev) = self.prev_crypto.as_ref().and_then(|crypto| { -219 // If this packet comes prior to acknowledgment of the key update by the peer, -220 if crypto.end_packet.is_none_or(|(pn, _)| number < pn) { -221 // use the previous keys. -222 Some(crypto) -223 } else { -224 // Otherwise, this must be a remotely-initiated key update, so fall through to the -225 // final case. -226 None -227 } -228 }) { -229 &*prev.crypto.remote -230 } else { -231 // We're in the Data space with a key phase mismatch and either there is no locally -232 // initiated key update or the locally initiated key update was acknowledged by a -233 // lower-numbered packet. The key phase mismatch must therefore represent a new -234 // remotely-initiated key update. -235 crypto_update = true; -236 &*self.next_crypto.as_ref().unwrap().remote -237 }; -238 -239 crypto -240 .decrypt(path_id, number, &packet.header_data, &mut packet.payload) -241 .map_err(|_| { -242 trace!("decryption failed with packet number {}", number); -243 None -244 })?; -245 -246 if !packet.reserved_bits_valid() { -247 return Err(Some(TransportError::PROTOCOL_VIOLATION( -248 "reserved bits set", -249 ))); -250 } -251 -252 let mut outgoing_key_update_acked = false; -253 if let Some(ref prev) = self.prev_crypto -254 && prev.end_packet.is_none() -255 && packet_key_phase == conn_key_phase -256 { -257 outgoing_key_update_acked = true; -258 } -259 -260 if crypto_update { -261 // Validate incoming key update -262 // If `rx_packet` is `None`, then either the path is entirely new, or we haven't received -263 // any packets on this path yet. In that case, having the first packet be a crypto update -264 // is fine. -265 let invalid_packet_number = rx_packet.is_some_and(|rx_packet| number <= rx_packet); -266 if invalid_packet_number || self.prev_crypto.as_ref().is_some_and(|x| x.update_unacked) -267 { -268 trace!(?number, ?rx_packet, %path_id, "crypto update failed"); -269 return Err(Some(TransportError::KEY_UPDATE_ERROR(""))); -270 } -271 } -272 -273 Ok(Some(DecryptPacketResult { -274 number, -275 outgoing_key_update_acked, -276 incoming_key_update: crypto_update, -277 })) -278 } -279 -280 /// Check if keys are available for the given encryption level. -281 pub(super) fn has_keys(&self, level: EncryptionLevel) -> bool { -282 match level { -283 EncryptionLevel::Initial => self.spaces[0].keys.is_some(), -284 EncryptionLevel::ZeroRtt => self.zero_rtt_crypto.is_some(), -285 EncryptionLevel::Handshake => self.spaces[1].keys.is_some(), -286 EncryptionLevel::OneRtt => self.spaces[2].keys.is_some(), -287 } -288 } -289 -290 /// Discard temporary key state (0-RTT and previous keys). -291 pub(super) fn discard_temporary_keys(&mut self) { -292 self.zero_rtt_crypto = None; -293 self.prev_crypto = None; -294 } -295 -296 /// Enable 0-RTT crypto with the given keys. -297 pub(super) fn enable_zero_rtt( -298 &mut self, -299 header: Box<dyn HeaderKey>, -300 packet: Box<dyn PacketKey>, -301 ) { -302 self.zero_rtt_enabled = true; -303 self.zero_rtt_crypto = Some(ZeroRttCrypto { header, packet }); -304 } -305 -306 /// Discard 0-RTT crypto keys. -307 pub(super) fn discard_zero_rtt(&mut self) { -308 self.zero_rtt_crypto = None; -309 } -310 -311 /// Get the integrity limit for the given space's local packet keys. -312 pub(super) fn integrity_limit(&self, space: SpaceKind) -> Option<u64> { -313 let keys = self.spaces[space].keys.as_ref()?; -314 Some(keys.packet.local.integrity_limit()) -315 } -316 -317 /// Get local (sending) crypto keys for the given encryption level. -318 /// -319 /// Use this only when sure the keys are allowed to be used. [`Self::encryption_keys`] should -320 /// be preferred otherwise. -321 pub(super) fn local_crypto( -322 &self, -323 level: EncryptionLevel, -324 ) -> Option<(&dyn HeaderKey, &dyn PacketKey)> { -325 match level { -326 EncryptionLevel::Initial => self.spaces[0].keys.as_ref().map(Keys::local), -327 EncryptionLevel::Handshake => self.spaces[1].keys.as_ref().map(Keys::local), -328 EncryptionLevel::OneRtt => self.spaces[2].keys.as_ref().map(Keys::local), -329 // 0-RTT uses the same keys for both directions -330 EncryptionLevel::ZeroRtt => self.zero_rtt_crypto.as_ref().map(ZeroRttCrypto::keys), -331 } -332 } -333 -334 /// Get remote (receiving) crypto keys for the given encryption level. -335 /// -336 /// Returns header and packet keys used for decrypting incoming packets. -337 fn remote_crypto(&self, level: EncryptionLevel) -> Option<(&dyn HeaderKey, &dyn PacketKey)> { -338 match level { -339 EncryptionLevel::Initial => self.spaces[0].keys.as_ref().map(Keys::remote), -340 EncryptionLevel::Handshake => self.spaces[1].keys.as_ref().map(Keys::remote), -341 EncryptionLevel::OneRtt => self.spaces[2].keys.as_ref().map(Keys::remote), -342 // 0-RTT uses the same keys for both directions -343 EncryptionLevel::ZeroRtt => self.zero_rtt_crypto.as_ref().map(ZeroRttCrypto::keys), -344 } -345 } -346 -347 /// Get local (sending) crypto keys and the actual encryption level for a given space. -348 /// -349 /// This method takes a [`SpaceKind`] and resolves the encryption level automatically: for the -350 /// [`SpaceKind::Data`] space on the client side, it falls back to 0-RTT keys when 1-RTT keys -351 /// are not yet available. Resolving the appropriate encryption keys makes this method -352 /// preferable to [`Self::local_crypto`] in general. -353 /// -354 /// Returns `None` if no keys are available. -355 pub(super) fn encryption_keys( -356 &self, -357 kind: SpaceKind, -358 side: Side, -359 ) -> Option<(&dyn HeaderKey, &dyn PacketKey, EncryptionLevel)> { -360 let mut keys = self.spaces[kind].keys.as_ref().map(Keys::local); -361 let mut level = match kind { -362 SpaceKind::Initial => EncryptionLevel::Initial, -363 SpaceKind::Handshake => EncryptionLevel::Handshake, -364 SpaceKind::Data => EncryptionLevel::OneRtt, -365 }; -366 -367 // Clients use 0-RTT keys if 1-RTT keys are not available. Servers never encrypt 0-RTT -368 if keys.is_none() && kind == SpaceKind::Data && side.is_client() { -369 keys = self.zero_rtt_crypto.as_ref().map(ZeroRttCrypto::keys); -370 level = EncryptionLevel::ZeroRtt; -371 } -372 -373 keys.map(|(header_keys, packet_keys)| (header_keys, packet_keys, level)) -374 } -375 -376 /// Perform a 1-RTT key update. -377 /// -378 /// Generates the next set of keys, rotates current keys into previous, and installs the new -379 /// keys. Updates `key_phase` and `key_phase_size` accordingly. -380 /// -381 /// PANICS: If 1-RTT keys are missing. -382 pub(super) fn update_keys(&mut self, end_packet: Option<(u64, Instant)>, remote: bool) { -383 trace!("executing key update"); -384 -385 let new = self -386 .session -387 .next_1rtt_keys() -388 .expect("only called for `Data` packets"); -389 let confidentiality_limit = new.local.confidentiality_limit(); -390 let old = mem::replace( -391 &mut self.spaces[SpaceKind::Data] -392 .keys -393 .as_mut() -394 .unwrap() // safe because update_keys() can only be triggered by short packets -395 .packet, -396 mem::replace(self.next_crypto.as_mut().unwrap(), new), -397 ); -398 self.prev_crypto = Some(PrevCrypto { -399 crypto: old, -400 end_packet, -401 update_unacked: remote, -402 }); -403 -404 self.key_phase_size = confidentiality_limit.saturating_sub(KEY_UPDATE_MARGIN); -405 self.key_phase = !self.key_phase; -406 self.spaces[2].sent_with_keys = 0; -407 } -408 -409 /// Number of packets encrypted with the current set of keys at `level`. -410 /// -411 /// For [`EncryptionLevel::OneRtt`], this counter resets to zero on every key update (see -412 /// [`Self::update_keys`]). -413 pub(crate) fn sent_with_keys(&self, level: EncryptionLevel) -> u64 { -414 match level { -415 EncryptionLevel::Initial => self.spaces[0].sent_with_keys, -416 EncryptionLevel::ZeroRtt => self.sent_with_zero_rtt, -417 EncryptionLevel::Handshake => self.spaces[1].sent_with_keys, -418 EncryptionLevel::OneRtt => self.spaces[2].sent_with_keys, -419 } -420 } -421 -422 /// Number of packets that may still be sent before the AEAD confidentiality limit is reached -423 /// at the given encryption level. -424 /// -425 /// For [`EncryptionLevel::OneRtt`] the effective limit is the minimum of the AEAD -426 /// confidentiality limit and the current key-phase size. For all other levels the raw AEAD -427 /// confidentiality limit is used. -428 /// -429 /// Returns `None` when no keys are available for `level`. -430 pub(crate) fn remaining_packet_budget(&self, level: EncryptionLevel) -> Option<u64> { -431 let sent_with_keys = self.sent_with_keys(level); -432 let (_header_keys, packet_keys) = self.local_crypto(level)?; -433 let limit = match level { -434 EncryptionLevel::OneRtt => self.key_phase_size.min(packet_keys.confidentiality_limit()), -435 _ => packet_keys.confidentiality_limit(), -436 }; -437 -438 Some(limit.saturating_sub(sent_with_keys)) -439 } -440 -441 /// Record that a packet has been encrypted at the given level. -442 pub(crate) fn inc_sent_with_keys(&mut self, level: EncryptionLevel) { -443 let count = match level { -444 EncryptionLevel::Initial => &mut self.spaces[0].sent_with_keys, -445 EncryptionLevel::ZeroRtt => &mut self.sent_with_zero_rtt, -446 EncryptionLevel::Handshake => &mut self.spaces[1].sent_with_keys, -447 EncryptionLevel::OneRtt => &mut self.spaces[2].sent_with_keys, -448 }; -449 *count = count.saturating_add(1u64); -450 } -451} -452 -453/// Per space kind cryptographic state. -454#[derive(Default)] -455pub(super) struct CryptoSpace { -456 /// Packet protection keys for this space. -457 pub(super) keys: Option<Keys>, -458 /// Incoming cryptographic handshake stream. -459 pub(super) crypto_stream: Assembler, -460 /// Current offset of outgoing cryptographic handshake stream. -461 pub(super) crypto_offset: u64, -462 /// Number of packets encrypted with the current set of keys. -463 pub(super) sent_with_keys: u64, -464} -465 -466/// QUIC packet protection levels (RFC 9001). -467#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -468pub(crate) enum EncryptionLevel { -469 /// Initial packets (client and server). -470 Initial, -471 /// Early data (0-RTT), client only. -472 ZeroRtt, -473 /// Handshake packets. -474 Handshake, -475 /// Application data (1-RTT). -476 OneRtt, -477} -478 -479impl From<SpaceKind> for crate::packet::SpaceId { -480 fn from(kind: SpaceKind) -> Self { -481 match kind { -482 SpaceKind::Initial => Self::Initial, -483 SpaceKind::Handshake => Self::Handshake, -484 SpaceKind::Data => Self::Data, -485 } -486 } -487} -488 -489impl IndexMut<SpaceKind> for [CryptoSpace; 3] { -490 fn index_mut(&mut self, index: SpaceKind) -> &mut Self::Output { -491 &mut self[index as usize] -492 } -493} -494 -495impl Index<SpaceKind> for [CryptoSpace; 3] { -496 type Output = CryptoSpace; -497 -498 fn index(&self, index: SpaceKind) -> &Self::Output { -499 &self[index as usize] +203 let rx_packet_number = spaces[space] +204 .path_space(path_id) +205 .and_then(|s| s.largest_received_packet_number); +206 let packet_number = packet +207 .header +208 .number() +209 .ok_or(None)? +210 .expand(rx_packet_number.map(|n| n + 1).unwrap_or_default()); +211 let packet_key_phase = packet.header.key_phase(); +212 +213 let mut crypto_update = false; +214 let crypto = if packet.header.is_0rtt() { +215 let (_, packet) = self.remote_crypto(EncryptionLevel::ZeroRtt).unwrap(); +216 packet +217 } else if packet_key_phase == conn_key_phase || space != SpaceKind::Data { +218 let (_, packet) = self.remote_crypto(space.encryption_level()).unwrap(); +219 packet +220 } else if let Some(prev) = self.prev_crypto.as_ref().and_then(|crypto| { +221 // If this packet comes prior to acknowledgment of the key update by the peer, +222 if crypto.end_packet.is_none_or(|(pn, _)| packet_number < pn) { +223 // use the previous keys. +224 Some(crypto) +225 } else { +226 // Otherwise, this must be a remotely-initiated key update, so fall through to the +227 // final case. +228 None +229 } +230 }) { +231 &*prev.crypto.remote +232 } else { +233 // We're in the Data space with a key phase mismatch and either there is no locally +234 // initiated key update or the locally initiated key update was acknowledged by a +235 // lower-numbered packet. The key phase mismatch must therefore represent a new +236 // remotely-initiated key update. +237 crypto_update = true; +238 &*self.next_crypto.as_ref().unwrap().remote +239 }; +240 +241 crypto +242 .decrypt( +243 path_id, +244 packet_number, +245 &packet.header_data, +246 &mut packet.payload, +247 ) +248 .map_err(|_| { +249 trace!("decryption failed with packet number {}", packet_number); +250 None +251 })?; +252 +253 if !packet.reserved_bits_valid() { +254 return Err(Some(TransportError::PROTOCOL_VIOLATION( +255 "reserved bits set", +256 ))); +257 } +258 +259 let mut outgoing_key_update_acked = false; +260 if let Some(ref prev) = self.prev_crypto +261 && prev.end_packet.is_none() +262 && packet_key_phase == conn_key_phase +263 { +264 outgoing_key_update_acked = true; +265 } +266 +267 if crypto_update { +268 // Validate incoming key update +269 // If `rx_packet` is `None`, then either the path is entirely new, or we haven't received +270 // any packets on this path yet. In that case, having the first packet be a crypto update +271 // is fine. +272 let invalid_packet_number = +273 rx_packet_number.is_some_and(|rx_packet| packet_number <= rx_packet); +274 if invalid_packet_number || self.prev_crypto.as_ref().is_some_and(|x| x.update_unacked) +275 { +276 trace!(?packet_number, ?rx_packet_number, %path_id, "crypto update failed"); +277 return Err(Some(TransportError::KEY_UPDATE_ERROR(""))); +278 } +279 } +280 +281 Ok(Some(DecryptPacketResult { +282 packet_number, +283 outgoing_key_update_acked, +284 incoming_key_update: crypto_update, +285 })) +286 } +287 +288 /// Check if keys are available for the given encryption level. +289 pub(super) fn has_keys(&self, level: EncryptionLevel) -> bool { +290 match level { +291 EncryptionLevel::Initial => self.spaces[0].keys.is_some(), +292 EncryptionLevel::ZeroRtt => self.zero_rtt_crypto.is_some(), +293 EncryptionLevel::Handshake => self.spaces[1].keys.is_some(), +294 EncryptionLevel::OneRtt => self.spaces[2].keys.is_some(), +295 } +296 } +297 +298 /// Discard temporary key state (0-RTT and previous keys). +299 pub(super) fn discard_temporary_keys(&mut self) { +300 self.zero_rtt_crypto = None; +301 self.prev_crypto = None; +302 } +303 +304 /// Enable 0-RTT crypto with the given keys. +305 pub(super) fn enable_zero_rtt( +306 &mut self, +307 header: Box<dyn HeaderKey>, +308 packet: Box<dyn PacketKey>, +309 ) { +310 self.zero_rtt_enabled = true; +311 self.zero_rtt_crypto = Some(ZeroRttCrypto { header, packet }); +312 } +313 +314 /// Discard 0-RTT crypto keys. +315 pub(super) fn discard_zero_rtt(&mut self) { +316 self.zero_rtt_crypto = None; +317 } +318 +319 /// Get the integrity limit for the given space's local packet keys. +320 pub(super) fn integrity_limit(&self, space: SpaceKind) -> Option<u64> { +321 let keys = self.spaces[space].keys.as_ref()?; +322 Some(keys.packet.local.integrity_limit()) +323 } +324 +325 /// Get local (sending) crypto keys for the given encryption level. +326 /// +327 /// Use this only when sure the keys are allowed to be used. [`Self::encryption_keys`] should +328 /// be preferred otherwise. +329 pub(super) fn local_crypto( +330 &self, +331 level: EncryptionLevel, +332 ) -> Option<(&dyn HeaderKey, &dyn PacketKey)> { +333 match level { +334 EncryptionLevel::Initial => self.spaces[0].keys.as_ref().map(Keys::local), +335 EncryptionLevel::Handshake => self.spaces[1].keys.as_ref().map(Keys::local), +336 EncryptionLevel::OneRtt => self.spaces[2].keys.as_ref().map(Keys::local), +337 // 0-RTT uses the same keys for both directions +338 EncryptionLevel::ZeroRtt => self.zero_rtt_crypto.as_ref().map(ZeroRttCrypto::keys), +339 } +340 } +341 +342 /// Get remote (receiving) crypto keys for the given encryption level. +343 /// +344 /// Returns header and packet keys used for decrypting incoming packets. +345 fn remote_crypto(&self, level: EncryptionLevel) -> Option<(&dyn HeaderKey, &dyn PacketKey)> { +346 match level { +347 EncryptionLevel::Initial => self.spaces[0].keys.as_ref().map(Keys::remote), +348 EncryptionLevel::Handshake => self.spaces[1].keys.as_ref().map(Keys::remote), +349 EncryptionLevel::OneRtt => self.spaces[2].keys.as_ref().map(Keys::remote), +350 // 0-RTT uses the same keys for both directions +351 EncryptionLevel::ZeroRtt => self.zero_rtt_crypto.as_ref().map(ZeroRttCrypto::keys), +352 } +353 } +354 +355 /// Get local (sending) crypto keys and the actual encryption level for a given space. +356 /// +357 /// This method takes a [`SpaceKind`] and resolves the encryption level automatically: for the +358 /// [`SpaceKind::Data`] space on the client side, it falls back to 0-RTT keys when 1-RTT keys +359 /// are not yet available. Resolving the appropriate encryption keys makes this method +360 /// preferable to [`Self::local_crypto`] in general. +361 /// +362 /// Returns `None` if no keys are available. +363 pub(super) fn encryption_keys( +364 &self, +365 kind: SpaceKind, +366 side: Side, +367 ) -> Option<(&dyn HeaderKey, &dyn PacketKey, EncryptionLevel)> { +368 let mut keys = self.spaces[kind].keys.as_ref().map(Keys::local); +369 let mut level = match kind { +370 SpaceKind::Initial => EncryptionLevel::Initial, +371 SpaceKind::Handshake => EncryptionLevel::Handshake, +372 SpaceKind::Data => EncryptionLevel::OneRtt, +373 }; +374 +375 // Clients use 0-RTT keys if 1-RTT keys are not available. Servers never encrypt 0-RTT +376 if keys.is_none() && kind == SpaceKind::Data && side.is_client() { +377 keys = self.zero_rtt_crypto.as_ref().map(ZeroRttCrypto::keys); +378 level = EncryptionLevel::ZeroRtt; +379 } +380 +381 keys.map(|(header_keys, packet_keys)| (header_keys, packet_keys, level)) +382 } +383 +384 /// Perform a 1-RTT key update. +385 /// +386 /// Generates the next set of keys, rotates current keys into previous, and installs the new +387 /// keys. Updates `key_phase` and `key_phase_size` accordingly. +388 /// +389 /// PANICS: If 1-RTT keys are missing. +390 pub(super) fn update_keys(&mut self, end_packet: Option<(u64, Instant)>, remote: bool) { +391 trace!("executing key update"); +392 +393 let new = self +394 .session +395 .next_1rtt_keys() +396 .expect("only called for `Data` packets"); +397 let confidentiality_limit = new.local.confidentiality_limit(); +398 let old = mem::replace( +399 &mut self.spaces[SpaceKind::Data] +400 .keys +401 .as_mut() +402 .unwrap() // safe because update_keys() can only be triggered by short packets +403 .packet, +404 mem::replace(self.next_crypto.as_mut().unwrap(), new), +405 ); +406 self.prev_crypto = Some(PrevCrypto { +407 crypto: old, +408 end_packet, +409 update_unacked: remote, +410 }); +411 +412 self.key_phase_size = confidentiality_limit.saturating_sub(KEY_UPDATE_MARGIN); +413 self.key_phase = !self.key_phase; +414 self.spaces[2].sent_with_keys = 0; +415 } +416 +417 /// Number of packets encrypted with the current set of keys at `level`. +418 /// +419 /// For [`EncryptionLevel::OneRtt`], this counter resets to zero on every key update (see +420 /// [`Self::update_keys`]). +421 pub(crate) fn sent_with_keys(&self, level: EncryptionLevel) -> u64 { +422 match level { +423 EncryptionLevel::Initial => self.spaces[0].sent_with_keys, +424 EncryptionLevel::ZeroRtt => self.sent_with_zero_rtt, +425 EncryptionLevel::Handshake => self.spaces[1].sent_with_keys, +426 EncryptionLevel::OneRtt => self.spaces[2].sent_with_keys, +427 } +428 } +429 +430 /// Number of packets that may still be sent before the AEAD confidentiality limit is reached +431 /// at the given encryption level. +432 /// +433 /// For [`EncryptionLevel::OneRtt`] the effective limit is the minimum of the AEAD +434 /// confidentiality limit and the current key-phase size. For all other levels the raw AEAD +435 /// confidentiality limit is used. +436 /// +437 /// Returns `None` when no keys are available for `level`. +438 pub(crate) fn remaining_packet_budget(&self, level: EncryptionLevel) -> Option<u64> { +439 let sent_with_keys = self.sent_with_keys(level); +440 let (_header_keys, packet_keys) = self.local_crypto(level)?; +441 let limit = match level { +442 EncryptionLevel::OneRtt => self.key_phase_size.min(packet_keys.confidentiality_limit()), +443 _ => packet_keys.confidentiality_limit(), +444 }; +445 +446 Some(limit.saturating_sub(sent_with_keys)) +447 } +448 +449 /// Record that a packet has been encrypted at the given level. +450 pub(crate) fn inc_sent_with_keys(&mut self, level: EncryptionLevel) { +451 let count = match level { +452 EncryptionLevel::Initial => &mut self.spaces[0].sent_with_keys, +453 EncryptionLevel::ZeroRtt => &mut self.sent_with_zero_rtt, +454 EncryptionLevel::Handshake => &mut self.spaces[1].sent_with_keys, +455 EncryptionLevel::OneRtt => &mut self.spaces[2].sent_with_keys, +456 }; +457 *count = count.saturating_add(1u64); +458 } +459} +460 +461/// Per space kind cryptographic state. +462#[derive(Default)] +463pub(super) struct CryptoSpace { +464 /// Packet protection keys for this space. +465 pub(super) keys: Option<Keys>, +466 /// Incoming cryptographic handshake stream. +467 pub(super) crypto_stream: Assembler, +468 /// Current offset of outgoing cryptographic handshake stream. +469 pub(super) crypto_offset: u64, +470 /// Number of packets encrypted with the current set of keys. +471 pub(super) sent_with_keys: u64, +472} +473 +474/// QUIC packet protection levels (RFC 9001). +475#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +476pub(crate) enum EncryptionLevel { +477 /// Initial packets (client and server). +478 Initial, +479 /// Early data (0-RTT), client only. +480 ZeroRtt, +481 /// Handshake packets. +482 Handshake, +483 /// Application data (1-RTT). +484 OneRtt, +485} +486 +487impl From<SpaceKind> for crate::packet::SpaceId { +488 fn from(kind: SpaceKind) -> Self { +489 match kind { +490 SpaceKind::Initial => Self::Initial, +491 SpaceKind::Handshake => Self::Handshake, +492 SpaceKind::Data => Self::Data, +493 } +494 } +495} +496 +497impl IndexMut<SpaceKind> for [CryptoSpace; 3] { +498 fn index_mut(&mut self, index: SpaceKind) -> &mut Self::Output { +499 &mut self[index as usize] 500 } -501} \ No newline at end of file +501} +502 +503impl Index<SpaceKind> for [CryptoSpace; 3] { +504 type Output = CryptoSpace; +505 +506 fn index(&self, index: SpaceKind) -> &Self::Output { +507 &self[index as usize] +508 } +509} \ 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 708afe4fd..f9e16d239 100644 --- a/pr/451/docs/src/noq_proto/connection/paths.rs.html +++ b/pr/451/docs/src/noq_proto/connection/paths.rs.html @@ -134,973 +134,1032 @@ 134 pub(super) network_path: FourTuple, 135} 136 -137/// Description of a particular network path -138#[derive(Debug)] -139pub(super) struct PathData { -140 pub(super) network_path: FourTuple, -141 pub(super) rtt: RttEstimator, -142 /// Whether we're enabling ECN on outgoing packets -143 pub(super) sending_ecn: bool, -144 /// Congestion controller state -145 pub(super) congestion: Box<dyn congestion::Controller>, -146 /// Pacing state -147 pub(super) pacing: Pacer, -148 /// Path challenges sent (on the wire, on-path) that we didn't receive a path response for yet -149 on_path_challenges_unconfirmed: IntMap<u64, SentChallengeInfo>, -150 /// Path challenges sent (on the wire, off-path) that we didn't receive a path response for yet -151 off_path_challenges_unconfirmed: IntMap<u64, SentChallengeInfo>, -152 /// Whether to trigger sending another PATH_CHALLENGE in the next poll_transmit. -153 /// -154 /// This is picked up by [`super::Connection::space_can_send`]. -155 /// -156 /// Only used for RFC9000-style path migration and multipath path validation (for opening). -157 /// -158 /// This is **not used** for n0 nat traversal challenge sending. -159 pub(super) pending_on_path_challenge: bool, -160 /// Pending responses to PATH_CHALLENGE frames -161 pub(super) path_responses: PathResponses, -162 /// Whether we're certain the peer can both send and receive on this address -163 /// -164 /// Initially equal to `use_stateless_retry` for servers, and becomes false again on every -165 /// migration. Always true for clients. -166 pub(super) validated: bool, -167 /// Total size of all UDP datagrams sent on this path -168 pub(super) total_sent: u64, -169 /// Total size of all UDP datagrams received on this path -170 pub(super) total_recvd: u64, -171 /// The state of the MTU discovery process -172 pub(super) mtud: MtuDiscovery, -173 /// Packet number of the first packet sent after an RTT sample was collected on this path +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 /// Used in persistent congestion determination. -176 pub(super) first_packet_after_rtt_sample: Option<(SpaceKind, u64)>, -177 /// The in-flight packets and bytes -178 /// -179 /// Note that this is across all spaces on this path -180 pub(super) in_flight: InFlight, -181 /// Whether this path has had it's remote address reported back to the peer. This only happens -182 /// if both peers agree to so based on their transport parameters. -183 pub(super) observed_addr_sent: bool, -184 /// Observed address frame with the largest sequence number received from the peer on this path. -185 pub(super) last_observed_addr_report: Option<ObservedAddr>, -186 /// The QUIC-MULTIPATH path status -187 pub(super) status: PathStatusState, -188 /// Number of the first packet sent on this path +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. 189 /// -190 /// With RFC9000 §9 style migration (i.e. not multipath) the PathId does not change and -191 /// hence packet numbers continue. This is used to determine whether a packet was sent -192 /// on such an earlier path. Insufficient to determine if a packet was sent on a later -193 /// path. -194 first_packet: Option<u64>, -195 /// The number of times a tail-loss probe has been sent without receiving an ack. -196 /// -197 /// This is incremented by one every time the [`LossDetection`] timer fires because a -198 /// tail-loss probe needs to be sent. Once an acknowledgement for a packet is received -199 /// again it is reset to 0. Used to compute the PTO duration. -200 /// -201 /// [`LossDetection`]: super::timer::PathTimer::LossDetection -202 pub(super) pto_count: u32, -203 -204 // -205 // Per-path idle & keep alive -206 // -207 /// Idle timeout for the path -208 /// -209 /// If expired, the path will be abandoned. This is different from the connection-wide -210 /// idle timeout which closes the connection if expired. -211 pub(super) idle_timeout: Option<Duration>, -212 /// Keep alives to send on this path -213 /// -214 /// There is also a connection-level keep alive configured in the -215 /// [`TransportParameters`]. This triggers activity on any path which can keep the -216 /// connection alive. -217 /// -218 /// [`TransportParameters`]: crate::transport_parameters::TransportParameters -219 pub(super) keep_alive: Option<Duration>, -220 -221 /// Whether the path has already been considered opened from an application perspective. -222 /// -223 /// This means, for paths other than the original [`PathId::ZERO`], a first path challenge has -224 /// been responded to, regardless of the initial validation status of the path. This state is -225 /// irreversible, since it's not affected by the path being closed. -226 /// -227 /// Sending a PATH_CHALLENGE and receiving a valid response before the application is informed -228 /// of the path, is a way to ensure the path is usable before it is reported. This is not -229 /// required by the spec, and in the future might be changed for simply requiring a first ack'd -230 /// packet. -231 pub(super) open_status: OpenStatus, -232 -233 /// Whether we're currently draining the path after having abandoned it. -234 /// -235 /// This should only be true when a path discard timer is armed, and after the path was -236 /// abandoned (and added to the abandoned_paths set). -237 /// -238 /// This will only ever be set from false to true. -239 pub(super) draining: bool, -240 -241 /// Snapshot of the qlog recovery metrics -242 #[cfg(feature = "qlog")] -243 recovery_metrics: RecoveryMetrics, -244 -245 /// Tag uniquely identifying a path in a connection. -246 /// -247 /// When a migration happens on the same [`PathId`] we still detect a change in the -248 /// 4-tuple and generate a new [`PathData`] for it. Each such generation has a unique -249 /// value to keep track of which 4-tuple a packet belonged to. -250 generation: u64, -251} -252 -253impl PathData { -254 pub(super) fn new( -255 network_path: FourTuple, -256 allow_mtud: bool, -257 peer_max_udp_payload_size: Option<u16>, -258 generation: u64, -259 now: Instant, -260 config: &TransportConfig, -261 ) -> Self { -262 let congestion = config -263 .congestion_controller_factory -264 .clone() -265 .build(now, config.get_initial_mtu()); -266 Self { -267 network_path, -268 rtt: RttEstimator::new(config.initial_rtt), -269 sending_ecn: true, -270 pacing: Pacer::new( -271 config.initial_rtt, -272 congestion.initial_window(), -273 config.get_initial_mtu(), -274 now, -275 ), -276 congestion, -277 on_path_challenges_unconfirmed: Default::default(), -278 off_path_challenges_unconfirmed: Default::default(), -279 pending_on_path_challenge: false, -280 path_responses: PathResponses::default(), -281 validated: false, -282 total_sent: 0, -283 total_recvd: 0, -284 mtud: config -285 .mtu_discovery_config -286 .as_ref() -287 .filter(|_| allow_mtud) -288 .map_or_else( -289 || MtuDiscovery::disabled(config.get_initial_mtu(), config.min_mtu), -290 |mtud_config| { -291 MtuDiscovery::new( -292 config.get_initial_mtu(), -293 config.min_mtu, -294 peer_max_udp_payload_size, -295 mtud_config.clone(), -296 ) -297 }, -298 ), -299 first_packet_after_rtt_sample: None, -300 in_flight: InFlight::new(), -301 observed_addr_sent: false, -302 last_observed_addr_report: None, -303 status: Default::default(), -304 first_packet: None, -305 pto_count: 0, -306 idle_timeout: config.default_path_max_idle_timeout, -307 keep_alive: config.default_path_keep_alive_interval, -308 open_status: OpenStatus::default(), -309 draining: false, -310 #[cfg(feature = "qlog")] -311 recovery_metrics: RecoveryMetrics::default(), -312 generation, -313 } -314 } -315 -316 /// Create a new path from a previous one. -317 /// -318 /// This should only be called when migrating paths. -319 pub(super) fn from_previous( -320 network_path: FourTuple, -321 prev: &Self, -322 generation: u64, -323 now: Instant, -324 ) -> Self { -325 let congestion = prev.congestion.clone_box(); -326 let smoothed_rtt = prev.rtt.get(); -327 Self { -328 network_path, -329 rtt: prev.rtt, -330 pacing: Pacer::new(smoothed_rtt, congestion.window(), prev.current_mtu(), now), -331 sending_ecn: true, -332 congestion, -333 on_path_challenges_unconfirmed: Default::default(), -334 off_path_challenges_unconfirmed: Default::default(), -335 pending_on_path_challenge: false, -336 path_responses: PathResponses::default(), -337 validated: false, -338 total_sent: 0, -339 total_recvd: 0, -340 mtud: prev.mtud.clone(), -341 first_packet_after_rtt_sample: prev.first_packet_after_rtt_sample, -342 in_flight: InFlight::new(), -343 observed_addr_sent: false, -344 last_observed_addr_report: None, -345 status: prev.status.clone(), -346 first_packet: None, -347 pto_count: 0, -348 idle_timeout: prev.idle_timeout, -349 keep_alive: prev.keep_alive, -350 open_status: OpenStatus::default(), -351 draining: false, -352 #[cfg(feature = "qlog")] -353 recovery_metrics: prev.recovery_metrics.clone(), -354 generation, -355 } -356 } -357 -358 /// Whether we're in the process of validating this path with PATH_CHALLENGEs -359 pub(super) fn is_validating_path(&self) -> bool { -360 !self.on_path_challenges_unconfirmed.is_empty() || self.pending_on_path_challenge -361 } -362 -363 /// Indicates whether we're a server that hasn't validated the peer's address and hasn't -364 /// received enough data from the peer to permit sending `bytes_to_send` additional bytes -365 pub(super) fn anti_amplification_blocked(&self, bytes_to_send: u64) -> bool { -366 !self.validated && self.total_recvd * 3 < self.total_sent + bytes_to_send -367 } -368 -369 /// Returns the path's current MTU -370 pub(super) fn current_mtu(&self) -> u16 { -371 self.mtud.current_mtu() -372 } -373 -374 /// Account for transmission of `packet` with number `pn` in `space` -375 pub(super) fn sent(&mut self, pn: u64, packet: SentPacket, space: &mut PacketNumberSpace) { -376 self.in_flight.insert(&packet); -377 if self.first_packet.is_none() { -378 self.first_packet = Some(pn); -379 } -380 if let Some(forgotten) = space.sent(pn, packet) { -381 self.remove_in_flight(&forgotten); -382 } -383 } -384 -385 pub(super) fn record_path_challenge_sent( -386 &mut self, -387 now: Instant, -388 token: u64, -389 network_path: FourTuple, -390 ) { -391 let info = SentChallengeInfo { -392 sent_instant: now, -393 network_path, -394 }; -395 if network_path == self.network_path { -396 self.on_path_challenges_unconfirmed.insert(token, info); -397 } else { -398 self.off_path_challenges_unconfirmed.insert(token, info); -399 } -400 } -401 -402 /// Remove `packet` with number `pn` from this path's congestion control counters, or return -403 /// `false` if `pn` was sent before this path was established. -404 pub(super) fn remove_in_flight(&mut self, packet: &SentPacket) -> bool { -405 if packet.path_generation != self.generation { -406 return false; -407 } -408 self.in_flight.remove(packet); -409 true -410 } -411 -412 /// Increment the total size of sent UDP datagrams -413 pub(super) fn inc_total_sent(&mut self, inc: u64) { -414 self.total_sent = self.total_sent.saturating_add(inc); -415 if !self.validated { -416 trace!( -417 network_path = %self.network_path, -418 anti_amplification_budget = %(self.total_recvd * 3).saturating_sub(self.total_sent), -419 "anti amplification budget decreased" -420 ); -421 } -422 } -423 -424 /// Increment the total size of received UDP datagrams -425 pub(super) fn inc_total_recvd(&mut self, inc: u64) { -426 self.total_recvd = self.total_recvd.saturating_add(inc); -427 if !self.validated { -428 trace!( -429 network_path = %self.network_path, -430 anti_amplification_budget = %(self.total_recvd * 3).saturating_sub(self.total_sent), -431 "anti amplification budget increased" -432 ); -433 } -434 } -435 -436 /// The earliest time at which an on-path challenge we sent is considered lost. -437 pub(super) fn earliest_on_path_expiring_challenge(&self) -> Option<Instant> { -438 if self.on_path_challenges_unconfirmed.is_empty() { -439 return None; -440 } -441 let pto = self.rtt.pto_base(); -442 self.on_path_challenges_unconfirmed -443 .values() -444 .map(|info| info.sent_instant + pto) -445 .min() -446 } -447 -448 /// Handle receiving a PATH_RESPONSE. -449 pub(super) fn on_path_response_received( -450 &mut self, -451 now: Instant, -452 token: u64, -453 network_path: FourTuple, -454 ) -> OnPathResponseReceived { -455 // > § 8.2.3 -456 // > Path validation succeeds when a PATH_RESPONSE frame is received that contains the -457 // > data that was sent in a previous PATH_CHALLENGE frame. A PATH_RESPONSE frame -458 // > received on any network path validates the path on which the PATH_CHALLENGE was -459 // > sent. -460 // -461 // At this point we have three potentially different network paths: -462 // - current network path (`Self::network_path`) -463 // - network path used to send the path challenge (`SentChallengeInfo::network_path`) -464 // - network path over which the response arrived (`network_path`) -465 // -466 // As per the spec, this only validates the network path on which this was *sent*. -467 match self.on_path_challenges_unconfirmed.remove(&token) { -468 // Response to an on-path PathChallenge that validates this path. -469 // The sent path should match the current path. However, it's possible that the -470 // challenge was sent when no local_ip was known. This case is allowed as well -471 Some(info) if info.network_path.is_probably_same_path(&self.network_path) => { -472 self.network_path.update_local_if_same_remote(&network_path); -473 let sent_instant = info.sent_instant; -474 if !std::mem::replace(&mut self.validated, true) { -475 trace!("new path validated"); -476 } -477 // Clear any other on-path sent challenge -478 self.on_path_challenges_unconfirmed.clear(); -479 -480 self.pending_on_path_challenge = false; -481 -482 // This RTT can only be used for the initial RTT, not as a normal -483 // sample: https://www.rfc-editor.org/rfc/rfc9002#section-6.2.2-2. -484 let rtt = now.saturating_duration_since(sent_instant); -485 self.rtt.reset_initial_rtt(rtt); -486 -487 let prev_status = std::mem::replace(&mut self.open_status, OpenStatus::Informed); -488 OnPathResponseReceived::OnPath { -489 was_open: prev_status == OpenStatus::Informed, -490 } -491 } -492 // Response to an on-path PathChallenge that does not validate this path -493 Some(info) => { -494 // This is a valid path response, but this validates a path we no longer have in -495 // use. Keep only sent challenges for the current path. -496 -497 self.on_path_challenges_unconfirmed -498 .retain(|_token, i| i.network_path == self.network_path); -499 -500 // if there are no challenges for the current path, schedule one -501 if !self.on_path_challenges_unconfirmed.is_empty() { -502 self.pending_on_path_challenge = true; -503 } -504 OnPathResponseReceived::Ignored { -505 sent_on: info.network_path, -506 current_path: self.network_path, -507 } -508 } -509 None => match self.off_path_challenges_unconfirmed.remove(&token) { -510 // Response to an off-path PathChallenge -511 Some(info) => { -512 // Since we do not store validation state for these paths, we only really care -513 // about reaching the same remote -514 self.off_path_challenges_unconfirmed -515 .retain(|_token, i| i.network_path.remote != info.network_path.remote); -516 OnPathResponseReceived::OffPath -517 } -518 // Response to an unknown PathChallenge. Does not indicate failure -519 None => OnPathResponseReceived::Unknown, -520 }, -521 } -522 } -523 -524 /// Removes all on-path challenges we remember and cancels sending new on-path challenges. -525 pub(super) fn reset_on_path_challenges(&mut self) { -526 self.on_path_challenges_unconfirmed.clear(); -527 self.pending_on_path_challenge = false; -528 } -529 -530 #[cfg(feature = "qlog")] -531 pub(super) fn qlog_recovery_metrics( -532 &mut self, -533 path_id: PathId, -534 ) -> Option<RecoveryMetricsUpdated> { -535 let controller_metrics = self.congestion.metrics(); -536 -537 let metrics = RecoveryMetrics { -538 min_rtt: Some(self.rtt.min), -539 smoothed_rtt: Some(self.rtt.get()), -540 latest_rtt: Some(self.rtt.latest), -541 rtt_variance: Some(self.rtt.var), -542 pto_count: Some(self.pto_count), -543 bytes_in_flight: Some(self.in_flight.bytes), -544 packets_in_flight: Some(self.in_flight.ack_eliciting), -545 -546 congestion_window: Some(controller_metrics.congestion_window), -547 ssthresh: controller_metrics.ssthresh, -548 pacing_rate: controller_metrics.pacing_rate, -549 }; -550 -551 let event = metrics.to_qlog_event(path_id, &self.recovery_metrics); -552 self.recovery_metrics = metrics; -553 event -554 } -555 -556 /// Return how long we need to wait before sending `bytes_to_send` -557 /// -558 /// See [`Pacer::delay`]. -559 pub(super) fn pacing_delay(&mut self, bytes_to_send: u64, now: Instant) -> Option<Instant> { -560 let smoothed_rtt = self.rtt.get(); -561 self.pacing.delay( -562 smoothed_rtt, -563 bytes_to_send, -564 self.current_mtu(), -565 self.congestion.window(), -566 now, -567 ) +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 +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). +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 } +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 +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() +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 /// Updates the last observed address report received on this path. -571 /// -572 /// If the address was updated, it's returned to be informed to the application. -573 #[must_use = "updated observed address must be reported to the application"] -574 pub(super) fn update_observed_addr_report( -575 &mut self, -576 observed: ObservedAddr, -577 ) -> Option<SocketAddr> { -578 match self.last_observed_addr_report.as_mut() { -579 Some(prev) => { -580 if prev.seq_no >= observed.seq_no { -581 // frames that do not increase the sequence number on this path are ignored -582 None -583 } else if prev.ip == observed.ip && prev.port == observed.port { -584 // keep track of the last seq_no but do not report the address as updated -585 prev.seq_no = observed.seq_no; -586 None -587 } else { -588 let addr = observed.socket_addr(); -589 self.last_observed_addr_report = Some(observed); -590 Some(addr) -591 } -592 } -593 None => { -594 let addr = observed.socket_addr(); -595 self.last_observed_addr_report = Some(observed); -596 Some(addr) -597 } -598 } -599 } -600 -601 pub(crate) fn remote_status(&self) -> Option<PathStatus> { -602 self.status.remote_status.map(|(_seq, status)| status) -603 } -604 -605 pub(crate) fn local_status(&self) -> PathStatus { -606 self.status.local_status -607 } -608 -609 /// Tag uniquely identifying a path in a connection. -610 /// -611 /// When a migration happens on the same [`PathId`] we still detect a change in the -612 /// 4-tuple and generate a new [`PathData`] for it. Each such generation has a unique -613 /// value to keep track of which 4-tuple a packet belonged to. -614 pub(super) fn generation(&self) -> u64 { -615 self.generation -616 } -617} -618 -619pub(super) enum OnPathResponseReceived { -620 /// This response validates the path on its current remote address. -621 OnPath { was_open: bool }, -622 /// This response is valid, but it's for a remote other than the path's current remote address. -623 OffPath, -624 /// The received token is unknown. -625 Unknown, -626 /// The response is valid but it's not usable for path validation. -627 Ignored { -628 sent_on: FourTuple, -629 current_path: FourTuple, -630 }, -631} -632 -633#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] -634pub(super) enum OpenStatus { -635 /// A first packet has not been sent using this [`PathId`]. -636 #[default] -637 Pending, -638 /// The first packet has been sent using this [`PathId`]. However, it is not yet deemed good -639 /// enough to be reported to the application. -640 Sent, -641 /// The application has been informed of this path. -642 Informed, -643} -644 -645/// Congestion metrics as described in [`recovery_metrics_updated`]. -646/// -647/// [`recovery_metrics_updated`]: https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-quic-events.html#name-recovery_metrics_updated -648#[cfg(feature = "qlog")] -649#[derive(Default, Clone, PartialEq, Debug)] -650#[non_exhaustive] -651struct RecoveryMetrics { -652 pub min_rtt: Option<Duration>, -653 pub smoothed_rtt: Option<Duration>, -654 pub latest_rtt: Option<Duration>, -655 pub rtt_variance: Option<Duration>, -656 pub pto_count: Option<u32>, -657 pub bytes_in_flight: Option<u64>, -658 pub packets_in_flight: Option<u64>, -659 pub congestion_window: Option<u64>, -660 pub ssthresh: Option<u64>, -661 pub pacing_rate: Option<u64>, -662} -663 -664#[cfg(feature = "qlog")] -665impl RecoveryMetrics { -666 /// Retain only values that have been updated since the last snapshot. -667 fn retain_updated(&self, previous: &Self) -> Self { -668 macro_rules! keep_if_changed { -669 ($name:ident) => { -670 if previous.$name == self.$name { -671 None -672 } else { -673 self.$name -674 } -675 }; -676 } -677 -678 Self { -679 min_rtt: keep_if_changed!(min_rtt), -680 smoothed_rtt: keep_if_changed!(smoothed_rtt), -681 latest_rtt: keep_if_changed!(latest_rtt), -682 rtt_variance: keep_if_changed!(rtt_variance), -683 pto_count: keep_if_changed!(pto_count), -684 bytes_in_flight: keep_if_changed!(bytes_in_flight), -685 packets_in_flight: keep_if_changed!(packets_in_flight), -686 congestion_window: keep_if_changed!(congestion_window), -687 ssthresh: keep_if_changed!(ssthresh), -688 pacing_rate: keep_if_changed!(pacing_rate), -689 } -690 } -691 -692 /// Emit a `MetricsUpdated` event containing only updated values -693 fn to_qlog_event(&self, path_id: PathId, previous: &Self) -> Option<RecoveryMetricsUpdated> { -694 let updated = self.retain_updated(previous); -695 -696 if updated == Self::default() { -697 return None; -698 } -699 -700 Some(RecoveryMetricsUpdated { -701 min_rtt: updated.min_rtt.map(|rtt| rtt.as_secs_f32()), -702 smoothed_rtt: updated.smoothed_rtt.map(|rtt| rtt.as_secs_f32()), -703 latest_rtt: updated.latest_rtt.map(|rtt| rtt.as_secs_f32()), -704 rtt_variance: updated.rtt_variance.map(|rtt| rtt.as_secs_f32()), -705 pto_count: updated -706 .pto_count -707 .map(|count| count.try_into().unwrap_or(u16::MAX)), -708 bytes_in_flight: updated.bytes_in_flight, -709 packets_in_flight: updated.packets_in_flight, -710 congestion_window: updated.congestion_window, -711 ssthresh: updated.ssthresh, -712 pacing_rate: updated.pacing_rate, -713 path_id: Some(path_id.as_u32() as u64), -714 }) -715 } -716} -717 -718/// RTT estimation for a particular network path -719#[derive(Copy, Clone, Debug)] -720pub struct RttEstimator { -721 /// The most recent RTT measurement made when receiving an ack for a previously unacked packet -722 latest: Duration, -723 /// The smoothed RTT of the connection, computed as described in RFC6298 -724 smoothed: Option<Duration>, -725 /// The RTT variance, computed as described in RFC6298 -726 var: Duration, -727 /// The minimum RTT seen in the connection, ignoring ack delay. -728 min: Duration, -729} -730 -731impl RttEstimator { -732 pub(super) fn new(initial_rtt: Duration) -> Self { -733 Self { -734 latest: initial_rtt, -735 smoothed: None, -736 var: initial_rtt / 2, -737 min: initial_rtt, -738 } -739 } +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 } +642 } +643 +644 pub(crate) fn remote_status(&self) -> Option<PathStatus> { +645 self.status.remote_status.map(|(_seq, status)| 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 /// Resets the estimator using a new initial_rtt value. -742 /// -743 /// This only resets the initial_rtt **if** no samples have been recorded yet. If there -744 /// are any recorded samples the initial estimate can not be adjusted after the fact. -745 /// -746 /// This is useful when you receive a PATH_RESPONSE in the first packet received on a -747 /// new path. In this case you can use the delay of the PATH_CHALLENGE-PATH_RESPONSE as -748 /// the initial RTT to get a better expected estimation. -749 /// -750 /// A PATH_CHALLENGE-PATH_RESPONSE pair later in the connection should not be used -751 /// explicitly as an estimation since PATH_CHALLENGE is an ACK-eliciting packet itself -752 /// already. -753 pub(crate) fn reset_initial_rtt(&mut self, initial_rtt: Duration) { -754 if self.smoothed.is_none() { -755 self.latest = initial_rtt; -756 self.var = initial_rtt / 2; -757 self.min = initial_rtt; -758 } -759 } -760 -761 /// The current best RTT estimation. -762 pub fn get(&self) -> Duration { -763 self.smoothed.unwrap_or(self.latest) -764 } -765 -766 /// Conservative estimate of RTT -767 /// -768 /// Takes the maximum of smoothed and latest RTT, as recommended -769 /// in 6.1.2 of the recovery spec (draft 29). -770 pub fn conservative(&self) -> Duration { -771 self.get().max(self.latest) -772 } -773 -774 /// Minimum RTT registered so far for this estimator. -775 pub fn min(&self) -> Duration { -776 self.min -777 } -778 -779 /// PTO computed as described in RFC9002#6.2.1. -780 pub(crate) fn pto_base(&self) -> Duration { -781 self.get() + cmp::max(4 * self.var, TIMER_GRANULARITY) -782 } -783 -784 /// Records an RTT sample. -785 pub(crate) fn update(&mut self, ack_delay: Duration, rtt: Duration) { -786 self.latest = rtt; -787 // https://www.rfc-editor.org/rfc/rfc9002.html#section-5.2-3: -788 // min_rtt does not adjust for ack_delay to avoid underestimating. -789 self.min = cmp::min(self.min, self.latest); -790 // Based on RFC6298. -791 if let Some(smoothed) = self.smoothed { -792 let adjusted_rtt = if self.min + ack_delay <= self.latest { -793 self.latest - ack_delay -794 } else { -795 self.latest -796 }; -797 let var_sample = smoothed.abs_diff(adjusted_rtt); -798 self.var = (3 * self.var + var_sample) / 4; -799 self.smoothed = Some((7 * smoothed + adjusted_rtt) / 8); -800 } else { -801 self.smoothed = Some(self.latest); -802 self.var = self.latest / 2; -803 self.min = self.latest; -804 } -805 } -806} +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#[derive(Default, Debug)] -809pub(crate) struct PathResponses { -810 pending: Vec<PathResponse>, -811} +808 /// The current best RTT estimation. +809 pub fn get(&self) -> Duration { +810 self.smoothed.unwrap_or(self.latest) +811 } 812 -813impl PathResponses { -814 pub(crate) fn push(&mut self, packet: u64, token: u64, network_path: FourTuple) { -815 /// Arbitrary permissive limit to prevent abuse -816 const MAX_PATH_RESPONSES: usize = 16; -817 let response = PathResponse { -818 packet, -819 token, -820 network_path, -821 }; -822 let existing = self -823 .pending -824 .iter_mut() -825 .find(|x| x.network_path.remote == network_path.remote); -826 if let Some(existing) = existing { -827 // Update a queued response -828 if existing.packet <= packet { -829 *existing = response; -830 } -831 return; -832 } -833 if self.pending.len() < MAX_PATH_RESPONSES { -834 self.pending.push(response); -835 } else { -836 // We don't expect to ever hit this with well-behaved peers, so we don't bother dropping -837 // older challenges. -838 trace!("ignoring excessive PATH_CHALLENGE"); -839 } -840 } -841 -842 pub(crate) fn pop_off_path(&mut self, network_path: FourTuple) -> Option<(u64, FourTuple)> { -843 let response = *self.pending.last()?; -844 // We use an exact comparison here, because once we've received for the first time, -845 // we really should either already have a local_ip, or we will never get one -846 // (because our OS doesn't support it). -847 if response.network_path == network_path { -848 // We don't bother searching further because we expect that the on-path response will -849 // get drained in the immediate future by a call to `pop_on_path` -850 return None; +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 self.pending.pop(); -853 Some((response.token, response.network_path)) -854 } -855 -856 pub(crate) fn pop_on_path(&mut self, network_path: FourTuple) -> Option<u64> { -857 let response = *self.pending.last()?; -858 // Using an exact comparison. See explanation in `pop_off_path`. -859 if response.network_path != network_path { -860 // We don't bother searching further because we expect that the off-path response will -861 // get drained in the immediate future by a call to `pop_off_path` -862 return None; -863 } -864 self.pending.pop(); -865 Some(response.token) -866 } -867 -868 pub(crate) fn is_empty(&self) -> bool { -869 self.pending.is_empty() -870 } -871} -872 -873#[derive(Copy, Clone, Debug)] -874struct PathResponse { -875 /// The packet number the corresponding PATH_CHALLENGE was received in -876 packet: u64, -877 /// The token of the PATH_CHALLENGE -878 token: u64, -879 /// The path the corresponding PATH_CHALLENGE was received from -880 network_path: FourTuple, -881} -882 -883/// Summary statistics of packets that have been sent on a particular path, but which have not yet -884/// been acked or deemed lost -885#[derive(Debug)] -886pub(super) struct InFlight { -887 /// Sum of the sizes of all sent packets considered "in flight" by congestion control -888 /// -889 /// The size does not include IP or UDP overhead. Packets only containing ACK frames do not -890 /// count towards this to ensure congestion control does not impede congestion feedback. -891 pub(super) bytes: u64, -892 /// Number of packets in flight containing frames other than ACK and PADDING -893 /// -894 /// This can be 0 even when bytes is not 0 because PADDING frames cause a packet to be -895 /// considered "in flight" by congestion control. However, if this is nonzero, bytes will always -896 /// also be nonzero. -897 pub(super) ack_eliciting: u64, -898} -899 -900impl InFlight { -901 fn new() -> Self { -902 Self { -903 bytes: 0, -904 ack_eliciting: 0, -905 } -906 } -907 -908 fn insert(&mut self, packet: &SentPacket) { -909 self.bytes += u64::from(packet.size); -910 self.ack_eliciting += u64::from(packet.ack_eliciting); -911 } -912 -913 /// Update counters to account for a packet becoming acknowledged, lost, or abandoned -914 fn remove(&mut self, packet: &SentPacket) { -915 self.bytes -= u64::from(packet.size); -916 self.ack_eliciting -= u64::from(packet.ack_eliciting); -917 } -918} -919 -920/// State for QUIC-MULTIPATH PATH_STATUS_AVAILABLE and PATH_STATUS_BACKUP frames -921#[derive(Debug, Clone, Default)] -922pub(super) struct PathStatusState { -923 /// The local status -924 local_status: PathStatus, -925 /// Local sequence number, for both PATH_STATUS_AVAILABLE and PATH_STATUS_BACKUP -926 /// -927 /// This is the number of the *next* path status frame to be sent. -928 local_seq: VarInt, -929 /// The status set by the remote -930 remote_status: Option<(VarInt, PathStatus)>, -931} -932 -933impl PathStatusState { -934 /// To be called on received PATH_STATUS_AVAILABLE/PATH_STATUS_BACKUP frames -935 pub(super) fn remote_update(&mut self, status: PathStatus, seq: VarInt) { -936 if self.remote_status.is_some_and(|(curr, _)| curr >= seq) { -937 return trace!(%seq, "ignoring path status update"); -938 } -939 -940 let prev = self.remote_status.replace((seq, status)).map(|(_, s)| s); -941 if prev != Some(status) { -942 debug!(?status, ?seq, "remote changed path status"); -943 } -944 } -945 -946 /// Updates the local status +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 } +899 } +900 +901 pub(crate) fn pop_off_path(&mut self, network_path: FourTuple) -> Option<(u64, FourTuple)> { +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 /// If the local status changed, the previous value is returned -949 pub(super) fn local_update(&mut self, status: PathStatus) -> Option<PathStatus> { -950 if self.local_status == status { -951 return None; -952 } -953 -954 self.local_seq = self.local_seq.saturating_add(1u8); -955 Some(std::mem::replace(&mut self.local_status, status)) -956 } -957 -958 pub(crate) fn seq(&self) -> VarInt { -959 self.local_seq -960 } -961} -962 -963/// The QUIC-MULTIPATH path status -964/// -965/// See section "3.3 Path Status Management": -966/// <https://quicwg.org/multipath/draft-ietf-quic-multipath.html#name-path-status-management> -967#[cfg_attr(test, derive(test_strategy::Arbitrary))] -968#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] -969pub enum PathStatus { -970 /// Paths marked with as available will be used when scheduling packets -971 /// -972 /// If multiple paths are available, packets will be scheduled on whichever has -973 /// capacity. -974 #[default] -975 Available, -976 /// Paths marked as backup will only be used if there are no available paths -977 /// -978 /// If the max_idle_timeout is specified the path will be kept alive so that it does not -979 /// expire. -980 Backup, -981} -982 -983/// Application events about paths -984#[derive(Debug, Clone, PartialEq, Eq)] -985pub enum PathEvent { -986 /// A new path has been opened -987 Opened { -988 /// Which path is now open -989 id: PathId, -990 }, -991 /// A path was abandoned and is no longer usable. -992 /// -993 /// This event will always be followed by [`Self::Discarded`] after some time. -994 Abandoned { -995 /// With path was abandoned. -996 id: PathId, -997 /// Reason why this path was abandoned. -998 reason: PathAbandonReason, -999 }, -1000 /// A path was discarded and all remaining state for it has been removed. -1001 /// -1002 /// This event is the last event for a path, and is always emitted after [`Self::Abandoned`]. -1003 Discarded { -1004 /// Which path had its state dropped -1005 id: PathId, -1006 /// The final path stats, they are no longer available via [`Connection::stats`] -1007 /// -1008 /// [`Connection::stats`]: super::Connection::stats -1009 path_stats: Box<PathStats>, -1010 }, -1011 /// The remote changed the status of the path -1012 /// -1013 /// The local status is not changed because of this event. It is up to the application -1014 /// to update the local status, which is used for packet scheduling, when the remote -1015 /// changes the status. -1016 RemoteStatus { -1017 /// Path which has changed status -1018 id: PathId, -1019 /// The new status set by the remote -1020 status: PathStatus, -1021 }, -1022 /// Received an observation of our external address from the peer. -1023 ObservedAddr { -1024 /// Path over which the observed address was reported, [`PathId::ZERO`] when multipath is -1025 /// not negotiated -1026 id: PathId, -1027 /// The address observed by the remote over this path -1028 addr: SocketAddr, -1029 }, -1030} -1031 -1032/// Reason for why a path was abandoned. -1033#[derive(Debug, Clone, Eq, PartialEq)] -1034pub enum PathAbandonReason { -1035 /// The path was closed locally by the application. -1036 ApplicationClosed { -1037 /// The error code to be sent with the abandon frame. -1038 error_code: VarInt, -1039 }, -1040 /// We didn't receive a path response in time after opening this path. -1041 ValidationFailed, -1042 /// We didn't receive any data from the remote within the path's idle timeout. -1043 TimedOut, -1044 /// The path became unusable after a local network change. -1045 UnusableAfterNetworkChange, -1046 /// The path was opened in a NAT traversal round which was terminated. -1047 NatTraversalRoundEnded, -1048 /// The remote closed the path. -1049 RemoteAbandoned { -1050 /// The error that was sent with the abandon frame. -1051 error_code: VarInt, -1052 }, -1053} -1054 -1055impl PathAbandonReason { -1056 /// Whether this abandon was initiated by the remote peer. -1057 pub(crate) fn is_remote(&self) -> bool { -1058 matches!(self, Self::RemoteAbandoned { .. }) -1059 } -1060 -1061 /// Returns the error code to send with a PATH_ABANDON frame. -1062 pub(crate) fn error_code(&self) -> TransportErrorCode { -1063 match self { -1064 Self::ApplicationClosed { error_code } => (*error_code).into(), -1065 Self::NatTraversalRoundEnded => TransportErrorCode::APPLICATION_ABANDON_PATH, -1066 Self::ValidationFailed | Self::TimedOut | Self::UnusableAfterNetworkChange => { -1067 TransportErrorCode::PATH_UNSTABLE_OR_POOR -1068 } -1069 Self::RemoteAbandoned { error_code } => (*error_code).into(), -1070 } -1071 } -1072} -1073 -1074/// Error from setting path status -1075#[derive(Debug, Error, Clone, PartialEq, Eq)] -1076pub enum SetPathStatusError { -1077 /// Error indicating that a path has not been opened or has already been abandoned -1078 #[error("closed path")] -1079 ClosedPath, -1080 /// Error indicating that this operation requires multipath to be negotiated whereas it hasn't been -1081 #[error("multipath not negotiated")] -1082 MultipathNotNegotiated, -1083} -1084 -1085/// Error indicating that a path has not been opened or has already been abandoned -1086#[derive(Debug, Default, Error, Clone, PartialEq, Eq)] -1087#[error("closed path")] -1088pub struct ClosedPath { -1089 pub(super) _private: (), -1090} -1091 -1092#[cfg(test)] -1093mod tests { -1094 use super::*; -1095 -1096 #[test] -1097 fn test_path_id_saturating_add() { -1098 // add within range behaves normally -1099 let large: PathId = u16::MAX.into(); -1100 let next = u32::from(u16::MAX) + 1; -1101 assert_eq!(large.saturating_add(1u8), PathId::from(next)); -1102 -1103 // outside range saturates -1104 assert_eq!(PathId::MAX.saturating_add(1u8), PathId::MAX) -1105 } -1106} \ No newline at end of file +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 } +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} +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`]. +1062 Discarded { +1063 /// Which path had its state dropped +1064 id: PathId, +1065 /// The final path stats, they are no longer available via [`Connection::stats`] +1066 /// +1067 /// [`Connection::stats`]: super::Connection::stats +1068 path_stats: Box<PathStats>, +1069 }, +1070 /// The remote changed the status of the path +1071 /// +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, +1107 /// The remote closed the path. +1108 RemoteAbandoned { +1109 /// The error that was sent with the abandon frame. +1110 error_code: VarInt, +1111 }, +1112} +1113 +1114impl PathAbandonReason { +1115 /// Whether this abandon was initiated by the remote peer. +1116 pub(crate) fn is_remote(&self) -> bool { +1117 matches!(self, Self::RemoteAbandoned { .. }) +1118 } +1119 +1120 /// Returns the error code to send with a PATH_ABANDON frame. +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 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 7698a578b..a4f47b22b 100644 --- a/pr/451/docs/src/noq_proto/connection/qlog.rs.html +++ b/pr/451/docs/src/noq_proto/connection/qlog.rs.html @@ -313,969 +313,985 @@ 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 }, -317 Timer::PerPath(_, path_timer) => match path_timer { -318 PathTimer::LossDetection => Some(QlogTimerType::LossTimeout.into()), -319 PathTimer::PathIdle => Some(TimerType::custom("path_idle")), -320 PathTimer::PathValidationFailed => Some(QlogTimerType::PathValidation.into()), -321 PathTimer::PathChallengeLost => Some(TimerType::custom("path_challenge_lost")), -322 PathTimer::PathOpenFailed => Some(TimerType::custom("path_open")), -323 PathTimer::PathKeepAlive => Some(TimerType::custom("path_keep_alive")), -324 PathTimer::Pacing => Some(TimerType::custom("pacing")), -325 PathTimer::MaxAckDelay => Some(QlogTimerType::Ack.into()), -326 PathTimer::PathDrained => Some(TimerType::custom("discard_path")), -327 }, -328 }; -329 -330 let Some(timer_type) = timer_type else { -331 return; -332 }; -333 -334 let delta = match op { -335 TimerOp::Set(instant) => instant -336 .checked_duration_since(now) -337 .map(|dur| dur.as_secs_f32() * 1000.), -338 _ => None, -339 }; -340 let path_id = match timer { -341 Timer::Conn(_) => None, -342 Timer::PerPath(path_id, _) => Some(path_id.as_u32() as u64), -343 }; -344 -345 let event_type = match op { -346 TimerOp::Set(_) => TimerEventType::Set, -347 TimerOp::Expire => TimerEventType::Expired, -348 TimerOp::Cancelled => TimerEventType::Cancelled, -349 }; -350 -351 let event = TimerUpdated { -352 path_id, -353 timer_type: Some(timer_type), -354 timer_id: None, -355 packet_number_space: None, -356 event_type, -357 delta, -358 }; -359 stream.emit_event(EventData::TimerUpdated(event), now); -360 } -361 -362 /// Returns a [`QlogSinkWithTime`] that passes along a `now` timestamp. -363 /// -364 /// This may be used if you want to pass a [`QlogSink`] downwards together with the current -365 /// `now` timestamp, to not have to pass the latter separately as an additional argument just -366 /// for qlog support. -367 pub(super) fn with_time(&self, now: Instant) -> QlogSinkWithTime<'_> { -368 #[cfg(feature = "qlog")] -369 let s = QlogSinkWithTime { sink: self, now }; -370 #[cfg(not(feature = "qlog"))] -371 let s = QlogSinkWithTime { -372 _phantom: PhantomData, -373 }; -374 s -375 } -376} -377 -378/// A [`QlogSink`] with a `now` timestamp. -379#[derive(Clone)] -380pub(super) struct QlogSinkWithTime<'a> { -381 #[cfg(feature = "qlog")] -382 sink: &'a QlogSink, -383 #[cfg(feature = "qlog")] -384 now: Instant, -385 #[cfg(not(feature = "qlog"))] -386 _phantom: PhantomData<&'a ()>, -387} -388 -389impl<'a> QlogSinkWithTime<'a> { -390 pub(super) fn emit_timer_stop(&self, timer: Timer) { -391 #[cfg(feature = "qlog")] -392 self.sink.emit_timer(timer, TimerOp::Cancelled, self.now) -393 } -394 -395 pub(super) fn emit_timer_set(&self, timer: Timer, expire_at: Instant) { +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 -398 .emit_timer(timer, TimerOp::Set(expire_at), self.now) -399 } -400 -401 pub(super) fn emit_timer_expire(&self, timer: Timer) { -402 #[cfg(feature = "qlog")] -403 self.sink.emit_timer(timer, TimerOp::Expire, self.now) +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 -407#[cfg(feature = "qlog")] -408enum TimerOp { -409 Set(Instant), -410 Expire, -411 Cancelled, -412} -413 -414/// Info about a sent packet. Zero-sized struct if `qlog` feature is not enabled. -415#[derive(Default)] -416pub(crate) struct QlogSentPacket { -417 #[cfg(feature = "qlog")] -418 inner: PacketSent, -419} -420 -421impl QlogSentPacket { -422 /// Sets data from the packet header. -423 pub(crate) fn header( -424 &mut self, -425 header: &Header, -426 pn: Option<u64>, -427 encryption_level: EncryptionLevel, -428 path_id: PathId, -429 ) { -430 #[cfg(feature = "qlog")] -431 { -432 self.inner.header.scid = header.src_cid().map(stringify_cid); -433 self.inner.header.dcid = Some(stringify_cid(header.dst_cid())); -434 self.inner.header.packet_number = pn; -435 self.inner.header.packet_type = encryption_level.into(); -436 self.inner.header.path_id = Some(path_id.as_u32() as u64); -437 } -438 } -439 -440 /// Adds a PADDING frame. -441 /// -442 /// This is a no-op if the `qlog` feature is not enabled. -443 pub(crate) fn frame_padding(&mut self, count: usize) { -444 #[cfg(feature = "qlog")] -445 self.frame_raw(QuicFrame::Padding { -446 raw: Some(RawInfo { -447 length: Some(count as u64), -448 payload_length: Some(count as u64), -449 data: None, -450 }), -451 }); -452 } -453 -454 /// Adds a frame by pushing a [`QuicFrame`]. -455 /// -456 /// This function is only available if the `qlog` feature is enabled, because constructing a [`QuicFrame`] may involve -457 /// calculations which shouldn't be performed if the `qlog` feature is disabled. -458 #[cfg(feature = "qlog")] -459 fn frame_raw(&mut self, frame: QuicFrame) { -460 self.inner.frames.get_or_insert_default().push(frame); -461 } -462 -463 /// Finalizes the packet by setting the final packet length (after encryption). -464 pub(super) fn finalize(&mut self, len: usize) { -465 #[cfg(feature = "qlog")] -466 { -467 self.inner.header.length = Some(len as u16); -468 } -469 } -470 -471 pub(crate) fn record<'a>(&mut self, frame: &EncodableFrame<'a>) { -472 #[cfg(feature = "qlog")] -473 self.frame_raw(frame.to_qlog()); +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 -477/// Info about a received packet. Zero-sized struct if `qlog` feature is not enabled. -478pub(crate) struct QlogRecvPacket { -479 #[cfg(feature = "qlog")] -480 inner: PacketReceived, -481 #[cfg(feature = "qlog")] -482 padding: usize, -483} -484 -485impl QlogRecvPacket { -486 /// Creates a new [`QlogRecvPacket`]. Noop if `qlog` feature is not enabled. -487 /// -488 /// `len` is the packet's full length (before decryption). -489 pub(crate) fn new(len: usize) -> Self { -490 #[cfg(not(feature = "qlog"))] -491 let this = Self {}; -492 -493 #[cfg(feature = "qlog")] -494 let this = { -495 let mut this = Self { -496 inner: Default::default(), -497 padding: 0, -498 }; -499 this.inner.header.length = Some(len as u16); -500 this -501 }; -502 -503 this -504 } -505 -506 /// Adds info from the packet header. -507 pub(crate) fn header(&mut self, header: &Header, pn: Option<u64>, path_id: PathId) { -508 #[cfg(feature = "qlog")] -509 { -510 let is_0rtt = !header.is_1rtt(); -511 self.inner.header.scid = header.src_cid().map(stringify_cid); -512 self.inner.header.dcid = Some(stringify_cid(header.dst_cid())); -513 self.inner.header.packet_number = pn; -514 self.inner.header.packet_type = packet_type(header.space(), is_0rtt); -515 self.inner.header.path_id = Some(path_id.as_u32() as u64); -516 } -517 } -518 -519 /// Adds a frame. -520 pub(crate) fn frame(&mut self, frame: &Frame) { -521 #[cfg(feature = "qlog")] -522 { -523 if matches!(frame, crate::Frame::Padding) { -524 self.padding += 1; -525 } else { -526 self.emit_padding(); -527 self.inner -528 .frames -529 .get_or_insert_default() -530 .push(frame.to_qlog()) -531 } -532 } -533 } -534 -535 #[cfg(feature = "qlog")] -536 fn emit_padding(&mut self) { -537 if self.padding > 0 { -538 self.inner -539 .frames -540 .get_or_insert_default() -541 .push(QuicFrame::Padding { -542 raw: Some(RawInfo { -543 length: Some(self.padding as u64), -544 payload_length: Some(self.padding as u64), -545 data: None, -546 }), -547 }); -548 self.padding = 0; -549 } -550 } -551} -552 -553/* Frame conversions to qlog */ -554#[cfg(feature = "qlog")] -555pub(crate) trait ToQlog { -556 fn to_qlog(&self) -> QuicFrame; -557} -558 -559#[cfg(feature = "qlog")] -560impl<'a> ToQlog for frame::AckEncoder<'a> { -561 fn to_qlog(&self) -> QuicFrame { -562 QuicFrame::Ack { -563 ack_delay: Some(self.delay as f32), -564 acked_ranges: Some(AckedRanges::Double( -565 self.ranges -566 .iter() -567 .map(|range| (range.start, range.end)) -568 .collect(), -569 )), -570 ect1: self.ecn.map(|e| e.ect1), -571 ect0: self.ecn.map(|e| e.ect0), -572 ce: self.ecn.map(|e| e.ce), -573 raw: None, -574 } -575 } -576} -577 -578#[cfg(feature = "qlog")] -579impl ToQlog for frame::AckFrequency { -580 fn to_qlog(&self) -> QuicFrame { -581 QuicFrame::AckFrequency { -582 sequence_number: self.sequence.into_inner(), -583 ack_eliciting_threshold: self.ack_eliciting_threshold.into_inner(), -584 requested_max_ack_delay: self.request_max_ack_delay.into_inner(), -585 reordering_threshold: self.reordering_threshold.into_inner(), -586 raw: None, -587 } -588 } -589} -590 -591#[cfg(feature = "qlog")] -592impl ToQlog for frame::AddAddress { -593 fn to_qlog(&self) -> QuicFrame { -594 QuicFrame::AddAddress { -595 sequence_number: self.seq_no.into_inner(), -596 ip_v4: match self.ip { -597 IpAddr::V4(ipv4_addr) => Some(ipv4_addr.to_string()), -598 IpAddr::V6(ipv6_addr) => None, -599 }, -600 ip_v6: match self.ip { -601 IpAddr::V4(ipv4_addr) => None, -602 IpAddr::V6(ipv6_addr) => Some(ipv6_addr.to_string()), -603 }, -604 port: self.port, -605 } -606 } -607} -608 -609#[cfg(feature = "qlog")] -610impl ToQlog for frame::CloseEncoder<'_> { -611 fn to_qlog(&self) -> QuicFrame { -612 self.close.to_qlog() -613 } -614} -615 -616#[cfg(feature = "qlog")] -617impl ToQlog for frame::Close { -618 fn to_qlog(&self) -> QuicFrame { -619 match self { -620 Self::Connection(f) => { -621 let (error, error_code) = transport_error(f.error_code); -622 let error = error.map(|transport_error| { -623 ConnectionClosedFrameError::TransportError(transport_error) -624 }); -625 QuicFrame::ConnectionClose { -626 error_space: Some(ErrorSpace::TransportError), -627 error, -628 error_code, -629 reason: String::from_utf8(f.reason.to_vec()).ok(), -630 reason_bytes: None, -631 trigger_frame_type: None, -632 } -633 } -634 Self::Application(f) => QuicFrame::ConnectionClose { -635 error_space: Some(ErrorSpace::ApplicationError), -636 error: None, -637 error_code: Some(f.error_code.into_inner()), -638 reason: String::from_utf8(f.reason.to_vec()).ok(), -639 reason_bytes: None, -640 trigger_frame_type: None, -641 }, -642 } -643 } -644} -645 -646#[cfg(feature = "qlog")] -647impl ToQlog for frame::Crypto { -648 fn to_qlog(&self) -> QuicFrame { -649 QuicFrame::Crypto { -650 offset: self.offset, -651 raw: Some(RawInfo { -652 length: Some(self.data.len() as u64), -653 ..Default::default() -654 }), -655 } -656 } -657} -658 -659#[cfg(feature = "qlog")] -660impl ToQlog for frame::Datagram { -661 fn to_qlog(&self) -> QuicFrame { -662 QuicFrame::Datagram { -663 raw: Some(RawInfo { -664 length: Some(self.data.len() as u64), -665 ..Default::default() -666 }), -667 } -668 } -669} -670 -671#[cfg(feature = "qlog")] -672impl ToQlog for frame::HandshakeDone { -673 fn to_qlog(&self) -> QuicFrame { -674 QuicFrame::HandshakeDone { raw: None } -675 } -676} -677 -678#[cfg(feature = "qlog")] -679impl ToQlog for frame::ImmediateAck { -680 fn to_qlog(&self) -> QuicFrame { -681 QuicFrame::ImmediateAck { raw: None } -682 } -683} -684 -685#[cfg(feature = "qlog")] -686impl ToQlog for frame::MaxData { -687 fn to_qlog(&self) -> QuicFrame { -688 QuicFrame::MaxData { -689 maximum: self.0.into(), -690 raw: None, -691 } -692 } -693} -694 -695#[cfg(feature = "qlog")] -696impl ToQlog for frame::MaxPathId { -697 fn to_qlog(&self) -> QuicFrame { -698 QuicFrame::MaxPathId { -699 maximum_path_id: self.0.as_u32().into(), -700 raw: None, -701 } -702 } -703} -704 -705#[cfg(feature = "qlog")] -706impl ToQlog for frame::MaxStreamData { -707 fn to_qlog(&self) -> QuicFrame { -708 QuicFrame::MaxStreamData { -709 stream_id: self.id.into(), -710 maximum: self.offset, -711 raw: None, -712 } -713 } -714} -715 -716#[cfg(feature = "qlog")] -717impl ToQlog for frame::MaxStreams { -718 fn to_qlog(&self) -> QuicFrame { -719 QuicFrame::MaxStreams { -720 maximum: self.count, -721 stream_type: self.dir.into(), -722 raw: None, -723 } -724 } -725} -726 -727#[cfg(feature = "qlog")] -728impl ToQlog for frame::NewConnectionId { -729 fn to_qlog(&self) -> QuicFrame { -730 match self.path_id { -731 None => QuicFrame::NewConnectionId { -732 sequence_number: self.sequence, -733 retire_prior_to: self.retire_prior_to, -734 connection_id_length: Some(self.id.len() as u8), -735 connection_id: self.id.to_string(), -736 stateless_reset_token: Some(self.reset_token.to_string()), -737 raw: None, -738 }, -739 Some(path_id) => QuicFrame::PathNewConnectionId { -740 path_id: path_id.0 as u64, -741 sequence_number: self.sequence, -742 retire_prior_to: self.retire_prior_to, -743 connection_id_length: Some(self.id.len() as u8), -744 connection_id: self.id.to_string(), -745 stateless_reset_token: Some(self.reset_token.to_string()), -746 raw: None, -747 }, -748 } -749 } -750} -751 -752#[cfg(feature = "qlog")] -753impl ToQlog for frame::NewToken { -754 fn to_qlog(&self) -> QuicFrame { -755 QuicFrame::NewToken { -756 token: qlog::Token { -757 ty: Some(TokenType::Retry), -758 raw: Some(RawInfo { -759 data: HexSlice::maybe_string(Some(&self.token)), -760 length: Some(self.token.len() as u64), -761 payload_length: None, -762 }), -763 details: None, -764 }, -765 raw: None, -766 } -767 } -768} -769 -770#[cfg(feature = "qlog")] -771impl ToQlog for frame::ObservedAddr { -772 fn to_qlog(&self) -> QuicFrame { -773 QuicFrame::ObservedAddress { -774 sequence_number: self.seq_no.into_inner(), -775 ip_v4: match self.ip { -776 IpAddr::V4(ipv4_addr) => Some(ipv4_addr.to_string()), -777 IpAddr::V6(ipv6_addr) => None, -778 }, -779 ip_v6: match self.ip { -780 IpAddr::V4(ipv4_addr) => None, -781 IpAddr::V6(ipv6_addr) => Some(ipv6_addr.to_string()), -782 }, -783 port: self.port, -784 raw: None, -785 } -786 } -787} -788 -789#[cfg(feature = "qlog")] -790impl ToQlog for frame::PathAbandon { -791 fn to_qlog(&self) -> QuicFrame { -792 QuicFrame::PathAbandon { -793 path_id: self.path_id.as_u32().into(), -794 error_code: self.error_code.into(), -795 raw: None, -796 } -797 } -798} -799 -800#[cfg(feature = "qlog")] -801impl ToQlog for frame::PathAckEncoder<'_> { -802 fn to_qlog(&self) -> QuicFrame { -803 QuicFrame::PathAck { -804 path_id: self.path_id.as_u32() as u64, -805 ack_delay: Some(self.delay as f32), -806 acked_ranges: Some(AckedRanges::Double( -807 self.ranges -808 .iter() -809 .map(|range| (range.start, range.end)) -810 .collect(), -811 )), -812 ect1: self.ecn.map(|e| e.ect1), -813 ect0: self.ecn.map(|e| e.ect0), -814 ce: self.ecn.map(|e| e.ce), -815 raw: None, -816 } -817 } -818} -819 -820#[cfg(feature = "qlog")] -821impl ToQlog for frame::PathChallenge { -822 #[cfg(feature = "qlog")] -823 fn to_qlog(&self) -> QuicFrame { -824 QuicFrame::PathChallenge { -825 data: Some(self.to_string()), -826 raw: None, -827 } -828 } -829} -830 -831#[cfg(feature = "qlog")] -832impl ToQlog for frame::PathCidsBlocked { -833 fn to_qlog(&self) -> QuicFrame { -834 QuicFrame::PathCidsBlocked { -835 path_id: self.path_id.as_u32().into(), -836 next_sequence_number: self.next_seq.into(), -837 raw: None, -838 } -839 } -840} -841 -842#[cfg(feature = "qlog")] -843impl ToQlog for frame::PathResponse { -844 fn to_qlog(&self) -> QuicFrame { -845 QuicFrame::PathResponse { -846 data: Some(self.to_string()), -847 raw: None, -848 } -849 } -850} -851 -852#[cfg(feature = "qlog")] -853impl ToQlog for frame::ReachOut { -854 fn to_qlog(&self) -> QuicFrame { -855 QuicFrame::ReachOut { -856 round: self.round.into_inner(), -857 ip_v4: match self.ip { -858 IpAddr::V4(ipv4_addr) => Some(ipv4_addr.to_string()), -859 IpAddr::V6(ipv6_addr) => None, -860 }, -861 ip_v6: match self.ip { -862 IpAddr::V4(ipv4_addr) => None, -863 IpAddr::V6(ipv6_addr) => Some(ipv6_addr.to_string()), -864 }, -865 port: self.port, -866 } -867 } -868} -869 -870#[cfg(feature = "qlog")] -871impl ToQlog for frame::Ping { -872 fn to_qlog(&self) -> QuicFrame { -873 QuicFrame::Ping { raw: None } -874 } -875} -876 -877#[cfg(feature = "qlog")] -878impl ToQlog for frame::PathStatusAvailable { -879 fn to_qlog(&self) -> QuicFrame { -880 QuicFrame::PathStatusAvailable { -881 path_id: self.path_id.as_u32().into(), -882 path_status_sequence_number: self.status_seq_no.into(), -883 raw: None, -884 } -885 } -886} -887 -888#[cfg(feature = "qlog")] -889impl ToQlog for frame::PathStatusBackup { -890 fn to_qlog(&self) -> QuicFrame { -891 QuicFrame::PathStatusBackup { -892 path_id: self.path_id.as_u32().into(), -893 path_status_sequence_number: self.status_seq_no.into(), -894 raw: None, -895 } -896 } -897} -898 -899#[cfg(feature = "qlog")] -900impl ToQlog for frame::PathsBlocked { -901 fn to_qlog(&self) -> QuicFrame { -902 QuicFrame::PathsBlocked { -903 maximum_path_id: self.0.as_u32().into(), -904 raw: None, -905 } -906 } -907} -908 -909#[cfg(feature = "qlog")] -910impl ToQlog for frame::ResetStream { -911 fn to_qlog(&self) -> QuicFrame { -912 QuicFrame::ResetStream { -913 stream_id: self.id.into(), -914 error_code: Some(self.error_code.into_inner()), -915 final_size: self.final_offset.into(), -916 error: ApplicationError::Unknown, -917 raw: None, -918 } -919 } -920} -921 -922#[cfg(feature = "qlog")] -923impl ToQlog for frame::StopSending { -924 fn to_qlog(&self) -> QuicFrame { -925 QuicFrame::StopSending { -926 stream_id: self.id.into(), -927 error_code: Some(self.error_code.into_inner()), -928 error: ApplicationError::Unknown, -929 raw: None, -930 } -931 } -932} -933 -934#[cfg(feature = "qlog")] -935impl ToQlog for frame::RetireConnectionId { -936 fn to_qlog(&self) -> QuicFrame { -937 match self.path_id { -938 None => QuicFrame::RetireConnectionId { -939 sequence_number: self.sequence, -940 raw: None, -941 }, -942 Some(path_id) => QuicFrame::PathRetireConnectionId { -943 path_id: path_id.0 as u64, -944 sequence_number: self.sequence, -945 raw: None, -946 }, -947 } -948 } -949} -950 -951#[cfg(feature = "qlog")] -952impl ToQlog for frame::RemoveAddress { -953 fn to_qlog(&self) -> QuicFrame { -954 QuicFrame::RemoveAddress { -955 sequence_number: self.seq_no.into_inner(), -956 } -957 } -958} -959 -960#[cfg(feature = "qlog")] -961impl ToQlog for frame::StreamMetaEncoder { -962 fn to_qlog(&self) -> QuicFrame { -963 let meta = &self.meta; -964 QuicFrame::Stream { -965 stream_id: meta.id.into(), -966 offset: Some(meta.offsets.start), -967 fin: Some(meta.fin), -968 raw: Some(RawInfo { -969 length: Some(meta.offsets.end - meta.offsets.start), -970 ..Default::default() -971 }), +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 Frame { -978 /// Converts a [`crate::Frame`] into a [`QuicFrame`]. -979 pub(crate) fn to_qlog(&self) -> QuicFrame { -980 match self { -981 Self::Padding => QuicFrame::Padding { -982 raw: Some(RawInfo { -983 length: None, -984 payload_length: Some(1), -985 data: None, -986 }), -987 }, -988 Self::Ping => frame::Ping.to_qlog(), -989 Self::Ack(f) => QuicFrame::Ack { -990 ack_delay: Some(f.delay as f32), -991 acked_ranges: Some(AckedRanges::Double( -992 f.iter().map(|range| (range.start, range.end)).collect(), -993 )), -994 ect1: f.ecn.as_ref().map(|e| e.ect1), -995 ect0: f.ecn.as_ref().map(|e| e.ect0), -996 ce: f.ecn.as_ref().map(|e| e.ce), -997 raw: None, -998 }, -999 Self::ResetStream(f) => f.to_qlog(), -1000 Self::StopSending(f) => f.to_qlog(), -1001 Self::Crypto(f) => f.to_qlog(), -1002 Self::NewToken(f) => f.to_qlog(), -1003 Self::Stream(s) => QuicFrame::Stream { -1004 stream_id: s.id.into(), -1005 offset: Some(s.offset), -1006 fin: Some(s.fin), -1007 raw: Some(RawInfo { -1008 length: Some(s.data.len() as u64), -1009 ..Default::default() -1010 }), -1011 }, -1012 Self::MaxData(v) => v.to_qlog(), -1013 Self::MaxStreamData(f) => f.to_qlog(), -1014 Self::MaxStreams(f) => f.to_qlog(), -1015 Self::DataBlocked(DataBlocked(offset)) => QuicFrame::DataBlocked { -1016 limit: *offset, -1017 raw: None, -1018 }, -1019 Self::StreamDataBlocked(StreamDataBlocked { id, offset }) => { -1020 QuicFrame::StreamDataBlocked { -1021 stream_id: (*id).into(), -1022 limit: *offset, -1023 raw: None, -1024 } -1025 } -1026 Self::StreamsBlocked(StreamsBlocked { dir, limit }) => QuicFrame::StreamsBlocked { -1027 stream_type: (*dir).into(), -1028 limit: *limit, -1029 raw: None, -1030 }, -1031 Self::NewConnectionId(f) => f.to_qlog(), -1032 Self::RetireConnectionId(f) => f.to_qlog(), -1033 Self::PathChallenge(f) => f.to_qlog(), -1034 Self::PathResponse(f) => f.to_qlog(), -1035 Self::Close(close) => close.to_qlog(), -1036 Self::Datagram(d) => d.to_qlog(), -1037 Self::HandshakeDone => frame::HandshakeDone.to_qlog(), -1038 Self::PathAck(ack) => QuicFrame::PathAck { -1039 path_id: ack.path_id.as_u32().into(), -1040 ack_delay: Some(ack.delay as f32), -1041 ect1: ack.ecn.as_ref().map(|e| e.ect1), -1042 ect0: ack.ecn.as_ref().map(|e| e.ect0), -1043 ce: ack.ecn.as_ref().map(|e| e.ce), -1044 raw: None, -1045 acked_ranges: Some(AckedRanges::Double( -1046 ack.ranges -1047 .iter() -1048 .map(|range| (range.start, range.end)) -1049 .collect(), -1050 )), -1051 }, -1052 Self::PathAbandon(frame) => frame.to_qlog(), -1053 Self::PathStatusAvailable(frame) => frame.to_qlog(), -1054 Self::PathStatusBackup(frame) => frame.to_qlog(), -1055 Self::PathsBlocked(frame) => frame.to_qlog(), -1056 Self::PathCidsBlocked(frame) => frame.to_qlog(), -1057 Self::MaxPathId(f) => f.to_qlog(), -1058 Self::AckFrequency(f) => f.to_qlog(), -1059 Self::ImmediateAck => frame::ImmediateAck.to_qlog(), -1060 Self::ObservedAddr(f) => f.to_qlog(), -1061 Self::AddAddress(f) => f.to_qlog(), -1062 Self::ReachOut(f) => f.to_qlog(), -1063 Self::RemoveAddress(f) => f.to_qlog(), -1064 } -1065 } -1066} -1067 -1068#[cfg(feature = "qlog")] -1069impl From<crate::Dir> for StreamType { -1070 fn from(value: crate::Dir) -> Self { -1071 match value { -1072 crate::Dir::Bi => Self::Bidirectional, -1073 crate::Dir::Uni => Self::Unidirectional, -1074 } -1075 } -1076} -1077 -1078#[cfg(feature = "qlog")] -1079fn packet_type(space: SpaceKind, is_0rtt: bool) -> PacketType { -1080 match space { -1081 SpaceKind::Initial => PacketType::Initial, -1082 SpaceKind::Handshake => PacketType::Handshake, -1083 SpaceKind::Data if is_0rtt => PacketType::ZeroRtt, -1084 SpaceKind::Data => PacketType::OneRtt, -1085 } -1086} -1087 -1088#[cfg(feature = "qlog")] -1089impl From<EncryptionLevel> for PacketType { -1090 fn from(encryption_level: EncryptionLevel) -> Self { -1091 match encryption_level { -1092 EncryptionLevel::Initial => Self::Initial, -1093 EncryptionLevel::Handshake => Self::Handshake, -1094 EncryptionLevel::ZeroRtt => Self::ZeroRtt, -1095 EncryptionLevel::OneRtt => Self::OneRtt, -1096 } -1097 } -1098} -1099 -1100#[cfg(feature = "qlog")] -1101fn stringify_cid(cid: ConnectionId) -> String { -1102 format!("{cid}") -1103} -1104 -1105#[cfg(feature = "qlog")] -1106fn tuple_endpoint_info( -1107 ip: Option<IpAddr>, -1108 port: Option<u16>, -1109 cid: Option<ConnectionId>, -1110) -> TupleEndpointInfo { -1111 let (ip_v4, port_v4, ip_v6, port_v6) = match ip { -1112 Some(addr) => match addr { -1113 IpAddr::V4(ipv4_addr) => (Some(ipv4_addr.to_string()), port, None, None), -1114 IpAddr::V6(ipv6_addr) => (None, None, Some(ipv6_addr.to_string()), port), -1115 }, -1116 None => (None, None, None, None), -1117 }; -1118 TupleEndpointInfo { -1119 ip_v4, -1120 port_v4, -1121 ip_v6, -1122 port_v6, -1123 connection_ids: cid.map(|cid| vec![cid.to_string()]), -1124 } -1125} -1126 -1127#[cfg(feature = "qlog")] -1128fn transport_error(code: TransportErrorCode) -> (Option<quic::TransportError>, Option<u64>) { -1129 let transport_error = match code { -1130 TransportErrorCode::NO_ERROR => Some(quic::TransportError::NoError), -1131 TransportErrorCode::INTERNAL_ERROR => Some(quic::TransportError::InternalError), -1132 TransportErrorCode::CONNECTION_REFUSED => Some(quic::TransportError::ConnectionRefused), -1133 TransportErrorCode::FLOW_CONTROL_ERROR => Some(quic::TransportError::FlowControlError), -1134 TransportErrorCode::STREAM_LIMIT_ERROR => Some(quic::TransportError::StreamLimitError), -1135 TransportErrorCode::STREAM_STATE_ERROR => Some(quic::TransportError::StreamStateError), -1136 TransportErrorCode::FINAL_SIZE_ERROR => Some(quic::TransportError::FinalSizeError), -1137 TransportErrorCode::FRAME_ENCODING_ERROR => Some(quic::TransportError::FrameEncodingError), -1138 TransportErrorCode::TRANSPORT_PARAMETER_ERROR => { -1139 Some(quic::TransportError::TransportParameterError) -1140 } -1141 TransportErrorCode::CONNECTION_ID_LIMIT_ERROR => { -1142 Some(quic::TransportError::ConnectionIdLimitError) -1143 } -1144 TransportErrorCode::PROTOCOL_VIOLATION => Some(quic::TransportError::ProtocolViolation), -1145 TransportErrorCode::INVALID_TOKEN => Some(quic::TransportError::InvalidToken), -1146 TransportErrorCode::APPLICATION_ERROR => Some(quic::TransportError::ApplicationError), -1147 TransportErrorCode::CRYPTO_BUFFER_EXCEEDED => { -1148 Some(quic::TransportError::CryptoBufferExceeded) -1149 } -1150 TransportErrorCode::KEY_UPDATE_ERROR => Some(quic::TransportError::KeyUpdateError), -1151 TransportErrorCode::AEAD_LIMIT_REACHED => Some(quic::TransportError::AeadLimitReached), -1152 TransportErrorCode::NO_VIABLE_PATH => Some(quic::TransportError::NoViablePath), -1153 // multipath -1154 TransportErrorCode::APPLICATION_ABANDON_PATH => { -1155 Some(quic::TransportError::ApplicationAbandonPath) +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::PATH_RESOURCE_LIMIT_REACHED => { -1158 Some(quic::TransportError::PathResourceLimitReached) +1157 TransportErrorCode::CONNECTION_ID_LIMIT_ERROR => { +1158 Some(quic::TransportError::ConnectionIdLimitError) 1159 } -1160 TransportErrorCode::PATH_UNSTABLE_OR_POOR => Some(quic::TransportError::PathUnstableOrPoor), -1161 TransportErrorCode::NO_CID_AVAILABLE_FOR_PATH => { -1162 Some(quic::TransportError::NoCidsAvailableForPath) -1163 } -1164 _ => None, -1165 }; -1166 let code = match transport_error { -1167 None => Some(code.into()), -1168 Some(_) => None, -1169 }; -1170 (transport_error, code) -1171} -1172 -1173#[cfg(feature = "qlog")] -1174fn fmt_tuple_id(path_id: u64) -> String { -1175 format!("p{path_id}") -1176} -1177 -1178#[cfg(feature = "qlog")] -1179impl TransportParameters { -1180 fn to_qlog(self, initiator: TransportInitiator) -> ParametersSet { -1181 ParametersSet { -1182 initiator: Some(initiator), -1183 resumption_allowed: None, -1184 early_data_enabled: None, -1185 tls_cipher: None, -1186 original_destination_connection_id: self -1187 .original_dst_cid -1188 .as_ref() -1189 .map(ToString::to_string), -1190 initial_source_connection_id: self.initial_src_cid.as_ref().map(ToString::to_string), -1191 retry_source_connection_id: self.retry_src_cid.as_ref().map(ToString::to_string), -1192 stateless_reset_token: self.stateless_reset_token.as_ref().map(ToString::to_string), -1193 disable_active_migration: Some(self.disable_active_migration), -1194 max_idle_timeout: Some(self.max_idle_timeout.into()), -1195 max_udp_payload_size: Some(self.max_udp_payload_size.into()), -1196 ack_delay_exponent: Some(self.ack_delay_exponent.into()), -1197 max_ack_delay: Some(self.max_ack_delay.into()), -1198 active_connection_id_limit: Some(self.active_connection_id_limit.into()), -1199 initial_max_data: Some(self.initial_max_data.into()), -1200 initial_max_stream_data_bidi_local: Some( -1201 self.initial_max_stream_data_bidi_local.into(), -1202 ), -1203 initial_max_stream_data_bidi_remote: Some( -1204 self.initial_max_stream_data_bidi_remote.into(), -1205 ), -1206 initial_max_stream_data_uni: Some(self.initial_max_stream_data_uni.into()), -1207 initial_max_streams_bidi: Some(self.initial_max_streams_bidi.into()), -1208 initial_max_streams_uni: Some(self.initial_max_streams_uni.into()), -1209 preferred_address: self.preferred_address.as_ref().map(Into::into), -1210 min_ack_delay: self.min_ack_delay.map(Into::into), -1211 address_discovery: self.address_discovery_role.to_qlog(), -1212 initial_max_path_id: self.initial_max_path_id.map(|p| p.as_u32() as u64), -1213 max_remote_nat_traversal_addresses: self -1214 .max_remote_nat_traversal_addresses -1215 .map(|v| u64::from(v.get())), -1216 max_datagram_frame_size: self.max_datagram_frame_size.map(Into::into), -1217 grease_quic_bit: Some(self.grease_quic_bit), -1218 unknown_parameters: Default::default(), -1219 } -1220 } -1221 -1222 fn to_qlog_restored(self) -> ParametersRestored { -1223 ParametersRestored { -1224 disable_active_migration: Some(self.disable_active_migration), -1225 max_idle_timeout: Some(self.max_idle_timeout.into()), -1226 max_udp_payload_size: Some(self.max_udp_payload_size.into()), -1227 active_connection_id_limit: Some(self.active_connection_id_limit.into()), -1228 initial_max_data: Some(self.initial_max_data.into()), -1229 initial_max_stream_data_bidi_local: Some( -1230 self.initial_max_stream_data_bidi_local.into(), -1231 ), -1232 initial_max_stream_data_bidi_remote: Some( -1233 self.initial_max_stream_data_bidi_remote.into(), -1234 ), -1235 initial_max_stream_data_uni: Some(self.initial_max_stream_data_uni.into()), -1236 initial_max_streams_bidi: Some(self.initial_max_streams_bidi.into()), -1237 initial_max_streams_uni: Some(self.initial_max_streams_uni.into()), -1238 max_datagram_frame_size: self.max_datagram_frame_size.map(Into::into), -1239 grease_quic_bit: Some(self.grease_quic_bit), -1240 } -1241 } -1242} -1243 -1244#[cfg(feature = "qlog")] -1245impl From<&crate::transport_parameters::PreferredAddress> for PreferredAddress { -1246 fn from(value: &crate::transport_parameters::PreferredAddress) -> Self { -1247 let port_v4 = value.address_v4.map(|addr| addr.port()).unwrap_or_default(); -1248 let port_v6 = value.address_v6.map(|addr| addr.port()).unwrap_or_default(); -1249 let ip_v4 = value -1250 .address_v4 -1251 .map(|addr| addr.ip().to_string()) -1252 .unwrap_or_default(); -1253 let ip_v6 = value -1254 .address_v6 -1255 .map(|addr| addr.ip().to_string()) -1256 .unwrap_or_default(); -1257 let connection_id = value.connection_id.to_string(); -1258 let stateless_reset_token = value.stateless_reset_token.to_string(); +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) +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(), +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(), +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 Self { -1261 ip_v4, -1262 ip_v6, -1263 port_v4, -1264 port_v6, -1265 connection_id, -1266 stateless_reset_token, -1267 } -1268 } -1269} -1270 -1271#[cfg(feature = "qlog")] -1272impl crate::address_discovery::Role { -1273 fn to_qlog(self) -> Option<AddressDiscoveryRole> { -1274 match self { -1275 Self::SendOnly => Some(AddressDiscoveryRole::SendOnly), -1276 Self::ReceiveOnly => Some(AddressDiscoveryRole::ReceiveOnly), -1277 Self::Both => Some(AddressDiscoveryRole::Both), -1278 Self::Disabled => None, -1279 } -1280 } -1281} \ No newline at end of file +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 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 4a693e87b..3f3a3fa55 100644 --- a/pr/451/docs/src/noq_proto/connection/spaces.rs.html +++ b/pr/451/docs/src/noq_proto/connection/spaces.rs.html @@ -2,1532 +2,1594 @@ 2 cmp, 3 collections::{BTreeMap, BTreeSet, VecDeque}, 4 mem, -5 net::IpAddr, -6 ops::{Bound, Index, IndexMut}, -7}; -8 -9use rand::{CryptoRng, RngExt}; -10use rustc_hash::{FxHashMap, FxHashSet}; -11use sorted_index_buffer::SortedIndexBuffer; -12use tracing::trace; -13 -14use super::PathId; -15use crate::{ -16 Dir, Duration, FourTuple, Instant, StreamId, TransportError, TransportErrorCode, VarInt, -17 connection::StreamsState, -18 frame::{self, AddAddress, RemoveAddress}, -19 packet::SpaceId, -20 range_set::ArrayRangeSet, -21 shared::IssuedCid, -22}; -23 -24pub(super) struct PacketSpace { -25 /// Data to send -26 pub(super) pending: Retransmits, -27 -28 /// Multipath packet number spaces -29 /// -30 /// Each [`PathId`] has it's own [`PacketNumberSpace`]. Only the [`SpaceId::Data`] can -31 /// have multiple packet number spaces, the other spaces only have a number space for -32 /// `PathId::ZERO`, which is populated at creation. -33 pub(super) number_spaces: BTreeMap<PathId, PacketNumberSpace>, -34} -35 -36impl PacketSpace { -37 pub(super) fn new(now: Instant, space: SpaceId, rng: &mut (impl CryptoRng + ?Sized)) -> Self { -38 let number_space_0 = PacketNumberSpace::new(now, space, rng); -39 Self { -40 pending: Retransmits::default(), -41 number_spaces: BTreeMap::from([(PathId::ZERO, number_space_0)]), -42 } -43 } -44 -45 #[cfg(test)] -46 pub(super) fn new_deterministic(now: Instant, space: SpaceId) -> Self { -47 let number_space_0 = PacketNumberSpace::new_deterministic(now, space); -48 Self { -49 pending: Retransmits::default(), -50 number_spaces: BTreeMap::from([(PathId::ZERO, number_space_0)]), -51 } -52 } -53 -54 /// Returns the [`PacketNumberSpace`] for a path -55 /// -56 /// When multipath is disabled use [`PathId::ZERO`]. -57 pub(super) fn path_space(&self, path_id: PathId) -> Option<&PacketNumberSpace> { -58 self.number_spaces.get(&path_id) -59 } -60 -61 /// Returns a mutable reference to the [`PacketNumberSpace`] for a path -62 /// -63 /// When multipath is disabled use [`PathId::ZERO`]. -64 pub(super) fn path_space_mut(&mut self, path_id: PathId) -> Option<&mut PacketNumberSpace> { -65 self.number_spaces.get_mut(&path_id) -66 } -67 -68 /// Returns the [`PacketNumberSpace`] for a path -69 /// -70 /// When multipath is disabled use `PathId::ZERO`. -71 // TODO(flub): Note that this only exists as `&mut self` because it creates a new -72 // [`PacketNumberSpace`] if one is not yet available for a path. This forces a few -73 // more `&mut` references to users than strictly needed. An alternative would be to -74 // return an Option but that would need to be handled for all callers. This could be -75 // worth exploring once we have all the main multipath bits fitted. -76 pub(super) fn for_path(&mut self, path: PathId) -> &mut PacketNumberSpace { -77 self.number_spaces -78 .get_mut(&path) -79 .unwrap_or_else(|| panic!("PacketNumberSpace missing for {path}")) -80 } -81 -82 pub(super) fn iter_paths_mut(&mut self) -> impl Iterator<Item = &mut PacketNumberSpace> { -83 self.number_spaces.values_mut() -84 } -85 -86 /// Queue data for a tail loss probe (or anti-amplification deadlock prevention) packet -87 /// -88 /// Does nothing if no tail loss probe needs to be sent. -89 /// -90 /// Probes are sent similarly to normal packets when an expected ACK has not arrived. We never -91 /// deem a packet lost until we receive an ACK that should have included it, but if a trailing -92 /// run of packets (or their ACKs) are lost, this might not happen in a timely fashion. We send -93 /// probe packets to force an ACK, and exempt them from congestion control to prevent a deadlock -94 /// when the congestion window is filled with lost tail packets. -95 /// -96 /// We prefer to send new data, to make the most efficient use of bandwidth. If there's no data -97 /// waiting to be sent, then we retransmit in-flight data to reduce odds of loss. If there's no -98 /// in-flight data either, we're probably a client guarding against a handshake -99 /// anti-amplification deadlock and we just make something up. -100 pub(super) fn maybe_queue_probe( -101 &mut self, -102 path_id: PathId, -103 request_immediate_ack: bool, -104 streams: &StreamsState, -105 ) { -106 if self.for_path(path_id).loss_probes == 0 { -107 return; -108 } -109 -110 if request_immediate_ack { -111 // The probe should be ACKed without delay (should only be used in the Data space and -112 // when the peer supports the acknowledgement frequency extension) -113 self.for_path(path_id).immediate_ack_pending = true; -114 } -115 -116 // Retransmit the data of the oldest in-flight packet -117 if !self.pending.is_empty(streams) { -118 // There's real data to send here, no need to make something up -119 return; -120 } -121 -122 // We use retransmits from any path. -123 for packet in self -124 .number_spaces -125 .values_mut() -126 .flat_map(|s| s.sent_packets.values_mut()) -127 { -128 if !packet.retransmits.is_empty(streams) { -129 // Remove retransmitted data from the old packet so we don't end up retransmitting -130 // it *again* even if the copy we're sending now gets acknowledged. -131 self.pending |= mem::take(&mut packet.retransmits); -132 return; -133 } +5 ops::{Bound, Index, IndexMut}, +6}; +7 +8use rand::{CryptoRng, RngExt}; +9use rustc_hash::{FxHashMap, FxHashSet}; +10use sorted_index_buffer::SortedIndexBuffer; +11use tracing::trace; +12 +13use super::PathId; +14use crate::{ +15 Dir, Duration, FourTuple, Instant, StreamId, TransportError, TransportErrorCode, VarInt, +16 connection::StreamsState, +17 frame::{self, AddAddress, RemoveAddress}, +18 packet::SpaceId, +19 range_set::ArrayRangeSet, +20 shared::IssuedCid, +21}; +22 +23pub(super) struct PacketSpace { +24 /// Data to send +25 pub(super) pending: Retransmits, +26 +27 /// Multipath packet number spaces +28 /// +29 /// Each [`PathId`] has it's own [`PacketNumberSpace`]. Only the [`SpaceId::Data`] can +30 /// have multiple packet number spaces, the other spaces only have a number space for +31 /// `PathId::ZERO`, which is populated at creation. +32 pub(super) number_spaces: BTreeMap<PathId, PacketNumberSpace>, +33} +34 +35impl PacketSpace { +36 pub(super) fn new(now: Instant, space: SpaceId, rng: &mut (impl CryptoRng + ?Sized)) -> Self { +37 let number_space_0 = PacketNumberSpace::new(now, space, rng); +38 Self { +39 pending: Retransmits::default(), +40 number_spaces: BTreeMap::from([(PathId::ZERO, number_space_0)]), +41 } +42 } +43 +44 #[cfg(test)] +45 pub(super) fn new_deterministic(now: Instant, space: SpaceId) -> Self { +46 let number_space_0 = PacketNumberSpace::new_deterministic(now, space); +47 Self { +48 pending: Retransmits::default(), +49 number_spaces: BTreeMap::from([(PathId::ZERO, number_space_0)]), +50 } +51 } +52 +53 /// Returns the [`PacketNumberSpace`] for a path +54 /// +55 /// When multipath is disabled use [`PathId::ZERO`]. +56 pub(super) fn path_space(&self, path_id: PathId) -> Option<&PacketNumberSpace> { +57 self.number_spaces.get(&path_id) +58 } +59 +60 /// Returns a mutable reference to the [`PacketNumberSpace`] for a path +61 /// +62 /// When multipath is disabled use [`PathId::ZERO`]. +63 pub(super) fn path_space_mut(&mut self, path_id: PathId) -> Option<&mut PacketNumberSpace> { +64 self.number_spaces.get_mut(&path_id) +65 } +66 +67 /// Returns the [`PacketNumberSpace`] for a path +68 /// +69 /// When multipath is disabled use `PathId::ZERO`. +70 // TODO(flub): Note that this only exists as `&mut self` because it creates a new +71 // [`PacketNumberSpace`] if one is not yet available for a path. This forces a few +72 // more `&mut` references to users than strictly needed. An alternative would be to +73 // return an Option but that would need to be handled for all callers. This could be +74 // worth exploring once we have all the main multipath bits fitted. +75 pub(super) fn for_path(&mut self, path: PathId) -> &mut PacketNumberSpace { +76 self.number_spaces +77 .get_mut(&path) +78 .unwrap_or_else(|| panic!("PacketNumberSpace missing for {path}")) +79 } +80 +81 pub(super) fn iter_paths_mut(&mut self) -> impl Iterator<Item = &mut PacketNumberSpace> { +82 self.number_spaces.values_mut() +83 } +84 +85 /// Queue data for a tail loss probe (or anti-amplification deadlock prevention) packet +86 /// +87 /// Probes are sent similarly to normal packets when an expected ACK has not arrived. We never +88 /// deem a packet lost until we receive an ACK that should have included it, but if a trailing +89 /// run of packets (or their ACKs) are lost, this might not happen in a timely fashion. We send +90 /// probe packets to force an ACK, and exempt them from congestion control to prevent a deadlock +91 /// when the congestion window is filled with lost tail packets. +92 /// +93 /// We prefer to send new data, to make the most efficient use of bandwidth. If there's no data +94 /// waiting to be sent, then we retransmit in-flight data to reduce odds of loss. If there's no +95 /// in-flight data either, we're probably a client guarding against a handshake +96 /// anti-amplification deadlock and we just make something up. +97 pub(super) fn queue_tail_loss_probe( +98 &mut self, +99 path_id: PathId, +100 request_immediate_ack: bool, +101 streams: &StreamsState, +102 ) { +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; +107 } +108 +109 // We prefer to send new data to make most efficient use of bandwidth. +110 if !self.pending.is_empty(streams) { +111 // There's real data to send here, no need to make something up +112 return; +113 } +114 +115 // Retransmit data from the oldest in-flight data from any path +116 for packet in self +117 .number_spaces +118 .values_mut() +119 .flat_map(|s| s.sent_packets.values_mut()) +120 { +121 if !packet.retransmits.is_empty(streams) { +122 // Remove retransmitted data from the old packet so we don't end up retransmitting +123 // it *again* even if the copy we're sending now gets acknowledged. +124 self.pending |= mem::take(&mut packet.retransmits); +125 return; +126 } +127 } +128 +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; 134 } -135 -136 // Nothing new to send and nothing to retransmit, so fall back on a ping. This should only -137 // happen in rare cases during the handshake when the server becomes blocked by -138 // anti-amplification. -139 if !self.for_path(path_id).immediate_ack_pending { -140 self.for_path(path_id).ping_pending = true; -141 } -142 } -143 -144 /// Whether there is anything to send in this space -145 /// -146 /// For the data space [`Connection::can_send_1rtt`] also needs to be consulted. Prefer -147 /// to use [`Connection::space_can_send`] which handles this. -148 /// -149 /// [`Connection::can_send_1rtt`]: super::Connection::can_send_1rtt -150 /// [`Connection::space_can_send`]: super::Connection::space_can_send -151 pub(super) fn can_send(&self, path_id: PathId, streams: &StreamsState) -> SendableFrames { -152 let acks = self -153 .number_spaces -154 .values() -155 .any(|pns| pns.pending_acks.can_send()); -156 let space_specific = self -157 .number_spaces -158 .get(&path_id) -159 .is_some_and(|s| s.ping_pending || s.immediate_ack_pending); -160 let other = !self.pending.is_empty(streams); -161 SendableFrames { -162 acks, -163 close: false, -164 space_specific, -165 other, -166 } +135 } +136 +137 /// Whether there is anything to send in this space +138 /// +139 /// For the data space [`Connection::can_send_1rtt`] also needs to be consulted. Prefer +140 /// to use [`Connection::space_can_send`] which handles this. +141 /// +142 /// [`Connection::can_send_1rtt`]: super::Connection::can_send_1rtt +143 /// [`Connection::space_can_send`]: super::Connection::space_can_send +144 pub(super) fn can_send(&self, path_id: PathId, streams: &StreamsState) -> SendableFrames { +145 let acks = self +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 Index<SpaceId> for [PacketSpace; 3] { -171 type Output = PacketSpace; -172 fn index(&self, space: SpaceId) -> &PacketSpace { -173 &self.as_ref()[space as usize] -174 } -175} -176 -177impl IndexMut<SpaceId> for [PacketSpace; 3] { -178 fn index_mut(&mut self, space: SpaceId) -> &mut PacketSpace { -179 &mut self.as_mut()[space as usize] -180 } -181} -182 -183/// The three QUIC packet number space kinds -184/// -185/// Unlike [`SpaceId`], this always has exactly three variants — it represents the -186/// encryption level / space kind, not a specific packet number space identity. -187#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -188pub(crate) enum SpaceKind { -189 /// Initial packets (client and server). -190 Initial = 0, -191 /// Handshake packets. -192 Handshake = 1, -193 /// Data (1-RTT and 0-RTT) -194 Data = 2, -195} -196 -197impl SpaceKind { -198 /// Returns the encryption level for this space kind. -199 pub(crate) fn encryption_level(self) -> super::EncryptionLevel { -200 match self { -201 Self::Initial => super::EncryptionLevel::Initial, -202 Self::Handshake => super::EncryptionLevel::Handshake, -203 Self::Data => super::EncryptionLevel::OneRtt, -204 } +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 Index<SpaceKind> for [PacketSpace; 3] { -209 type Output = PacketSpace; -210 fn index(&self, space: SpaceKind) -> &PacketSpace { -211 &self.as_ref()[space as usize] -212 } -213} -214 -215impl IndexMut<SpaceKind> for [PacketSpace; 3] { -216 fn index_mut(&mut self, space: SpaceKind) -> &mut PacketSpace { -217 &mut self.as_mut()[space as usize] -218 } -219} -220 -221/// The per-path packet number space to support multipath. -222/// -223/// This contains the data specific to a per-path packet number space. You should access -224/// this via [`PacketSpace::for_path`]. -225pub(super) struct PacketNumberSpace { -226 /// Highest received packet number, if any -227 pub(super) rx_packet: Option<u64>, -228 /// The packet number of the next packet that will be sent, if any. In the Data space, the -229 /// packet number stored here is sometimes skipped by [`PacketNumberFilter`] logic. -230 pub(super) next_packet_number: u64, -231 /// The largest packet number the remote peer acknowledged in an ACK frame. -232 pub(super) largest_acked_packet: Option<u64>, -233 pub(super) largest_acked_packet_sent: Instant, -234 /// The highest-numbered ACK-eliciting packet we've sent -235 pub(super) largest_ack_eliciting_sent: u64, -236 /// Number of packets in `sent_packets` with numbers above `largest_ack_eliciting_sent` -237 pub(super) unacked_non_ack_eliciting_tail: u64, -238 /// Transmitted but not acked -239 // We use a BTreeMap here so we can efficiently query by range on ACK and for loss detection -240 pub(super) sent_packets: SortedIndexBuffer<SentPacket>, -241 /// Packets that were deemed lost -242 // Older packets are regularly removed in `Connection::drain_lost_packets`. -243 pub(super) lost_packets: SortedIndexBuffer<LostPacket>, -244 /// Number of explicit congestion notification codepoints seen on incoming packets -245 pub(super) ecn_counters: frame::EcnCounts, -246 /// Recent ECN counters sent by the peer in ACK frames -247 /// -248 /// Updated (and inspected) whenever we receive an ACK with a new highest acked packet -249 /// number. Stored per-space to simplify verification, which would otherwise have difficulty -250 /// distinguishing between ECN bleaching and counts having been updated by a near-simultaneous -251 /// ACK already processed in another space. -252 pub(super) ecn_feedback: frame::EcnCounts, -253 /// A PING frame needs to be sent on this path -254 pub(super) ping_pending: bool, -255 /// An IMMEDIATE_ACK (draft-ietf-quic-ack-frequency) frame needs to be sent on this path -256 pub(super) immediate_ack_pending: bool, -257 /// Packet deduplicator -258 pub(super) dedup: Dedup, -259 /// Packet numbers to acknowledge -260 pub(super) pending_acks: PendingAcks, -261 -262 // -263 // Loss Detection -264 // -265 /// The time the most recently sent retransmittable packet was sent. -266 pub(super) time_of_last_ack_eliciting_packet: Option<Instant>, -267 /// Earliest time when we might declare a packet lost. -268 /// -269 /// The time at which the earliest sent packet in this space will be considered lost -270 /// based on exceeding the reordering window in time. Only set for packets numbered -271 /// prior to a packet that has been acknowledged. -272 pub(super) loss_time: Option<Instant>, -273 /// Number of tail loss probes to send -274 pub(super) loss_probes: u32, -275 -276 /// Packet numbers to skip, only used in the data package space. -277 pn_filter: Option<PacketNumberFilter>, -278} -279 -280impl PacketNumberSpace { -281 pub(super) fn new(now: Instant, space: SpaceId, rng: &mut (impl CryptoRng + ?Sized)) -> Self { -282 let pn_filter = match space { -283 SpaceId::Initial | SpaceId::Handshake => None, -284 SpaceId::Data => Some(PacketNumberFilter::new(rng)), -285 }; -286 Self { -287 rx_packet: None, -288 next_packet_number: 0, -289 largest_acked_packet: None, -290 largest_acked_packet_sent: now, -291 largest_ack_eliciting_sent: 0, -292 unacked_non_ack_eliciting_tail: 0, -293 sent_packets: SortedIndexBuffer::new(), -294 lost_packets: SortedIndexBuffer::new(), -295 ecn_counters: frame::EcnCounts::ZERO, -296 ecn_feedback: frame::EcnCounts::ZERO, -297 ping_pending: false, -298 immediate_ack_pending: false, -299 dedup: Default::default(), -300 pending_acks: PendingAcks::new(), -301 time_of_last_ack_eliciting_packet: None, -302 loss_time: None, -303 loss_probes: 0, -304 pn_filter, -305 } -306 } -307 -308 #[cfg(test)] -309 fn new_deterministic(now: Instant, space: SpaceId) -> Self { -310 let pn_filter = match space { -311 SpaceId::Initial | SpaceId::Handshake => None, -312 SpaceId::Data => Some(PacketNumberFilter::disabled()), -313 }; -314 Self { -315 rx_packet: None, -316 next_packet_number: 0, -317 largest_acked_packet: None, -318 largest_acked_packet_sent: now, -319 largest_ack_eliciting_sent: 0, -320 unacked_non_ack_eliciting_tail: 0, -321 sent_packets: SortedIndexBuffer::new(), -322 lost_packets: SortedIndexBuffer::new(), -323 ecn_counters: frame::EcnCounts::ZERO, -324 ecn_feedback: frame::EcnCounts::ZERO, -325 ping_pending: false, -326 immediate_ack_pending: false, -327 dedup: Default::default(), -328 pending_acks: PendingAcks::new(), -329 time_of_last_ack_eliciting_packet: None, -330 loss_time: None, -331 loss_probes: 0, -332 pn_filter, -333 } -334 } -335 -336 /// Get the next outgoing packet number in this space -337 /// -338 /// In the Data space, the connection's [`PacketNumberFilter`] must be used rather than calling -339 /// this directly. -340 pub(super) fn get_tx_number(&mut self, rng: &mut (impl CryptoRng + ?Sized)) -> u64 { -341 // TODO: Handle packet number overflow gracefully -342 assert!(self.next_packet_number < 2u64.pow(62)); -343 let mut pn = self.next_packet_number; -344 self.next_packet_number += 1; -345 -346 // Skip this number if the filter says so, only enabled in the data space -347 if let Some(ref mut filter) = self.pn_filter -348 && filter.skip_pn(pn, rng) -349 { -350 pn = self.next_packet_number; -351 self.next_packet_number += 1; -352 } -353 pn -354 } -355 -356 pub(super) fn peek_tx_number(&mut self) -> u64 { -357 let pn = self.next_packet_number; -358 if let Some(ref filter) = self.pn_filter -359 && pn == filter.next_skipped_packet_number -360 { -361 return pn + 1; -362 } -363 pn -364 } -365 -366 /// Checks whether a skipped packet number was ACKed. -367 pub(super) fn check_ack(&self, range: std::ops::Range<u64>) -> Result<(), TransportError> { -368 if let Some(ref filter) = self.pn_filter -369 && filter -370 .prev_skipped_packet_number -371 .is_some_and(|pn| range.contains(&pn)) -372 { -373 return Err(TransportError::PROTOCOL_VIOLATION("unsent packet acked")); -374 } -375 Ok(()) -376 } -377 -378 /// Verifies sanity of an ECN block and returns whether congestion was encountered. -379 pub(super) fn detect_ecn( -380 &mut self, -381 newly_acked: u64, -382 ecn: frame::EcnCounts, -383 ) -> Result<bool, &'static str> { -384 let ect0_increase = ecn -385 .ect0 -386 .checked_sub(self.ecn_feedback.ect0) -387 .ok_or("peer ECT(0) count regression")?; -388 let ect1_increase = ecn -389 .ect1 -390 .checked_sub(self.ecn_feedback.ect1) -391 .ok_or("peer ECT(1) count regression")?; -392 let ce_increase = ecn -393 .ce -394 .checked_sub(self.ecn_feedback.ce) -395 .ok_or("peer CE count regression")?; -396 let total_increase = ect0_increase + ect1_increase + ce_increase; -397 if total_increase < newly_acked { -398 return Err("ECN bleaching"); -399 } -400 if (ect0_increase + ce_increase) < newly_acked || ect1_increase != 0 { -401 return Err("ECN corruption"); -402 } -403 // If total_increase > newly_acked (which happens when ACKs are lost), this is required by -404 // the draft so that long-term drift does not occur. If =, then the only question is whether -405 // to count CE packets as CE or ECT0. Recording them as CE is more consistent and keeps the -406 // congestion check obvious. -407 self.ecn_feedback = ecn; -408 Ok(ce_increase != 0) -409 } -410 -411 /// Stop tracking sent packet `number`, and return what we knew about it -412 pub(super) fn take(&mut self, number: u64) -> Option<SentPacket> { -413 let packet = self.sent_packets.remove(number)?; -414 if !packet.ack_eliciting && number > self.largest_ack_eliciting_sent { -415 self.unacked_non_ack_eliciting_tail = -416 self.unacked_non_ack_eliciting_tail.checked_sub(1).unwrap(); -417 } -418 Some(packet) -419 } -420 -421 /// May return a packet that should be forgotten -422 pub(super) fn sent(&mut self, number: u64, packet: SentPacket) -> Option<SentPacket> { -423 // Retain state for at most this many non-ACK-eliciting packets sent after the most recently -424 // sent ACK-eliciting packet. We're never guaranteed to receive an ACK for those, and we -425 // can't judge them as lost without an ACK, so to limit memory in applications which receive -426 // packets but don't send ACK-eliciting data for long periods use we must eventually start -427 // forgetting about them, although it might also be reasonable to just kill the connection -428 // due to weird peer behavior. -429 const MAX_UNACKED_NON_ACK_ELICTING_TAIL: u64 = 1_000; -430 -431 let mut forgotten = None; -432 if packet.ack_eliciting { -433 self.unacked_non_ack_eliciting_tail = 0; -434 self.largest_ack_eliciting_sent = number; -435 } else if self.unacked_non_ack_eliciting_tail > MAX_UNACKED_NON_ACK_ELICTING_TAIL { -436 let oldest_after_ack_eliciting = self -437 .sent_packets -438 .keys_range(( -439 Bound::Excluded(self.largest_ack_eliciting_sent), -440 Bound::Unbounded, -441 )) -442 .next() -443 .unwrap(); -444 // Per https://www.rfc-editor.org/rfc/rfc9000.html#name-frames-and-frame-types, -445 // non-ACK-eliciting packets must only contain PADDING, ACK, and CONNECTION_CLOSE -446 // frames, which require no special handling on ACK or loss beyond removal from -447 // in-flight counters if padded. -448 let packet = self -449 .sent_packets -450 .remove(oldest_after_ack_eliciting) -451 .unwrap(); -452 debug_assert!(!packet.ack_eliciting); -453 forgotten = Some(packet); -454 } else { -455 self.unacked_non_ack_eliciting_tail += 1; -456 } -457 -458 self.sent_packets.insert(number, packet); -459 forgotten -460 } -461 -462 /// Whether any congestion-controlled packets in this space are not yet acknowledged or lost -463 pub(super) fn has_in_flight(&self) -> bool { -464 // The number of non-congestion-controlled (i.e. size == 0) packets in flight at a time -465 // should be small, since otherwise congestion control wouldn't be effective. Therefore, -466 // this shouldn't need to visit many packets before finishing one way or another. -467 self.sent_packets.values().any(|x| x.size != 0) -468 } -469} -470 -471/// Represents one or more packets subject to retransmission -472#[derive(Debug, Clone)] -473pub(super) struct SentPacket { -474 /// [`PathData::generation`](super::PathData::generation) of the path on which this packet was sent -475 pub(super) path_generation: u64, -476 /// The time the packet was sent. -477 pub(super) time_sent: Instant, -478 /// The number of bytes sent in the packet, not including UDP or IP overhead, but including QUIC -479 /// framing overhead. Zero if this packet is not counted towards congestion control, i.e. not an -480 /// "in flight" packet. -481 pub(super) size: u16, -482 /// Whether an acknowledgement is expected directly in response to this packet. -483 pub(super) ack_eliciting: bool, -484 /// The largest packet number acknowledged by this packet -485 pub(super) largest_acked: FxHashMap<PathId, u64>, -486 /// Data which needs to be retransmitted in case the packet is lost. -487 /// The data is boxed to minimize `SentPacket` size for the typical case of -488 /// packets only containing ACKs and STREAM frames. -489 pub(super) retransmits: ThinRetransmits, -490 /// Metadata for stream frames in a packet -491 /// -492 /// The actual application data is stored with the stream state. -493 pub(super) stream_frames: frame::StreamMetaVec, -494} -495 -496/// Represents one or more packets that are deemed lost. -497#[derive(Debug)] -498pub(super) struct LostPacket { -499 /// The time the packet was sent. -500 pub(super) time_sent: Instant, -501} -502 -503/// Retransmittable data queue -504#[allow(unreachable_pub)] // fuzzing only -505#[derive(Debug, Default, Clone)] -506pub struct Retransmits { -507 pub(super) max_data: bool, -508 pub(super) max_stream_id: [bool; 2], -509 pub(super) reset_stream: Vec<(StreamId, VarInt)>, -510 pub(super) stop_sending: Vec<frame::StopSending>, -511 pub(super) max_stream_data: FxHashSet<StreamId>, -512 pub(super) crypto: VecDeque<frame::Crypto>, -513 pub(super) new_cids: PendingNewCids, -514 pub(super) retire_cids: Vec<(PathId, u64)>, -515 pub(super) ack_frequency: bool, -516 pub(super) handshake_done: bool, -517 pub(super) observed_addr: bool, -518 /// Whether we should inform the peer we will allow higher [`PathId`]s. -519 pub(super) max_path_id: bool, -520 /// Whether we should inform the peer that their max [`PathId`] is blocking our attempt to open -521 /// new paths. -522 // TODO(@divma): we need logic to prevent sending this more than once after being ack-d once -523 pub(super) paths_blocked: bool, -524 /// For each enqueued NEW_TOKEN frame, a copy of the path's remote address -525 /// -526 /// There are 2 reasons this is unusual: -527 /// -528 /// - If the path changes, NEW_TOKEN frames bound for the old path are not retransmitted on the -529 /// new path. That is why this field stores the remote address: so that ones for old paths -530 /// can be filtered out. -531 /// - If a token is lost, a new randomly generated token is re-transmitted, rather than the -532 /// original. This is so that if both transmissions are received, the client won't risk -533 /// sending the same token twice. That is why this field does _not_ store any actual token. -534 /// -535 /// It is true that a QUIC endpoint will only want to effectively have NEW_TOKEN frames -536 /// enqueued for its current path at a given point in time. Based on that, we could conceivably -537 /// change this from a vector to an `Option<(FourTuple, usize)>` or just a `usize` or -538 /// something. However, due to the architecture of noq, it is considerably simpler to not do -539 /// that; consider what such a change would mean for implementing `BitOrAssign` on Self. -540 pub(super) new_tokens: Vec<FourTuple>, -541 /// Paths which need to be abandoned -542 pub(super) path_abandon: BTreeMap<PathId, TransportErrorCode>, -543 /// If a [`frame::PathStatusAvailable`] and [`frame::PathStatusBackup`] need to be sent for a path -544 pub(super) path_status: BTreeSet<PathId>, -545 /// If a PATH_CIDS_BLOCKED frame needs to be sent for a path -546 pub(super) path_cids_blocked: BTreeSet<PathId>, -547 -548 // Nat traversal data -549 /// Addresses to report in `ADD_ADDRESS` frames -550 pub(super) add_address: BTreeSet<AddAddress>, -551 /// Address IDs to remove in `REMOVE_ADDRESS` frames -552 pub(super) remove_address: BTreeSet<RemoveAddress>, -553 /// Round and local addresses to advertise in `REACH_OUT` frames -554 pub(super) reach_out: Option<(VarInt, FxHashSet<(IpAddr, u16)>)>, -555} -556 -557impl Retransmits { -558 pub(super) fn is_empty(&self, streams: &StreamsState) -> bool { -559 let Self { -560 max_data, -561 max_stream_id, -562 reset_stream, -563 stop_sending, -564 max_stream_data, -565 crypto, -566 new_cids, -567 retire_cids, -568 ack_frequency, -569 handshake_done, -570 observed_addr, -571 max_path_id, -572 paths_blocked, -573 new_tokens, -574 path_abandon, -575 path_status, -576 path_cids_blocked, -577 add_address, -578 remove_address, -579 reach_out, -580 } = &self; -581 !max_data -582 && !max_stream_id.iter().any(|x| *x) -583 && reset_stream.is_empty() -584 && stop_sending.is_empty() -585 && max_stream_data -586 .iter() -587 .all(|&id| !streams.can_send_flow_control(id)) -588 && crypto.is_empty() -589 && new_cids.is_empty() -590 && retire_cids.is_empty() -591 && !ack_frequency -592 && !handshake_done -593 && !observed_addr -594 && !max_path_id -595 && !paths_blocked -596 && new_tokens.is_empty() -597 && path_abandon.is_empty() -598 && path_status.is_empty() -599 && path_cids_blocked.is_empty() -600 && add_address.is_empty() -601 && remove_address.is_empty() -602 && reach_out.is_none() -603 } -604} -605 -606impl ::std::ops::BitOrAssign for Retransmits { -607 fn bitor_assign(&mut self, rhs: Self) { -608 let Self { -609 max_data, -610 max_stream_id, -611 reset_stream, -612 stop_sending, -613 max_stream_data, -614 crypto, -615 new_cids, -616 retire_cids, -617 ack_frequency, -618 handshake_done, -619 observed_addr, -620 max_path_id, -621 paths_blocked, -622 new_tokens, -623 mut path_abandon, -624 mut path_status, -625 mut path_cids_blocked, -626 add_address, -627 remove_address, -628 reach_out, -629 } = rhs; -630 -631 // We reduce in-stream head-of-line blocking by queueing retransmits before other data for -632 // STREAM and CRYPTO frames. -633 self.max_data |= max_data; -634 for dir in Dir::iter() { -635 self.max_stream_id[dir as usize] |= max_stream_id[dir as usize]; -636 } -637 self.reset_stream.extend_from_slice(&reset_stream); -638 self.stop_sending.extend_from_slice(&stop_sending); -639 self.max_stream_data.extend(&max_stream_data); -640 for crypto in crypto.into_iter().rev() { -641 self.crypto.push_front(crypto); -642 } -643 self.new_cids.extend(&new_cids); -644 self.retire_cids.extend(retire_cids); -645 self.ack_frequency |= ack_frequency; -646 self.handshake_done |= handshake_done; -647 self.observed_addr |= observed_addr; -648 self.max_path_id |= max_path_id; -649 self.paths_blocked |= paths_blocked; -650 self.new_tokens.extend_from_slice(&new_tokens); -651 self.path_abandon.append(&mut path_abandon); -652 self.path_status.append(&mut path_status); -653 self.path_cids_blocked.append(&mut path_cids_blocked); -654 self.add_address.extend(add_address.iter().copied()); -655 self.remove_address.extend(remove_address.iter().copied()); -656 if let Some((rhs_round, rhs_addrs)) = reach_out { -657 match self.reach_out.as_mut() { -658 // Use RHS if there is no recorded round. -659 None => self.reach_out = Some((rhs_round, rhs_addrs)), -660 // Use RHS if newer. -661 Some((lhs_round, _lhs_addrs)) if rhs_round > *lhs_round => { -662 self.reach_out = Some((rhs_round, rhs_addrs)); -663 } -664 // If both rounds are the same, merge them. -665 Some((lhs_round, lhs_addrs)) if rhs_round == *lhs_round => { -666 lhs_addrs.extend(rhs_addrs); -667 } -668 // LHS round is newer, ignore RHS -669 Some(_) => {} -670 } -671 } -672 } -673} -674 -675impl ::std::ops::BitOrAssign<ThinRetransmits> for Retransmits { -676 fn bitor_assign(&mut self, rhs: ThinRetransmits) { -677 let ThinRetransmits { retransmits } = rhs; -678 if let Some(retransmits) = retransmits { -679 self.bitor_assign(*retransmits) -680 } -681 } -682} -683 -684impl ::std::iter::FromIterator<Self> for Retransmits { -685 fn from_iter<T>(iter: T) -> Self -686 where -687 T: IntoIterator<Item = Self>, -688 { -689 let mut result = Self::default(); -690 for packet in iter { -691 result |= packet; -692 } -693 result -694 } -695} -696 -697/// The queue of new CIDs to be transmitted to the peer. -698/// -699/// This queue is always sorted, so that popping off the last item is always the lowest -700/// sequence number of the lowest path ID. Which is the CID you want to be issued next. -701/// -702/// This is but a newtype over a `Vec` to enforce the sorted invariant. -703#[derive(Clone, Debug, Default)] -704pub(super) struct PendingNewCids { -705 /// The CIDs themselves. -706 cids: Vec<IssuedCid>, -707 /// Whether [`Self::cids`] is sorted or not. -708 sorted: bool, -709} -710 -711impl PendingNewCids { -712 /// Inserts an issued CID into the queue. -713 pub(super) fn push(&mut self, cid: IssuedCid) { -714 self.cids.push(cid); -715 self.sorted = false; -716 } -717 -718 /// Pops the next issued CID to transmit from the queue. -719 pub(super) fn pop(&mut self) -> Option<IssuedCid> { -720 if !std::mem::replace(&mut self.sorted, true) { -721 self.cids -722 .sort_by_key(|cid| cmp::Reverse((cid.path_id, cid.sequence))); -723 } -724 self.cids.pop() -725 } -726 -727 pub(super) fn is_empty(&self) -> bool { -728 self.cids.is_empty() +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 +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; +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; 729 } 730 -731 pub(super) fn extend(&mut self, other: &Self) { -732 self.cids.extend(&other.cids); -733 self.sorted = false; -734 } -735 -736 pub(super) fn retain<F>(&mut self, f: F) -737 where -738 F: FnMut(&IssuedCid) -> bool, -739 { -740 self.cids.retain(f); -741 } -742} -743 -744/// A variant of `Retransmits` which only allocates storage when required +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(super) struct ThinRetransmits { -747 retransmits: Option<Box<Retransmits>>, -748} -749 -750impl ThinRetransmits { -751 /// Returns `true` if no retransmits are necessary -752 pub(super) fn is_empty(&self, streams: &StreamsState) -> bool { -753 match &self.retransmits { -754 Some(retransmits) => retransmits.is_empty(streams), -755 None => true, -756 } -757 } -758 -759 /// Returns a reference to the retransmits stored in this box -760 pub(super) fn get(&self) -> Option<&Retransmits> { -761 self.retransmits.as_deref() -762 } -763 -764 /// Returns a mutable reference to the retransmits stored in this box -765 pub(super) fn get_mut(&mut self) -> Option<&mut Retransmits> { -766 self.retransmits.as_deref_mut() -767 } -768 -769 /// Returns a mutable reference to the stored retransmits -770 /// -771 /// This function will allocate a backing storage if required. -772 pub(super) fn get_or_create(&mut self) -> &mut Retransmits { -773 if self.retransmits.is_none() { -774 self.retransmits = Some(Box::default()); -775 } -776 self.retransmits.as_deref_mut().unwrap() -777 } -778} -779 -780/// RFC4303-style sliding window packet number deduplicator. -781/// -782/// A contiguous bitfield, where each bit corresponds to a packet number and the rightmost bit is -783/// always set. A set bit represents a packet that has been successfully authenticated. Bits left of -784/// the window are assumed to be set. -785/// -786/// ```text -787/// ...xxxxxxxxx 1 0 -788/// ^ ^ ^ -789/// window highest next -790/// ``` -791#[derive(Debug, Default)] -792pub(super) struct Dedup { -793 window: Window, -794 /// Lowest packet number higher than all yet authenticated. -795 next: u64, -796} -797 -798/// Inner bitfield type. -799/// -800/// Because QUIC never reuses packet numbers, this only needs to be large enough to deal with -801/// packets that are reordered but still delivered in a timely manner. -802type Window = u128; -803 -804/// Number of packets tracked by `Dedup`. -805const WINDOW_SIZE: u64 = 1 + mem::size_of::<Window>() as u64 * 8; -806 -807impl Dedup { -808 /// Construct an empty window positioned at the start. -809 #[cfg(test)] -810 pub(super) fn new() -> Self { -811 Self { window: 0, next: 0 } -812 } -813 -814 /// Highest packet number authenticated. -815 fn highest(&self) -> u64 { -816 self.next - 1 -817 } -818 -819 /// Record a newly authenticated packet number. -820 /// -821 /// Returns whether the packet might be a duplicate. -822 pub(super) fn insert(&mut self, packet: u64) -> bool { -823 if let Some(diff) = packet.checked_sub(self.next) { -824 // Right of window -825 self.window = ((self.window << 1) | 1) -826 .checked_shl(cmp::min(diff, u64::from(u32::MAX)) as u32) -827 .unwrap_or(0); -828 self.next = packet + 1; -829 false -830 } else if self.highest() - packet < WINDOW_SIZE { -831 // Within window -832 if let Some(bit) = (self.highest() - packet).checked_sub(1) { -833 // < highest -834 let mask = 1 << bit; -835 let duplicate = self.window & mask != 0; -836 self.window |= mask; -837 duplicate -838 } else { -839 // == highest -840 true -841 } -842 } else { -843 // Left of window -844 true -845 } -846 } -847 -848 /// Returns the packet number of the smallest packet missing between the provided interval -849 /// -850 /// If there are no missing packets, returns `None` -851 fn smallest_missing_in_interval(&self, lower_bound: u64, upper_bound: u64) -> Option<u64> { -852 debug_assert!(lower_bound <= upper_bound); -853 debug_assert!(upper_bound <= self.highest()); -854 const BITFIELD_SIZE: u64 = (mem::size_of::<Window>() * 8) as u64; -855 -856 // Since we already know the packets at the boundaries have been received, we only need to -857 // check those in between them (this removes the necessity of extra logic to deal with the -858 // highest packet, which is stored outside the bitfield) -859 let lower_bound = lower_bound + 1; -860 let upper_bound = upper_bound.saturating_sub(1); -861 -862 // Note: the offsets are counted from the right -863 // The highest packet is not included in the bitfield, so we subtract 1 to account for that -864 let start_offset = (self.highest() - upper_bound).max(1) - 1; -865 if start_offset >= BITFIELD_SIZE { -866 // The start offset is outside of the window. All packets outside of the window are -867 // considered to be received. -868 return None; -869 } -870 -871 let end_offset_exclusive = self.highest().saturating_sub(lower_bound); -872 -873 // The range is clamped at the edge of the window, because any earlier packets are -874 // considered to be received -875 let range_len = end_offset_exclusive -876 .saturating_sub(start_offset) -877 .min(BITFIELD_SIZE); -878 if range_len == 0 { -879 return None; -880 } -881 -882 // Ensure the shift is within bounds (we already know start_offset < BITFIELD_SIZE, -883 // because of the early return) -884 let mask = if range_len == BITFIELD_SIZE { -885 u128::MAX -886 } else { -887 ((1u128 << range_len) - 1) << start_offset -888 }; -889 let gaps = !self.window & mask; -890 -891 let smallest_missing_offset = 128 - gaps.leading_zeros() as u64; -892 let smallest_missing_packet = self.highest() - smallest_missing_offset; -893 -894 if smallest_missing_packet <= upper_bound { -895 Some(smallest_missing_packet) -896 } else { -897 None -898 } -899 } -900 -901 /// Returns true if there are any missing packets between the provided interval -902 /// -903 /// The provided packet numbers must have been received before calling this function -904 fn missing_in_interval(&self, lower_bound: u64, upper_bound: u64) -> bool { -905 self.smallest_missing_in_interval(lower_bound, upper_bound) -906 .is_some() -907 } -908} +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} +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() +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} +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 } +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; +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/// Indicates which data is available for sending. -911/// -912/// This applies to a particular space ID that was queried and all refers to on-path data. -913#[derive(Clone, Copy, PartialEq, Eq, Debug)] -914pub(super) struct SendableFrames { -915 /// Whether there are ACK frames to send, these are not ack-eliciting. -916 pub(super) acks: bool, -917 /// Whether there is a CONNECTION_CLOSE to send, this is not ack-eliciting. -918 pub(super) close: bool, -919 /// Whether there are any frames that must be sent on this specific space. -920 /// -921 /// A space here in the sense of a QUIC Multipath packet number space: `Initial`, -922 /// `Handshake` and all `Data(PathId)` spaces. -923 /// -924 /// These are ack-eliciting. Some frames are scheduled per path, e.g. PING, -925 /// IMMEDIATE_ACK, PATH_CHALLENGE or PATH_RESPONSE. -926 pub(super) space_specific: bool, -927 /// Whether there are any other frames to send, these are ack-eliciting. -928 pub(super) other: bool, -929} -930 -931impl SendableFrames { -932 /// Returns that no data is available for sending -933 pub(super) fn empty() -> Self { -934 Self { -935 acks: false, -936 close: false, -937 space_specific: false, -938 other: false, -939 } -940 } -941 -942 /// Whether an ack-eliciting packet will be sent. -943 pub(super) fn is_ack_eliciting(&self) -> bool { -944 let Self { -945 acks: _, -946 close, -947 space_specific, -948 other, -949 } = *self; -950 if close { -951 // No ack-eliciting frames are included with a CONNECTION_CLOSE, only acks. -952 return false; -953 } -954 space_specific || other -955 } -956 -957 /// Whether no data is sendable. -958 pub(super) fn is_empty(&self) -> bool { -959 let Self { -960 acks, -961 close, -962 space_specific, -963 other, -964 } = *self; -965 !acks && !close && !space_specific && !other -966 } -967} -968 -969impl ::std::ops::BitOrAssign for SendableFrames { -970 fn bitor_assign(&mut self, rhs: Self) { -971 let Self { -972 acks, -973 close, -974 space_specific, -975 other, -976 } = rhs; -977 -978 self.acks |= acks; -979 self.close |= close; -980 self.space_specific |= space_specific; -981 self.other |= other; -982 } -983} -984 -985#[derive(Debug)] -986pub(super) struct PendingAcks { -987 /// Whether we should send an ACK immediately, even if that means sending an ACK-only packet -988 /// -989 /// When `immediate_ack_required` is false, the normal behavior is to send ACK frames only when -990 /// there is other data to send, or when the `MaxAckDelay` timer expires. -991 immediate_ack_required: bool, -992 /// The number of ack-eliciting packets received since the last ACK frame was sent -993 /// -994 /// Once the count _exceeds_ `ack_eliciting_threshold`, an immediate ACK is required -995 ack_eliciting_since_last_ack_sent: u64, -996 non_ack_eliciting_since_last_ack_sent: u64, -997 ack_eliciting_threshold: u64, -998 /// The reordering threshold, controlling how we respond to out-of-order ack-eliciting packets -999 /// -1000 /// Different values enable different behavior: -1001 /// -1002 /// * `0`: no special action is taken -1003 /// * `1`: an ACK is immediately sent if it is out-of-order according to RFC 9000 -1004 /// * `>1`: an ACK is immediately sent if it is out-of-order according to the ACK frequency draft -1005 reordering_threshold: u64, -1006 /// The earliest ack-eliciting packet since the last ACK was sent, used to calculate the moment -1007 /// upon which `max_ack_delay` elapses -1008 earliest_ack_eliciting_since_last_ack_sent: Option<Instant>, -1009 /// Packet number ranges for which to still send acknowledgements. -1010 /// -1011 /// These are packet number ranges of ack-eliciting packets the peer has sent and which -1012 /// need to be acknowledged. Packet numbers are only removed from here once the peer has -1013 /// acknowledged the ACKs for them. -1014 ranges: ArrayRangeSet, -1015 /// The largest packet number received and the time it was received -1016 /// -1017 /// Used to calculate ACK delay in [`PendingAcks::ack_delay`]. -1018 largest_packet: Option<(u64, Instant)>, -1019 /// The ack-eliciting packet we have received with the largest packet number -1020 largest_ack_eliciting_packet: Option<u64>, -1021 /// The largest acknowledged packet number sent in an ACK frame -1022 largest_acked: Option<u64>, -1023} -1024 -1025impl PendingAcks { -1026 fn new() -> Self { -1027 Self { -1028 immediate_ack_required: false, -1029 ack_eliciting_since_last_ack_sent: 0, -1030 non_ack_eliciting_since_last_ack_sent: 0, -1031 ack_eliciting_threshold: 1, -1032 reordering_threshold: 1, -1033 earliest_ack_eliciting_since_last_ack_sent: None, -1034 ranges: Default::default(), -1035 largest_packet: Default::default(), -1036 largest_ack_eliciting_packet: Default::default(), -1037 largest_acked: Default::default(), -1038 } -1039 } -1040 -1041 pub(super) fn set_ack_frequency_params(&mut self, frame: &frame::AckFrequency) { -1042 self.ack_eliciting_threshold = frame.ack_eliciting_threshold.into_inner(); -1043 self.reordering_threshold = frame.reordering_threshold.into_inner(); +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} +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 pub(super) fn set_immediate_ack_required(&mut self) { -1047 self.immediate_ack_required = true; -1048 } -1049 -1050 pub(super) fn on_max_ack_delay_timeout(&mut self) { -1051 self.immediate_ack_required = self.ack_eliciting_since_last_ack_sent > 0; -1052 } -1053 -1054 pub(super) fn max_ack_delay_timeout(&self, max_ack_delay: Duration) -> Option<Instant> { -1055 self.earliest_ack_eliciting_since_last_ack_sent -1056 .map(|earliest_unacked| earliest_unacked + max_ack_delay) -1057 } -1058 -1059 /// Whether any ACK frames SHOULD be sent -1060 /// -1061 /// This is used in the top-level [`Connection::space_can_send`], so determines if a -1062 /// packet will be built. It is often possible to construct new ACK ranges to send -1063 /// before this returns `true`. This results in more ACK frames being sent, and -1064 /// processing those at the receiver costs CPU for very little improvements. -1065 /// -1066 /// [`Connection::space_can_send`]: super::Connection::space_can_send -1067 pub(super) fn can_send(&self) -> bool { -1068 self.immediate_ack_required && !self.ranges.is_empty() -1069 } -1070 -1071 /// Returns the delay since the packet with the largest packet number was received -1072 pub(super) fn ack_delay(&self, now: Instant) -> Duration { -1073 self.largest_packet -1074 .map_or_else(Duration::default, |(_, received)| now - received) -1075 } -1076 -1077 /// Handle receipt of a new packet +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 /// Returns true if the max ack delay timer should be armed -1080 pub(super) fn packet_received( -1081 &mut self, -1082 now: Instant, -1083 packet_number: u64, -1084 ack_eliciting: bool, -1085 dedup: &Dedup, -1086 ) -> bool { -1087 if !ack_eliciting { -1088 self.non_ack_eliciting_since_last_ack_sent += 1; -1089 return false; -1090 } -1091 -1092 let prev_largest_ack_eliciting = self.largest_ack_eliciting_packet.unwrap_or(0); -1093 -1094 // Track largest ack-eliciting packet -1095 self.largest_ack_eliciting_packet = self -1096 .largest_ack_eliciting_packet -1097 .map(|pn| pn.max(packet_number)) -1098 .or(Some(packet_number)); -1099 -1100 // Handle ack_eliciting_threshold -1101 self.ack_eliciting_since_last_ack_sent += 1; -1102 self.immediate_ack_required |= -1103 self.ack_eliciting_since_last_ack_sent > self.ack_eliciting_threshold; -1104 -1105 // Handle out-of-order packets -1106 self.immediate_ack_required |= -1107 self.is_out_of_order(packet_number, prev_largest_ack_eliciting, dedup); -1108 -1109 // Arm max_ack_delay timer if necessary -1110 if self.earliest_ack_eliciting_since_last_ack_sent.is_none() && !self.can_send() { -1111 self.earliest_ack_eliciting_since_last_ack_sent = Some(now); -1112 return true; -1113 } -1114 -1115 false -1116 } -1117 -1118 fn is_out_of_order( -1119 &self, -1120 packet_number: u64, -1121 prev_largest_ack_eliciting: u64, -1122 dedup: &Dedup, -1123 ) -> bool { -1124 match self.reordering_threshold { -1125 0 => false, -1126 1 => { -1127 // From https://www.rfc-editor.org/rfc/rfc9000#section-13.2.1-7 -1128 packet_number < prev_largest_ack_eliciting -1129 || dedup.missing_in_interval(prev_largest_ack_eliciting, packet_number) -1130 } -1131 _ => { -1132 // From acknowledgement frequency draft, section 6.1: send an ACK immediately if -1133 // doing so would cause the sender to detect a new packet loss -1134 let Some((largest_acked, largest_unacked)) = -1135 self.largest_acked.zip(self.largest_ack_eliciting_packet) -1136 else { -1137 return false; -1138 }; -1139 if self.reordering_threshold > largest_acked { -1140 return false; -1141 } -1142 // The largest packet number that could be declared lost without a new ACK being -1143 // sent -1144 let largest_reported = largest_acked - self.reordering_threshold + 1; -1145 let Some(smallest_missing_unreported) = -1146 dedup.smallest_missing_in_interval(largest_reported, largest_unacked) -1147 else { -1148 return false; -1149 }; -1150 largest_unacked - smallest_missing_unreported >= self.reordering_threshold -1151 } +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>, +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 -1155 /// Should be called whenever ACKs have been sent -1156 /// -1157 /// This will suppress sending further ACKs until additional ACK eliciting frames arrive -1158 pub(super) fn acks_sent(&mut self) { -1159 // It is possible (though unlikely) that the ACKs we just sent do not cover all the -1160 // ACK-eliciting packets we have received (e.g. if there is not enough room in the packet to -1161 // fit all the ranges). To keep things simple, however, we assume they do. If there are -1162 // indeed some ACKs that weren't covered, the packets might be ACKed later anyway, because -1163 // they are still contained in `self.ranges`. If we somehow fail to send the ACKs at a later -1164 // moment, the peer will assume the packets got lost and will retransmit their frames in a -1165 // new packet, which is suboptimal, because we already received them. Our assumption here is -1166 // that simplicity results in code that is more performant, even in the presence of -1167 // occasional redundant retransmits. -1168 self.immediate_ack_required = false; -1169 self.ack_eliciting_since_last_ack_sent = 0; -1170 self.non_ack_eliciting_since_last_ack_sent = 0; -1171 self.earliest_ack_eliciting_since_last_ack_sent = None; -1172 self.largest_acked = self.largest_ack_eliciting_packet; -1173 } -1174 -1175 /// Insert one packet that needs to be acknowledged -1176 pub(super) fn insert_one(&mut self, packet: u64, now: Instant) { -1177 self.ranges.insert_one(packet); -1178 -1179 if self.largest_packet.is_none_or(|(pn, _)| packet > pn) { -1180 self.largest_packet = Some((packet, now)); -1181 } -1182 -1183 if self.ranges.len() > MAX_ACK_BLOCKS { -1184 self.ranges.pop_min(); -1185 } -1186 } -1187 -1188 /// Remove ACKs of packets numbered at or below `max` from the set of pending ACKs -1189 pub(super) fn subtract_below(&mut self, max: u64) { -1190 self.ranges.remove(0..(max + 1)); -1191 } -1192 -1193 /// Returns the set of currently pending ACK ranges -1194 pub(super) fn ranges(&self) -> &ArrayRangeSet { -1195 &self.ranges -1196 } -1197 -1198 /// Queue an ACK if a significant number of non-ACK-eliciting packets have not yet been -1199 /// acknowledged -1200 /// -1201 /// Should be called immediately before a non-probing packet is composed, when we've already -1202 /// committed to sending a packet regardless. -1203 pub(super) fn maybe_ack_non_eliciting(&mut self) { -1204 // If we're going to send a packet anyway, and we've received a significant number of -1205 // non-ACK-eliciting packets, then include an ACK to help the peer perform timely loss -1206 // detection even if they're not sending any ACK-eliciting packets themselves. Exact -1207 // threshold chosen somewhat arbitrarily. -1208 const LAZY_ACK_THRESHOLD: u64 = 10; -1209 if self.non_ack_eliciting_since_last_ack_sent > LAZY_ACK_THRESHOLD { -1210 self.immediate_ack_required = true; -1211 } -1212 } -1213} -1214 -1215/// Helper for mitigating [optimistic ACK attacks] -1216/// -1217/// A malicious peer could prompt the local application to begin a large data transfer, and then -1218/// send ACKs without first waiting for data to be received. This could defeat congestion control, -1219/// allowing the connection to consume disproportionate resources. We therefore occasionally skip -1220/// packet numbers, and classify any ACK referencing a skipped packet number as a transport error. -1221/// -1222/// Skipped packet numbers occur only in the application data space (where costly transfers might -1223/// take place) and are distributed exponentially to reflect the reduced likelihood and impact of -1224/// bad behavior from a peer that has been well-behaved for an extended period. -1225/// -1226/// ACKs for packet numbers that have not yet been allocated are also a transport error, but an -1227/// attacker with knowledge of the congestion control algorithm in use could time falsified ACKs to -1228/// arrive after the packets they reference are sent. -1229/// -1230/// [optimistic ACK attacks]: https://www.rfc-editor.org/rfc/rfc9000.html#name-optimistic-ack-attack -1231pub(super) struct PacketNumberFilter { -1232 /// Next outgoing packet number to skip -1233 next_skipped_packet_number: u64, -1234 /// Most recently skipped packet number -1235 prev_skipped_packet_number: Option<u64>, -1236 /// Next packet number to skip is randomly selected from 2^n..2^n+1 -1237 exponent: u32, -1238} -1239 -1240impl PacketNumberFilter { -1241 pub(super) fn new(rng: &mut (impl CryptoRng + ?Sized)) -> Self { -1242 // First skipped PN is in 0..64 -1243 let exponent = 6; -1244 Self { -1245 next_skipped_packet_number: rng.random_range(0..2u64.saturating_pow(exponent)), -1246 prev_skipped_packet_number: None, -1247 exponent, -1248 } -1249 } -1250 -1251 #[cfg(test)] -1252 pub(super) fn disabled() -> Self { -1253 Self { -1254 next_skipped_packet_number: u64::MAX, -1255 prev_skipped_packet_number: None, -1256 exponent: u32::MAX, -1257 } +1153 +1154 let prev_largest_ack_eliciting = self.largest_ack_eliciting_packet.unwrap_or(0); +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 } +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 /// Whether to use the provided packet number (false) or to skip it (true) -1261 pub(super) fn skip_pn(&mut self, n: u64, rng: &mut (impl CryptoRng + ?Sized)) -> bool { -1262 if n != self.next_skipped_packet_number { -1263 return false; -1264 } -1265 -1266 trace!("skipping pn {n}"); -1267 // Skip this packet number, and choose the next one to skip -1268 self.prev_skipped_packet_number = Some(self.next_skipped_packet_number); -1269 let next_exponent = self.exponent.saturating_add(1); -1270 self.next_skipped_packet_number = rng -1271 .random_range(2u64.saturating_pow(self.exponent)..2u64.saturating_pow(next_exponent)); -1272 self.exponent = next_exponent; -1273 true -1274 } +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} 1276 -1277/// Ensures we can always fit all our ACKs in a single minimum-MTU packet with room to spare -1278const MAX_ACK_BLOCKS: usize = 64; -1279 -1280#[cfg(test)] -1281mod test { -1282 use rand::Rng; -1283 use rand::seq::SliceRandom; -1284 -1285 use crate::token::ResetToken; -1286 use crate::{ConnectionIdGenerator, RandomConnectionIdGenerator}; -1287 -1288 use super::*; -1289 -1290 #[test] -1291 fn sanity() { -1292 let mut dedup = Dedup::new(); -1293 assert!(!dedup.insert(0)); -1294 assert_eq!(dedup.next, 1); -1295 assert_eq!(dedup.window, 0b1); -1296 assert!(dedup.insert(0)); -1297 assert_eq!(dedup.next, 1); -1298 assert_eq!(dedup.window, 0b1); -1299 assert!(!dedup.insert(1)); -1300 assert_eq!(dedup.next, 2); -1301 assert_eq!(dedup.window, 0b11); -1302 assert!(!dedup.insert(2)); -1303 assert_eq!(dedup.next, 3); -1304 assert_eq!(dedup.window, 0b111); -1305 assert!(!dedup.insert(4)); -1306 assert_eq!(dedup.next, 5); -1307 assert_eq!(dedup.window, 0b11110); -1308 assert!(!dedup.insert(7)); -1309 assert_eq!(dedup.next, 8); -1310 assert_eq!(dedup.window, 0b1111_0100); -1311 assert!(dedup.insert(4)); -1312 assert!(!dedup.insert(3)); -1313 assert_eq!(dedup.next, 8); -1314 assert_eq!(dedup.window, 0b1111_1100); -1315 assert!(!dedup.insert(6)); -1316 assert_eq!(dedup.next, 8); -1317 assert_eq!(dedup.window, 0b1111_1101); -1318 assert!(!dedup.insert(5)); -1319 assert_eq!(dedup.next, 8); -1320 assert_eq!(dedup.window, 0b1111_1111); -1321 } -1322 -1323 #[test] -1324 fn happypath() { -1325 let mut dedup = Dedup::new(); -1326 for i in 0..(2 * WINDOW_SIZE) { -1327 assert!(!dedup.insert(i)); -1328 for j in 0..=i { -1329 assert!(dedup.insert(j)); -1330 } -1331 } -1332 } -1333 -1334 #[test] -1335 fn jump() { -1336 let mut dedup = Dedup::new(); -1337 dedup.insert(2 * WINDOW_SIZE); -1338 assert!(dedup.insert(WINDOW_SIZE)); -1339 assert_eq!(dedup.next, 2 * WINDOW_SIZE + 1); -1340 assert_eq!(dedup.window, 0); -1341 assert!(!dedup.insert(WINDOW_SIZE + 1)); -1342 assert_eq!(dedup.next, 2 * WINDOW_SIZE + 1); -1343 assert_eq!(dedup.window, 1 << (WINDOW_SIZE - 2)); -1344 } -1345 -1346 #[test] -1347 fn dedup_has_missing() { -1348 let mut dedup = Dedup::new(); +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; +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 dedup.insert(0); -1351 assert!(!dedup.missing_in_interval(0, 0)); -1352 -1353 dedup.insert(1); -1354 assert!(!dedup.missing_in_interval(0, 1)); -1355 -1356 dedup.insert(3); -1357 assert!(dedup.missing_in_interval(1, 3)); -1358 -1359 dedup.insert(4); -1360 assert!(!dedup.missing_in_interval(3, 4)); -1361 assert!(dedup.missing_in_interval(0, 4)); -1362 -1363 dedup.insert(2); -1364 assert!(!dedup.missing_in_interval(0, 4)); -1365 } -1366 -1367 #[test] -1368 fn dedup_outside_of_window_has_missing() { -1369 let mut dedup = Dedup::new(); -1370 -1371 for i in 0..140 { -1372 dedup.insert(i); -1373 } -1374 -1375 // 0 and 4 are outside of the window -1376 assert!(!dedup.missing_in_interval(0, 4)); -1377 dedup.insert(160); -1378 assert!(!dedup.missing_in_interval(0, 4)); -1379 assert!(!dedup.missing_in_interval(0, 140)); -1380 assert!(dedup.missing_in_interval(0, 160)); -1381 } -1382 -1383 #[test] -1384 fn dedup_smallest_missing() { -1385 let mut dedup = Dedup::new(); -1386 -1387 dedup.insert(0); -1388 assert_eq!(dedup.smallest_missing_in_interval(0, 0), None); -1389 -1390 dedup.insert(1); -1391 assert_eq!(dedup.smallest_missing_in_interval(0, 1), None); -1392 -1393 dedup.insert(5); -1394 dedup.insert(7); -1395 assert_eq!(dedup.smallest_missing_in_interval(0, 7), Some(2)); -1396 assert_eq!(dedup.smallest_missing_in_interval(5, 7), Some(6)); -1397 -1398 dedup.insert(2); -1399 assert_eq!(dedup.smallest_missing_in_interval(1, 7), Some(3)); -1400 -1401 dedup.insert(170); -1402 dedup.insert(172); -1403 dedup.insert(300); -1404 assert_eq!(dedup.smallest_missing_in_interval(170, 172), None); -1405 -1406 dedup.insert(500); -1407 assert_eq!(dedup.smallest_missing_in_interval(0, 500), Some(372)); -1408 assert_eq!(dedup.smallest_missing_in_interval(0, 373), Some(372)); -1409 assert_eq!(dedup.smallest_missing_in_interval(0, 372), None); -1410 } +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 #[test] -1413 fn pending_acks_first_packet_is_not_considered_reordered() { -1414 let mut acks = PendingAcks::new(); -1415 let mut dedup = Dedup::new(); -1416 dedup.insert(0); -1417 acks.packet_received(Instant::now(), 0, true, &dedup); -1418 assert!(!acks.immediate_ack_required); -1419 } +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 #[test] -1422 fn pending_acks_after_immediate_ack_set() { -1423 let mut acks = PendingAcks::new(); -1424 let mut dedup = Dedup::new(); -1425 -1426 // Receive ack-eliciting packet -1427 dedup.insert(0); -1428 let now = Instant::now(); -1429 acks.insert_one(0, now); -1430 acks.packet_received(now, 0, true, &dedup); -1431 -1432 // Sanity check -1433 assert!(!acks.ranges.is_empty()); -1434 assert!(!acks.can_send()); -1435 -1436 // Can send ACK after max_ack_delay exceeded -1437 acks.set_immediate_ack_required(); -1438 assert!(acks.can_send()); -1439 } -1440 -1441 #[test] -1442 fn pending_acks_ack_delay() { -1443 let mut acks = PendingAcks::new(); -1444 let mut dedup = Dedup::new(); -1445 -1446 let t1 = Instant::now(); -1447 let t2 = t1 + Duration::from_millis(2); -1448 let t3 = t2 + Duration::from_millis(5); -1449 assert_eq!(acks.ack_delay(t1), Duration::from_millis(0)); -1450 assert_eq!(acks.ack_delay(t2), Duration::from_millis(0)); -1451 assert_eq!(acks.ack_delay(t3), Duration::from_millis(0)); -1452 -1453 // In-order packet -1454 dedup.insert(0); -1455 acks.insert_one(0, t1); -1456 acks.packet_received(t1, 0, true, &dedup); -1457 assert_eq!(acks.ack_delay(t1), Duration::from_millis(0)); -1458 assert_eq!(acks.ack_delay(t2), Duration::from_millis(2)); -1459 assert_eq!(acks.ack_delay(t3), Duration::from_millis(7)); -1460 -1461 // Out of order (higher than expected) -1462 dedup.insert(3); -1463 acks.insert_one(3, t2); -1464 acks.packet_received(t2, 3, true, &dedup); -1465 assert_eq!(acks.ack_delay(t2), Duration::from_millis(0)); -1466 assert_eq!(acks.ack_delay(t3), Duration::from_millis(5)); +1421 dedup.insert(4); +1422 assert!(!dedup.missing_in_interval(3, 4)); +1423 assert!(dedup.missing_in_interval(0, 4)); +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); +1451 +1452 dedup.insert(1); +1453 assert_eq!(dedup.smallest_missing_in_interval(0, 1), None); +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 // Out of order (lower than expected, so previous instant is kept) -1469 dedup.insert(2); -1470 acks.insert_one(2, t3); -1471 acks.packet_received(t3, 2, true, &dedup); -1472 assert_eq!(acks.ack_delay(t3), Duration::from_millis(5)); -1473 } -1474 -1475 #[test] -1476 fn sent_packet_size() { -1477 // The tracking state of sent packets should be minimal, and not grow -1478 // over time. -1479 assert!(std::mem::size_of::<SentPacket>() <= 128); -1480 } -1481 -1482 #[test] -1483 fn pending_new_cids() { -1484 #[cfg(all(feature = "aws-lc-rs", not(feature = "ring")))] -1485 use aws_lc_rs::hmac; -1486 #[cfg(feature = "ring")] -1487 use ring::hmac; -1488 -1489 let mut cid_generator = RandomConnectionIdGenerator::new(8); -1490 let mut reset_key = [0; 64]; -1491 rand::rng().fill_bytes(&mut reset_key); -1492 let hmac = hmac::Key::new(hmac::HMAC_SHA256, &reset_key); +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 let cid_a = cid_generator.generate_cid(); -1495 let a = IssuedCid { -1496 path_id: PathId::ZERO, -1497 sequence: 1, -1498 id: cid_a, -1499 reset_token: ResetToken::new(&hmac, cid_a), -1500 }; -1501 let cid_b = cid_generator.generate_cid(); -1502 let b = IssuedCid { -1503 path_id: PathId::ZERO, -1504 sequence: 2, -1505 id: cid_b, -1506 reset_token: ResetToken::new(&hmac, cid_b), -1507 }; -1508 let cid_c = cid_generator.generate_cid(); -1509 let c = IssuedCid { -1510 path_id: PathId(1), -1511 sequence: 1, -1512 id: cid_c, -1513 reset_token: ResetToken::new(&hmac, cid_c), -1514 }; -1515 -1516 let mut pending_cids = PendingNewCids::default(); -1517 -1518 for _ in 0..9 { -1519 // Push CIDs in a random order -1520 let mut input = vec![a, b, c]; -1521 input.shuffle(&mut rand::rng()); -1522 for cid in input { -1523 pending_cids.push(cid); -1524 } -1525 -1526 // Pop order is always the same -1527 assert_eq!(pending_cids.pop().map(|i| i.id), Some(a.id)); -1528 assert_eq!(pending_cids.pop().map(|i| i.id), Some(b.id)); -1529 assert_eq!(pending_cids.pop().map(|i| i.id), Some(c.id)); -1530 assert!(pending_cids.pop().is_none()); -1531 } -1532 } -1533} \ No newline at end of file +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 } +1502 +1503 #[test] +1504 fn pending_acks_ack_delay() { +1505 let mut acks = PendingAcks::new(); +1506 let mut dedup = Dedup::new(); +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)); +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 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 4ff7c4999..365b026db 100644 --- a/pr/451/docs/src/noq_proto/connection/stats.rs.html +++ b/pr/451/docs/src/noq_proto/connection/stats.rs.html @@ -227,121 +227,141 @@ 227 pub cwnd: u64, 228 /// Congestion events on the connection. 229 pub congestion_events: u64, -230 /// The amount of packets lost on this path. -231 pub lost_packets: u64, -232 /// The amount of bytes lost on this path. -233 pub lost_bytes: u64, -234 /// The number of PLPMTUD probe packets sent on this path. -235 /// -236 /// These are also counted by [`UdpStats::datagrams`]. -237 pub sent_plpmtud_probes: u64, -238 /// The number of PLPMTUD probe packets lost on this path. -239 /// -240 /// These are not included in [`Self::lost_packets`] and [`Self::lost_bytes`]. -241 pub lost_plpmtud_probes: u64, -242 /// The number of times a black hole was detected in the path. -243 pub black_holes_detected: u64, -244 /// Largest UDP payload size the path currently supports. -245 pub current_mtu: u16, -246} -247 -248/// Connection statistics. -249/// -250/// The fields here are a sum of the respective fields in the [`PathStats`] for all the -251/// paths that exist as well as all paths that previously existed. -252#[derive(Debug, Default, Clone)] -253#[non_exhaustive] -254pub struct ConnectionStats { -255 /// Statistics about UDP datagrams transmitted on a connection. -256 pub udp_tx: UdpStats, -257 /// Statistics about UDP datagrams received on a connection. -258 pub udp_rx: UdpStats, -259 /// Statistics about frames transmitted on a connection. -260 pub frame_tx: FrameStats, -261 /// Statistics about frames received on a connection. -262 pub frame_rx: FrameStats, -263} -264 -265impl std::ops::Add<PathStats> for ConnectionStats { -266 type Output = Self; -267 -268 fn add(self, rhs: PathStats) -> Self::Output { -269 // Be aware that Connection::stats() relies on the fact this function ignores the -270 // rtt, cwnd and current_mtu fields. -271 let PathStats { -272 rtt: _, -273 udp_tx, -274 udp_rx, -275 frame_tx, -276 frame_rx, -277 cwnd: _, -278 congestion_events: _, -279 lost_packets: _, -280 lost_bytes: _, -281 sent_plpmtud_probes: _, -282 lost_plpmtud_probes: _, -283 black_holes_detected: _, -284 current_mtu: _, -285 } = rhs; -286 Self { -287 udp_tx: self.udp_tx + udp_tx, -288 udp_rx: self.udp_rx + udp_rx, -289 frame_tx: self.frame_tx + frame_tx, -290 frame_rx: self.frame_rx + frame_rx, -291 } -292 } -293} -294 -295impl std::ops::AddAssign<PathStats> for ConnectionStats { -296 fn add_assign(&mut self, rhs: PathStats) { -297 // Be aware that Connection::stats() relies on the fact this function ignores the -298 // rtt, cwnd and current_mtu fields. -299 let PathStats { -300 rtt: _, -301 udp_tx, -302 udp_rx, -303 frame_tx, -304 frame_rx, -305 cwnd: _, -306 congestion_events: _, -307 lost_packets: _, -308 lost_bytes: _, -309 sent_plpmtud_probes: _, -310 lost_plpmtud_probes: _, -311 black_holes_detected: _, -312 current_mtu: _, -313 } = rhs; -314 self.udp_tx += udp_tx; -315 self.udp_rx += udp_rx; -316 self.frame_tx += frame_tx; -317 self.frame_rx += frame_rx; -318 } -319} -320 -321/// Helper to make [`PathStats`] infallibly available. -322/// -323/// This helper also helps with borrowing issues compared to having the [`Self::for_path`] -324/// function as a helper directly on [`Connection`]. -325/// -326/// [`Connection`]: super::Connection -327#[derive(Debug, Default)] -328pub(super) struct PathStatsMap(FxHashMap<PathId, PathStats>); -329 -330impl PathStatsMap { -331 /// Returns the [`PathStats`] for the path. -332 pub(super) fn for_path(&mut self, path_id: PathId) -> &mut PathStats { -333 self.0.entry(path_id).or_default() -334 } -335 -336 /// An iterator over all contained [`PathStats`]. -337 pub(super) fn iter_stats(&self) -> impl Iterator<Item = &PathStats> { -338 self.0.values() -339 } +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 /// Removes the stats for a given path. -342 /// -343 /// Only do this once you are discarding the path. -344 pub(super) fn discard(&mut self, path_id: &PathId) -> PathStats { -345 self.0.remove(path_id).unwrap_or_default() -346 } -347} \ No newline at end of file +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 } +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 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 d927a36e6..d24e9083d 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 @@ -21,534 +21,553 @@ 21mod send; 22pub(crate) use send::{ByteSlice, BytesArray}; 23use send::{BytesSource, Send, SendState}; -24pub use send::{FinishError, WriteError, Written}; -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 // TODO: Queue STREAM_ID_BLOCKED if this fails -52 if self.state.next[dir as usize] >= self.state.max[dir as usize] { -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(false, 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 /// Check whether this stream has been reset by the peer, returning the reset error code if so -167 /// -168 /// After returning `Ok(Some(_))` once, stream state will be discarded and all future calls will -169 /// return `Err(ClosedStream)`. -170 pub fn received_reset(&mut self) -> Result<Option<VarInt>, ClosedStream> { -171 let hash_map::Entry::Occupied(entry) = self.state.recv.entry(self.id) else { -172 return Err(ClosedStream { _private: () }); -173 }; -174 let Some(s) = entry.get().as_ref().and_then(|s| s.as_open_recv()) else { -175 return Ok(None); -176 }; -177 if s.stopped { -178 return Err(ClosedStream { _private: () }); -179 } -180 let Some(code) = s.reset_code() else { -181 return Ok(None); -182 }; -183 -184 // Clean up state after application observes the reset, since there's no reason for the -185 // application to attempt to read or stop the stream once it knows it's reset -186 let (_, recv) = entry.remove_entry(); -187 self.state -188 .stream_recv_freed(self.id, recv.expect("must have recv on reset")); -189 self.state.queue_max_stream_id(self.pending); -190 -191 Ok(Some(code)) -192 } -193} -194 -195/// Access to streams -196pub struct SendStream<'a> { -197 pub(super) id: StreamId, -198 pub(super) state: &'a mut StreamsState, -199 pub(super) pending: &'a mut Retransmits, -200 pub(super) conn_state: &'a super::State, -201} -202 -203#[allow(clippy::needless_lifetimes)] // Needed for cfg(fuzzing) -204impl<'a> SendStream<'a> { -205 #[cfg(fuzzing)] -206 pub fn new( -207 id: StreamId, -208 state: &'a mut StreamsState, -209 pending: &'a mut Retransmits, -210 conn_state: &'a super::State, -211 ) -> Self { -212 Self { -213 id, -214 state, -215 pending, -216 conn_state, -217 } -218 } -219 -220 /// Send data on the given stream -221 /// -222 /// Returns the number of bytes successfully written. -223 pub fn write(&mut self, data: &[u8]) -> Result<usize, WriteError> { -224 Ok(self.write_source(&mut ByteSlice::from_slice(data))?.bytes) -225 } -226 -227 /// Send data on the given stream -228 /// -229 /// Returns the number of bytes and chunks successfully written. -230 /// Note that this method might also write a partial chunk. In this case -231 /// [`Written::chunks`] will not count this chunk as fully written. However -232 /// the chunk will be advanced and contain only non-written data after the call. -233 pub fn write_chunks(&mut self, data: &mut [Bytes]) -> Result<Written, WriteError> { -234 self.write_source(&mut BytesArray::from_chunks(data)) -235 } -236 -237 fn write_source<'b, B: BytesSource<'b>>( -238 &mut self, -239 source: &'b mut B, -240 ) -> Result<Written, WriteError> { -241 if self.conn_state.is_closed() { -242 trace!(%self.id, "write blocked; connection draining"); -243 return Err(WriteError::Blocked); -244 } -245 -246 let limit = self.state.write_limit(); -247 -248 let max_send_data = self.state.max_send_data(self.id); -249 -250 let stream = self -251 .state -252 .send -253 .get_mut(&self.id) -254 .map(get_or_insert_send(max_send_data)) -255 .ok_or(WriteError::ClosedStream)?; -256 -257 if limit == 0 { -258 trace!( -259 stream = %self.id, max_data = self.state.max_data, data_sent = self.state.data_sent, -260 "write blocked by connection-level flow control or send window" -261 ); -262 if !stream.connection_blocked { -263 stream.connection_blocked = true; -264 self.state.connection_blocked.push(self.id); -265 } -266 return Err(WriteError::Blocked); -267 } +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 was_pending = stream.is_pending(); -270 let written = stream.write(source, limit)?; -271 self.state.data_sent += written.bytes as u64; -272 self.state.unacked_data += written.bytes as u64; -273 trace!(stream = %self.id, "wrote {} bytes", written.bytes); -274 if !was_pending { -275 self.state.pending.push_pending(self.id, stream.priority); -276 } -277 Ok(written) -278 } -279 -280 /// Check if this stream was stopped, get the reason if it was -281 pub fn stopped(&self) -> Result<Option<VarInt>, ClosedStream> { -282 match self.state.send.get(&self.id).as_ref() { -283 Some(Some(s)) => Ok(s.stop_reason), -284 Some(None) => Ok(None), -285 None => Err(ClosedStream { _private: () }), +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 -289 /// Finish a send stream, signalling that no more data will be sent. -290 /// -291 /// If this fails, no [`StreamEvent::Finished`] will be generated. -292 /// -293 /// [`StreamEvent::Finished`]: crate::StreamEvent::Finished -294 pub fn finish(&mut self) -> Result<(), FinishError> { -295 let max_send_data = self.state.max_send_data(self.id); -296 let stream = self -297 .state -298 .send -299 .get_mut(&self.id) -300 .map(get_or_insert_send(max_send_data)) -301 .ok_or(FinishError::ClosedStream)?; -302 -303 let was_pending = stream.is_pending(); -304 stream.finish()?; -305 if !was_pending { -306 self.state.pending.push_pending(self.id, stream.priority); -307 } -308 -309 Ok(()) -310 } -311 -312 /// Abandon transmitting data on a stream -313 /// -314 /// # Panics -315 /// - when applied to a receive stream -316 pub fn reset(&mut self, error_code: VarInt) -> Result<(), ClosedStream> { -317 let max_send_data = self.state.max_send_data(self.id); -318 let stream = self -319 .state -320 .send -321 .get_mut(&self.id) -322 .map(get_or_insert_send(max_send_data)) -323 .ok_or(ClosedStream { _private: () })?; -324 -325 if matches!(stream.state, SendState::ResetSent) { -326 // Redundant reset call -327 return Err(ClosedStream { _private: () }); -328 } -329 -330 // Restore the portion of the send window consumed by the data that we aren't about to -331 // send. We leave flow control alone because the peer's responsible for issuing additional -332 // credit based on the final offset communicated in the RESET_STREAM frame we send. -333 self.state.unacked_data -= stream.pending.unacked(); -334 stream.reset(); -335 self.pending.reset_stream.push((self.id, error_code)); -336 -337 // Don't reopen an already-closed stream we haven't forgotten yet -338 Ok(()) -339 } -340 -341 /// Set the priority of a stream -342 /// -343 /// # Panics -344 /// - when applied to a receive stream -345 pub fn set_priority(&mut self, priority: i32) -> Result<(), ClosedStream> { -346 let max_send_data = self.state.max_send_data(self.id); -347 let stream = self -348 .state -349 .send -350 .get_mut(&self.id) -351 .map(get_or_insert_send(max_send_data)) -352 .ok_or(ClosedStream { _private: () })?; -353 -354 stream.priority = priority; -355 Ok(()) -356 } -357 -358 /// Get the priority of a stream -359 /// -360 /// # Panics -361 /// - when applied to a receive stream -362 pub fn priority(&self) -> Result<i32, ClosedStream> { -363 let stream = self -364 .state -365 .send -366 .get(&self.id) -367 .ok_or(ClosedStream { _private: () })?; -368 -369 Ok(stream.as_ref().map(|s| s.priority).unwrap_or_default()) -370 } -371} +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/// A queue of streams with pending outgoing data, sorted by priority -374struct PendingStreamsQueue { -375 streams: BinaryHeap<PendingStream>, -376 /// The next stream to write out. This is `Some` when `TransportConfig::send_fairness(false)` and writing a stream is -377 /// interrupted while the stream still has some pending data. See `reinsert_pending()`. -378 next: Option<PendingStream>, -379 /// A monotonically decreasing counter, used to implement round-robin scheduling for streams of the same priority. -380 /// Underflowing is not a practical concern, as it is initialized to u64::MAX and only decremented by 1 in `push_pending` -381 recency: u64, -382} -383 -384impl PendingStreamsQueue { -385 fn new() -> Self { -386 Self { -387 streams: BinaryHeap::new(), -388 next: None, -389 recency: u64::MAX, -390 } -391 } -392 -393 /// Reinsert a stream that was pending and still contains unsent data. -394 fn reinsert_pending(&mut self, id: StreamId, priority: i32) { -395 assert!(self.next.is_none()); -396 -397 self.next = Some(PendingStream { -398 priority, -399 recency: self.recency, // the value here doesn't really matter -400 id, -401 }); -402 } -403 -404 /// Push a pending stream ID with the given priority, queued after any already-queued streams for the priority -405 fn push_pending(&mut self, id: StreamId, priority: i32) { -406 // Note that in the case where fairness is disabled, if we have a reinserted stream we don't -407 // bump it even if priority > next.priority. In order to minimize fragmentation we -408 // always try to complete a stream once part of it has been written. -409 -410 // As the recency counter is monotonically decreasing, we know that using its value to sort this stream will queue it -411 // after all other queued streams of the same priority. -412 // This is enough to implement round-robin scheduling for streams that are still pending even after being handled, -413 // as in that case they are removed from the `BinaryHeap`, handled, and then immediately reinserted. -414 self.recency -= 1; -415 self.streams.push(PendingStream { -416 priority, -417 recency: self.recency, -418 id, -419 }); -420 } -421 -422 fn pop(&mut self) -> Option<PendingStream> { -423 self.next.take().or_else(|| self.streams.pop()) -424 } -425 -426 fn clear(&mut self) { -427 self.next = None; -428 self.streams.clear(); -429 } -430 -431 fn iter(&self) -> impl Iterator<Item = &PendingStream> { -432 self.next.iter().chain(self.streams.iter()) -433 } -434 -435 #[cfg(test)] -436 fn len(&self) -> usize { -437 self.streams.len() + self.next.is_some() as usize -438 } -439} +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/// The [`StreamId`] of a stream with pending data queued, ordered by its priority and recency -442#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] -443struct PendingStream { -444 /// The priority of the stream -445 // Note that this field should be kept above the `recency` field, in order for the `Ord` derive to be correct -446 // (See https://doc.rust-lang.org/stable/std/cmp/trait.Ord.html#derivable) -447 priority: i32, -448 /// A tie-breaker for streams of the same priority, used to improve fairness by implementing round-robin scheduling: -449 /// Larger values are prioritized, so it is initialised to `u64::MAX`, and when a stream writes data, we know -450 /// that it currently has the highest recency value, so it is deprioritized by setting its recency to 1 less than the -451 /// previous lowest recency value, such that all other streams of this priority will get processed once before we get back -452 /// round to this one -453 recency: u64, -454 /// The ID of the stream -455 // The way this type is used ensures that every instance has a unique `recency` value, so this field should be kept below -456 // the `priority` and `recency` fields, so that it does not interfere with the behaviour of the `Ord` derive -457 id: StreamId, +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/// Application events about streams -461#[derive(Debug, PartialEq, Eq)] -462pub enum StreamEvent { -463 /// One or more new streams has been opened and might be readable -464 Opened { -465 /// Directionality for which streams have been opened -466 dir: Dir, -467 }, -468 /// A currently open stream likely has data or errors waiting to be read -469 Readable { -470 /// Which stream is now readable -471 id: StreamId, -472 }, -473 /// A formerly write-blocked stream might be ready for a write or have been stopped -474 /// -475 /// Only generated for streams that are currently open. -476 Writable { -477 /// Which stream is now writable -478 id: StreamId, -479 }, -480 /// A finished stream has been fully acknowledged or stopped -481 Finished { -482 /// Which stream has been finished -483 id: StreamId, -484 }, -485 /// The peer asked us to stop sending on an outgoing stream -486 Stopped { -487 /// Which stream has been stopped -488 id: StreamId, -489 /// Error code supplied by the peer -490 error_code: VarInt, +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 /// At least one new stream of a certain directionality may be opened -493 Available { -494 /// Directionality for which streams are newly available -495 dir: Dir, -496 }, -497} -498 -499/// Indicates whether a frame needs to be transmitted -500/// -501/// This type wraps around bool and uses the `#[must_use]` attribute in order -502/// to prevent accidental loss of the frame transmission requirement. -503#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] -504#[must_use = "A frame might need to be enqueued"] -505pub struct ShouldTransmit(bool); -506 -507impl ShouldTransmit { -508 /// Returns whether a frame should be transmitted -509 pub fn should_transmit(self) -> bool { -510 self.0 -511 } -512} -513 -514/// Error indicating that a stream has not been opened or has already been finished or reset -515#[derive(Debug, Default, Error, Clone, PartialEq, Eq)] -516#[error("closed stream")] -517pub struct ClosedStream { -518 _private: (), -519} -520 -521impl From<ClosedStream> for io::Error { -522 fn from(x: ClosedStream) -> Self { -523 Self::new(io::ErrorKind::NotConnected, x) -524 } -525} -526 -527#[derive(Debug, Copy, Clone, Eq, PartialEq)] -528enum StreamHalf { -529 Send, -530 Recv, +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/// A helper trait to unify Bytes, `Vec<u8>` and `&[u8]` as sources of bytes -534pub(super) trait BytesOrSlice<'a>: AsRef<[u8]> + 'a { -535 fn len(&self) -> usize { -536 self.as_ref().len() -537 } -538 fn is_empty(&self) -> bool { -539 self.as_ref().is_empty() -540 } -541 fn into_bytes(self) -> Bytes; -542} -543 -544impl BytesOrSlice<'_> for Bytes { -545 fn into_bytes(self) -> Bytes { -546 self -547 } -548} -549 -550impl<'a> BytesOrSlice<'a> for &'a [u8] { -551 fn into_bytes(self) -> Bytes { -552 Bytes::copy_from_slice(self) -553 } -554} \ No newline at end of file +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 diff --git a/pr/451/docs/src/noq_proto/connection/streams/send.rs.html b/pr/451/docs/src/noq_proto/connection/streams/send.rs.html index ba29956ef..c6ab8ff77 100644 --- a/pr/451/docs/src/noq_proto/connection/streams/send.rs.html +++ b/pr/451/docs/src/noq_proto/connection/streams/send.rs.html @@ -257,13 +257,13 @@ 257 258/// Indicates how many bytes and chunks had been transferred in a write operation 259#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] -260pub struct Written { +260pub(crate) struct Written { 261 /// The amount of bytes which had been written -262 pub bytes: usize, +262 pub(crate) bytes: usize, 263 /// The amount of full chunks which had been written 264 /// 265 /// If a chunk was only partially written, it will not be counted by this field. -266 pub chunks: usize, +266 pub(crate) chunks: usize, 267} 268 269/// Errors triggered while writing to a send stream 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 19576363d..b888816cf 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 @@ -135,1898 +135,1918 @@ 135 136 /// The shrink to be applied to local_max_data when receive_window is shrunk 137 receive_window_shrink_debt: u64, -138} -139 -140impl StreamsState { -141 #[allow(unreachable_pub)] // fuzzing only -142 pub fn new( -143 side: Side, -144 max_remote_uni: VarInt, -145 max_remote_bi: VarInt, -146 send_window: u64, -147 receive_window: VarInt, -148 stream_receive_window: VarInt, -149 ) -> Self { -150 let mut this = Self { -151 side, -152 send: FxHashMap::default(), -153 recv: FxHashMap::default(), -154 free_recv: Vec::new(), -155 next: [0, 0], -156 max: [0, 0], -157 max_remote: [max_remote_bi.into(), max_remote_uni.into()], -158 sent_max_remote: [max_remote_bi.into(), max_remote_uni.into()], -159 allocated_remote_count: [max_remote_bi.into(), max_remote_uni.into()], -160 max_concurrent_remote_count: [max_remote_bi.into(), max_remote_uni.into()], -161 flow_control_adjusted: false, -162 next_remote: [0, 0], -163 opened: [false, false], -164 next_reported_remote: [0, 0], -165 send_streams: 0, -166 pending: PendingStreamsQueue::new(), -167 events: VecDeque::new(), -168 connection_blocked: Vec::new(), -169 max_data: 0, -170 receive_window: receive_window.into(), -171 local_max_data: receive_window.into(), -172 sent_max_data: receive_window, -173 data_sent: 0, -174 data_recvd: 0, -175 unacked_data: 0, -176 send_window, -177 stream_receive_window: stream_receive_window.into(), -178 initial_max_stream_data_uni: 0u32.into(), -179 initial_max_stream_data_bidi_local: 0u32.into(), -180 initial_max_stream_data_bidi_remote: 0u32.into(), -181 receive_window_shrink_debt: 0, -182 }; -183 -184 for dir in Dir::iter() { -185 for i in 0..this.max_remote[dir as usize] { -186 this.insert(true, StreamId::new(!side, dir, i)); -187 } -188 } -189 -190 this -191 } +138 /// Whether the locally-initiated stream limit has been hit, per direction +139 pub(super) streams_blocked: [bool; 2], +140} +141 +142impl StreamsState { +143 #[allow(unreachable_pub)] // fuzzing only +144 pub fn new( +145 side: Side, +146 max_remote_uni: VarInt, +147 max_remote_bi: VarInt, +148 send_window: u64, +149 receive_window: VarInt, +150 stream_receive_window: VarInt, +151 ) -> Self { +152 let mut this = Self { +153 side, +154 send: FxHashMap::default(), +155 recv: FxHashMap::default(), +156 free_recv: Vec::new(), +157 next: [0, 0], +158 max: [0, 0], +159 max_remote: [max_remote_bi.into(), max_remote_uni.into()], +160 sent_max_remote: [max_remote_bi.into(), max_remote_uni.into()], +161 allocated_remote_count: [max_remote_bi.into(), max_remote_uni.into()], +162 max_concurrent_remote_count: [max_remote_bi.into(), max_remote_uni.into()], +163 flow_control_adjusted: false, +164 next_remote: [0, 0], +165 opened: [false, false], +166 next_reported_remote: [0, 0], +167 send_streams: 0, +168 pending: PendingStreamsQueue::new(), +169 events: VecDeque::new(), +170 connection_blocked: Vec::new(), +171 max_data: 0, +172 receive_window: receive_window.into(), +173 local_max_data: receive_window.into(), +174 sent_max_data: receive_window, +175 data_sent: 0, +176 data_recvd: 0, +177 unacked_data: 0, +178 send_window, +179 stream_receive_window: stream_receive_window.into(), +180 initial_max_stream_data_uni: 0u32.into(), +181 initial_max_stream_data_bidi_local: 0u32.into(), +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 pub(crate) fn set_params(&mut self, params: &TransportParameters) { -194 self.initial_max_stream_data_uni = params.initial_max_stream_data_uni; -195 self.initial_max_stream_data_bidi_local = params.initial_max_stream_data_bidi_local; -196 self.initial_max_stream_data_bidi_remote = params.initial_max_stream_data_bidi_remote; -197 self.max[Dir::Bi as usize] = params.initial_max_streams_bidi.into(); -198 self.max[Dir::Uni as usize] = params.initial_max_streams_uni.into(); -199 self.received_max_data(params.initial_max_data); -200 for i in 0..self.max_remote[Dir::Bi as usize] { -201 let id = StreamId::new(!self.side, Dir::Bi, i); -202 if let Some(s) = self.send.get_mut(&id).and_then(|s| s.as_mut()) { -203 s.max_data = params.initial_max_stream_data_bidi_local.into(); -204 } -205 } -206 } -207 -208 /// Ensure we have space for at least a full flow control window of remotely-initiated streams -209 /// to be open, and notify the peer if the window has moved -210 fn ensure_remote_streams(&mut self, dir: Dir) { -211 let new_count = self.max_concurrent_remote_count[dir as usize] -212 .saturating_sub(self.allocated_remote_count[dir as usize]); -213 for i in 0..new_count { -214 let id = StreamId::new(!self.side, dir, self.max_remote[dir as usize] + i); -215 self.insert(true, id); -216 } -217 self.allocated_remote_count[dir as usize] += new_count; -218 self.max_remote[dir as usize] += new_count; -219 } -220 -221 pub(crate) fn zero_rtt_rejected(&mut self) { -222 // Revert to initial state for outgoing streams -223 for dir in Dir::iter() { -224 for i in 0..self.next[dir as usize] { -225 // We don't bother calling `stream_freed` here because we explicitly reset affected -226 // counters below. -227 let id = StreamId::new(self.side, dir, i); -228 self.send.remove(&id).unwrap(); -229 if let Dir::Bi = dir { -230 self.recv.remove(&id).unwrap(); -231 } -232 } -233 self.next[dir as usize] = 0; -234 -235 // If 0-RTT was rejected, any flow control frames we sent were lost. -236 if self.flow_control_adjusted { -237 // Conservative approximation of whatever we sent in transport parameters -238 self.sent_max_remote[dir as usize] = 0; -239 } -240 } -241 -242 self.pending.clear(); -243 self.send_streams = 0; -244 self.data_sent = 0; -245 self.connection_blocked.clear(); -246 } -247 -248 /// Process incoming stream frame -249 /// -250 /// If successful, returns whether a `MAX_DATA` frame needs to be transmitted -251 pub(crate) fn received( -252 &mut self, -253 frame: frame::Stream, -254 payload_len: usize, -255 ) -> Result<ShouldTransmit, TransportError> { -256 let id = frame.id; -257 self.validate_receive_id(id).inspect_err(|_e| { -258 debug!("received illegal STREAM frame"); -259 })?; -260 -261 let Some(rs) = self -262 .recv -263 .get_mut(&id) -264 .map(get_or_insert_recv(self.stream_receive_window)) -265 else { -266 trace!("dropping frame for closed stream"); -267 return Ok(ShouldTransmit(false)); -268 }; -269 -270 if !rs.is_receiving() { -271 trace!("dropping frame for finished stream"); -272 return Ok(ShouldTransmit(false)); -273 } -274 -275 let (new_bytes, closed) = -276 rs.ingest(frame, payload_len, self.data_recvd, self.local_max_data)?; -277 self.data_recvd = self.data_recvd.saturating_add(new_bytes); -278 -279 if !rs.stopped { -280 self.on_stream_frame(true, id); -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 let Some(rs) = self -312 .recv -313 .get_mut(&id) -314 .map(get_or_insert_recv(self.stream_receive_window)) -315 else { -316 trace!("received RESET_STREAM on closed stream"); -317 return Ok(ShouldTransmit(false)); -318 }; -319 -320 // State transition -321 if !rs.reset( -322 error_code, -323 final_offset, -324 self.data_recvd, -325 self.local_max_data, -326 )? { -327 // Redundant reset -328 return Ok(ShouldTransmit(false)); -329 } -330 let bytes_read = rs.assembler.bytes_read(); -331 let stopped = rs.stopped; -332 let end = rs.end; -333 if stopped { -334 // Stopped streams should be disposed immediately on reset -335 let rs = self.recv.remove(&id).flatten().unwrap(); -336 self.stream_recv_freed(id, rs); -337 } -338 self.on_stream_frame(!stopped, id); -339 -340 // Update connection-level flow control -341 Ok(if bytes_read != final_offset.into_inner() { -342 // bytes_read is always <= end, so this won't underflow. -343 self.data_recvd = self -344 .data_recvd -345 .saturating_add(u64::from(final_offset) - end); -346 self.add_read_credits(u64::from(final_offset) - bytes_read) -347 } else { -348 ShouldTransmit(false) -349 }) -350 } -351 -352 /// Process incoming `STOP_SENDING` frame -353 #[allow(unreachable_pub)] // fuzzing only -354 pub fn received_stop_sending(&mut self, id: StreamId, error_code: VarInt) { -355 let max_send_data = self.max_send_data(id); -356 let Some(stream) = self -357 .send -358 .get_mut(&id) -359 .map(get_or_insert_send(max_send_data)) -360 else { -361 return; -362 }; -363 -364 if stream.try_stop(error_code) { -365 self.events -366 .push_back(StreamEvent::Stopped { id, error_code }); -367 self.on_stream_frame(false, id); -368 } -369 } -370 -371 pub(crate) fn reset_acked(&mut self, id: StreamId) { -372 match self.send.entry(id) { -373 hash_map::Entry::Vacant(_) => {} -374 hash_map::Entry::Occupied(e) => { -375 if let Some(SendState::ResetSent) = e.get().as_ref().map(|s| s.state) { -376 e.remove_entry(); -377 self.stream_freed(id, StreamHalf::Send); -378 } -379 } -380 } -381 } -382 -383 /// Whether any stream data is queued, regardless of control frames -384 pub(crate) fn can_send_stream_data(&self) -> bool { -385 // Reset streams may linger in the pending stream list, but will never produce stream frames -386 self.pending.iter().any(|stream| { -387 self.send -388 .get(&stream.id) -389 .and_then(|s| s.as_ref()) -390 .is_some_and(|s| !s.is_reset()) -391 }) -392 } -393 -394 /// Whether MAX_STREAM_DATA frames could be sent for stream `id` -395 pub(crate) fn can_send_flow_control(&self, id: StreamId) -> bool { -396 self.recv -397 .get(&id) -398 .and_then(|s| s.as_ref()) -399 .and_then(|s| s.as_open_recv()) -400 .is_some_and(|s| s.can_send_flow_control()) -401 } -402 -403 pub(in crate::connection) fn write_control_frames<'a, 'b>( -404 &mut self, -405 builder: &mut PacketBuilder<'a, 'b>, -406 pending: &mut Retransmits, -407 stats: &mut FrameStats, -408 ) { -409 // RESET_STREAM -410 while builder.frame_space_remaining() > frame::ResetStream::SIZE_BOUND { -411 let Some((id, error_code)) = pending.reset_stream.pop() else { -412 break; -413 }; -414 let Some(stream) = self.send.get_mut(&id).and_then(|s| s.as_mut()) else { -415 continue; +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 })?; +313 +314 let Some(rs) = self +315 .recv +316 .get_mut(&id) +317 .map(get_or_insert_recv(self.stream_receive_window)) +318 else { +319 trace!("received RESET_STREAM on closed stream"); +320 return Ok(ShouldTransmit(false)); +321 }; +322 +323 // State transition +324 if !rs.reset( +325 error_code, +326 final_offset, +327 self.data_recvd, +328 self.local_max_data, +329 )? { +330 // Redundant reset +331 return Ok(ShouldTransmit(false)); +332 } +333 let bytes_read = rs.assembler.bytes_read(); +334 let stopped = rs.stopped; +335 let end = rs.end; +336 if stopped { +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 frame = frame::ResetStream { -418 id, -419 error_code, -420 final_offset: VarInt::try_from(stream.offset()).expect("impossibly large offset"), -421 }; -422 builder.write_frame(frame, stats); -423 } -424 -425 // STOP_SENDING -426 while builder.frame_space_remaining() > frame::StopSending::SIZE_BOUND { -427 let Some(frame) = pending.stop_sending.pop() else { -428 break; -429 }; -430 // We may need to transmit STOP_SENDING even for streams whose state we have discarded, -431 // because we are able to discard local state for stopped streams immediately upon -432 // receiving FIN, even if the peer still has arbitrarily large amounts of data to -433 // (re)transmit due to loss or unconventional sending strategy. We could fine-tune this -434 // a little by dropping the frame if we specifically know the stream's been reset by the -435 // peer, but we discard that information as soon as the application consumes it, so it -436 // can't be relied upon regardless. -437 builder.write_frame(frame, stats); -438 } -439 -440 // MAX_DATA -441 if pending.max_data && builder.frame_space_remaining() > 9 { -442 pending.max_data = false; -443 -444 // `local_max_data` can grow bigger than `VarInt`. -445 // For transmission inside QUIC frames we need to clamp it to the -446 // maximum allowed `VarInt` size. -447 let max = VarInt::try_from(self.local_max_data).unwrap_or(VarInt::MAX); -448 -449 if max > self.sent_max_data { -450 // Record that a `MAX_DATA` announcing a certain window was sent. This will -451 // suppress enqueuing further `MAX_DATA` frames unless either the previous -452 // transmission was not acknowledged or the window further increased. -453 self.sent_max_data = max; -454 } -455 -456 builder.write_frame(frame::MaxData(max), stats); -457 } +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"), +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); +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 // MAX_STREAM_DATA -460 while builder.frame_space_remaining() > 17 { -461 let id = match pending.max_stream_data.iter().next() { -462 Some(x) => *x, -463 None => break, -464 }; -465 pending.max_stream_data.remove(&id); -466 let Some(rs) = self -467 .recv -468 .get_mut(&id) -469 .and_then(|s| s.as_mut()) -470 .and_then(|s| s.as_open_recv_mut()) -471 else { -472 continue; -473 }; -474 if !rs.can_send_flow_control() { +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 -478 let (max, _) = rs.max_stream_data(self.stream_receive_window); -479 rs.record_sent_max_stream_data(max); -480 builder.write_frame(frame::MaxStreamData { id, offset: max }, stats); -481 } -482 -483 // MAX_STREAMS -484 for dir in Dir::iter() { -485 if !pending.max_stream_id[dir as usize] || builder.frame_space_remaining() <= 9 { -486 continue; -487 } -488 -489 pending.max_stream_id[dir as usize] = false; -490 self.sent_max_remote[dir as usize] = self.max_remote[dir as usize]; -491 let count = self.max_remote[dir as usize]; -492 builder.write_frame(frame::MaxStreams { dir, count }, stats); -493 } -494 } -495 -496 pub(in crate::connection) fn write_stream_frames<'a, 'b>( -497 &mut self, -498 builder: &mut PacketBuilder<'a, 'b>, -499 fair: bool, -500 stats: &mut FrameStats, -501 ) { -502 while builder.frame_space_remaining() > frame::Stream::SIZE_BOUND { -503 // Pop the stream of the highest priority that currently has pending data. If -504 // the stream still has some pending data left after writing, it will be -505 // reinserted, otherwise not -506 let Some(stream) = self.pending.pop() else { -507 break; -508 }; -509 -510 let id = stream.id; -511 -512 let Some(stream) = self.send.get_mut(&id).and_then(|s| s.as_mut()) else { -513 // Stream was reset with pending data and the reset was acknowledged -514 continue; -515 }; -516 -517 // Reset streams aren't removed from the pending list and still exist while the peer -518 // hasn't acknowledged the reset, but should not generate STREAM frames, so we need to -519 // check for them explicitly. -520 if stream.is_reset() { -521 continue; -522 } -523 -524 // Now that we know the `StreamId`, we can better account for how many bytes -525 // are required to encode it. -526 let max_buf_size = builder.frame_space_remaining() - 1 - VarInt::size(id.into()); -527 let (offsets, encode_length) = stream.pending.poll_transmit(max_buf_size); -528 let fin = offsets.end == stream.pending.offset() -529 && matches!(stream.state, SendState::DataSent { .. }); -530 if fin { -531 stream.fin_pending = false; -532 } -533 -534 if stream.is_pending() { -535 // If the stream still has pending data, reinsert it, possibly with an updated priority value -536 // Fairness with other streams is achieved by implementing round-robin scheduling, -537 // so that the other streams will have a chance to write data -538 // before we touch this stream again. -539 if fair { -540 self.pending.push_pending(id, stream.priority); -541 } else { -542 self.pending.reinsert_pending(id, stream.priority); -543 } -544 } -545 -546 let range = offsets.clone(); -547 let meta = frame::StreamMeta { id, offsets, fin }; -548 builder.write_frame(meta.encoder(encode_length), stats); -549 stream.pending.get_into(range, builder.buf); -550 } -551 } +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 } +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 }; +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 #[cfg(test)] -554 fn write_frames_for_test(&mut self, capacity: usize, fair: bool) -> frame::StreamMetaVec { -555 let buf = &mut Vec::with_capacity(capacity); -556 let mut tbuf = crate::connection::TransmitBuf::new(buf, std::num::NonZeroUsize::MIN, 1_200); -557 tbuf.start_new_datagram_with_size(capacity); -558 let builder = &mut PacketBuilder::simple_data_buf(&mut tbuf); -559 let stats = &mut FrameStats::default(); -560 self.write_stream_frames(builder, fair, stats); -561 builder.sent_frames().stream_frames.clone() -562 } -563 -564 /// Notify the application that new streams were opened or a stream became readable. -565 fn on_stream_frame(&mut self, notify_readable: bool, stream: StreamId) { -566 if stream.initiator() == self.side { -567 // Notifying about the opening of locally-initiated streams would be redundant. -568 if notify_readable { -569 self.events.push_back(StreamEvent::Readable { id: stream }); -570 } -571 return; -572 } -573 let next = &mut self.next_remote[stream.dir() as usize]; -574 if stream.index() >= *next { -575 *next = stream.index() + 1; -576 self.opened[stream.dir() as usize] = true; -577 } else if notify_readable { -578 self.events.push_back(StreamEvent::Readable { id: stream }); -579 } -580 } -581 -582 pub(crate) fn received_ack_of(&mut self, frame: frame::StreamMeta) { -583 let mut entry = match self.send.entry(frame.id) { -584 hash_map::Entry::Vacant(_) => return, -585 hash_map::Entry::Occupied(e) => e, -586 }; -587 -588 let Some(stream) = entry.get_mut().as_mut() else { -589 // Because we only call this after sending data on this stream, -590 // this closure should be unreachable. If we did somehow screw that up, -591 // then we might hit an underflow below with unpredictable effects down -592 // the line. Best to short-circuit. -593 return; -594 }; -595 -596 if stream.is_reset() { -597 // We account for outstanding data on reset streams at time of reset -598 return; -599 } -600 let id = frame.id; -601 self.unacked_data -= frame.offsets.end - frame.offsets.start; -602 if !stream.ack(frame) { -603 // The stream is unfinished or may still need retransmits -604 return; -605 } +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 entry.remove_entry(); -608 self.stream_freed(id, StreamHalf::Send); -609 self.events.push_back(StreamEvent::Finished { id }); -610 } -611 -612 pub(crate) fn retransmit(&mut self, frame: frame::StreamMeta) { -613 let Some(stream) = self.send.get_mut(&frame.id).and_then(|s| s.as_mut()) else { -614 // Loss of data on a closed stream is a noop -615 return; -616 }; -617 if !stream.is_pending() { -618 self.pending.push_pending(frame.id, stream.priority); -619 } -620 stream.fin_pending |= frame.fin; -621 stream.pending.retransmit(frame.offsets); -622 } -623 -624 pub(crate) fn retransmit_all_for_0rtt(&mut self) { -625 for dir in Dir::iter() { -626 for index in 0..self.next[dir as usize] { -627 let id = StreamId::new(Side::Client, dir, index); -628 let Some(stream) = self.send.get_mut(&id).and_then(|s| s.as_mut()) else { -629 continue; -630 }; -631 if stream.pending.is_fully_acked() && !stream.fin_pending { -632 // Stream data can't be acked in 0-RTT, so we must not have sent anything on -633 // this stream -634 continue; -635 } -636 if !stream.is_pending() { -637 self.pending.push_pending(id, stream.priority); -638 } -639 stream.pending.retransmit_all_for_0rtt(); -640 } -641 } -642 } -643 -644 pub(crate) fn received_max_streams( -645 &mut self, -646 dir: Dir, -647 count: u64, -648 ) -> Result<(), TransportError> { -649 if count > MAX_STREAM_COUNT { -650 return Err(TransportError::FRAME_ENCODING_ERROR( -651 "unrepresentable stream limit", -652 )); -653 } -654 -655 let current = &mut self.max[dir as usize]; -656 if count > *current { -657 *current = count; -658 self.events.push_back(StreamEvent::Available { dir }); -659 } -660 -661 Ok(()) -662 } -663 -664 /// Handle increase to connection-level flow control limit -665 pub(crate) fn received_max_data(&mut self, n: VarInt) { -666 self.max_data = self.max_data.max(n.into()); -667 } -668 -669 pub(crate) fn received_max_stream_data( -670 &mut self, -671 id: StreamId, -672 offset: u64, -673 ) -> Result<(), TransportError> { -674 if id.initiator() != self.side && id.dir() == Dir::Uni { -675 debug!("got MAX_STREAM_DATA on recv-only {}", id); -676 return Err(TransportError::STREAM_STATE_ERROR( -677 "MAX_STREAM_DATA on recv-only stream", -678 )); +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 let write_limit = self.write_limit(); -682 let max_send_data = self.max_send_data(id); -683 if let Some(ss) = self -684 .send -685 .get_mut(&id) -686 .map(get_or_insert_send(max_send_data)) -687 { -688 if ss.increase_max_data(offset) { -689 if write_limit > 0 { -690 self.events.push_back(StreamEvent::Writable { id }); -691 } else if !ss.connection_blocked { -692 // The stream is still blocked on the connection flow control -693 // window. In order to get unblocked when the window relaxes -694 // it needs to be in the connection blocked list. -695 ss.connection_blocked = true; -696 self.connection_blocked.push(id); -697 } -698 } -699 } else if id.initiator() == self.side && self.is_local_unopened(id) { -700 debug!("got MAX_STREAM_DATA on unopened {}", id); -701 return Err(TransportError::STREAM_STATE_ERROR( -702 "MAX_STREAM_DATA on unopened stream", -703 )); -704 } -705 -706 self.on_stream_frame(false, id); -707 Ok(()) -708 } -709 -710 /// Returns the maximum amount of data this is allowed to be written on the connection -711 pub(crate) fn write_limit(&self) -> u64 { -712 (self.max_data - self.data_sent) -713 // `send_window` can be set after construction to something *less* than `unacked_data` -714 .min(self.send_window.saturating_sub(self.unacked_data)) -715 } -716 -717 /// Yield stream events -718 pub(crate) fn poll(&mut self) -> Option<StreamEvent> { -719 if let Some(dir) = Dir::iter().find(|&i| mem::replace(&mut self.opened[i as usize], false)) -720 { -721 return Some(StreamEvent::Opened { dir }); -722 } -723 -724 if self.write_limit() > 0 { -725 while let Some(id) = self.connection_blocked.pop() { -726 let Some(stream) = self.send.get_mut(&id).and_then(|s| s.as_mut()) else { -727 continue; -728 }; +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 } +725 +726 self.on_stream_frame(false, id); +727 Ok(()) +728 } 729 -730 debug_assert!(stream.connection_blocked); -731 stream.connection_blocked = false; -732 -733 // If it's no longer sensible to write to a stream (even to detect an error) then don't -734 // report it. -735 if stream.is_writable() && stream.max_data > stream.offset() { -736 return Some(StreamEvent::Writable { id }); -737 } -738 } -739 } -740 -741 self.events.pop_front() -742 } +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 /// Queues MAX_STREAM_ID frames in `pending` if needed -745 /// -746 /// Returns whether any frames were queued. -747 pub(crate) fn queue_max_stream_id(&mut self, pending: &mut Retransmits) -> bool { -748 let mut queued = false; -749 for dir in Dir::iter() { -750 let diff = self.max_remote[dir as usize] - self.sent_max_remote[dir as usize]; -751 // To reduce traffic, only announce updates if at least 1/8 of the flow control window -752 // has been consumed. -753 if diff > self.max_concurrent_remote_count[dir as usize] / 8 { -754 pending.max_stream_id[dir as usize] = true; -755 queued = true; -756 } -757 } -758 queued -759 } +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 }; +749 +750 debug_assert!(stream.connection_blocked); +751 stream.connection_blocked = false; +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 /// Check for errors entailed by the peer's use of `id` as a send stream -762 fn validate_receive_id(&mut self, id: StreamId) -> Result<(), TransportError> { -763 if self.side == id.initiator() { -764 match id.dir() { -765 Dir::Uni => { -766 return Err(TransportError::STREAM_STATE_ERROR( -767 "illegal operation on send-only stream", -768 )); -769 } -770 Dir::Bi if id.index() >= self.next[Dir::Bi as usize] => { -771 return Err(TransportError::STREAM_STATE_ERROR( -772 "operation on unopened stream", -773 )); -774 } -775 Dir::Bi => {} -776 }; -777 } else { -778 let limit = self.max_remote[id.dir() as usize]; -779 if id.index() >= limit { -780 return Err(TransportError::STREAM_LIMIT_ERROR("")); -781 } -782 } -783 Ok(()) -784 } -785 -786 /// Whether a locally initiated stream has never been open -787 pub(crate) fn is_local_unopened(&self, id: StreamId) -> bool { -788 id.index() >= self.next[id.dir() as usize] -789 } -790 -791 pub(crate) fn set_max_concurrent(&mut self, dir: Dir, count: VarInt) { -792 self.flow_control_adjusted = true; -793 self.max_concurrent_remote_count[dir as usize] = count.into(); -794 self.ensure_remote_streams(dir); -795 } -796 -797 pub(crate) fn max_concurrent(&self, dir: Dir) -> u64 { -798 self.allocated_remote_count[dir as usize] -799 } -800 -801 pub(crate) fn set_send_window(&mut self, send_window: u64) { -802 self.send_window = send_window; -803 } -804 -805 /// Set the receive_window and returns whether the receive_window has been -806 /// expanded or shrunk: true if expanded, false if shrunk. -807 pub(crate) fn set_receive_window(&mut self, receive_window: VarInt) -> bool { -808 let receive_window = receive_window.into(); -809 let mut expanded = false; -810 if receive_window > self.receive_window { -811 self.local_max_data = self -812 .local_max_data -813 .saturating_add(receive_window - self.receive_window); -814 expanded = true; -815 } else { -816 let diff = self.receive_window - receive_window; -817 self.receive_window_shrink_debt = self.receive_window_shrink_debt.saturating_add(diff); -818 } -819 self.receive_window = receive_window; -820 expanded -821 } -822 -823 pub(super) fn insert(&mut self, remote: bool, id: StreamId) { -824 let bi = id.dir() == Dir::Bi; -825 // bidirectional OR (unidirectional AND NOT remote) -826 if bi || !remote { -827 assert!(self.send.insert(id, None).is_none()); -828 } -829 // bidirectional OR (unidirectional AND remote) -830 if bi || remote { -831 let recv = self.free_recv.pop(); -832 assert!(self.recv.insert(id, recv).is_none()); -833 } -834 } -835 -836 /// Adds credits to the connection flow control window -837 /// -838 /// Returns whether a `MAX_DATA` frame should be enqueued as soon as possible. -839 /// This will only be the case if the window update would is significant -840 /// enough. As soon as a window update with a `MAX_DATA` frame has been -841 /// queued, the [`Recv::record_sent_max_stream_data`] function should be called to -842 /// suppress sending further updates until the window increases significantly -843 /// again. -844 pub(super) fn add_read_credits(&mut self, credits: u64) -> ShouldTransmit { -845 if credits > self.receive_window_shrink_debt { -846 let net_credits = credits - self.receive_window_shrink_debt; -847 self.local_max_data = self.local_max_data.saturating_add(net_credits); -848 self.receive_window_shrink_debt = 0; -849 } else { -850 self.receive_window_shrink_debt -= credits; -851 } -852 -853 if self.local_max_data > VarInt::MAX.into_inner() { -854 return ShouldTransmit(false); -855 } -856 -857 // Only announce a window update if it's significant enough -858 // to make it worthwhile sending a MAX_DATA frame. -859 // We use a fraction of the configured connection receive window to make -860 // the decision, to accommodate for connection using bigger windows requiring -861 // less updates. -862 let diff = self.local_max_data - self.sent_max_data.into_inner(); -863 ShouldTransmit(diff >= (self.receive_window / 8)) -864 } -865 -866 /// Update counters for removal of a stream -867 pub(super) fn stream_freed(&mut self, id: StreamId, half: StreamHalf) { -868 if id.initiator() != self.side { -869 let fully_free = id.dir() == Dir::Uni -870 || match half { -871 StreamHalf::Send => !self.recv.contains_key(&id), -872 StreamHalf::Recv => !self.send.contains_key(&id), -873 }; -874 if fully_free { -875 self.allocated_remote_count[id.dir() as usize] -= 1; -876 self.ensure_remote_streams(id.dir()); -877 } -878 } -879 if half == StreamHalf::Send { -880 self.send_streams -= 1; -881 } -882 } -883 -884 pub(super) fn stream_recv_freed(&mut self, id: StreamId, recv: StreamRecv) { -885 self.free_recv.push(recv.free(self.stream_receive_window)); -886 self.stream_freed(id, StreamHalf::Recv); -887 } -888 -889 pub(super) fn max_send_data(&self, id: StreamId) -> VarInt { -890 let remote = self.side != id.initiator(); -891 match id.dir() { -892 Dir::Uni => self.initial_max_stream_data_uni, -893 // Remote/local appear reversed here because the transport parameters are named from -894 // the perspective of the peer. -895 Dir::Bi if remote => self.initial_max_stream_data_bidi_local, -896 Dir::Bi => self.initial_max_stream_data_bidi_remote, -897 } -898 } -899} -900 -901#[inline] -902pub(super) fn get_or_insert_send( -903 max_data: VarInt, -904) -> impl Fn(&mut Option<Box<Send>>) -> &mut Box<Send> { -905 move |opt| opt.get_or_insert_with(|| Send::new(max_data)) -906} -907 -908#[inline] -909pub(super) fn get_or_insert_recv( -910 initial_max_data: u64, -911) -> impl FnMut(&mut Option<StreamRecv>) -> &mut Recv { -912 move |opt| { -913 *opt = opt.take().map(|s| match s { -914 StreamRecv::Free(recv) => StreamRecv::Open(recv), -915 s => s, -916 }); -917 opt.get_or_insert_with(|| StreamRecv::Open(Recv::new(initial_max_data))) -918 .as_open_recv_mut() -919 .unwrap() -920 } -921} -922 -923#[cfg(test)] -924mod tests { -925 use super::*; -926 use crate::{ -927 ReadableError, RecvStream, SendStream, TransportErrorCode, WriteError, -928 connection::State as ConnState, connection::Streams, -929 }; -930 use bytes::Bytes; -931 -932 fn make(side: Side) -> StreamsState { -933 StreamsState::new( -934 side, -935 128u32.into(), -936 128u32.into(), -937 1024 * 1024, -938 (1024 * 1024u32).into(), -939 (1024 * 1024u32).into(), -940 ) -941 } +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(()) +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 +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; +871 } +872 +873 if self.local_max_data > VarInt::MAX.into_inner() { +874 return ShouldTransmit(false); +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 #[test] -944 fn trivial_flow_control() { -945 let mut client = StreamsState::new( -946 Side::Client, -947 1u32.into(), -948 1u32.into(), -949 1024 * 1024, -950 (1024 * 1024u32).into(), -951 (1024 * 1024u32).into(), -952 ); -953 let id = StreamId::new(Side::Server, Dir::Uni, 0); -954 let initial_max = client.local_max_data; -955 const MESSAGE_SIZE: usize = 2048; -956 assert_eq!( -957 client -958 .received( -959 frame::Stream { -960 id, -961 offset: 0, -962 fin: true, -963 data: Bytes::from_static(&[0; MESSAGE_SIZE]), -964 }, -965 2048 -966 ) -967 .unwrap(), -968 ShouldTransmit(false) -969 ); -970 assert_eq!(client.data_recvd, 2048); -971 assert_eq!(client.local_max_data - initial_max, 0); -972 -973 let mut pending = Retransmits::default(); -974 let mut recv = RecvStream { -975 id, -976 state: &mut client, -977 pending: &mut pending, -978 }; -979 -980 let mut chunks = recv.read(true).unwrap(); -981 assert_eq!( -982 chunks.next(MESSAGE_SIZE).unwrap().unwrap().bytes.len(), -983 MESSAGE_SIZE -984 ); -985 assert!(chunks.next(0).unwrap().is_none()); -986 let should_transmit = chunks.finalize(); -987 assert!(should_transmit.0); -988 assert!(pending.max_stream_id[Dir::Uni as usize]); -989 assert_eq!(client.local_max_data - initial_max, MESSAGE_SIZE as u64); -990 } -991 -992 #[test] -993 fn reset_flow_control() { -994 let mut client = make(Side::Client); -995 let id = StreamId::new(Side::Server, Dir::Uni, 0); -996 let initial_max = client.local_max_data; -997 assert_eq!( -998 client -999 .received( -1000 frame::Stream { -1001 id, -1002 offset: 0, -1003 fin: false, -1004 data: Bytes::from_static(&[0; 2048]), -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 chunks.next(1024).unwrap(); -1023 let _ = chunks.finalize(); -1024 assert_eq!(client.local_max_data - initial_max, 1024); -1025 assert_eq!( -1026 client -1027 .received_reset(frame::ResetStream { -1028 id, -1029 error_code: 0u32.into(), -1030 final_offset: 4096u32.into(), -1031 }) -1032 .unwrap(), -1033 ShouldTransmit(false) -1034 ); -1035 -1036 assert_eq!(client.data_recvd, 4096); -1037 assert_eq!(client.local_max_data - initial_max, 4096); -1038 -1039 // Ensure reading after a reset doesn't issue redundant credit -1040 let mut recv = RecvStream { -1041 id, -1042 state: &mut client, -1043 pending: &mut pending, -1044 }; -1045 let mut chunks = recv.read(true).unwrap(); -1046 assert_eq!( -1047 chunks.next(1024).unwrap_err(), -1048 crate::ReadError::Reset(0u32.into()) -1049 ); -1050 let _ = chunks.finalize(); -1051 assert_eq!(client.data_recvd, 4096); -1052 assert_eq!(client.local_max_data - initial_max, 4096); -1053 } -1054 -1055 #[test] -1056 fn reset_after_empty_frame_flow_control() { -1057 let mut client = make(Side::Client); -1058 let id = StreamId::new(Side::Server, Dir::Uni, 0); -1059 let initial_max = client.local_max_data; -1060 assert_eq!( -1061 client -1062 .received( -1063 frame::Stream { -1064 id, -1065 offset: 4096, -1066 fin: false, -1067 data: Bytes::from_static(&[0; 0]), -1068 }, -1069 0 -1070 ) -1071 .unwrap(), -1072 ShouldTransmit(false) -1073 ); -1074 assert_eq!(client.data_recvd, 4096); -1075 assert_eq!(client.local_max_data - initial_max, 0); -1076 assert_eq!( -1077 client -1078 .received_reset(frame::ResetStream { -1079 id, -1080 error_code: 0u32.into(), -1081 final_offset: 4096u32.into(), -1082 }) -1083 .unwrap(), -1084 ShouldTransmit(false) -1085 ); -1086 assert_eq!(client.data_recvd, 4096); -1087 assert_eq!(client.local_max_data - initial_max, 4096); -1088 } -1089 -1090 #[test] -1091 fn duplicate_reset_flow_control() { -1092 let mut client = make(Side::Client); -1093 let id = StreamId::new(Side::Server, Dir::Uni, 0); -1094 assert_eq!( -1095 client -1096 .received_reset(frame::ResetStream { -1097 id, -1098 error_code: 0u32.into(), -1099 final_offset: 4096u32.into(), -1100 }) -1101 .unwrap(), -1102 ShouldTransmit(false) -1103 ); -1104 assert_eq!(client.data_recvd, 4096); -1105 assert_eq!( -1106 client -1107 .received_reset(frame::ResetStream { -1108 id, -1109 error_code: 0u32.into(), -1110 final_offset: 4096u32.into(), -1111 }) -1112 .unwrap(), -1113 ShouldTransmit(false) -1114 ); -1115 assert_eq!(client.data_recvd, 4096); -1116 } -1117 -1118 #[test] -1119 fn recv_stopped() { -1120 let mut client = make(Side::Client); -1121 let id = StreamId::new(Side::Server, Dir::Uni, 0); -1122 let initial_max = client.local_max_data; -1123 assert_eq!( -1124 client -1125 .received( -1126 frame::Stream { -1127 id, -1128 offset: 0, -1129 fin: false, -1130 data: Bytes::from_static(&[0; 32]), -1131 }, -1132 32 -1133 ) -1134 .unwrap(), -1135 ShouldTransmit(false) -1136 ); -1137 assert_eq!(client.local_max_data, initial_max); -1138 -1139 let mut pending = Retransmits::default(); -1140 let mut recv = RecvStream { -1141 id, -1142 state: &mut client, -1143 pending: &mut pending, -1144 }; -1145 -1146 recv.stop(0u32.into()).unwrap(); -1147 assert_eq!(recv.pending.stop_sending.len(), 1); -1148 assert!(!recv.pending.max_data); -1149 -1150 assert!(recv.stop(0u32.into()).is_err()); -1151 assert_eq!(recv.read(true).err(), Some(ReadableError::ClosedStream)); -1152 assert_eq!(recv.read(false).err(), Some(ReadableError::ClosedStream)); -1153 -1154 assert_eq!(client.local_max_data - initial_max, 32); -1155 assert_eq!( -1156 client -1157 .received( -1158 frame::Stream { -1159 id, -1160 offset: 32, -1161 fin: true, -1162 data: Bytes::from_static(&[0; 16]), -1163 }, -1164 16 -1165 ) -1166 .unwrap(), -1167 ShouldTransmit(false) -1168 ); -1169 assert_eq!(client.local_max_data - initial_max, 48); -1170 assert!(!client.recv.contains_key(&id)); -1171 } -1172 -1173 #[test] -1174 fn stopped_reset() { -1175 let mut client = make(Side::Client); -1176 let id = StreamId::new(Side::Server, Dir::Uni, 0); -1177 // Server opens stream -1178 assert_eq!( -1179 client -1180 .received( -1181 frame::Stream { -1182 id, -1183 offset: 0, -1184 fin: false, -1185 data: Bytes::from_static(&[0; 32]) -1186 }, -1187 32 -1188 ) -1189 .unwrap(), -1190 ShouldTransmit(false) -1191 ); +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 ) +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(); +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); +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 let mut pending = Retransmits::default(); -1194 let mut recv = RecvStream { -1195 id, -1196 state: &mut client, -1197 pending: &mut pending, -1198 }; -1199 -1200 recv.stop(0u32.into()).unwrap(); -1201 assert_eq!(pending.stop_sending.len(), 1); -1202 assert!(!pending.max_data); -1203 -1204 // Server complies -1205 let prev_max = client.max_remote[Dir::Uni as usize]; -1206 assert_eq!( -1207 client -1208 .received_reset(frame::ResetStream { -1209 id, -1210 error_code: 0u32.into(), -1211 final_offset: 32u32.into(), -1212 }) -1213 .unwrap(), -1214 ShouldTransmit(false) -1215 ); -1216 assert!(!client.recv.contains_key(&id), "stream state is freed"); -1217 assert_eq!(client.max_remote[Dir::Uni as usize], prev_max + 1); -1218 } +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 #[test] -1221 fn send_stopped() { -1222 let mut server = make(Side::Server); -1223 server.set_params(&TransportParameters { -1224 initial_max_streams_uni: 1u32.into(), -1225 initial_max_data: 42u32.into(), -1226 initial_max_stream_data_uni: 42u32.into(), -1227 ..TransportParameters::default() -1228 }); -1229 -1230 let (mut pending, state) = (Retransmits::default(), ConnState::established()); -1231 let id = Streams { -1232 state: &mut server, -1233 conn_state: &state, -1234 } -1235 .open(Dir::Uni) -1236 .unwrap(); -1237 -1238 let mut stream = SendStream { -1239 id, -1240 state: &mut server, -1241 pending: &mut pending, -1242 conn_state: &state, -1243 }; -1244 -1245 let error_code = 0u32.into(); -1246 stream.state.received_stop_sending(id, error_code); -1247 assert!( -1248 stream -1249 .state -1250 .events -1251 .contains(&StreamEvent::Stopped { id, error_code }) -1252 ); -1253 stream.state.events.clear(); -1254 -1255 assert_eq!(stream.write(&[]), Err(WriteError::Stopped(error_code))); -1256 -1257 stream.reset(0u32.into()).unwrap(); -1258 assert_eq!(stream.write(&[]), Err(WriteError::ClosedStream)); -1259 -1260 // A duplicate frame is a no-op -1261 stream.state.received_stop_sending(id, error_code); -1262 assert!(stream.state.events.is_empty()); -1263 } +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 #[test] -1266 fn final_offset_flow_control() { -1267 let mut client = make(Side::Client); -1268 assert_eq!( -1269 client -1270 .received_reset(frame::ResetStream { -1271 id: StreamId::new(Side::Server, Dir::Uni, 0), -1272 error_code: 0u32.into(), -1273 final_offset: VarInt::MAX, -1274 }) -1275 .unwrap_err() -1276 .code, -1277 TransportErrorCode::FLOW_CONTROL_ERROR -1278 ); -1279 } -1280 -1281 #[test] -1282 fn stream_priority() { -1283 let mut server = make(Side::Server); -1284 server.set_params(&TransportParameters { -1285 initial_max_streams_bidi: 3u32.into(), -1286 initial_max_data: 10u32.into(), -1287 initial_max_stream_data_bidi_remote: 10u32.into(), -1288 ..TransportParameters::default() -1289 }); -1290 -1291 let (mut pending, state) = (Retransmits::default(), ConnState::established()); -1292 let mut streams = Streams { -1293 state: &mut server, -1294 conn_state: &state, -1295 }; -1296 -1297 let id_high = streams.open(Dir::Bi).unwrap(); -1298 let id_mid = streams.open(Dir::Bi).unwrap(); -1299 let id_low = streams.open(Dir::Bi).unwrap(); +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 } 1300 -1301 let mut mid = SendStream { -1302 id: id_mid, -1303 state: &mut server, -1304 pending: &mut pending, -1305 conn_state: &state, -1306 }; -1307 mid.write(b"mid").unwrap(); -1308 -1309 let mut low = SendStream { -1310 id: id_low, -1311 state: &mut server, -1312 pending: &mut pending, -1313 conn_state: &state, -1314 }; -1315 low.set_priority(-1).unwrap(); -1316 low.write(b"low").unwrap(); -1317 -1318 let mut high = SendStream { -1319 id: id_high, -1320 state: &mut server, -1321 pending: &mut pending, -1322 conn_state: &state, -1323 }; -1324 high.set_priority(1).unwrap(); -1325 high.write(b"high").unwrap(); -1326 -1327 let meta = server.write_frames_for_test(40, true); -1328 assert_eq!(meta[0].id, id_high); -1329 assert_eq!(meta[1].id, id_mid); -1330 assert_eq!(meta[2].id, id_low); -1331 -1332 assert!(!server.can_send_stream_data()); -1333 assert_eq!(server.pending.len(), 0); -1334 } -1335 -1336 #[test] -1337 fn requeue_stream_priority() { -1338 let mut server = make(Side::Server); -1339 server.set_params(&TransportParameters { -1340 initial_max_streams_bidi: 3u32.into(), -1341 initial_max_data: 1000u32.into(), -1342 initial_max_stream_data_bidi_remote: 1000u32.into(), -1343 ..TransportParameters::default() -1344 }); -1345 -1346 let (mut pending, state) = (Retransmits::default(), ConnState::established()); -1347 let mut streams = Streams { -1348 state: &mut server, -1349 conn_state: &state, -1350 }; +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(); +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 let id_high = streams.open(Dir::Bi).unwrap(); -1353 let id_mid = streams.open(Dir::Bi).unwrap(); -1354 -1355 let mut mid = SendStream { -1356 id: id_mid, -1357 state: &mut server, -1358 pending: &mut pending, -1359 conn_state: &state, -1360 }; -1361 assert_eq!(mid.write(b"mid").unwrap(), 3); -1362 assert_eq!(server.pending.len(), 1); -1363 -1364 let mut high = SendStream { -1365 id: id_high, -1366 state: &mut server, -1367 pending: &mut pending, -1368 conn_state: &state, -1369 }; -1370 high.set_priority(1).unwrap(); -1371 assert_eq!(high.write(&[0; 200]).unwrap(), 200); -1372 assert_eq!(server.pending.len(), 2); -1373 -1374 // Requeue the high priority stream to lowest priority. The initial send -1375 // still uses high priority since it's queued that way. After that it will -1376 // switch to low priority -1377 let mut high = SendStream { -1378 id: id_high, -1379 state: &mut server, -1380 pending: &mut pending, -1381 conn_state: &state, -1382 }; -1383 high.set_priority(-1).unwrap(); -1384 -1385 let meta = server.write_frames_for_test(40, true); -1386 assert_eq!(meta.len(), 1); -1387 assert_eq!(meta[0].id, id_high); -1388 -1389 // After requeuing we should end up with 2 priorities - not 3 -1390 assert_eq!(server.pending.len(), 2); -1391 -1392 // Send the remaining data. The initial mid priority one should go first now -1393 let meta = server.write_frames_for_test(1000 - 40, true); -1394 assert_eq!(meta.len(), 2); -1395 assert_eq!(meta[0].id, id_mid); -1396 assert_eq!(meta[1].id, id_high); -1397 -1398 assert!(!server.can_send_stream_data()); -1399 assert_eq!(server.pending.len(), 0); -1400 } -1401 -1402 #[test] -1403 fn same_stream_priority() { -1404 for fair in [true, false] { -1405 let mut server = make(Side::Server); -1406 server.set_params(&TransportParameters { -1407 initial_max_streams_bidi: 3u32.into(), -1408 initial_max_data: 300u32.into(), -1409 initial_max_stream_data_bidi_remote: 300u32.into(), -1410 ..TransportParameters::default() -1411 }); -1412 -1413 let (mut pending, state) = (Retransmits::default(), ConnState::established()); -1414 let mut streams = Streams { -1415 state: &mut server, -1416 conn_state: &state, -1417 }; -1418 -1419 // a, b and c all have the same priority -1420 let id_a = streams.open(Dir::Bi).unwrap(); -1421 let id_b = streams.open(Dir::Bi).unwrap(); -1422 let id_c = streams.open(Dir::Bi).unwrap(); -1423 -1424 let mut stream_a = SendStream { -1425 id: id_a, -1426 state: &mut server, -1427 pending: &mut pending, -1428 conn_state: &state, -1429 }; -1430 stream_a.write(&[b'a'; 100]).unwrap(); -1431 -1432 let mut stream_b = SendStream { -1433 id: id_b, -1434 state: &mut server, -1435 pending: &mut pending, +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(); +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 }); +1432 +1433 let (mut pending, state) = (Retransmits::default(), ConnState::established()); +1434 let mut streams = Streams { +1435 state: &mut server, 1436 conn_state: &state, 1437 }; -1438 stream_b.write(&[b'b'; 100]).unwrap(); -1439 -1440 let mut stream_c = SendStream { -1441 id: id_c, -1442 state: &mut server, -1443 pending: &mut pending, -1444 conn_state: &state, -1445 }; -1446 stream_c.write(&[b'c'; 100]).unwrap(); -1447 -1448 let mut metas = vec![]; -1449 -1450 // loop until all the streams are written -1451 loop { -1452 let meta = server.write_frames_for_test(40, fair); -1453 if meta.is_empty() { -1454 break; -1455 } -1456 metas.extend(meta); -1457 } -1458 -1459 assert!(!server.can_send_stream_data()); -1460 assert_eq!(server.pending.len(), 0); -1461 -1462 let stream_ids = metas.iter().map(|m| m.id).collect::<Vec<_>>(); -1463 if fair { -1464 // When fairness is enabled, if we run out of buffer space to write out a stream, -1465 // the stream is re-queued after all the streams with the same priority. -1466 assert_eq!( -1467 stream_ids, -1468 vec![id_a, id_b, id_c, id_a, id_b, id_c, id_a, id_b, id_c] -1469 ); -1470 } else { -1471 // When fairness is disabled the stream is re-queued before all the other streams -1472 // with the same priority. -1473 assert_eq!( -1474 stream_ids, -1475 vec![id_a, id_a, id_a, id_b, id_b, id_b, id_c, id_c, id_c] -1476 ); +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(); +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 } -1480 -1481 #[test] -1482 fn unfair_priority_bump() { -1483 let mut server = make(Side::Server); -1484 server.set_params(&TransportParameters { -1485 initial_max_streams_bidi: 3u32.into(), -1486 initial_max_data: 300u32.into(), -1487 initial_max_stream_data_bidi_remote: 300u32.into(), -1488 ..TransportParameters::default() -1489 }); -1490 -1491 let (mut pending, state) = (Retransmits::default(), ConnState::established()); -1492 let mut streams = Streams { -1493 state: &mut server, -1494 conn_state: &state, -1495 }; -1496 -1497 // a, and b have the same priority, c has higher priority -1498 let id_a = streams.open(Dir::Bi).unwrap(); -1499 let id_b = streams.open(Dir::Bi).unwrap(); -1500 let id_c = streams.open(Dir::Bi).unwrap(); -1501 -1502 let mut stream_a = SendStream { -1503 id: id_a, -1504 state: &mut server, -1505 pending: &mut pending, -1506 conn_state: &state, -1507 }; -1508 stream_a.write(&[b'a'; 100]).unwrap(); -1509 -1510 let mut stream_b = SendStream { -1511 id: id_b, -1512 state: &mut server, -1513 pending: &mut pending, +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 stream_b.write(&[b'b'; 100]).unwrap(); -1517 -1518 let mut metas = vec![]; -1519 -1520 // Write the first chunk of stream_a -1521 let meta = server.write_frames_for_test(40, false); -1522 assert!(!meta.is_empty()); -1523 metas.extend(meta); -1524 -1525 // Queue stream_c which has higher priority -1526 let mut stream_c = SendStream { -1527 id: id_c, -1528 state: &mut server, -1529 pending: &mut pending, -1530 conn_state: &state, -1531 }; -1532 stream_c.set_priority(1).unwrap(); -1533 stream_c.write(&[b'b'; 100]).unwrap(); -1534 -1535 // loop until all the streams are written -1536 loop { -1537 let meta = server.write_frames_for_test(40, false); -1538 if meta.is_empty() { -1539 break; -1540 } -1541 metas.extend(meta); -1542 } -1543 -1544 assert!(!server.can_send_stream_data()); -1545 assert_eq!(server.pending.len(), 0); -1546 -1547 let stream_ids = metas.iter().map(|m| m.id).collect::<Vec<_>>(); -1548 assert_eq!( -1549 stream_ids, -1550 // stream_c bumps stream_b but doesn't bump stream_a which had already been partly -1551 // written out -1552 vec![id_a, id_a, id_a, id_c, id_c, id_c, id_b, id_b, id_b] -1553 ); -1554 } -1555 -1556 #[test] -1557 fn stop_finished() { -1558 let mut client = make(Side::Client); -1559 let id = StreamId::new(Side::Server, Dir::Uni, 0); -1560 // Server finishes stream -1561 let _ = client -1562 .received( -1563 frame::Stream { -1564 id, -1565 offset: 0, -1566 fin: true, -1567 data: Bytes::from_static(&[0; 32]), -1568 }, -1569 32, -1570 ) -1571 .unwrap(); -1572 let mut pending = Retransmits::default(); -1573 let mut stream = RecvStream { -1574 id, -1575 state: &mut client, -1576 pending: &mut pending, -1577 }; -1578 stream.stop(0u32.into()).unwrap(); -1579 assert!(client.recv.get_mut(&id).is_none(), "stream is freed"); -1580 } -1581 -1582 // Verify that a stream that's been reset doesn't cause the appearance of pending data -1583 #[test] -1584 fn reset_stream_cannot_send() { -1585 let mut server = make(Side::Server); -1586 server.set_params(&TransportParameters { -1587 initial_max_streams_uni: 1u32.into(), -1588 initial_max_data: 42u32.into(), -1589 initial_max_stream_data_uni: 42u32.into(), -1590 ..TransportParameters::default() -1591 }); -1592 let (mut pending, state) = (Retransmits::default(), ConnState::established()); -1593 let mut streams = Streams { -1594 state: &mut server, -1595 conn_state: &state, -1596 }; -1597 -1598 let id = streams.open(Dir::Uni).unwrap(); -1599 let mut stream = SendStream { -1600 id, -1601 state: &mut server, -1602 pending: &mut pending, -1603 conn_state: &state, -1604 }; -1605 stream.write(b"hello").unwrap(); -1606 stream.reset(0u32.into()).unwrap(); -1607 -1608 assert_eq!(pending.reset_stream, &[(id, 0u32.into())]); -1609 assert!(!server.can_send_stream_data()); -1610 } -1611 -1612 #[test] -1613 fn stream_limit_fixed() { -1614 let mut client = make(Side::Client); -1615 // Open streams 0-127 -1616 assert_eq!( -1617 client.received( -1618 frame::Stream { -1619 id: StreamId::new(Side::Server, Dir::Uni, 127), -1620 offset: 0, -1621 fin: true, -1622 data: Bytes::from_static(&[]), -1623 }, -1624 0 -1625 ), -1626 Ok(ShouldTransmit(false)) -1627 ); -1628 // Try to open stream 128, exceeding limit -1629 assert_eq!( -1630 client -1631 .received( -1632 frame::Stream { -1633 id: StreamId::new(Side::Server, Dir::Uni, 128), -1634 offset: 0, -1635 fin: true, -1636 data: Bytes::from_static(&[]), -1637 }, -1638 0 -1639 ) -1640 .unwrap_err() -1641 .code, -1642 TransportErrorCode::STREAM_LIMIT_ERROR -1643 ); -1644 -1645 // Free stream 127 -1646 let mut pending = Retransmits::default(); -1647 let mut stream = RecvStream { -1648 id: StreamId::new(Side::Server, Dir::Uni, 127), -1649 state: &mut client, -1650 pending: &mut pending, -1651 }; -1652 stream.stop(0u32.into()).unwrap(); -1653 -1654 // Open stream 128 -1655 assert_eq!( -1656 client.received( -1657 frame::Stream { -1658 id: StreamId::new(Side::Server, Dir::Uni, 128), -1659 offset: 0, -1660 fin: true, -1661 data: Bytes::from_static(&[]), -1662 }, -1663 0 -1664 ), -1665 Ok(ShouldTransmit(false)) -1666 ); -1667 } -1668 -1669 #[test] -1670 fn stream_limit_grows() { -1671 let mut client = make(Side::Client); -1672 // Open streams 0-127 -1673 assert_eq!( -1674 client.received( -1675 frame::Stream { -1676 id: StreamId::new(Side::Server, Dir::Uni, 127), -1677 offset: 0, -1678 fin: true, -1679 data: Bytes::from_static(&[]), -1680 }, -1681 0 -1682 ), -1683 Ok(ShouldTransmit(false)) -1684 ); -1685 // Try to open stream 128, exceeding limit -1686 assert_eq!( -1687 client -1688 .received( -1689 frame::Stream { -1690 id: StreamId::new(Side::Server, Dir::Uni, 128), -1691 offset: 0, -1692 fin: true, -1693 data: Bytes::from_static(&[]), -1694 }, -1695 0 -1696 ) -1697 .unwrap_err() -1698 .code, -1699 TransportErrorCode::STREAM_LIMIT_ERROR -1700 ); -1701 -1702 // Relax limit by one -1703 client.set_max_concurrent(Dir::Uni, 129u32.into()); -1704 -1705 // Open stream 128 +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(); +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(); +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 } +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.received( -1708 frame::Stream { -1709 id: StreamId::new(Side::Server, Dir::Uni, 128), -1710 offset: 0, -1711 fin: true, -1712 data: Bytes::from_static(&[]), -1713 }, -1714 0 -1715 ), -1716 Ok(ShouldTransmit(false)) -1717 ); -1718 } -1719 -1720 #[test] -1721 fn stream_limit_shrinks() { -1722 let mut client = make(Side::Client); -1723 // Open streams 0-127 -1724 assert_eq!( -1725 client.received( -1726 frame::Stream { -1727 id: StreamId::new(Side::Server, Dir::Uni, 127), -1728 offset: 0, -1729 fin: true, -1730 data: Bytes::from_static(&[]), -1731 }, -1732 0 -1733 ), -1734 Ok(ShouldTransmit(false)) -1735 ); -1736 -1737 // Tighten limit by one -1738 client.set_max_concurrent(Dir::Uni, 127u32.into()); +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 // Free stream 127 -1741 let mut pending = Retransmits::default(); -1742 let mut stream = RecvStream { -1743 id: StreamId::new(Side::Server, Dir::Uni, 127), -1744 state: &mut client, -1745 pending: &mut pending, -1746 }; -1747 stream.stop(0u32.into()).unwrap(); -1748 -1749 // Try to open stream 128, still exceeding limit -1750 assert_eq!( -1751 client -1752 .received( -1753 frame::Stream { -1754 id: StreamId::new(Side::Server, Dir::Uni, 128), -1755 offset: 0, -1756 fin: true, -1757 data: Bytes::from_static(&[]), -1758 }, -1759 0 -1760 ) -1761 .unwrap_err() -1762 .code, -1763 TransportErrorCode::STREAM_LIMIT_ERROR -1764 ); -1765 -1766 // Free stream 126 -1767 assert_eq!( -1768 client.received_reset(frame::ResetStream { -1769 id: StreamId::new(Side::Server, Dir::Uni, 126), -1770 error_code: 0u32.into(), -1771 final_offset: 0u32.into(), -1772 }), -1773 Ok(ShouldTransmit(false)) -1774 ); -1775 let mut pending = Retransmits::default(); -1776 let mut stream = RecvStream { -1777 id: StreamId::new(Side::Server, Dir::Uni, 126), -1778 state: &mut client, -1779 pending: &mut pending, -1780 }; -1781 stream.stop(0u32.into()).unwrap(); -1782 -1783 // Open stream 128 -1784 assert_eq!( -1785 client.received( -1786 frame::Stream { -1787 id: StreamId::new(Side::Server, Dir::Uni, 128), -1788 offset: 0, -1789 fin: true, -1790 data: Bytes::from_static(&[]), -1791 }, -1792 0 -1793 ), -1794 Ok(ShouldTransmit(false)) -1795 ); -1796 } -1797 -1798 #[test] -1799 fn remote_stream_capacity() { -1800 let mut client = make(Side::Client); -1801 for _ in 0..2 { -1802 client.set_max_concurrent(Dir::Uni, 200u32.into()); -1803 client.set_max_concurrent(Dir::Bi, 201u32.into()); -1804 assert_eq!(client.recv.len(), 200 + 201); -1805 assert_eq!(client.max_remote[Dir::Uni as usize], 200); -1806 assert_eq!(client.max_remote[Dir::Bi as usize], 201); -1807 } -1808 } -1809 -1810 #[test] -1811 fn expand_receive_window() { -1812 let mut server = make(Side::Server); -1813 let new_receive_window = 2 * server.receive_window as u32; -1814 let expanded = server.set_receive_window(new_receive_window.into()); -1815 assert!(expanded); -1816 assert_eq!(server.receive_window, new_receive_window as u64); -1817 assert_eq!(server.local_max_data, new_receive_window as u64); -1818 assert_eq!(server.receive_window_shrink_debt, 0); -1819 let prev_local_max_data = server.local_max_data; -1820 -1821 // credit, expecting all of them added to local_max_data -1822 let credits = 1024u64; -1823 let should_transmit = server.add_read_credits(credits); -1824 assert_eq!(server.receive_window_shrink_debt, 0); -1825 assert_eq!(server.local_max_data, prev_local_max_data + credits); -1826 assert!(should_transmit.should_transmit()); -1827 } -1828 -1829 #[test] -1830 fn shrink_receive_window() { -1831 let mut server = make(Side::Server); -1832 let new_receive_window = server.receive_window as u32 / 2; -1833 let prev_local_max_data = server.local_max_data; -1834 -1835 // shrink the receive_winbow, local_max_data is not expected to be changed -1836 let shrink_diff = server.receive_window - new_receive_window as u64; -1837 let expanded = server.set_receive_window(new_receive_window.into()); -1838 assert!(!expanded); -1839 assert_eq!(server.receive_window, new_receive_window as u64); -1840 assert_eq!(server.local_max_data, prev_local_max_data); -1841 assert_eq!(server.receive_window_shrink_debt, shrink_diff); -1842 let prev_local_max_data = server.local_max_data; -1843 -1844 // credit twice, local_max_data does not change as it is absorbed by receive_window_shrink_debt -1845 let credits = 1024u64; -1846 for _ in 0..2 { -1847 let expected_receive_window_shrink_debt = server.receive_window_shrink_debt - credits; -1848 let should_transmit = server.add_read_credits(credits); -1849 assert_eq!( -1850 server.receive_window_shrink_debt, -1851 expected_receive_window_shrink_debt -1852 ); -1853 assert_eq!(server.local_max_data, prev_local_max_data); -1854 assert!(!should_transmit.should_transmit()); -1855 } -1856 -1857 // credit again which exceeds all remaining expected_receive_window_shrink_debt -1858 let credits = 1024 * 512; -1859 let prev_local_max_data = server.local_max_data; -1860 let expected_local_max_data = -1861 server.local_max_data + (credits - server.receive_window_shrink_debt); -1862 let _should_transmit = server.add_read_credits(credits); -1863 assert_eq!(server.receive_window_shrink_debt, 0); -1864 assert_eq!(server.local_max_data, expected_local_max_data); -1865 assert!(server.local_max_data > prev_local_max_data); -1866 -1867 // credit again, all should be added to local_max_data -1868 let credits = 1024 * 512; -1869 let expected_local_max_data = server.local_max_data + credits; -1870 let should_transmit = server.add_read_credits(credits); -1871 assert_eq!(server.receive_window_shrink_debt, 0); -1872 assert_eq!(server.local_max_data, expected_local_max_data); -1873 assert!(should_transmit.should_transmit()); -1874 } -1875 -1876 #[test] -1877 fn expand_send_window() { -1878 let mut server = make(Side::Server); -1879 -1880 let initial_send_window = server.send_window; -1881 let larger_send_window = initial_send_window * 2; -1882 -1883 // Set `initial_max_data` larger than `send_window` so we're limited by local flow control -1884 server.set_params(&TransportParameters { -1885 initial_max_data: VarInt::MAX, -1886 initial_max_stream_data_uni: VarInt::MAX, -1887 initial_max_streams_uni: VarInt::from_u32(100), -1888 ..TransportParameters::default() -1889 }); -1890 -1891 assert_eq!(server.write_limit(), initial_send_window); -1892 assert_eq!(server.poll(), None); -1893 -1894 let mut retransmits = Retransmits::default(); -1895 let conn_state = ConnState::established(); -1896 -1897 let stream_id = Streams { -1898 state: &mut server, -1899 conn_state: &conn_state, -1900 } -1901 .open(Dir::Uni) -1902 .expect("should be able to open a stream"); -1903 -1904 let mut stream = SendStream { -1905 id: stream_id, -1906 state: &mut server, -1907 pending: &mut retransmits, -1908 conn_state: &conn_state, -1909 }; +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 ), +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; +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 // Check that the stream accepts `initial_send_window` bytes -1912 let initial_send_len = initial_send_window as usize; -1913 let data = vec![0xFFu8; initial_send_len]; -1914 -1915 assert_eq!(stream.write(&data), Ok(initial_send_len)); +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 // Try to write the same data again, observe that it's blocked -1918 assert_eq!(stream.write(&data), Err(WriteError::Blocked)); -1919 -1920 // Check that we get a `Writable` event after increasing the send window -1921 stream.state.set_send_window(larger_send_window); -1922 assert_eq!( -1923 stream.state.poll(), -1924 Some(StreamEvent::Writable { id: stream_id }) -1925 ); -1926 -1927 // Check that the stream accepts the exact same amount of data again -1928 assert_eq!(stream.write(&data), Ok(initial_send_len)); -1929 assert_eq!(stream.write(&data), Err(WriteError::Blocked)); +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 assert_eq!(stream.state.poll(), None); -1932 -1933 // Ack the data -1934 stream.state.received_ack_of(frame::StreamMeta { -1935 id: stream_id, -1936 offsets: 0..larger_send_window, -1937 fin: false, -1938 }); +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 assert_eq!( -1941 stream.state.poll(), -1942 Some(StreamEvent::Writable { id: stream_id }) -1943 ); -1944 -1945 // Check that our full send window is available again -1946 assert_eq!(stream.write(&data), Ok(initial_send_len)); -1947 assert_eq!(stream.write(&data), Ok(initial_send_len)); -1948 assert_eq!(stream.write(&data), Err(WriteError::Blocked)); -1949 } +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 #[test] -1952 fn shrink_send_window() { -1953 let mut server = make(Side::Server); -1954 -1955 let initial_send_window = server.send_window; -1956 let smaller_send_window = server.send_window / 2; -1957 -1958 // Set `initial_max_data` larger than `send_window` so we're limited by local flow control -1959 server.set_params(&TransportParameters { -1960 initial_max_data: VarInt::MAX, -1961 initial_max_stream_data_uni: VarInt::MAX, -1962 initial_max_streams_uni: VarInt::from_u32(100), -1963 ..TransportParameters::default() -1964 }); -1965 -1966 assert_eq!(server.write_limit(), initial_send_window); -1967 assert_eq!(server.poll(), None); -1968 -1969 let mut retransmits = Retransmits::default(); -1970 let conn_state = ConnState::established(); -1971 -1972 let stream_id = Streams { -1973 state: &mut server, -1974 conn_state: &conn_state, -1975 } -1976 .open(Dir::Uni) -1977 .expect("should be able to open a stream"); -1978 -1979 let mut stream = SendStream { -1980 id: stream_id, -1981 state: &mut server, -1982 pending: &mut retransmits, -1983 conn_state: &conn_state, -1984 }; +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 let initial_send_len = initial_send_window as usize; -1987 -1988 let data = vec![0xFFu8; initial_send_len]; -1989 -1990 // Assert that the full send window is accepted -1991 assert_eq!(stream.write(&data), Ok(initial_send_len)); -1992 assert_eq!(stream.write(&data), Err(WriteError::Blocked)); -1993 -1994 assert_eq!(stream.state.write_limit(), 0); -1995 assert_eq!(stream.state.poll(), None); -1996 -1997 // Shrink our send window, assert that it's still not writable -1998 stream.state.set_send_window(smaller_send_window); -1999 assert_eq!(stream.state.write_limit(), 0); -2000 assert_eq!(stream.state.poll(), None); -2001 -2002 // Assert that data is still not accepted -2003 assert_eq!(stream.write(&data), Err(WriteError::Blocked)); -2004 -2005 // Ack some data, assert that writes are still not accepted due to outstanding sends -2006 stream.state.received_ack_of(frame::StreamMeta { -2007 id: stream_id, -2008 offsets: 0..smaller_send_window, -2009 fin: false, -2010 }); -2011 +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; +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 // Ack the rest of the data -2015 stream.state.received_ack_of(frame::StreamMeta { -2016 id: stream_id, -2017 offsets: smaller_send_window..initial_send_window, -2018 fin: false, -2019 }); -2020 -2021 // This should generate a `Writable` event -2022 assert_eq!( -2023 stream.state.poll(), -2024 Some(StreamEvent::Writable { id: stream_id }) -2025 ); -2026 assert_eq!(stream.state.write_limit(), smaller_send_window); -2027 -2028 // Assert that only `smaller_send_window` bytes are accepted -2029 assert_eq!(stream.write(&data), Ok(smaller_send_window as usize)); -2030 assert_eq!(stream.write(&data), Err(WriteError::Blocked)); -2031 } -2032} \ No newline at end of file +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 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 1ce4d7503..5a95404d1 100644 --- a/pr/451/docs/src/noq_proto/connection/timer.rs.html +++ b/pr/451/docs/src/noq_proto/connection/timer.rs.html @@ -32,487 +32,495 @@ 32 /// If no new path is opened before this fires, close the connection. 33 /// See <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-21.html#section-3.4-8> 34 NoAvailablePath = 5, -35} -36 -37impl ConnTimer { -38 const VALUES: [Self; 6] = [ -39 Self::Idle, -40 Self::Close, -41 Self::KeyDiscard, -42 Self::KeepAlive, -43 Self::PushNewCid, -44 Self::NoAvailablePath, -45 ]; -46} -47 -48#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] -49pub(crate) enum PathTimer { -50 /// When to send an ack-eliciting probe packet or declare unacked packets lost -51 LossDetection = 0, -52 /// When to abandon a path after no activity -53 PathIdle = 1, -54 /// When to give up on validating a new path from RFC9000 migration -55 PathValidationFailed = 2, -56 /// When to resend an on-path path challenge deemed lost -57 PathChallengeLost = 3, -58 /// When we give up opening a path. -59 /// -60 /// When opening a path we validate it according to RFC9000 §8.2 as required by the -61 /// multipath spec. If validation fails we will abandon the path again. This timer fires -62 /// when we want to give up on this path validation of opening the path. -63 PathOpenFailed = 4, -64 /// When to send a `PING` frame to keep the path alive -65 PathKeepAlive = 5, -66 /// When pacing will allow us to send a packet -67 Pacing = 6, -68 /// When to send an immediate ACK if there are unacked ack-eliciting packets of the peer -69 MaxAckDelay = 7, -70 /// When to clean up state for an abandoned path -71 PathDrained = 8, -72} -73 -74impl PathTimer { -75 pub(super) const VALUES: [Self; 9] = [ -76 Self::LossDetection, -77 Self::PathIdle, -78 Self::PathValidationFailed, -79 Self::PathChallengeLost, -80 Self::PathOpenFailed, -81 Self::PathKeepAlive, -82 Self::Pacing, -83 Self::MaxAckDelay, -84 Self::PathDrained, -85 ]; -86} -87 -88/// Keeps track of the nearest timeout for each `Timer` -89/// -90/// The [`TimerTable`] is advanced with [`TimerTable::expire_before`]. -91#[derive(Debug, Clone, Default)] -92pub(crate) struct TimerTable { -93 generic: [Option<Instant>; ConnTimer::VALUES.len()], -94 path_timers: SmallMap<PathId, PathTimerTable, STACK_TIMERS>, -95} -96 -97/// For how many paths we keep the timers on the stack, before spilling onto the heap. -98const STACK_TIMERS: usize = 4; -99 -100/// Works like a `HashMap` but stores up to `SIZE` items on the stack. -101#[derive(Debug, Clone)] -102struct SmallMap<K, V, const SIZE: usize> { -103 stack: [Option<(K, V)>; SIZE], -104 heap: Option<IntMap<K, V>>, -105} -106 -107impl<K, V, const SIZE: usize> Default for SmallMap<K, V, SIZE> { -108 fn default() -> Self { -109 Self { -110 stack: [const { None }; SIZE], -111 heap: None, -112 } -113 } -114} -115 -116impl<K, V, const SIZE: usize> SmallMap<K, V, SIZE> -117where -118 K: std::cmp::Eq + std::hash::Hash + IdentityHashable, -119{ -120 fn insert(&mut self, key: K, value: V) -> Option<V> { -121 // check stack for space -122 for el in self.stack.iter_mut() { -123 match el { -124 Some((k, v)) => { -125 if *k == key { -126 let old_value = std::mem::replace(v, value); -127 return Some(old_value); -128 } -129 } -130 None => { -131 // make sure to remove a potentially old value from the heap -132 let old_heap = self.heap.as_mut().and_then(|h| h.remove(&key)); -133 *el = Some((key, value)); -134 -135 return old_heap; -136 } -137 } -138 } -139 -140 // No space on the stack, use the heap -141 let heap = self.heap.get_or_insert_default(); -142 heap.insert(key, value) -143 } -144 -145 #[cfg(test)] -146 fn remove(&mut self, key: &K) -> Option<V> { -147 for el in self.stack.iter_mut() { -148 if let Some((k, _)) = el -149 && key == k -150 { -151 return el.take().map(|(_, v)| v); -152 } -153 } -154 -155 self.heap.as_mut().and_then(|h| h.remove(key)) -156 } -157 -158 fn get(&self, key: &K) -> Option<&V> { -159 for (k, v) in self.stack.iter().filter_map(|v| v.as_ref()) { -160 if k == key { -161 return Some(v); -162 } -163 } -164 -165 self.heap.as_ref().and_then(|h| h.get(key)) -166 } -167 -168 fn get_mut(&mut self, key: &K) -> Option<&mut V> { -169 for (k, v) in self.stack.iter_mut().filter_map(|v| v.as_mut()) { -170 if k == key { -171 return Some(v); -172 } -173 } -174 -175 self.heap.as_mut().and_then(|h| h.get_mut(key)) -176 } -177 -178 #[cfg(test)] -179 fn iter(&self) -> impl Iterator<Item = (&K, &V)> { -180 let a = self -181 .stack -182 .iter() -183 .filter_map(|v| v.as_ref().map(|(k, v)| (k, v))); -184 let b = self.heap.iter().flat_map(|h| h.iter()); -185 a.chain(b) -186 } -187 -188 fn values(&self) -> impl Iterator<Item = &V> { -189 let a = self.stack.iter().filter_map(|v| v.as_ref().map(|(_, v)| v)); -190 let b = self.heap.iter().flat_map(|h| h.values()); -191 a.chain(b) -192 } -193 -194 fn iter_mut(&mut self) -> impl Iterator<Item = (&K, &mut V)> { -195 let a = self -196 .stack -197 .iter_mut() -198 .filter_map(|v| v.as_mut().map(|(k, v)| (&*k, v))); -199 let b = self.heap.iter_mut().flat_map(|h| h.iter_mut()); -200 a.chain(b) -201 } -202 -203 fn retain<F>(&mut self, mut f: F) -204 where -205 F: FnMut(&K, &mut V) -> bool, -206 { -207 let mut to_remove = [false; SIZE]; -208 for (i, el) in self.stack.iter_mut().enumerate() { -209 if let Some((key, value)) = el { -210 to_remove[i] = !f(key, value); -211 } -212 } -213 for (i, to_remove) in to_remove.into_iter().enumerate() { -214 if to_remove { -215 self.stack[i] = None; -216 } -217 } -218 -219 if let Some(ref mut heap) = self.heap { -220 heap.retain(f); -221 } -222 } -223 -224 fn clear(&mut self) { -225 for el in self.stack.iter_mut() { -226 *el = None; -227 } -228 self.heap = None; -229 } -230} +35 /// When to retry NAT traversal probes. +36 /// +37 /// Fires at initial PTO intervals to retransmit probes that got no PATH_RESPONSE. +38 NatTraversalProbeRetry = 6, +39} +40 +41impl ConnTimer { +42 const VALUES: [Self; 7] = [ +43 Self::Idle, +44 Self::Close, +45 Self::KeyDiscard, +46 Self::KeepAlive, +47 Self::PushNewCid, +48 Self::NoAvailablePath, +49 Self::NatTraversalProbeRetry, +50 ]; +51} +52 +53#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] +54pub(crate) enum PathTimer { +55 /// When to send an ack-eliciting probe packet or declare unacked packets lost +56 LossDetection = 0, +57 /// When to abandon a path after no activity +58 PathIdle = 1, +59 /// When to give up on validating a new path from RFC9000 migration +60 PathValidationFailed = 2, +61 /// When to resend an on-path path challenge deemed lost +62 PathChallengeLost = 3, +63 /// When to abandon a path due to failed validation. +64 /// +65 /// There are two situations in which we give up a path from validation. +66 /// 1. When opening a path we validate it according to RFC9000 §8.2 as required by the +67 /// multipath spec. This timer is armed to time-bound that validation. +68 /// 2. When validating an already opened multipath path for various reasons in which we +69 /// expect the path to either work or not work and want to respond correspondingly in +70 /// a timely manner. +71 AbandonFromValidation = 4, +72 /// When to send a `PING` frame to keep the path alive +73 PathKeepAlive = 5, +74 /// When pacing will allow us to send a packet +75 Pacing = 6, +76 /// When to send an immediate ACK if there are unacked ack-eliciting packets of the peer +77 MaxAckDelay = 7, +78 /// When to clean up state for an abandoned path +79 PathDrained = 8, +80} +81 +82impl PathTimer { +83 pub(super) const VALUES: [Self; 9] = [ +84 Self::LossDetection, +85 Self::PathIdle, +86 Self::PathValidationFailed, +87 Self::PathChallengeLost, +88 Self::AbandonFromValidation, +89 Self::PathKeepAlive, +90 Self::Pacing, +91 Self::MaxAckDelay, +92 Self::PathDrained, +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; +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#[derive(Debug, Clone, Copy, Default)] -233struct PathTimerTable { -234 timers: [Option<Instant>; PathTimer::VALUES.len()], -235} -236 -237impl PathTimerTable { -238 fn set(&mut self, timer: PathTimer, time: Instant) { -239 self.timers[timer as usize] = Some(time); -240 } -241 -242 fn get(&self, timer: PathTimer) -> Option<Instant> { -243 self.timers[timer as usize] -244 } -245 -246 fn stop(&mut self, timer: PathTimer) { -247 self.timers[timer as usize] = None; +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 /// Remove the next timer up until `now`, including it -251 fn expire_before(&mut self, now: Instant) -> Option<(PathTimer, Instant)> { -252 for timer in PathTimer::VALUES { -253 if self.timers[timer as usize].is_some() -254 && self.timers[timer as usize].expect("checked") <= now -255 { -256 return self.timers[timer as usize].take().map(|time| (timer, time)); -257 } -258 } -259 -260 None -261 } -262} -263 -264impl TimerTable { -265 /// Sets the timer unconditionally. -266 /// -267 /// If the timer is already set, this will change the timer's value. -268 pub(super) fn set(&mut self, timer: Timer, time: Instant, qlog: QlogSinkWithTime<'_>) { -269 match timer { -270 Timer::Conn(timer) => { -271 self.generic[timer as usize] = Some(time); -272 } -273 Timer::PerPath(path_id, timer) => match self.path_timers.get_mut(&path_id) { -274 None => { -275 let mut table = PathTimerTable::default(); -276 table.set(timer, time); -277 self.path_timers.insert(path_id, table); -278 } -279 Some(table) => { -280 table.set(timer, time); -281 } -282 }, -283 } -284 qlog.emit_timer_set(timer, time); -285 } -286 -287 pub(super) fn get(&self, timer: Timer) -> Option<Instant> { -288 match timer { -289 Timer::Conn(timer) => self.generic[timer as usize], -290 Timer::PerPath(path_id, timer) => self.path_timers.get(&path_id)?.get(timer), +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 } -293 -294 pub(super) fn set_or_stop( -295 &mut self, -296 timer: Timer, -297 time: Option<Instant>, -298 qlog: QlogSinkWithTime<'_>, -299 ) { -300 match time { -301 Some(time) => self.set(timer, time, qlog), -302 None => self.stop(timer, qlog), -303 } -304 } -305 -306 pub(super) fn stop(&mut self, timer: Timer, qlog: QlogSinkWithTime<'_>) { -307 match timer { -308 Timer::Conn(timer) => { -309 self.generic[timer as usize] = None; -310 } -311 Timer::PerPath(path_id, timer) => { -312 if let Some(e) = self.path_timers.get_mut(&path_id) { -313 e.stop(timer); -314 } -315 } -316 } -317 qlog.emit_timer_stop(timer); -318 } -319 -320 /// Stops all per-path timers -321 pub(super) fn stop_per_path(&mut self, path_id: PathId, qlog: QlogSinkWithTime<'_>) { -322 for timer in PathTimer::VALUES { -323 if let Some(e) = self.path_timers.get_mut(&path_id) { -324 e.stop(timer); -325 qlog.emit_timer_stop(Timer::PerPath(path_id, timer)); -326 } -327 } -328 } -329 -330 /// Get the next queued timeout -331 pub(super) fn peek(&mut self) -> Option<Instant> { -332 // TODO: this is currently linear in the number of paths -333 -334 let min_generic = self.generic.iter().filter_map(|&x| x).min(); -335 let min_path = self -336 .path_timers -337 .values() -338 .flat_map(|p| p.timers.iter().filter_map(|&x| x)) -339 .min(); -340 -341 match (min_generic, min_path) { -342 (None, None) => None, -343 (Some(val), None) => Some(val), -344 (Some(a), Some(b)) => Some(a.min(b)), -345 (None, Some(val)) => Some(val), -346 } -347 } +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), +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 /// Remove the next timer up until `now`, including it -350 pub(super) fn expire_before( -351 &mut self, -352 now: Instant, -353 qlog: &QlogSink, -354 ) -> Option<(Timer, Instant)> { -355 let (timer, instant) = self.expire_before_inner(now)?; -356 qlog.with_time(now).emit_timer_expire(timer); -357 Some((timer, instant)) -358 } -359 -360 fn expire_before_inner(&mut self, now: Instant) -> Option<(Timer, Instant)> { -361 // TODO: this is currently linear in the number of paths -362 -363 for timer in ConnTimer::VALUES { -364 if self.generic[timer as usize].is_some() -365 && self.generic[timer as usize].expect("checked") <= now -366 { -367 return self.generic[timer as usize] -368 .take() -369 .map(|time| (Timer::Conn(timer), time)); -370 } -371 } -372 -373 let mut res = None; -374 for (path_id, timers) in self.path_timers.iter_mut() { -375 if let Some((timer, time)) = timers.expire_before(now) { -376 res = Some((Timer::PerPath(*path_id, timer), time)); -377 break; +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 // clear out old timers -382 self.path_timers -383 .retain(|_path_id, timers| timers.timers.iter().any(|t| t.is_some())); -384 res -385 } -386 -387 pub(super) fn reset(&mut self) { -388 for timer in ConnTimer::VALUES { -389 self.generic[timer as usize] = None; -390 } -391 self.path_timers.clear(); -392 } -393 -394 #[cfg(test)] -395 pub(super) fn values(&self) -> Vec<(Timer, Instant)> { -396 let mut values = Vec::new(); -397 -398 for timer in ConnTimer::VALUES { -399 if let Some(time) = self.generic[timer as usize] { -400 values.push((Timer::Conn(timer), time)); -401 } -402 } -403 -404 for timer in PathTimer::VALUES { -405 for (path_id, timers) in self.path_timers.iter() { -406 if let Some(time) = timers.timers[timer as usize] { -407 values.push((Timer::PerPath(*path_id, timer), time)); -408 } +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 } 410 } 411 -412 values -413 } -414} -415 -416#[cfg(test)] -417mod tests { -418 use std::time::Duration; +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 use crate::connection::qlog::QlogSink; -421 -422 use super::*; +420 values +421 } +422} 423 -424 #[test] -425 fn timer_table() { -426 let mut timers = TimerTable::default(); -427 let sec = Duration::from_secs(1); -428 let now = Instant::now() + Duration::from_secs(10); -429 timers.set( -430 Timer::Conn(ConnTimer::Idle), -431 now - 3 * sec, -432 QlogSink::default().with_time(now), -433 ); -434 timers.set( -435 Timer::Conn(ConnTimer::Close), -436 now - 2 * sec, -437 QlogSink::default().with_time(now), -438 ); -439 -440 assert_eq!(timers.peek(), Some(now - 3 * sec)); -441 assert_eq!( -442 timers.expire_before(now, &QlogSink::default()), -443 Some((Timer::Conn(ConnTimer::Idle), now - 3 * sec)) -444 ); -445 assert_eq!( -446 timers.expire_before(now, &QlogSink::default()), -447 Some((Timer::Conn(ConnTimer::Close), now - 2 * sec)) -448 ); -449 assert_eq!(timers.expire_before(now, &QlogSink::default()), None); -450 } -451 -452 #[test] -453 fn test_small_map() { -454 let mut map = SmallMap::<usize, usize, 2>::default(); -455 -456 // inserts only on the stack -457 assert_eq!(map.insert(1, 1), None); -458 assert!(map.heap.is_none()); -459 assert_eq!(map.insert(2, 2), None); -460 assert!(map.heap.is_none()); -461 -462 // replace on the stack -463 assert_eq!(map.insert(1, 2), Some(1)); -464 -465 assert_eq!(map.remove(&1), Some(2)); -466 assert_eq!(map.insert(3, 3), None); -467 assert!(map.heap.is_none()); -468 -469 // spill -470 assert_eq!(map.insert(4, 4), None); -471 assert!(map.heap.is_some()); +424#[cfg(test)] +425mod tests { +426 use std::time::Duration; +427 +428 use crate::connection::qlog::QlogSink; +429 +430 use super::*; +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 } +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!( -474 map.iter() -475 .map(|(&a, &b)| (a, b)) -476 .collect::<Vec<(usize, usize)>>(), -477 vec![(3, 3), (2, 2), (4, 4)] -478 ); -479 assert_eq!( -480 map.iter() -481 .map(|(a, b)| (*a, *b)) -482 .collect::<Vec<(usize, usize)>>(), -483 map.iter_mut() -484 .map(|(a, b)| (*a, *b)) -485 .collect::<Vec<(usize, usize)>>(), +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 -488 assert_eq!(map.heap.as_ref().unwrap().len(), 1); -489 -490 for i in 0..10 { -491 map.insert(10 + i, 10 + i); -492 } -493 assert_eq!(map.heap.as_ref().unwrap().len(), 11); -494 map.retain(|k, _v| *k < 10); +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 assert_eq!( -499 map.iter() -500 .map(|(&a, &b)| (a, b)) -501 .collect::<Vec<(usize, usize)>>(), -502 vec![(3, 3), (2, 2), (4, 4)] -503 ); -504 -505 assert_eq!( -506 map.iter() -507 .map(|(a, b)| (*a, *b)) -508 .collect::<Vec<(usize, usize)>>(), -509 map.iter_mut() -510 .map(|(a, b)| (*a, *b)) -511 .collect::<Vec<(usize, usize)>>(), -512 ); -513 -514 map.clear(); -515 assert_eq!(map.iter().collect::<Vec<_>>(), Vec::new()); -516 assert!(map.heap.is_none()); -517 } -518} \ No newline at end of file +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 diff --git a/pr/451/docs/src/noq_proto/crypto/ring_like.rs.html b/pr/451/docs/src/noq_proto/crypto/ring_like.rs.html index 5176f2dc0..75f692af4 100644 --- a/pr/451/docs/src/noq_proto/crypto/ring_like.rs.html +++ b/pr/451/docs/src/noq_proto/crypto/ring_like.rs.html @@ -1,7 +1,7 @@ ring_like.rs - source

    noq_proto/crypto/
    ring_like.rs

    1#[cfg(all(feature = "aws-lc-rs", not(feature = "ring")))]
    -2use aws_lc_rs::{aead, error, hkdf, hmac};
    +2use aws_lc_rs::{aead, hkdf, hmac};
     3#[cfg(feature = "ring")]
    -4use ring::{aead, error, hkdf, hmac};
    +4use ring::{aead, hkdf, hmac};
     5
     6use crate::crypto::{self, CryptoError};
     7
    @@ -15,7 +15,7 @@
     15    }
     16
     17    fn verify(&self, data: &[u8], signature: &[u8]) -> Result<(), CryptoError> {
    -18        Ok(hmac::verify(self, data, signature)?)
    +18        hmac::verify(self, data, signature).map_err(|_| CryptoError)
     19    }
     20}
     21
    @@ -58,20 +58,18 @@
     58        let aead_key = self.derive_aead(token_nonce);
     59        let nonce = aead::Nonce::assume_unique_for_key([0u8; 12]); // See docs for RetryTokenKey
     60        let aad = aead::Aad::empty();
    -61        aead_key.seal_in_place_append_tag(nonce, aad, data)?;
    -62        Ok(())
    -63    }
    -64
    -65    fn open<'a>(&self, token_nonce: u128, data: &'a mut [u8]) -> Result<&'a [u8], CryptoError> {
    -66        let aead_key = self.derive_aead(token_nonce);
    -67        let aad = aead::Aad::empty();
    -68        let nonce = aead::Nonce::assume_unique_for_key([0u8; 12]); // See docs for RetryTokenKey
    -69        Ok(aead_key.open_in_place(nonce, aad, data)?)
    -70    }
    -71}
    -72
    -73impl From<error::Unspecified> for CryptoError {
    -74    fn from(_: error::Unspecified) -> Self {
    -75        Self
    -76    }
    -77}
    \ No newline at end of file +61 aead_key +62 .seal_in_place_append_tag(nonce, aad, data) +63 .map_err(|_| CryptoError)?; +64 Ok(()) +65 } +66 +67 fn open<'a>(&self, token_nonce: u128, data: &'a mut [u8]) -> Result<&'a [u8], CryptoError> { +68 let aead_key = self.derive_aead(token_nonce); +69 let aad = aead::Aad::empty(); +70 let nonce = aead::Nonce::assume_unique_for_key([0u8; 12]); // See docs for RetryTokenKey +71 Ok(aead_key +72 .open_in_place(nonce, aad, data) +73 .map_err(|_| CryptoError)?) +74 } +75} \ No newline at end of file diff --git a/pr/451/docs/src/noq_proto/crypto/rustls.rs.html b/pr/451/docs/src/noq_proto/crypto/rustls.rs.html index 3d6c191dd..4c404765e 100644 --- a/pr/451/docs/src/noq_proto/crypto/rustls.rs.html +++ b/pr/451/docs/src/noq_proto/crypto/rustls.rs.html @@ -61,212 +61,212 @@ 61 Connection::Client(_) => None, 62 Connection::Server(ref session) => session.server_name().map(|x| x.into()), 63 }, -64 #[cfg(feature = "__rustls-post-quantum-test")] -65 negotiated_key_exchange_group: self -66 .inner -67 .negotiated_key_exchange_group() -68 .expect("key exchange group is negotiated") -69 .name(), -70 })) -71 } -72 -73 /// For the rustls `TlsSession`, the `Any` type is `Vec<rustls::pki_types::CertificateDer>` -74 fn peer_identity(&self) -> Option<Box<dyn Any>> { -75 self.inner.peer_certificates().map(|v| -> Box<dyn Any> { -76 Box::new( -77 v.iter() -78 .map(|v| v.clone().into_owned()) -79 .collect::<Vec<CertificateDer<'static>>>(), -80 ) -81 }) -82 } -83 -84 fn early_crypto(&self) -> Option<(Box<dyn HeaderKey>, Box<dyn crypto::PacketKey>)> { -85 let keys = self.inner.zero_rtt_keys()?; -86 Some((Box::new(keys.header), Box::new(keys.packet))) -87 } -88 -89 fn early_data_accepted(&self) -> Option<bool> { -90 match self.inner { -91 Connection::Client(ref session) => Some(session.is_early_data_accepted()), -92 _ => None, -93 } -94 } -95 -96 fn is_handshaking(&self) -> bool { -97 self.inner.is_handshaking() -98 } -99 -100 fn read_handshake(&mut self, buf: &[u8]) -> Result<bool, TransportError> { -101 self.inner.read_hs(buf).map_err(|e| { -102 if let Some(alert) = self.inner.alert() { -103 TransportError { -104 code: TransportErrorCode::crypto(alert.into()), -105 frame: crate::frame::MaybeFrame::None, -106 reason: e.to_string(), -107 crypto: Some(Arc::new(e)), -108 } -109 } else { -110 TransportError::PROTOCOL_VIOLATION(format!("TLS error: {e}")) -111 } -112 })?; -113 if !self.got_handshake_data { -114 // Hack around the lack of an explicit signal from rustls to reflect ClientHello being -115 // ready on incoming connections, or ALPN negotiation completing on outgoing -116 // connections. -117 let have_server_name = match self.inner { -118 Connection::Client(_) => false, -119 Connection::Server(ref session) => session.server_name().is_some(), -120 }; -121 if self.inner.alpn_protocol().is_some() || have_server_name || !self.is_handshaking() { -122 self.got_handshake_data = true; -123 return Ok(true); -124 } -125 } -126 Ok(false) -127 } -128 -129 fn transport_parameters(&self) -> Result<Option<TransportParameters>, TransportError> { -130 match self.inner.quic_transport_parameters() { -131 None => Ok(None), -132 Some(buf) => match TransportParameters::read(self.side(), &mut io::Cursor::new(buf)) { -133 Ok(params) => Ok(Some(params)), -134 Err(e) => Err(e.into()), -135 }, -136 } -137 } -138 -139 fn write_handshake(&mut self, buf: &mut Vec<u8>) -> Option<Keys> { -140 let keys = match self.inner.write_hs(buf)? { -141 KeyChange::Handshake { keys } => keys, -142 KeyChange::OneRtt { keys, next } => { -143 self.next_secrets = Some(next); -144 keys -145 } -146 }; -147 -148 Some(Keys { -149 header: KeyPair { -150 local: Box::new(keys.local.header), -151 remote: Box::new(keys.remote.header), -152 }, -153 packet: KeyPair { -154 local: Box::new(keys.local.packet), -155 remote: Box::new(keys.remote.packet), -156 }, -157 }) -158 } -159 -160 fn next_1rtt_keys(&mut self) -> Option<KeyPair<Box<dyn crypto::PacketKey>>> { -161 let secrets = self.next_secrets.as_mut()?; -162 let keys = secrets.next_packet_keys(); -163 Some(KeyPair { -164 local: Box::new(keys.local), -165 remote: Box::new(keys.remote), -166 }) -167 } -168 -169 fn is_valid_retry(&self, orig_dst_cid: ConnectionId, header: &[u8], payload: &[u8]) -> bool { -170 if payload.len() < 16 { -171 return false; -172 } -173 -174 let mut pseudo_packet = -175 Vec::with_capacity(header.len() + payload.len() + orig_dst_cid.len() + 1); -176 pseudo_packet.push(orig_dst_cid.len() as u8); -177 pseudo_packet.extend_from_slice(&orig_dst_cid); -178 pseudo_packet.extend_from_slice(header); -179 pseudo_packet.extend_from_slice(payload); -180 -181 let (nonce, key) = match self.version { -182 Version::V1 => (&RETRY_INTEGRITY_NONCE_V1, &RETRY_INTEGRITY_KEY_V1), -183 Version::V1Draft => (&RETRY_INTEGRITY_NONCE_DRAFT, &RETRY_INTEGRITY_KEY_DRAFT), -184 _ => unreachable!(), -185 }; -186 -187 let Some((aad, tag)) = pseudo_packet.split_last_chunk::<16>() else { -188 return false; // But length is actually already checked above. -189 }; -190 -191 // This implements https://www.rfc-editor.org/rfc/rfc9001#name-retry-packet-integrity -192 let key = aes_gcm::Key::<aes_gcm::Aes128Gcm>::from_slice(key); -193 let nonce = aes_gcm::Nonce::from_slice(nonce); -194 let tag = aes_gcm::Tag::from_slice(tag); -195 aes_gcm::Aes128Gcm::new(key) -196 .decrypt_in_place_detached(nonce, aad, &mut [], tag) -197 .is_ok() -198 } -199 -200 fn export_keying_material( -201 &self, -202 output: &mut [u8], -203 label: &[u8], -204 context: &[u8], -205 ) -> Result<(), ExportKeyingMaterialError> { -206 self.inner -207 .export_keying_material(output, label, Some(context)) -208 .map_err(|_| ExportKeyingMaterialError)?; -209 Ok(()) -210 } -211} -212 -213const RETRY_INTEGRITY_KEY_DRAFT: [u8; 16] = [ -214 0xcc, 0xce, 0x18, 0x7e, 0xd0, 0x9a, 0x09, 0xd0, 0x57, 0x28, 0x15, 0x5a, 0x6c, 0xb9, 0x6b, 0xe1, -215]; -216const RETRY_INTEGRITY_NONCE_DRAFT: [u8; 12] = [ -217 0xe5, 0x49, 0x30, 0xf9, 0x7f, 0x21, 0x36, 0xf0, 0x53, 0x0a, 0x8c, 0x1c, -218]; -219 -220const RETRY_INTEGRITY_KEY_V1: [u8; 16] = [ -221 0xbe, 0x0c, 0x69, 0x0b, 0x9f, 0x66, 0x57, 0x5a, 0x1d, 0x76, 0x6b, 0x54, 0xe3, 0x68, 0xc8, 0x4e, -222]; -223const RETRY_INTEGRITY_NONCE_V1: [u8; 12] = [ -224 0x46, 0x15, 0x99, 0xd3, 0x5d, 0x63, 0x2b, 0xf2, 0x23, 0x98, 0x25, 0xbb, -225]; -226 -227impl crypto::HeaderKey for Box<dyn HeaderProtectionKey> { -228 fn decrypt(&self, pn_offset: usize, packet: &mut [u8]) { -229 let (header, sample) = packet.split_at_mut(pn_offset + 4); -230 let (first, rest) = header.split_at_mut(1); -231 let pn_end = Ord::min(pn_offset + 3, rest.len()); -232 self.decrypt_in_place( -233 &sample[..self.sample_size()], -234 &mut first[0], -235 &mut rest[pn_offset - 1..pn_end], -236 ) -237 .unwrap(); -238 } -239 -240 fn encrypt(&self, pn_offset: usize, packet: &mut [u8]) { -241 let (header, sample) = packet.split_at_mut(pn_offset + 4); -242 let (first, rest) = header.split_at_mut(1); -243 let pn_end = Ord::min(pn_offset + 3, rest.len()); -244 self.encrypt_in_place( -245 &sample[..self.sample_size()], -246 &mut first[0], -247 &mut rest[pn_offset - 1..pn_end], -248 ) -249 .unwrap(); -250 } -251 -252 fn sample_size(&self) -> usize { -253 self.sample_len() -254 } -255} -256 -257/// Authentication data for (rustls) TLS session -258pub struct HandshakeData { -259 /// The negotiated application protocol, if ALPN is in use -260 /// -261 /// Guaranteed to be set if a nonempty list of protocols was specified for this connection. -262 pub protocol: Option<Vec<u8>>, -263 /// The server name specified by the client, if any -264 /// -265 /// Always `None` for outgoing connections -266 pub server_name: Option<String>, -267 /// The key exchange group negotiated with the peer -268 #[cfg(feature = "__rustls-post-quantum-test")] -269 pub negotiated_key_exchange_group: rustls::NamedGroup, +64 negotiated_key_exchange_group: self +65 .inner +66 .negotiated_key_exchange_group() +67 .map(|kx| kx.name()), +68 })) +69 } +70 +71 /// For the rustls `TlsSession`, the `Any` type is `Vec<rustls::pki_types::CertificateDer>` +72 fn peer_identity(&self) -> Option<Box<dyn Any>> { +73 self.inner.peer_certificates().map(|v| -> Box<dyn Any> { +74 Box::new( +75 v.iter() +76 .map(|v| v.clone().into_owned()) +77 .collect::<Vec<CertificateDer<'static>>>(), +78 ) +79 }) +80 } +81 +82 fn early_crypto(&self) -> Option<(Box<dyn HeaderKey>, Box<dyn crypto::PacketKey>)> { +83 let keys = self.inner.zero_rtt_keys()?; +84 Some((Box::new(keys.header), Box::new(keys.packet))) +85 } +86 +87 fn early_data_accepted(&self) -> Option<bool> { +88 match self.inner { +89 Connection::Client(ref session) => Some(session.is_early_data_accepted()), +90 _ => None, +91 } +92 } +93 +94 fn is_handshaking(&self) -> bool { +95 self.inner.is_handshaking() +96 } +97 +98 fn read_handshake(&mut self, buf: &[u8]) -> Result<bool, TransportError> { +99 self.inner.read_hs(buf).map_err(|e| { +100 if let Some(alert) = self.inner.alert() { +101 TransportError { +102 code: TransportErrorCode::crypto(alert.into()), +103 frame: crate::frame::MaybeFrame::None, +104 reason: e.to_string(), +105 crypto: Some(Arc::new(e)), +106 } +107 } else { +108 TransportError::PROTOCOL_VIOLATION(format!("TLS error: {e}")) +109 } +110 })?; +111 if !self.got_handshake_data { +112 // Hack around the lack of an explicit signal from rustls to reflect ClientHello being +113 // ready on incoming connections, or ALPN negotiation completing on outgoing +114 // connections. +115 let have_server_name = match self.inner { +116 Connection::Client(_) => false, +117 Connection::Server(ref session) => session.server_name().is_some(), +118 }; +119 if self.inner.alpn_protocol().is_some() || have_server_name || !self.is_handshaking() { +120 self.got_handshake_data = true; +121 return Ok(true); +122 } +123 } +124 Ok(false) +125 } +126 +127 fn transport_parameters(&self) -> Result<Option<TransportParameters>, TransportError> { +128 match self.inner.quic_transport_parameters() { +129 None => Ok(None), +130 Some(buf) => match TransportParameters::read(self.side(), &mut io::Cursor::new(buf)) { +131 Ok(params) => Ok(Some(params)), +132 Err(e) => Err(e.into()), +133 }, +134 } +135 } +136 +137 fn write_handshake(&mut self, buf: &mut Vec<u8>) -> Option<Keys> { +138 let keys = match self.inner.write_hs(buf)? { +139 KeyChange::Handshake { keys } => keys, +140 KeyChange::OneRtt { keys, next } => { +141 self.next_secrets = Some(next); +142 keys +143 } +144 }; +145 +146 Some(Keys { +147 header: KeyPair { +148 local: Box::new(keys.local.header), +149 remote: Box::new(keys.remote.header), +150 }, +151 packet: KeyPair { +152 local: Box::new(keys.local.packet), +153 remote: Box::new(keys.remote.packet), +154 }, +155 }) +156 } +157 +158 fn next_1rtt_keys(&mut self) -> Option<KeyPair<Box<dyn crypto::PacketKey>>> { +159 let secrets = self.next_secrets.as_mut()?; +160 let keys = secrets.next_packet_keys(); +161 Some(KeyPair { +162 local: Box::new(keys.local), +163 remote: Box::new(keys.remote), +164 }) +165 } +166 +167 fn is_valid_retry(&self, orig_dst_cid: ConnectionId, header: &[u8], payload: &[u8]) -> bool { +168 if payload.len() < 16 { +169 return false; +170 } +171 +172 let mut pseudo_packet = +173 Vec::with_capacity(header.len() + payload.len() + orig_dst_cid.len() + 1); +174 pseudo_packet.push(orig_dst_cid.len() as u8); +175 pseudo_packet.extend_from_slice(&orig_dst_cid); +176 pseudo_packet.extend_from_slice(header); +177 pseudo_packet.extend_from_slice(payload); +178 +179 let (nonce, key) = match self.version { +180 Version::V1 => (&RETRY_INTEGRITY_NONCE_V1, &RETRY_INTEGRITY_KEY_V1), +181 Version::V1Draft => (&RETRY_INTEGRITY_NONCE_DRAFT, &RETRY_INTEGRITY_KEY_DRAFT), +182 _ => unreachable!(), +183 }; +184 +185 let Some((aad, tag)) = pseudo_packet.split_last_chunk::<16>() else { +186 return false; // But length is actually already checked above. +187 }; +188 +189 // This implements https://www.rfc-editor.org/rfc/rfc9001#name-retry-packet-integrity +190 let key = aes_gcm::Key::<aes_gcm::Aes128Gcm>::from_slice(key); +191 let nonce = aes_gcm::Nonce::from_slice(nonce); +192 let tag = aes_gcm::Tag::from_slice(tag); +193 aes_gcm::Aes128Gcm::new(key) +194 .decrypt_in_place_detached(nonce, aad, &mut [], tag) +195 .is_ok() +196 } +197 +198 fn export_keying_material( +199 &self, +200 output: &mut [u8], +201 label: &[u8], +202 context: &[u8], +203 ) -> Result<(), ExportKeyingMaterialError> { +204 self.inner +205 .export_keying_material(output, label, Some(context)) +206 .map_err(|_| ExportKeyingMaterialError)?; +207 Ok(()) +208 } +209} +210 +211const RETRY_INTEGRITY_KEY_DRAFT: [u8; 16] = [ +212 0xcc, 0xce, 0x18, 0x7e, 0xd0, 0x9a, 0x09, 0xd0, 0x57, 0x28, 0x15, 0x5a, 0x6c, 0xb9, 0x6b, 0xe1, +213]; +214const RETRY_INTEGRITY_NONCE_DRAFT: [u8; 12] = [ +215 0xe5, 0x49, 0x30, 0xf9, 0x7f, 0x21, 0x36, 0xf0, 0x53, 0x0a, 0x8c, 0x1c, +216]; +217 +218const RETRY_INTEGRITY_KEY_V1: [u8; 16] = [ +219 0xbe, 0x0c, 0x69, 0x0b, 0x9f, 0x66, 0x57, 0x5a, 0x1d, 0x76, 0x6b, 0x54, 0xe3, 0x68, 0xc8, 0x4e, +220]; +221const RETRY_INTEGRITY_NONCE_V1: [u8; 12] = [ +222 0x46, 0x15, 0x99, 0xd3, 0x5d, 0x63, 0x2b, 0xf2, 0x23, 0x98, 0x25, 0xbb, +223]; +224 +225impl crypto::HeaderKey for Box<dyn HeaderProtectionKey> { +226 fn decrypt(&self, pn_offset: usize, packet: &mut [u8]) { +227 let (header, sample) = packet.split_at_mut(pn_offset + 4); +228 let (first, rest) = header.split_at_mut(1); +229 let pn_end = Ord::min(pn_offset + 3, rest.len()); +230 self.decrypt_in_place( +231 &sample[..self.sample_size()], +232 &mut first[0], +233 &mut rest[pn_offset - 1..pn_end], +234 ) +235 .unwrap(); +236 } +237 +238 fn encrypt(&self, pn_offset: usize, packet: &mut [u8]) { +239 let (header, sample) = packet.split_at_mut(pn_offset + 4); +240 let (first, rest) = header.split_at_mut(1); +241 let pn_end = Ord::min(pn_offset + 3, rest.len()); +242 self.encrypt_in_place( +243 &sample[..self.sample_size()], +244 &mut first[0], +245 &mut rest[pn_offset - 1..pn_end], +246 ) +247 .unwrap(); +248 } +249 +250 fn sample_size(&self) -> usize { +251 self.sample_len() +252 } +253} +254 +255/// Authentication data for (rustls) TLS session +256#[non_exhaustive] +257pub struct HandshakeData { +258 /// The negotiated application protocol, if ALPN is in use +259 /// +260 /// Guaranteed to be set if a nonempty list of protocols was specified for this connection. +261 pub protocol: Option<Vec<u8>>, +262 /// The server name specified by the client, if any +263 /// +264 /// Always `None` for outgoing connections +265 pub server_name: Option<String>, +266 /// The key exchange group negotiated with the peer. Useful for logging and metrics. +267 /// +268 /// `None` until the handshake has completed. +269 pub negotiated_key_exchange_group: Option<rustls::NamedGroup>, 270} 271 272/// A QUIC-compatible TLS client configuration diff --git a/pr/451/docs/src/noq_proto/endpoint.rs.html b/pr/451/docs/src/noq_proto/endpoint.rs.html index 9d73ba09f..0e0389133 100644 --- a/pr/451/docs/src/noq_proto/endpoint.rs.html +++ b/pr/451/docs/src/noq_proto/endpoint.rs.html @@ -660,974 +660,986 @@ 660 incoming.rest, 661 ) { 662 Ok(()) => { -663 trace!(id = ch.0, icid = %dst_cid, "new connection"); -664 -665 for event in incoming_buffer.datagrams { -666 conn.handle_event(ConnectionEvent(ConnectionEventInner::Datagram(event))) -667 } -668 -669 Ok((ch, conn)) -670 } -671 Err(e) => { -672 debug!("handshake failed: {}", e); -673 self.handle_event(ch, EndpointEvent(EndpointEventInner::Drained)); -674 let response = match e { -675 ConnectionError::TransportError(ref e) => Some(self.initial_close( -676 version, -677 incoming.network_path, -678 &incoming.crypto, -679 src_cid, -680 e.clone(), -681 buf, -682 )), -683 _ => None, -684 }; -685 Err(Box::new(AcceptError { cause: e, response })) -686 } -687 } -688 } -689 -690 /// Check if we should refuse a connection attempt regardless of the packet's contents -691 fn early_validate_first_packet( -692 &mut self, -693 header: &ProtectedInitialHeader, -694 ) -> Result<(), TransportError> { -695 let config = &self.server_config.as_ref().unwrap(); -696 if self.cids_exhausted() || self.incoming_buffers.len() >= config.max_incoming { -697 return Err(TransportError::CONNECTION_REFUSED("")); -698 } -699 -700 // RFC9000 §7.2 dictates that initial (client-chosen) destination CIDs must be at least 8 -701 // bytes. If this is a Retry packet, then the length must instead match our usual CID -702 // length. If we ever issue non-Retry address validation tokens via `NEW_TOKEN`, then we'll -703 // also need to validate CID length for those after decoding the token. -704 if header.dst_cid.len() < 8 -705 && (header.token_pos.is_empty() -706 || header.dst_cid.len() != self.local_cid_generator.cid_len()) -707 { -708 debug!( -709 "rejecting connection due to invalid DCID length {}", -710 header.dst_cid.len() -711 ); -712 return Err(TransportError::PROTOCOL_VIOLATION( -713 "invalid destination CID length", -714 )); -715 } -716 -717 Ok(()) -718 } -719 -720 /// Reject this incoming connection attempt -721 pub fn refuse(&mut self, incoming: Incoming, buf: &mut Vec<u8>) -> Transmit { -722 self.clean_up_incoming(&incoming); -723 incoming.improper_drop_warner.dismiss(); +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 } 724 -725 self.initial_close( -726 incoming.packet.header.version, -727 incoming.network_path, -728 &incoming.crypto, -729 incoming.packet.header.src_cid, -730 TransportError::CONNECTION_REFUSED(""), -731 buf, -732 ) -733 } -734 -735 /// Respond with a retry packet, requiring the client to retry with address validation -736 /// -737 /// Errors if `incoming.may_retry()` is false. -738 pub fn retry(&mut self, incoming: Incoming, buf: &mut Vec<u8>) -> Result<Transmit, RetryError> { -739 if !incoming.may_retry() { -740 return Err(RetryError(Box::new(incoming))); -741 } -742 -743 self.clean_up_incoming(&incoming); -744 incoming.improper_drop_warner.dismiss(); -745 -746 let server_config = self.server_config.as_ref().unwrap(); +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 // First Initial -749 // The peer will use this as the DCID of its following Initials. Initial DCIDs are -750 // looked up separately from Handshake/Data DCIDs, so there is no risk of collision -751 // with established connections. In the unlikely event that a collision occurs -752 // between two connections in the initial phase, both will fail fast and may be -753 // retried by the application layer. -754 let local_cid = self.local_cid_generator.generate_cid(); -755 -756 let payload = TokenPayload::Retry { -757 address: incoming.network_path.remote, -758 orig_dst_cid: incoming.packet.header.dst_cid, -759 issued: server_config.time_source.now(), -760 }; -761 let token = Token::new(payload, &mut self.rng).encode(&*server_config.token_key); -762 -763 let header = Header::Retry { -764 src_cid: local_cid, -765 dst_cid: incoming.packet.header.src_cid, -766 version: incoming.packet.header.version, -767 }; -768 -769 let encode = header.encode(buf); -770 buf.put_slice(&token); -771 buf.extend_from_slice(&server_config.crypto.retry_tag( -772 incoming.packet.header.version, -773 incoming.packet.header.dst_cid, -774 buf, -775 )); -776 encode.finish(buf, &*incoming.crypto.header.local, None); -777 -778 Ok(Transmit { -779 destination: incoming.network_path.remote, -780 ecn: None, -781 size: buf.len(), -782 segment_size: None, -783 src_ip: incoming.network_path.local_ip, -784 }) -785 } -786 -787 /// Ignore this incoming connection attempt, not sending any packet in response -788 /// -789 /// Doing this actively, rather than merely dropping the [`Incoming`], is necessary to prevent -790 /// memory leaks due to state within [`Endpoint`] tracking the incoming connection. -791 pub fn ignore(&mut self, incoming: Incoming) { -792 self.clean_up_incoming(&incoming); -793 incoming.improper_drop_warner.dismiss(); -794 } -795 -796 /// Clean up endpoint data structures associated with an `Incoming`. -797 fn clean_up_incoming(&mut self, incoming: &Incoming) { -798 self.index.remove_initial(incoming.packet.header.dst_cid); -799 let incoming_buffer = self.incoming_buffers.remove(incoming.incoming_idx); -800 self.all_incoming_buffers_total_bytes -= incoming_buffer.total_bytes; -801 } -802 -803 fn add_connection( -804 &mut self, -805 ch: ConnectionHandle, -806 version: u32, -807 init_cid: ConnectionId, -808 local_cid: ConnectionId, -809 remote_cid: ConnectionId, -810 network_path: FourTuple, -811 now: Instant, -812 tls: Box<dyn crypto::Session>, -813 transport_config: Arc<TransportConfig>, -814 side_args: SideArgs, -815 // Only used for qlog. -816 params: &TransportParameters, -817 ) -> Connection { -818 let mut rng_seed = [0; 32]; -819 self.rng.fill_bytes(&mut rng_seed); -820 let side = side_args.side(); -821 let pref_addr_cid = side_args.pref_addr_cid(); -822 -823 let qlog = -824 transport_config.create_qlog_sink(side_args.side(), network_path.remote, init_cid, now); -825 -826 qlog.emit_connection_started( -827 now, -828 local_cid, -829 remote_cid, -830 network_path.remote, -831 network_path.local_ip, -832 params, -833 ); -834 -835 let conn = Connection::new( -836 self.config.clone(), -837 transport_config, -838 init_cid, -839 local_cid, -840 remote_cid, -841 network_path, -842 tls, -843 self.local_cid_generator.as_ref(), -844 now, -845 version, -846 self.allow_mtud, -847 rng_seed, -848 side_args, -849 qlog, -850 ); -851 -852 let mut path_cids = PathLocalCids::default(); -853 path_cids.cids.insert(path_cids.issued, local_cid); -854 path_cids.issued += 1; -855 -856 if let Some(cid) = pref_addr_cid { -857 debug_assert_eq!(path_cids.issued, 1, "preferred address cid seq must be 1"); -858 path_cids.cids.insert(path_cids.issued, cid); -859 path_cids.issued += 1; -860 } -861 -862 let id = self.connections.insert(ConnectionMeta { -863 init_cid, -864 local_cids: FxHashMap::from_iter([(PathId::ZERO, path_cids)]), -865 network_path, -866 side, -867 reset_token: Default::default(), -868 }); -869 debug_assert_eq!(id, ch.0, "connection handle allocation out of sync"); -870 -871 self.index.insert_conn(network_path, local_cid, ch, side); -872 -873 conn -874 } +748 self.clean_up_incoming(&incoming); +749 incoming.improper_drop_warner.dismiss(); +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); +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 fn initial_close( -877 &mut self, -878 version: u32, -879 network_path: FourTuple, -880 crypto: &Keys, -881 remote_id: ConnectionId, -882 reason: TransportError, -883 buf: &mut Vec<u8>, -884 ) -> Transmit { -885 // We don't need to worry about CID collisions in initial closes because the peer -886 // shouldn't respond, and if it does, and the CID collides, we'll just drop the -887 // unexpected response. -888 let local_id = self.local_cid_generator.generate_cid(); -889 let number = PacketNumber::U8(0); -890 let header = Header::Initial(InitialHeader { -891 dst_cid: remote_id, -892 src_cid: local_id, -893 number, -894 token: Bytes::new(), -895 version, -896 }); -897 -898 let partial_encode = header.encode(buf); -899 let max_len = -900 INITIAL_MTU as usize - partial_encode.header_len - crypto.packet.local.tag_len(); -901 frame::Close::from(reason).encoder(max_len).encode(buf); -902 buf.resize(buf.len() + crypto.packet.local.tag_len(), 0); -903 partial_encode.finish( -904 buf, -905 &*crypto.header.local, -906 Some((0, Default::default(), &*crypto.packet.local)), -907 ); -908 Transmit { -909 destination: network_path.remote, -910 ecn: None, -911 size: buf.len(), -912 segment_size: None, -913 src_ip: network_path.local_ip, -914 } -915 } -916 -917 /// Access the configuration used by this endpoint -918 pub fn config(&self) -> &EndpointConfig { -919 &self.config +876 self.index.insert_conn(network_path, local_cid, ch, side); +877 +878 conn +879 } +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 /// Number of connections that are currently open -923 pub fn open_connections(&self) -> usize { -924 self.connections.len() +922 /// Access the configuration used by this endpoint +923 pub fn config(&self) -> &EndpointConfig { +924 &self.config 925 } 926 -927 /// Counter for the number of bytes currently used -928 /// in the buffers for Initial and 0-RTT messages for pending incoming connections -929 pub fn incoming_buffer_bytes(&self) -> u64 { -930 self.all_incoming_buffers_total_bytes -931 } -932 -933 #[cfg(test)] -934 pub(crate) fn known_connections(&self) -> usize { -935 let x = self.connections.len(); -936 debug_assert_eq!(x, self.index.connection_ids_initial.len()); -937 // Not all connections have known reset tokens -938 debug_assert!(x >= self.index.connection_reset_tokens.0.len()); -939 // Not all connections have unique remotes, and 0-length CIDs might not be in use. -940 debug_assert!(x >= self.index.incoming_connection_remotes.len()); -941 debug_assert!(x >= self.index.outgoing_connection_remotes.len()); -942 x -943 } -944 -945 #[cfg(test)] -946 pub(crate) fn known_cids(&self) -> usize { -947 self.index.connection_ids.len() +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 /// Whether we've used up 3/4 of the available CID space -951 /// -952 /// We leave some space unused so that `new_cid` can be relied upon to finish quickly. We don't -953 /// bother to check when CID longer than 4 bytes are used because 2^40 connections is a lot. -954 fn cids_exhausted(&self) -> bool { -955 self.local_cid_generator.cid_len() <= 4 -956 && self.local_cid_generator.cid_len() != 0 -957 && (2usize.pow(self.local_cid_generator.cid_len() as u32 * 8) -958 - self.index.connection_ids.len()) -959 < 2usize.pow(self.local_cid_generator.cid_len() as u32 * 8 - 2) -960 } -961} -962 -963impl fmt::Debug for Endpoint { -964 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { -965 fmt.debug_struct("Endpoint") -966 .field("rng", &self.rng) -967 .field("index", &self.index) -968 .field("connections", &self.connections) -969 .field("config", &self.config) -970 .field("server_config", &self.server_config) -971 // incoming_buffers too large -972 .field("incoming_buffers.len", &self.incoming_buffers.len()) -973 .field( -974 "all_incoming_buffers_total_bytes", -975 &self.all_incoming_buffers_total_bytes, -976 ) -977 .finish() -978 } -979} -980 -981/// Buffered Initial and 0-RTT messages for a pending incoming connection -982#[derive(Default)] -983struct IncomingBuffer { -984 datagrams: Vec<DatagramConnectionEvent>, -985 total_bytes: u64, -986} -987 -988/// Part of protocol state incoming datagrams can be routed to -989#[derive(Copy, Clone, Debug)] -990enum RouteDatagramTo { -991 Incoming(usize), -992 Connection(ConnectionHandle, PathId), -993} -994 -995/// Maps packets to existing connections -996#[derive(Default, Debug)] -997struct ConnectionIndex { -998 /// Identifies connections based on the initial DCID the peer utilized -999 /// -1000 /// Uses a standard `HashMap` to protect against hash collision attacks. -1001 /// -1002 /// Used by the server, not the client. -1003 connection_ids_initial: HashMap<ConnectionId, RouteDatagramTo>, -1004 /// Identifies connections based on locally created CIDs -1005 /// -1006 /// Uses a cheaper hash function since keys are locally created -1007 connection_ids: FxHashMap<ConnectionId, (ConnectionHandle, PathId)>, -1008 /// Identifies incoming connections with zero-length CIDs -1009 /// -1010 /// Uses a standard `HashMap` to protect against hash collision attacks. -1011 incoming_connection_remotes: HashMap<FourTuple, ConnectionHandle>, -1012 /// Identifies outgoing connections with zero-length CIDs +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 /// We don't yet support explicit source addresses for client connections, and zero-length CIDs -1015 /// require a unique 4-tuple, so at most one client connection with zero-length local CIDs -1016 /// may be established per remote. We must omit the local address from the key because we don't -1017 /// necessarily know what address we're sending from, and hence receiving at. -1018 /// -1019 /// Uses a standard `HashMap` to protect against hash collision attacks. -1020 // TODO(matheus23): It's possible this could be changed now that we track the full 4-tuple on the client side, too. -1021 outgoing_connection_remotes: HashMap<SocketAddr, ConnectionHandle>, -1022 /// Reset tokens provided by the peer for the CID each connection is currently sending to -1023 /// -1024 /// Incoming stateless resets do not have correct CIDs, so we need this to identify the correct -1025 /// recipient, if any. -1026 connection_reset_tokens: ResetTokenTable, -1027} -1028 -1029impl ConnectionIndex { -1030 /// Associate an incoming connection with its initial destination CID -1031 fn insert_initial_incoming(&mut self, dst_cid: ConnectionId, incoming_key: usize) { -1032 if dst_cid.is_empty() { -1033 return; -1034 } -1035 self.connection_ids_initial -1036 .insert(dst_cid, RouteDatagramTo::Incoming(incoming_key)); -1037 } -1038 -1039 /// Remove an association with an initial destination CID -1040 fn remove_initial(&mut self, dst_cid: ConnectionId) { -1041 if dst_cid.is_empty() { -1042 return; -1043 } -1044 let removed = self.connection_ids_initial.remove(&dst_cid); -1045 debug_assert!(removed.is_some()); -1046 } -1047 -1048 /// Associate a connection with its initial destination CID -1049 fn insert_initial(&mut self, dst_cid: ConnectionId, connection: ConnectionHandle) { -1050 if dst_cid.is_empty() { -1051 return; -1052 } -1053 self.connection_ids_initial.insert( -1054 dst_cid, -1055 RouteDatagramTo::Connection(connection, PathId::ZERO), -1056 ); -1057 } -1058 -1059 /// Associate a connection with its first locally-chosen destination CID if used, or otherwise -1060 /// its current 4-tuple -1061 fn insert_conn( -1062 &mut self, -1063 network_path: FourTuple, -1064 dst_cid: ConnectionId, -1065 connection: ConnectionHandle, -1066 side: Side, -1067 ) { -1068 match dst_cid.len() { -1069 0 => match side { -1070 Side::Server => { -1071 self.incoming_connection_remotes -1072 .insert(network_path, connection); -1073 } -1074 Side::Client => { -1075 self.outgoing_connection_remotes -1076 .insert(network_path.remote, connection); -1077 } -1078 }, -1079 _ => { -1080 self.connection_ids -1081 .insert(dst_cid, (connection, PathId::ZERO)); -1082 } -1083 } -1084 } -1085 -1086 /// Discard a connection ID -1087 fn retire(&mut self, dst_cid: ConnectionId) { -1088 self.connection_ids.remove(&dst_cid); -1089 } -1090 -1091 /// Remove all references to a connection -1092 fn remove(&mut self, conn: &ConnectionMeta) { -1093 if conn.side.is_server() { -1094 self.remove_initial(conn.init_cid); +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 for cid in conn -1097 .local_cids -1098 .values() -1099 .flat_map(|pcids| pcids.cids.values()) -1100 { -1101 self.connection_ids.remove(cid); -1102 } -1103 self.incoming_connection_remotes.remove(&conn.network_path); -1104 self.outgoing_connection_remotes -1105 .remove(&conn.network_path.remote); -1106 for (remote, token) in conn.reset_token.values() { -1107 self.connection_reset_tokens.remove(*remote, *token); -1108 } -1109 } -1110 -1111 /// Find the existing connection that `datagram` should be routed to, if any -1112 fn get(&self, network_path: &FourTuple, datagram: &PartialDecode) -> Option<RouteDatagramTo> { -1113 if !datagram.dst_cid().is_empty() -1114 && let Some(&(ch, path_id)) = self.connection_ids.get(&datagram.dst_cid()) -1115 { -1116 return Some(RouteDatagramTo::Connection(ch, path_id)); -1117 } -1118 if (datagram.is_initial() || datagram.is_0rtt()) -1119 && let Some(&ch) = self.connection_ids_initial.get(&datagram.dst_cid()) -1120 { -1121 return Some(ch); -1122 } -1123 if datagram.dst_cid().is_empty() { -1124 if let Some(&ch) = self.incoming_connection_remotes.get(network_path) { -1125 // Never multipath because QUIC-MULTIPATH 1.1 mandates the use of non-zero -1126 // length CIDs. So this is always PathId::ZERO. -1127 return Some(RouteDatagramTo::Connection(ch, PathId::ZERO)); -1128 } -1129 if let Some(&ch) = self.outgoing_connection_remotes.get(&network_path.remote) { -1130 // Like above, QUIC-MULTIPATH 1.1 mandates the use of non-zero length CIDs. -1131 return Some(RouteDatagramTo::Connection(ch, PathId::ZERO)); -1132 } -1133 } -1134 let data = datagram.data(); -1135 if data.len() < RESET_TOKEN_SIZE { -1136 return None; -1137 } -1138 // For stateless resets the PathId is meaningless since it closes the entire -1139 // connection regardless of path. So use PathId::ZERO. -1140 self.connection_reset_tokens -1141 .get(network_path.remote, &data[data.len() - RESET_TOKEN_SIZE..]) -1142 .cloned() -1143 .map(|ch| RouteDatagramTo::Connection(ch, PathId::ZERO)) -1144 } -1145} -1146 -1147#[derive(Debug)] -1148pub(crate) struct ConnectionMeta { -1149 init_cid: ConnectionId, -1150 /// Locally issues CIDs for each path -1151 local_cids: FxHashMap<PathId, PathLocalCids>, -1152 /// Remote/local addresses the connection began with -1153 /// -1154 /// Only needed to support connections with zero-length CIDs, which cannot migrate, so we don't -1155 /// bother keeping it up to date. -1156 network_path: FourTuple, -1157 side: Side, -1158 /// Reset tokens provided by the peer for CIDs we're currently sending to -1159 /// -1160 /// Since each reset token is for a CID, it is also for a fixed remote address which is -1161 /// also stored. This allows us to look up which reset tokens we might expect from a -1162 /// given remote address, see [`ResetTokenTable`]. -1163 /// -1164 /// Each path has its own active CID. We use the [`PathId`] as a unique index, allowing -1165 /// us to retire the reset token when a path is abandoned. -1166 // TODO(matheus23): Should be migrated to make reset tokens per 4-tuple instead of per remote addr -1167 reset_token: FxHashMap<PathId, (SocketAddr, ResetToken)>, -1168} -1169 -1170/// Local connection IDs for a single path -1171#[derive(Debug, Default)] -1172struct PathLocalCids { -1173 /// Number of connection IDs that have been issued in (PATH_)NEW_CONNECTION_ID frames -1174 /// -1175 /// Another way of saying this is that this is the next sequence number to be issued. -1176 issued: u64, -1177 /// Issues CIDs indexed by their sequence number. -1178 cids: FxHashMap<u64, ConnectionId>, -1179} -1180 -1181/// Internal identifier for a `Connection` currently associated with an endpoint -1182#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] -1183pub struct ConnectionHandle(pub usize); -1184 -1185impl From<ConnectionHandle> for usize { -1186 fn from(x: ConnectionHandle) -> Self { -1187 x.0 -1188 } -1189} -1190 -1191impl Index<ConnectionHandle> for Slab<ConnectionMeta> { -1192 type Output = ConnectionMeta; -1193 fn index(&self, ch: ConnectionHandle) -> &ConnectionMeta { -1194 &self[ch.0] -1195 } -1196} -1197 -1198impl IndexMut<ConnectionHandle> for Slab<ConnectionMeta> { -1199 fn index_mut(&mut self, ch: ConnectionHandle) -> &mut ConnectionMeta { -1200 &mut self[ch.0] -1201 } -1202} -1203 -1204/// Event resulting from processing a single datagram -1205pub enum DatagramEvent { -1206 /// The datagram is redirected to its `Connection` -1207 ConnectionEvent(ConnectionHandle, ConnectionEvent), -1208 /// The datagram may result in starting a new `Connection` -1209 NewConnection(Incoming), -1210 /// Response generated directly by the endpoint -1211 Response(Transmit), -1212} -1213 -1214/// An incoming connection for which the server has not yet begun its part of the handshake. -1215#[derive(derive_more::Debug)] -1216pub struct Incoming { -1217 #[debug(skip)] -1218 received_at: Instant, -1219 network_path: FourTuple, -1220 ecn: Option<EcnCodepoint>, -1221 #[debug(skip)] -1222 packet: InitialPacket, -1223 #[debug(skip)] -1224 rest: Option<BytesMut>, -1225 #[debug(skip)] -1226 crypto: Keys, -1227 token: IncomingToken, -1228 incoming_idx: usize, +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 improper_drop_warner: IncomingImproperDropWarner, -1231} -1232 -1233impl Incoming { -1234 /// The local IP address which was used when the peer established the connection -1235 pub fn local_ip(&self) -> Option<IpAddr> { -1236 self.network_path.local_ip -1237 } -1238 -1239 /// The peer's UDP address -1240 pub fn remote_address(&self) -> SocketAddr { -1241 self.network_path.remote -1242 } -1243 -1244 /// Whether the socket address that is initiating this connection has been validated -1245 /// -1246 /// This means that the sender of the initial packet has proved that they can receive traffic -1247 /// sent to `self.remote_address()`. -1248 /// -1249 /// If `self.remote_address_validated()` is false, `self.may_retry()` is guaranteed to be true. -1250 /// The inverse is not guaranteed. -1251 pub fn remote_address_validated(&self) -> bool { -1252 self.token.validated -1253 } -1254 -1255 /// Whether it is legal to respond with a retry packet -1256 /// -1257 /// If `self.remote_address_validated()` is false, `self.may_retry()` is guaranteed to be true. -1258 /// The inverse is not guaranteed. -1259 pub fn may_retry(&self) -> bool { -1260 self.token.retry_src_cid.is_none() -1261 } -1262 -1263 /// The original destination connection ID sent by the client -1264 pub fn orig_dst_cid(&self) -> ConnectionId { -1265 self.token.orig_dst_cid -1266 } -1267 -1268 /// Decrypt the Initial packet payload -1269 /// -1270 /// This clones and decrypts the packet payload (~1200 bytes). -1271 /// Can be used to extract information from the TLS ClientHello without completing the handshake. -1272 pub fn decrypt(&self) -> Option<DecryptedInitial> { -1273 let packet_number = self.packet.header.number.expand(0); -1274 let mut payload = self.packet.payload.clone(); -1275 self.crypto -1276 .packet -1277 .remote -1278 .decrypt( -1279 PathId::ZERO, -1280 packet_number, -1281 &self.packet.header_data, -1282 &mut payload, -1283 ) -1284 .ok()?; -1285 Some(DecryptedInitial(payload.freeze())) -1286 } -1287} -1288 -1289/// Decrypted payload of a QUIC Initial packet -1290/// -1291/// Obtained via [`Incoming::decrypt`]. Can be used to extract information from -1292/// the TLS ClientHello without completing the handshake. -1293pub struct DecryptedInitial(Bytes); -1294 -1295impl DecryptedInitial { -1296 /// Best-effort extraction of the ALPN protocols from the TLS ClientHello -1297 /// -1298 /// Parses the CRYPTO frames to extract the ALPN extension. This is intended -1299 /// for routing and filtering; it is not guaranteed to succeed if the -1300 /// ClientHello spans multiple packets. Returns `None` if parsing fails. -1301 pub fn alpns(&self) -> Option<IncomingAlpns> { -1302 let frames = frame::Iter::new(self.0.clone()).ok()?; -1303 let mut first = None; -1304 let mut rest = Vec::new(); -1305 for frame in frames { -1306 match frame { -1307 Ok(frame::Frame::Crypto(crypto)) => match first { -1308 None => first = Some(crypto), -1309 Some(_) => rest.push(crypto), -1310 }, -1311 Err(_) => return None, -1312 _ => {} -1313 } -1314 } -1315 let first = first?; -1316 -1317 // Fast path: single CRYPTO frame at offset 0 (no extra allocation) -1318 if rest.is_empty() && first.offset == 0 { -1319 let data = find_alpn_data(&first.data).ok()?; -1320 return Some(IncomingAlpns { data, pos: 0 }); -1321 } -1322 -1323 // Slow path: reassemble multiple CRYPTO frames -1324 rest.push(first); -1325 let source = assemble_crypto_frames(&mut rest)?; -1326 let data = find_alpn_data(&source).ok()?; -1327 Some(IncomingAlpns { data, pos: 0 }) -1328 } -1329} -1330 -1331/// TLS handshake type for ClientHello messages -1332/// <https://www.rfc-editor.org/rfc/rfc8446#section-4.1.2> -1333const TLS_HANDSHAKE_TYPE_CLIENT_HELLO: u8 = 0x01; -1334/// TLS extension type for Application-Layer Protocol Negotiation -1335/// <https://www.rfc-editor.org/rfc/rfc7301#section-3.1> -1336const TLS_EXTENSION_TYPE_ALPN: u16 = 0x0010; -1337/// Size of the fixed-length fields in a ClientHello (client_version + random) -1338/// <https://www.rfc-editor.org/rfc/rfc8446#section-4.1.2> -1339const TLS_CLIENT_HELLO_FIXED_LEN: usize = 2 + 32; -1340 -1341/// Iterator over ALPN protocol names from a TLS ClientHello -1342/// -1343/// Yields protocol names as [`Bytes`] slices. On the common fast path (single -1344/// CRYPTO frame), the only allocation is the payload clone for decryption. -1345pub struct IncomingAlpns { -1346 data: Bytes, -1347 pos: usize, -1348} -1349 -1350impl Iterator for IncomingAlpns { -1351 type Item = Result<Bytes, UnexpectedEnd>; +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()`. +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 fn next(&mut self) -> Option<Self::Item> { -1354 if self.pos >= self.data.len() { -1355 return None; -1356 } -1357 let len = self.data[self.pos] as usize; -1358 self.pos += 1; -1359 if self.pos + len > self.data.len() { -1360 return Some(Err(UnexpectedEnd)); -1361 } -1362 let proto = self.data.slice(self.pos..self.pos + len); -1363 self.pos += len; -1364 Some(Ok(proto)) -1365 } -1366} -1367 -1368/// Sort CRYPTO frames by offset and concatenate into a contiguous `Bytes` -1369/// -1370/// Returns `None` if there are gaps in the stream. -1371fn assemble_crypto_frames(frames: &mut [frame::Crypto]) -> Option<Bytes> { -1372 frames.sort_by_key(|f| f.offset); -1373 let capacity = frames.iter().map(|f| f.data.len()).sum(); -1374 let mut buf = Vec::with_capacity(capacity); -1375 for f in frames.iter() { -1376 let start = f.offset as usize; -1377 if start > buf.len() { -1378 return None; -1379 } -1380 let end = start + f.data.len(); -1381 if end > buf.len() { -1382 buf.extend_from_slice(&f.data[buf.len() - start..]); -1383 } -1384 } -1385 Some(Bytes::from(buf)) -1386} -1387 -1388/// Locate the raw ALPN protocol list data within a TLS ClientHello message -1389/// -1390/// Parses the ClientHello in `source` and returns a [`Bytes`] containing the -1391/// u8-length-prefixed protocol names (after the outer ProtocolNameList u16 -1392/// length prefix). The returned `Bytes` is a zero-copy slice of `source`. -1393fn find_alpn_data(source: &Bytes) -> Result<Bytes, UnexpectedEnd> { -1394 let mut r = &**source; -1395 -1396 if u8::decode(&mut r)? != TLS_HANDSHAKE_TYPE_CLIENT_HELLO { -1397 return Err(UnexpectedEnd); -1398 } +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>; +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 // Handshake message length (u24), scopes the remainder -1401 let len = decode_u24(&mut r)?; -1402 let mut body = take(&mut r, len)?; -1403 -1404 // Client version + random -1405 skip(&mut body, TLS_CLIENT_HELLO_FIXED_LEN)?; -1406 -1407 // Session ID, cipher suites, compression methods -1408 skip_u8_prefixed(&mut body)?; -1409 skip_u16_prefixed(&mut body)?; -1410 skip_u8_prefixed(&mut body)?; +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 // Extensions -1413 let mut exts = take_u16_prefixed(&mut body)?; -1414 while exts.has_remaining() { -1415 let ext_type = u16::decode(&mut exts)?; -1416 let ext_data = take_u16_prefixed(&mut exts)?; -1417 if ext_type == TLS_EXTENSION_TYPE_ALPN { -1418 let list = take_u16_prefixed(&mut &*ext_data)?; -1419 return Ok(source.slice_ref(list)); -1420 } -1421 } -1422 Err(UnexpectedEnd) -1423} -1424 -1425/// Decode a big-endian u24 as usize -1426fn decode_u24(r: &mut &[u8]) -> Result<usize, UnexpectedEnd> { -1427 let a = u8::decode(r)?; -1428 let b = u8::decode(r)?; -1429 let c = u8::decode(r)?; -1430 Ok(u32::from_be_bytes([0, a, b, c]) as usize) -1431} -1432 -1433/// Take `len` bytes from the front and return them as a sub-slice -1434fn take<'a>(r: &mut &'a [u8], len: usize) -> Result<&'a [u8], UnexpectedEnd> { -1435 if r.remaining() < len { -1436 return Err(UnexpectedEnd); -1437 } -1438 let data = &r[..len]; -1439 r.advance(len); -1440 Ok(data) -1441} -1442 -1443/// Read a u16 length prefix and return the sub-slice it covers -1444fn take_u16_prefixed<'a>(r: &mut &'a [u8]) -> Result<&'a [u8], UnexpectedEnd> { -1445 let len = u16::decode(r)? as usize; -1446 take(r, len) -1447} -1448 -1449/// Advance past `n` bytes -1450fn skip(r: &mut &[u8], len: usize) -> Result<(), UnexpectedEnd> { -1451 take(r, len)?; -1452 Ok(()) +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)?; +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/// Skip a u8-length-prefixed field -1456fn skip_u8_prefixed(r: &mut &[u8]) -> Result<(), UnexpectedEnd> { -1457 let len = u8::decode(r)? as usize; -1458 skip(r, len) +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/// Skip a u16-length-prefixed field -1462fn skip_u16_prefixed(r: &mut &[u8]) -> Result<(), UnexpectedEnd> { -1463 let len = u16::decode(r)? as usize; -1464 skip(r, len) +1461/// Advance past `n` bytes +1462fn skip(r: &mut &[u8], len: usize) -> Result<(), UnexpectedEnd> { +1463 take(r, len)?; +1464 Ok(()) 1465} 1466 -1467struct IncomingImproperDropWarner; -1468 -1469impl IncomingImproperDropWarner { -1470 fn dismiss(self) { -1471 mem::forget(self); -1472 } -1473} -1474 -1475impl Drop for IncomingImproperDropWarner { -1476 fn drop(&mut self) { -1477 warn!( -1478 "noq_proto::Incoming dropped without passing to Endpoint::accept/refuse/retry/ignore \ -1479 (may cause memory leak and eventual inability to accept new connections)" -1480 ); -1481 } -1482} -1483 -1484/// Errors in the parameters being used to create a new connection -1485/// -1486/// These arise before any I/O has been performed. -1487#[derive(Debug, Error, Clone, PartialEq, Eq)] -1488pub enum ConnectError { -1489 /// The endpoint can no longer create new connections -1490 /// -1491 /// Indicates that a necessary component of the endpoint has been dropped or otherwise disabled. -1492 #[error("endpoint stopping")] -1493 EndpointStopping, -1494 /// The connection could not be created because not enough of the CID space is available -1495 /// -1496 /// Try using longer connection IDs -1497 #[error("CIDs exhausted")] -1498 CidsExhausted, -1499 /// The given server name was malformed -1500 #[error("invalid server name: {0}")] -1501 InvalidServerName(String), -1502 /// The remote [`SocketAddr`] supplied was malformed -1503 /// -1504 /// Examples include attempting to connect to port 0, or using an inappropriate address family. -1505 #[error("invalid remote address: {0}")] -1506 InvalidRemoteAddress(SocketAddr), -1507 /// No default client configuration was set up -1508 /// -1509 /// Use `Endpoint::connect_with` to specify a client configuration. -1510 #[error("no default client config")] -1511 NoDefaultClientConfig, -1512 /// The local endpoint does not support the QUIC version specified in the client configuration -1513 #[error("unsupported QUIC version")] -1514 UnsupportedVersion, -1515} -1516 -1517/// Error type for attempting to accept an [`Incoming`] -1518#[derive(Debug)] -1519pub struct AcceptError { -1520 /// Underlying error describing reason for failure -1521 pub cause: ConnectionError, -1522 /// Optional response to transmit back -1523 pub response: Option<Transmit>, -1524} -1525 -1526/// Error for attempting to retry an [`Incoming`] which already bears a token from a previous retry -1527#[derive(Debug, Error)] -1528#[error("retry() with validated Incoming")] -1529pub struct RetryError(Box<Incoming>); -1530 -1531impl RetryError { -1532 /// Get the [`Incoming`] -1533 pub fn into_incoming(self) -> Incoming { -1534 *self.0 -1535 } +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/// Reset Tokens which are associated with peer socket addresses -1539/// -1540/// The standard `HashMap` is used since both `SocketAddr` and `ResetToken` are -1541/// peer generated and might be usable for hash collision attacks. -1542#[derive(Default, Debug)] -1543struct ResetTokenTable(HashMap<SocketAddr, HashMap<ResetToken, ConnectionHandle>>); -1544 -1545impl ResetTokenTable { -1546 fn insert(&mut self, remote: SocketAddr, token: ResetToken, ch: ConnectionHandle) -> bool { -1547 self.0 -1548 .entry(remote) -1549 .or_default() -1550 .insert(token, ch) -1551 .is_some() -1552 } -1553 -1554 fn remove(&mut self, remote: SocketAddr, token: ResetToken) { -1555 use std::collections::hash_map::Entry; -1556 match self.0.entry(remote) { -1557 Entry::Vacant(_) => {} -1558 Entry::Occupied(mut e) => { -1559 e.get_mut().remove(&token); -1560 if e.get().is_empty() { -1561 e.remove_entry(); -1562 } -1563 } -1564 } -1565 } -1566 -1567 fn get(&self, remote: SocketAddr, token: &[u8]) -> Option<&ConnectionHandle> { -1568 let token = ResetToken::from(<[u8; RESET_TOKEN_SIZE]>::try_from(token).ok()?); -1569 self.0.get(&remote)?.get(&token) -1570 } -1571} -1572 -1573#[cfg(test)] -1574mod tests { -1575 use super::*; -1576 -1577 #[test] -1578 fn assemble_contiguous() { -1579 let data = b"hello world"; -1580 let mut frames = vec![ -1581 frame::Crypto { -1582 offset: 0, -1583 data: Bytes::from_static(&data[..5]), -1584 }, -1585 frame::Crypto { -1586 offset: 5, -1587 data: Bytes::from_static(&data[5..]), -1588 }, -1589 ]; -1590 assert_eq!(&assemble_crypto_frames(&mut frames).unwrap()[..], &data[..]); -1591 } -1592 -1593 #[test] -1594 fn assemble_out_of_order() { -1595 let data = b"hello world"; -1596 let mut frames = vec![ +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 frame::Crypto { -1602 offset: 0, -1603 data: Bytes::from_static(&data[..5]), -1604 }, -1605 ]; -1606 assert_eq!(&assemble_crypto_frames(&mut frames).unwrap()[..], &data[..]); -1607 } -1608 -1609 #[test] -1610 fn assemble_with_overlap() { -1611 let data = b"hello world"; -1612 let mut frames = vec![ +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[..7]), +1615 data: Bytes::from_static(&data[..5]), 1616 }, -1617 frame::Crypto { -1618 offset: 5, -1619 data: Bytes::from_static(&data[5..]), -1620 }, -1621 ]; -1622 assert_eq!(&assemble_crypto_frames(&mut frames).unwrap()[..], &data[..]); -1623 } -1624 -1625 #[test] -1626 fn assemble_with_gap() { -1627 let mut frames = vec![frame::Crypto { -1628 offset: 10, -1629 data: Bytes::from_static(b"world"), -1630 }]; -1631 assert!(assemble_crypto_frames(&mut frames).is_none()); -1632 } -1633} \ No newline at end of file +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 diff --git a/pr/451/docs/src/noq_proto/frame.rs.html b/pr/451/docs/src/noq_proto/frame.rs.html index eec6ca497..3a12df1b4 100644 --- a/pr/451/docs/src/noq_proto/frame.rs.html +++ b/pr/451/docs/src/noq_proto/frame.rs.html @@ -216,2629 +216,2652 @@ 216 MaxData(MaxData), 217 MaxStreamData(MaxStreamData), 218 MaxStreams(MaxStreams), -219} -220 -221impl<'a> Encodable for EncodableFrame<'a> { -222 fn encode<B: BufMut>(&self, buf: &mut B) { -223 self.encode_inner(buf) -224 } -225} -226 -227pub(crate) trait FrameStruct { -228 /// Smallest number of bytes this type of frame is guaranteed to fit within. -229 const SIZE_BOUND: usize; -230} -231 -232/// The type used to refer to [`FrameType`]s in closing and transport errors. -233#[derive(Copy, Clone, Eq, PartialEq, derive_more::Debug, derive_more::Display)] -234pub enum MaybeFrame { -235 /// Not attributed to any particular [`FrameType`]. -236 None, -237 /// Attributed to some frame type this implementation does not recognize. -238 #[display("UNKNOWN({:02x})", _0)] -239 #[debug("Unknown({:02x})", _0)] -240 Unknown(u64), -241 /// Attributed to a specific [`FrameType`], never [`FrameType::Padding`]. -242 Known(FrameType), -243} -244 -245impl MaybeFrame { -246 /// Encoded size of this [`MaybeFrame`]. -247 const fn size(&self) -> usize { -248 match self { -249 Self::None => VarInt(0).size(), -250 Self::Unknown(other) => VarInt(*other).size(), -251 Self::Known(frame_type) => frame_type.size(), -252 } -253 } -254} -255 -256impl Decodable for MaybeFrame { -257 fn decode<B: Buf>(buf: &mut B) -> coding::Result<Self> { -258 match FrameType::try_from(buf.get_var()?) { -259 Ok(FrameType::Padding) => Ok(Self::None), -260 Ok(other_frame) => Ok(Self::Known(other_frame)), -261 Err(InvalidFrameId(other)) => Ok(Self::Unknown(other)), -262 } -263 } -264} -265 -266impl Encodable for MaybeFrame { -267 fn encode<B: BufMut>(&self, buf: &mut B) { -268 match self { -269 Self::None => buf.write_var(0u64), -270 Self::Unknown(frame_id) => buf.write_var(*frame_id), -271 Self::Known(frame_type) => buf.write(*frame_type), -272 } -273 } -274} -275 -276#[cfg(test)] -277impl proptest::arbitrary::Arbitrary for MaybeFrame { -278 type Parameters = (); -279 type Strategy = proptest::strategy::BoxedStrategy<Self>; -280 -281 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { -282 use proptest::prelude::*; -283 prop_oneof![ -284 Just(Self::None), -285 any::<VarInt>().prop_map(|v| Self::Unknown(v.0)), -286 // do not generate padding frames here, since they are not allowed in MaybeFrame::Known -287 any::<FrameType>() -288 .prop_filter("not Padding", |ft| *ft != FrameType::Padding) -289 .prop_map(MaybeFrame::Known), -290 ] -291 .boxed() -292 } -293} -294 -295#[derive(derive_more::Display)] -296#[cfg_attr(test, derive(Arbitrary, Debug, Clone, PartialEq, Eq))] -297#[display("HANDSHAKE_DONE")] -298pub(crate) struct HandshakeDone; -299 -300impl HandshakeDone { -301 const fn get_type(&self) -> FrameType { -302 FrameType::HandshakeDone -303 } -304} -305 -306impl Encodable for HandshakeDone { -307 fn encode<B: BufMut>(&self, buf: &mut B) { -308 FrameType::HandshakeDone.encode(buf); -309 } -310} -311 -312#[derive(derive_more::Display)] -313#[cfg_attr(test, derive(Arbitrary, Debug, Clone, PartialEq, Eq))] -314#[display("PING")] -315pub(crate) struct Ping; -316 -317impl Ping { -318 const fn get_type(&self) -> FrameType { -319 FrameType::Ping -320 } -321} -322 -323impl Encodable for Ping { -324 fn encode<B: BufMut>(&self, buf: &mut B) { -325 FrameType::Ping.encode(buf); -326 } -327} -328 -329#[derive(derive_more::Display)] -330#[cfg_attr(test, derive(Arbitrary, Debug, Clone, PartialEq, Eq))] -331#[display("IMMEDIATE_ACK")] -332pub(crate) struct ImmediateAck; -333 -334impl ImmediateAck { -335 const fn get_type(&self) -> FrameType { -336 FrameType::ImmediateAck -337 } -338} -339 -340impl Encodable for ImmediateAck { -341 fn encode<B: BufMut>(&self, buf: &mut B) { -342 FrameType::ImmediateAck.encode(buf); -343 } -344} -345 -346#[allow(missing_docs)] -347#[derive(Debug, Copy, Clone, Eq, PartialEq, derive_more::Display)] -348#[display("STREAM")] -349#[cfg_attr(test, derive(Arbitrary))] -350pub struct StreamInfo(#[cfg_attr(test, strategy(0x08u8..=0x0f))] u8); -351 -352impl StreamInfo { -353 const VALUES: RangeInclusive<u64> = RangeInclusive::new(0x08, 0x0f); -354 fn fin(self) -> bool { -355 self.0 & 0x01 != 0 -356 } -357 fn len(self) -> bool { -358 self.0 & 0x02 != 0 -359 } -360 fn off(self) -> bool { -361 self.0 & 0x04 != 0 -362 } -363 -364 const fn to_u64(self) -> u64 { -365 self.0 as u64 -366 } -367} -368 -369#[allow(missing_docs)] -370#[derive(Debug, Copy, Clone, Eq, PartialEq, derive_more::Display)] -371#[display("DATAGRAM")] -372#[cfg_attr(test, derive(Arbitrary))] -373pub struct DatagramInfo(#[cfg_attr(test, strategy(0x30u8..=0x31))] u8); -374 -375impl DatagramInfo { -376 const VALUES: RangeInclusive<u64> = RangeInclusive::new(0x30, 0x31); +219 StreamsBlocked(StreamsBlocked), +220} +221 +222impl<'a> EncodableFrame<'a> { +223 /// Whether this is an ACK-eliciting frame. +224 pub(crate) fn is_ack_eliciting(&self) -> bool { +225 match self { +226 EncodableFrame::PathAck(_) | EncodableFrame::Ack(_) | EncodableFrame::Close(_) => false, +227 EncodableFrame::PathResponse(_) +228 | EncodableFrame::HandshakeDone(_) +229 | EncodableFrame::ReachOut(_) +230 | EncodableFrame::ObservedAddr(_) +231 | EncodableFrame::Ping(_) +232 | EncodableFrame::ImmediateAck(_) +233 | EncodableFrame::AckFrequency(_) +234 | EncodableFrame::PathChallenge(_) +235 | EncodableFrame::Crypto(_) +236 | EncodableFrame::PathAbandon(_) +237 | EncodableFrame::PathStatusAvailable(_) +238 | EncodableFrame::PathStatusBackup(_) +239 | EncodableFrame::MaxPathId(_) +240 | EncodableFrame::PathsBlocked(_) +241 | EncodableFrame::PathCidsBlocked(_) +242 | EncodableFrame::ResetStream(_) +243 | EncodableFrame::StopSending(_) +244 | EncodableFrame::NewConnectionId(_) +245 | EncodableFrame::RetireConnectionId(_) +246 | EncodableFrame::Datagram(_) +247 | EncodableFrame::NewToken(_) +248 | EncodableFrame::AddAddress(_) +249 | EncodableFrame::RemoveAddress(_) +250 | EncodableFrame::StreamMeta(_) +251 | EncodableFrame::MaxData(_) +252 | EncodableFrame::MaxStreamData(_) +253 | EncodableFrame::MaxStreams(_) +254 | EncodableFrame::StreamsBlocked(_) => true, +255 } +256 } +257} +258 +259impl<'a> Encodable for EncodableFrame<'a> { +260 fn encode<B: BufMut>(&self, buf: &mut B) { +261 self.encode_inner(buf) +262 } +263} +264 +265pub(crate) trait FrameStruct { +266 /// Smallest number of bytes this type of frame is guaranteed to fit within. +267 const SIZE_BOUND: usize; +268} +269 +270/// The type used to refer to [`FrameType`]s in closing and transport errors. +271#[derive(Copy, Clone, Eq, PartialEq, derive_more::Debug, derive_more::Display)] +272pub enum MaybeFrame { +273 /// Not attributed to any particular [`FrameType`]. +274 None, +275 /// Attributed to some frame type this implementation does not recognize. +276 #[display("UNKNOWN({:02x})", _0)] +277 #[debug("Unknown({:02x})", _0)] +278 Unknown(u64), +279 /// Attributed to a specific [`FrameType`], never [`FrameType::Padding`]. +280 Known(FrameType), +281} +282 +283impl MaybeFrame { +284 /// Encoded size of this [`MaybeFrame`]. +285 const fn size(&self) -> usize { +286 match self { +287 Self::None => VarInt(0).size(), +288 Self::Unknown(other) => VarInt(*other).size(), +289 Self::Known(frame_type) => frame_type.size(), +290 } +291 } +292} +293 +294impl Decodable for MaybeFrame { +295 fn decode<B: Buf>(buf: &mut B) -> coding::Result<Self> { +296 match FrameType::try_from(buf.get_var()?) { +297 Ok(FrameType::Padding) => Ok(Self::None), +298 Ok(other_frame) => Ok(Self::Known(other_frame)), +299 Err(InvalidFrameId(other)) => Ok(Self::Unknown(other)), +300 } +301 } +302} +303 +304impl Encodable for MaybeFrame { +305 fn encode<B: BufMut>(&self, buf: &mut B) { +306 match self { +307 Self::None => buf.write_var(0u64), +308 Self::Unknown(frame_id) => buf.write_var(*frame_id), +309 Self::Known(frame_type) => buf.write(*frame_type), +310 } +311 } +312} +313 +314#[cfg(test)] +315impl proptest::arbitrary::Arbitrary for MaybeFrame { +316 type Parameters = (); +317 type Strategy = proptest::strategy::BoxedStrategy<Self>; +318 +319 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { +320 use proptest::prelude::*; +321 prop_oneof![ +322 Just(Self::None), +323 any::<VarInt>().prop_map(|v| Self::Unknown(v.0)), +324 // do not generate padding frames here, since they are not allowed in MaybeFrame::Known +325 any::<FrameType>() +326 .prop_filter("not Padding", |ft| *ft != FrameType::Padding) +327 .prop_map(MaybeFrame::Known), +328 ] +329 .boxed() +330 } +331} +332 +333#[derive(derive_more::Display)] +334#[cfg_attr(test, derive(Arbitrary, Debug, Clone, PartialEq, Eq))] +335#[display("HANDSHAKE_DONE")] +336pub(crate) struct HandshakeDone; +337 +338impl HandshakeDone { +339 const fn get_type(&self) -> FrameType { +340 FrameType::HandshakeDone +341 } +342} +343 +344impl Encodable for HandshakeDone { +345 fn encode<B: BufMut>(&self, buf: &mut B) { +346 FrameType::HandshakeDone.encode(buf); +347 } +348} +349 +350#[derive(derive_more::Display)] +351#[cfg_attr(test, derive(Arbitrary, Debug, Clone, PartialEq, Eq))] +352#[display("PING")] +353pub(crate) struct Ping; +354 +355impl Ping { +356 const fn get_type(&self) -> FrameType { +357 FrameType::Ping +358 } +359} +360 +361impl Encodable for Ping { +362 fn encode<B: BufMut>(&self, buf: &mut B) { +363 FrameType::Ping.encode(buf); +364 } +365} +366 +367#[derive(derive_more::Display)] +368#[cfg_attr(test, derive(Arbitrary, Debug, Clone, PartialEq, Eq))] +369#[display("IMMEDIATE_ACK")] +370pub(crate) struct ImmediateAck; +371 +372impl ImmediateAck { +373 const fn get_type(&self) -> FrameType { +374 FrameType::ImmediateAck +375 } +376} 377 -378 fn len(self) -> bool { -379 self.0 & 0x01 != 0 -380 } -381 -382 const fn to_u64(self) -> u64 { -383 self.0 as u64 -384 } -385} -386 -387#[derive(Debug, derive_more::Display)] -388#[cfg_attr(test, derive(Arbitrary))] -389pub(crate) enum Frame { -390 #[display("PADDING")] -391 Padding, -392 #[display("PING")] -393 Ping, -394 Ack(Ack), -395 PathAck(PathAck), -396 ResetStream(ResetStream), -397 StopSending(StopSending), -398 Crypto(Crypto), -399 NewToken(NewToken), -400 Stream(Stream), -401 MaxData(MaxData), -402 MaxStreamData(MaxStreamData), -403 MaxStreams(MaxStreams), -404 DataBlocked(DataBlocked), -405 StreamDataBlocked(StreamDataBlocked), -406 StreamsBlocked(StreamsBlocked), -407 NewConnectionId(NewConnectionId), -408 RetireConnectionId(RetireConnectionId), -409 PathChallenge(PathChallenge), -410 PathResponse(PathResponse), -411 Close(Close), -412 Datagram(Datagram), -413 AckFrequency(AckFrequency), -414 #[display("IMMEDIATE_ACK")] -415 ImmediateAck, -416 #[display("HANDSHAKE_DONE")] -417 HandshakeDone, -418 ObservedAddr(ObservedAddr), -419 PathAbandon(PathAbandon), -420 PathStatusAvailable(PathStatusAvailable), -421 PathStatusBackup(PathStatusBackup), -422 MaxPathId(MaxPathId), -423 PathsBlocked(PathsBlocked), -424 PathCidsBlocked(PathCidsBlocked), -425 AddAddress(AddAddress), -426 ReachOut(ReachOut), -427 RemoveAddress(RemoveAddress), -428} -429 -430impl Frame { -431 pub(crate) fn ty(&self) -> FrameType { -432 use Frame::*; -433 match &self { -434 Padding => FrameType::Padding, -435 ResetStream(_) => FrameType::ResetStream, -436 Close(self::Close::Connection(_)) => FrameType::ConnectionClose, -437 Close(self::Close::Application(_)) => FrameType::ConnectionClose, -438 MaxData(_) => FrameType::MaxData, -439 MaxStreamData(_) => FrameType::MaxStreamData, -440 MaxStreams(max_streams) => max_streams.get_type(), -441 Ping => FrameType::Ping, -442 DataBlocked(_) => FrameType::DataBlocked, -443 StreamDataBlocked(sdb) => sdb.get_type(), -444 StreamsBlocked(sb) => sb.get_type(), -445 StopSending { .. } => FrameType::StopSending, -446 RetireConnectionId(retire_frame) => retire_frame.get_type(), -447 Ack(ack) => ack.get_type(), -448 PathAck(path_ack) => path_ack.get_type(), -449 Stream(x) => { -450 let mut ty = *StreamInfo::VALUES.start() as u8; -451 if x.fin { -452 ty |= 0x01; -453 } -454 if x.offset != 0 { -455 ty |= 0x04; -456 } -457 // TODO(@divma): move all this to getframetype for Stream -458 FrameType::Stream(StreamInfo(ty)) -459 } -460 PathChallenge(_) => FrameType::PathChallenge, -461 PathResponse(_) => FrameType::PathResponse, -462 NewConnectionId(cid) => cid.get_type(), -463 Crypto(_) => FrameType::Crypto, -464 NewToken(_) => FrameType::NewToken, -465 Datagram(_) => FrameType::Datagram(DatagramInfo(*DatagramInfo::VALUES.start() as u8)), -466 AckFrequency(_) => FrameType::AckFrequency, -467 ImmediateAck => FrameType::ImmediateAck, -468 HandshakeDone => FrameType::HandshakeDone, -469 ObservedAddr(observed) => observed.get_type(), -470 PathAbandon(_) => FrameType::PathAbandon, -471 PathStatusAvailable(_) => FrameType::PathStatusAvailable, -472 PathStatusBackup(_) => FrameType::PathStatusBackup, -473 MaxPathId(_) => FrameType::MaxPathId, -474 PathsBlocked(_) => FrameType::PathsBlocked, -475 PathCidsBlocked(_) => FrameType::PathCidsBlocked, -476 AddAddress(frame) => frame.get_type(), -477 ReachOut(frame) => frame.get_type(), -478 RemoveAddress(_) => self::RemoveAddress::TYPE, -479 } -480 } -481 -482 pub(crate) fn is_ack_eliciting(&self) -> bool { -483 !matches!( -484 *self, -485 Self::Ack(_) | Self::PathAck(_) | Self::Padding | Self::Close(_) -486 ) -487 } -488 -489 /// Returns `true` if this frame MUST be sent in 1-RTT space -490 pub(crate) fn is_1rtt(&self) -> bool { -491 // See also https://www.ietf.org/archive/id/draft-ietf-quic-multipath-17.html#section-4-1: -492 // > All frames defined in this document MUST only be sent in 1-RTT packets. -493 // > If an endpoint receives a multipath-specific frame in a different packet type, it MUST close the -494 // > connection with an error of type PROTOCOL_VIOLATION. -495 -496 self.is_multipath_frame() || self.is_qad_frame() -497 } -498 -499 fn is_qad_frame(&self) -> bool { -500 matches!(*self, Self::ObservedAddr(_)) -501 } -502 -503 fn is_multipath_frame(&self) -> bool { -504 matches!( -505 *self, -506 Self::PathAck(_) -507 | Self::PathAbandon(_) -508 | Self::PathStatusBackup(_) -509 | Self::PathStatusAvailable(_) -510 | Self::MaxPathId(_) -511 | Self::PathsBlocked(_) -512 | Self::PathCidsBlocked(_) -513 | Self::NewConnectionId(NewConnectionId { -514 path_id: Some(_), -515 .. -516 }) -517 | Self::RetireConnectionId(RetireConnectionId { -518 path_id: Some(_), -519 .. -520 }) -521 ) -522 } -523} -524 -525#[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Display)] -526#[cfg_attr(test, derive(Arbitrary))] -527#[display("PATH_CHALLENGE({_0:08x})")] -528pub(crate) struct PathChallenge(pub(crate) u64); -529 -530impl PathChallenge { -531 pub(crate) const SIZE_BOUND: usize = 9; -532 -533 const fn get_type(&self) -> FrameType { -534 FrameType::PathChallenge +378impl Encodable for ImmediateAck { +379 fn encode<B: BufMut>(&self, buf: &mut B) { +380 FrameType::ImmediateAck.encode(buf); +381 } +382} +383 +384#[allow(missing_docs)] +385#[derive(Debug, Copy, Clone, Eq, PartialEq, derive_more::Display)] +386#[display("STREAM")] +387#[cfg_attr(test, derive(Arbitrary))] +388pub struct StreamInfo(#[cfg_attr(test, strategy(0x08u8..=0x0f))] u8); +389 +390impl StreamInfo { +391 const VALUES: RangeInclusive<u64> = RangeInclusive::new(0x08, 0x0f); +392 fn fin(self) -> bool { +393 self.0 & 0x01 != 0 +394 } +395 fn len(self) -> bool { +396 self.0 & 0x02 != 0 +397 } +398 fn off(self) -> bool { +399 self.0 & 0x04 != 0 +400 } +401 +402 const fn to_u64(self) -> u64 { +403 self.0 as u64 +404 } +405} +406 +407#[allow(missing_docs)] +408#[derive(Debug, Copy, Clone, Eq, PartialEq, derive_more::Display)] +409#[display("DATAGRAM")] +410#[cfg_attr(test, derive(Arbitrary))] +411pub struct DatagramInfo(#[cfg_attr(test, strategy(0x30u8..=0x31))] u8); +412 +413impl DatagramInfo { +414 const VALUES: RangeInclusive<u64> = RangeInclusive::new(0x30, 0x31); +415 +416 fn len(self) -> bool { +417 self.0 & 0x01 != 0 +418 } +419 +420 const fn to_u64(self) -> u64 { +421 self.0 as u64 +422 } +423} +424 +425#[derive(Debug, derive_more::Display)] +426#[cfg_attr(test, derive(Arbitrary))] +427pub(crate) enum Frame { +428 #[display("PADDING")] +429 Padding, +430 #[display("PING")] +431 Ping, +432 Ack(Ack), +433 PathAck(PathAck), +434 ResetStream(ResetStream), +435 StopSending(StopSending), +436 Crypto(Crypto), +437 NewToken(NewToken), +438 Stream(Stream), +439 MaxData(MaxData), +440 MaxStreamData(MaxStreamData), +441 MaxStreams(MaxStreams), +442 DataBlocked(DataBlocked), +443 StreamDataBlocked(StreamDataBlocked), +444 StreamsBlocked(StreamsBlocked), +445 NewConnectionId(NewConnectionId), +446 RetireConnectionId(RetireConnectionId), +447 PathChallenge(PathChallenge), +448 PathResponse(PathResponse), +449 Close(Close), +450 Datagram(Datagram), +451 AckFrequency(AckFrequency), +452 #[display("IMMEDIATE_ACK")] +453 ImmediateAck, +454 #[display("HANDSHAKE_DONE")] +455 HandshakeDone, +456 ObservedAddr(ObservedAddr), +457 PathAbandon(PathAbandon), +458 PathStatusAvailable(PathStatusAvailable), +459 PathStatusBackup(PathStatusBackup), +460 MaxPathId(MaxPathId), +461 PathsBlocked(PathsBlocked), +462 PathCidsBlocked(PathCidsBlocked), +463 AddAddress(AddAddress), +464 ReachOut(ReachOut), +465 RemoveAddress(RemoveAddress), +466} +467 +468impl Frame { +469 pub(crate) fn ty(&self) -> FrameType { +470 use Frame::*; +471 match &self { +472 Padding => FrameType::Padding, +473 ResetStream(_) => FrameType::ResetStream, +474 Close(self::Close::Connection(_)) => FrameType::ConnectionClose, +475 Close(self::Close::Application(_)) => FrameType::ConnectionClose, +476 MaxData(_) => FrameType::MaxData, +477 MaxStreamData(_) => FrameType::MaxStreamData, +478 MaxStreams(max_streams) => max_streams.get_type(), +479 Ping => FrameType::Ping, +480 DataBlocked(_) => FrameType::DataBlocked, +481 StreamDataBlocked(sdb) => sdb.get_type(), +482 StreamsBlocked(sb) => sb.get_type(), +483 StopSending { .. } => FrameType::StopSending, +484 RetireConnectionId(retire_frame) => retire_frame.get_type(), +485 Ack(ack) => ack.get_type(), +486 PathAck(path_ack) => path_ack.get_type(), +487 Stream(x) => { +488 let mut ty = *StreamInfo::VALUES.start() as u8; +489 if x.fin { +490 ty |= 0x01; +491 } +492 if x.offset != 0 { +493 ty |= 0x04; +494 } +495 // TODO(@divma): move all this to getframetype for Stream +496 FrameType::Stream(StreamInfo(ty)) +497 } +498 PathChallenge(_) => FrameType::PathChallenge, +499 PathResponse(_) => FrameType::PathResponse, +500 NewConnectionId(cid) => cid.get_type(), +501 Crypto(_) => FrameType::Crypto, +502 NewToken(_) => FrameType::NewToken, +503 Datagram(_) => FrameType::Datagram(DatagramInfo(*DatagramInfo::VALUES.start() as u8)), +504 AckFrequency(_) => FrameType::AckFrequency, +505 ImmediateAck => FrameType::ImmediateAck, +506 HandshakeDone => FrameType::HandshakeDone, +507 ObservedAddr(observed) => observed.get_type(), +508 PathAbandon(_) => FrameType::PathAbandon, +509 PathStatusAvailable(_) => FrameType::PathStatusAvailable, +510 PathStatusBackup(_) => FrameType::PathStatusBackup, +511 MaxPathId(_) => FrameType::MaxPathId, +512 PathsBlocked(_) => FrameType::PathsBlocked, +513 PathCidsBlocked(_) => FrameType::PathCidsBlocked, +514 AddAddress(frame) => frame.get_type(), +515 ReachOut(frame) => frame.get_type(), +516 RemoveAddress(_) => self::RemoveAddress::TYPE, +517 } +518 } +519 +520 pub(crate) fn is_ack_eliciting(&self) -> bool { +521 !matches!( +522 *self, +523 Self::Ack(_) | Self::PathAck(_) | Self::Padding | Self::Close(_) +524 ) +525 } +526 +527 /// Returns `true` if this frame MUST be sent in 1-RTT space +528 pub(crate) fn is_1rtt(&self) -> bool { +529 // See also https://www.ietf.org/archive/id/draft-ietf-quic-multipath-17.html#section-4-1: +530 // > All frames defined in this document MUST only be sent in 1-RTT packets. +531 // > If an endpoint receives a multipath-specific frame in a different packet type, it MUST close the +532 // > connection with an error of type PROTOCOL_VIOLATION. +533 +534 self.is_multipath_frame() || self.is_qad_frame() 535 } -536} -537 -538impl Decodable for PathChallenge { -539 fn decode<B: Buf>(buf: &mut B) -> coding::Result<Self> { -540 Ok(Self(buf.get()?)) -541 } -542} -543 -544impl Encodable for PathChallenge { -545 fn encode<B: BufMut>(&self, buf: &mut B) { -546 buf.write(FrameType::PathChallenge); -547 buf.write(self.0); -548 } -549} -550 -551#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, derive_more::Display)] -552#[cfg_attr(test, derive(Arbitrary))] -553#[display("PATH_RESPONSE({_0:08x})")] -554pub(crate) struct PathResponse(pub(crate) u64); -555 -556impl PathResponse { -557 pub(crate) const SIZE_BOUND: usize = 9; -558 -559 const fn get_type(&self) -> FrameType { -560 FrameType::PathResponse -561 } -562} -563 -564impl Decodable for PathResponse { -565 fn decode<B: Buf>(buf: &mut B) -> coding::Result<Self> { -566 Ok(Self(buf.get()?)) -567 } -568} -569 -570impl Encodable for PathResponse { -571 fn encode<B: BufMut>(&self, buf: &mut B) { -572 buf.write(FrameType::PathResponse); -573 buf.write(self.0); -574 } -575} -576 -577#[derive(Debug, Clone, Copy, derive_more::Display)] -578#[cfg_attr(test, derive(Arbitrary, PartialEq, Eq))] -579#[display("DATA_BLOCKED offset: {_0}")] -580pub(crate) struct DataBlocked(#[cfg_attr(test, strategy(varint_u64()))] pub(crate) u64); +536 +537 fn is_qad_frame(&self) -> bool { +538 matches!(*self, Self::ObservedAddr(_)) +539 } +540 +541 fn is_multipath_frame(&self) -> bool { +542 matches!( +543 *self, +544 Self::PathAck(_) +545 | Self::PathAbandon(_) +546 | Self::PathStatusBackup(_) +547 | Self::PathStatusAvailable(_) +548 | Self::MaxPathId(_) +549 | Self::PathsBlocked(_) +550 | Self::PathCidsBlocked(_) +551 | Self::NewConnectionId(NewConnectionId { +552 path_id: Some(_), +553 .. +554 }) +555 | Self::RetireConnectionId(RetireConnectionId { +556 path_id: Some(_), +557 .. +558 }) +559 ) +560 } +561} +562 +563#[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Display)] +564#[cfg_attr(test, derive(Arbitrary))] +565#[display("PATH_CHALLENGE({_0:08x})")] +566pub(crate) struct PathChallenge(pub(crate) u64); +567 +568impl PathChallenge { +569 pub(crate) const SIZE_BOUND: usize = 9; +570 +571 const fn get_type(&self) -> FrameType { +572 FrameType::PathChallenge +573 } +574} +575 +576impl Decodable for PathChallenge { +577 fn decode<B: Buf>(buf: &mut B) -> coding::Result<Self> { +578 Ok(Self(buf.get()?)) +579 } +580} 581 -582impl Encodable for DataBlocked { +582impl Encodable for PathChallenge { 583 fn encode<B: BufMut>(&self, buf: &mut B) { -584 buf.write(FrameType::DataBlocked); -585 buf.write_var(self.0); +584 buf.write(FrameType::PathChallenge); +585 buf.write(self.0); 586 } 587} 588 -589#[derive(Debug, Clone, Copy, derive_more::Display)] -590#[cfg_attr(test, derive(Arbitrary, PartialEq, Eq))] -591#[display("STREAM_DATA_BLOCKED id: {id} offset: {offset}")] -592pub(crate) struct StreamDataBlocked { -593 pub(crate) id: StreamId, -594 #[cfg_attr(test, strategy(varint_u64()))] -595 pub(crate) offset: u64, -596} -597 -598impl StreamDataBlocked { -599 const fn get_type(&self) -> FrameType { -600 FrameType::StreamDataBlocked -601 } -602} -603 -604impl Encodable for StreamDataBlocked { -605 fn encode<B: BufMut>(&self, buf: &mut B) { -606 buf.write(FrameType::StreamDataBlocked); -607 buf.write(self.id); -608 buf.write_var(self.offset); -609 } -610} -611 -612#[derive(Debug, Clone, Copy, derive_more::Display)] -613#[cfg_attr(test, derive(Arbitrary, PartialEq, Eq))] -614#[display("STREAMS_BLOCKED dir: {:?} limit: {limit}", dir)] -615pub(crate) struct StreamsBlocked { -616 pub(crate) dir: Dir, -617 #[cfg_attr(test, strategy(varint_u64()))] -618 pub(crate) limit: u64, -619} -620 -621impl StreamsBlocked { -622 const fn get_type(&self) -> FrameType { -623 match self.dir { -624 Dir::Bi => FrameType::StreamsBlockedBidi, -625 Dir::Uni => FrameType::StreamsBlockedUni, -626 } -627 } -628} -629 -630impl Encodable for StreamsBlocked { -631 fn encode<B: BufMut>(&self, buf: &mut B) { -632 buf.write(self.get_type()); -633 buf.write_var(self.limit); -634 } -635} -636 -637#[derive(Debug, Clone, Copy, derive_more::Display)] -638#[cfg_attr(test, derive(Arbitrary, PartialEq, Eq))] -639#[display("MAX_DATA({_0})")] -640pub(crate) struct MaxData(pub(crate) VarInt); +589#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, derive_more::Display)] +590#[cfg_attr(test, derive(Arbitrary))] +591#[display("PATH_RESPONSE({_0:08x})")] +592pub(crate) struct PathResponse(pub(crate) u64); +593 +594impl PathResponse { +595 pub(crate) const SIZE_BOUND: usize = 9; +596 +597 const fn get_type(&self) -> FrameType { +598 FrameType::PathResponse +599 } +600} +601 +602impl Decodable for PathResponse { +603 fn decode<B: Buf>(buf: &mut B) -> coding::Result<Self> { +604 Ok(Self(buf.get()?)) +605 } +606} +607 +608impl Encodable for PathResponse { +609 fn encode<B: BufMut>(&self, buf: &mut B) { +610 buf.write(FrameType::PathResponse); +611 buf.write(self.0); +612 } +613} +614 +615#[derive(Debug, Clone, Copy, derive_more::Display)] +616#[cfg_attr(test, derive(Arbitrary, PartialEq, Eq))] +617#[display("DATA_BLOCKED offset: {_0}")] +618pub(crate) struct DataBlocked(#[cfg_attr(test, strategy(varint_u64()))] pub(crate) u64); +619 +620impl Encodable for DataBlocked { +621 fn encode<B: BufMut>(&self, buf: &mut B) { +622 buf.write(FrameType::DataBlocked); +623 buf.write_var(self.0); +624 } +625} +626 +627#[derive(Debug, Clone, Copy, derive_more::Display)] +628#[cfg_attr(test, derive(Arbitrary, PartialEq, Eq))] +629#[display("STREAM_DATA_BLOCKED id: {id} offset: {offset}")] +630pub(crate) struct StreamDataBlocked { +631 pub(crate) id: StreamId, +632 #[cfg_attr(test, strategy(varint_u64()))] +633 pub(crate) offset: u64, +634} +635 +636impl StreamDataBlocked { +637 const fn get_type(&self) -> FrameType { +638 FrameType::StreamDataBlocked +639 } +640} 641 -642impl MaxData { -643 const fn get_type(&self) -> FrameType { -644 FrameType::MaxData -645 } -646} -647 -648impl Decodable for MaxData { -649 fn decode<B: Buf>(buf: &mut B) -> coding::Result<Self> { -650 Ok(Self(buf.get()?)) -651 } -652} -653 -654impl Encodable for MaxData { -655 fn encode<B: BufMut>(&self, buf: &mut B) { -656 buf.write(FrameType::MaxData); -657 buf.write(self.0); -658 } -659} -660 -661#[derive(Debug, Clone, Copy, derive_more::Display)] -662#[cfg_attr(test, derive(Arbitrary, PartialEq, Eq))] -663#[display("MAX_STREAM_DATA id: {id} max: {offset}")] -664pub(crate) struct MaxStreamData { -665 pub(crate) id: StreamId, -666 #[cfg_attr(test, strategy(varint_u64()))] -667 pub(crate) offset: u64, -668} -669 -670impl MaxStreamData { -671 const fn get_type(&self) -> FrameType { -672 FrameType::MaxStreamData -673 } -674} -675 -676impl Decodable for MaxStreamData { -677 fn decode<B: Buf>(buf: &mut B) -> coding::Result<Self> { -678 Ok(Self { -679 id: buf.get()?, -680 offset: buf.get_var()?, -681 }) -682 } -683} -684 -685impl Encodable for MaxStreamData { -686 fn encode<B: BufMut>(&self, buf: &mut B) { -687 buf.write(FrameType::MaxStreamData); -688 buf.write(self.id); -689 buf.write_var(self.offset); -690 } -691} -692 -693#[derive(Debug, Clone, Copy, derive_more::Display)] -694#[cfg_attr(test, derive(Arbitrary, PartialEq, Eq))] -695#[display("{} count: {count}", self.get_type())] -696pub(crate) struct MaxStreams { -697 pub(crate) dir: Dir, -698 #[cfg_attr(test, strategy(varint_u64()))] -699 pub(crate) count: u64, -700} -701 -702impl MaxStreams { -703 const fn get_type(&self) -> FrameType { -704 match self.dir { -705 Dir::Bi => FrameType::MaxStreamsBidi, -706 Dir::Uni => FrameType::MaxStreamsUni, -707 } -708 } -709} -710 -711impl Encodable for MaxStreams { -712 fn encode<B: BufMut>(&self, buf: &mut B) { -713 buf.write(self.get_type()); -714 buf.write_var(self.count); -715 } -716} -717 -718#[derive(Debug, PartialEq, Eq, derive_more::Display)] -719#[cfg_attr(test, derive(Arbitrary))] -720#[display("{} {} seq: {sequence}", self.get_type(), DisplayOption::new("path_id", path_id.as_ref()))] -721pub(crate) struct RetireConnectionId { -722 pub(crate) path_id: Option<PathId>, -723 #[cfg_attr(test, strategy(varint_u64()))] -724 pub(crate) sequence: u64, -725} -726 -727impl RetireConnectionId { -728 /// Maximum size of this frame when the frame type is [`FrameType::RetireConnectionId`] -729 pub(crate) const SIZE_BOUND: usize = { -730 let type_len = FrameType::RetireConnectionId.size(); -731 let seq_max_len = 8usize; -732 type_len + seq_max_len -733 }; -734 -735 /// Maximum size of this frame when the frame type is [`FrameType::PathRetireConnectionId`] -736 pub(crate) const SIZE_BOUND_MULTIPATH: usize = { -737 let type_len = FrameType::PathRetireConnectionId.size(); -738 let path_id_len = VarInt::from_u32(u32::MAX).size(); -739 let seq_max_len = 8usize; -740 type_len + path_id_len + seq_max_len -741 }; -742 -743 /// Decode [`Self`] from the buffer, provided that the frame type has been verified (either -744 /// [`FrameType::PathRetireConnectionId`], or [`FrameType::RetireConnectionId`]) -745 pub(crate) fn decode<R: Buf>(bytes: &mut R, read_path: bool) -> coding::Result<Self> { -746 Ok(Self { -747 path_id: if read_path { Some(bytes.get()?) } else { None }, -748 sequence: bytes.get_var()?, -749 }) -750 } -751 -752 /// Get the [`FrameType`] for this [`RetireConnectionId`] -753 const fn get_type(&self) -> FrameType { -754 if self.path_id.is_some() { -755 FrameType::PathRetireConnectionId -756 } else { -757 FrameType::RetireConnectionId -758 } -759 } -760 -761 /// Returns the maximum encoded size on the wire -762 /// -763 /// `path_retire_cid` determines whether this frame is a multipath frame. This is a rough upper -764 /// estimate, does not squeeze every last byte out. -765 pub(crate) const fn size_bound(path_retire_cid: bool) -> usize { -766 match path_retire_cid { -767 true => Self::SIZE_BOUND_MULTIPATH, -768 false => Self::SIZE_BOUND, -769 } -770 } -771} +642impl Encodable for StreamDataBlocked { +643 fn encode<B: BufMut>(&self, buf: &mut B) { +644 buf.write(FrameType::StreamDataBlocked); +645 buf.write(self.id); +646 buf.write_var(self.offset); +647 } +648} +649 +650#[derive(Debug, Clone, Copy, derive_more::Display)] +651#[cfg_attr(test, derive(Arbitrary, PartialEq, Eq))] +652#[display("STREAMS_BLOCKED dir: {:?} limit: {limit}", dir)] +653pub(crate) struct StreamsBlocked { +654 pub(crate) dir: Dir, +655 #[cfg_attr(test, strategy(varint_u64()))] +656 pub(crate) limit: u64, +657} +658 +659impl StreamsBlocked { +660 const fn get_type(&self) -> FrameType { +661 match self.dir { +662 Dir::Bi => FrameType::StreamsBlockedBidi, +663 Dir::Uni => FrameType::StreamsBlockedUni, +664 } +665 } +666} +667 +668impl Encodable for StreamsBlocked { +669 fn encode<B: BufMut>(&self, buf: &mut B) { +670 buf.write(self.get_type()); +671 buf.write_var(self.limit); +672 } +673} +674 +675#[derive(Debug, Clone, Copy, derive_more::Display)] +676#[cfg_attr(test, derive(Arbitrary, PartialEq, Eq))] +677#[display("MAX_DATA({_0})")] +678pub(crate) struct MaxData(pub(crate) VarInt); +679 +680impl MaxData { +681 const fn get_type(&self) -> FrameType { +682 FrameType::MaxData +683 } +684} +685 +686impl Decodable for MaxData { +687 fn decode<B: Buf>(buf: &mut B) -> coding::Result<Self> { +688 Ok(Self(buf.get()?)) +689 } +690} +691 +692impl Encodable for MaxData { +693 fn encode<B: BufMut>(&self, buf: &mut B) { +694 buf.write(FrameType::MaxData); +695 buf.write(self.0); +696 } +697} +698 +699#[derive(Debug, Clone, Copy, derive_more::Display)] +700#[cfg_attr(test, derive(Arbitrary, PartialEq, Eq))] +701#[display("MAX_STREAM_DATA id: {id} max: {offset}")] +702pub(crate) struct MaxStreamData { +703 pub(crate) id: StreamId, +704 #[cfg_attr(test, strategy(varint_u64()))] +705 pub(crate) offset: u64, +706} +707 +708impl MaxStreamData { +709 const fn get_type(&self) -> FrameType { +710 FrameType::MaxStreamData +711 } +712} +713 +714impl Decodable for MaxStreamData { +715 fn decode<B: Buf>(buf: &mut B) -> coding::Result<Self> { +716 Ok(Self { +717 id: buf.get()?, +718 offset: buf.get_var()?, +719 }) +720 } +721} +722 +723impl Encodable for MaxStreamData { +724 fn encode<B: BufMut>(&self, buf: &mut B) { +725 buf.write(FrameType::MaxStreamData); +726 buf.write(self.id); +727 buf.write_var(self.offset); +728 } +729} +730 +731#[derive(Debug, Clone, Copy, derive_more::Display)] +732#[cfg_attr(test, derive(Arbitrary, PartialEq, Eq))] +733#[display("{} count: {count}", self.get_type())] +734pub(crate) struct MaxStreams { +735 pub(crate) dir: Dir, +736 #[cfg_attr(test, strategy(varint_u64()))] +737 pub(crate) count: u64, +738} +739 +740impl MaxStreams { +741 const fn get_type(&self) -> FrameType { +742 match self.dir { +743 Dir::Bi => FrameType::MaxStreamsBidi, +744 Dir::Uni => FrameType::MaxStreamsUni, +745 } +746 } +747} +748 +749impl Encodable for MaxStreams { +750 fn encode<B: BufMut>(&self, buf: &mut B) { +751 buf.write(self.get_type()); +752 buf.write_var(self.count); +753 } +754} +755 +756#[derive(Debug, PartialEq, Eq, derive_more::Display)] +757#[cfg_attr(test, derive(Arbitrary))] +758#[display("{} {} seq: {sequence}", self.get_type(), DisplayOption::new("path_id", path_id.as_ref()))] +759pub(crate) struct RetireConnectionId { +760 pub(crate) path_id: Option<PathId>, +761 #[cfg_attr(test, strategy(varint_u64()))] +762 pub(crate) sequence: u64, +763} +764 +765impl RetireConnectionId { +766 /// Maximum size of this frame when the frame type is [`FrameType::RetireConnectionId`] +767 pub(crate) const SIZE_BOUND: usize = { +768 let type_len = FrameType::RetireConnectionId.size(); +769 let seq_max_len = 8usize; +770 type_len + seq_max_len +771 }; 772 -773impl Encodable for RetireConnectionId { -774 fn encode<W: BufMut>(&self, buf: &mut W) { -775 buf.write(self.get_type()); -776 if let Some(id) = self.path_id { -777 buf.write(id); -778 } -779 buf.write_var(self.sequence); -780 } -781} -782 -783#[cfg_attr(test, derive(Arbitrary))] -784#[derive(Clone, Debug, derive_more::Display)] -785#[display("CONNECTION_CLOSE error_code={} reason='{}'", self.error_code(), self.reason())] -786pub(crate) enum Close { -787 Connection(ConnectionClose), -788 Application(ApplicationClose), -789} -790 -791impl Close { -792 pub(crate) fn encoder(&self, max_len: usize) -> CloseEncoder<'_> { -793 CloseEncoder { -794 close: self, -795 max_len, +773 /// Maximum size of this frame when the frame type is [`FrameType::PathRetireConnectionId`] +774 pub(crate) const SIZE_BOUND_MULTIPATH: usize = { +775 let type_len = FrameType::PathRetireConnectionId.size(); +776 let path_id_len = VarInt::from_u32(u32::MAX).size(); +777 let seq_max_len = 8usize; +778 type_len + path_id_len + seq_max_len +779 }; +780 +781 /// Decode [`Self`] from the buffer, provided that the frame type has been verified (either +782 /// [`FrameType::PathRetireConnectionId`], or [`FrameType::RetireConnectionId`]) +783 pub(crate) fn decode<R: Buf>(bytes: &mut R, read_path: bool) -> coding::Result<Self> { +784 Ok(Self { +785 path_id: if read_path { Some(bytes.get()?) } else { None }, +786 sequence: bytes.get_var()?, +787 }) +788 } +789 +790 /// Get the [`FrameType`] for this [`RetireConnectionId`] +791 const fn get_type(&self) -> FrameType { +792 if self.path_id.is_some() { +793 FrameType::PathRetireConnectionId +794 } else { +795 FrameType::RetireConnectionId 796 } 797 } 798 -799 pub(crate) fn is_transport_layer(&self) -> bool { -800 matches!(*self, Self::Connection(_)) -801 } -802 -803 fn error_code(&self) -> u64 { -804 match self { -805 Self::Connection(ConnectionClose { error_code, .. }) => u64::from(*error_code), -806 Self::Application(ApplicationClose { error_code, .. }) => u64::from(*error_code), +799 /// Returns the maximum encoded size on the wire +800 /// +801 /// `path_retire_cid` determines whether this frame is a multipath frame. This is a rough upper +802 /// estimate, does not squeeze every last byte out. +803 pub(crate) const fn size_bound(path_retire_cid: bool) -> usize { +804 match path_retire_cid { +805 true => Self::SIZE_BOUND_MULTIPATH, +806 false => Self::SIZE_BOUND, 807 } 808 } -809 -810 fn reason(&self) -> Cow<'_, str> { -811 match self { -812 Self::Connection(ConnectionClose { reason, .. }) => String::from_utf8_lossy(reason), -813 Self::Application(ApplicationClose { reason, .. }) => String::from_utf8_lossy(reason), -814 } -815 } -816} -817 -818#[derive(derive_more::Display)] -819#[display("{close}")] -820pub(crate) struct CloseEncoder<'a> { -821 pub(crate) close: &'a Close, -822 max_len: usize, -823} -824 -825impl<'a> CloseEncoder<'a> { -826 const fn get_type(&self) -> FrameType { -827 match self.close { -828 Close::Connection(_) => FrameType::ConnectionClose, -829 Close::Application(_) => FrameType::ApplicationClose, -830 } -831 } -832} -833 -834impl<'a> Encodable for CloseEncoder<'a> { -835 fn encode<W: BufMut>(&self, out: &mut W) { -836 match self.close { -837 Close::Connection(x) => x.encode(out, self.max_len), -838 Close::Application(x) => x.encode(out, self.max_len), -839 } -840 } -841} -842 -843impl From<TransportError> for Close { -844 fn from(x: TransportError) -> Self { -845 Self::Connection(x.into()) +809} +810 +811impl Encodable for RetireConnectionId { +812 fn encode<W: BufMut>(&self, buf: &mut W) { +813 buf.write(self.get_type()); +814 if let Some(id) = self.path_id { +815 buf.write(id); +816 } +817 buf.write_var(self.sequence); +818 } +819} +820 +821#[cfg_attr(test, derive(Arbitrary))] +822#[derive(Clone, Debug, derive_more::Display)] +823#[display("CONNECTION_CLOSE error_code={} reason='{}'", self.error_code(), self.reason())] +824pub(crate) enum Close { +825 Connection(ConnectionClose), +826 Application(ApplicationClose), +827} +828 +829impl Close { +830 pub(crate) fn encoder(&self, max_len: usize) -> CloseEncoder<'_> { +831 CloseEncoder { +832 close: self, +833 max_len, +834 } +835 } +836 +837 pub(crate) fn is_transport_layer(&self) -> bool { +838 matches!(*self, Self::Connection(_)) +839 } +840 +841 fn error_code(&self) -> u64 { +842 match self { +843 Self::Connection(ConnectionClose { error_code, .. }) => u64::from(*error_code), +844 Self::Application(ApplicationClose { error_code, .. }) => u64::from(*error_code), +845 } 846 } -847} -848impl From<ConnectionClose> for Close { -849 fn from(x: ConnectionClose) -> Self { -850 Self::Connection(x) -851 } -852} -853impl From<ApplicationClose> for Close { -854 fn from(x: ApplicationClose) -> Self { -855 Self::Application(x) -856 } -857} -858 -859/// Reason given by the transport for closing the connection -860#[derive(Debug, Clone, PartialEq, Eq)] -861#[cfg_attr(test, derive(Arbitrary))] -862pub struct ConnectionClose { -863 /// Class of error as encoded in the specification -864 pub error_code: TransportErrorCode, -865 /// Type of frame that caused the close -866 pub frame_type: MaybeFrame, -867 /// Human-readable reason for the close -868 #[cfg_attr(test, strategy(proptest::collection::vec(any::<u8>(), 0..64).prop_map(Bytes::from)))] -869 pub reason: Bytes, +847 +848 fn reason(&self) -> Cow<'_, str> { +849 match self { +850 Self::Connection(ConnectionClose { reason, .. }) => String::from_utf8_lossy(reason), +851 Self::Application(ApplicationClose { reason, .. }) => String::from_utf8_lossy(reason), +852 } +853 } +854} +855 +856#[derive(derive_more::Display)] +857#[display("{close}")] +858pub(crate) struct CloseEncoder<'a> { +859 pub(crate) close: &'a Close, +860 max_len: usize, +861} +862 +863impl<'a> CloseEncoder<'a> { +864 const fn get_type(&self) -> FrameType { +865 match self.close { +866 Close::Connection(_) => FrameType::ConnectionClose, +867 Close::Application(_) => FrameType::ApplicationClose, +868 } +869 } 870} 871 -872impl fmt::Display for ConnectionClose { -873 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -874 self.error_code.fmt(f)?; -875 if !self.reason.as_ref().is_empty() { -876 f.write_str(": ")?; -877 f.write_str(&String::from_utf8_lossy(&self.reason))?; -878 } -879 Ok(()) -880 } -881} -882 -883impl From<TransportError> for ConnectionClose { -884 fn from(x: TransportError) -> Self { -885 Self { -886 error_code: x.code, -887 frame_type: x.frame, -888 reason: x.reason.into(), -889 } -890 } -891} -892 -893impl FrameStruct for ConnectionClose { -894 const SIZE_BOUND: usize = 1 + 8 + 8 + 8; +872impl<'a> Encodable for CloseEncoder<'a> { +873 fn encode<W: BufMut>(&self, out: &mut W) { +874 match self.close { +875 Close::Connection(x) => x.encode(out, self.max_len), +876 Close::Application(x) => x.encode(out, self.max_len), +877 } +878 } +879} +880 +881impl From<TransportError> for Close { +882 fn from(x: TransportError) -> Self { +883 Self::Connection(x.into()) +884 } +885} +886impl From<ConnectionClose> for Close { +887 fn from(x: ConnectionClose) -> Self { +888 Self::Connection(x) +889 } +890} +891impl From<ApplicationClose> for Close { +892 fn from(x: ApplicationClose) -> Self { +893 Self::Application(x) +894 } 895} 896 -897impl ConnectionClose { -898 pub(crate) fn encode<W: BufMut>(&self, out: &mut W, max_len: usize) { -899 out.write(FrameType::ConnectionClose); // 1 byte -900 out.write(self.error_code); // <= 8 bytes -901 out.write(self.frame_type); // <= 8 bytes -902 let max_len = max_len -903 - 3 -904 - self.frame_type.size() -905 - VarInt::from_u64(self.reason.len() as u64).unwrap().size(); -906 let actual_len = self.reason.len().min(max_len); -907 out.write_var(actual_len as u64); // <= 8 bytes -908 out.put_slice(&self.reason[0..actual_len]); // whatever's left -909 } -910} -911 -912/// Reason given by an application for closing the connection -913#[derive(Debug, Clone, PartialEq, Eq)] -914#[cfg_attr(test, derive(Arbitrary))] -915pub struct ApplicationClose { -916 /// Application-specific reason code -917 pub error_code: VarInt, -918 /// Human-readable reason for the close -919 #[cfg_attr(test, strategy(proptest::collection::vec(any::<u8>(), 0..64).prop_map(Bytes::from)))] -920 pub reason: Bytes, -921} -922 -923impl fmt::Display for ApplicationClose { -924 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -925 if !self.reason.as_ref().is_empty() { -926 f.write_str(&String::from_utf8_lossy(&self.reason))?; -927 f.write_str(" (code ")?; -928 self.error_code.fmt(f)?; -929 f.write_str(")")?; -930 } else { -931 self.error_code.fmt(f)?; -932 } -933 Ok(()) -934 } -935} -936 -937impl FrameStruct for ApplicationClose { -938 const SIZE_BOUND: usize = 1 + 8 + 8; -939} -940 -941impl ApplicationClose { -942 pub(crate) fn encode<W: BufMut>(&self, out: &mut W, max_len: usize) { -943 out.write(FrameType::ApplicationClose); // 1 byte -944 out.write(self.error_code); // <= 8 bytes -945 let max_len = max_len - 3 - VarInt::from_u64(self.reason.len() as u64).unwrap().size(); -946 let actual_len = self.reason.len().min(max_len); -947 out.write_var(actual_len as u64); // <= 8 bytes -948 out.put_slice(&self.reason[0..actual_len]); // whatever's left -949 } -950} -951 -952#[derive(Debug, Clone, Eq, PartialEq, derive_more::Display)] -953#[display("{} path_id: {path_id} ranges: {ranges:?} delay: {delay}µs", self.get_type())] -954pub(crate) struct PathAck { -955 pub path_id: PathId, -956 pub largest: u64, -957 pub delay: u64, -958 pub ranges: ArrayRangeSet, -959 pub ecn: Option<EcnCounts>, -960} -961 -962#[cfg(test)] -963impl proptest::arbitrary::Arbitrary for PathAck { -964 type Parameters = (); -965 type Strategy = proptest::strategy::BoxedStrategy<Self>; -966 -967 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { -968 use proptest::prelude::*; -969 ( -970 any::<PathId>(), -971 varint_u64(), -972 any::<ArrayRangeSet>() -973 .prop_filter("ranges must be non empty", |ranges| !ranges.is_empty()), -974 any::<Option<EcnCounts>>(), -975 ) -976 .prop_map(|(path_id, delay, ranges, ecn)| Self { -977 path_id, -978 largest: ranges.max().expect("ranges must be non empty"), -979 delay, -980 ranges, -981 ecn, -982 }) -983 .boxed() -984 } -985} -986 -987impl PathAck { -988 pub(crate) fn into_ack(self) -> (Ack, PathId) { -989 let ack = Ack { -990 largest: self.largest, -991 delay: self.delay, -992 ranges: self.ranges, -993 ecn: self.ecn, -994 }; -995 -996 (ack, self.path_id) -997 } -998 -999 fn get_type(&self) -> FrameType { -1000 if self.ecn.is_some() { -1001 FrameType::PathAckEcn -1002 } else { -1003 FrameType::PathAck -1004 } -1005 } -1006 -1007 pub(crate) fn encoder<'a>( -1008 path_id: PathId, -1009 delay: u64, -1010 ranges: &'a ArrayRangeSet, -1011 ecn: Option<&'a EcnCounts>, -1012 ) -> PathAckEncoder<'a> { -1013 PathAckEncoder { -1014 path_id, -1015 delay, -1016 ranges, -1017 ecn, -1018 } -1019 } -1020} -1021 -1022#[derive(derive_more::Display)] -1023#[display("{} path_id: {path_id} ranges: {ranges:?} delay: {delay}µs", self.get_type())] -1024pub(crate) struct PathAckEncoder<'a> { -1025 pub(super) path_id: PathId, -1026 pub(super) delay: u64, -1027 pub(super) ranges: &'a ArrayRangeSet, -1028 pub(super) ecn: Option<&'a EcnCounts>, -1029} -1030 -1031impl<'a> PathAckEncoder<'a> { -1032 const fn get_type(&self) -> FrameType { -1033 match self.ecn.is_some() { -1034 true => FrameType::PathAckEcn, -1035 false => FrameType::PathAck, -1036 } -1037 } -1038} -1039 -1040impl<'a> Encodable for PathAckEncoder<'a> { -1041 /// Encode [`Self`] into the given buffer -1042 /// -1043 /// The [`FrameType`] will be either [`FrameType::PathAckEcn`] or [`FrameType::PathAck`] -1044 /// depending on whether [`EcnCounts`] are provided. -1045 /// -1046 /// PANICS: if `ranges` is empty. -1047 fn encode<W: BufMut>(&self, buf: &mut W) { -1048 let PathAckEncoder { -1049 path_id, -1050 delay, -1051 ranges, -1052 ecn, -1053 } = self; -1054 let mut rest = ranges.iter().rev(); -1055 let first = rest -1056 .next() -1057 .expect("Caller has verified ranges is non empty"); -1058 let largest = first.end - 1; -1059 let first_size = first.end - first.start; -1060 let kind = match ecn.is_some() { -1061 true => FrameType::PathAckEcn, -1062 false => FrameType::PathAck, -1063 }; -1064 buf.write(kind); -1065 buf.write(*path_id); -1066 buf.write_var(largest); -1067 buf.write_var(*delay); -1068 buf.write_var(ranges.len() as u64 - 1); -1069 buf.write_var(first_size - 1); -1070 let mut prev = first.start; -1071 for block in rest { -1072 let size = block.end - block.start; -1073 buf.write_var(prev - block.end - 1); -1074 buf.write_var(size - 1); -1075 prev = block.start; -1076 } -1077 if let Some(x) = ecn { -1078 x.encode(buf) -1079 } -1080 } -1081} -1082 -1083#[derive(Debug, Clone, Eq, PartialEq, derive_more::Display)] -1084#[display("{} ranges: {ranges:?} delay: {delay}µs largest: {largest}", self.get_type())] -1085pub(crate) struct Ack { -1086 pub largest: u64, -1087 pub delay: u64, -1088 pub ranges: ArrayRangeSet, -1089 pub ecn: Option<EcnCounts>, -1090} -1091 -1092#[cfg(test)] -1093impl proptest::arbitrary::Arbitrary for Ack { -1094 type Parameters = (); -1095 type Strategy = proptest::strategy::BoxedStrategy<Self>; -1096 -1097 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { -1098 use proptest::prelude::*; -1099 ( -1100 varint_u64(), -1101 any::<ArrayRangeSet>() -1102 .prop_filter("ranges must be non empty", |ranges| !ranges.is_empty()), -1103 any::<Option<EcnCounts>>(), -1104 ) -1105 .prop_map(|(delay, ranges, ecn)| Self { -1106 largest: ranges.max().expect("ranges must be non empty"), -1107 delay, -1108 ranges, -1109 ecn, -1110 }) -1111 .boxed() -1112 } -1113} -1114 -1115impl Ack { -1116 pub(crate) fn encoder<'a>( -1117 delay: u64, -1118 ranges: &'a ArrayRangeSet, -1119 ecn: Option<&'a EcnCounts>, -1120 ) -> AckEncoder<'a> { -1121 AckEncoder { delay, ranges, ecn } -1122 } -1123 -1124 pub(crate) fn iter(&self) -> impl DoubleEndedIterator<Item = Range<u64>> + '_ { -1125 self.ranges.iter() -1126 } -1127 -1128 pub(crate) const fn get_type(&self) -> FrameType { -1129 if self.ecn.is_some() { -1130 FrameType::AckEcn -1131 } else { -1132 FrameType::Ack -1133 } -1134 } -1135} -1136 -1137#[derive(derive_more::Display)] -1138#[display("{} ranges: {ranges:?} delay: {delay}µs", self.get_type())] -1139pub(crate) struct AckEncoder<'a> { -1140 pub(crate) delay: u64, -1141 pub(crate) ranges: &'a ArrayRangeSet, -1142 pub(crate) ecn: Option<&'a EcnCounts>, -1143} -1144 -1145impl<'a> AckEncoder<'a> { -1146 const fn get_type(&self) -> FrameType { -1147 match self.ecn.is_some() { -1148 true => FrameType::AckEcn, -1149 false => FrameType::Ack, -1150 } -1151 } -1152} -1153 -1154impl<'a> Encodable for AckEncoder<'a> { -1155 fn encode<W: BufMut>(&self, buf: &mut W) { -1156 let AckEncoder { delay, ranges, ecn } = self; -1157 let mut rest = ranges.iter().rev(); -1158 let first = rest.next().unwrap(); -1159 let largest = first.end - 1; -1160 let first_size = first.end - first.start; -1161 let kind = match ecn.is_some() { -1162 true => FrameType::AckEcn, -1163 false => FrameType::Ack, -1164 }; -1165 buf.write(kind); -1166 buf.write_var(largest); -1167 buf.write_var(*delay); -1168 buf.write_var(ranges.len() as u64 - 1); -1169 buf.write_var(first_size - 1); -1170 let mut prev = first.start; -1171 for block in rest { -1172 let size = block.end - block.start; -1173 buf.write_var(prev - block.end - 1); -1174 buf.write_var(size - 1); -1175 prev = block.start; -1176 } -1177 if let Some(x) = ecn { -1178 x.encode(buf) -1179 } -1180 } -1181} -1182 -1183#[cfg_attr(test, derive(Arbitrary))] -1184#[derive(Debug, Copy, Clone, Eq, PartialEq)] -1185pub(crate) struct EcnCounts { -1186 #[cfg_attr(test, strategy(varint_u64()))] -1187 pub ect0: u64, -1188 #[cfg_attr(test, strategy(varint_u64()))] -1189 pub ect1: u64, -1190 #[cfg_attr(test, strategy(varint_u64()))] -1191 pub ce: u64, -1192} -1193 -1194impl std::ops::AddAssign<EcnCodepoint> for EcnCounts { -1195 fn add_assign(&mut self, rhs: EcnCodepoint) { -1196 match rhs { -1197 EcnCodepoint::Ect0 => { -1198 self.ect0 += 1; -1199 } -1200 EcnCodepoint::Ect1 => { -1201 self.ect1 += 1; -1202 } -1203 EcnCodepoint::Ce => { -1204 self.ce += 1; -1205 } -1206 } -1207 } -1208} -1209 -1210impl EcnCounts { -1211 pub(crate) const ZERO: Self = Self { -1212 ect0: 0, -1213 ect1: 0, -1214 ce: 0, -1215 }; -1216} -1217 -1218impl Encodable for EcnCounts { -1219 fn encode<W: BufMut>(&self, out: &mut W) { -1220 out.write_var(self.ect0); -1221 out.write_var(self.ect1); -1222 out.write_var(self.ce); -1223 } -1224} -1225 -1226#[derive(Debug, Clone, derive_more::Display)] -1227#[cfg_attr(test, derive(Arbitrary, PartialEq, Eq))] -1228#[display("STREAM id: {id} off: {offset} len: {} fin: {fin}", data.len())] -1229pub(crate) struct Stream { -1230 pub(crate) id: StreamId, -1231 #[cfg_attr(test, strategy(varint_u64()))] -1232 pub(crate) offset: u64, -1233 pub(crate) fin: bool, -1234 #[cfg_attr(test, strategy(Strategy::prop_map(collection::vec(any::<u8>(), 0..100), Bytes::from)))] -1235 pub(crate) data: Bytes, -1236} -1237 -1238impl FrameStruct for Stream { -1239 const SIZE_BOUND: usize = 1 + 8 + 8 + 8; -1240} -1241 -1242/// Metadata from a stream frame -1243#[derive(Debug, Clone, derive_more::Display)] -1244#[display("STREAM id: {id} off: {} len: {} fin: {fin}", offsets.start, offsets.end - offsets.start)] -1245pub(crate) struct StreamMeta { -1246 pub(crate) id: StreamId, -1247 pub(crate) offsets: Range<u64>, -1248 pub(crate) fin: bool, -1249} -1250 -1251// This manual implementation exists because `Default` is not implemented for `StreamId` -1252impl Default for StreamMeta { -1253 fn default() -> Self { -1254 Self { -1255 id: StreamId(0), -1256 offsets: 0..0, -1257 fin: false, -1258 } +897/// Reason given by the transport for closing the connection +898#[derive(Debug, Clone, PartialEq, Eq)] +899#[cfg_attr(test, derive(Arbitrary))] +900pub struct ConnectionClose { +901 /// Class of error as encoded in the specification +902 pub error_code: TransportErrorCode, +903 /// Type of frame that caused the close +904 pub frame_type: MaybeFrame, +905 /// Human-readable reason for the close +906 #[cfg_attr(test, strategy(proptest::collection::vec(any::<u8>(), 0..64).prop_map(Bytes::from)))] +907 pub reason: Bytes, +908} +909 +910impl fmt::Display for ConnectionClose { +911 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +912 self.error_code.fmt(f)?; +913 if !self.reason.as_ref().is_empty() { +914 f.write_str(": ")?; +915 f.write_str(&String::from_utf8_lossy(&self.reason))?; +916 } +917 Ok(()) +918 } +919} +920 +921impl From<TransportError> for ConnectionClose { +922 fn from(x: TransportError) -> Self { +923 Self { +924 error_code: x.code, +925 frame_type: x.frame, +926 reason: x.reason.into(), +927 } +928 } +929} +930 +931impl FrameStruct for ConnectionClose { +932 const SIZE_BOUND: usize = 1 + 8 + 8 + 8; +933} +934 +935impl ConnectionClose { +936 pub(crate) fn encode<W: BufMut>(&self, out: &mut W, max_len: usize) { +937 out.write(FrameType::ConnectionClose); // 1 byte +938 out.write(self.error_code); // <= 8 bytes +939 out.write(self.frame_type); // <= 8 bytes +940 let max_len = max_len +941 - 3 +942 - self.frame_type.size() +943 - VarInt::from_u64(self.reason.len() as u64).unwrap().size(); +944 let actual_len = self.reason.len().min(max_len); +945 out.write_var(actual_len as u64); // <= 8 bytes +946 out.put_slice(&self.reason[0..actual_len]); // whatever's left +947 } +948} +949 +950/// Reason given by an application for closing the connection +951#[derive(Debug, Clone, PartialEq, Eq)] +952#[cfg_attr(test, derive(Arbitrary))] +953pub struct ApplicationClose { +954 /// Application-specific reason code +955 pub error_code: VarInt, +956 /// Human-readable reason for the close +957 #[cfg_attr(test, strategy(proptest::collection::vec(any::<u8>(), 0..64).prop_map(Bytes::from)))] +958 pub reason: Bytes, +959} +960 +961impl fmt::Display for ApplicationClose { +962 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +963 if !self.reason.as_ref().is_empty() { +964 f.write_str(&String::from_utf8_lossy(&self.reason))?; +965 f.write_str(" (code ")?; +966 self.error_code.fmt(f)?; +967 f.write_str(")")?; +968 } else { +969 self.error_code.fmt(f)?; +970 } +971 Ok(()) +972 } +973} +974 +975impl FrameStruct for ApplicationClose { +976 const SIZE_BOUND: usize = 1 + 8 + 8; +977} +978 +979impl ApplicationClose { +980 pub(crate) fn encode<W: BufMut>(&self, out: &mut W, max_len: usize) { +981 out.write(FrameType::ApplicationClose); // 1 byte +982 out.write(self.error_code); // <= 8 bytes +983 let max_len = max_len - 3 - VarInt::from_u64(self.reason.len() as u64).unwrap().size(); +984 let actual_len = self.reason.len().min(max_len); +985 out.write_var(actual_len as u64); // <= 8 bytes +986 out.put_slice(&self.reason[0..actual_len]); // whatever's left +987 } +988} +989 +990#[derive(Debug, Clone, Eq, PartialEq, derive_more::Display)] +991#[display("{} path_id: {path_id} ranges: {ranges:?} delay: {delay}µs", self.get_type())] +992pub(crate) struct PathAck { +993 pub path_id: PathId, +994 pub largest: u64, +995 pub delay: u64, +996 pub ranges: ArrayRangeSet, +997 pub ecn: Option<EcnCounts>, +998} +999 +1000#[cfg(test)] +1001impl proptest::arbitrary::Arbitrary for PathAck { +1002 type Parameters = (); +1003 type Strategy = proptest::strategy::BoxedStrategy<Self>; +1004 +1005 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { +1006 use proptest::prelude::*; +1007 ( +1008 any::<PathId>(), +1009 varint_u64(), +1010 any::<ArrayRangeSet>(), +1011 any::<Option<EcnCounts>>(), +1012 ) +1013 .prop_map(|(path_id, delay, ranges, ecn)| Self { +1014 path_id, +1015 largest: ranges.max().expect("ranges must be non empty"), +1016 delay, +1017 ranges, +1018 ecn, +1019 }) +1020 .boxed() +1021 } +1022} +1023 +1024impl PathAck { +1025 pub(crate) fn into_ack(self) -> (Ack, PathId) { +1026 let ack = Ack { +1027 largest: self.largest, +1028 delay: self.delay, +1029 ranges: self.ranges, +1030 ecn: self.ecn, +1031 }; +1032 +1033 (ack, self.path_id) +1034 } +1035 +1036 fn get_type(&self) -> FrameType { +1037 if self.ecn.is_some() { +1038 FrameType::PathAckEcn +1039 } else { +1040 FrameType::PathAck +1041 } +1042 } +1043 +1044 pub(crate) fn encoder<'a>( +1045 path_id: PathId, +1046 delay: u64, +1047 ranges: &'a ArrayRangeSet, +1048 ecn: Option<&'a EcnCounts>, +1049 ) -> PathAckEncoder<'a> { +1050 PathAckEncoder { +1051 path_id, +1052 delay, +1053 ranges, +1054 ecn, +1055 } +1056 } +1057} +1058 +1059#[derive(derive_more::Display)] +1060#[display("{} path_id: {path_id} ranges: {ranges:?} delay: {delay}µs", self.get_type())] +1061pub(crate) struct PathAckEncoder<'a> { +1062 pub(super) path_id: PathId, +1063 pub(super) delay: u64, +1064 pub(super) ranges: &'a ArrayRangeSet, +1065 pub(super) ecn: Option<&'a EcnCounts>, +1066} +1067 +1068impl<'a> PathAckEncoder<'a> { +1069 const fn get_type(&self) -> FrameType { +1070 match self.ecn.is_some() { +1071 true => FrameType::PathAckEcn, +1072 false => FrameType::PathAck, +1073 } +1074 } +1075} +1076 +1077impl<'a> Encodable for PathAckEncoder<'a> { +1078 /// Encode [`Self`] into the given buffer +1079 /// +1080 /// The [`FrameType`] will be either [`FrameType::PathAckEcn`] or [`FrameType::PathAck`] +1081 /// depending on whether [`EcnCounts`] are provided. +1082 /// +1083 /// PANICS: if `ranges` is empty. +1084 fn encode<W: BufMut>(&self, buf: &mut W) { +1085 let PathAckEncoder { +1086 path_id, +1087 delay, +1088 ranges, +1089 ecn, +1090 } = self; +1091 let mut rest = ranges.iter().rev(); +1092 let first = rest +1093 .next() +1094 .expect("Caller has verified ranges is non empty"); +1095 let largest = first.end - 1; +1096 let first_size = first.end - first.start; +1097 let kind = match ecn.is_some() { +1098 true => FrameType::PathAckEcn, +1099 false => FrameType::PathAck, +1100 }; +1101 buf.write(kind); +1102 buf.write(*path_id); +1103 buf.write_var(largest); +1104 buf.write_var(*delay); +1105 buf.write_var(ranges.len() as u64 - 1); +1106 buf.write_var(first_size - 1); +1107 let mut prev = first.start; +1108 for block in rest { +1109 let size = block.end - block.start; +1110 buf.write_var(prev - block.end - 1); +1111 buf.write_var(size - 1); +1112 prev = block.start; +1113 } +1114 if let Some(x) = ecn { +1115 x.encode(buf) +1116 } +1117 } +1118} +1119 +1120#[derive(Debug, Clone, Eq, PartialEq, derive_more::Display)] +1121#[display("{} ranges: {ranges:?} delay: {delay}µs largest: {largest}", self.get_type())] +1122pub(crate) struct Ack { +1123 pub largest: u64, +1124 pub delay: u64, +1125 pub ranges: ArrayRangeSet, +1126 pub ecn: Option<EcnCounts>, +1127} +1128 +1129#[cfg(test)] +1130impl proptest::arbitrary::Arbitrary for Ack { +1131 type Parameters = (); +1132 type Strategy = proptest::strategy::BoxedStrategy<Self>; +1133 +1134 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { +1135 use proptest::prelude::*; +1136 ( +1137 varint_u64(), +1138 any::<ArrayRangeSet>(), +1139 any::<Option<EcnCounts>>(), +1140 ) +1141 .prop_map(|(delay, ranges, ecn)| Self { +1142 largest: ranges.max().expect("ranges must be non empty"), +1143 delay, +1144 ranges, +1145 ecn, +1146 }) +1147 .boxed() +1148 } +1149} +1150 +1151impl Ack { +1152 pub(crate) fn encoder<'a>( +1153 delay: u64, +1154 ranges: &'a ArrayRangeSet, +1155 ecn: Option<&'a EcnCounts>, +1156 ) -> AckEncoder<'a> { +1157 AckEncoder { delay, ranges, ecn } +1158 } +1159 +1160 pub(crate) fn iter(&self) -> impl DoubleEndedIterator<Item = Range<u64>> + '_ { +1161 self.ranges.iter() +1162 } +1163 +1164 pub(crate) const fn get_type(&self) -> FrameType { +1165 if self.ecn.is_some() { +1166 FrameType::AckEcn +1167 } else { +1168 FrameType::Ack +1169 } +1170 } +1171} +1172 +1173#[derive(derive_more::Display)] +1174#[display("{} ranges: {ranges:?} delay: {delay}µs", self.get_type())] +1175pub(crate) struct AckEncoder<'a> { +1176 pub(crate) delay: u64, +1177 pub(crate) ranges: &'a ArrayRangeSet, +1178 pub(crate) ecn: Option<&'a EcnCounts>, +1179} +1180 +1181impl<'a> AckEncoder<'a> { +1182 const fn get_type(&self) -> FrameType { +1183 match self.ecn.is_some() { +1184 true => FrameType::AckEcn, +1185 false => FrameType::Ack, +1186 } +1187 } +1188} +1189 +1190impl<'a> Encodable for AckEncoder<'a> { +1191 fn encode<W: BufMut>(&self, buf: &mut W) { +1192 let AckEncoder { delay, ranges, ecn } = self; +1193 let mut rest = ranges.iter().rev(); +1194 let first = rest.next().unwrap(); +1195 let largest = first.end - 1; +1196 let first_size = first.end - first.start; +1197 let kind = match ecn.is_some() { +1198 true => FrameType::AckEcn, +1199 false => FrameType::Ack, +1200 }; +1201 buf.write(kind); +1202 buf.write_var(largest); +1203 buf.write_var(*delay); +1204 buf.write_var(ranges.len() as u64 - 1); +1205 buf.write_var(first_size - 1); +1206 let mut prev = first.start; +1207 for block in rest { +1208 let size = block.end - block.start; +1209 buf.write_var(prev - block.end - 1); +1210 buf.write_var(size - 1); +1211 prev = block.start; +1212 } +1213 if let Some(x) = ecn { +1214 x.encode(buf) +1215 } +1216 } +1217} +1218 +1219#[cfg_attr(test, derive(Arbitrary))] +1220#[derive(Debug, Copy, Clone, Eq, PartialEq)] +1221pub(crate) struct EcnCounts { +1222 #[cfg_attr(test, strategy(varint_u64()))] +1223 pub ect0: u64, +1224 #[cfg_attr(test, strategy(varint_u64()))] +1225 pub ect1: u64, +1226 #[cfg_attr(test, strategy(varint_u64()))] +1227 pub ce: u64, +1228} +1229 +1230impl std::ops::AddAssign<EcnCodepoint> for EcnCounts { +1231 fn add_assign(&mut self, rhs: EcnCodepoint) { +1232 match rhs { +1233 EcnCodepoint::Ect0 => { +1234 self.ect0 += 1; +1235 } +1236 EcnCodepoint::Ect1 => { +1237 self.ect1 += 1; +1238 } +1239 EcnCodepoint::Ce => { +1240 self.ce += 1; +1241 } +1242 } +1243 } +1244} +1245 +1246impl EcnCounts { +1247 pub(crate) const ZERO: Self = Self { +1248 ect0: 0, +1249 ect1: 0, +1250 ce: 0, +1251 }; +1252} +1253 +1254impl Encodable for EcnCounts { +1255 fn encode<W: BufMut>(&self, out: &mut W) { +1256 out.write_var(self.ect0); +1257 out.write_var(self.ect1); +1258 out.write_var(self.ce); 1259 } 1260} 1261 -1262impl StreamMeta { -1263 pub(crate) fn encoder(self, encode_length: bool) -> StreamMetaEncoder { -1264 StreamMetaEncoder { -1265 meta: self, -1266 encode_length, -1267 } -1268 } -1269 -1270 const fn get_type(&self, encode_length: bool) -> StreamInfo { -1271 let mut ty = *StreamInfo::VALUES.start(); -1272 if self.offsets.start != 0 { -1273 ty |= 0x04; -1274 } -1275 if encode_length { -1276 ty |= 0x02; -1277 } -1278 if self.fin { -1279 ty |= 0x01; -1280 } -1281 StreamInfo(ty as u8) -1282 } -1283} -1284 -1285#[derive(derive_more::Display)] -1286#[display("{meta}")] -1287pub(crate) struct StreamMetaEncoder { -1288 pub(crate) meta: StreamMeta, -1289 encode_length: bool, -1290} -1291 -1292impl StreamMetaEncoder { -1293 const fn get_type(&self) -> FrameType { -1294 FrameType::Stream(self.meta.get_type(self.encode_length)) +1262#[derive(Debug, Clone, derive_more::Display)] +1263#[cfg_attr(test, derive(Arbitrary, PartialEq, Eq))] +1264#[display("STREAM id: {id} off: {offset} len: {} fin: {fin}", data.len())] +1265pub(crate) struct Stream { +1266 pub(crate) id: StreamId, +1267 #[cfg_attr(test, strategy(varint_u64()))] +1268 pub(crate) offset: u64, +1269 pub(crate) fin: bool, +1270 #[cfg_attr(test, strategy(Strategy::prop_map(collection::vec(any::<u8>(), 0..100), Bytes::from)))] +1271 pub(crate) data: Bytes, +1272} +1273 +1274impl FrameStruct for Stream { +1275 const SIZE_BOUND: usize = 1 + 8 + 8 + 8; +1276} +1277 +1278/// Metadata from a stream frame +1279#[derive(Debug, Clone, derive_more::Display)] +1280#[display("STREAM id: {id} off: {} len: {} fin: {fin}", offsets.start, offsets.end - offsets.start)] +1281pub(crate) struct StreamMeta { +1282 pub(crate) id: StreamId, +1283 pub(crate) offsets: Range<u64>, +1284 pub(crate) fin: bool, +1285} +1286 +1287// This manual implementation exists because `Default` is not implemented for `StreamId` +1288impl Default for StreamMeta { +1289 fn default() -> Self { +1290 Self { +1291 id: StreamId(0), +1292 offsets: 0..0, +1293 fin: false, +1294 } 1295 } 1296} 1297 -1298impl Encodable for StreamMetaEncoder { -1299 fn encode<W: BufMut>(&self, out: &mut W) { -1300 let Self { -1301 meta, +1298impl StreamMeta { +1299 pub(crate) fn encoder(self, encode_length: bool) -> StreamMetaEncoder { +1300 StreamMetaEncoder { +1301 meta: self, 1302 encode_length, -1303 } = self; -1304 out.write_var(meta.get_type(*encode_length).0 as u64); // 1 byte -1305 out.write(meta.id); // <=8 bytes -1306 if meta.offsets.start != 0 { -1307 out.write_var(meta.offsets.start); // <=8 bytes -1308 } -1309 if *encode_length { -1310 out.write_var(meta.offsets.end - meta.offsets.start); // <=8 bytes -1311 } -1312 } -1313} -1314 -1315/// A vector of [`StreamMeta`] with optimization for the single element case -1316pub(crate) type StreamMetaVec = TinyVec<[StreamMeta; 1]>; -1317 -1318#[derive(Debug, Clone, derive_more::Display)] -1319#[cfg_attr(test, derive(Arbitrary, PartialEq, Eq))] -1320#[display("CRYPTO off: {offset} len = {}", data.len())] -1321pub(crate) struct Crypto { -1322 #[cfg_attr(test, strategy(varint_u64()))] -1323 pub(crate) offset: u64, -1324 #[cfg_attr(test, strategy(Strategy::prop_map(collection::vec(any::<u8>(), 0..1024), Bytes::from)))] -1325 pub(crate) data: Bytes, +1303 } +1304 } +1305 +1306 const fn get_type(&self, encode_length: bool) -> StreamInfo { +1307 let mut ty = *StreamInfo::VALUES.start(); +1308 if self.offsets.start != 0 { +1309 ty |= 0x04; +1310 } +1311 if encode_length { +1312 ty |= 0x02; +1313 } +1314 if self.fin { +1315 ty |= 0x01; +1316 } +1317 StreamInfo(ty as u8) +1318 } +1319} +1320 +1321#[derive(derive_more::Display)] +1322#[display("{meta}")] +1323pub(crate) struct StreamMetaEncoder { +1324 pub(crate) meta: StreamMeta, +1325 encode_length: bool, 1326} 1327 -1328impl Crypto { -1329 pub(crate) const SIZE_BOUND: usize = 17; -1330 -1331 const fn get_type(&self) -> FrameType { -1332 FrameType::Crypto -1333 } -1334} -1335 -1336impl Encodable for Crypto { -1337 fn encode<W: BufMut>(&self, out: &mut W) { -1338 out.write(FrameType::Crypto); -1339 out.write_var(self.offset); -1340 out.write_var(self.data.len() as u64); -1341 out.put_slice(&self.data); -1342 } -1343} -1344 -1345#[derive(Debug, Clone, derive_more::Display)] -1346#[cfg_attr(test, derive(Arbitrary, PartialEq, Eq))] -1347#[display("NEW_TOKEN")] -1348pub(crate) struct NewToken { -1349 #[cfg_attr(test, strategy(Strategy::prop_map(collection::vec(any::<u8>(), 0..1024), Bytes::from)))] -1350 pub(crate) token: Bytes, -1351} -1352 -1353impl Encodable for NewToken { -1354 fn encode<W: BufMut>(&self, out: &mut W) { -1355 out.write(FrameType::NewToken); -1356 out.write_var(self.token.len() as u64); -1357 out.put_slice(&self.token); -1358 } -1359} -1360 -1361impl NewToken { -1362 pub(crate) fn size(&self) -> usize { -1363 1 + VarInt::from_u64(self.token.len() as u64).unwrap().size() + self.token.len() -1364 } -1365 -1366 const fn get_type(&self) -> FrameType { -1367 FrameType::NewToken -1368 } -1369} -1370 -1371#[derive(Debug, Clone, derive_more::Display)] -1372#[cfg_attr(test, derive(Arbitrary, PartialEq, Eq))] -1373#[display("MAX_PATH_ID path_id: {_0}")] -1374pub(crate) struct MaxPathId(pub(crate) PathId); -1375 -1376impl MaxPathId { -1377 pub(crate) const SIZE_BOUND: usize = -1378 FrameType::MaxPathId.size() + VarInt(u32::MAX as u64).size(); -1379 -1380 const fn get_type(&self) -> FrameType { -1381 FrameType::MaxPathId -1382 } -1383} -1384 -1385impl Decodable for MaxPathId { -1386 fn decode<B: Buf>(buf: &mut B) -> coding::Result<Self> { -1387 Ok(Self(buf.get()?)) -1388 } -1389} -1390 -1391impl Encodable for MaxPathId { -1392 fn encode<B: BufMut>(&self, buf: &mut B) { -1393 buf.write(FrameType::MaxPathId); -1394 buf.write(self.0); -1395 } -1396} -1397 -1398#[derive(Debug, Clone, PartialEq, Eq, derive_more::Display)] -1399#[cfg_attr(test, derive(Arbitrary))] -1400#[display("PATHS_BLOCKED remote_max_path_id: {_0}")] -1401pub(crate) struct PathsBlocked(pub(crate) PathId); -1402 -1403impl PathsBlocked { -1404 pub(crate) const SIZE_BOUND: usize = -1405 FrameType::PathsBlocked.size() + VarInt(u32::MAX as u64).size(); +1328impl StreamMetaEncoder { +1329 const fn get_type(&self) -> FrameType { +1330 FrameType::Stream(self.meta.get_type(self.encode_length)) +1331 } +1332} +1333 +1334impl Encodable for StreamMetaEncoder { +1335 fn encode<W: BufMut>(&self, out: &mut W) { +1336 let Self { +1337 meta, +1338 encode_length, +1339 } = self; +1340 out.write_var(meta.get_type(*encode_length).0 as u64); // 1 byte +1341 out.write(meta.id); // <=8 bytes +1342 if meta.offsets.start != 0 { +1343 out.write_var(meta.offsets.start); // <=8 bytes +1344 } +1345 if *encode_length { +1346 out.write_var(meta.offsets.end - meta.offsets.start); // <=8 bytes +1347 } +1348 } +1349} +1350 +1351/// A vector of [`StreamMeta`] with optimization for the single element case +1352pub(crate) type StreamMetaVec = TinyVec<[StreamMeta; 1]>; +1353 +1354#[derive(Debug, Clone, derive_more::Display)] +1355#[cfg_attr(test, derive(Arbitrary, PartialEq, Eq))] +1356#[display("CRYPTO off: {offset} len = {}", data.len())] +1357pub(crate) struct Crypto { +1358 #[cfg_attr(test, strategy(varint_u64()))] +1359 pub(crate) offset: u64, +1360 #[cfg_attr(test, strategy(Strategy::prop_map(collection::vec(any::<u8>(), 0..1024), Bytes::from)))] +1361 pub(crate) data: Bytes, +1362} +1363 +1364impl Crypto { +1365 pub(crate) const SIZE_BOUND: usize = 17; +1366 +1367 const fn get_type(&self) -> FrameType { +1368 FrameType::Crypto +1369 } +1370} +1371 +1372impl Encodable for Crypto { +1373 fn encode<W: BufMut>(&self, out: &mut W) { +1374 out.write(FrameType::Crypto); +1375 out.write_var(self.offset); +1376 out.write_var(self.data.len() as u64); +1377 out.put_slice(&self.data); +1378 } +1379} +1380 +1381#[derive(Debug, Clone, derive_more::Display)] +1382#[cfg_attr(test, derive(Arbitrary, PartialEq, Eq))] +1383#[display("NEW_TOKEN")] +1384pub(crate) struct NewToken { +1385 #[cfg_attr(test, strategy(Strategy::prop_map(collection::vec(any::<u8>(), 0..1024), Bytes::from)))] +1386 pub(crate) token: Bytes, +1387} +1388 +1389impl Encodable for NewToken { +1390 fn encode<W: BufMut>(&self, out: &mut W) { +1391 out.write(FrameType::NewToken); +1392 out.write_var(self.token.len() as u64); +1393 out.put_slice(&self.token); +1394 } +1395} +1396 +1397impl NewToken { +1398 pub(crate) fn size(&self) -> usize { +1399 1 + VarInt::from_u64(self.token.len() as u64).unwrap().size() + self.token.len() +1400 } +1401 +1402 const fn get_type(&self) -> FrameType { +1403 FrameType::NewToken +1404 } +1405} 1406 -1407 const fn get_type(&self) -> FrameType { -1408 FrameType::PathsBlocked -1409 } -1410} +1407#[derive(Debug, Clone, derive_more::Display)] +1408#[cfg_attr(test, derive(Arbitrary, PartialEq, Eq))] +1409#[display("MAX_PATH_ID path_id: {_0}")] +1410pub(crate) struct MaxPathId(pub(crate) PathId); 1411 -1412impl Encodable for PathsBlocked { -1413 fn encode<B: BufMut>(&self, buf: &mut B) { -1414 buf.write(FrameType::PathsBlocked); -1415 buf.write(self.0); -1416 } -1417} -1418 -1419impl Decodable for PathsBlocked { -1420 /// Decode [`Self`] from the buffer, provided that the frame type has been verified -1421 fn decode<B: Buf>(buf: &mut B) -> coding::Result<Self> { -1422 Ok(Self(buf.get()?)) -1423 } -1424} -1425 -1426#[derive(Debug, Clone, PartialEq, Eq, derive_more::Display)] -1427#[cfg_attr(test, derive(Arbitrary))] -1428#[display("PATH_CIDS_BLOCKED path_id: {path_id} next_seq: {next_seq}")] -1429pub(crate) struct PathCidsBlocked { -1430 pub(crate) path_id: PathId, -1431 pub(crate) next_seq: VarInt, +1412impl MaxPathId { +1413 pub(crate) const SIZE_BOUND: usize = +1414 FrameType::MaxPathId.size() + VarInt(u32::MAX as u64).size(); +1415 +1416 const fn get_type(&self) -> FrameType { +1417 FrameType::MaxPathId +1418 } +1419} +1420 +1421impl Decodable for MaxPathId { +1422 fn decode<B: Buf>(buf: &mut B) -> coding::Result<Self> { +1423 Ok(Self(buf.get()?)) +1424 } +1425} +1426 +1427impl Encodable for MaxPathId { +1428 fn encode<B: BufMut>(&self, buf: &mut B) { +1429 buf.write(FrameType::MaxPathId); +1430 buf.write(self.0); +1431 } 1432} 1433 -1434impl PathCidsBlocked { -1435 pub(crate) const SIZE_BOUND: usize = -1436 FrameType::PathCidsBlocked.size() + VarInt(u32::MAX as u64).size() + VarInt::MAX.size(); -1437 -1438 const fn get_type(&self) -> FrameType { -1439 FrameType::PathCidsBlocked -1440 } -1441} +1434#[derive(Debug, Clone, PartialEq, Eq, derive_more::Display)] +1435#[cfg_attr(test, derive(Arbitrary))] +1436#[display("PATHS_BLOCKED remote_max_path_id: {_0}")] +1437pub(crate) struct PathsBlocked(pub(crate) PathId); +1438 +1439impl PathsBlocked { +1440 pub(crate) const SIZE_BOUND: usize = +1441 FrameType::PathsBlocked.size() + VarInt(u32::MAX as u64).size(); 1442 -1443impl Decodable for PathCidsBlocked { -1444 fn decode<R: Buf>(buf: &mut R) -> coding::Result<Self> { -1445 Ok(Self { -1446 path_id: buf.get()?, -1447 next_seq: buf.get()?, -1448 }) -1449 } -1450} -1451 -1452impl Encodable for PathCidsBlocked { -1453 fn encode<W: BufMut>(&self, buf: &mut W) { -1454 buf.write(FrameType::PathCidsBlocked); -1455 buf.write(self.path_id); -1456 buf.write(self.next_seq); -1457 } -1458} -1459 -1460pub(crate) struct Iter { -1461 bytes: Bytes, -1462 last_ty: MaybeFrame, -1463} -1464 -1465impl Iter { -1466 pub(crate) fn new(payload: Bytes) -> Result<Self, TransportError> { -1467 if payload.is_empty() { -1468 // "An endpoint MUST treat receipt of a packet containing no frames as a -1469 // connection error of type PROTOCOL_VIOLATION." -1470 // https://www.rfc-editor.org/rfc/rfc9000.html#name-frames-and-frame-types -1471 return Err(TransportError::PROTOCOL_VIOLATION( -1472 "packet payload is empty", -1473 )); -1474 } -1475 -1476 Ok(Self { -1477 bytes: payload, -1478 last_ty: MaybeFrame::None, -1479 }) -1480 } -1481 -1482 fn take_len(&mut self) -> Result<Bytes, UnexpectedEnd> { -1483 let len = self.bytes.get_var()?; -1484 if len > self.bytes.remaining() as u64 { -1485 return Err(UnexpectedEnd); -1486 } -1487 Ok(self.bytes.split_to(len as usize)) -1488 } -1489 -1490 #[track_caller] -1491 fn try_next(&mut self) -> Result<Frame, IterErr> { -1492 self.last_ty = self.bytes.get()?; -1493 -1494 let ty = match self.last_ty { -1495 MaybeFrame::None => FrameType::Padding, -1496 MaybeFrame::Unknown(_other) => return Err(IterErr::InvalidFrameId), -1497 MaybeFrame::Known(frame_type) => frame_type, -1498 }; -1499 Ok(match ty { -1500 FrameType::Padding => Frame::Padding, -1501 FrameType::ResetStream => Frame::ResetStream(ResetStream { -1502 id: self.bytes.get()?, -1503 error_code: self.bytes.get()?, -1504 final_offset: self.bytes.get()?, -1505 }), -1506 FrameType::ConnectionClose => Frame::Close(Close::Connection(ConnectionClose { -1507 error_code: self.bytes.get()?, -1508 frame_type: self.bytes.get()?, -1509 reason: self.take_len()?, -1510 })), -1511 FrameType::ApplicationClose => Frame::Close(Close::Application(ApplicationClose { -1512 error_code: self.bytes.get()?, -1513 reason: self.take_len()?, -1514 })), -1515 FrameType::MaxData => Frame::MaxData(self.bytes.get()?), -1516 FrameType::MaxStreamData => Frame::MaxStreamData(self.bytes.get()?), -1517 FrameType::MaxStreamsBidi => Frame::MaxStreams(MaxStreams { -1518 dir: Dir::Bi, -1519 count: self.bytes.get_var()?, -1520 }), -1521 FrameType::MaxStreamsUni => Frame::MaxStreams(MaxStreams { -1522 dir: Dir::Uni, -1523 count: self.bytes.get_var()?, -1524 }), -1525 FrameType::Ping => Frame::Ping, -1526 FrameType::DataBlocked => Frame::DataBlocked(DataBlocked(self.bytes.get_var()?)), -1527 FrameType::StreamDataBlocked => Frame::StreamDataBlocked(StreamDataBlocked { -1528 id: self.bytes.get()?, -1529 offset: self.bytes.get_var()?, -1530 }), -1531 FrameType::StreamsBlockedBidi => Frame::StreamsBlocked(StreamsBlocked { -1532 dir: Dir::Bi, -1533 limit: self.bytes.get_var()?, -1534 }), -1535 FrameType::StreamsBlockedUni => Frame::StreamsBlocked(StreamsBlocked { -1536 dir: Dir::Uni, -1537 limit: self.bytes.get_var()?, -1538 }), -1539 FrameType::StopSending => Frame::StopSending(StopSending { -1540 id: self.bytes.get()?, -1541 error_code: self.bytes.get()?, -1542 }), -1543 FrameType::RetireConnectionId | FrameType::PathRetireConnectionId => { -1544 Frame::RetireConnectionId(RetireConnectionId::decode( -1545 &mut self.bytes, -1546 ty == FrameType::PathRetireConnectionId, -1547 )?) -1548 } -1549 FrameType::Ack => { -1550 let largest = self.bytes.get_var()?; -1551 let delay = self.bytes.get_var()?; -1552 let ranges = read_ack_blocks(&mut self.bytes, largest)?; -1553 Frame::Ack(Ack { -1554 delay, -1555 largest, -1556 ranges, -1557 ecn: None, -1558 }) -1559 } -1560 FrameType::AckEcn => { -1561 let largest = self.bytes.get_var()?; -1562 let delay = self.bytes.get_var()?; -1563 let ranges = read_ack_blocks(&mut self.bytes, largest)?; -1564 let ecn = Some(EcnCounts { -1565 ect0: self.bytes.get_var()?, -1566 ect1: self.bytes.get_var()?, -1567 ce: self.bytes.get_var()?, -1568 }); -1569 -1570 Frame::Ack(Ack { -1571 delay, -1572 largest, -1573 ranges, -1574 ecn, -1575 }) -1576 } -1577 FrameType::PathAck => { -1578 let path_id = self.bytes.get()?; -1579 let largest = self.bytes.get_var()?; -1580 let delay = self.bytes.get_var()?; -1581 let ranges = read_ack_blocks(&mut self.bytes, largest)?; -1582 Frame::PathAck(PathAck { -1583 path_id, -1584 delay, -1585 largest, -1586 ranges, -1587 ecn: None, -1588 }) -1589 } -1590 FrameType::PathAckEcn => { -1591 let path_id = self.bytes.get()?; -1592 let largest = self.bytes.get_var()?; -1593 let delay = self.bytes.get_var()?; -1594 let ranges = read_ack_blocks(&mut self.bytes, largest)?; -1595 let ecn = Some(EcnCounts { -1596 ect0: self.bytes.get_var()?, -1597 ect1: self.bytes.get_var()?, -1598 ce: self.bytes.get_var()?, -1599 }); -1600 Frame::PathAck(PathAck { -1601 path_id, -1602 delay, -1603 largest, -1604 ranges, -1605 ecn, -1606 }) -1607 } -1608 FrameType::PathChallenge => Frame::PathChallenge(self.bytes.get()?), -1609 FrameType::PathResponse => Frame::PathResponse(self.bytes.get()?), -1610 FrameType::NewConnectionId | FrameType::PathNewConnectionId => { -1611 let read_path = ty == FrameType::PathNewConnectionId; -1612 Frame::NewConnectionId(NewConnectionId::read(&mut self.bytes, read_path)?) -1613 } -1614 FrameType::Crypto => Frame::Crypto(Crypto { -1615 offset: self.bytes.get_var()?, -1616 data: self.take_len()?, -1617 }), -1618 FrameType::NewToken => Frame::NewToken(NewToken { -1619 token: self.take_len()?, -1620 }), -1621 FrameType::HandshakeDone => Frame::HandshakeDone, -1622 FrameType::AckFrequency => Frame::AckFrequency(AckFrequency { -1623 sequence: self.bytes.get()?, -1624 ack_eliciting_threshold: self.bytes.get()?, -1625 request_max_ack_delay: self.bytes.get()?, -1626 reordering_threshold: self.bytes.get()?, -1627 }), -1628 FrameType::ImmediateAck => Frame::ImmediateAck, -1629 FrameType::ObservedIpv4Addr | FrameType::ObservedIpv6Addr => { -1630 let is_ipv6 = ty == FrameType::ObservedIpv6Addr; -1631 let observed = ObservedAddr::read(&mut self.bytes, is_ipv6)?; -1632 Frame::ObservedAddr(observed) -1633 } -1634 FrameType::PathAbandon => Frame::PathAbandon(PathAbandon::decode(&mut self.bytes)?), -1635 FrameType::PathStatusAvailable => { -1636 Frame::PathStatusAvailable(PathStatusAvailable::decode(&mut self.bytes)?) -1637 } -1638 FrameType::PathStatusBackup => { -1639 Frame::PathStatusBackup(PathStatusBackup::decode(&mut self.bytes)?) -1640 } -1641 FrameType::MaxPathId => Frame::MaxPathId(MaxPathId::decode(&mut self.bytes)?), -1642 FrameType::PathsBlocked => Frame::PathsBlocked(PathsBlocked::decode(&mut self.bytes)?), -1643 FrameType::PathCidsBlocked => { -1644 Frame::PathCidsBlocked(PathCidsBlocked::decode(&mut self.bytes)?) -1645 } -1646 FrameType::AddIpv4Address | FrameType::AddIpv6Address => { -1647 let is_ipv6 = ty == FrameType::AddIpv6Address; -1648 let add_address = AddAddress::read(&mut self.bytes, is_ipv6)?; -1649 Frame::AddAddress(add_address) -1650 } -1651 FrameType::ReachOutAtIpv4 | FrameType::ReachOutAtIpv6 => { -1652 let is_ipv6 = ty == FrameType::ReachOutAtIpv6; -1653 let reach_out = ReachOut::read(&mut self.bytes, is_ipv6)?; -1654 Frame::ReachOut(reach_out) -1655 } -1656 FrameType::RemoveAddress => Frame::RemoveAddress(RemoveAddress::read(&mut self.bytes)?), -1657 FrameType::Stream(s) => Frame::Stream(Stream { -1658 id: self.bytes.get()?, -1659 offset: if s.off() { self.bytes.get_var()? } else { 0 }, -1660 fin: s.fin(), -1661 data: if s.len() { -1662 self.take_len()? -1663 } else { -1664 self.take_remaining() -1665 }, -1666 }), -1667 FrameType::Datagram(d) => Frame::Datagram(Datagram { -1668 data: if d.len() { -1669 self.take_len()? -1670 } else { -1671 self.take_remaining() -1672 }, -1673 }), -1674 }) -1675 } -1676 -1677 pub(crate) fn take_remaining(&mut self) -> Bytes { -1678 mem::take(&mut self.bytes) -1679 } -1680} -1681 -1682impl Iterator for Iter { -1683 type Item = Result<Frame, InvalidFrame>; -1684 fn next(&mut self) -> Option<Self::Item> { -1685 if !self.bytes.has_remaining() { -1686 return None; -1687 } -1688 match self.try_next() { -1689 Ok(x) => Some(Ok(x)), -1690 Err(e) => { -1691 // Corrupt frame, skip it and everything that follows -1692 self.bytes.clear(); -1693 Some(Err(InvalidFrame { -1694 ty: self.last_ty, -1695 reason: e.reason(), -1696 })) -1697 } -1698 } -1699 } -1700} -1701 -1702#[derive(Debug)] -1703pub(crate) struct InvalidFrame { -1704 pub(crate) ty: MaybeFrame, -1705 pub(crate) reason: &'static str, -1706} -1707 -1708impl From<InvalidFrame> for TransportError { -1709 fn from(err: InvalidFrame) -> Self { -1710 let mut te = Self::FRAME_ENCODING_ERROR(err.reason); -1711 te.frame = err.ty; -1712 te -1713 } -1714} -1715 -1716/// Decodes the ACK Ranges from the given buffer. -1717/// This means, reading these three values -1718/// -1719/// - ACK Range Count -1720/// - First ACK Range -1721/// - ACK Ranges -1722/// -1723/// Ref <https://www.rfc-editor.org/rfc/rfc9000.html#name-ack-ranges> -1724fn read_ack_blocks(buf: &mut Bytes, mut largest: u64) -> Result<ArrayRangeSet, IterErr> { -1725 // Ack Range Count -1726 let num_blocks = buf.get_var()?; -1727 -1728 let mut out = ArrayRangeSet::new(); -1729 let mut block_to_block; -1730 let mut range; -1731 -1732 for num_block in 0..num_blocks + 1 { -1733 range = buf.get_var()?; -1734 range += 1; -1735 -1736 let start = (largest + 1).checked_sub(range).ok_or(IterErr::Malformed)?; +1443 const fn get_type(&self) -> FrameType { +1444 FrameType::PathsBlocked +1445 } +1446} +1447 +1448impl Encodable for PathsBlocked { +1449 fn encode<B: BufMut>(&self, buf: &mut B) { +1450 buf.write(FrameType::PathsBlocked); +1451 buf.write(self.0); +1452 } +1453} +1454 +1455impl Decodable for PathsBlocked { +1456 /// Decode [`Self`] from the buffer, provided that the frame type has been verified +1457 fn decode<B: Buf>(buf: &mut B) -> coding::Result<Self> { +1458 Ok(Self(buf.get()?)) +1459 } +1460} +1461 +1462#[derive(Debug, Clone, PartialEq, Eq, derive_more::Display)] +1463#[cfg_attr(test, derive(Arbitrary))] +1464#[display("PATH_CIDS_BLOCKED path_id: {path_id} next_seq: {next_seq}")] +1465pub(crate) struct PathCidsBlocked { +1466 pub(crate) path_id: PathId, +1467 pub(crate) next_seq: VarInt, +1468} +1469 +1470impl PathCidsBlocked { +1471 pub(crate) const SIZE_BOUND: usize = +1472 FrameType::PathCidsBlocked.size() + VarInt(u32::MAX as u64).size() + VarInt::MAX.size(); +1473 +1474 const fn get_type(&self) -> FrameType { +1475 FrameType::PathCidsBlocked +1476 } +1477} +1478 +1479impl Decodable for PathCidsBlocked { +1480 fn decode<R: Buf>(buf: &mut R) -> coding::Result<Self> { +1481 Ok(Self { +1482 path_id: buf.get()?, +1483 next_seq: buf.get()?, +1484 }) +1485 } +1486} +1487 +1488impl Encodable for PathCidsBlocked { +1489 fn encode<W: BufMut>(&self, buf: &mut W) { +1490 buf.write(FrameType::PathCidsBlocked); +1491 buf.write(self.path_id); +1492 buf.write(self.next_seq); +1493 } +1494} +1495 +1496pub(crate) struct Iter { +1497 bytes: Bytes, +1498 last_ty: MaybeFrame, +1499} +1500 +1501impl Iter { +1502 pub(crate) fn new(payload: Bytes) -> Result<Self, TransportError> { +1503 if payload.is_empty() { +1504 // "An endpoint MUST treat receipt of a packet containing no frames as a +1505 // connection error of type PROTOCOL_VIOLATION." +1506 // https://www.rfc-editor.org/rfc/rfc9000.html#name-frames-and-frame-types +1507 return Err(TransportError::PROTOCOL_VIOLATION( +1508 "packet payload is empty", +1509 )); +1510 } +1511 +1512 Ok(Self { +1513 bytes: payload, +1514 last_ty: MaybeFrame::None, +1515 }) +1516 } +1517 +1518 fn take_len(&mut self) -> Result<Bytes, UnexpectedEnd> { +1519 let len = self.bytes.get_var()?; +1520 if len > self.bytes.remaining() as u64 { +1521 return Err(UnexpectedEnd); +1522 } +1523 Ok(self.bytes.split_to(len as usize)) +1524 } +1525 +1526 #[track_caller] +1527 fn try_next(&mut self) -> Result<Frame, IterErr> { +1528 self.last_ty = self.bytes.get()?; +1529 +1530 let ty = match self.last_ty { +1531 MaybeFrame::None => FrameType::Padding, +1532 MaybeFrame::Unknown(_other) => return Err(IterErr::InvalidFrameId), +1533 MaybeFrame::Known(frame_type) => frame_type, +1534 }; +1535 Ok(match ty { +1536 FrameType::Padding => Frame::Padding, +1537 FrameType::ResetStream => Frame::ResetStream(ResetStream { +1538 id: self.bytes.get()?, +1539 error_code: self.bytes.get()?, +1540 final_offset: self.bytes.get()?, +1541 }), +1542 FrameType::ConnectionClose => Frame::Close(Close::Connection(ConnectionClose { +1543 error_code: self.bytes.get()?, +1544 frame_type: self.bytes.get()?, +1545 reason: self.take_len()?, +1546 })), +1547 FrameType::ApplicationClose => Frame::Close(Close::Application(ApplicationClose { +1548 error_code: self.bytes.get()?, +1549 reason: self.take_len()?, +1550 })), +1551 FrameType::MaxData => Frame::MaxData(self.bytes.get()?), +1552 FrameType::MaxStreamData => Frame::MaxStreamData(self.bytes.get()?), +1553 FrameType::MaxStreamsBidi => Frame::MaxStreams(MaxStreams { +1554 dir: Dir::Bi, +1555 count: self.bytes.get_var()?, +1556 }), +1557 FrameType::MaxStreamsUni => Frame::MaxStreams(MaxStreams { +1558 dir: Dir::Uni, +1559 count: self.bytes.get_var()?, +1560 }), +1561 FrameType::Ping => Frame::Ping, +1562 FrameType::DataBlocked => Frame::DataBlocked(DataBlocked(self.bytes.get_var()?)), +1563 FrameType::StreamDataBlocked => Frame::StreamDataBlocked(StreamDataBlocked { +1564 id: self.bytes.get()?, +1565 offset: self.bytes.get_var()?, +1566 }), +1567 FrameType::StreamsBlockedBidi => Frame::StreamsBlocked(StreamsBlocked { +1568 dir: Dir::Bi, +1569 limit: self.bytes.get_var()?, +1570 }), +1571 FrameType::StreamsBlockedUni => Frame::StreamsBlocked(StreamsBlocked { +1572 dir: Dir::Uni, +1573 limit: self.bytes.get_var()?, +1574 }), +1575 FrameType::StopSending => Frame::StopSending(StopSending { +1576 id: self.bytes.get()?, +1577 error_code: self.bytes.get()?, +1578 }), +1579 FrameType::RetireConnectionId | FrameType::PathRetireConnectionId => { +1580 Frame::RetireConnectionId(RetireConnectionId::decode( +1581 &mut self.bytes, +1582 ty == FrameType::PathRetireConnectionId, +1583 )?) +1584 } +1585 FrameType::Ack => { +1586 let largest = self.bytes.get_var()?; +1587 let delay = self.bytes.get_var()?; +1588 let ranges = read_ack_blocks(&mut self.bytes, largest)?; +1589 Frame::Ack(Ack { +1590 delay, +1591 largest, +1592 ranges, +1593 ecn: None, +1594 }) +1595 } +1596 FrameType::AckEcn => { +1597 let largest = self.bytes.get_var()?; +1598 let delay = self.bytes.get_var()?; +1599 let ranges = read_ack_blocks(&mut self.bytes, largest)?; +1600 let ecn = Some(EcnCounts { +1601 ect0: self.bytes.get_var()?, +1602 ect1: self.bytes.get_var()?, +1603 ce: self.bytes.get_var()?, +1604 }); +1605 +1606 Frame::Ack(Ack { +1607 delay, +1608 largest, +1609 ranges, +1610 ecn, +1611 }) +1612 } +1613 FrameType::PathAck => { +1614 let path_id = self.bytes.get()?; +1615 let largest = self.bytes.get_var()?; +1616 let delay = self.bytes.get_var()?; +1617 let ranges = read_ack_blocks(&mut self.bytes, largest)?; +1618 Frame::PathAck(PathAck { +1619 path_id, +1620 delay, +1621 largest, +1622 ranges, +1623 ecn: None, +1624 }) +1625 } +1626 FrameType::PathAckEcn => { +1627 let path_id = self.bytes.get()?; +1628 let largest = self.bytes.get_var()?; +1629 let delay = self.bytes.get_var()?; +1630 let ranges = read_ack_blocks(&mut self.bytes, largest)?; +1631 let ecn = Some(EcnCounts { +1632 ect0: self.bytes.get_var()?, +1633 ect1: self.bytes.get_var()?, +1634 ce: self.bytes.get_var()?, +1635 }); +1636 Frame::PathAck(PathAck { +1637 path_id, +1638 delay, +1639 largest, +1640 ranges, +1641 ecn, +1642 }) +1643 } +1644 FrameType::PathChallenge => Frame::PathChallenge(self.bytes.get()?), +1645 FrameType::PathResponse => Frame::PathResponse(self.bytes.get()?), +1646 FrameType::NewConnectionId | FrameType::PathNewConnectionId => { +1647 let read_path = ty == FrameType::PathNewConnectionId; +1648 Frame::NewConnectionId(NewConnectionId::read(&mut self.bytes, read_path)?) +1649 } +1650 FrameType::Crypto => Frame::Crypto(Crypto { +1651 offset: self.bytes.get_var()?, +1652 data: self.take_len()?, +1653 }), +1654 FrameType::NewToken => Frame::NewToken(NewToken { +1655 token: self.take_len()?, +1656 }), +1657 FrameType::HandshakeDone => Frame::HandshakeDone, +1658 FrameType::AckFrequency => Frame::AckFrequency(AckFrequency { +1659 sequence: self.bytes.get()?, +1660 ack_eliciting_threshold: self.bytes.get()?, +1661 request_max_ack_delay: self.bytes.get()?, +1662 reordering_threshold: self.bytes.get()?, +1663 }), +1664 FrameType::ImmediateAck => Frame::ImmediateAck, +1665 FrameType::ObservedIpv4Addr | FrameType::ObservedIpv6Addr => { +1666 let is_ipv6 = ty == FrameType::ObservedIpv6Addr; +1667 let observed = ObservedAddr::read(&mut self.bytes, is_ipv6)?; +1668 Frame::ObservedAddr(observed) +1669 } +1670 FrameType::PathAbandon => Frame::PathAbandon(PathAbandon::decode(&mut self.bytes)?), +1671 FrameType::PathStatusAvailable => { +1672 Frame::PathStatusAvailable(PathStatusAvailable::decode(&mut self.bytes)?) +1673 } +1674 FrameType::PathStatusBackup => { +1675 Frame::PathStatusBackup(PathStatusBackup::decode(&mut self.bytes)?) +1676 } +1677 FrameType::MaxPathId => Frame::MaxPathId(MaxPathId::decode(&mut self.bytes)?), +1678 FrameType::PathsBlocked => Frame::PathsBlocked(PathsBlocked::decode(&mut self.bytes)?), +1679 FrameType::PathCidsBlocked => { +1680 Frame::PathCidsBlocked(PathCidsBlocked::decode(&mut self.bytes)?) +1681 } +1682 FrameType::AddIpv4Address | FrameType::AddIpv6Address => { +1683 let is_ipv6 = ty == FrameType::AddIpv6Address; +1684 let add_address = AddAddress::read(&mut self.bytes, is_ipv6)?; +1685 Frame::AddAddress(add_address) +1686 } +1687 FrameType::ReachOutAtIpv4 | FrameType::ReachOutAtIpv6 => { +1688 let is_ipv6 = ty == FrameType::ReachOutAtIpv6; +1689 let reach_out = ReachOut::read(&mut self.bytes, is_ipv6)?; +1690 Frame::ReachOut(reach_out) +1691 } +1692 FrameType::RemoveAddress => Frame::RemoveAddress(RemoveAddress::read(&mut self.bytes)?), +1693 FrameType::Stream(s) => Frame::Stream(Stream { +1694 id: self.bytes.get()?, +1695 offset: if s.off() { self.bytes.get_var()? } else { 0 }, +1696 fin: s.fin(), +1697 data: if s.len() { +1698 self.take_len()? +1699 } else { +1700 self.take_remaining() +1701 }, +1702 }), +1703 FrameType::Datagram(d) => Frame::Datagram(Datagram { +1704 data: if d.len() { +1705 self.take_len()? +1706 } else { +1707 self.take_remaining() +1708 }, +1709 }), +1710 }) +1711 } +1712 +1713 pub(crate) fn take_remaining(&mut self) -> Bytes { +1714 mem::take(&mut self.bytes) +1715 } +1716} +1717 +1718impl Iterator for Iter { +1719 type Item = Result<Frame, InvalidFrame>; +1720 fn next(&mut self) -> Option<Self::Item> { +1721 if !self.bytes.has_remaining() { +1722 return None; +1723 } +1724 match self.try_next() { +1725 Ok(x) => Some(Ok(x)), +1726 Err(e) => { +1727 // Corrupt frame, skip it and everything that follows +1728 self.bytes.clear(); +1729 Some(Err(InvalidFrame { +1730 ty: self.last_ty, +1731 reason: e.reason(), +1732 })) +1733 } +1734 } +1735 } +1736} 1737 -1738 if range > 0 { -1739 out.insert(start..largest + 1); -1740 } -1741 -1742 // no gap on the last "block" -1743 if num_block < num_blocks { -1744 // skip the gap -1745 block_to_block = buf.get_var()?; -1746 block_to_block += 1; -1747 block_to_block += range; -1748 -1749 largest = largest -1750 .checked_sub(block_to_block) -1751 .ok_or(IterErr::Malformed)?; -1752 } -1753 } -1754 -1755 Ok(out) -1756} -1757 -1758#[derive(Debug)] -1759enum IterErr { -1760 UnexpectedEnd, -1761 InvalidFrameId, -1762 Malformed, -1763} -1764 -1765impl IterErr { -1766 fn reason(&self) -> &'static str { -1767 use IterErr::*; -1768 match *self { -1769 UnexpectedEnd => "unexpected end", -1770 InvalidFrameId => "invalid frame ID", -1771 Malformed => "malformed", -1772 } -1773 } -1774} -1775 -1776impl From<UnexpectedEnd> for IterErr { -1777 fn from(_: UnexpectedEnd) -> Self { -1778 Self::UnexpectedEnd -1779 } -1780} -1781 -1782#[allow(unreachable_pub)] // fuzzing only -1783#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -1784#[cfg_attr(test, derive(Arbitrary))] -1785#[derive(Debug, Copy, Clone, derive_more::Display)] -1786#[display("RESET_STREAM id: {id}")] -1787pub struct ResetStream { -1788 pub(crate) id: StreamId, -1789 pub(crate) error_code: VarInt, -1790 pub(crate) final_offset: VarInt, -1791} -1792 -1793impl ResetStream { -1794 const fn get_type(&self) -> FrameType { -1795 FrameType::ResetStream -1796 } -1797} -1798 -1799impl FrameStruct for ResetStream { -1800 const SIZE_BOUND: usize = 1 + 8 + 8 + 8; -1801} -1802 -1803impl Encodable for ResetStream { -1804 fn encode<W: BufMut>(&self, out: &mut W) { -1805 out.write(FrameType::ResetStream); // 1 byte -1806 out.write(self.id); // <= 8 bytes -1807 out.write(self.error_code); // <= 8 bytes -1808 out.write(self.final_offset); // <= 8 bytes -1809 } +1738#[derive(Debug)] +1739pub(crate) struct InvalidFrame { +1740 pub(crate) ty: MaybeFrame, +1741 pub(crate) reason: &'static str, +1742} +1743 +1744impl From<InvalidFrame> for TransportError { +1745 fn from(err: InvalidFrame) -> Self { +1746 let mut te = Self::FRAME_ENCODING_ERROR(err.reason); +1747 te.frame = err.ty; +1748 te +1749 } +1750} +1751 +1752/// Decodes the ACK Ranges from the given buffer. +1753/// This means, reading these three values +1754/// +1755/// - ACK Range Count +1756/// - First ACK Range +1757/// - ACK Ranges +1758/// +1759/// Ref <https://www.rfc-editor.org/rfc/rfc9000.html#name-ack-ranges> +1760fn read_ack_blocks(buf: &mut Bytes, mut largest: u64) -> Result<ArrayRangeSet, IterErr> { +1761 // Ack Range Count +1762 let num_blocks = buf.get_var()?; +1763 +1764 let mut out = ArrayRangeSet::new(); +1765 let mut block_to_block; +1766 let mut range; +1767 +1768 for num_block in 0..num_blocks + 1 { +1769 range = buf.get_var()?; +1770 range += 1; +1771 +1772 let start = (largest + 1).checked_sub(range).ok_or(IterErr::Malformed)?; +1773 +1774 if range > 0 { +1775 out.insert(start..largest + 1); +1776 } +1777 +1778 // no gap on the last "block" +1779 if num_block < num_blocks { +1780 // skip the gap +1781 block_to_block = buf.get_var()?; +1782 block_to_block += 1; +1783 block_to_block += range; +1784 +1785 largest = largest +1786 .checked_sub(block_to_block) +1787 .ok_or(IterErr::Malformed)?; +1788 } +1789 } +1790 +1791 Ok(out) +1792} +1793 +1794#[derive(Debug)] +1795enum IterErr { +1796 UnexpectedEnd, +1797 InvalidFrameId, +1798 Malformed, +1799} +1800 +1801impl IterErr { +1802 fn reason(&self) -> &'static str { +1803 use IterErr::*; +1804 match *self { +1805 UnexpectedEnd => "unexpected end", +1806 InvalidFrameId => "invalid frame ID", +1807 Malformed => "malformed", +1808 } +1809 } 1810} 1811 -1812#[cfg_attr(test, derive(Arbitrary))] -1813#[derive(Debug, Copy, Clone, derive_more::Display)] -1814#[display("STOP_SENDING id: {id}")] -1815pub(crate) struct StopSending { -1816 pub(crate) id: StreamId, -1817 pub(crate) error_code: VarInt, -1818} -1819 -1820impl FrameStruct for StopSending { -1821 const SIZE_BOUND: usize = 1 + 8 + 8; -1822} -1823 -1824impl StopSending { -1825 const fn get_type(&self) -> FrameType { -1826 FrameType::StopSending -1827 } -1828} -1829 -1830impl Encodable for StopSending { -1831 fn encode<W: BufMut>(&self, out: &mut W) { -1832 out.write(FrameType::StopSending); // 1 byte -1833 out.write(self.id); // <= 8 bytes -1834 out.write(self.error_code) // <= 8 bytes -1835 } -1836} -1837 -1838#[derive(Debug, Copy, Clone, PartialEq, Eq, derive_more::Display)] -1839#[display("{} {} seq: {sequence} id: {id}", self.get_type(), DisplayOption::new("path_id", path_id.as_ref()))] -1840pub(crate) struct NewConnectionId { -1841 pub(crate) path_id: Option<PathId>, -1842 pub(crate) sequence: u64, -1843 pub(crate) retire_prior_to: u64, -1844 pub(crate) id: ConnectionId, -1845 pub(crate) reset_token: ResetToken, +1812impl From<UnexpectedEnd> for IterErr { +1813 fn from(_: UnexpectedEnd) -> Self { +1814 Self::UnexpectedEnd +1815 } +1816} +1817 +1818#[allow(unreachable_pub)] // fuzzing only +1819#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +1820#[cfg_attr(test, derive(Arbitrary))] +1821#[derive(Debug, Copy, Clone, derive_more::Display)] +1822#[display("RESET_STREAM id: {id}")] +1823pub struct ResetStream { +1824 pub(crate) id: StreamId, +1825 pub(crate) error_code: VarInt, +1826 pub(crate) final_offset: VarInt, +1827} +1828 +1829impl ResetStream { +1830 const fn get_type(&self) -> FrameType { +1831 FrameType::ResetStream +1832 } +1833} +1834 +1835impl FrameStruct for ResetStream { +1836 const SIZE_BOUND: usize = 1 + 8 + 8 + 8; +1837} +1838 +1839impl Encodable for ResetStream { +1840 fn encode<W: BufMut>(&self, out: &mut W) { +1841 out.write(FrameType::ResetStream); // 1 byte +1842 out.write(self.id); // <= 8 bytes +1843 out.write(self.error_code); // <= 8 bytes +1844 out.write(self.final_offset); // <= 8 bytes +1845 } 1846} 1847 -1848#[cfg(test)] -1849fn connection_id_and_reset_token() -> impl Strategy<Value = (crate::ConnectionId, ResetToken)> { -1850 (any::<ConnectionId>(), any::<[u8; 64]>()).prop_map(|(id, reset_key)| { -1851 #[cfg(all(feature = "aws-lc-rs", not(feature = "ring")))] -1852 use aws_lc_rs::hmac; -1853 #[cfg(feature = "ring")] -1854 use ring::hmac; -1855 let key = hmac::Key::new(hmac::HMAC_SHA256, &reset_key); -1856 (id, ResetToken::new(&key, id)) -1857 }) +1848#[cfg_attr(test, derive(Arbitrary))] +1849#[derive(Debug, Copy, Clone, derive_more::Display)] +1850#[display("STOP_SENDING id: {id}")] +1851pub(crate) struct StopSending { +1852 pub(crate) id: StreamId, +1853 pub(crate) error_code: VarInt, +1854} +1855 +1856impl FrameStruct for StopSending { +1857 const SIZE_BOUND: usize = 1 + 8 + 8; 1858} 1859 -1860#[cfg(test)] -1861impl proptest::arbitrary::Arbitrary for NewConnectionId { -1862 type Parameters = (); -1863 type Strategy = proptest::strategy::BoxedStrategy<Self>; -1864 -1865 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { -1866 use proptest::prelude::*; -1867 ( -1868 any::<Option<PathId>>(), -1869 varint_u64(), -1870 varint_u64(), -1871 connection_id_and_reset_token(), -1872 ) -1873 .prop_map(|(path_id, a, b, (id, reset_token))| { -1874 let sequence = std::cmp::max(a, b); -1875 let retire_prior_to = std::cmp::min(a, b); -1876 Self { -1877 path_id, -1878 sequence, -1879 retire_prior_to, -1880 id, -1881 reset_token, -1882 } -1883 }) -1884 .boxed() -1885 } -1886} -1887 -1888impl NewConnectionId { -1889 /// Maximum size of this frame when the frame type is [`FrameType::NewConnectionId`], -1890 pub(crate) const SIZE_BOUND: usize = { -1891 let type_len = FrameType::NewConnectionId.size(); -1892 let seq_max_len = 8usize; -1893 let retire_prior_to_max_len = 8usize; -1894 let cid_len_len = 1; -1895 let cid_len = 160; -1896 let reset_token_len = 16; -1897 type_len + seq_max_len + retire_prior_to_max_len + cid_len_len + cid_len + reset_token_len -1898 }; -1899 -1900 /// Maximum size of this frame when the frame type is [`FrameType::PathNewConnectionId`], -1901 pub(crate) const SIZE_BOUND_MULTIPATH: usize = { -1902 let type_len = FrameType::PathNewConnectionId.size(); -1903 let path_id_len = VarInt::from_u32(u32::MAX).size(); -1904 let seq_max_len = 8usize; -1905 let retire_prior_to_max_len = 8usize; -1906 let cid_len_len = 1; -1907 let cid_len = 160; -1908 let reset_token_len = 16; -1909 type_len -1910 + path_id_len -1911 + seq_max_len -1912 + retire_prior_to_max_len -1913 + cid_len_len -1914 + cid_len -1915 + reset_token_len -1916 }; -1917 -1918 const fn get_type(&self) -> FrameType { -1919 if self.path_id.is_some() { -1920 FrameType::PathNewConnectionId -1921 } else { -1922 FrameType::NewConnectionId -1923 } -1924 } -1925 -1926 /// Returns the maximum encoded size on the wire. -1927 /// -1928 /// This is a rough upper estimate, does not squeeze every last byte out. -1929 pub(crate) const fn size_bound(path_new_cid: bool, cid_len: usize) -> usize { -1930 let upper_bound = match path_new_cid { -1931 true => Self::SIZE_BOUND_MULTIPATH, -1932 false => Self::SIZE_BOUND, -1933 }; -1934 // instead of using the maximum cid len, use the provided one -1935 upper_bound - 160 + cid_len -1936 } -1937 -1938 fn read<R: Buf>(bytes: &mut R, read_path: bool) -> Result<Self, IterErr> { -1939 let path_id = if read_path { Some(bytes.get()?) } else { None }; -1940 let sequence = bytes.get_var()?; -1941 let retire_prior_to = bytes.get_var()?; -1942 if retire_prior_to > sequence { -1943 return Err(IterErr::Malformed); -1944 } -1945 let length = bytes.get::<u8>()? as usize; -1946 if length > MAX_CID_SIZE || length == 0 { -1947 return Err(IterErr::Malformed); -1948 } -1949 if length > bytes.remaining() { -1950 return Err(IterErr::UnexpectedEnd); -1951 } -1952 let mut stage = [0; MAX_CID_SIZE]; -1953 bytes.copy_to_slice(&mut stage[0..length]); -1954 let id = ConnectionId::new(&stage[..length]); -1955 if bytes.remaining() < 16 { -1956 return Err(IterErr::UnexpectedEnd); -1957 } -1958 let mut reset_token = [0; RESET_TOKEN_SIZE]; -1959 bytes.copy_to_slice(&mut reset_token); -1960 Ok(Self { -1961 path_id, -1962 sequence, -1963 retire_prior_to, -1964 id, -1965 reset_token: reset_token.into(), -1966 }) -1967 } -1968 -1969 pub(crate) fn issued(&self) -> crate::shared::IssuedCid { -1970 crate::shared::IssuedCid { -1971 path_id: self.path_id.unwrap_or_default(), -1972 sequence: self.sequence, -1973 id: self.id, -1974 reset_token: self.reset_token, -1975 } -1976 } -1977} -1978 -1979impl Encodable for NewConnectionId { -1980 fn encode<W: BufMut>(&self, out: &mut W) { -1981 out.write(self.get_type()); -1982 if let Some(id) = self.path_id { -1983 out.write(id); +1860impl StopSending { +1861 const fn get_type(&self) -> FrameType { +1862 FrameType::StopSending +1863 } +1864} +1865 +1866impl Encodable for StopSending { +1867 fn encode<W: BufMut>(&self, out: &mut W) { +1868 out.write(FrameType::StopSending); // 1 byte +1869 out.write(self.id); // <= 8 bytes +1870 out.write(self.error_code) // <= 8 bytes +1871 } +1872} +1873 +1874#[derive(Debug, Copy, Clone, PartialEq, Eq, derive_more::Display)] +1875#[display("{} {} seq: {sequence} id: {id}", self.get_type(), DisplayOption::new("path_id", path_id.as_ref()))] +1876pub(crate) struct NewConnectionId { +1877 pub(crate) path_id: Option<PathId>, +1878 pub(crate) sequence: u64, +1879 pub(crate) retire_prior_to: u64, +1880 pub(crate) id: ConnectionId, +1881 pub(crate) reset_token: ResetToken, +1882} +1883 +1884#[cfg(test)] +1885fn connection_id_and_reset_token() -> impl Strategy<Value = (crate::ConnectionId, ResetToken)> { +1886 (any::<ConnectionId>(), any::<[u8; 64]>()).prop_map(|(id, reset_key)| { +1887 #[cfg(all(feature = "aws-lc-rs", not(feature = "ring")))] +1888 use aws_lc_rs::hmac; +1889 #[cfg(feature = "ring")] +1890 use ring::hmac; +1891 let key = hmac::Key::new(hmac::HMAC_SHA256, &reset_key); +1892 (id, ResetToken::new(&key, id)) +1893 }) +1894} +1895 +1896#[cfg(test)] +1897impl proptest::arbitrary::Arbitrary for NewConnectionId { +1898 type Parameters = (); +1899 type Strategy = proptest::strategy::BoxedStrategy<Self>; +1900 +1901 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { +1902 use proptest::prelude::*; +1903 ( +1904 any::<Option<PathId>>(), +1905 varint_u64(), +1906 varint_u64(), +1907 connection_id_and_reset_token(), +1908 ) +1909 .prop_map(|(path_id, a, b, (id, reset_token))| { +1910 let sequence = std::cmp::max(a, b); +1911 let retire_prior_to = std::cmp::min(a, b); +1912 Self { +1913 path_id, +1914 sequence, +1915 retire_prior_to, +1916 id, +1917 reset_token, +1918 } +1919 }) +1920 .boxed() +1921 } +1922} +1923 +1924impl NewConnectionId { +1925 /// Maximum size of this frame when the frame type is [`FrameType::NewConnectionId`], +1926 pub(crate) const SIZE_BOUND: usize = { +1927 let type_len = FrameType::NewConnectionId.size(); +1928 let seq_max_len = 8usize; +1929 let retire_prior_to_max_len = 8usize; +1930 let cid_len_len = 1; +1931 let cid_len = 160; +1932 let reset_token_len = 16; +1933 type_len + seq_max_len + retire_prior_to_max_len + cid_len_len + cid_len + reset_token_len +1934 }; +1935 +1936 /// Maximum size of this frame when the frame type is [`FrameType::PathNewConnectionId`], +1937 pub(crate) const SIZE_BOUND_MULTIPATH: usize = { +1938 let type_len = FrameType::PathNewConnectionId.size(); +1939 let path_id_len = VarInt::from_u32(u32::MAX).size(); +1940 let seq_max_len = 8usize; +1941 let retire_prior_to_max_len = 8usize; +1942 let cid_len_len = 1; +1943 let cid_len = 160; +1944 let reset_token_len = 16; +1945 type_len +1946 + path_id_len +1947 + seq_max_len +1948 + retire_prior_to_max_len +1949 + cid_len_len +1950 + cid_len +1951 + reset_token_len +1952 }; +1953 +1954 const fn get_type(&self) -> FrameType { +1955 if self.path_id.is_some() { +1956 FrameType::PathNewConnectionId +1957 } else { +1958 FrameType::NewConnectionId +1959 } +1960 } +1961 +1962 /// Returns the maximum encoded size on the wire. +1963 /// +1964 /// This is a rough upper estimate, does not squeeze every last byte out. +1965 pub(crate) const fn size_bound(path_new_cid: bool, cid_len: usize) -> usize { +1966 let upper_bound = match path_new_cid { +1967 true => Self::SIZE_BOUND_MULTIPATH, +1968 false => Self::SIZE_BOUND, +1969 }; +1970 // instead of using the maximum cid len, use the provided one +1971 upper_bound - 160 + cid_len +1972 } +1973 +1974 fn read<R: Buf>(bytes: &mut R, read_path: bool) -> Result<Self, IterErr> { +1975 let path_id = if read_path { Some(bytes.get()?) } else { None }; +1976 let sequence = bytes.get_var()?; +1977 let retire_prior_to = bytes.get_var()?; +1978 if retire_prior_to > sequence { +1979 return Err(IterErr::Malformed); +1980 } +1981 let length = bytes.get::<u8>()? as usize; +1982 if length > MAX_CID_SIZE || length == 0 { +1983 return Err(IterErr::Malformed); 1984 } -1985 out.write_var(self.sequence); -1986 out.write_var(self.retire_prior_to); -1987 out.write(self.id.len() as u8); -1988 out.put_slice(&self.id); -1989 out.put_slice(&self.reset_token); -1990 } -1991} -1992 -1993impl FrameStruct for NewConnectionId { -1994 const SIZE_BOUND: usize = 1 + 8 + 8 + 1 + MAX_CID_SIZE + RESET_TOKEN_SIZE; -1995} -1996 -1997/// An unreliable datagram -1998#[derive(Debug, Clone, derive_more::Display)] -1999#[cfg_attr(test, derive(Arbitrary))] -2000#[display("DATAGRAM len: {}", data.len())] -2001pub struct Datagram { -2002 /// Payload -2003 #[cfg_attr(test, strategy(Strategy::prop_map(collection::vec(any::<u8>(), 0..100), Bytes::from)))] -2004 pub data: Bytes, -2005} -2006 -2007impl FrameStruct for Datagram { -2008 const SIZE_BOUND: usize = 1 + 8; -2009} -2010 -2011impl Datagram { -2012 pub(crate) fn size(&self, length: bool) -> usize { -2013 1 + if length { -2014 VarInt::from_u64(self.data.len() as u64).unwrap().size() -2015 } else { -2016 0 -2017 } + self.data.len() -2018 } -2019 -2020 const fn get_type(&self) -> FrameType { -2021 FrameType::Datagram(DatagramInfo(*DatagramInfo::VALUES.start() as u8)) -2022 } -2023} -2024 -2025impl Encodable for Datagram { -2026 fn encode<B: BufMut>(&self, out: &mut B) { -2027 // A datagram is encoded only after this is verified. -2028 const ENCODE_LEN: bool = true; -2029 out.write(FrameType::Datagram(DatagramInfo( -2030 *DatagramInfo::VALUES.start() as u8 | u8::from(ENCODE_LEN), -2031 ))); // 1 byte -2032 // Safe to unwrap because we check length sanity before queueing datagrams -2033 out.write(VarInt::from_u64(self.data.len() as u64).unwrap()); // <= 8 bytes -2034 out.put_slice(&self.data); -2035 } -2036} -2037 -2038#[derive(Debug, Copy, Clone, PartialEq, Eq, derive_more::Display)] -2039#[cfg_attr(test, derive(Arbitrary))] -2040#[display("ACK_FREQUENCY max_ack_delay: {}µs", request_max_ack_delay.0)] -2041pub(crate) struct AckFrequency { -2042 pub(crate) sequence: VarInt, -2043 pub(crate) ack_eliciting_threshold: VarInt, -2044 pub(crate) request_max_ack_delay: VarInt, -2045 pub(crate) reordering_threshold: VarInt, -2046} -2047 -2048impl AckFrequency { -2049 const fn get_type(&self) -> FrameType { -2050 FrameType::AckFrequency -2051 } -2052} -2053 -2054impl Encodable for AckFrequency { -2055 fn encode<W: BufMut>(&self, buf: &mut W) { -2056 buf.write(FrameType::AckFrequency); -2057 buf.write(self.sequence); -2058 buf.write(self.ack_eliciting_threshold); -2059 buf.write(self.request_max_ack_delay); -2060 buf.write(self.reordering_threshold); -2061 } -2062} -2063 -2064/* Address Discovery https://datatracker.ietf.org/doc/draft-seemann-quic-address-discovery/ */ -2065 -2066/// Conjunction of the information contained in the address discovery frames -2067/// ([`FrameType::ObservedIpv4Addr`], [`FrameType::ObservedIpv6Addr`]). -2068#[derive(Debug, PartialEq, Eq, Clone, derive_more::Display)] -2069#[display("{} seq_no: {seq_no} addr: {}", self.get_type(), self.socket_addr())] -2070#[cfg_attr(test, derive(Arbitrary))] -2071pub(crate) struct ObservedAddr { -2072 /// Monotonically increasing integer within the same connection. -2073 pub(crate) seq_no: VarInt, -2074 /// Reported observed address. -2075 pub(crate) ip: IpAddr, -2076 /// Reported observed port. -2077 pub(crate) port: u16, -2078} -2079 -2080impl ObservedAddr { -2081 pub(crate) fn new<N: Into<VarInt>>(remote: std::net::SocketAddr, seq_no: N) -> Self { -2082 Self { -2083 ip: remote.ip(), -2084 port: remote.port(), -2085 seq_no: seq_no.into(), -2086 } +1985 if length > bytes.remaining() { +1986 return Err(IterErr::UnexpectedEnd); +1987 } +1988 let mut stage = [0; MAX_CID_SIZE]; +1989 bytes.copy_to_slice(&mut stage[0..length]); +1990 let id = ConnectionId::new(&stage[..length]); +1991 if bytes.remaining() < 16 { +1992 return Err(IterErr::UnexpectedEnd); +1993 } +1994 let mut reset_token = [0; RESET_TOKEN_SIZE]; +1995 bytes.copy_to_slice(&mut reset_token); +1996 Ok(Self { +1997 path_id, +1998 sequence, +1999 retire_prior_to, +2000 id, +2001 reset_token: reset_token.into(), +2002 }) +2003 } +2004 +2005 pub(crate) fn issued(&self) -> crate::shared::IssuedCid { +2006 crate::shared::IssuedCid { +2007 path_id: self.path_id.unwrap_or_default(), +2008 sequence: self.sequence, +2009 id: self.id, +2010 reset_token: self.reset_token, +2011 } +2012 } +2013} +2014 +2015impl Encodable for NewConnectionId { +2016 fn encode<W: BufMut>(&self, out: &mut W) { +2017 out.write(self.get_type()); +2018 if let Some(id) = self.path_id { +2019 out.write(id); +2020 } +2021 out.write_var(self.sequence); +2022 out.write_var(self.retire_prior_to); +2023 out.write(self.id.len() as u8); +2024 out.put_slice(&self.id); +2025 out.put_slice(&self.reset_token); +2026 } +2027} +2028 +2029impl FrameStruct for NewConnectionId { +2030 const SIZE_BOUND: usize = 1 + 8 + 8 + 1 + MAX_CID_SIZE + RESET_TOKEN_SIZE; +2031} +2032 +2033/// An unreliable datagram +2034#[derive(Debug, Clone, derive_more::Display)] +2035#[cfg_attr(test, derive(Arbitrary))] +2036#[display("DATAGRAM len: {}", data.len())] +2037pub struct Datagram { +2038 /// Payload +2039 #[cfg_attr(test, strategy(Strategy::prop_map(collection::vec(any::<u8>(), 0..100), Bytes::from)))] +2040 pub data: Bytes, +2041} +2042 +2043impl FrameStruct for Datagram { +2044 const SIZE_BOUND: usize = 1 + 8; +2045} +2046 +2047impl Datagram { +2048 pub(crate) fn size(&self, length: bool) -> usize { +2049 1 + if length { +2050 VarInt::from_u64(self.data.len() as u64).unwrap().size() +2051 } else { +2052 0 +2053 } + self.data.len() +2054 } +2055 +2056 const fn get_type(&self) -> FrameType { +2057 FrameType::Datagram(DatagramInfo(*DatagramInfo::VALUES.start() as u8)) +2058 } +2059} +2060 +2061impl Encodable for Datagram { +2062 fn encode<B: BufMut>(&self, out: &mut B) { +2063 // A datagram is encoded only after this is verified. +2064 const ENCODE_LEN: bool = true; +2065 out.write(FrameType::Datagram(DatagramInfo( +2066 *DatagramInfo::VALUES.start() as u8 | u8::from(ENCODE_LEN), +2067 ))); // 1 byte +2068 // Safe to unwrap because we check length sanity before queueing datagrams +2069 out.write(VarInt::from_u64(self.data.len() as u64).unwrap()); // <= 8 bytes +2070 out.put_slice(&self.data); +2071 } +2072} +2073 +2074#[derive(Debug, Copy, Clone, PartialEq, Eq, derive_more::Display)] +2075#[cfg_attr(test, derive(Arbitrary))] +2076#[display("ACK_FREQUENCY max_ack_delay: {}µs", request_max_ack_delay.0)] +2077pub(crate) struct AckFrequency { +2078 pub(crate) sequence: VarInt, +2079 pub(crate) ack_eliciting_threshold: VarInt, +2080 pub(crate) request_max_ack_delay: VarInt, +2081 pub(crate) reordering_threshold: VarInt, +2082} +2083 +2084impl AckFrequency { +2085 const fn get_type(&self) -> FrameType { +2086 FrameType::AckFrequency 2087 } -2088 -2089 /// Get the [`FrameType`] for this frame. -2090 const fn get_type(&self) -> FrameType { -2091 if self.ip.is_ipv6() { -2092 FrameType::ObservedIpv6Addr -2093 } else { -2094 FrameType::ObservedIpv4Addr -2095 } -2096 } -2097 -2098 /// Compute the number of bytes needed to encode the frame. -2099 pub(crate) fn size(&self) -> usize { -2100 let type_size = self.get_type().size(); -2101 let req_id_bytes = self.seq_no.size(); -2102 let ip_bytes = if self.ip.is_ipv6() { 16 } else { 4 }; -2103 let port_bytes = 2; -2104 type_size + req_id_bytes + ip_bytes + port_bytes -2105 } -2106 -2107 /// Reads the frame contents from the buffer. -2108 /// -2109 /// Should only be called when the frame type has been identified as -2110 /// [`FrameType::ObservedIpv4Addr`] or [`FrameType::ObservedIpv6Addr`]. -2111 pub(crate) fn read<R: Buf>(bytes: &mut R, is_ipv6: bool) -> coding::Result<Self> { -2112 let seq_no = bytes.get()?; -2113 let ip = if is_ipv6 { -2114 IpAddr::V6(bytes.get()?) -2115 } else { -2116 IpAddr::V4(bytes.get()?) -2117 }; -2118 let port = bytes.get()?; -2119 Ok(Self { seq_no, ip, port }) -2120 } -2121 -2122 /// Gives the [`SocketAddr`] reported in the frame. -2123 pub(crate) fn socket_addr(&self) -> SocketAddr { -2124 (self.ip, self.port).into() -2125 } -2126} -2127 -2128impl Encodable for ObservedAddr { -2129 fn encode<W: BufMut>(&self, buf: &mut W) { -2130 buf.write(self.get_type()); -2131 buf.write(self.seq_no); -2132 match self.ip { -2133 IpAddr::V4(ipv4_addr) => { -2134 buf.write(ipv4_addr); -2135 } -2136 IpAddr::V6(ipv6_addr) => { -2137 buf.write(ipv6_addr); -2138 } -2139 } -2140 buf.write::<u16>(self.port); +2088} +2089 +2090impl Encodable for AckFrequency { +2091 fn encode<W: BufMut>(&self, buf: &mut W) { +2092 buf.write(FrameType::AckFrequency); +2093 buf.write(self.sequence); +2094 buf.write(self.ack_eliciting_threshold); +2095 buf.write(self.request_max_ack_delay); +2096 buf.write(self.reordering_threshold); +2097 } +2098} +2099 +2100/* Address Discovery https://datatracker.ietf.org/doc/draft-seemann-quic-address-discovery/ */ +2101 +2102/// Conjunction of the information contained in the address discovery frames +2103/// ([`FrameType::ObservedIpv4Addr`], [`FrameType::ObservedIpv6Addr`]). +2104#[derive(Debug, PartialEq, Eq, Clone, derive_more::Display)] +2105#[display("{} seq_no: {seq_no} addr: {}", self.get_type(), self.socket_addr())] +2106#[cfg_attr(test, derive(Arbitrary))] +2107pub(crate) struct ObservedAddr { +2108 /// Monotonically increasing integer within the same connection. +2109 pub(crate) seq_no: VarInt, +2110 /// Reported observed address. +2111 pub(crate) ip: IpAddr, +2112 /// Reported observed port. +2113 pub(crate) port: u16, +2114} +2115 +2116impl ObservedAddr { +2117 pub(crate) fn new<N: Into<VarInt>>(remote: std::net::SocketAddr, seq_no: N) -> Self { +2118 Self { +2119 ip: remote.ip(), +2120 port: remote.port(), +2121 seq_no: seq_no.into(), +2122 } +2123 } +2124 +2125 /// Get the [`FrameType`] for this frame. +2126 const fn get_type(&self) -> FrameType { +2127 if self.ip.is_ipv6() { +2128 FrameType::ObservedIpv6Addr +2129 } else { +2130 FrameType::ObservedIpv4Addr +2131 } +2132 } +2133 +2134 /// Compute the number of bytes needed to encode the frame. +2135 pub(crate) fn size(&self) -> usize { +2136 let type_size = self.get_type().size(); +2137 let req_id_bytes = self.seq_no.size(); +2138 let ip_bytes = if self.ip.is_ipv6() { 16 } else { 4 }; +2139 let port_bytes = 2; +2140 type_size + req_id_bytes + ip_bytes + port_bytes 2141 } -2142} -2143 -2144/* Multipath <https://datatracker.ietf.org/doc/draft-ietf-quic-multipath/> */ -2145 -2146#[derive(Debug, PartialEq, Eq, derive_more::Display)] -2147#[cfg_attr(test, derive(Arbitrary))] -2148#[display("PATH_ABANDON path_id: {path_id}")] -2149pub(crate) struct PathAbandon { -2150 pub(crate) path_id: PathId, -2151 pub(crate) error_code: TransportErrorCode, -2152} -2153 -2154impl PathAbandon { -2155 pub(crate) const SIZE_BOUND: usize = FrameType::PathAbandon.size() + 8 + 8; -2156 -2157 const fn get_type(&self) -> FrameType { -2158 FrameType::PathAbandon -2159 } -2160} -2161 -2162impl Encodable for PathAbandon { -2163 fn encode<W: BufMut>(&self, buf: &mut W) { -2164 buf.write(FrameType::PathAbandon); -2165 buf.write(self.path_id); -2166 buf.write(self.error_code); -2167 } -2168} -2169 -2170impl Decodable for PathAbandon { -2171 fn decode<R: Buf>(bytes: &mut R) -> coding::Result<Self> { -2172 Ok(Self { -2173 path_id: bytes.get()?, -2174 error_code: bytes.get()?, -2175 }) -2176 } -2177} -2178 -2179#[derive(Debug, PartialEq, Eq, derive_more::Display)] -2180#[cfg_attr(test, derive(Arbitrary))] -2181#[display("PATH_STATUS_AVAILABLE path_id: {path_id} seq_no: {status_seq_no}")] -2182pub(crate) struct PathStatusAvailable { -2183 pub(crate) path_id: PathId, -2184 pub(crate) status_seq_no: VarInt, -2185} -2186 -2187impl PathStatusAvailable { -2188 const TYPE: FrameType = FrameType::PathStatusAvailable; -2189 pub(crate) const SIZE_BOUND: usize = FrameType::PathStatusAvailable.size() + 8 + 8; -2190 -2191 const fn get_type(&self) -> FrameType { -2192 FrameType::PathStatusAvailable -2193 } -2194} -2195 -2196impl Encodable for PathStatusAvailable { -2197 fn encode<W: BufMut>(&self, buf: &mut W) { -2198 buf.write(Self::TYPE); -2199 buf.write(self.path_id); -2200 buf.write(self.status_seq_no); -2201 } -2202} -2203 -2204impl Decodable for PathStatusAvailable { -2205 fn decode<R: Buf>(bytes: &mut R) -> coding::Result<Self> { -2206 Ok(Self { -2207 path_id: bytes.get()?, -2208 status_seq_no: bytes.get()?, -2209 }) -2210 } -2211} -2212 -2213#[derive(Debug, PartialEq, Eq, derive_more::Display)] -2214#[cfg_attr(test, derive(Arbitrary))] -2215#[display("PATH_STATUS_BACKUP path_id: {path_id} seq_no: {status_seq_no}")] -2216pub(crate) struct PathStatusBackup { -2217 pub(crate) path_id: PathId, -2218 pub(crate) status_seq_no: VarInt, -2219} -2220 -2221impl PathStatusBackup { -2222 const TYPE: FrameType = FrameType::PathStatusBackup; -2223 -2224 const fn get_type(&self) -> FrameType { -2225 FrameType::PathStatusBackup -2226 } -2227} -2228 -2229impl Encodable for PathStatusBackup { -2230 fn encode<W: BufMut>(&self, buf: &mut W) { -2231 buf.write(Self::TYPE); -2232 buf.write(self.path_id); -2233 buf.write(self.status_seq_no); -2234 } -2235} -2236 -2237impl Decodable for PathStatusBackup { -2238 fn decode<R: Buf>(bytes: &mut R) -> coding::Result<Self> { -2239 Ok(Self { -2240 path_id: bytes.get()?, -2241 status_seq_no: bytes.get()?, -2242 }) -2243 } -2244} -2245 -2246/* Nat traversal frames */ -2247 -2248/// Conjunction of the information contained in the add address frames -2249/// ([`FrameType::AddIpv4Address`], [`FrameType::AddIpv6Address`]). -2250#[derive(Debug, PartialEq, Eq, Copy, Clone, PartialOrd, Ord, derive_more::Display)] -2251#[display("{} seq_no: {seq_no} addr: {}", self.get_type(), self.socket_addr())] -2252#[cfg_attr(test, derive(Arbitrary))] -2253pub(crate) struct AddAddress { -2254 /// Monotonically increasing integer within the same connection -2255 // TODO(@divma): both assumed, the draft has no mention of this but it's standard -2256 pub(crate) seq_no: VarInt, -2257 /// Address to include in the known set -2258 pub(crate) ip: IpAddr, -2259 /// Port to use with this address -2260 pub(crate) port: u16, -2261} -2262 -2263// TODO(@divma): remove -2264#[allow(dead_code)] -2265impl AddAddress { -2266 /// Smallest number of bytes this type of frame is guaranteed to fit within. -2267 pub(crate) const SIZE_BOUND: usize = Self { -2268 ip: IpAddr::V6(std::net::Ipv6Addr::LOCALHOST), -2269 port: u16::MAX, -2270 seq_no: VarInt::MAX, -2271 } -2272 .size(); -2273 -2274 pub(crate) const fn new((ip, port): (IpAddr, u16), seq_no: VarInt) -> Self { -2275 Self { ip, port, seq_no } -2276 } -2277 -2278 /// Get the [`FrameType`] for this frame. -2279 const fn get_type(&self) -> FrameType { -2280 if self.ip.is_ipv6() { -2281 FrameType::AddIpv6Address -2282 } else { -2283 FrameType::AddIpv4Address -2284 } -2285 } -2286 -2287 /// Compute the number of bytes needed to encode the frame -2288 pub(crate) const fn size(&self) -> usize { -2289 let type_size = self.get_type().size(); -2290 let seq_no_bytes = self.seq_no.size(); -2291 let ip_bytes = if self.ip.is_ipv6() { 16 } else { 4 }; -2292 let port_bytes = 2; -2293 type_size + seq_no_bytes + ip_bytes + port_bytes -2294 } -2295 -2296 /// Read the frame contents from the buffer -2297 /// -2298 /// Should only be called when the frame type has been identified as -2299 /// [`FrameType::AddIpv4Address`] or [`FrameType::AddIpv6Address`]. -2300 pub(crate) fn read<R: Buf>(bytes: &mut R, is_ipv6: bool) -> coding::Result<Self> { -2301 let seq_no = bytes.get()?; -2302 let ip = if is_ipv6 { -2303 IpAddr::V6(bytes.get()?) -2304 } else { -2305 IpAddr::V4(bytes.get()?) -2306 }; -2307 let port = bytes.get()?; -2308 Ok(Self { seq_no, ip, port }) -2309 } -2310 -2311 /// Give the [`SocketAddr`] encoded in the frame -2312 pub(crate) fn socket_addr(&self) -> SocketAddr { -2313 self.ip_port().into() -2314 } -2315 -2316 pub(crate) fn ip_port(&self) -> (IpAddr, u16) { -2317 (self.ip, self.port) -2318 } -2319} -2320 -2321impl Encodable for AddAddress { -2322 fn encode<W: BufMut>(&self, buf: &mut W) { -2323 buf.write(self.get_type()); -2324 buf.write(self.seq_no); -2325 match self.ip { -2326 IpAddr::V4(ipv4_addr) => { -2327 buf.write(ipv4_addr); -2328 } -2329 IpAddr::V6(ipv6_addr) => { -2330 buf.write(ipv6_addr); -2331 } -2332 } -2333 buf.write::<u16>(self.port); -2334 } -2335} -2336 -2337/// Conjunction of the information contained in the reach out frames -2338/// ([`FrameType::ReachOutAtIpv4`], [`FrameType::ReachOutAtIpv6`]) -2339#[derive(Debug, PartialEq, Eq, Clone, derive_more::Display)] -2340#[display("REACH_OUT round: {round} local_addr: {}", self.socket_addr())] -2341#[cfg_attr(test, derive(Arbitrary))] -2342pub(crate) struct ReachOut { -2343 /// The sequence number of the NAT Traversal attempts -2344 pub(crate) round: VarInt, -2345 /// Address to use -2346 pub(crate) ip: IpAddr, -2347 /// Port to use with this address -2348 pub(crate) port: u16, -2349} -2350 -2351// TODO(@divma): remove -2352#[allow(dead_code)] -2353impl ReachOut { -2354 /// Smallest number of bytes this type of frame is guaranteed to fit within -2355 pub(crate) const SIZE_BOUND: usize = Self { -2356 round: VarInt::MAX, -2357 ip: IpAddr::V6(std::net::Ipv6Addr::LOCALHOST), -2358 port: u16::MAX, -2359 } -2360 .size(); -2361 -2362 pub(crate) const fn new(round: VarInt, (ip, port): (IpAddr, u16)) -> Self { -2363 Self { round, ip, port } -2364 } -2365 -2366 /// Get the [`FrameType`] for this frame -2367 pub(crate) const fn get_type(&self) -> FrameType { -2368 if self.ip.is_ipv6() { -2369 FrameType::ReachOutAtIpv6 -2370 } else { -2371 FrameType::ReachOutAtIpv4 -2372 } -2373 } -2374 -2375 /// Compute the number of bytes needed to encode the frame -2376 pub(crate) const fn size(&self) -> usize { -2377 let type_size = self.get_type().size(); -2378 let round_bytes = self.round.size(); -2379 let ip_bytes = if self.ip.is_ipv6() { 16 } else { 4 }; -2380 let port_bytes = 2; -2381 type_size + round_bytes + ip_bytes + port_bytes -2382 } -2383 -2384 /// Read the frame contents from the buffer -2385 /// -2386 /// Should only be called when the frame type has been identified as -2387 /// [`FrameType::ReachOutAtIpv4`] or [`FrameType::ReachOutAtIpv6`]. -2388 pub(crate) fn read<R: Buf>(bytes: &mut R, is_ipv6: bool) -> coding::Result<Self> { -2389 let round = bytes.get()?; -2390 let ip = if is_ipv6 { -2391 IpAddr::V6(bytes.get()?) -2392 } else { -2393 IpAddr::V4(bytes.get()?) -2394 }; -2395 let port = bytes.get()?; -2396 Ok(Self { round, ip, port }) -2397 } -2398 -2399 /// Give the [`SocketAddr`] encoded in the frame -2400 pub(crate) fn socket_addr(&self) -> SocketAddr { -2401 (self.ip, self.port).into() -2402 } -2403} -2404 -2405impl Encodable for ReachOut { -2406 fn encode<W: BufMut>(&self, buf: &mut W) { -2407 buf.write(self.get_type()); -2408 buf.write(self.round); -2409 match self.ip { -2410 IpAddr::V4(ipv4_addr) => { -2411 buf.write(ipv4_addr); -2412 } -2413 IpAddr::V6(ipv6_addr) => { -2414 buf.write(ipv6_addr); -2415 } -2416 } -2417 buf.write::<u16>(self.port); -2418 } -2419} -2420 -2421/// Frame signaling an address is no longer being advertised -2422#[derive(Debug, PartialEq, Eq, Copy, Clone, PartialOrd, Ord, derive_more::Display)] -2423#[cfg_attr(test, derive(Arbitrary))] -2424#[display("REMOVE_ADDRESS seq_no: {seq_no}")] -2425pub(crate) struct RemoveAddress { -2426 /// The sequence number of the address advertisement to be removed -2427 pub(crate) seq_no: VarInt, -2428} -2429 -2430// TODO(@divma): remove -2431#[allow(dead_code)] -2432impl RemoveAddress { -2433 /// [`FrameType`] of this frame -2434 pub(crate) const TYPE: FrameType = FrameType::RemoveAddress; -2435 -2436 /// Smallest number of bytes this type of frame is guaranteed to fit within -2437 pub(crate) const SIZE_BOUND: usize = Self::new(VarInt::MAX).size(); -2438 -2439 pub(crate) const fn new(seq_no: VarInt) -> Self { -2440 Self { seq_no } +2142 +2143 /// Reads the frame contents from the buffer. +2144 /// +2145 /// Should only be called when the frame type has been identified as +2146 /// [`FrameType::ObservedIpv4Addr`] or [`FrameType::ObservedIpv6Addr`]. +2147 pub(crate) fn read<R: Buf>(bytes: &mut R, is_ipv6: bool) -> coding::Result<Self> { +2148 let seq_no = bytes.get()?; +2149 let ip = if is_ipv6 { +2150 IpAddr::V6(bytes.get()?) +2151 } else { +2152 IpAddr::V4(bytes.get()?) +2153 }; +2154 let port = bytes.get()?; +2155 Ok(Self { seq_no, ip, port }) +2156 } +2157 +2158 /// Gives the [`SocketAddr`] reported in the frame. +2159 pub(crate) fn socket_addr(&self) -> SocketAddr { +2160 (self.ip, self.port).into() +2161 } +2162} +2163 +2164impl Encodable for ObservedAddr { +2165 fn encode<W: BufMut>(&self, buf: &mut W) { +2166 buf.write(self.get_type()); +2167 buf.write(self.seq_no); +2168 match self.ip { +2169 IpAddr::V4(ipv4_addr) => { +2170 buf.write(ipv4_addr); +2171 } +2172 IpAddr::V6(ipv6_addr) => { +2173 buf.write(ipv6_addr); +2174 } +2175 } +2176 buf.write::<u16>(self.port); +2177 } +2178} +2179 +2180/* Multipath <https://datatracker.ietf.org/doc/draft-ietf-quic-multipath/> */ +2181 +2182#[derive(Debug, PartialEq, Eq, derive_more::Display)] +2183#[cfg_attr(test, derive(Arbitrary))] +2184#[display("PATH_ABANDON path_id: {path_id}")] +2185pub(crate) struct PathAbandon { +2186 pub(crate) path_id: PathId, +2187 pub(crate) error_code: TransportErrorCode, +2188} +2189 +2190impl PathAbandon { +2191 pub(crate) const SIZE_BOUND: usize = FrameType::PathAbandon.size() + 8 + 8; +2192 +2193 const fn get_type(&self) -> FrameType { +2194 FrameType::PathAbandon +2195 } +2196} +2197 +2198impl Encodable for PathAbandon { +2199 fn encode<W: BufMut>(&self, buf: &mut W) { +2200 buf.write(FrameType::PathAbandon); +2201 buf.write(self.path_id); +2202 buf.write(self.error_code); +2203 } +2204} +2205 +2206impl Decodable for PathAbandon { +2207 fn decode<R: Buf>(bytes: &mut R) -> coding::Result<Self> { +2208 Ok(Self { +2209 path_id: bytes.get()?, +2210 error_code: bytes.get()?, +2211 }) +2212 } +2213} +2214 +2215#[derive(Debug, PartialEq, Eq, derive_more::Display)] +2216#[cfg_attr(test, derive(Arbitrary))] +2217#[display("PATH_STATUS_AVAILABLE path_id: {path_id} seq_no: {status_seq_no}")] +2218pub(crate) struct PathStatusAvailable { +2219 pub(crate) path_id: PathId, +2220 pub(crate) status_seq_no: VarInt, +2221} +2222 +2223impl PathStatusAvailable { +2224 const TYPE: FrameType = FrameType::PathStatusAvailable; +2225 pub(crate) const SIZE_BOUND: usize = FrameType::PathStatusAvailable.size() + 8 + 8; +2226 +2227 const fn get_type(&self) -> FrameType { +2228 FrameType::PathStatusAvailable +2229 } +2230} +2231 +2232impl Encodable for PathStatusAvailable { +2233 fn encode<W: BufMut>(&self, buf: &mut W) { +2234 buf.write(Self::TYPE); +2235 buf.write(self.path_id); +2236 buf.write(self.status_seq_no); +2237 } +2238} +2239 +2240impl Decodable for PathStatusAvailable { +2241 fn decode<R: Buf>(bytes: &mut R) -> coding::Result<Self> { +2242 Ok(Self { +2243 path_id: bytes.get()?, +2244 status_seq_no: bytes.get()?, +2245 }) +2246 } +2247} +2248 +2249#[derive(Debug, PartialEq, Eq, derive_more::Display)] +2250#[cfg_attr(test, derive(Arbitrary))] +2251#[display("PATH_STATUS_BACKUP path_id: {path_id} seq_no: {status_seq_no}")] +2252pub(crate) struct PathStatusBackup { +2253 pub(crate) path_id: PathId, +2254 pub(crate) status_seq_no: VarInt, +2255} +2256 +2257impl PathStatusBackup { +2258 const TYPE: FrameType = FrameType::PathStatusBackup; +2259 +2260 const fn get_type(&self) -> FrameType { +2261 FrameType::PathStatusBackup +2262 } +2263} +2264 +2265impl Encodable for PathStatusBackup { +2266 fn encode<W: BufMut>(&self, buf: &mut W) { +2267 buf.write(Self::TYPE); +2268 buf.write(self.path_id); +2269 buf.write(self.status_seq_no); +2270 } +2271} +2272 +2273impl Decodable for PathStatusBackup { +2274 fn decode<R: Buf>(bytes: &mut R) -> coding::Result<Self> { +2275 Ok(Self { +2276 path_id: bytes.get()?, +2277 status_seq_no: bytes.get()?, +2278 }) +2279 } +2280} +2281 +2282/* Nat traversal frames */ +2283 +2284/// Conjunction of the information contained in the add address frames +2285/// ([`FrameType::AddIpv4Address`], [`FrameType::AddIpv6Address`]). +2286#[derive(Debug, PartialEq, Eq, Copy, Clone, PartialOrd, Ord, derive_more::Display)] +2287#[display("{} seq_no: {seq_no} addr: {}", self.get_type(), self.socket_addr())] +2288#[cfg_attr(test, derive(Arbitrary))] +2289pub(crate) struct AddAddress { +2290 /// Monotonically increasing integer within the same connection +2291 // TODO(@divma): both assumed, the draft has no mention of this but it's standard +2292 pub(crate) seq_no: VarInt, +2293 /// Address to include in the known set +2294 pub(crate) ip: IpAddr, +2295 /// Port to use with this address +2296 pub(crate) port: u16, +2297} +2298 +2299// TODO(@divma): remove +2300#[allow(dead_code)] +2301impl AddAddress { +2302 /// Smallest number of bytes this type of frame is guaranteed to fit within. +2303 pub(crate) const SIZE_BOUND: usize = Self { +2304 ip: IpAddr::V6(std::net::Ipv6Addr::LOCALHOST), +2305 port: u16::MAX, +2306 seq_no: VarInt::MAX, +2307 } +2308 .size(); +2309 +2310 pub(crate) const fn new((ip, port): (IpAddr, u16), seq_no: VarInt) -> Self { +2311 Self { ip, port, seq_no } +2312 } +2313 +2314 /// Get the [`FrameType`] for this frame. +2315 const fn get_type(&self) -> FrameType { +2316 if self.ip.is_ipv6() { +2317 FrameType::AddIpv6Address +2318 } else { +2319 FrameType::AddIpv4Address +2320 } +2321 } +2322 +2323 /// Compute the number of bytes needed to encode the frame +2324 pub(crate) const fn size(&self) -> usize { +2325 let type_size = self.get_type().size(); +2326 let seq_no_bytes = self.seq_no.size(); +2327 let ip_bytes = if self.ip.is_ipv6() { 16 } else { 4 }; +2328 let port_bytes = 2; +2329 type_size + seq_no_bytes + ip_bytes + port_bytes +2330 } +2331 +2332 /// Read the frame contents from the buffer +2333 /// +2334 /// Should only be called when the frame type has been identified as +2335 /// [`FrameType::AddIpv4Address`] or [`FrameType::AddIpv6Address`]. +2336 pub(crate) fn read<R: Buf>(bytes: &mut R, is_ipv6: bool) -> coding::Result<Self> { +2337 let seq_no = bytes.get()?; +2338 let ip = if is_ipv6 { +2339 IpAddr::V6(bytes.get()?) +2340 } else { +2341 IpAddr::V4(bytes.get()?) +2342 }; +2343 let port = bytes.get()?; +2344 Ok(Self { seq_no, ip, port }) +2345 } +2346 +2347 /// Give the [`SocketAddr`] encoded in the frame +2348 pub(crate) fn socket_addr(&self) -> SocketAddr { +2349 self.ip_port().into() +2350 } +2351 +2352 pub(crate) fn ip_port(&self) -> (IpAddr, u16) { +2353 (self.ip, self.port) +2354 } +2355} +2356 +2357impl Encodable for AddAddress { +2358 fn encode<W: BufMut>(&self, buf: &mut W) { +2359 buf.write(self.get_type()); +2360 buf.write(self.seq_no); +2361 match self.ip { +2362 IpAddr::V4(ipv4_addr) => { +2363 buf.write(ipv4_addr); +2364 } +2365 IpAddr::V6(ipv6_addr) => { +2366 buf.write(ipv6_addr); +2367 } +2368 } +2369 buf.write::<u16>(self.port); +2370 } +2371} +2372 +2373/// Conjunction of the information contained in the reach out frames. +2374/// +2375/// ([`FrameType::ReachOutAtIpv4`], [`FrameType::ReachOutAtIpv6`]) +2376#[derive(Debug, PartialEq, Eq, Clone, derive_more::Display)] +2377#[display("REACH_OUT round: {round} local_addr: {}", self.socket_addr())] +2378#[cfg_attr(test, derive(Arbitrary))] +2379pub(crate) struct ReachOut { +2380 /// The sequence number of the NAT Traversal attempts +2381 pub(crate) round: VarInt, +2382 /// Address to use +2383 pub(crate) ip: IpAddr, +2384 /// Port to use with this address +2385 pub(crate) port: u16, +2386} +2387 +2388impl ReachOut { +2389 /// Get the [`FrameType`] for this frame +2390 pub(crate) const fn get_type(&self) -> FrameType { +2391 if self.ip.is_ipv6() { +2392 FrameType::ReachOutAtIpv6 +2393 } else { +2394 FrameType::ReachOutAtIpv4 +2395 } +2396 } +2397 +2398 /// Compute the number of bytes needed to encode the frame +2399 pub(crate) const fn size(&self) -> usize { +2400 let type_size = self.get_type().size(); +2401 let round_bytes = self.round.size(); +2402 let ip_bytes = if self.ip.is_ipv6() { 16 } else { 4 }; +2403 let port_bytes = 2; +2404 type_size + round_bytes + ip_bytes + port_bytes +2405 } +2406 +2407 /// Read the frame contents from the buffer +2408 /// +2409 /// Should only be called when the frame type has been identified as +2410 /// [`FrameType::ReachOutAtIpv4`] or [`FrameType::ReachOutAtIpv6`]. +2411 pub(crate) fn read<R: Buf>(bytes: &mut R, is_ipv6: bool) -> coding::Result<Self> { +2412 let round = bytes.get()?; +2413 let ip = if is_ipv6 { +2414 IpAddr::V6(bytes.get()?) +2415 } else { +2416 IpAddr::V4(bytes.get()?) +2417 }; +2418 let port = bytes.get()?; +2419 Ok(Self { round, ip, port }) +2420 } +2421 +2422 /// Give the [`SocketAddr`] encoded in the frame +2423 pub(crate) fn socket_addr(&self) -> SocketAddr { +2424 (self.ip, self.port).into() +2425 } +2426} +2427 +2428impl Encodable for ReachOut { +2429 fn encode<W: BufMut>(&self, buf: &mut W) { +2430 buf.write(self.get_type()); +2431 buf.write(self.round); +2432 match self.ip { +2433 IpAddr::V4(ipv4_addr) => { +2434 buf.write(ipv4_addr); +2435 } +2436 IpAddr::V6(ipv6_addr) => { +2437 buf.write(ipv6_addr); +2438 } +2439 } +2440 buf.write::<u16>(self.port); 2441 } -2442 -2443 /// Compute the number of bytes needed to encode the frame -2444 pub(crate) const fn size(&self) -> usize { -2445 let type_size = Self::TYPE.size(); -2446 let seq_no_bytes = self.seq_no.size(); -2447 type_size + seq_no_bytes -2448 } -2449 -2450 /// Read the frame contents from the buffer -2451 /// -2452 /// Should only be called when the frame type has been identified as -2453 /// [`FrameType::RemoveAddress`]. -2454 pub(crate) fn read<R: Buf>(bytes: &mut R) -> coding::Result<Self> { -2455 Ok(Self { -2456 seq_no: bytes.get()?, -2457 }) -2458 } -2459 -2460 const fn get_type(&self) -> FrameType { -2461 FrameType::RemoveAddress -2462 } -2463} -2464 -2465impl Encodable for RemoveAddress { -2466 fn encode<W: BufMut>(&self, buf: &mut W) { -2467 buf.write(Self::TYPE); -2468 buf.write(self.seq_no); -2469 } -2470} -2471 -2472/// Helper struct for display implementations. -2473// NOTE: Due to lifetimes in fmt::Arguments it's not possible to make this a simple function that -2474// avoids allocations. -2475struct DisplayOption<T: Display> { -2476 field_name: &'static str, -2477 op: Option<T>, -2478} -2479 -2480impl<T: Display> DisplayOption<T> { -2481 fn new(field_name: &'static str, op: Option<T>) -> Self { -2482 Self { field_name, op } -2483 } -2484} -2485 -2486impl<T: Display> Display for DisplayOption<T> { -2487 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -2488 if let Some(x) = self.op.as_ref() { -2489 write!(f, "{}: {x}", self.field_name) -2490 } else { -2491 fmt::Result::Ok(()) -2492 } -2493 } -2494} -2495 -2496#[cfg(test)] -2497mod test { -2498 use super::*; -2499 use crate::coding::Encodable; -2500 use assert_matches::assert_matches; -2501 -2502 #[test] -2503 fn frame_type() { -2504 assert_eq!( -2505 FrameType::try_from(FrameType::Padding.to_u64()), -2506 Ok(FrameType::Padding), -2507 ); +2442} +2443 +2444/// Frame signaling an address is no longer being advertised +2445#[derive(Debug, PartialEq, Eq, Copy, Clone, PartialOrd, Ord, derive_more::Display)] +2446#[cfg_attr(test, derive(Arbitrary))] +2447#[display("REMOVE_ADDRESS seq_no: {seq_no}")] +2448pub(crate) struct RemoveAddress { +2449 /// The sequence number of the address advertisement to be removed +2450 pub(crate) seq_no: VarInt, +2451} +2452 +2453// TODO(@divma): remove +2454#[allow(dead_code)] +2455impl RemoveAddress { +2456 /// [`FrameType`] of this frame +2457 pub(crate) const TYPE: FrameType = FrameType::RemoveAddress; +2458 +2459 /// Smallest number of bytes this type of frame is guaranteed to fit within +2460 pub(crate) const SIZE_BOUND: usize = Self::new(VarInt::MAX).size(); +2461 +2462 pub(crate) const fn new(seq_no: VarInt) -> Self { +2463 Self { seq_no } +2464 } +2465 +2466 /// Compute the number of bytes needed to encode the frame +2467 pub(crate) const fn size(&self) -> usize { +2468 let type_size = Self::TYPE.size(); +2469 let seq_no_bytes = self.seq_no.size(); +2470 type_size + seq_no_bytes +2471 } +2472 +2473 /// Read the frame contents from the buffer +2474 /// +2475 /// Should only be called when the frame type has been identified as +2476 /// [`FrameType::RemoveAddress`]. +2477 pub(crate) fn read<R: Buf>(bytes: &mut R) -> coding::Result<Self> { +2478 Ok(Self { +2479 seq_no: bytes.get()?, +2480 }) +2481 } +2482 +2483 const fn get_type(&self) -> FrameType { +2484 FrameType::RemoveAddress +2485 } +2486} +2487 +2488impl Encodable for RemoveAddress { +2489 fn encode<W: BufMut>(&self, buf: &mut W) { +2490 buf.write(Self::TYPE); +2491 buf.write(self.seq_no); +2492 } +2493} +2494 +2495/// Helper struct for display implementations. +2496// NOTE: Due to lifetimes in fmt::Arguments it's not possible to make this a simple function that +2497// avoids allocations. +2498struct DisplayOption<T: Display> { +2499 field_name: &'static str, +2500 op: Option<T>, +2501} +2502 +2503impl<T: Display> DisplayOption<T> { +2504 fn new(field_name: &'static str, op: Option<T>) -> Self { +2505 Self { field_name, op } +2506 } +2507} 2508 -2509 assert_eq!( -2510 FrameType::try_from(FrameType::Datagram(DatagramInfo(0x30)).to_u64()), -2511 Ok(FrameType::Datagram(DatagramInfo(0x30))), -2512 ); -2513 -2514 assert_eq!( -2515 FrameType::try_from(FrameType::Stream(StreamInfo(0x08)).to_u64()), -2516 Ok(FrameType::Stream(StreamInfo(0x08))), -2517 ); -2518 } -2519 -2520 #[track_caller] -2521 fn frames(buf: Vec<u8>) -> Vec<Frame> { -2522 Iter::new(Bytes::from(buf)) -2523 .unwrap() -2524 .collect::<Result<Vec<_>, _>>() -2525 .unwrap() -2526 } -2527 -2528 #[test] -2529 fn ack_coding() { -2530 const PACKETS: &[u64] = &[1, 2, 3, 5, 10, 11, 14]; -2531 let mut ranges = ArrayRangeSet::new(); -2532 for &packet in PACKETS { -2533 ranges.insert(packet..packet + 1); -2534 } -2535 let mut buf = Vec::new(); -2536 const ECN: EcnCounts = EcnCounts { -2537 ect0: 42, -2538 ect1: 24, -2539 ce: 12, -2540 }; -2541 Ack::encoder(42, &ranges, Some(&ECN)).encode(&mut buf); -2542 let frames = frames(buf); -2543 assert_eq!(frames.len(), 1); -2544 match frames[0] { -2545 Frame::Ack(ref ack) => { -2546 let mut packets = ack.iter().flatten().collect::<Vec<_>>(); -2547 packets.sort_unstable(); -2548 assert_eq!(&packets[..], PACKETS); -2549 assert_eq!(ack.ecn, Some(ECN)); -2550 } -2551 ref x => panic!("incorrect frame {x:?}"), -2552 } -2553 } -2554 -2555 #[test] -2556 #[allow(clippy::range_plus_one)] -2557 fn path_ack_coding_with_ecn() { -2558 const PACKETS: &[u64] = &[1, 2, 3, 5, 10, 11, 14]; -2559 let mut ranges = ArrayRangeSet::new(); -2560 for &packet in PACKETS { -2561 ranges.insert(packet..packet + 1); -2562 } -2563 let mut buf = Vec::new(); -2564 const ECN: EcnCounts = EcnCounts { -2565 ect0: 42, -2566 ect1: 24, -2567 ce: 12, -2568 }; -2569 const PATH_ID: PathId = PathId::MAX; -2570 PathAck::encoder(PATH_ID, 42, &ranges, Some(&ECN)).encode(&mut buf); -2571 let frames = frames(buf); -2572 assert_eq!(frames.len(), 1); -2573 match frames[0] { -2574 Frame::PathAck(ref ack) => { -2575 assert_eq!(ack.path_id, PATH_ID); -2576 let mut packets = ack.ranges.iter().flatten().collect::<Vec<_>>(); -2577 packets.sort_unstable(); -2578 assert_eq!(&packets[..], PACKETS); -2579 assert_eq!(ack.ecn, Some(ECN)); -2580 } -2581 ref x => panic!("incorrect frame {x:?}"), -2582 } -2583 } -2584 -2585 #[test] -2586 #[allow(clippy::range_plus_one)] -2587 fn path_ack_coding_no_ecn() { -2588 const PACKETS: &[u64] = &[1, 2, 3, 5, 10, 11, 14]; -2589 let mut ranges = ArrayRangeSet::new(); -2590 for &packet in PACKETS { -2591 ranges.insert(packet..packet + 1); -2592 } -2593 let mut buf = Vec::new(); -2594 const PATH_ID: PathId = PathId::MAX; -2595 PathAck::encoder(PATH_ID, 42, &ranges, None).encode(&mut buf); -2596 let frames = frames(buf); -2597 assert_eq!(frames.len(), 1); -2598 match frames[0] { -2599 Frame::PathAck(ref ack) => { -2600 assert_eq!(ack.path_id, PATH_ID); -2601 let mut packets = ack.ranges.iter().flatten().collect::<Vec<_>>(); -2602 packets.sort_unstable(); -2603 assert_eq!(&packets[..], PACKETS); -2604 assert_eq!(ack.ecn, None); -2605 } -2606 ref x => panic!("incorrect frame {x:?}"), -2607 } -2608 } -2609 -2610 #[test] -2611 fn ack_frequency_coding() { -2612 let mut buf = Vec::new(); -2613 let original = AckFrequency { -2614 sequence: VarInt(42), -2615 ack_eliciting_threshold: VarInt(20), -2616 request_max_ack_delay: VarInt(50_000), -2617 reordering_threshold: VarInt(1), -2618 }; -2619 original.encode(&mut buf); -2620 let frames = frames(buf); -2621 assert_eq!(frames.len(), 1); -2622 match &frames[0] { -2623 Frame::AckFrequency(decoded) => assert_eq!(decoded, &original), -2624 x => panic!("incorrect frame {x:?}"), -2625 } -2626 } -2627 -2628 #[test] -2629 fn immediate_ack_coding() { -2630 let mut buf = Vec::new(); -2631 FrameType::ImmediateAck.encode(&mut buf); -2632 let frames = frames(buf); -2633 assert_eq!(frames.len(), 1); -2634 assert_matches!(&frames[0], Frame::ImmediateAck); -2635 } -2636 -2637 /// Test that encoding and decoding [`ObservedAddr`] produces the same result. -2638 #[test] -2639 fn test_observed_addr_roundrip() { -2640 let observed_addr = ObservedAddr { -2641 seq_no: VarInt(42), -2642 ip: std::net::Ipv4Addr::LOCALHOST.into(), -2643 port: 4242, -2644 }; -2645 let mut buf = Vec::with_capacity(observed_addr.size()); -2646 observed_addr.encode(&mut buf); -2647 -2648 assert_eq!( -2649 observed_addr.size(), -2650 buf.len(), -2651 "expected written bytes and actual size differ" -2652 ); -2653 -2654 let mut decoded = frames(buf); -2655 assert_eq!(decoded.len(), 1); -2656 match decoded.pop().expect("non empty") { -2657 Frame::ObservedAddr(decoded) => assert_eq!(decoded, observed_addr), -2658 x => panic!("incorrect frame {x:?}"), -2659 } -2660 } -2661 -2662 #[test] -2663 fn test_path_abandon_roundtrip() { -2664 let abandon = PathAbandon { -2665 path_id: PathId(42), -2666 error_code: TransportErrorCode::NO_ERROR, +2509impl<T: Display> Display for DisplayOption<T> { +2510 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +2511 if let Some(x) = self.op.as_ref() { +2512 write!(f, "{}: {x}", self.field_name) +2513 } else { +2514 fmt::Result::Ok(()) +2515 } +2516 } +2517} +2518 +2519#[cfg(test)] +2520mod test { +2521 use super::*; +2522 use crate::coding::Encodable; +2523 use assert_matches::assert_matches; +2524 +2525 #[test] +2526 fn frame_type() { +2527 assert_eq!( +2528 FrameType::try_from(FrameType::Padding.to_u64()), +2529 Ok(FrameType::Padding), +2530 ); +2531 +2532 assert_eq!( +2533 FrameType::try_from(FrameType::Datagram(DatagramInfo(0x30)).to_u64()), +2534 Ok(FrameType::Datagram(DatagramInfo(0x30))), +2535 ); +2536 +2537 assert_eq!( +2538 FrameType::try_from(FrameType::Stream(StreamInfo(0x08)).to_u64()), +2539 Ok(FrameType::Stream(StreamInfo(0x08))), +2540 ); +2541 } +2542 +2543 #[track_caller] +2544 fn frames(buf: Vec<u8>) -> Vec<Frame> { +2545 Iter::new(Bytes::from(buf)) +2546 .unwrap() +2547 .collect::<Result<Vec<_>, _>>() +2548 .unwrap() +2549 } +2550 +2551 #[test] +2552 fn ack_coding() { +2553 const PACKETS: &[u64] = &[1, 2, 3, 5, 10, 11, 14]; +2554 let mut ranges = ArrayRangeSet::new(); +2555 for &packet in PACKETS { +2556 ranges.insert(packet..packet + 1); +2557 } +2558 let mut buf = Vec::new(); +2559 const ECN: EcnCounts = EcnCounts { +2560 ect0: 42, +2561 ect1: 24, +2562 ce: 12, +2563 }; +2564 Ack::encoder(42, &ranges, Some(&ECN)).encode(&mut buf); +2565 let frames = frames(buf); +2566 assert_eq!(frames.len(), 1); +2567 match frames[0] { +2568 Frame::Ack(ref ack) => { +2569 let mut packets = ack.iter().flatten().collect::<Vec<_>>(); +2570 packets.sort_unstable(); +2571 assert_eq!(&packets[..], PACKETS); +2572 assert_eq!(ack.ecn, Some(ECN)); +2573 } +2574 ref x => panic!("incorrect frame {x:?}"), +2575 } +2576 } +2577 +2578 #[test] +2579 #[allow(clippy::range_plus_one)] +2580 fn path_ack_coding_with_ecn() { +2581 const PACKETS: &[u64] = &[1, 2, 3, 5, 10, 11, 14]; +2582 let mut ranges = ArrayRangeSet::new(); +2583 for &packet in PACKETS { +2584 ranges.insert(packet..packet + 1); +2585 } +2586 let mut buf = Vec::new(); +2587 const ECN: EcnCounts = EcnCounts { +2588 ect0: 42, +2589 ect1: 24, +2590 ce: 12, +2591 }; +2592 const PATH_ID: PathId = PathId::MAX; +2593 PathAck::encoder(PATH_ID, 42, &ranges, Some(&ECN)).encode(&mut buf); +2594 let frames = frames(buf); +2595 assert_eq!(frames.len(), 1); +2596 match frames[0] { +2597 Frame::PathAck(ref ack) => { +2598 assert_eq!(ack.path_id, PATH_ID); +2599 let mut packets = ack.ranges.iter().flatten().collect::<Vec<_>>(); +2600 packets.sort_unstable(); +2601 assert_eq!(&packets[..], PACKETS); +2602 assert_eq!(ack.ecn, Some(ECN)); +2603 } +2604 ref x => panic!("incorrect frame {x:?}"), +2605 } +2606 } +2607 +2608 #[test] +2609 #[allow(clippy::range_plus_one)] +2610 fn path_ack_coding_no_ecn() { +2611 const PACKETS: &[u64] = &[1, 2, 3, 5, 10, 11, 14]; +2612 let mut ranges = ArrayRangeSet::new(); +2613 for &packet in PACKETS { +2614 ranges.insert(packet..packet + 1); +2615 } +2616 let mut buf = Vec::new(); +2617 const PATH_ID: PathId = PathId::MAX; +2618 PathAck::encoder(PATH_ID, 42, &ranges, None).encode(&mut buf); +2619 let frames = frames(buf); +2620 assert_eq!(frames.len(), 1); +2621 match frames[0] { +2622 Frame::PathAck(ref ack) => { +2623 assert_eq!(ack.path_id, PATH_ID); +2624 let mut packets = ack.ranges.iter().flatten().collect::<Vec<_>>(); +2625 packets.sort_unstable(); +2626 assert_eq!(&packets[..], PACKETS); +2627 assert_eq!(ack.ecn, None); +2628 } +2629 ref x => panic!("incorrect frame {x:?}"), +2630 } +2631 } +2632 +2633 #[test] +2634 fn ack_frequency_coding() { +2635 let mut buf = Vec::new(); +2636 let original = AckFrequency { +2637 sequence: VarInt(42), +2638 ack_eliciting_threshold: VarInt(20), +2639 request_max_ack_delay: VarInt(50_000), +2640 reordering_threshold: VarInt(1), +2641 }; +2642 original.encode(&mut buf); +2643 let frames = frames(buf); +2644 assert_eq!(frames.len(), 1); +2645 match &frames[0] { +2646 Frame::AckFrequency(decoded) => assert_eq!(decoded, &original), +2647 x => panic!("incorrect frame {x:?}"), +2648 } +2649 } +2650 +2651 #[test] +2652 fn immediate_ack_coding() { +2653 let mut buf = Vec::new(); +2654 FrameType::ImmediateAck.encode(&mut buf); +2655 let frames = frames(buf); +2656 assert_eq!(frames.len(), 1); +2657 assert_matches!(&frames[0], Frame::ImmediateAck); +2658 } +2659 +2660 /// Test that encoding and decoding [`ObservedAddr`] produces the same result. +2661 #[test] +2662 fn test_observed_addr_roundrip() { +2663 let observed_addr = ObservedAddr { +2664 seq_no: VarInt(42), +2665 ip: std::net::Ipv4Addr::LOCALHOST.into(), +2666 port: 4242, 2667 }; -2668 let mut buf = Vec::new(); -2669 abandon.encode(&mut buf); +2668 let mut buf = Vec::with_capacity(observed_addr.size()); +2669 observed_addr.encode(&mut buf); 2670 -2671 let mut decoded = frames(buf); -2672 assert_eq!(decoded.len(), 1); -2673 match decoded.pop().expect("non empty") { -2674 Frame::PathAbandon(decoded) => assert_eq!(decoded, abandon), -2675 x => panic!("incorrect frame {x:?}"), -2676 } -2677 } -2678 -2679 #[test] -2680 fn test_path_status_available_roundtrip() { -2681 let path_status_available = PathStatusAvailable { -2682 path_id: PathId(42), -2683 status_seq_no: VarInt(73), -2684 }; -2685 let mut buf = Vec::new(); -2686 path_status_available.encode(&mut buf); -2687 -2688 let mut decoded = frames(buf); -2689 assert_eq!(decoded.len(), 1); -2690 match decoded.pop().expect("non empty") { -2691 Frame::PathStatusAvailable(decoded) => assert_eq!(decoded, path_status_available), -2692 x => panic!("incorrect frame {x:?}"), -2693 } -2694 } -2695 -2696 #[test] -2697 fn test_path_status_backup_roundtrip() { -2698 let path_status_backup = PathStatusBackup { -2699 path_id: PathId(42), -2700 status_seq_no: VarInt(73), -2701 }; -2702 let mut buf = Vec::new(); -2703 path_status_backup.encode(&mut buf); -2704 -2705 let mut decoded = frames(buf); -2706 assert_eq!(decoded.len(), 1); -2707 match decoded.pop().expect("non empty") { -2708 Frame::PathStatusBackup(decoded) => assert_eq!(decoded, path_status_backup), -2709 x => panic!("incorrect frame {x:?}"), -2710 } -2711 } -2712 -2713 #[test] -2714 fn test_path_new_connection_id_roundtrip() { -2715 let cid = NewConnectionId { -2716 path_id: Some(PathId(22)), -2717 sequence: 31, -2718 retire_prior_to: 13, -2719 id: ConnectionId::new(&[0xAB; 8]), -2720 reset_token: ResetToken::from([0xCD; crate::RESET_TOKEN_SIZE]), -2721 }; -2722 let mut buf = Vec::new(); -2723 cid.encode(&mut buf); -2724 -2725 let mut decoded = frames(buf); -2726 assert_eq!(decoded.len(), 1); -2727 match decoded.pop().expect("non empty") { -2728 Frame::NewConnectionId(decoded) => assert_eq!(decoded, cid), -2729 x => panic!("incorrect frame {x:?}"), -2730 } -2731 } -2732 -2733 #[test] -2734 fn test_path_retire_connection_id_roundtrip() { -2735 let retire_cid = RetireConnectionId { -2736 path_id: Some(PathId(22)), -2737 sequence: 31, -2738 }; -2739 let mut buf = Vec::new(); -2740 retire_cid.encode(&mut buf); -2741 -2742 let mut decoded = frames(buf); -2743 assert_eq!(decoded.len(), 1); -2744 match decoded.pop().expect("non empty") { -2745 Frame::RetireConnectionId(decoded) => assert_eq!(decoded, retire_cid), -2746 x => panic!("incorrect frame {x:?}"), -2747 } -2748 } -2749 -2750 #[test] -2751 fn test_paths_blocked_path_cids_blocked_roundtrip() { -2752 let mut buf = Vec::new(); -2753 -2754 let frame0 = PathsBlocked(PathId(22)); -2755 frame0.encode(&mut buf); -2756 let frame1 = PathCidsBlocked { -2757 path_id: PathId(23), -2758 next_seq: VarInt(32), -2759 }; -2760 frame1.encode(&mut buf); -2761 -2762 let mut decoded = frames(buf); -2763 assert_eq!(decoded.len(), 2); -2764 match decoded.pop().expect("non empty") { -2765 Frame::PathCidsBlocked(decoded) => assert_eq!(decoded, frame1), -2766 x => panic!("incorrect frame {x:?}"), -2767 } -2768 match decoded.pop().expect("non empty") { -2769 Frame::PathsBlocked(decoded) => assert_eq!(decoded, frame0), -2770 x => panic!("incorrect frame {x:?}"), -2771 } -2772 } -2773 -2774 /// Test that encoding and decoding [`AddAddress`] produces the same result -2775 #[test] -2776 fn test_add_address_roundrip() { -2777 let add_address = AddAddress { -2778 seq_no: VarInt(42), -2779 ip: std::net::Ipv4Addr::LOCALHOST.into(), -2780 port: 4242, -2781 }; -2782 let mut buf = Vec::with_capacity(add_address.size()); -2783 add_address.encode(&mut buf); +2671 assert_eq!( +2672 observed_addr.size(), +2673 buf.len(), +2674 "expected written bytes and actual size differ" +2675 ); +2676 +2677 let mut decoded = frames(buf); +2678 assert_eq!(decoded.len(), 1); +2679 match decoded.pop().expect("non empty") { +2680 Frame::ObservedAddr(decoded) => assert_eq!(decoded, observed_addr), +2681 x => panic!("incorrect frame {x:?}"), +2682 } +2683 } +2684 +2685 #[test] +2686 fn test_path_abandon_roundtrip() { +2687 let abandon = PathAbandon { +2688 path_id: PathId(42), +2689 error_code: TransportErrorCode::NO_ERROR, +2690 }; +2691 let mut buf = Vec::new(); +2692 abandon.encode(&mut buf); +2693 +2694 let mut decoded = frames(buf); +2695 assert_eq!(decoded.len(), 1); +2696 match decoded.pop().expect("non empty") { +2697 Frame::PathAbandon(decoded) => assert_eq!(decoded, abandon), +2698 x => panic!("incorrect frame {x:?}"), +2699 } +2700 } +2701 +2702 #[test] +2703 fn test_path_status_available_roundtrip() { +2704 let path_status_available = PathStatusAvailable { +2705 path_id: PathId(42), +2706 status_seq_no: VarInt(73), +2707 }; +2708 let mut buf = Vec::new(); +2709 path_status_available.encode(&mut buf); +2710 +2711 let mut decoded = frames(buf); +2712 assert_eq!(decoded.len(), 1); +2713 match decoded.pop().expect("non empty") { +2714 Frame::PathStatusAvailable(decoded) => assert_eq!(decoded, path_status_available), +2715 x => panic!("incorrect frame {x:?}"), +2716 } +2717 } +2718 +2719 #[test] +2720 fn test_path_status_backup_roundtrip() { +2721 let path_status_backup = PathStatusBackup { +2722 path_id: PathId(42), +2723 status_seq_no: VarInt(73), +2724 }; +2725 let mut buf = Vec::new(); +2726 path_status_backup.encode(&mut buf); +2727 +2728 let mut decoded = frames(buf); +2729 assert_eq!(decoded.len(), 1); +2730 match decoded.pop().expect("non empty") { +2731 Frame::PathStatusBackup(decoded) => assert_eq!(decoded, path_status_backup), +2732 x => panic!("incorrect frame {x:?}"), +2733 } +2734 } +2735 +2736 #[test] +2737 fn test_path_new_connection_id_roundtrip() { +2738 let cid = NewConnectionId { +2739 path_id: Some(PathId(22)), +2740 sequence: 31, +2741 retire_prior_to: 13, +2742 id: ConnectionId::new(&[0xAB; 8]), +2743 reset_token: ResetToken::from([0xCD; crate::RESET_TOKEN_SIZE]), +2744 }; +2745 let mut buf = Vec::new(); +2746 cid.encode(&mut buf); +2747 +2748 let mut decoded = frames(buf); +2749 assert_eq!(decoded.len(), 1); +2750 match decoded.pop().expect("non empty") { +2751 Frame::NewConnectionId(decoded) => assert_eq!(decoded, cid), +2752 x => panic!("incorrect frame {x:?}"), +2753 } +2754 } +2755 +2756 #[test] +2757 fn test_path_retire_connection_id_roundtrip() { +2758 let retire_cid = RetireConnectionId { +2759 path_id: Some(PathId(22)), +2760 sequence: 31, +2761 }; +2762 let mut buf = Vec::new(); +2763 retire_cid.encode(&mut buf); +2764 +2765 let mut decoded = frames(buf); +2766 assert_eq!(decoded.len(), 1); +2767 match decoded.pop().expect("non empty") { +2768 Frame::RetireConnectionId(decoded) => assert_eq!(decoded, retire_cid), +2769 x => panic!("incorrect frame {x:?}"), +2770 } +2771 } +2772 +2773 #[test] +2774 fn test_paths_blocked_path_cids_blocked_roundtrip() { +2775 let mut buf = Vec::new(); +2776 +2777 let frame0 = PathsBlocked(PathId(22)); +2778 frame0.encode(&mut buf); +2779 let frame1 = PathCidsBlocked { +2780 path_id: PathId(23), +2781 next_seq: VarInt(32), +2782 }; +2783 frame1.encode(&mut buf); 2784 -2785 assert_eq!( -2786 add_address.size(), -2787 buf.len(), -2788 "expected written bytes and actual size differ" -2789 ); -2790 -2791 let mut decoded = frames(buf); -2792 assert_eq!(decoded.len(), 1); -2793 match decoded.pop().expect("non empty") { -2794 Frame::AddAddress(decoded) => assert_eq!(decoded, add_address), -2795 x => panic!("incorrect frame {x:?}"), -2796 } -2797 } -2798 -2799 /// Test that encoding and decoding [`AddAddress`] produces the same result -2800 #[test] -2801 fn test_reach_out_roundrip() { -2802 let reach_out = ReachOut { -2803 round: VarInt(42), -2804 ip: std::net::Ipv6Addr::LOCALHOST.into(), -2805 port: 4242, -2806 }; -2807 let mut buf = Vec::with_capacity(reach_out.size()); -2808 reach_out.encode(&mut buf); -2809 -2810 assert_eq!( -2811 reach_out.size(), -2812 buf.len(), -2813 "expected written bytes and actual size differ" -2814 ); -2815 -2816 let mut decoded = frames(buf); -2817 assert_eq!(decoded.len(), 1); -2818 match decoded.pop().expect("non empty") { -2819 Frame::ReachOut(decoded) => assert_eq!(decoded, reach_out), -2820 x => panic!("incorrect frame {x:?}"), -2821 } -2822 } -2823 -2824 /// Test that encoding and decoding [`RemoveAddress`] produces the same result -2825 #[test] -2826 fn test_remove_address_roundrip() { -2827 let remove_addr = RemoveAddress::new(VarInt(10)); -2828 let mut buf = Vec::with_capacity(remove_addr.size()); -2829 remove_addr.encode(&mut buf); -2830 -2831 assert_eq!( -2832 remove_addr.size(), -2833 buf.len(), -2834 "expected written bytes and actual size differ" -2835 ); -2836 -2837 let mut decoded = frames(buf); -2838 assert_eq!(decoded.len(), 1); -2839 match decoded.pop().expect("non empty") { -2840 Frame::RemoveAddress(decoded) => assert_eq!(decoded, remove_addr), -2841 x => panic!("incorrect frame {x:?}"), -2842 } -2843 } -2844} \ No newline at end of file +2785 let mut decoded = frames(buf); +2786 assert_eq!(decoded.len(), 2); +2787 match decoded.pop().expect("non empty") { +2788 Frame::PathCidsBlocked(decoded) => assert_eq!(decoded, frame1), +2789 x => panic!("incorrect frame {x:?}"), +2790 } +2791 match decoded.pop().expect("non empty") { +2792 Frame::PathsBlocked(decoded) => assert_eq!(decoded, frame0), +2793 x => panic!("incorrect frame {x:?}"), +2794 } +2795 } +2796 +2797 /// Test that encoding and decoding [`AddAddress`] produces the same result +2798 #[test] +2799 fn test_add_address_roundrip() { +2800 let add_address = AddAddress { +2801 seq_no: VarInt(42), +2802 ip: std::net::Ipv4Addr::LOCALHOST.into(), +2803 port: 4242, +2804 }; +2805 let mut buf = Vec::with_capacity(add_address.size()); +2806 add_address.encode(&mut buf); +2807 +2808 assert_eq!( +2809 add_address.size(), +2810 buf.len(), +2811 "expected written bytes and actual size differ" +2812 ); +2813 +2814 let mut decoded = frames(buf); +2815 assert_eq!(decoded.len(), 1); +2816 match decoded.pop().expect("non empty") { +2817 Frame::AddAddress(decoded) => assert_eq!(decoded, add_address), +2818 x => panic!("incorrect frame {x:?}"), +2819 } +2820 } +2821 +2822 /// Test that encoding and decoding [`AddAddress`] produces the same result +2823 #[test] +2824 fn test_reach_out_roundrip() { +2825 let reach_out = ReachOut { +2826 round: VarInt(42), +2827 ip: std::net::Ipv6Addr::LOCALHOST.into(), +2828 port: 4242, +2829 }; +2830 let mut buf = Vec::with_capacity(reach_out.size()); +2831 reach_out.encode(&mut buf); +2832 +2833 assert_eq!( +2834 reach_out.size(), +2835 buf.len(), +2836 "expected written bytes and actual size differ" +2837 ); +2838 +2839 let mut decoded = frames(buf); +2840 assert_eq!(decoded.len(), 1); +2841 match decoded.pop().expect("non empty") { +2842 Frame::ReachOut(decoded) => assert_eq!(decoded, reach_out), +2843 x => panic!("incorrect frame {x:?}"), +2844 } +2845 } +2846 +2847 /// Test that encoding and decoding [`RemoveAddress`] produces the same result +2848 #[test] +2849 fn test_remove_address_roundrip() { +2850 let remove_addr = RemoveAddress::new(VarInt(10)); +2851 let mut buf = Vec::with_capacity(remove_addr.size()); +2852 remove_addr.encode(&mut buf); +2853 +2854 assert_eq!( +2855 remove_addr.size(), +2856 buf.len(), +2857 "expected written bytes and actual size differ" +2858 ); +2859 +2860 let mut decoded = frames(buf); +2861 assert_eq!(decoded.len(), 1); +2862 match decoded.pop().expect("non empty") { +2863 Frame::RemoveAddress(decoded) => assert_eq!(decoded, remove_addr), +2864 x => panic!("incorrect frame {x:?}"), +2865 } +2866 } +2867} \ No newline at end of file diff --git a/pr/451/docs/src/noq_proto/lib.rs.html b/pr/451/docs/src/noq_proto/lib.rs.html index 686d69c0f..5d4afe9d1 100644 --- a/pr/451/docs/src/noq_proto/lib.rs.html +++ b/pr/451/docs/src/noq_proto/lib.rs.html @@ -47,7 +47,7 @@ 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, Written, +50 SetPathStatusError, ShouldTransmit, StreamEvent, Streams, UdpStats, WriteError, 51}; 52#[cfg(test)] 53use test_strategy::Arbitrary; @@ -56,422 +56,420 @@ 56
    pub use rustls; 57 58mod config; -59#[cfg(doc)] -60pub use config::DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED; -61pub use config::{ -62 AckFrequencyConfig, ClientConfig, ConfigError, EndpointConfig, IdleTimeout, MtuDiscoveryConfig, -63 ServerConfig, StdSystemTime, TimeSource, TransportConfig, ValidationTokenConfig, -64}; -65#[cfg(feature = "qlog")] -66pub use config::{QlogConfig, QlogFactory, QlogFileFactory}; +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 -68pub mod crypto; -69 -70mod frame; -71pub use crate::frame::{ -72 ApplicationClose, ConnectionClose, Datagram, DatagramInfo, FrameType, InvalidFrameId, -73 MaybeFrame, StreamInfo, -74}; -75use crate::{ -76 coding::{Decodable, Encodable}, -77 frame::Frame, -78}; -79 -80mod endpoint; -81pub use crate::endpoint::{ -82 AcceptError, ConnectError, ConnectionHandle, DatagramEvent, DecryptedInitial, Endpoint, -83 Incoming, IncomingAlpns, RetryError, -84}; -85 -86mod packet; -87pub use packet::{ -88 ConnectionIdParser, FixedLengthConnectionIdParser, LongType, PacketDecodeError, PartialDecode, -89 ProtectedHeader, ProtectedInitialHeader, -90}; -91 -92mod shared; -93pub use crate::shared::{ConnectionEvent, ConnectionId, EcnCodepoint, EndpointEvent}; -94 -95mod transport_error; -96pub use crate::transport_error::{Code as TransportErrorCode, Error as TransportError}; +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 -98pub mod congestion; -99 -100mod cid_generator; -101pub use crate::cid_generator::{ -102 ConnectionIdGenerator, HashedConnectionIdGenerator, InvalidCid, RandomConnectionIdGenerator, -103}; -104 -105mod token; -106use token::ResetToken; -107pub use token::{NoneTokenLog, NoneTokenStore, TokenLog, TokenReuseError, TokenStore}; +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 address_discovery; -110 -111mod token_memory_cache; -112pub use token_memory_cache::TokenMemoryCache; +109mod token_memory_cache; +110pub use token_memory_cache::TokenMemoryCache; +111 +112pub mod n0_nat_traversal; 113 -114pub mod n0_nat_traversal; -115 -116// Deal with time -117#[cfg(not(all(target_family = "wasm", target_os = "unknown")))] -118pub(crate) use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; -119#[cfg(all(target_family = "wasm", target_os = "unknown"))] -120pub(crate) use web_time::{Duration, Instant, SystemTime, UNIX_EPOCH}; -121 -122#[cfg(feature = "bench")] -123pub mod bench_exports { -124 //! Exports for benchmarks -125 pub use crate::connection::send_buffer::send_buffer_benches; -126} -127 -128#[cfg(fuzzing)] -129pub mod fuzzing { -130 pub use crate::connection::{Retransmits, State as ConnectionState, StreamsState}; -131 pub use crate::frame::ResetStream; -132 pub use crate::packet::PartialDecode; -133 pub use crate::transport_parameters::TransportParameters; -134 pub use bytes::{BufMut, BytesMut}; -135 -136 #[cfg(feature = "arbitrary")] -137 use arbitrary::{Arbitrary, Result, Unstructured}; -138 -139 #[cfg(feature = "arbitrary")] -140 impl<'arbitrary> Arbitrary<'arbitrary> for TransportParameters { -141 fn arbitrary(u: &mut Unstructured<'arbitrary>) -> Result<Self> { -142 Ok(Self { -143 initial_max_streams_bidi: u.arbitrary()?, -144 initial_max_streams_uni: u.arbitrary()?, -145 ack_delay_exponent: u.arbitrary()?, -146 max_udp_payload_size: u.arbitrary()?, -147 ..Self::default() -148 }) -149 } -150 } -151 -152 #[derive(Debug)] -153 pub struct PacketParams { -154 pub local_cid_len: usize, -155 pub buf: BytesMut, -156 pub grease_quic_bit: bool, -157 } -158 -159 #[cfg(feature = "arbitrary")] -160 impl<'arbitrary> Arbitrary<'arbitrary> for PacketParams { -161 fn arbitrary(u: &mut Unstructured<'arbitrary>) -> Result<Self> { -162 let local_cid_len: usize = u.int_in_range(0..=crate::MAX_CID_SIZE)?; -163 let bytes: Vec<u8> = Vec::arbitrary(u)?; -164 let mut buf = BytesMut::new(); -165 buf.put_slice(&bytes[..]); -166 Ok(Self { -167 local_cid_len, -168 buf, -169 grease_quic_bit: bool::arbitrary(u)?, -170 }) -171 } -172 } -173} -174 -175/// The QUIC protocol version implemented. -176pub const DEFAULT_SUPPORTED_VERSIONS: &[u32] = &[ -177 0x00000001, -178 0xff00_001d, -179 0xff00_001e, -180 0xff00_001f, -181 0xff00_0020, -182 0xff00_0021, -183 0xff00_0022, -184]; -185 -186/// Whether an endpoint was the initiator of a connection -187#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -188#[cfg_attr(test, derive(Arbitrary))] -189#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -190pub enum Side { -191 /// The initiator of a connection -192 Client = 0, -193 /// The acceptor of a connection -194 Server = 1, -195} -196 -197impl Side { -198 #[inline] -199 /// Shorthand for `self == Side::Client` -200 pub fn is_client(self) -> bool { -201 self == Self::Client -202 } -203 -204 #[inline] -205 /// Shorthand for `self == Side::Server` -206 pub fn is_server(self) -> bool { -207 self == Self::Server -208 } -209} -210 -211impl ops::Not for Side { -212 type Output = Self; -213 fn not(self) -> Self { -214 match self { -215 Self::Client => Self::Server, -216 Self::Server => Self::Client, -217 } -218 } -219} -220 -221/// Whether a stream communicates data in both directions or only from the initiator -222#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -223#[cfg_attr(test, derive(Arbitrary))] -224#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -225pub enum Dir { -226 /// Data flows in both directions -227 Bi = 0, -228 /// Data flows only from the stream's initiator -229 Uni = 1, -230} -231 -232impl Dir { -233 fn iter() -> impl Iterator<Item = Self> { -234 [Self::Bi, Self::Uni].iter().cloned() -235 } -236} -237 -238impl fmt::Display for Dir { -239 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -240 use Dir::*; -241 f.pad(match *self { -242 Bi => "bidirectional", -243 Uni => "unidirectional", -244 }) -245 } -246} -247 -248/// Identifier for a stream within a particular connection -249#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -250#[cfg_attr(test, derive(Arbitrary))] -251pub struct StreamId(#[cfg_attr(test, strategy(crate::varint::varint_u64()))] u64); -252 -253impl fmt::Display for StreamId { -254 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -255 let initiator = match self.initiator() { -256 Side::Client => "client", -257 Side::Server => "server", -258 }; -259 let dir = match self.dir() { -260 Dir::Uni => "uni", -261 Dir::Bi => "bi", -262 }; -263 write!( -264 f, -265 "{} {}directional stream {}", -266 initiator, -267 dir, -268 self.index() -269 ) -270 } -271} -272 -273impl StreamId { -274 /// Create a new StreamId -275 pub fn new(initiator: Side, dir: Dir, index: u64) -> Self { -276 Self((index << 2) | ((dir as u64) << 1) | initiator as u64) -277 } -278 /// Which side of a connection initiated the stream -279 pub fn initiator(self) -> Side { -280 if self.0 & 0x1 == 0 { -281 Side::Client -282 } else { -283 Side::Server -284 } -285 } -286 /// Which directions data flows in -287 pub fn dir(self) -> Dir { -288 if self.0 & 0x2 == 0 { Dir::Bi } else { Dir::Uni } -289 } -290 /// Distinguishes streams of the same initiator and directionality -291 pub fn index(self) -> u64 { -292 self.0 >> 2 -293 } -294} -295 -296impl From<StreamId> for VarInt { -297 fn from(x: StreamId) -> Self { -298 unsafe { Self::from_u64_unchecked(x.0) } -299 } -300} -301 -302impl From<VarInt> for StreamId { -303 fn from(v: VarInt) -> Self { -304 Self(v.0) -305 } -306} -307 -308impl From<StreamId> for u64 { -309 fn from(x: StreamId) -> Self { -310 x.0 -311 } -312} -313 -314impl Decodable for StreamId { -315 fn decode<B: bytes::Buf>(buf: &mut B) -> coding::Result<Self> { -316 VarInt::decode(buf).map(|x| Self(x.into_inner())) -317 } -318} -319 -320impl Encodable for StreamId { -321 fn encode<B: bytes::BufMut>(&self, buf: &mut B) { -322 VarInt::from_u64(self.0).unwrap().encode(buf); -323 } -324} -325 -326#[cfg(feature = "arbitrary")] -327impl<'arbitrary> arbitrary::Arbitrary<'arbitrary> for StreamId { -328 fn arbitrary(u: &mut arbitrary::Unstructured<'arbitrary>) -> arbitrary::Result<Self> { -329 Ok(VarInt::arbitrary(u)?.into()) -330 } -331} -332 -333/// An outgoing packet -334#[derive(Debug)] -335#[must_use] -336pub struct Transmit { -337 /// The socket this datagram should be sent to -338 pub destination: SocketAddr, -339 /// Explicit congestion notification bits to set on the packet -340 pub ecn: Option<EcnCodepoint>, -341 /// Amount of data written to the caller-supplied buffer -342 pub size: usize, -343 /// The segment size if this transmission contains multiple datagrams. -344 /// This is `None` if the transmit only contains a single datagram -345 pub segment_size: Option<usize>, -346 /// Optional source IP address for the datagram -347 pub src_ip: Option<IpAddr>, -348} -349 -350// -351// Useful internal constants -352// -353 -354/// The maximum number of CIDs we bother to issue per path -355const LOCAL_CID_COUNT: u64 = 12; -356const RESET_TOKEN_SIZE: usize = 16; -357const MAX_CID_SIZE: usize = 20; -358const MIN_INITIAL_SIZE: u16 = 1200; -359/// <https://www.rfc-editor.org/rfc/rfc9000.html#name-datagram-size> -360const INITIAL_MTU: u16 = 1200; -361const MAX_UDP_PAYLOAD: u16 = 65527; -362const TIMER_GRANULARITY: Duration = Duration::from_millis(1); -363/// Maximum number of streams that can be uniquely identified by a stream ID -364const MAX_STREAM_COUNT: u64 = 1 << 60; -365 -366/// Identifies a network path by the combination of remote and local addresses -367/// -368/// Including the local ensures good behavior when the host has multiple IP addresses on the same -369/// subnet and zero-length connection IDs are in use or when multipath is enabled and multiple -370/// paths exist with the same remote, but different local IP interfaces. -371#[derive(Hash, Eq, PartialEq, Copy, Clone)] -372pub struct FourTuple { -373 /// The remote side of this tuple. -374 remote: SocketAddr, -375 /// The local side of this tuple. -376 /// -377 /// The socket is irrelevant for our intents and purposes: -378 /// When we send, we can only specify the `src_ip`, not the source port. -379 /// So even if we track the port, we won't be able to make use of it. -380 local_ip: Option<IpAddr>, -381} -382 -383impl FourTuple { -384 /// Creates a new [`FourTuple`]. -385 pub fn new(mut remote: SocketAddr, local_ip: Option<IpAddr>) -> Self { -386 if let SocketAddr::V6(socket_addr) = &mut remote { -387 // RFC3493 §3.3 -388 // > (…) applications should set this field to zero when constructing a sockaddr_in6, -389 // > and ignore this field in a sockaddr_in6 structure constructed by the system. -390 // -391 // This is cleared so that comparisons of remotes are guaranteed to be meaningful: two -392 // socket addresses with the same contents should not differ if only the flow label -393 // differs -394 socket_addr.set_flowinfo(0); -395 -396 // NOTE: not all multicast addresses require a scope. Use `Ipv6Addr::multicast_scope` -397 // when stabilized (<https://github.com/rust-lang/rust/issues/27709>) -398 let requires_scope_id = -399 socket_addr.ip().is_unicast_link_local() || socket_addr.ip().is_multicast(); -400 if !requires_scope_id { -401 // Keep the scope id only when it might be relevant. This ensure network paths can -402 // be compared meaningfully while keeping it when it's important (mainly link local -403 // addresses) -404 socket_addr.set_scope_id(0); -405 } -406 } -407 -408 Self { remote, local_ip } -409 } -410 -411 /// Creates a new [`FourTuple`] without a known local address. -412 pub fn from_remote(remote: SocketAddr) -> Self { -413 Self::new(remote, None) -414 } -415 -416 /// Returns the remote address of the network path. -417 pub fn remote(&self) -> SocketAddr { -418 self.remote -419 } -420 -421 /// Returns the local address of the network path. -422 pub fn local_ip(&self) -> Option<IpAddr> { -423 self.local_ip -424 } -425 -426 /// Returns whether we think the other address probably represents the same path -427 /// as ours. -428 /// -429 /// If we have a local IP set, then we're exact and only match if the 4-tuples are -430 /// exactly equal. -431 /// If we don't have a local IP set, then we only check the remote addresses for equality. -432 /// -433 /// Note that because of this, the following calls might differ: -434 /// - `a.is_probably_same_path(b)` -435 /// - `b.is_probably_same_path(a)` -436 pub fn is_probably_same_path(&self, other: &Self) -> bool { -437 self.remote == other.remote && (self.local_ip.is_none() || self.local_ip == other.local_ip) -438 } -439 -440 /// Updates this tuple's local address iff -441 /// - it was unset before, -442 /// - the other tuple has the same remote, and -443 /// - the other tuple has a local address set. -444 /// -445 /// Returns whether this and the other remote are now fully equal. -446 pub fn update_local_if_same_remote(&mut self, other: &Self) -> bool { -447 if self.remote != other.remote { -448 return false; -449 } -450 if self.local_ip.is_some() && self.local_ip != other.local_ip { -451 return false; -452 } -453 self.local_ip = other.local_ip; -454 true -455 } -456} -457 -458impl fmt::Display for FourTuple { -459 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -460 f.write_str("(local: ")?; -461 if let Some(local_ip) = &self.local_ip { -462 local_ip.fmt(f)?; -463 f.write_str(", ")?; -464 } else { -465 f.write_str("<unspecified>, ")?; -466 } -467 f.write_str("remote: ")?; -468 self.remote.fmt(f)?; -469 f.write_str(")") -470 } -471} -472 -473impl fmt::Debug for FourTuple { -474 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -475 fmt::Display::fmt(&self, f) -476 } -477} \ No newline at end of file +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 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 68df2cd8a..477704228 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 @@ -1,578 +1,1071 @@ -n0_nat_traversal.rs - source

    noq_proto/
    n0_nat_traversal.rs

    1//! n0's (<https://n0.computer>) NAT Traversal protocol implementation.
    +n0_nat_traversal.rs - source

    noq_proto/
    n0_nat_traversal.rs

    1//! n0's (<https://n0.computer>) NAT Traversal protocol implementation.
     2
     3use std::{
     4    collections::hash_map::Entry,
    -5    net::{IpAddr, SocketAddr},
    -6};
    -7
    -8use rustc_hash::{FxHashMap, FxHashSet};
    -9use tracing::trace;
    -10
    -11use crate::{
    -12    PathId, Side, VarInt,
    -13    frame::{AddAddress, ReachOut, RemoveAddress},
    -14};
    -15
    -16type IpPort = (IpAddr, u16);
    -17
    -18/// Errors that the nat traversal state might encounter.
    -19#[derive(Debug, thiserror::Error)]
    -20pub enum Error {
    -21    /// An endpoint (local or remote) tried to add too many addresses to their advertised set
    -22    #[error("Tried to add too many addresses to their advertised set")]
    -23    TooManyAddresses,
    -24    /// The operation is not allowed for this endpoint's connection side
    -25    #[error("Not allowed for this endpoint's connection side")]
    -26    WrongConnectionSide,
    -27    /// The extension was not negotiated
    -28    #[error("n0's nat traversal was not negotiated")]
    -29    ExtensionNotNegotiated,
    -30    /// Not enough addresses to complete the operation
    -31    #[error("Not enough addresses")]
    -32    NotEnoughAddresses,
    -33    /// Nat traversal attempt failed due to a multipath error
    -34    #[error("Failed to establish paths {0}")]
    -35    Multipath(super::PathError),
    -36    /// Attempted to initiate NAT traversal on a closed, or closing connection.
    -37    #[error("The connection is already closed")]
    -38    Closed,
    -39}
    -40
    -41pub(crate) struct NatTraversalRound {
    -42    /// Sequence number to use for the new reach out frames.
    -43    pub(crate) new_round: VarInt,
    -44    /// Addresses to use to send reach out frames.
    -45    pub(crate) reach_out_at: FxHashSet<IpPort>,
    -46    /// Remotes to probe by attempting to open new paths.
    -47    ///
    -48    /// The addresses include their Id, so that it can be used to signal these should be returned
    -49    /// in a nat traversal continuation by calling [`ClientState::report_in_continuation`].
    -50    ///
    -51    /// These are filtered and mapped to the IP family the local socket supports.
    -52    pub(crate) addresses_to_probe: Vec<(VarInt, IpPort)>,
    -53    /// [`PathId`]s of the cancelled round.
    -54    pub(crate) prev_round_path_ids: Vec<PathId>,
    -55}
    -56
    -57/// Event emitted when the client receives ADD_ADDRESS or REMOVE_ADDRESS frames.
    -58#[derive(Debug, Clone)]
    -59pub enum Event {
    -60    /// An ADD_ADDRESS frame was received.
    -61    AddressAdded(SocketAddr),
    -62    /// A REMOVE_ADDRESS frame was received.
    -63    AddressRemoved(SocketAddr),
    -64}
    -65
    -66/// State kept for n0's nat traversal
    -67#[derive(Debug, Default)]
    -68pub(crate) enum State {
    -69    #[default]
    -70    NotNegotiated,
    -71    ClientSide(ClientState),
    -72    ServerSide(ServerState),
    -73}
    -74
    -75#[derive(Debug)]
    -76pub(crate) struct ClientState {
    -77    /// Max number of remote addresses we allow
    -78    ///
    -79    /// This is set by the local endpoint.
    -80    max_remote_addresses: usize,
    -81    /// Max number of local addresses allowed
    -82    ///
    -83    /// This is set by the remote endpoint.
    -84    max_local_addresses: usize,
    -85    /// Candidate addresses the remote server reports as potentially reachable, to use for nat
    -86    /// traversal attempts.
    -87    ///
    -88    /// These are indexed by their advertised Id. For each address, whether the address should be
    -89    /// reported in nat traversal continuations is kept.
    -90    remote_addresses: FxHashMap<VarInt, (IpPort, bool)>,
    -91    /// Candidate addresses the local client reports as potentially reachable, to use for nat
    -92    /// traversal attempts.
    -93    local_addresses: FxHashSet<IpPort>,
    -94    /// Current nat traversal round.
    -95    round: VarInt,
    -96    /// [`PathId`]s used to probe remotes assigned to this round.
    -97    round_path_ids: Vec<PathId>,
    -98}
    -99
    -100impl ClientState {
    -101    fn new(max_remote_addresses: usize, max_local_addresses: usize) -> Self {
    -102        Self {
    -103            max_remote_addresses,
    -104            max_local_addresses,
    -105            remote_addresses: Default::default(),
    -106            local_addresses: Default::default(),
    -107            round: Default::default(),
    -108            round_path_ids: Default::default(),
    -109        }
    -110    }
    +5    fmt::Display,
    +6    net::{IpAddr, SocketAddr},
    +7    time::Duration,
    +8};
    +9
    +10use rustc_hash::{FxHashMap, FxHashSet};
    +11use tracing::{debug, trace};
    +12
    +13use crate::{
    +14    FourTuple, Side, VarInt,
    +15    connection::spaces::PendingReachOutFrames,
    +16    frame::{AddAddress, ReachOut, RemoveAddress},
    +17};
    +18
    +19/// Maximum number of times we send a NAT probe to the same remote address in a round.
    +20///
    +21/// This is a trade-off between several factors:
    +22/// - Probe packets could be lost. This allows recovery.
    +23/// - We may need two probes to reach the NAT firewall to get through.
    +24/// - We may be sending probes to innocent bystanders on the internet.
    +25/// - A round never "finishes": probing of remotes only stops when:
    +26///   1. A new round is started.
    +27///   2. A probe was successful.
    +28///   3. This number of attempts is exhausted.
    +29///
    +30/// See [`State::retry_delay`] for the capped exponential backoff used. With this we send
    +31/// probes for up to 4s by default.
    +32pub(crate) const MAX_NAT_PROBE_ATTEMPTS: u8 = 9;
    +33
    +34/// An IP & port.
    +35///
    +36/// Invariant: This value should always be in the ip family that the local
    +37/// socket operates in.
    +38/// E.g. if the local socket is ipv4, then all `IpPort`s should only have
    +39/// IPv4 addresses, and if the socket supports ipv6, then all `IpPort`s
    +40/// should be IPv6 addresses or IPv6-mapped IPv4 addresses.
    +41///
    +42/// See also [`map_to_local_socket_family`], which powers this conversion.
    +43type IpPort = (IpAddr, u16);
    +44
    +45/// An IP & port in canonical form.
    +46///
    +47/// Avoids using ipv6-mapped ipv4 addresses.
    +48/// This is the primary type used to send ip addresses around remotely
    +49/// and the primary type used to canonicalize received addresses.
    +50#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
    +51pub(crate) struct CanonicalIpPort {
    +52    canonical_ip: IpAddr,
    +53    port: u16,
    +54}
    +55
    +56impl CanonicalIpPort {
    +57    pub(crate) fn ip(&self) -> IpAddr {
    +58        self.canonical_ip
    +59    }
    +60
    +61    pub(crate) fn port(&self) -> u16 {
    +62        self.port
    +63    }
    +64
    +65    /// Converts this into a local-socket-family-mapped IP & port.
    +66    ///
    +67    /// Instead of using ipv4 and ipv6 addresses, this tries to match `ipv6`, which
    +68    /// should indicate whether the local socket supports ipv6 or not.
    +69    ///
    +70    /// If ipv6 is supported, all ipv4 addresses are mapped using ipv6-mapped ipv4
    +71    /// addresses.
    +72    /// If ipv6 is not supported, then this returns `None` for ipv6 addresses.
    +73    ///
    +74    /// See also [`map_to_local_socket_family`].
    +75    pub(crate) fn as_local_socket_family(&self, ipv6: bool) -> Option<IpPort> {
    +76        Some((
    +77            map_to_local_socket_family(self.canonical_ip, ipv6)?,
    +78            self.port,
    +79        ))
    +80    }
    +81
    +82    /// Returns this address as-is with the canonical IP used in a `SocketAddr`.
    +83    pub(crate) fn as_canonical_addr(&self) -> SocketAddr {
    +84        SocketAddr::new(self.canonical_ip, self.port)
    +85    }
    +86}
    +87
    +88impl Display for CanonicalIpPort {
    +89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    +90        self.as_canonical_addr().fmt(f)
    +91    }
    +92}
    +93
    +94impl From<SocketAddr> for CanonicalIpPort {
    +95    fn from(addr: SocketAddr) -> Self {
    +96        Self {
    +97            canonical_ip: addr.ip().to_canonical(),
    +98            port: addr.port(),
    +99        }
    +100    }
    +101}
    +102
    +103impl From<IpPort> for CanonicalIpPort {
    +104    fn from((ip, port): IpPort) -> Self {
    +105        Self {
    +106            canonical_ip: ip.to_canonical(),
    +107            port,
    +108        }
    +109    }
    +110}
     111
    -112    fn add_local_address(&mut self, address: IpPort) -> Result<(), Error> {
    -113        if self.local_addresses.len() < self.max_local_addresses {
    -114            self.local_addresses.insert(address);
    -115            Ok(())
    -116        } else if self.local_addresses.contains(&address) {
    -117            // at capacity, but the address is known, no issues here
    -118            Ok(())
    -119        } else {
    -120            // at capacity and the address is new
    -121            Err(Error::TooManyAddresses)
    -122        }
    -123    }
    -124
    -125    fn remove_local_address(&mut self, address: &IpPort) {
    -126        self.local_addresses.remove(address);
    -127    }
    -128
    -129    /// Initiates a new nat traversal round.
    -130    ///
    -131    /// A nat traversal round involves advertising the client's local addresses in `REACH_OUT`
    -132    /// frames, and initiating probing of the known remote addresses. When a new round is
    -133    /// initiated, the previous one is cancelled, and paths that have not been opened should be
    -134    /// closed.
    -135    ///
    -136    /// `ipv6` indicates if the connection runs on a socket that supports IPv6. If so, then all
    -137    /// addresses returned in [`NatTraversalRound`] will be IPv6 addresses (and IPv4-mapped IPv6
    -138    /// addresses if necessary). Otherwise they're all IPv4 addresses.
    -139    /// See also [`map_to_local_socket_family`].
    -140    pub(crate) fn initiate_nat_traversal_round(
    -141        &mut self,
    -142        ipv6: bool,
    -143    ) -> Result<NatTraversalRound, Error> {
    -144        if self.local_addresses.is_empty() {
    -145            return Err(Error::NotEnoughAddresses);
    -146        }
    -147
    -148        let prev_round_path_ids = std::mem::take(&mut self.round_path_ids);
    -149        self.round = self.round.saturating_add(1u8);
    -150        let mut addresses_to_probe = Vec::with_capacity(self.remote_addresses.len());
    -151        for (id, ((ip, port), report_in_continuation)) in self.remote_addresses.iter_mut() {
    -152            *report_in_continuation = false;
    -153
    -154            if let Some(ip) = map_to_local_socket_family(*ip, ipv6) {
    -155                addresses_to_probe.push((*id, (ip, *port)));
    -156            } else {
    -157                trace!(?ip, "not using IPv6 nat candidate for IPv4 socket");
    -158            }
    -159        }
    -160
    -161        Ok(NatTraversalRound {
    -162            new_round: self.round,
    -163            reach_out_at: self.local_addresses.iter().copied().collect(),
    -164            addresses_to_probe,
    -165            prev_round_path_ids,
    -166        })
    -167    }
    -168
    -169    /// Mark a remote address to be reported back in a nat traversal continuation if the error is
    -170    /// considered spurious from a nat traversal point of view.
    -171    ///
    -172    /// Ids not present are silently ignored.
    -173    pub(crate) fn report_in_continuation(&mut self, id: VarInt, e: crate::PathError) {
    -174        match e {
    -175            crate::PathError::MaxPathIdReached | crate::PathError::RemoteCidsExhausted => {
    -176                if let Some((_address, report_in_continuation)) = self.remote_addresses.get_mut(&id)
    -177                {
    -178                    *report_in_continuation = true;
    -179                }
    -180            }
    -181            _ => {}
    -182        }
    -183    }
    -184
    -185    /// Returns an address that needs to be probed, if any.
    -186    ///
    -187    /// The address will not be returned twice unless marked as such again with
    -188    /// [`Self::report_in_continuation`].
    -189    ///
    -190    /// `ipv6` indicates if the connection runs on a socket that supports IPv6. If so, then all
    -191    /// addresses returned in [`NatTraversalRound`] will be IPv6 addresses (and IPv4-mapped IPv6
    -192    /// addresses if necessary). Otherwise they're all IPv4 addresses.
    -193    /// See also [`map_to_local_socket_family`].
    -194    pub(crate) fn continue_nat_traversal_round(&mut self, ipv6: bool) -> Option<(VarInt, IpPort)> {
    -195        // this being random depends on iteration not returning always on the same order
    -196        let (id, (address, report_in_continuation)) = self
    -197            .remote_addresses
    -198            .iter_mut()
    -199            .filter(|(_id, (_addr, report))| *report)
    -200            .filter_map(|(id, ((ip, port), report))| {
    -201                // only continue with addresses we can send on our local socket
    -202                let Some(ip) = map_to_local_socket_family(*ip, ipv6) else {
    -203                    trace!(?ip, "not using IPv6 nat candidate for IPv4 socket");
    -204                    return None;
    -205                };
    -206                Some((*id, ((ip, *port), report)))
    -207            })
    -208            .next()?;
    -209        *report_in_continuation = false;
    -210        Some((id, address))
    -211    }
    -212
    -213    /// Add a [`PathId`] as part of the current attempts to create paths based on the server's
    -214    /// advertised addresses.
    -215    pub(crate) fn set_round_path_ids(&mut self, path_ids: Vec<PathId>) {
    -216        self.round_path_ids = path_ids;
    +112/// Errors that the nat traversal state might encounter.
    +113#[derive(Debug, thiserror::Error)]
    +114pub enum Error {
    +115    /// An endpoint (local or remote) tried to add too many addresses to their advertised set
    +116    #[error("Tried to add too many addresses to their advertised set")]
    +117    TooManyAddresses,
    +118    /// The operation is not allowed for this endpoint's connection side
    +119    #[error("Not allowed for this endpoint's connection side")]
    +120    WrongConnectionSide,
    +121    /// The extension was not negotiated
    +122    #[error("n0's nat traversal was not negotiated")]
    +123    ExtensionNotNegotiated,
    +124    /// Not enough addresses to complete the operation
    +125    #[error("Not enough addresses")]
    +126    NotEnoughAddresses,
    +127    /// Nat traversal attempt failed due to a multipath error
    +128    #[error("Failed to establish paths {0}")]
    +129    Multipath(super::PathError),
    +130    /// Attempted to initiate NAT traversal on a closed, or closing connection.
    +131    #[error("The connection is already closed")]
    +132    Closed,
    +133}
    +134
    +135/// Event emitted when the client receives ADD_ADDRESS or REMOVE_ADDRESS frames.
    +136#[derive(Debug, Clone)]
    +137pub enum Event {
    +138    /// An ADD_ADDRESS frame was received.
    +139    AddressAdded(SocketAddr),
    +140    /// A REMOVE_ADDRESS frame was received.
    +141    AddressRemoved(SocketAddr),
    +142}
    +143
    +144/// State kept for n0's nat traversal
    +145#[derive(Debug, Default)]
    +146pub(crate) enum State {
    +147    #[default]
    +148    NotNegotiated,
    +149    ClientSide(ClientState),
    +150    ServerSide(ServerState),
    +151}
    +152
    +153impl State {
    +154    pub(crate) fn new(max_remote_addresses: u8, max_local_addresses: u8, side: Side) -> Self {
    +155        match side {
    +156            Side::Client => Self::ClientSide(ClientState::new(
    +157                max_remote_addresses.into(),
    +158                max_local_addresses.into(),
    +159            )),
    +160            Side::Server => Self::ServerSide(ServerState::new(
    +161                max_remote_addresses.into(),
    +162                max_local_addresses.into(),
    +163            )),
    +164        }
    +165    }
    +166
    +167    pub(crate) fn is_negotiated(&self) -> bool {
    +168        match self {
    +169            Self::NotNegotiated => false,
    +170            Self::ClientSide(_) | Self::ServerSide(_) => true,
    +171        }
    +172    }
    +173
    +174    pub(crate) fn client_side(&self) -> Result<&ClientState, Error> {
    +175        match self {
    +176            Self::NotNegotiated => Err(Error::ExtensionNotNegotiated),
    +177            Self::ClientSide(client_side) => Ok(client_side),
    +178            Self::ServerSide(_) => Err(Error::WrongConnectionSide),
    +179        }
    +180    }
    +181
    +182    pub(crate) fn client_side_mut(&mut self) -> Result<&mut ClientState, Error> {
    +183        match self {
    +184            Self::NotNegotiated => Err(Error::ExtensionNotNegotiated),
    +185            Self::ClientSide(client_side) => Ok(client_side),
    +186            Self::ServerSide(_) => Err(Error::WrongConnectionSide),
    +187        }
    +188    }
    +189
    +190    pub(crate) fn server_side_mut(&mut self) -> Result<&mut ServerState, Error> {
    +191        match self {
    +192            Self::NotNegotiated => Err(Error::ExtensionNotNegotiated),
    +193            Self::ClientSide(_) => Err(Error::WrongConnectionSide),
    +194            Self::ServerSide(server_side) => Ok(server_side),
    +195        }
    +196    }
    +197
    +198    /// Adds a local address to use for nat traversal.
    +199    ///
    +200    /// When this endpoint is the server within the connection, these addresses will be sent to the
    +201    /// client in add address frames. For clients, these addresses will be sent in reach out frames
    +202    /// when nat traversal attempts are initiated.
    +203    ///
    +204    /// If a frame should be sent, it is returned.
    +205    pub(crate) fn add_local_address(
    +206        &mut self,
    +207        address: SocketAddr,
    +208    ) -> Result<Option<AddAddress>, Error> {
    +209        match self {
    +210            Self::NotNegotiated => Err(Error::ExtensionNotNegotiated),
    +211            Self::ClientSide(client_state) => {
    +212                client_state.add_local_address(address)?;
    +213                Ok(None)
    +214            }
    +215            Self::ServerSide(server_state) => server_state.add_local_address(address),
    +216        }
     217    }
     218
    -219    /// Add a [`PathId`] as part of the current attempts to create paths based on the server's
    -220    /// advertised addresses.
    -221    pub(crate) fn add_round_path_id(&mut self, path_id: PathId) {
    -222        self.round_path_ids.push(path_id);
    -223    }
    -224
    -225    /// Adds an address to the remote set
    -226    ///
    -227    /// On success returns the address if it was new to the set. It will error when the set has no
    -228    /// capacity for the address.
    -229    pub(crate) fn add_remote_address(
    -230        &mut self,
    -231        add_addr: AddAddress,
    -232    ) -> Result<Option<SocketAddr>, Error> {
    -233        let AddAddress { seq_no, ip, port } = add_addr;
    -234        let address = (ip, port);
    -235        let allow_new = self.remote_addresses.len() < self.max_remote_addresses;
    -236        match self.remote_addresses.entry(seq_no) {
    -237            Entry::Occupied(mut occupied_entry) => {
    -238                let is_update = occupied_entry.get().0 != address;
    -239                if is_update {
    -240                    occupied_entry.insert((address, false));
    -241                }
    -242                // The value might be different. This should not happen, but we assume that the new
    -243                // address is more recent than the previous, and thus worth updating
    -244                Ok(is_update.then_some(address.into()))
    -245            }
    -246            Entry::Vacant(vacant_entry) if allow_new => {
    -247                vacant_entry.insert((address, false));
    -248                Ok(Some(address.into()))
    -249            }
    -250            _ => Err(Error::TooManyAddresses),
    -251        }
    -252    }
    -253
    -254    /// Removes an address from the remote set
    -255    ///
    -256    /// Returns whether the address was present.
    -257    pub(crate) fn remove_remote_address(
    -258        &mut self,
    -259        remove_addr: RemoveAddress,
    -260    ) -> Option<SocketAddr> {
    -261        self.remote_addresses
    -262            .remove(&remove_addr.seq_no)
    -263            .map(|(address, _report_in_continuation)| address.into())
    -264    }
    -265
    -266    /// Checks that a received remote address is valid
    -267    ///
    -268    /// An address is valid as long as it does not change the value of a known address id.
    -269    pub(crate) fn check_remote_address(&self, add_addr: &AddAddress) -> bool {
    -270        match self.remote_addresses.get(&add_addr.seq_no) {
    -271            None => true,
    -272            Some((existing, _)) => existing == &add_addr.ip_port(),
    +219    /// Removes a local address from the advertised set for nat traversal.
    +220    ///
    +221    /// When this endpoint is the server, removed addresses must be reported with remove address
    +222    /// frames. Clients will simply stop reporting these addresses in reach out frames.
    +223    ///
    +224    /// If a frame should be sent, it is returned.
    +225    pub(crate) fn remove_local_address(
    +226        &mut self,
    +227        address: SocketAddr,
    +228    ) -> Result<Option<RemoveAddress>, Error> {
    +229        let address = IpPort::from((address.ip(), address.port()));
    +230        match self {
    +231            Self::NotNegotiated => Err(Error::ExtensionNotNegotiated),
    +232            Self::ClientSide(client_state) => {
    +233                client_state.remove_local_address(&address);
    +234                Ok(None)
    +235            }
    +236            Self::ServerSide(server_state) => Ok(server_state.remove_local_address(&address)),
    +237        }
    +238    }
    +239
    +240    pub(crate) fn get_local_nat_traversal_addresses(&self) -> Result<Vec<SocketAddr>, Error> {
    +241        match self {
    +242            Self::NotNegotiated => Err(Error::ExtensionNotNegotiated),
    +243            Self::ClientSide(client_state) => Ok(client_state
    +244                .local_addresses
    +245                .iter()
    +246                .map(CanonicalIpPort::as_canonical_addr)
    +247                .collect()),
    +248            Self::ServerSide(server_state) => Ok(server_state
    +249                .local_addresses
    +250                .keys()
    +251                .map(CanonicalIpPort::as_canonical_addr)
    +252                .collect()),
    +253        }
    +254    }
    +255
    +256    /// Returns the next ready probe's address.
    +257    ///
    +258    /// If this is actually sent you must call [`Self::mark_probe_sent`].
    +259    pub(crate) fn next_probe_addr(&self) -> Option<IpPort> {
    +260        match self {
    +261            Self::NotNegotiated => None,
    +262            Self::ClientSide(state) => state.next_probe_addr(),
    +263            Self::ServerSide(state) => state.next_probe_addr(),
    +264        }
    +265    }
    +266
    +267    /// Marks a probe as sent to the address with the challenge.
    +268    pub(crate) fn mark_probe_sent(&mut self, remote: IpPort, challenge: u64) {
    +269        match self {
    +270            Self::NotNegotiated => (),
    +271            Self::ClientSide(state) => state.mark_probe_sent(remote, challenge),
    +272            Self::ServerSide(state) => state.mark_probe_sent(remote, challenge),
     273        }
     274    }
     275
    -276    pub(crate) fn get_remote_nat_traversal_addresses(&self) -> Vec<SocketAddr> {
    -277        self.remote_addresses
    -278            .values()
    -279            .map(|(address, _report_in_continuation)| (*address).into())
    -280            .collect()
    -281    }
    -282}
    -283
    -284#[derive(Debug)]
    -285pub(crate) struct ServerState {
    -286    /// Max number of remote addresses we allow.
    -287    ///
    -288    /// This is set by the local endpoint.
    -289    max_remote_addresses: usize,
    -290    /// Max number of local addresses allowed.
    -291    ///
    -292    /// This is set by the remote endpoint.
    -293    max_local_addresses: usize,
    -294    /// Candidate addresses the server reports as potentially reachable, to use for nat
    -295    /// traversal attempts.
    -296    local_addresses: FxHashMap<IpPort, VarInt>,
    -297    /// The next id to use for local addresses sent to the client.
    -298    next_local_addr_id: VarInt,
    -299    /// Current nat traversal round
    -300    ///
    -301    /// Servers keep track of the client's most recent round and cancel probing related to previous
    -302    /// rounds.
    -303    round: VarInt,
    -304    /// Addresses to which PATH_CHALLENGES need to be sent.
    -305    pending_probes: FxHashSet<IpPort>,
    -306}
    -307
    -308impl ServerState {
    -309    fn new(max_remote_addresses: usize, max_local_addresses: usize) -> Self {
    -310        Self {
    -311            max_remote_addresses,
    -312            max_local_addresses,
    -313            local_addresses: Default::default(),
    -314            next_local_addr_id: Default::default(),
    -315            round: Default::default(),
    -316            pending_probes: Default::default(),
    -317        }
    -318    }
    -319
    -320    fn add_local_address(&mut self, address: IpPort) -> Result<Option<AddAddress>, Error> {
    -321        let allow_new = self.local_addresses.len() < self.max_local_addresses;
    -322        match self.local_addresses.entry(address) {
    -323            Entry::Occupied(_) => Ok(None),
    -324            Entry::Vacant(vacant_entry) if allow_new => {
    -325                let id = self.next_local_addr_id;
    -326                self.next_local_addr_id = self.next_local_addr_id.saturating_add(1u8);
    -327                vacant_entry.insert(id);
    -328                Ok(Some(AddAddress::new(address, id)))
    -329            }
    -330            _ => Err(Error::TooManyAddresses),
    -331        }
    -332    }
    -333
    -334    fn remove_local_address(&mut self, address: &IpPort) -> Option<RemoveAddress> {
    -335        self.local_addresses.remove(address).map(RemoveAddress::new)
    -336    }
    -337
    -338    /// Handles a received [`ReachOut`].
    -339    ///
    -340    /// This might ignore the reach out frame if it belongs to an older round or if
    -341    /// the reach out can't be handled by an ipv4-only local socket.
    -342    pub(crate) fn handle_reach_out(
    -343        &mut self,
    -344        reach_out: ReachOut,
    -345        ipv6: bool,
    -346    ) -> Result<(), Error> {
    -347        let ReachOut { round, ip, port } = reach_out;
    -348
    -349        if round < self.round {
    -350            trace!(current_round=%self.round, "ignoring REACH_OUT for previous round");
    -351            return Ok(());
    -352        }
    -353        let Some(ip) = map_to_local_socket_family(ip, ipv6) else {
    -354            trace!("Ignoring IPv6 REACH_OUT frame due to not supporting IPv6 locally");
    -355            return Ok(());
    -356        };
    -357
    -358        if round > self.round {
    -359            self.round = round;
    -360            self.pending_probes.clear();
    -361        } else if self.pending_probes.len() >= self.max_remote_addresses
    -362            && !self.pending_probes.contains(&(ip, port))
    -363        {
    -364            return Err(Error::TooManyAddresses);
    -365        }
    -366        self.pending_probes.insert((ip, port));
    -367        Ok(())
    -368    }
    -369
    -370    pub(crate) fn next_probe(&mut self) -> Option<ServerProbing<'_>> {
    -371        self.pending_probes
    -372            .iter()
    -373            .next()
    -374            .copied()
    -375            .map(|remote| ServerProbing {
    -376                remote,
    -377                pending_probes: &mut self.pending_probes,
    -378            })
    -379    }
    -380}
    -381
    -382pub(crate) struct ServerProbing<'a> {
    -383    remote: IpPort,
    -384    pending_probes: &'a mut FxHashSet<IpPort>,
    -385}
    -386
    -387impl<'a> ServerProbing<'a> {
    -388    pub(crate) fn mark_as_sent(self) {
    -389        self.pending_probes.remove(&self.remote);
    -390    }
    -391
    -392    pub(crate) fn remote(&self) -> SocketAddr {
    -393        self.remote.into()
    -394    }
    -395}
    -396
    -397impl State {
    -398    pub(crate) fn new(max_remote_addresses: u8, max_local_addresses: u8, side: Side) -> Self {
    -399        match side {
    -400            Side::Client => Self::ClientSide(ClientState::new(
    -401                max_remote_addresses.into(),
    -402                max_local_addresses.into(),
    -403            )),
    -404            Side::Server => Self::ServerSide(ServerState::new(
    -405                max_remote_addresses.into(),
    -406                max_local_addresses.into(),
    -407            )),
    -408        }
    -409    }
    -410
    -411    pub(crate) fn client_side(&self) -> Result<&ClientState, Error> {
    -412        match self {
    -413            Self::NotNegotiated => Err(Error::ExtensionNotNegotiated),
    -414            Self::ClientSide(client_side) => Ok(client_side),
    -415            Self::ServerSide(_) => Err(Error::WrongConnectionSide),
    -416        }
    -417    }
    -418
    -419    pub(crate) fn client_side_mut(&mut self) -> Result<&mut ClientState, Error> {
    -420        match self {
    -421            Self::NotNegotiated => Err(Error::ExtensionNotNegotiated),
    -422            Self::ClientSide(client_side) => Ok(client_side),
    -423            Self::ServerSide(_) => Err(Error::WrongConnectionSide),
    -424        }
    -425    }
    -426
    -427    pub(crate) fn server_side_mut(&mut self) -> Result<&mut ServerState, Error> {
    -428        match self {
    -429            Self::NotNegotiated => Err(Error::ExtensionNotNegotiated),
    -430            Self::ClientSide(_) => Err(Error::WrongConnectionSide),
    -431            Self::ServerSide(server_side) => Ok(server_side),
    -432        }
    -433    }
    -434
    -435    /// Adds a local address to use for nat traversal.
    -436    ///
    -437    /// When this endpoint is the server within the connection, these addresses will be sent to the
    -438    /// client in add address frames. For clients, these addresses will be sent in reach out frames
    -439    /// when nat traversal attempts are initiated.
    -440    ///
    -441    /// If a frame should be sent, it is returned.
    -442    pub(crate) fn add_local_address(
    -443        &mut self,
    -444        address: SocketAddr,
    -445    ) -> Result<Option<AddAddress>, Error> {
    -446        let ip_port = IpPort::from((address.ip(), address.port()));
    -447        match self {
    -448            Self::NotNegotiated => Err(Error::ExtensionNotNegotiated),
    -449            Self::ClientSide(client_state) => {
    -450                client_state.add_local_address(ip_port)?;
    -451                Ok(None)
    -452            }
    -453            Self::ServerSide(server_state) => server_state.add_local_address(ip_port),
    -454        }
    -455    }
    -456
    -457    /// Removes a local address from the advertised set for nat traversal.
    -458    ///
    -459    /// When this endpoint is the server, removed addresses must be reported with remove address
    -460    /// frames. Clients will simply stop reporting these addresses in reach out frames.
    -461    ///
    -462    /// If a frame should be sent, it is returned.
    -463    pub(crate) fn remove_local_address(
    -464        &mut self,
    -465        address: SocketAddr,
    -466    ) -> Result<Option<RemoveAddress>, Error> {
    -467        let address = IpPort::from((address.ip(), address.port()));
    -468        match self {
    -469            Self::NotNegotiated => Err(Error::ExtensionNotNegotiated),
    -470            Self::ClientSide(client_state) => {
    -471                client_state.remove_local_address(&address);
    -472                Ok(None)
    -473            }
    -474            Self::ServerSide(server_state) => Ok(server_state.remove_local_address(&address)),
    -475        }
    -476    }
    -477
    -478    pub(crate) fn get_local_nat_traversal_addresses(&self) -> Result<Vec<SocketAddr>, Error> {
    -479        match self {
    -480            Self::NotNegotiated => Err(Error::ExtensionNotNegotiated),
    -481            Self::ClientSide(client_state) => Ok(client_state
    -482                .local_addresses
    -483                .iter()
    -484                .copied()
    -485                .map(Into::into)
    -486                .collect()),
    -487            Self::ServerSide(server_state) => Ok(server_state
    -488                .local_addresses
    -489                .keys()
    -490                .copied()
    -491                .map(Into::into)
    -492                .collect()),
    -493        }
    -494    }
    -495}
    -496
    -497/// Returns the given address as canonicalized IP address.
    -498///
    -499/// This checks that the address family is supported by our local socket.
    -500/// If it is supported, then the address is mapped to the respective IP address.
    -501/// If the given address is an IPv6 address, but our local socket doesn't support
    -502/// IPv6, then this returns `None`.
    -503pub(crate) fn map_to_local_socket_family(address: IpAddr, ipv6: bool) -> Option<IpAddr> {
    -504    let ip = match address {
    -505        IpAddr::V4(addr) if ipv6 => IpAddr::V6(addr.to_ipv6_mapped()),
    -506        IpAddr::V4(_) => address,
    -507        IpAddr::V6(_) if ipv6 => address,
    -508        IpAddr::V6(addr) => IpAddr::V4(addr.to_ipv4_mapped()?),
    -509    };
    -510    Some(ip)
    -511}
    -512
    -513#[cfg(test)]
    -514mod tests {
    -515    use super::*;
    -516
    -517    #[test]
    -518    fn test_basic_server_state() {
    -519        let mut state = ServerState::new(2, 2);
    -520
    -521        state
    -522            .handle_reach_out(
    -523                ReachOut {
    -524                    round: 1u32.into(),
    -525                    ip: std::net::Ipv4Addr::LOCALHOST.into(),
    -526                    port: 1,
    -527                },
    -528                true,
    -529            )
    -530            .unwrap();
    +276    /// Re-queues probes that have not yet succeeded or reached 0 remaining retries.
    +277    ///
    +278    /// After calling [`Self::retry_delay`] must be checked.
    +279    pub(crate) fn queue_retries(&mut self, ipv6: bool) {
    +280        match self {
    +281            Self::NotNegotiated => (),
    +282            Self::ClientSide(state) => state.queue_retries(ipv6),
    +283            Self::ServerSide(state) => state.queue_retries(),
    +284        };
    +285    }
    +286
    +287    /// Marks a remote as successful if the response matches a sent probe.
    +288    ///
    +289    /// Returns true if it was a response to one of the NAT traversal probes and a path
    +290    /// needs to be opened. Note that the NAT probes are not padded to 1200 bytes so only
    +291    /// the address is validated, but not the entire path.
    +292    pub(crate) fn handle_path_response(&mut self, src: FourTuple, challenge: u64) -> bool {
    +293        match self {
    +294            Self::NotNegotiated => false,
    +295            Self::ClientSide(state) => state.handle_path_response(src, challenge),
    +296            Self::ServerSide(state) => state.handle_path_response(src, challenge),
    +297        }
    +298    }
    +299
    +300    /// Returns the delay to arm the `NatTraversalProbeRetry` timer.
    +301    ///
    +302    /// `initial_rtt` must be [`TransportConfig::initial_rtt`] so retries are scaled to this
    +303    /// value.
    +304    ///
    +305    /// [`TransportConfig::initial_rtt`]: crate::TransportConfig::initial_rtt
    +306    pub(crate) fn retry_delay(&self, initial_rtt: Duration) -> Option<Duration> {
    +307        match self {
    +308            Self::NotNegotiated => return None,
    +309            Self::ClientSide(state) => {
    +310                if !state
    +311                    .remote_addresses
    +312                    .values()
    +313                    .any(|(_, probes)| probes.remaining() > 0)
    +314                {
    +315                    return None;
    +316                }
    +317            }
    +318            Self::ServerSide(state) => {
    +319                if !state.remotes.values().any(|probes| probes.remaining() > 0) {
    +320                    return None;
    +321                }
    +322            }
    +323        }
    +324
    +325        let attempt = match self {
    +326            Self::NotNegotiated => return None,
    +327            Self::ClientSide(state) => state.attempt,
    +328            Self::ServerSide(state) => state.attempt,
    +329        };
    +330
    +331        // Retries follow at an exponential backoff, capped at max 2s interval. The base
    +332        // delay is initial_rtt/10, which for the default value means 33.3ms. Just under
    +333        // 10_000 km at the speed of light.
    +334        const MAX_BACKOFF_EXPONENT: u8 = 8;
    +335        const MAX_INTERVAL: Duration = Duration::from_secs(2);
    +336        let base = initial_rtt / 10;
    +337        let attempt = attempt.min(MAX_BACKOFF_EXPONENT) as u32;
    +338        let interval = match attempt {
    +339            0 => base * 2u32.pow(attempt),
    +340            _ => base * 2u32.pow(attempt) - base * 2u32.pow(attempt - 1),
    +341        };
    +342        Some(interval.min(MAX_INTERVAL))
    +343    }
    +344}
    +345
    +346#[derive(Debug)]
    +347pub(crate) struct ClientState {
    +348    /// Max number of remote addresses we allow
    +349    ///
    +350    /// This is set by the local endpoint.
    +351    max_remote_addresses: usize,
    +352    /// Max number of local addresses allowed
    +353    ///
    +354    /// This is set by the remote endpoint.
    +355    max_local_addresses: usize,
    +356    /// Candidate addresses the remote endpoint advertises.
    +357    ///
    +358    /// These are addresses on which the server is potentially reachable, to use for NAT
    +359    /// traversal attempts.
    +360    ///
    +361    /// They are indexed by their ADD_ADDRESS sequence id and stored in **canonical
    +362    /// form**. Not in the socket-native form as usual. This because we need to store them
    +363    /// so we have the correct sequence IDs.
    +364    remote_addresses: FxHashMap<VarInt, (CanonicalIpPort, ProbeState)>,
    +365    /// Candidate addresses for the local endpoint.
    +366    ///
    +367    /// These are addresses on which we are potentially reachable, to use for NAT traversal
    +368    /// attempts.
    +369    ///
    +370    /// They are stored in **canonical form**, not in socket-native form as usual. We may
    +371    /// nave a reflexive address that is IPv6 even if our local socket can only handle IPv4.
    +372    local_addresses: FxHashSet<CanonicalIpPort>,
    +373    /// Current nat traversal round.
    +374    round: VarInt,
    +375    /// The probing attempt in the round.
    +376    ///
    +377    /// Probes are sent to all remotes at the same time in a round, at intervals from
    +378    /// [`State::retry_delay`]. This is the number of times probes have been sent.
    +379    attempt: u8,
    +380    /// The data of PATH_CHALLENGE frames sent in probes.
    +381    ///
    +382    /// These are cleared when a new round starts, so any late-arriving PATH_RESPONSEs will
    +383    /// have no effect.
    +384    ///
    +385    /// They are stored in the usual socket-native form.
    +386    sent_challenges: FxHashMap<u64, IpPort>,
    +387    /// Queued probes to be sent in the next [`poll_transmit`] call.
    +388    ///
    +389    /// [`poll_transmit`]: crate::connection::Connection::poll_transmit
    +390    ///
    +391    /// They are stored in the usual socket-native form. Probes to address families not
    +392    /// addressable by the family are never inserted.
    +393    pending_probes: FxHashSet<IpPort>,
    +394    /// Network paths that were successfully probed but not yet opened.
    +395    ///
    +396    /// When we do not have enough CIDs or free path IDs we might not have been able to open
    +397    /// a new path. This allows us to try re-open the path when we get new CIDs or a new
    +398    /// MAX_PATH_ID.
    +399    // TODO(flub): perhaps there should be a time-limit on these?
    +400    paths_to_be_opened: Vec<FourTuple>,
    +401}
    +402
    +403impl ClientState {
    +404    fn new(max_remote_addresses: usize, max_local_addresses: usize) -> Self {
    +405        Self {
    +406            max_remote_addresses,
    +407            max_local_addresses,
    +408            remote_addresses: Default::default(),
    +409            local_addresses: Default::default(),
    +410            round: Default::default(),
    +411            attempt: 0,
    +412            sent_challenges: Default::default(),
    +413            pending_probes: Default::default(),
    +414            paths_to_be_opened: Default::default(),
    +415        }
    +416    }
    +417
    +418    fn add_local_address(&mut self, address: SocketAddr) -> Result<(), Error> {
    +419        let address = CanonicalIpPort::from(address);
    +420        if self.local_addresses.len() < self.max_local_addresses {
    +421            self.local_addresses.insert(address);
    +422            Ok(())
    +423        } else if self.local_addresses.contains(&address) {
    +424            // at capacity, but the address is known, no issues here
    +425            Ok(())
    +426        } else {
    +427            // at capacity and the address is new
    +428            Err(Error::TooManyAddresses)
    +429        }
    +430    }
    +431
    +432    fn remove_local_address(&mut self, address: &IpPort) {
    +433        let address = CanonicalIpPort::from(*address);
    +434        self.local_addresses.remove(&address);
    +435    }
    +436
    +437    /// Initiates a new nat traversal round.
    +438    ///
    +439    /// A nat traversal round involves advertising the client's local addresses in
    +440    /// `REACH_OUT` frames, and initiating probing of the known remote addresses. When a new
    +441    /// round is initiated, the previous one is cancelled.
    +442    ///
    +443    /// `ipv6` indicates if the connection runs on a socket that supports IPv6. If so, then
    +444    /// all addresses returned [`PendingReachOutFrames`] will be IPv6 addresses (and
    +445    /// IPv4-mapped IPv6 addresses if necessary). Otherwise they're all IPv4 addresses.  See
    +446    /// also [`map_to_local_socket_family`].
    +447    ///
    +448    /// # Returns
    +449    ///
    +450    /// The REACH_OUT frames that need to be sent to the peer and the probed addresses. The
    +451    /// probed addresses are only informational, the pending probes are stored in
    +452    /// [`Self::pending_probes`].
    +453    ///
    +454    /// If the probed addresses are non-empty the `NatTraversalProbeRetry` timer needs to be
    +455    /// set.
    +456    pub(crate) fn initiate_nat_traversal_round(
    +457        &mut self,
    +458        ipv6: bool,
    +459    ) -> Result<(PendingReachOutFrames, Vec<SocketAddr>), Error> {
    +460        if self.local_addresses.is_empty() {
    +461            return Err(Error::NotEnoughAddresses);
    +462        }
    +463
    +464        self.round = self.round.saturating_add(1u8);
    +465        self.attempt = 0;
    +466        self.sent_challenges.clear();
    +467        self.pending_probes.clear();
    +468
    +469        // Enqueue the NAT probes to known remote addresses.
    +470        self.remote_addresses
    +471            .values_mut()
    +472            .for_each(|(ip_port, state)| {
    +473                if let Some(ip_port) = ip_port.as_local_socket_family(ipv6) {
    +474                    self.pending_probes.insert(ip_port);
    +475                    *state = ProbeState::Active(MAX_NAT_PROBE_ATTEMPTS - 1);
    +476                } else {
    +477                    trace!(%ip_port, "not using IPv6 NAT candidate for IPv4 socket");
    +478                    *state = ProbeState::Active(0);
    +479                }
    +480            });
    +481        let probed_addrs: Vec<SocketAddr> = self
    +482            .pending_probes
    +483            .iter()
    +484            .copied()
    +485            .map(Into::into)
    +486            .collect();
    +487
    +488        // Build the REACH_OUT frames.
    +489        let reach_out_frames: PendingReachOutFrames = self
    +490            .local_addresses
    +491            .iter()
    +492            .map(|ip_port| ReachOut {
    +493                round: self.round,
    +494                ip: ip_port.ip(),
    +495                port: ip_port.port(),
    +496            })
    +497            .collect();
    +498
    +499        trace!(
    +500            round = %self.round,
    +501            reach_out = %reach_out_frames.len(),
    +502            to_probe = %self.pending_probes.len(),
    +503            "initiating NAT traversal round",
    +504        );
    +505        Ok((reach_out_frames, probed_addrs))
    +506    }
    +507
    +508    /// Re-queues probes that have not yet succeeded or reached 0 remaining retries.
    +509    ///
    +510    /// Returns whether any probes are now queued to send. In this case the
    +511    /// `NatTraversalProbeRetry` timer needs to be reset.
    +512    ///
    +513    /// `ipv6` as for [`Self::initiate_nat_traversal_round`].
    +514    pub(crate) fn queue_retries(&mut self, ipv6: bool) {
    +515        self.attempt += 1;
    +516        self.remote_addresses
    +517            .values_mut()
    +518            .for_each(|(ip_port, state)| match state {
    +519                ProbeState::Active(remaining) if *remaining > 0 => {
    +520                    *remaining -= 1;
    +521                    if let Some(ip_port) = ip_port.as_local_socket_family(ipv6) {
    +522                        self.pending_probes.insert(ip_port);
    +523                    } else {
    +524                        trace!(%ip_port, "skipping IPv6 NAT candidate for IPv4 socket");
    +525                        *remaining = 0;
    +526                    }
    +527                }
    +528                ProbeState::Active(_) | ProbeState::Succeeded => {}
    +529            });
    +530    }
     531
    -532        state
    -533            .handle_reach_out(
    -534                ReachOut {
    -535                    round: 1u32.into(),
    -536                    ip: "1.1.1.1".parse().unwrap(), //std::net::Ipv4Addr::LOCALHOST.into(),
    -537                    port: 2,
    -538                },
    -539                true,
    -540            )
    -541            .unwrap();
    -542
    -543        dbg!(&state);
    -544        assert_eq!(state.pending_probes.len(), 2);
    -545
    -546        let probe = state.next_probe().unwrap();
    -547        probe.mark_as_sent();
    -548        let probe = state.next_probe().unwrap();
    -549        probe.mark_as_sent();
    -550
    -551        assert!(state.next_probe().is_none());
    -552        assert_eq!(state.pending_probes.len(), 0);
    -553    }
    -554
    -555    #[test]
    -556    fn test_map_to_local_socket() {
    -557        assert_eq!(
    -558            map_to_local_socket_family("1.1.1.1".parse().unwrap(), false),
    -559            Some("1.1.1.1".parse().unwrap())
    -560        );
    -561        assert_eq!(
    -562            map_to_local_socket_family("1.1.1.1".parse().unwrap(), true),
    -563            Some("::ffff:1.1.1.1".parse().unwrap())
    -564        );
    -565        assert_eq!(
    -566            map_to_local_socket_family("::1".parse().unwrap(), true),
    -567            Some("::1".parse().unwrap())
    -568        );
    -569        assert_eq!(
    -570            map_to_local_socket_family("::1".parse().unwrap(), false),
    -571            None
    -572        );
    -573        assert_eq!(
    -574            map_to_local_socket_family("::ffff:1.1.1.1".parse().unwrap(), false),
    -575            Some("1.1.1.1".parse().unwrap())
    -576        )
    -577    }
    -578}
    \ No newline at end of file +532 /// Returns the next ready probe's address. +533 /// +534 /// If this is actually sent you must call [`Self::mark_probe_sent`]. +535 fn next_probe_addr(&self) -> Option<IpPort> { +536 self.pending_probes.iter().next().copied() +537 } +538 +539 /// Marks a probe as sent to the address with the challenge. +540 fn mark_probe_sent(&mut self, remote: IpPort, challenge: u64) { +541 self.pending_probes.remove(&remote); +542 self.sent_challenges.insert(challenge, remote); +543 } +544 +545 /// Adds an address to the remote set. +546 /// +547 /// On success returns the address if it was new to the set. It will error when the set +548 /// has no capacity for the address. +549 /// +550 /// If this is called while a round is in progress this will effectively add the address +551 /// to the current round. There is no guarantee however that the current round is still +552 /// in progress however, if the last [`Self::queue_retries`] call returned `false` the +553 /// round has stopped. +554 // TODO(flub): probably should add an event to signal that the round is finished, so +555 // that the application knows to start a new round. +556 pub(crate) fn add_remote_address( +557 &mut self, +558 add_addr: AddAddress, +559 ) -> Result<Option<SocketAddr>, Error> { +560 let AddAddress { seq_no, ip, port } = add_addr; +561 let address = CanonicalIpPort::from((ip, port)); +562 let allow_new = self.remote_addresses.len() < self.max_remote_addresses; +563 match self.remote_addresses.entry(seq_no) { +564 Entry::Occupied(mut occupied_entry) => { +565 let is_update = occupied_entry.get().0 != address; +566 if is_update { +567 occupied_entry.insert((address, ProbeState::Active(MAX_NAT_PROBE_ATTEMPTS))); +568 } +569 // The value might be different. This should not happen, but we assume that the new +570 // address is more recent than the previous, and thus worth updating +571 Ok(is_update.then_some(address.as_canonical_addr())) +572 } +573 Entry::Vacant(vacant_entry) if allow_new => { +574 vacant_entry.insert((address, ProbeState::Active(MAX_NAT_PROBE_ATTEMPTS))); +575 Ok(Some(address.as_canonical_addr())) +576 } +577 _ => Err(Error::TooManyAddresses), +578 } +579 } +580 +581 /// Removes an address from the remote set. +582 /// +583 /// Returns whether the address was present. +584 pub(crate) fn remove_remote_address( +585 &mut self, +586 remove_addr: RemoveAddress, +587 ) -> Option<SocketAddr> { +588 self.remote_addresses +589 .remove(&remove_addr.seq_no) +590 .map(|(address, _)| address.as_canonical_addr()) +591 } +592 +593 /// Checks that a received remote address is valid. +594 /// +595 /// An address is valid as long as it does not change the value of a known address id. +596 pub(crate) fn check_remote_address(&self, add_addr: &AddAddress) -> bool { +597 match self.remote_addresses.get(&add_addr.seq_no) { +598 None => true, +599 Some((existing, _)) => *existing == CanonicalIpPort::from(add_addr.ip_port()), +600 } +601 } +602 +603 pub(crate) fn get_remote_nat_traversal_addresses(&self) -> Vec<SocketAddr> { +604 self.remote_addresses +605 .values() +606 .map(|(address, _)| (*address).as_canonical_addr()) +607 .collect() +608 } +609 +610 /// Marks a remote as successful if the response matches a sent probe. +611 /// +612 /// Returns `true` if it was a response to one of the NAT traversal probes. In that case +613 /// [`Self::pop_pending_path_open`] should be called to open the next path. +614 fn handle_path_response(&mut self, network_path: FourTuple, challenge: u64) -> bool { +615 if let Entry::Occupied(entry) = self.sent_challenges.entry(challenge) { +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. +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. +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"); +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; +891 +892 use super::*; +893 +894 #[test] +895 fn test_basic_server_state() { +896 let mut state = ServerState::new(2, 2); +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(); +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 ); +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()) +968 ); +969 assert_eq!( +970 map_to_local_socket_family("::1".parse().unwrap(), true), +971 Some("::1".parse().unwrap()) +972 ); +973 assert_eq!( +974 map_to_local_socket_family("::1".parse().unwrap(), false), +975 None +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 diff --git a/pr/451/docs/src/noq_proto/packet.rs.html b/pr/451/docs/src/noq_proto/packet.rs.html index f7b5e138e..541cd3277 100644 --- a/pr/451/docs/src/noq_proto/packet.rs.html +++ b/pr/451/docs/src/noq_proto/packet.rs.html @@ -233,857 +233,863 @@ 233 } 234} 235 -236pub(crate) struct Packet { -237 pub(crate) header: Header, -238 pub(crate) header_data: Bytes, -239 pub(crate) payload: BytesMut, -240} -241 -242impl Packet { -243 pub(crate) fn reserved_bits_valid(&self) -> bool { -244 let mask = match self.header { -245 Header::Short { .. } => SHORT_RESERVED_BITS, -246 _ => LONG_RESERVED_BITS, -247 }; -248 self.header_data[0] & mask == 0 -249 } -250} -251 -252pub(crate) struct InitialPacket { -253 pub(crate) header: InitialHeader, -254 pub(crate) header_data: Bytes, -255 pub(crate) payload: BytesMut, +236/// A received packet with header protection removed. +237// TODO(flub): Would be grand to make this typesafe by adding a generic that indicates +238// whether the payload is encrypted or decrypted. +239pub(crate) struct Packet { +240 /// The decoded header. +241 pub(crate) header: Header, +242 /// The encoded header data, with header protection removed (i.e. decrypted). +243 pub(crate) header_data: Bytes, +244 /// Packet payload, still encrypted when created, later decrypted in-place. +245 pub(crate) payload: BytesMut, +246} +247 +248impl Packet { +249 pub(crate) fn reserved_bits_valid(&self) -> bool { +250 let mask = match self.header { +251 Header::Short { .. } => SHORT_RESERVED_BITS, +252 _ => LONG_RESERVED_BITS, +253 }; +254 self.header_data[0] & mask == 0 +255 } 256} 257 -258impl From<InitialPacket> for Packet { -259 fn from(x: InitialPacket) -> Self { -260 Self { -261 header: Header::Initial(x.header), -262 header_data: x.header_data, -263 payload: x.payload, -264 } -265 } -266} -267 -268#[cfg_attr(test, derive(Clone))] -269#[derive(Debug)] -270pub(crate) enum Header { -271 Initial(InitialHeader), -272 Long { -273 ty: LongType, -274 dst_cid: ConnectionId, -275 src_cid: ConnectionId, -276 number: PacketNumber, -277 version: u32, -278 }, -279 Retry { +258pub(crate) struct InitialPacket { +259 pub(crate) header: InitialHeader, +260 pub(crate) header_data: Bytes, +261 pub(crate) payload: BytesMut, +262} +263 +264impl From<InitialPacket> for Packet { +265 fn from(x: InitialPacket) -> Self { +266 Self { +267 header: Header::Initial(x.header), +268 header_data: x.header_data, +269 payload: x.payload, +270 } +271 } +272} +273 +274#[cfg_attr(test, derive(Clone))] +275#[derive(Debug)] +276pub(crate) enum Header { +277 Initial(InitialHeader), +278 Long { +279 ty: LongType, 280 dst_cid: ConnectionId, 281 src_cid: ConnectionId, -282 version: u32, -283 }, -284 Short { -285 spin: bool, -286 key_phase: bool, -287 dst_cid: ConnectionId, -288 number: PacketNumber, +282 number: PacketNumber, +283 version: u32, +284 }, +285 Retry { +286 dst_cid: ConnectionId, +287 src_cid: ConnectionId, +288 version: u32, 289 }, -290 VersionNegotiate { -291 random: u8, -292 src_cid: ConnectionId, +290 Short { +291 spin: bool, +292 key_phase: bool, 293 dst_cid: ConnectionId, -294 }, -295} -296 -297impl Header { -298 pub(crate) fn encode(&self, w: &mut (impl BufMut + BufLen)) -> PartialEncode { -299 use Header::*; -300 let start = w.len(); -301 match *self { -302 Initial(InitialHeader { -303 ref dst_cid, -304 ref src_cid, -305 ref token, -306 number, -307 version, -308 }) => { -309 w.write(u8::from(LongHeaderType::Initial) | number.tag()); -310 w.write(version); -311 dst_cid.encode_long(w); -312 src_cid.encode_long(w); -313 w.write_var(token.len() as u64); -314 w.put_slice(token); -315 w.write::<u16>(0); // Placeholder for payload length; see `set_payload_length` -316 number.encode(w); -317 PartialEncode { -318 start, -319 header_len: w.len() - start, -320 pn: Some((number.len(), true)), -321 } -322 } -323 Long { -324 ty, -325 ref dst_cid, -326 ref src_cid, -327 number, -328 version, -329 } => { -330 w.write(u8::from(LongHeaderType::Standard(ty)) | number.tag()); -331 w.write(version); -332 dst_cid.encode_long(w); -333 src_cid.encode_long(w); -334 w.write::<u16>(0); // Placeholder for payload length; see `set_payload_length` -335 number.encode(w); -336 PartialEncode { -337 start, -338 header_len: w.len() - start, -339 pn: Some((number.len(), true)), -340 } -341 } -342 Retry { -343 ref dst_cid, -344 ref src_cid, -345 version, -346 } => { -347 w.write(u8::from(LongHeaderType::Retry)); -348 w.write(version); -349 dst_cid.encode_long(w); -350 src_cid.encode_long(w); -351 PartialEncode { -352 start, -353 header_len: w.len() - start, -354 pn: None, -355 } -356 } -357 Short { -358 spin, -359 key_phase, -360 ref dst_cid, -361 number, -362 } => { -363 w.write( -364 FIXED_BIT -365 | if key_phase { KEY_PHASE_BIT } else { 0 } -366 | if spin { SPIN_BIT } else { 0 } -367 | number.tag(), -368 ); -369 w.put_slice(dst_cid); -370 number.encode(w); -371 PartialEncode { -372 start, -373 header_len: w.len() - start, -374 pn: Some((number.len(), false)), -375 } -376 } -377 VersionNegotiate { -378 ref random, -379 ref dst_cid, -380 ref src_cid, -381 } => { -382 w.write(0x80u8 | random); -383 w.write::<u32>(0); -384 dst_cid.encode_long(w); -385 src_cid.encode_long(w); -386 PartialEncode { -387 start, -388 header_len: w.len() - start, -389 pn: None, -390 } -391 } -392 } -393 } -394 -395 /// Whether the packet is encrypted on the wire -396 pub(crate) fn is_protected(&self) -> bool { -397 !matches!(*self, Self::Retry { .. } | Self::VersionNegotiate { .. }) -398 } -399 -400 pub(crate) fn number(&self) -> Option<PacketNumber> { -401 use Header::*; -402 Some(match *self { -403 Initial(InitialHeader { number, .. }) => number, -404 Long { number, .. } => number, -405 Short { number, .. } => number, -406 _ => { -407 return None; -408 } -409 }) -410 } -411 -412 pub(crate) fn space(&self) -> SpaceKind { -413 use Header::*; -414 match *self { -415 Short { .. } => SpaceKind::Data, -416 Long { -417 ty: LongType::ZeroRtt, -418 .. -419 } => SpaceKind::Data, -420 Long { -421 ty: LongType::Handshake, -422 .. -423 } => SpaceKind::Handshake, -424 _ => SpaceKind::Initial, -425 } -426 } -427 -428 pub(crate) fn key_phase(&self) -> bool { -429 match *self { -430 Self::Short { key_phase, .. } => key_phase, -431 _ => false, -432 } -433 } -434 -435 pub(crate) fn is_short(&self) -> bool { -436 matches!(*self, Self::Short { .. }) -437 } -438 -439 pub(crate) fn is_1rtt(&self) -> bool { -440 self.is_short() -441 } -442 -443 pub(crate) fn is_0rtt(&self) -> bool { -444 matches!( -445 *self, -446 Self::Long { -447 ty: LongType::ZeroRtt, -448 .. -449 } -450 ) -451 } -452 -453 pub(crate) fn dst_cid(&self) -> ConnectionId { -454 use Header::*; -455 match *self { -456 Initial(InitialHeader { dst_cid, .. }) => dst_cid, -457 Long { dst_cid, .. } => dst_cid, -458 Retry { dst_cid, .. } => dst_cid, -459 Short { dst_cid, .. } => dst_cid, -460 VersionNegotiate { dst_cid, .. } => dst_cid, -461 } -462 } -463 -464 /// Is this packet allowed to be coalesced with others? -465 /// -466 /// Ref <https://www.rfc-editor.org/rfc/rfc9000.html#name-coalescing-packets> -467 pub(crate) fn can_coalesce(&self) -> bool { -468 use Header::*; -469 match *self { -470 Initial(_) => true, -471 Long { .. } => true, -472 Retry { .. } => false, -473 Short { .. } => false, -474 VersionNegotiate { .. } => false, -475 } -476 } -477 -478 /// Whether the payload of this packet contains QUIC frames -479 pub(crate) fn has_frames(&self) -> bool { -480 use Header::*; -481 match *self { -482 Initial(_) => true, -483 Long { .. } => true, -484 Retry { .. } => false, -485 Short { .. } => true, -486 VersionNegotiate { .. } => false, -487 } -488 } -489 -490 #[cfg(feature = "qlog")] -491 pub(crate) fn src_cid(&self) -> Option<ConnectionId> { -492 match self { -493 Self::Initial(initial_header) => Some(initial_header.src_cid), -494 Self::Long { src_cid, .. } => Some(*src_cid), -495 Self::Retry { src_cid, .. } => Some(*src_cid), -496 Self::Short { .. } => None, -497 Self::VersionNegotiate { src_cid, .. } => Some(*src_cid), -498 } -499 } -500} -501 -502pub(crate) struct PartialEncode { -503 pub(crate) start: usize, -504 pub(crate) header_len: usize, -505 // Packet number length, payload length needed -506 pn: Option<(usize, bool)>, -507} -508 -509impl PartialEncode { -510 pub(crate) fn finish( -511 self, -512 buf: &mut [u8], -513 header_crypto: &dyn crypto::HeaderKey, -514 crypto: Option<(u64, PathId, &dyn crypto::PacketKey)>, -515 ) { -516 let Self { header_len, pn, .. } = self; -517 let Some((pn_len, write_len)) = pn else { -518 return; -519 }; -520 -521 let pn_pos = header_len - pn_len; -522 if write_len { -523 let len = buf.len() - header_len + pn_len; -524 assert!(len < 2usize.pow(14)); // Fits in reserved space -525 let mut slice = &mut buf[pn_pos - 2..pn_pos]; -526 slice.put_u16(len as u16 | (0b01 << 14)); -527 } -528 -529 if let Some((packet_number, path_id, crypto)) = crypto { -530 crypto.encrypt(path_id, packet_number, buf, header_len); -531 } -532 -533 debug_assert!( -534 pn_pos + 4 + header_crypto.sample_size() <= buf.len(), -535 "packet must be padded to at least {} bytes for header protection sampling", -536 pn_pos + 4 + header_crypto.sample_size() -537 ); -538 header_crypto.encrypt(pn_pos, buf); -539 } -540 -541 /// Creates a [`PartialEncode`] that has not encoded a header into the buffer. -542 /// -543 /// This is used exclusively for testing as such a type is otherwise invalid. -544 #[cfg(test)] -545 pub(crate) fn no_header() -> Self { -546 Self { -547 start: 0, -548 header_len: 0, -549 pn: None, -550 } -551 } -552} -553 -554/// Plain packet header -555#[derive(Clone, Debug)] -556pub enum ProtectedHeader { -557 /// An Initial packet header -558 Initial(ProtectedInitialHeader), -559 /// A Long packet header, as used during the handshake -560 Long { -561 /// Type of the Long header packet -562 ty: LongType, -563 /// Destination Connection ID -564 dst_cid: ConnectionId, -565 /// Source Connection ID -566 src_cid: ConnectionId, -567 /// Length of the packet payload -568 len: u64, -569 /// QUIC version -570 version: u32, -571 }, -572 /// A Retry packet header -573 Retry { -574 /// Destination Connection ID -575 dst_cid: ConnectionId, -576 /// Source Connection ID -577 src_cid: ConnectionId, -578 /// QUIC version -579 version: u32, -580 }, -581 /// A short packet header, as used during the data phase -582 Short { -583 /// Spin bit -584 spin: bool, -585 /// Destination Connection ID -586 dst_cid: ConnectionId, -587 }, -588 /// A Version Negotiation packet header -589 VersionNegotiate { -590 /// Random value -591 random: u8, -592 /// Destination Connection ID -593 dst_cid: ConnectionId, -594 /// Source Connection ID -595 src_cid: ConnectionId, -596 }, -597} -598 -599impl ProtectedHeader { -600 fn as_initial(&self) -> Option<&ProtectedInitialHeader> { -601 match self { -602 Self::Initial(x) => Some(x), -603 _ => None, -604 } -605 } -606 -607 /// The destination Connection ID of the packet -608 pub fn dst_cid(&self) -> ConnectionId { -609 use ProtectedHeader::*; -610 match self { -611 Initial(header) => header.dst_cid, -612 &Long { dst_cid, .. } => dst_cid, -613 &Retry { dst_cid, .. } => dst_cid, -614 &Short { dst_cid, .. } => dst_cid, -615 &VersionNegotiate { dst_cid, .. } => dst_cid, -616 } -617 } -618 -619 fn payload_len(&self) -> Option<u64> { -620 use ProtectedHeader::*; -621 match self { -622 Initial(ProtectedInitialHeader { len, .. }) | Long { len, .. } => Some(*len), -623 _ => None, -624 } -625 } -626 -627 /// Decode a plain header from given buffer, with given [`ConnectionIdParser`]. -628 pub fn decode( -629 buf: &mut io::Cursor<BytesMut>, -630 cid_parser: &(impl ConnectionIdParser + ?Sized), -631 supported_versions: &[u32], -632 grease_quic_bit: bool, -633 ) -> Result<Self, PacketDecodeError> { -634 let first = buf.get::<u8>()?; -635 if !grease_quic_bit && first & FIXED_BIT == 0 { -636 return Err(PacketDecodeError::InvalidHeader("fixed bit unset")); -637 } -638 if first & LONG_HEADER_FORM == 0 { -639 let spin = first & SPIN_BIT != 0; -640 -641 Ok(Self::Short { -642 spin, -643 dst_cid: cid_parser.parse(buf)?, -644 }) -645 } else { -646 let version = buf.get::<u32>()?; -647 -648 let dst_cid = ConnectionId::decode_long(buf) -649 .ok_or(PacketDecodeError::InvalidHeader("malformed cid"))?; -650 let src_cid = ConnectionId::decode_long(buf) -651 .ok_or(PacketDecodeError::InvalidHeader("malformed cid"))?; -652 -653 // TODO: Support long CIDs for compatibility with future QUIC versions -654 if version == 0 { -655 let random = first & !LONG_HEADER_FORM; -656 return Ok(Self::VersionNegotiate { -657 random, -658 dst_cid, -659 src_cid, -660 }); -661 } -662 -663 if !supported_versions.contains(&version) { -664 return Err(PacketDecodeError::UnsupportedVersion { +294 number: PacketNumber, +295 }, +296 VersionNegotiate { +297 random: u8, +298 src_cid: ConnectionId, +299 dst_cid: ConnectionId, +300 }, +301} +302 +303impl Header { +304 pub(crate) fn encode(&self, w: &mut (impl BufMut + BufLen)) -> PartialEncode { +305 use Header::*; +306 let start = w.len(); +307 match *self { +308 Initial(InitialHeader { +309 ref dst_cid, +310 ref src_cid, +311 ref token, +312 number, +313 version, +314 }) => { +315 w.write(u8::from(LongHeaderType::Initial) | number.tag()); +316 w.write(version); +317 dst_cid.encode_long(w); +318 src_cid.encode_long(w); +319 w.write_var(token.len() as u64); +320 w.put_slice(token); +321 w.write::<u16>(0); // Placeholder for payload length; see `set_payload_length` +322 number.encode(w); +323 PartialEncode { +324 start, +325 header_len: w.len() - start, +326 pn: Some((number.len(), true)), +327 } +328 } +329 Long { +330 ty, +331 ref dst_cid, +332 ref src_cid, +333 number, +334 version, +335 } => { +336 w.write(u8::from(LongHeaderType::Standard(ty)) | number.tag()); +337 w.write(version); +338 dst_cid.encode_long(w); +339 src_cid.encode_long(w); +340 w.write::<u16>(0); // Placeholder for payload length; see `set_payload_length` +341 number.encode(w); +342 PartialEncode { +343 start, +344 header_len: w.len() - start, +345 pn: Some((number.len(), true)), +346 } +347 } +348 Retry { +349 ref dst_cid, +350 ref src_cid, +351 version, +352 } => { +353 w.write(u8::from(LongHeaderType::Retry)); +354 w.write(version); +355 dst_cid.encode_long(w); +356 src_cid.encode_long(w); +357 PartialEncode { +358 start, +359 header_len: w.len() - start, +360 pn: None, +361 } +362 } +363 Short { +364 spin, +365 key_phase, +366 ref dst_cid, +367 number, +368 } => { +369 w.write( +370 FIXED_BIT +371 | if key_phase { KEY_PHASE_BIT } else { 0 } +372 | if spin { SPIN_BIT } else { 0 } +373 | number.tag(), +374 ); +375 w.put_slice(dst_cid); +376 number.encode(w); +377 PartialEncode { +378 start, +379 header_len: w.len() - start, +380 pn: Some((number.len(), false)), +381 } +382 } +383 VersionNegotiate { +384 ref random, +385 ref dst_cid, +386 ref src_cid, +387 } => { +388 w.write(0x80u8 | random); +389 w.write::<u32>(0); +390 dst_cid.encode_long(w); +391 src_cid.encode_long(w); +392 PartialEncode { +393 start, +394 header_len: w.len() - start, +395 pn: None, +396 } +397 } +398 } +399 } +400 +401 /// Whether the packet is encrypted on the wire +402 pub(crate) fn is_protected(&self) -> bool { +403 !matches!(*self, Self::Retry { .. } | Self::VersionNegotiate { .. }) +404 } +405 +406 pub(crate) fn number(&self) -> Option<PacketNumber> { +407 use Header::*; +408 Some(match *self { +409 Initial(InitialHeader { number, .. }) => number, +410 Long { number, .. } => number, +411 Short { number, .. } => number, +412 _ => { +413 return None; +414 } +415 }) +416 } +417 +418 pub(crate) fn space(&self) -> SpaceKind { +419 use Header::*; +420 match *self { +421 Short { .. } => SpaceKind::Data, +422 Long { +423 ty: LongType::ZeroRtt, +424 .. +425 } => SpaceKind::Data, +426 Long { +427 ty: LongType::Handshake, +428 .. +429 } => SpaceKind::Handshake, +430 _ => SpaceKind::Initial, +431 } +432 } +433 +434 pub(crate) fn key_phase(&self) -> bool { +435 match *self { +436 Self::Short { key_phase, .. } => key_phase, +437 _ => false, +438 } +439 } +440 +441 pub(crate) fn is_short(&self) -> bool { +442 matches!(*self, Self::Short { .. }) +443 } +444 +445 pub(crate) fn is_1rtt(&self) -> bool { +446 self.is_short() +447 } +448 +449 pub(crate) fn is_0rtt(&self) -> bool { +450 matches!( +451 *self, +452 Self::Long { +453 ty: LongType::ZeroRtt, +454 .. +455 } +456 ) +457 } +458 +459 pub(crate) fn dst_cid(&self) -> ConnectionId { +460 use Header::*; +461 match *self { +462 Initial(InitialHeader { dst_cid, .. }) => dst_cid, +463 Long { dst_cid, .. } => dst_cid, +464 Retry { dst_cid, .. } => dst_cid, +465 Short { dst_cid, .. } => dst_cid, +466 VersionNegotiate { dst_cid, .. } => dst_cid, +467 } +468 } +469 +470 /// Is this packet allowed to be coalesced with others? +471 /// +472 /// Ref <https://www.rfc-editor.org/rfc/rfc9000.html#name-coalescing-packets> +473 pub(crate) fn can_coalesce(&self) -> bool { +474 use Header::*; +475 match *self { +476 Initial(_) => true, +477 Long { .. } => true, +478 Retry { .. } => false, +479 Short { .. } => false, +480 VersionNegotiate { .. } => false, +481 } +482 } +483 +484 /// Whether the payload of this packet contains QUIC frames +485 pub(crate) fn has_frames(&self) -> bool { +486 use Header::*; +487 match *self { +488 Initial(_) => true, +489 Long { .. } => true, +490 Retry { .. } => false, +491 Short { .. } => true, +492 VersionNegotiate { .. } => false, +493 } +494 } +495 +496 #[cfg(feature = "qlog")] +497 pub(crate) fn src_cid(&self) -> Option<ConnectionId> { +498 match self { +499 Self::Initial(initial_header) => Some(initial_header.src_cid), +500 Self::Long { src_cid, .. } => Some(*src_cid), +501 Self::Retry { src_cid, .. } => Some(*src_cid), +502 Self::Short { .. } => None, +503 Self::VersionNegotiate { src_cid, .. } => Some(*src_cid), +504 } +505 } +506} +507 +508pub(crate) struct PartialEncode { +509 pub(crate) start: usize, +510 pub(crate) header_len: usize, +511 // Packet number length, payload length needed +512 pn: Option<(usize, bool)>, +513} +514 +515impl PartialEncode { +516 pub(crate) fn finish( +517 self, +518 buf: &mut [u8], +519 header_crypto: &dyn crypto::HeaderKey, +520 crypto: Option<(u64, PathId, &dyn crypto::PacketKey)>, +521 ) { +522 let Self { header_len, pn, .. } = self; +523 let Some((pn_len, write_len)) = pn else { +524 return; +525 }; +526 +527 let pn_pos = header_len - pn_len; +528 if write_len { +529 let len = buf.len() - header_len + pn_len; +530 assert!(len < 2usize.pow(14)); // Fits in reserved space +531 let mut slice = &mut buf[pn_pos - 2..pn_pos]; +532 slice.put_u16(len as u16 | (0b01 << 14)); +533 } +534 +535 if let Some((packet_number, path_id, crypto)) = crypto { +536 crypto.encrypt(path_id, packet_number, buf, header_len); +537 } +538 +539 debug_assert!( +540 pn_pos + 4 + header_crypto.sample_size() <= buf.len(), +541 "packet must be padded to at least {} bytes for header protection sampling", +542 pn_pos + 4 + header_crypto.sample_size() +543 ); +544 header_crypto.encrypt(pn_pos, buf); +545 } +546 +547 /// Creates a [`PartialEncode`] that has not encoded a header into the buffer. +548 /// +549 /// This is used exclusively for testing as such a type is otherwise invalid. +550 #[cfg(test)] +551 pub(crate) fn no_header() -> Self { +552 Self { +553 start: 0, +554 header_len: 0, +555 pn: None, +556 } +557 } +558} +559 +560/// Plain packet header +561#[derive(Clone, Debug)] +562pub enum ProtectedHeader { +563 /// An Initial packet header +564 Initial(ProtectedInitialHeader), +565 /// A Long packet header, as used during the handshake +566 Long { +567 /// Type of the Long header packet +568 ty: LongType, +569 /// Destination Connection ID +570 dst_cid: ConnectionId, +571 /// Source Connection ID +572 src_cid: ConnectionId, +573 /// Length of the packet payload +574 len: u64, +575 /// QUIC version +576 version: u32, +577 }, +578 /// A Retry packet header +579 Retry { +580 /// Destination Connection ID +581 dst_cid: ConnectionId, +582 /// Source Connection ID +583 src_cid: ConnectionId, +584 /// QUIC version +585 version: u32, +586 }, +587 /// A short packet header, as used during the data phase +588 Short { +589 /// Spin bit +590 spin: bool, +591 /// Destination Connection ID +592 dst_cid: ConnectionId, +593 }, +594 /// A Version Negotiation packet header +595 VersionNegotiate { +596 /// Random value +597 random: u8, +598 /// Destination Connection ID +599 dst_cid: ConnectionId, +600 /// Source Connection ID +601 src_cid: ConnectionId, +602 }, +603} +604 +605impl ProtectedHeader { +606 fn as_initial(&self) -> Option<&ProtectedInitialHeader> { +607 match self { +608 Self::Initial(x) => Some(x), +609 _ => None, +610 } +611 } +612 +613 /// The destination Connection ID of the packet +614 pub fn dst_cid(&self) -> ConnectionId { +615 use ProtectedHeader::*; +616 match self { +617 Initial(header) => header.dst_cid, +618 &Long { dst_cid, .. } => dst_cid, +619 &Retry { dst_cid, .. } => dst_cid, +620 &Short { dst_cid, .. } => dst_cid, +621 &VersionNegotiate { dst_cid, .. } => dst_cid, +622 } +623 } +624 +625 fn payload_len(&self) -> Option<u64> { +626 use ProtectedHeader::*; +627 match self { +628 Initial(ProtectedInitialHeader { len, .. }) | Long { len, .. } => Some(*len), +629 _ => None, +630 } +631 } +632 +633 /// Decode a plain header from given buffer, with given [`ConnectionIdParser`]. +634 pub fn decode( +635 buf: &mut io::Cursor<BytesMut>, +636 cid_parser: &(impl ConnectionIdParser + ?Sized), +637 supported_versions: &[u32], +638 grease_quic_bit: bool, +639 ) -> Result<Self, PacketDecodeError> { +640 let first = buf.get::<u8>()?; +641 if !grease_quic_bit && first & FIXED_BIT == 0 { +642 return Err(PacketDecodeError::InvalidHeader("fixed bit unset")); +643 } +644 if first & LONG_HEADER_FORM == 0 { +645 let spin = first & SPIN_BIT != 0; +646 +647 Ok(Self::Short { +648 spin, +649 dst_cid: cid_parser.parse(buf)?, +650 }) +651 } else { +652 let version = buf.get::<u32>()?; +653 +654 let dst_cid = ConnectionId::decode_long(buf) +655 .ok_or(PacketDecodeError::InvalidHeader("malformed cid"))?; +656 let src_cid = ConnectionId::decode_long(buf) +657 .ok_or(PacketDecodeError::InvalidHeader("malformed cid"))?; +658 +659 // TODO: Support long CIDs for compatibility with future QUIC versions +660 if version == 0 { +661 let random = first & !LONG_HEADER_FORM; +662 return Ok(Self::VersionNegotiate { +663 random, +664 dst_cid, 665 src_cid, -666 dst_cid, -667 version, -668 }); -669 } -670 -671 match LongHeaderType::from_byte(first)? { -672 LongHeaderType::Initial => { -673 let token_len = buf.get_var()? as usize; -674 let token_start = buf.position() as usize; -675 if token_len > buf.remaining() { -676 return Err(PacketDecodeError::InvalidHeader("token out of bounds")); -677 } -678 buf.advance(token_len); -679 -680 let len = buf.get_var()?; -681 Ok(Self::Initial(ProtectedInitialHeader { -682 dst_cid, -683 src_cid, -684 token_pos: token_start..token_start + token_len, -685 len, -686 version, -687 })) -688 } -689 LongHeaderType::Retry => Ok(Self::Retry { -690 dst_cid, -691 src_cid, -692 version, -693 }), -694 LongHeaderType::Standard(ty) => Ok(Self::Long { -695 ty, +666 }); +667 } +668 +669 if !supported_versions.contains(&version) { +670 return Err(PacketDecodeError::UnsupportedVersion { +671 src_cid, +672 dst_cid, +673 version, +674 }); +675 } +676 +677 match LongHeaderType::from_byte(first)? { +678 LongHeaderType::Initial => { +679 let token_len = buf.get_var()? as usize; +680 let token_start = buf.position() as usize; +681 if token_len > buf.remaining() { +682 return Err(PacketDecodeError::InvalidHeader("token out of bounds")); +683 } +684 buf.advance(token_len); +685 +686 let len = buf.get_var()?; +687 Ok(Self::Initial(ProtectedInitialHeader { +688 dst_cid, +689 src_cid, +690 token_pos: token_start..token_start + token_len, +691 len, +692 version, +693 })) +694 } +695 LongHeaderType::Retry => Ok(Self::Retry { 696 dst_cid, 697 src_cid, -698 len: buf.get_var()?, -699 version, -700 }), -701 } -702 } -703 } -704} -705 -706/// Header of an Initial packet, before decryption -707#[derive(Clone, Debug)] -708pub struct ProtectedInitialHeader { -709 /// Destination Connection ID -710 pub dst_cid: ConnectionId, -711 /// Source Connection ID -712 pub src_cid: ConnectionId, -713 /// The position of a token in the packet buffer -714 pub token_pos: Range<usize>, -715 /// Length of the packet payload -716 pub len: u64, -717 /// QUIC version -718 pub version: u32, -719} -720 -721#[derive(Clone, Debug)] -722pub(crate) struct InitialHeader { -723 pub(crate) dst_cid: ConnectionId, -724 pub(crate) src_cid: ConnectionId, -725 pub(crate) token: Bytes, -726 pub(crate) number: PacketNumber, -727 pub(crate) version: u32, -728} -729 -730// An encoded packet number -731#[derive(Debug, Copy, Clone, Eq, PartialEq)] -732pub(crate) enum PacketNumber { -733 U8(u8), -734 U16(u16), -735 U24(u32), -736 U32(u32), -737} -738 -739impl PacketNumber { -740 pub(crate) fn new(n: u64, largest_acked: u64) -> Self { -741 let range = (n - largest_acked) * 2; -742 if range < 1 << 8 { -743 Self::U8(n as u8) -744 } else if range < 1 << 16 { -745 Self::U16(n as u16) -746 } else if range < 1 << 24 { -747 Self::U24(n as u32) -748 } else if range < 1 << 32 { -749 Self::U32(n as u32) -750 } else { -751 panic!("packet number too large to encode") -752 } -753 } -754 -755 pub(crate) fn len(self) -> usize { -756 use PacketNumber::*; -757 match self { -758 U8(_) => 1, -759 U16(_) => 2, -760 U24(_) => 3, -761 U32(_) => 4, -762 } -763 } -764 -765 pub(crate) fn encode<W: BufMut>(self, w: &mut W) { -766 use PacketNumber::*; -767 match self { -768 U8(x) => w.write(x), -769 U16(x) => w.write(x), -770 U24(x) => w.put_uint(u64::from(x), 3), -771 U32(x) => w.write(x), -772 } -773 } -774 -775 pub(crate) fn decode<R: Buf>(len: usize, r: &mut R) -> Result<Self, PacketDecodeError> { -776 use PacketNumber::*; -777 let pn = match len { -778 1 => U8(r.get()?), -779 2 => U16(r.get()?), -780 3 => U24(r.get_uint(3) as u32), -781 4 => U32(r.get()?), -782 _ => unreachable!(), -783 }; -784 Ok(pn) -785 } -786 -787 pub(crate) fn decode_len(tag: u8) -> usize { -788 1 + (tag & 0x03) as usize -789 } -790 -791 fn tag(self) -> u8 { -792 use PacketNumber::*; -793 match self { -794 U8(_) => 0b00, -795 U16(_) => 0b01, -796 U24(_) => 0b10, -797 U32(_) => 0b11, -798 } -799 } -800 -801 pub(crate) fn expand(self, expected: u64) -> u64 { -802 // From Appendix A -803 use PacketNumber::*; -804 let truncated = match self { -805 U8(x) => u64::from(x), -806 U16(x) => u64::from(x), -807 U24(x) => u64::from(x), -808 U32(x) => u64::from(x), -809 }; -810 let nbits = self.len() * 8; -811 let win = 1 << nbits; -812 let hwin = win / 2; -813 let mask = win - 1; -814 // The incoming packet number should be greater than expected - hwin and less than or equal -815 // to expected + hwin -816 // -817 // This means we can't just strip the trailing bits from expected and add the truncated -818 // because that might yield a value outside the window. -819 // -820 // The following code calculates a candidate value and makes sure it's within the packet -821 // number window. -822 let candidate = (expected & !mask) | truncated; -823 if expected.checked_sub(hwin).is_some_and(|x| candidate <= x) { -824 candidate + win -825 } else if candidate > expected + hwin && candidate > win { -826 candidate - win -827 } else { -828 candidate -829 } -830 } -831} -832 -833/// A [`ConnectionIdParser`] implementation that assumes the connection ID is of fixed length -834pub struct FixedLengthConnectionIdParser { -835 expected_len: usize, -836} -837 -838impl FixedLengthConnectionIdParser { -839 /// Create a new instance of `FixedLengthConnectionIdParser` -840 pub fn new(expected_len: usize) -> Self { -841 Self { expected_len } -842 } -843} -844 -845impl ConnectionIdParser for FixedLengthConnectionIdParser { -846 fn parse(&self, buffer: &mut dyn Buf) -> Result<ConnectionId, PacketDecodeError> { -847 (buffer.remaining() >= self.expected_len) -848 .then(|| ConnectionId::from_buf(buffer, self.expected_len)) -849 .ok_or(PacketDecodeError::InvalidHeader("packet too small")) -850 } -851} -852 -853/// Parse connection id in short header packet -854pub trait ConnectionIdParser { -855 /// Parse a connection id from given buffer -856 fn parse(&self, buf: &mut dyn Buf) -> Result<ConnectionId, PacketDecodeError>; +698 version, +699 }), +700 LongHeaderType::Standard(ty) => Ok(Self::Long { +701 ty, +702 dst_cid, +703 src_cid, +704 len: buf.get_var()?, +705 version, +706 }), +707 } +708 } +709 } +710} +711 +712/// Header of an Initial packet, before decryption +713#[derive(Clone, Debug)] +714pub struct ProtectedInitialHeader { +715 /// Destination Connection ID +716 pub dst_cid: ConnectionId, +717 /// Source Connection ID +718 pub src_cid: ConnectionId, +719 /// The position of a token in the packet buffer +720 pub token_pos: Range<usize>, +721 /// Length of the packet payload +722 pub len: u64, +723 /// QUIC version +724 pub version: u32, +725} +726 +727#[derive(Clone, Debug)] +728pub(crate) struct InitialHeader { +729 pub(crate) dst_cid: ConnectionId, +730 pub(crate) src_cid: ConnectionId, +731 pub(crate) token: Bytes, +732 pub(crate) number: PacketNumber, +733 pub(crate) version: u32, +734} +735 +736// An encoded packet number +737#[derive(Debug, Copy, Clone, Eq, PartialEq)] +738pub(crate) enum PacketNumber { +739 U8(u8), +740 U16(u16), +741 U24(u32), +742 U32(u32), +743} +744 +745impl PacketNumber { +746 pub(crate) fn new(n: u64, largest_acked: u64) -> Self { +747 let range = (n - largest_acked) * 2; +748 if range < 1 << 8 { +749 Self::U8(n as u8) +750 } else if range < 1 << 16 { +751 Self::U16(n as u16) +752 } else if range < 1 << 24 { +753 Self::U24(n as u32) +754 } else if range < 1 << 32 { +755 Self::U32(n as u32) +756 } else { +757 panic!("packet number too large to encode") +758 } +759 } +760 +761 pub(crate) fn len(self) -> usize { +762 use PacketNumber::*; +763 match self { +764 U8(_) => 1, +765 U16(_) => 2, +766 U24(_) => 3, +767 U32(_) => 4, +768 } +769 } +770 +771 pub(crate) fn encode<W: BufMut>(self, w: &mut W) { +772 use PacketNumber::*; +773 match self { +774 U8(x) => w.write(x), +775 U16(x) => w.write(x), +776 U24(x) => w.put_uint(u64::from(x), 3), +777 U32(x) => w.write(x), +778 } +779 } +780 +781 pub(crate) fn decode<R: Buf>(len: usize, r: &mut R) -> Result<Self, PacketDecodeError> { +782 use PacketNumber::*; +783 let pn = match len { +784 1 => U8(r.get()?), +785 2 => U16(r.get()?), +786 3 => U24(r.get_uint(3) as u32), +787 4 => U32(r.get()?), +788 _ => unreachable!(), +789 }; +790 Ok(pn) +791 } +792 +793 pub(crate) fn decode_len(tag: u8) -> usize { +794 1 + (tag & 0x03) as usize +795 } +796 +797 fn tag(self) -> u8 { +798 use PacketNumber::*; +799 match self { +800 U8(_) => 0b00, +801 U16(_) => 0b01, +802 U24(_) => 0b10, +803 U32(_) => 0b11, +804 } +805 } +806 +807 pub(crate) fn expand(self, expected: u64) -> u64 { +808 // From Appendix A +809 use PacketNumber::*; +810 let truncated = match self { +811 U8(x) => u64::from(x), +812 U16(x) => u64::from(x), +813 U24(x) => u64::from(x), +814 U32(x) => u64::from(x), +815 }; +816 let nbits = self.len() * 8; +817 let win = 1 << nbits; +818 let hwin = win / 2; +819 let mask = win - 1; +820 // The incoming packet number should be greater than expected - hwin and less than or equal +821 // to expected + hwin +822 // +823 // This means we can't just strip the trailing bits from expected and add the truncated +824 // because that might yield a value outside the window. +825 // +826 // The following code calculates a candidate value and makes sure it's within the packet +827 // number window. +828 let candidate = (expected & !mask) | truncated; +829 if expected.checked_sub(hwin).is_some_and(|x| candidate <= x) { +830 candidate + win +831 } else if candidate > expected + hwin && candidate > win { +832 candidate - win +833 } else { +834 candidate +835 } +836 } +837} +838 +839/// A [`ConnectionIdParser`] implementation that assumes the connection ID is of fixed length +840pub struct FixedLengthConnectionIdParser { +841 expected_len: usize, +842} +843 +844impl FixedLengthConnectionIdParser { +845 /// Create a new instance of `FixedLengthConnectionIdParser` +846 pub fn new(expected_len: usize) -> Self { +847 Self { expected_len } +848 } +849} +850 +851impl ConnectionIdParser for FixedLengthConnectionIdParser { +852 fn parse(&self, buffer: &mut dyn Buf) -> Result<ConnectionId, PacketDecodeError> { +853 (buffer.remaining() >= self.expected_len) +854 .then(|| ConnectionId::from_buf(buffer, self.expected_len)) +855 .ok_or(PacketDecodeError::InvalidHeader("packet too small")) +856 } 857} 858 -859/// Long packet type including non-uniform cases -860#[derive(Clone, Copy, Debug, Eq, PartialEq)] -861pub(crate) enum LongHeaderType { -862 Initial, -863 Retry, -864 Standard(LongType), -865} -866 -867impl LongHeaderType { -868 fn from_byte(b: u8) -> Result<Self, PacketDecodeError> { -869 use {LongHeaderType::*, LongType::*}; -870 debug_assert!(b & LONG_HEADER_FORM != 0, "not a long packet"); -871 Ok(match (b & 0x30) >> 4 { -872 0x0 => Initial, -873 0x1 => Standard(ZeroRtt), -874 0x2 => Standard(Handshake), -875 0x3 => Retry, -876 _ => unreachable!(), -877 }) -878 } -879} -880 -881impl From<LongHeaderType> for u8 { -882 fn from(ty: LongHeaderType) -> Self { -883 use {LongHeaderType::*, LongType::*}; -884 match ty { -885 Initial => LONG_HEADER_FORM | FIXED_BIT, -886 Standard(ZeroRtt) => LONG_HEADER_FORM | FIXED_BIT | (0x1 << 4), -887 Standard(Handshake) => LONG_HEADER_FORM | FIXED_BIT | (0x2 << 4), -888 Retry => LONG_HEADER_FORM | FIXED_BIT | (0x3 << 4), -889 } -890 } -891} -892 -893/// Long packet types with uniform header structure -894#[derive(Clone, Copy, Debug, Eq, PartialEq)] -895pub enum LongType { -896 /// Handshake packet -897 Handshake, -898 /// 0-RTT packet -899 ZeroRtt, -900} -901 -902/// Packet decode error -903#[derive(Debug, Error, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -904pub enum PacketDecodeError { -905 /// Packet uses a QUIC version that is not supported -906 #[error("unsupported version {version:x}")] -907 UnsupportedVersion { -908 /// Source Connection ID -909 src_cid: ConnectionId, -910 /// Destination Connection ID -911 dst_cid: ConnectionId, -912 /// The version that was unsupported -913 version: u32, -914 }, -915 /// The packet header is invalid -916 #[error("invalid header: {0}")] -917 InvalidHeader(&'static str), -918} -919 -920impl From<coding::UnexpectedEnd> for PacketDecodeError { -921 fn from(_: coding::UnexpectedEnd) -> Self { -922 Self::InvalidHeader("unexpected end of packet") -923 } +859/// Parse connection id in short header packet +860pub trait ConnectionIdParser { +861 /// Parse a connection id from given buffer +862 fn parse(&self, buf: &mut dyn Buf) -> Result<ConnectionId, PacketDecodeError>; +863} +864 +865/// Long packet type including non-uniform cases +866#[derive(Clone, Copy, Debug, Eq, PartialEq)] +867pub(crate) enum LongHeaderType { +868 Initial, +869 Retry, +870 Standard(LongType), +871} +872 +873impl LongHeaderType { +874 fn from_byte(b: u8) -> Result<Self, PacketDecodeError> { +875 use {LongHeaderType::*, LongType::*}; +876 debug_assert!(b & LONG_HEADER_FORM != 0, "not a long packet"); +877 Ok(match (b & 0x30) >> 4 { +878 0x0 => Initial, +879 0x1 => Standard(ZeroRtt), +880 0x2 => Standard(Handshake), +881 0x3 => Retry, +882 _ => unreachable!(), +883 }) +884 } +885} +886 +887impl From<LongHeaderType> for u8 { +888 fn from(ty: LongHeaderType) -> Self { +889 use {LongHeaderType::*, LongType::*}; +890 match ty { +891 Initial => LONG_HEADER_FORM | FIXED_BIT, +892 Standard(ZeroRtt) => LONG_HEADER_FORM | FIXED_BIT | (0x1 << 4), +893 Standard(Handshake) => LONG_HEADER_FORM | FIXED_BIT | (0x2 << 4), +894 Retry => LONG_HEADER_FORM | FIXED_BIT | (0x3 << 4), +895 } +896 } +897} +898 +899/// Long packet types with uniform header structure +900#[derive(Clone, Copy, Debug, Eq, PartialEq)] +901pub enum LongType { +902 /// Handshake packet +903 Handshake, +904 /// 0-RTT packet +905 ZeroRtt, +906} +907 +908/// Packet decode error +909#[derive(Debug, Error, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +910pub enum PacketDecodeError { +911 /// Packet uses a QUIC version that is not supported +912 #[error("unsupported version {version:x}")] +913 UnsupportedVersion { +914 /// Source Connection ID +915 src_cid: ConnectionId, +916 /// Destination Connection ID +917 dst_cid: ConnectionId, +918 /// The version that was unsupported +919 version: u32, +920 }, +921 /// The packet header is invalid +922 #[error("invalid header: {0}")] +923 InvalidHeader(&'static str), 924} 925 -926pub(crate) const LONG_HEADER_FORM: u8 = 0x80; -927pub(crate) const FIXED_BIT: u8 = 0x40; -928pub(crate) const SPIN_BIT: u8 = 0x20; -929const SHORT_RESERVED_BITS: u8 = 0x18; -930const LONG_RESERVED_BITS: u8 = 0x0c; -931const KEY_PHASE_BIT: u8 = 0x04; -932 -933/// Packet number space identifiers -934#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] -935pub(crate) enum SpaceId { -936 /// Unprotected packets, used to bootstrap the handshake -937 Initial = 0, -938 Handshake = 1, -939 /// Application data space, used for 0-RTT and post-handshake/1-RTT packets -940 Data = 2, -941} -942 -943impl SpaceId { -944 pub(crate) fn iter() -> impl Iterator<Item = Self> { -945 [Self::Initial, Self::Handshake, Self::Data].iter().cloned() -946 } -947 -948 /// Returns the next higher packet space. -949 /// -950 /// Returns `None` if at [`SpaceId::Data`]. -951 pub(crate) fn next(&self) -> Option<Self> { -952 match self { -953 Self::Initial => Some(Self::Handshake), -954 Self::Handshake => Some(Self::Data), -955 Self::Data => None, -956 } -957 } -958 -959 /// Returns the encryption level for this packet space. -960 pub(crate) fn encryption_level(self) -> EncryptionLevel { -961 match self { -962 Self::Initial => EncryptionLevel::Initial, -963 Self::Handshake => EncryptionLevel::Handshake, -964 Self::Data => EncryptionLevel::OneRtt, -965 } -966 } -967 -968 /// Returns the [`SpaceKind`] for this packet space. -969 pub(crate) fn kind(self) -> SpaceKind { -970 match self { -971 Self::Initial => SpaceKind::Initial, -972 Self::Handshake => SpaceKind::Handshake, -973 Self::Data => SpaceKind::Data, -974 } -975 } -976} -977 -978#[cfg(test)] -979mod tests { -980 use super::*; -981 use hex_literal::hex; -982 use std::io; +926impl From<coding::UnexpectedEnd> for PacketDecodeError { +927 fn from(_: coding::UnexpectedEnd) -> Self { +928 Self::InvalidHeader("unexpected end of packet") +929 } +930} +931 +932pub(crate) const LONG_HEADER_FORM: u8 = 0x80; +933pub(crate) const FIXED_BIT: u8 = 0x40; +934pub(crate) const SPIN_BIT: u8 = 0x20; +935const SHORT_RESERVED_BITS: u8 = 0x18; +936const LONG_RESERVED_BITS: u8 = 0x0c; +937const KEY_PHASE_BIT: u8 = 0x04; +938 +939/// Packet number space identifiers +940#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +941pub(crate) enum SpaceId { +942 /// Unprotected packets, used to bootstrap the handshake +943 Initial = 0, +944 Handshake = 1, +945 /// Application data space, used for 0-RTT and post-handshake/1-RTT packets +946 Data = 2, +947} +948 +949impl SpaceId { +950 pub(crate) fn iter() -> impl Iterator<Item = Self> { +951 [Self::Initial, Self::Handshake, Self::Data].iter().cloned() +952 } +953 +954 /// Returns the next higher packet space. +955 /// +956 /// Returns `None` if at [`SpaceId::Data`]. +957 pub(crate) fn next(&self) -> Option<Self> { +958 match self { +959 Self::Initial => Some(Self::Handshake), +960 Self::Handshake => Some(Self::Data), +961 Self::Data => None, +962 } +963 } +964 +965 /// Returns the encryption level for this packet space. +966 pub(crate) fn encryption_level(self) -> EncryptionLevel { +967 match self { +968 Self::Initial => EncryptionLevel::Initial, +969 Self::Handshake => EncryptionLevel::Handshake, +970 Self::Data => EncryptionLevel::OneRtt, +971 } +972 } +973 +974 /// Returns the [`SpaceKind`] for this packet space. +975 pub(crate) fn kind(self) -> SpaceKind { +976 match self { +977 Self::Initial => SpaceKind::Initial, +978 Self::Handshake => SpaceKind::Handshake, +979 Self::Data => SpaceKind::Data, +980 } +981 } +982} 983 -984 fn check_pn(typed: PacketNumber, encoded: &[u8]) { -985 let mut buf = Vec::new(); -986 typed.encode(&mut buf); -987 assert_eq!(&buf[..], encoded); -988 let decoded = PacketNumber::decode(typed.len(), &mut io::Cursor::new(&buf)).unwrap(); -989 assert_eq!(typed, decoded); -990 } -991 -992 #[test] -993 fn roundtrip_packet_numbers() { -994 check_pn(PacketNumber::U8(0x7f), &hex!("7f")); -995 check_pn(PacketNumber::U16(0x80), &hex!("0080")); -996 check_pn(PacketNumber::U16(0x3fff), &hex!("3fff")); -997 check_pn(PacketNumber::U32(0x0000_4000), &hex!("0000 4000")); -998 check_pn(PacketNumber::U32(0xffff_ffff), &hex!("ffff ffff")); -999 } -1000 -1001 #[test] -1002 fn pn_encode() { -1003 check_pn(PacketNumber::new(0x10, 0), &hex!("10")); -1004 check_pn(PacketNumber::new(0x100, 0), &hex!("0100")); -1005 check_pn(PacketNumber::new(0x10000, 0), &hex!("010000")); -1006 } -1007 -1008 #[test] -1009 fn pn_expand_roundtrip() { -1010 for expected in 0..1024 { -1011 for actual in expected..1024 { -1012 assert_eq!(actual, PacketNumber::new(actual, expected).expand(expected)); -1013 } -1014 } -1015 } -1016 -1017 #[cfg(all(feature = "rustls", any(feature = "aws-lc-rs", feature = "ring")))] -1018 #[test] -1019 fn header_encoding() { -1020 use crate::Side; -1021 use crate::crypto::rustls::{configured_provider, initial_keys, initial_suite_from_provider}; -1022 use rustls::quic::Version; -1023 -1024 let dcid = ConnectionId::new(&hex!("06b858ec6f80452b")); -1025 let provider = configured_provider(); -1026 -1027 let suite = initial_suite_from_provider(&provider).unwrap(); -1028 let client = initial_keys(Version::V1, dcid, Side::Client, &suite); -1029 let mut buf = Vec::new(); -1030 let header = Header::Initial(InitialHeader { -1031 number: PacketNumber::U8(0), -1032 src_cid: ConnectionId::new(&[]), -1033 dst_cid: dcid, -1034 token: Bytes::new(), -1035 version: crate::DEFAULT_SUPPORTED_VERSIONS[0], -1036 }); -1037 let encode = header.encode(&mut buf); -1038 let header_len = buf.len(); -1039 buf.resize(header_len + 16 + client.packet.local.tag_len(), 0); -1040 encode.finish( -1041 &mut buf, -1042 &*client.header.local, -1043 Some((0, PathId::ZERO, &*client.packet.local)), -1044 ); -1045 -1046 for byte in &buf { -1047 print!("{byte:02x}"); -1048 } -1049 println!(); -1050 assert_eq!( -1051 buf[..], -1052 hex!( -1053 "c8000000010806b858ec6f80452b00004021be -1054 3ef50807b84191a196f760a6dad1e9d1c430c48952cba0148250c21c0a6a70e1" -1055 )[..] -1056 ); -1057 -1058 let server = initial_keys(Version::V1, dcid, Side::Server, &suite); -1059 let supported_versions = crate::DEFAULT_SUPPORTED_VERSIONS.to_vec(); -1060 let decode = PartialDecode::new( -1061 buf.as_slice().into(), -1062 &FixedLengthConnectionIdParser::new(0), -1063 &supported_versions, -1064 false, -1065 ) -1066 .unwrap() -1067 .0; -1068 let mut packet = decode.finish(Some(&*server.header.remote)).unwrap(); -1069 assert_eq!( -1070 packet.header_data[..], -1071 hex!("c0000000010806b858ec6f80452b0000402100")[..] -1072 ); -1073 server -1074 .packet -1075 .remote -1076 .decrypt(PathId::ZERO, 0, &packet.header_data, &mut packet.payload) -1077 .unwrap(); -1078 assert_eq!(packet.payload[..], [0; 16]); -1079 match packet.header { -1080 Header::Initial(InitialHeader { -1081 number: PacketNumber::U8(0), -1082 .. -1083 }) => {} -1084 _ => { -1085 panic!("unexpected header {:?}", packet.header); -1086 } -1087 } -1088 } -1089} \ No newline at end of file +984#[cfg(test)] +985mod tests { +986 use super::*; +987 use hex_literal::hex; +988 use std::io; +989 +990 fn check_pn(typed: PacketNumber, encoded: &[u8]) { +991 let mut buf = Vec::new(); +992 typed.encode(&mut buf); +993 assert_eq!(&buf[..], encoded); +994 let decoded = PacketNumber::decode(typed.len(), &mut io::Cursor::new(&buf)).unwrap(); +995 assert_eq!(typed, decoded); +996 } +997 +998 #[test] +999 fn roundtrip_packet_numbers() { +1000 check_pn(PacketNumber::U8(0x7f), &hex!("7f")); +1001 check_pn(PacketNumber::U16(0x80), &hex!("0080")); +1002 check_pn(PacketNumber::U16(0x3fff), &hex!("3fff")); +1003 check_pn(PacketNumber::U32(0x0000_4000), &hex!("0000 4000")); +1004 check_pn(PacketNumber::U32(0xffff_ffff), &hex!("ffff ffff")); +1005 } +1006 +1007 #[test] +1008 fn pn_encode() { +1009 check_pn(PacketNumber::new(0x10, 0), &hex!("10")); +1010 check_pn(PacketNumber::new(0x100, 0), &hex!("0100")); +1011 check_pn(PacketNumber::new(0x10000, 0), &hex!("010000")); +1012 } +1013 +1014 #[test] +1015 fn pn_expand_roundtrip() { +1016 for expected in 0..1024 { +1017 for actual in expected..1024 { +1018 assert_eq!(actual, PacketNumber::new(actual, expected).expand(expected)); +1019 } +1020 } +1021 } +1022 +1023 #[cfg(all(feature = "rustls", any(feature = "aws-lc-rs", feature = "ring")))] +1024 #[test] +1025 fn header_encoding() { +1026 use crate::Side; +1027 use crate::crypto::rustls::{configured_provider, initial_keys, initial_suite_from_provider}; +1028 use rustls::quic::Version; +1029 +1030 let dcid = ConnectionId::new(&hex!("06b858ec6f80452b")); +1031 let provider = configured_provider(); +1032 +1033 let suite = initial_suite_from_provider(&provider).unwrap(); +1034 let client = initial_keys(Version::V1, dcid, Side::Client, &suite); +1035 let mut buf = Vec::new(); +1036 let header = Header::Initial(InitialHeader { +1037 number: PacketNumber::U8(0), +1038 src_cid: ConnectionId::new(&[]), +1039 dst_cid: dcid, +1040 token: Bytes::new(), +1041 version: crate::DEFAULT_SUPPORTED_VERSIONS[0], +1042 }); +1043 let encode = header.encode(&mut buf); +1044 let header_len = buf.len(); +1045 buf.resize(header_len + 16 + client.packet.local.tag_len(), 0); +1046 encode.finish( +1047 &mut buf, +1048 &*client.header.local, +1049 Some((0, PathId::ZERO, &*client.packet.local)), +1050 ); +1051 +1052 for byte in &buf { +1053 print!("{byte:02x}"); +1054 } +1055 println!(); +1056 assert_eq!( +1057 buf[..], +1058 hex!( +1059 "c8000000010806b858ec6f80452b00004021be +1060 3ef50807b84191a196f760a6dad1e9d1c430c48952cba0148250c21c0a6a70e1" +1061 )[..] +1062 ); +1063 +1064 let server = initial_keys(Version::V1, dcid, Side::Server, &suite); +1065 let supported_versions = crate::DEFAULT_SUPPORTED_VERSIONS.to_vec(); +1066 let decode = PartialDecode::new( +1067 buf.as_slice().into(), +1068 &FixedLengthConnectionIdParser::new(0), +1069 &supported_versions, +1070 false, +1071 ) +1072 .unwrap() +1073 .0; +1074 let mut packet = decode.finish(Some(&*server.header.remote)).unwrap(); +1075 assert_eq!( +1076 packet.header_data[..], +1077 hex!("c0000000010806b858ec6f80452b0000402100")[..] +1078 ); +1079 server +1080 .packet +1081 .remote +1082 .decrypt(PathId::ZERO, 0, &packet.header_data, &mut packet.payload) +1083 .unwrap(); +1084 assert_eq!(packet.payload[..], [0; 16]); +1085 match packet.header { +1086 Header::Initial(InitialHeader { +1087 number: PacketNumber::U8(0), +1088 .. +1089 }) => {} +1090 _ => { +1091 panic!("unexpected header {:?}", packet.header); +1092 } +1093 } +1094 } +1095} \ 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 caaa8a824..afb3736b8 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 @@ -236,7 +236,7 @@ 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), 0..8) +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; diff --git a/pr/451/docs/src/noq_udp/lib.rs.html b/pr/451/docs/src/noq_udp/lib.rs.html index a21874d37..0d8f30d7a 100644 --- a/pr/451/docs/src/noq_udp/lib.rs.html +++ b/pr/451/docs/src/noq_udp/lib.rs.html @@ -162,140 +162,141 @@ 162 /// This case is actually quite common when splitting up a prepared GSO batch 163 /// again after GSO has been disabled because the last datagram in a GSO 164 /// batch is allowed to be smaller than the segment size. -165 fn effective_segment_size(&self) -> Option<usize> { -166 match self.segment_size? { -167 size if size >= self.contents.len() => None, -168 size => Some(size), -169 } -170 } -171} -172 -173/// Log at most 1 IO error per minute -174#[cfg(not(wasm_browser))] -175const IO_ERROR_LOG_INTERVAL: Duration = std::time::Duration::from_secs(60); -176 -177/// Logs a warning message when sendmsg fails -178/// -179/// Logging will only be performed if at least [`IO_ERROR_LOG_INTERVAL`] -180/// has elapsed since the last error was logged. -181#[cfg(all(not(wasm_browser), any(feature = "tracing-log", feature = "log")))] -182fn log_sendmsg_error( -183 last_send_error: &Mutex<Instant>, -184 err: impl core::fmt::Debug, -185 transmit: &Transmit<'_>, -186) { -187 let now = Instant::now(); -188 let last_send_error = &mut *last_send_error.lock().expect("poisend lock"); -189 if now.saturating_duration_since(*last_send_error) > IO_ERROR_LOG_INTERVAL { -190 *last_send_error = now; -191 log::warn!( -192 "sendmsg error: {:?}, Transmit: {{ destination: {:?}, src_ip: {:?}, ecn: {:?}, len: {:?}, segment_size: {:?} }}", -193 err, -194 transmit.destination, -195 transmit.src_ip, -196 transmit.ecn, -197 transmit.contents.len(), -198 transmit.segment_size -199 ); -200 } -201} -202 -203// No-op -204#[cfg(not(any(wasm_browser, feature = "tracing-log", feature = "log")))] -205fn log_sendmsg_error(_: &Mutex<Instant>, _: impl core::fmt::Debug, _: &Transmit<'_>) {} -206 -207/// A borrowed UDP socket -208/// -209/// On Unix, constructible via `From<T: AsFd>`. On Windows, constructible via `From<T: -210/// AsSocket>`. -211// Wrapper around socket2 to avoid making it a public dependency and incurring stability risk -212#[cfg(not(wasm_browser))] -213pub struct UdpSockRef<'a>(socket2::SockRef<'a>); -214 -215#[cfg(unix)] -216impl<'s, S> From<&'s S> for UdpSockRef<'s> -217where -218 S: AsFd, -219{ -220 fn from(socket: &'s S) -> Self { -221 Self(socket.into()) -222 } -223} -224 -225#[cfg(windows)] -226impl<'s, S> From<&'s S> for UdpSockRef<'s> -227where -228 S: AsSocket, -229{ -230 fn from(socket: &'s S) -> Self { -231 Self(socket.into()) -232 } -233} -234 -235/// Explicit congestion notification codepoint -236#[repr(u8)] -237#[derive(Debug, Copy, Clone, Eq, PartialEq)] -238pub enum EcnCodepoint { -239 /// The ECT(0) codepoint, indicating that an endpoint is ECN-capable -240 Ect0 = 0b10, -241 /// The ECT(1) codepoint, indicating that an endpoint is ECN-capable -242 Ect1 = 0b01, -243 /// The CE codepoint, signalling that congestion was experienced -244 Ce = 0b11, -245} -246 -247impl EcnCodepoint { -248 /// Create new object from the given bits -249 pub fn from_bits(x: u8) -> Option<Self> { -250 use EcnCodepoint::*; -251 Some(match x & 0b11 { -252 0b10 => Ect0, -253 0b01 => Ect1, -254 0b11 => Ce, -255 _ => { -256 return None; -257 } -258 }) -259 } -260} -261 -262#[cfg(all(test, not(posix_minimal)))] -263mod tests { -264 use std::net::Ipv4Addr; -265 -266 use super::*; -267 -268 #[test] -269 fn effective_segment_size() { -270 assert_eq!( -271 make_transmit(&[0u8; 10], Some(15)).effective_segment_size(), -272 None, -273 "segment_size > content_len should yield no effective segment_size" -274 ); -275 assert_eq!( -276 make_transmit(&[0u8; 10], Some(10)).effective_segment_size(), -277 None, -278 "segment_size == content_len should yield no effective segment_size" -279 ); -280 assert_eq!( -281 make_transmit(&[0u8; 10], None).effective_segment_size(), -282 None, -283 "no segment_size should yield no effective segment_size" -284 ); -285 assert_eq!( -286 make_transmit(&[0u8; 10], Some(5)).effective_segment_size(), -287 Some(5), -288 "segment_size < content_len should yield effective segment_size" -289 ); -290 } -291 -292 fn make_transmit(contents: &[u8], segment_size: Option<usize>) -> Transmit<'_> { -293 Transmit { -294 destination: SocketAddr::from((Ipv4Addr::UNSPECIFIED, 1)), -295 ecn: None, -296 contents, -297 segment_size, -298 src_ip: None, -299 } -300 } -301} \ No newline at end of file +165 #[cfg_attr(apple_fast, allow(dead_code))] // Used by prepare_msg, which is unused when apple_fast +166 fn effective_segment_size(&self) -> Option<usize> { +167 match self.segment_size? { +168 size if size >= self.contents.len() => None, +169 size => Some(size), +170 } +171 } +172} +173 +174/// Log at most 1 IO error per minute +175#[cfg(not(wasm_browser))] +176const IO_ERROR_LOG_INTERVAL: Duration = std::time::Duration::from_secs(60); +177 +178/// Logs a warning message when sendmsg fails +179/// +180/// Logging will only be performed if at least [`IO_ERROR_LOG_INTERVAL`] +181/// has elapsed since the last error was logged. +182#[cfg(all(not(wasm_browser), any(feature = "tracing-log", feature = "log")))] +183fn log_sendmsg_error( +184 last_send_error: &Mutex<Instant>, +185 err: impl core::fmt::Debug, +186 transmit: &Transmit<'_>, +187) { +188 let now = Instant::now(); +189 let last_send_error = &mut *last_send_error.lock().expect("poisend lock"); +190 if now.saturating_duration_since(*last_send_error) > IO_ERROR_LOG_INTERVAL { +191 *last_send_error = now; +192 log::warn!( +193 "sendmsg error: {:?}, Transmit: {{ destination: {:?}, src_ip: {:?}, ecn: {:?}, len: {:?}, segment_size: {:?} }}", +194 err, +195 transmit.destination, +196 transmit.src_ip, +197 transmit.ecn, +198 transmit.contents.len(), +199 transmit.segment_size +200 ); +201 } +202} +203 +204// No-op +205#[cfg(not(any(wasm_browser, feature = "tracing-log", feature = "log")))] +206fn log_sendmsg_error(_: &Mutex<Instant>, _: impl core::fmt::Debug, _: &Transmit<'_>) {} +207 +208/// A borrowed UDP socket +209/// +210/// On Unix, constructible via `From<T: AsFd>`. On Windows, constructible via `From<T: +211/// AsSocket>`. +212// Wrapper around socket2 to avoid making it a public dependency and incurring stability risk +213#[cfg(not(wasm_browser))] +214pub struct UdpSockRef<'a>(socket2::SockRef<'a>); +215 +216#[cfg(unix)] +217impl<'s, S> From<&'s S> for UdpSockRef<'s> +218where +219 S: AsFd, +220{ +221 fn from(socket: &'s S) -> Self { +222 Self(socket.into()) +223 } +224} +225 +226#[cfg(windows)] +227impl<'s, S> From<&'s S> for UdpSockRef<'s> +228where +229 S: AsSocket, +230{ +231 fn from(socket: &'s S) -> Self { +232 Self(socket.into()) +233 } +234} +235 +236/// Explicit congestion notification codepoint +237#[repr(u8)] +238#[derive(Debug, Copy, Clone, Eq, PartialEq)] +239pub enum EcnCodepoint { +240 /// The ECT(0) codepoint, indicating that an endpoint is ECN-capable +241 Ect0 = 0b10, +242 /// The ECT(1) codepoint, indicating that an endpoint is ECN-capable +243 Ect1 = 0b01, +244 /// The CE codepoint, signalling that congestion was experienced +245 Ce = 0b11, +246} +247 +248impl EcnCodepoint { +249 /// Create new object from the given bits +250 pub fn from_bits(x: u8) -> Option<Self> { +251 use EcnCodepoint::*; +252 Some(match x & 0b11 { +253 0b10 => Ect0, +254 0b01 => Ect1, +255 0b11 => Ce, +256 _ => { +257 return None; +258 } +259 }) +260 } +261} +262 +263#[cfg(all(test, not(posix_minimal)))] +264mod tests { +265 use std::net::Ipv4Addr; +266 +267 use super::*; +268 +269 #[test] +270 fn effective_segment_size() { +271 assert_eq!( +272 make_transmit(&[0u8; 10], Some(15)).effective_segment_size(), +273 None, +274 "segment_size > content_len should yield no effective segment_size" +275 ); +276 assert_eq!( +277 make_transmit(&[0u8; 10], Some(10)).effective_segment_size(), +278 None, +279 "segment_size == content_len should yield no effective segment_size" +280 ); +281 assert_eq!( +282 make_transmit(&[0u8; 10], None).effective_segment_size(), +283 None, +284 "no segment_size should yield no effective segment_size" +285 ); +286 assert_eq!( +287 make_transmit(&[0u8; 10], Some(5)).effective_segment_size(), +288 Some(5), +289 "segment_size < content_len should yield effective segment_size" +290 ); +291 } +292 +293 fn make_transmit(contents: &[u8], segment_size: Option<usize>) -> Transmit<'_> { +294 Transmit { +295 destination: SocketAddr::from((Ipv4Addr::UNSPECIFIED, 1)), +296 ecn: None, +297 contents, +298 segment_size, +299 src_ip: None, +300 } +301 } +302} \ No newline at end of file diff --git a/pr/451/docs/src/noq_udp/unix.rs.html b/pr/451/docs/src/noq_udp/unix.rs.html index 7ac3a2dd6..3772fc41b 100644 --- a/pr/451/docs/src/noq_udp/unix.rs.html +++ b/pr/451/docs/src/noq_udp/unix.rs.html @@ -5,7 +5,7 @@ 5 mem::{self, MaybeUninit}, 6 net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, 7 num::NonZeroUsize, -8 os::unix::io::AsRawFd, +8 os::fd::AsRawFd, 9 sync::{ 10 Mutex, 11 atomic::{AtomicBool, AtomicUsize, Ordering}, @@ -34,1044 +34,1225 @@ 34 pub msg_datalen: usize, 35} 36 -37#[cfg(apple_fast)] -38unsafe extern "C" { -39 fn recvmsg_x( -40 s: libc::c_int, -41 msgp: *const msghdr_x, -42 cnt: libc::c_uint, -43 flags: libc::c_int, -44 ) -> isize; -45 -46 fn sendmsg_x( -47 s: libc::c_int, -48 msgp: *const msghdr_x, -49 cnt: libc::c_uint, -50 flags: libc::c_int, -51 ) -> isize; -52} -53 -54#[cfg(target_os = "freebsd")] -55type IpTosTy = libc::c_uchar; -56#[cfg(not(any(target_os = "freebsd", target_os = "netbsd")))] -57type IpTosTy = libc::c_int; -58 -59/// Tokio-compatible UDP socket with some useful specializations. -60/// -61/// Unlike a standard tokio UDP socket, this allows ECN bits to be read and written on some -62/// platforms. -63#[derive(Debug)] -64pub struct UdpSocketState { -65 last_send_error: Mutex<Instant>, -66 max_gso_segments: AtomicUsize, -67 gro_segments: NonZeroUsize, -68 may_fragment: bool, -69 -70 /// True if we have received EINVAL error from `sendmsg` system call at least once. -71 /// -72 /// If enabled, we assume that old kernel is used and switch to fallback mode. -73 /// In particular, we do not use IP_TOS cmsg_type in this case, -74 /// which is not supported on Linux <3.13 and results in not sending the UDP packet at all. -75 sendmsg_einval: AtomicBool, -76} -77 -78impl UdpSocketState { -79 pub fn new(sock: UdpSockRef<'_>) -> io::Result<Self> { -80 let io = sock.0; -81 let mut cmsg_platform_space = 0; -82 if cfg!(target_os = "linux") -83 || cfg!(bsd) -84 || cfg!(apple) -85 || cfg!(target_os = "android") -86 || cfg!(solarish) -87 { -88 cmsg_platform_space += -89 unsafe { libc::CMSG_SPACE(mem::size_of::<libc::in6_pktinfo>() as _) as usize }; -90 } +37#[cfg(target_os = "freebsd")] +38type IpTosTy = libc::c_uchar; +39#[cfg(not(any(target_os = "freebsd", target_os = "netbsd")))] +40type IpTosTy = libc::c_int; +41 +42/// Tokio-compatible UDP socket with some useful specializations. +43/// +44/// Unlike a standard tokio UDP socket, this allows ECN bits to be read and written on some +45/// platforms. +46#[derive(Debug)] +47pub struct UdpSocketState { +48 last_send_error: Mutex<Instant>, +49 max_gso_segments: AtomicUsize, +50 gro_segments: NonZeroUsize, +51 may_fragment: bool, +52 +53 /// True if we have received EINVAL error from `sendmsg` system call at least once. +54 /// +55 /// If enabled, we assume that old kernel is used and switch to fallback mode. +56 /// In particular, we do not use IP_TOS cmsg_type in this case, +57 /// which is not supported on Linux <3.13 and results in not sending the UDP packet at all. +58 sendmsg_einval: AtomicBool, +59 +60 /// Whether to use Apple's fast `sendmsg_x`/`recvmsg_x` APIs. +61 /// +62 /// These private APIs provide better performance but may not be available on all +63 /// Apple OS versions. Callers must verify availability before enabling. +64 #[cfg(apple_fast)] +65 apple_fast_path: AtomicBool, +66} +67 +68impl UdpSocketState { +69 pub fn new(sock: UdpSockRef<'_>) -> io::Result<Self> { +70 let io = sock.0; +71 let mut cmsg_platform_space = 0; +72 if cfg!(target_os = "linux") +73 || cfg!(bsd) +74 || cfg!(apple) +75 || cfg!(target_os = "android") +76 || cfg!(solarish) +77 { +78 cmsg_platform_space += +79 unsafe { libc::CMSG_SPACE(mem::size_of::<libc::in6_pktinfo>() as _) as usize }; +80 } +81 +82 assert!( +83 CMSG_LEN +84 >= unsafe { libc::CMSG_SPACE(mem::size_of::<libc::c_int>() as _) as usize } +85 + cmsg_platform_space +86 ); +87 assert!( +88 mem::align_of::<libc::cmsghdr>() <= mem::align_of::<cmsg::Aligned<[u8; 0]>>(), +89 "control message buffers will be misaligned" +90 ); 91 -92 assert!( -93 CMSG_LEN -94 >= unsafe { libc::CMSG_SPACE(mem::size_of::<libc::c_int>() as _) as usize } -95 + cmsg_platform_space -96 ); -97 assert!( -98 mem::align_of::<libc::cmsghdr>() <= mem::align_of::<cmsg::Aligned<[u8; 0]>>(), -99 "control message buffers will be misaligned" -100 ); -101 -102 io.set_nonblocking(true)?; -103 -104 let addr = io.local_addr()?; -105 let is_ipv4 = addr.family() == libc::AF_INET as libc::sa_family_t; -106 -107 // mac and ios do not support IP_RECVTOS on dual-stack sockets :( -108 // older macos versions also don't have the flag and will error out if we don't ignore it -109 #[cfg(not(any( -110 target_os = "openbsd", -111 target_os = "netbsd", -112 target_os = "dragonfly", -113 solarish -114 )))] -115 if (is_ipv4 || !io.only_v6()?) -116 && let Err(_err) = -117 set_socket_option(&*io, libc::IPPROTO_IP, libc::IP_RECVTOS, OPTION_ON) -118 { -119 crate::log::debug!("Ignoring error setting IP_RECVTOS on socket: {_err:?}"); -120 } -121 -122 let mut may_fragment = false; -123 #[cfg(any(target_os = "linux", target_os = "android"))] -124 { -125 // opportunistically try to enable GRO. See gro::gro_segments(). -126 let _ = set_socket_option(&*io, libc::SOL_UDP, libc::UDP_GRO, OPTION_ON); -127 -128 // Forbid IPv4 fragmentation. Set even for IPv6 to account for IPv6 mapped IPv4 addresses. -129 // Set `may_fragment` to `true` if this option is not supported on the platform. -130 may_fragment |= !set_socket_option_supported( -131 &*io, -132 libc::IPPROTO_IP, -133 libc::IP_MTU_DISCOVER, -134 libc::IP_PMTUDISC_PROBE, -135 )?; -136 -137 if is_ipv4 { -138 set_socket_option(&*io, libc::IPPROTO_IP, libc::IP_PKTINFO, OPTION_ON)?; -139 } else { -140 // Set `may_fragment` to `true` if this option is not supported on the platform. -141 may_fragment |= !set_socket_option_supported( -142 &*io, -143 libc::IPPROTO_IPV6, -144 libc::IPV6_MTU_DISCOVER, -145 libc::IPV6_PMTUDISC_PROBE, -146 )?; -147 } -148 } -149 #[cfg(any(target_os = "freebsd", apple))] -150 { -151 if is_ipv4 { -152 // Set `may_fragment` to `true` if this option is not supported on the platform. -153 may_fragment |= !set_socket_option_supported( -154 &*io, -155 libc::IPPROTO_IP, -156 libc::IP_DONTFRAG, -157 OPTION_ON, -158 )?; -159 } -160 } -161 #[cfg(any(bsd, apple, solarish))] -162 // IP_RECVDSTADDR == IP_SENDSRCADDR on FreeBSD -163 // macOS uses only IP_RECVDSTADDR, no IP_SENDSRCADDR on macOS (the same on Solaris) -164 // macOS also supports IP_PKTINFO -165 { -166 if is_ipv4 { -167 set_socket_option(&*io, libc::IPPROTO_IP, libc::IP_RECVDSTADDR, OPTION_ON)?; -168 } -169 } -170 -171 // Options standardized in RFC 3542 -172 if !is_ipv4 { -173 set_socket_option(&*io, libc::IPPROTO_IPV6, libc::IPV6_RECVPKTINFO, OPTION_ON)?; -174 set_socket_option(&*io, libc::IPPROTO_IPV6, libc::IPV6_RECVTCLASS, OPTION_ON)?; -175 // Linux's IP_PMTUDISC_PROBE allows us to operate under interface MTU rather than the -176 // kernel's path MTU guess, but actually disabling fragmentation requires this too. See -177 // __ip6_append_data in ip6_output.c. -178 // Set `may_fragment` to `true` if this option is not supported on the platform. -179 may_fragment |= !set_socket_option_supported( -180 &*io, -181 libc::IPPROTO_IPV6, -182 libc::IPV6_DONTFRAG, -183 OPTION_ON, -184 )?; -185 } -186 -187 let now = Instant::now(); -188 Ok(Self { -189 last_send_error: Mutex::new(now.checked_sub(2 * IO_ERROR_LOG_INTERVAL).unwrap_or(now)), -190 max_gso_segments: AtomicUsize::new(gso::max_gso_segments()), -191 gro_segments: gro::gro_segments(), -192 may_fragment, -193 sendmsg_einval: AtomicBool::new(false), -194 }) -195 } -196 -197 /// Sends a [`Transmit`] on the given socket. -198 /// -199 /// This function will only ever return errors of kind [`io::ErrorKind::WouldBlock`]. -200 /// All other errors will be logged and converted to `Ok`. -201 /// -202 /// UDP transmission errors are considered non-fatal because higher-level protocols must -203 /// employ retransmits and timeouts anyway in order to deal with UDP's unreliable nature. -204 /// Thus, logging is most likely the only thing you can do with these errors. -205 /// -206 /// If you would like to handle these errors yourself, use [`UdpSocketState::try_send`] -207 /// instead. -208 pub fn send(&self, socket: UdpSockRef<'_>, transmit: &Transmit<'_>) -> io::Result<()> { -209 match send(self, socket.0, transmit) { -210 Ok(()) => Ok(()), -211 Err(e) if e.kind() == io::ErrorKind::WouldBlock => Err(e), -212 // - EMSGSIZE is expected for MTU probes. Future work might be able to avoid -213 // these by automatically clamping the MTUD upper bound to the interface MTU. -214 Err(e) if e.raw_os_error() == Some(libc::EMSGSIZE) => Ok(()), -215 Err(e) => { -216 log_sendmsg_error(&self.last_send_error, e, transmit); -217 -218 Ok(()) -219 } -220 } -221 } -222 -223 /// Sends a [`Transmit`] on the given socket without any additional error handling. -224 pub fn try_send(&self, socket: UdpSockRef<'_>, transmit: &Transmit<'_>) -> io::Result<()> { -225 send(self, socket.0, transmit) -226 } -227 -228 pub fn recv( -229 &self, -230 socket: UdpSockRef<'_>, -231 bufs: &mut [IoSliceMut<'_>], -232 meta: &mut [RecvMeta], -233 ) -> io::Result<usize> { -234 recv(socket.0, bufs, meta) -235 } -236 -237 /// The maximum amount of segments which can be transmitted if a platform -238 /// supports Generic Send Offload (GSO). -239 /// -240 /// This is 1 if the platform doesn't support GSO. Subject to change if errors are detected -241 /// while using GSO. -242 #[inline] -243 pub fn max_gso_segments(&self) -> NonZeroUsize { -244 self.max_gso_segments -245 .load(Ordering::Relaxed) -246 .try_into() -247 .expect("must have non zero GSO segments") +92 io.set_nonblocking(true)?; +93 +94 let addr = io.local_addr()?; +95 let is_ipv4 = addr.family() == libc::AF_INET as libc::sa_family_t; +96 +97 // mac and ios do not support IP_RECVTOS on dual-stack sockets :( +98 // older macos versions also don't have the flag and will error out if we don't ignore it +99 #[cfg(not(any( +100 target_os = "openbsd", +101 target_os = "netbsd", +102 target_os = "dragonfly", +103 solarish +104 )))] +105 if (is_ipv4 || !io.only_v6()?) +106 && let Err(_err) = +107 set_socket_option(&*io, libc::IPPROTO_IP, libc::IP_RECVTOS, OPTION_ON) +108 { +109 crate::log::debug!("Ignoring error setting IP_RECVTOS on socket: {_err:?}"); +110 } +111 +112 let mut may_fragment = false; +113 #[cfg_attr( +114 not(any(target_os = "linux", target_os = "android")), +115 expect(unused_mut) +116 )] +117 let mut gro_segments = NonZeroUsize::MIN; +118 +119 #[cfg(any(target_os = "linux", target_os = "android"))] +120 { +121 // Forbid IPv4 fragmentation. Set even for IPv6 to account for IPv6 mapped IPv4 addresses. +122 // Set `may_fragment` to `true` if this option is not supported on the platform. +123 may_fragment |= !set_socket_option_supported( +124 &*io, +125 libc::IPPROTO_IP, +126 libc::IP_MTU_DISCOVER, +127 libc::IP_PMTUDISC_PROBE, +128 )?; +129 +130 if is_ipv4 { +131 set_socket_option(&*io, libc::IPPROTO_IP, libc::IP_PKTINFO, OPTION_ON)?; +132 } else { +133 // Set `may_fragment` to `true` if this option is not supported on the platform. +134 may_fragment |= !set_socket_option_supported( +135 &*io, +136 libc::IPPROTO_IPV6, +137 libc::IPV6_MTU_DISCOVER, +138 libc::IPV6_PMTUDISC_PROBE, +139 )?; +140 } +141 +142 if set_socket_option(&*io, libc::SOL_UDP, libc::UDP_GRO, OPTION_ON).is_ok() { +143 // As defined in net/ipv4/udp_offload.c +144 // #define UDP_GRO_CNT_MAX 64 +145 // +146 // NOTE: this MUST be set to UDP_GRO_CNT_MAX to ensure that the receive buffer size +147 // (get_max_udp_payload_size() * gro_segments()) is large enough to hold the largest GRO +148 // list the kernel might potentially produce. See +149 // https://github.com/quinn-rs/quinn/pull/1354. +150 gro_segments = NonZeroUsize::new(64).expect("known"); +151 } +152 } +153 #[cfg(any(target_os = "freebsd", apple))] +154 { +155 if is_ipv4 { +156 // Set `may_fragment` to `true` if this option is not supported on the platform. +157 may_fragment |= !set_socket_option_supported( +158 &*io, +159 libc::IPPROTO_IP, +160 libc::IP_DONTFRAG, +161 OPTION_ON, +162 )?; +163 } +164 } +165 #[cfg(any(bsd, apple, solarish))] +166 // IP_RECVDSTADDR == IP_SENDSRCADDR on FreeBSD +167 // macOS uses only IP_RECVDSTADDR, no IP_SENDSRCADDR on macOS (the same on Solaris) +168 // macOS also supports IP_PKTINFO +169 { +170 if is_ipv4 { +171 set_socket_option(&*io, libc::IPPROTO_IP, libc::IP_RECVDSTADDR, OPTION_ON)?; +172 } +173 } +174 +175 // Options standardized in RFC 3542 +176 if !is_ipv4 { +177 set_socket_option(&*io, libc::IPPROTO_IPV6, libc::IPV6_RECVPKTINFO, OPTION_ON)?; +178 set_socket_option(&*io, libc::IPPROTO_IPV6, libc::IPV6_RECVTCLASS, OPTION_ON)?; +179 // Linux's IP_PMTUDISC_PROBE allows us to operate under interface MTU rather than the +180 // kernel's path MTU guess, but actually disabling fragmentation requires this too. See +181 // __ip6_append_data in ip6_output.c. +182 // Set `may_fragment` to `true` if this option is not supported on the platform. +183 may_fragment |= !set_socket_option_supported( +184 &*io, +185 libc::IPPROTO_IPV6, +186 libc::IPV6_DONTFRAG, +187 OPTION_ON, +188 )?; +189 } +190 +191 let now = Instant::now(); +192 Ok(Self { +193 last_send_error: Mutex::new(now.checked_sub(2 * IO_ERROR_LOG_INTERVAL).unwrap_or(now)), +194 max_gso_segments: AtomicUsize::new(gso::max_gso_segments(&*io)), +195 gro_segments, +196 may_fragment, +197 sendmsg_einval: AtomicBool::new(false), +198 #[cfg(apple_fast)] +199 apple_fast_path: AtomicBool::new(false), +200 }) +201 } +202 +203 /// Sends a [`Transmit`] on the given socket. +204 /// +205 /// This function will only ever return errors of kind [`io::ErrorKind::WouldBlock`]. +206 /// All other errors will be logged and converted to `Ok`. +207 /// +208 /// UDP transmission errors are considered non-fatal because higher-level protocols must +209 /// employ retransmits and timeouts anyway in order to deal with UDP's unreliable nature. +210 /// Thus, logging is most likely the only thing you can do with these errors. +211 /// +212 /// If you would like to handle these errors yourself, use [`UdpSocketState::try_send`] +213 /// instead. +214 pub fn send(&self, socket: UdpSockRef<'_>, transmit: &Transmit<'_>) -> io::Result<()> { +215 match send(self, socket.0, transmit) { +216 Ok(()) => Ok(()), +217 Err(e) if e.kind() == io::ErrorKind::WouldBlock => Err(e), +218 // - EMSGSIZE is expected for MTU probes. Future work might be able to avoid +219 // these by automatically clamping the MTUD upper bound to the interface MTU. +220 Err(e) if e.raw_os_error() == Some(libc::EMSGSIZE) => Ok(()), +221 Err(e) => { +222 log_sendmsg_error(&self.last_send_error, e, transmit); +223 +224 Ok(()) +225 } +226 } +227 } +228 +229 /// Sends a [`Transmit`] on the given socket without any additional error handling. +230 pub fn try_send(&self, socket: UdpSockRef<'_>, transmit: &Transmit<'_>) -> io::Result<()> { +231 send(self, socket.0, transmit) +232 } +233 +234 #[cfg(not(any( +235 apple, +236 target_os = "openbsd", +237 target_os = "netbsd", +238 target_os = "dragonfly", +239 solarish +240 )))] +241 pub fn recv( +242 &self, +243 socket: UdpSockRef<'_>, +244 bufs: &mut [IoSliceMut<'_>], +245 meta: &mut [RecvMeta], +246 ) -> io::Result<usize> { +247 recv_via_recvmmsg(socket.0, bufs, meta) 248 } 249 -250 /// The number of segments to read when GRO is enabled. Used as a factor to -251 /// compute the receive buffer size. -252 /// -253 /// Returns 1 if the platform doesn't support GRO. -254 #[inline] -255 pub fn gro_segments(&self) -> NonZeroUsize { -256 self.gro_segments -257 } -258 -259 /// Resize the send buffer of `socket` to `bytes` -260 #[inline] -261 pub fn set_send_buffer_size(&self, socket: UdpSockRef<'_>, bytes: usize) -> io::Result<()> { -262 socket.0.set_send_buffer_size(bytes) -263 } -264 -265 /// Resize the receive buffer of `socket` to `bytes` -266 #[inline] -267 pub fn set_recv_buffer_size(&self, socket: UdpSockRef<'_>, bytes: usize) -> io::Result<()> { -268 socket.0.set_recv_buffer_size(bytes) -269 } -270 -271 /// Get the size of the `socket` send buffer -272 #[inline] -273 pub fn send_buffer_size(&self, socket: UdpSockRef<'_>) -> io::Result<usize> { -274 socket.0.send_buffer_size() -275 } -276 -277 /// Get the size of the `socket` receive buffer -278 #[inline] -279 pub fn recv_buffer_size(&self, socket: UdpSockRef<'_>) -> io::Result<usize> { -280 socket.0.recv_buffer_size() -281 } -282 -283 /// Whether transmitted datagrams might get fragmented by the IP layer -284 /// -285 /// Returns `false` on targets which employ e.g. the `IPV6_DONTFRAG` socket option. -286 #[inline] -287 pub fn may_fragment(&self) -> bool { -288 self.may_fragment -289 } -290 -291 /// Returns true if we previously got an EINVAL error from `sendmsg` syscall. -292 fn sendmsg_einval(&self) -> bool { -293 self.sendmsg_einval.load(Ordering::Relaxed) -294 } -295 -296 /// Sets the flag indicating we got EINVAL error from `sendmsg` syscall. -297 #[cfg(not(any(apple, target_os = "openbsd", target_os = "netbsd")))] -298 fn set_sendmsg_einval(&self) { -299 self.sendmsg_einval.store(true, Ordering::Relaxed) +250 #[cfg(apple_fast)] +251 pub fn recv( +252 &self, +253 socket: UdpSockRef<'_>, +254 bufs: &mut [IoSliceMut<'_>], +255 meta: &mut [RecvMeta], +256 ) -> io::Result<usize> { +257 if self.is_apple_fast_path_enabled() { +258 recv_via_recvmsg_x(self, socket.0, bufs, meta) +259 } else { +260 recv_single(socket.0, bufs, meta) +261 } +262 } +263 +264 #[cfg(any( +265 target_os = "openbsd", +266 target_os = "netbsd", +267 target_os = "dragonfly", +268 solarish, +269 apple_slow +270 ))] +271 pub fn recv( +272 &self, +273 socket: UdpSockRef<'_>, +274 bufs: &mut [IoSliceMut<'_>], +275 meta: &mut [RecvMeta], +276 ) -> io::Result<usize> { +277 recv_single(socket.0, bufs, meta) +278 } +279 +280 /// The maximum amount of segments which can be transmitted if a platform +281 /// supports Generic Send Offload (GSO). +282 /// +283 /// This is 1 if the platform doesn't support GSO. Subject to change if errors are detected +284 /// while using GSO. +285 #[inline] +286 pub fn max_gso_segments(&self) -> NonZeroUsize { +287 self.max_gso_segments +288 .load(Ordering::Relaxed) +289 .try_into() +290 .expect("must have non zero GSO segments") +291 } +292 +293 /// The number of segments to read when GRO is enabled. Used as a factor to +294 /// compute the receive buffer size. +295 /// +296 /// Returns 1 if the platform doesn't support GRO. +297 #[inline] +298 pub fn gro_segments(&self) -> NonZeroUsize { +299 self.gro_segments 300 } -301} -302 -303#[cfg(not(any(apple, target_os = "openbsd", target_os = "netbsd")))] -304fn send( -305 #[allow(unused_variables)] // only used on Linux -306 state: &UdpSocketState, -307 io: SockRef<'_>, -308 transmit: &Transmit<'_>, -309) -> io::Result<()> { -310 #[allow(unused_mut)] // only mutable on FreeBSD -311 let mut encode_src_ip = true; -312 #[cfg(target_os = "freebsd")] -313 { -314 let addr = io.local_addr()?; -315 let is_ipv4 = addr.family() == libc::AF_INET as libc::sa_family_t; -316 if is_ipv4 { -317 if let Some(socket) = addr.as_socket_ipv4() { -318 encode_src_ip = socket.ip() == &Ipv4Addr::UNSPECIFIED; -319 } -320 } -321 } -322 let mut msg_hdr: libc::msghdr = unsafe { mem::zeroed() }; -323 let mut iovec: libc::iovec = unsafe { mem::zeroed() }; -324 let mut cmsgs = cmsg::Aligned([0u8; CMSG_LEN]); -325 let dst_addr = socket2::SockAddr::from(transmit.destination); -326 prepare_msg( -327 transmit, -328 &dst_addr, -329 &mut msg_hdr, -330 &mut iovec, -331 &mut cmsgs, -332 encode_src_ip, -333 state.sendmsg_einval(), -334 ); -335 -336 loop { -337 let n = unsafe { libc::sendmsg(io.as_raw_fd(), &msg_hdr, 0) }; +301 +302 /// Resize the send buffer of `socket` to `bytes` +303 #[inline] +304 pub fn set_send_buffer_size(&self, socket: UdpSockRef<'_>, bytes: usize) -> io::Result<()> { +305 socket.0.set_send_buffer_size(bytes) +306 } +307 +308 /// Resize the receive buffer of `socket` to `bytes` +309 #[inline] +310 pub fn set_recv_buffer_size(&self, socket: UdpSockRef<'_>, bytes: usize) -> io::Result<()> { +311 socket.0.set_recv_buffer_size(bytes) +312 } +313 +314 /// Get the size of the `socket` send buffer +315 #[inline] +316 pub fn send_buffer_size(&self, socket: UdpSockRef<'_>) -> io::Result<usize> { +317 socket.0.send_buffer_size() +318 } +319 +320 /// Get the size of the `socket` receive buffer +321 #[inline] +322 pub fn recv_buffer_size(&self, socket: UdpSockRef<'_>) -> io::Result<usize> { +323 socket.0.recv_buffer_size() +324 } +325 +326 /// Whether transmitted datagrams might get fragmented by the IP layer +327 /// +328 /// Returns `false` on targets which employ e.g. the `IPV6_DONTFRAG` socket option. +329 #[inline] +330 pub fn may_fragment(&self) -> bool { +331 self.may_fragment +332 } +333 +334 /// Returns true if we previously got an EINVAL error from `sendmsg` syscall. +335 fn sendmsg_einval(&self) -> bool { +336 self.sendmsg_einval.load(Ordering::Relaxed) +337 } 338 -339 if n >= 0 { -340 return Ok(()); -341 } -342 -343 let e = io::Error::last_os_error(); -344 match e.kind() { -345 // Retry the transmission -346 io::ErrorKind::Interrupted => continue, -347 io::ErrorKind::WouldBlock => return Err(e), -348 _ => { -349 // Some network adapters and drivers do not support GSO. Unfortunately, Linux -350 // offers no easy way for us to detect this short of an EIO or sometimes EINVAL -351 // when we try to actually send datagrams using it. -352 #[cfg(any(target_os = "linux", target_os = "android"))] -353 if let Some(libc::EIO) | Some(libc::EINVAL) = e.raw_os_error() { -354 // Prevent new transmits from being scheduled using GSO. Existing GSO transmits -355 // may already be in the pipeline, so we need to tolerate additional failures. -356 if state.max_gso_segments().get() > 1 { -357 crate::log::info!( -358 "`libc::sendmsg` failed with {e}; halting segmentation offload" -359 ); -360 state -361 .max_gso_segments -362 .store(1, std::sync::atomic::Ordering::Relaxed); -363 } -364 } +339 /// Sets the flag indicating we got EINVAL error from `sendmsg` syscall. +340 #[cfg(not(any(apple, target_os = "openbsd", target_os = "netbsd")))] +341 fn set_sendmsg_einval(&self) { +342 self.sendmsg_einval.store(true, Ordering::Relaxed) +343 } +344 +345 /// Enables Apple's fast UDP datapath using private `sendmsg_x`/`recvmsg_x` APIs. +346 /// Once enabled, this also updates [`max_gso_segments`] to allow batched sends. +347 /// +348 /// # Safety +349 /// +350 /// These APIs may crash on unsupported OS versions, so callers must verify +351 /// availability before enabling. +352 /// +353 /// [`max_gso_segments`]: Self::max_gso_segments +354 #[cfg(apple_fast)] +355 pub unsafe fn set_apple_fast_path(&self) { +356 self.apple_fast_path.store(true, Ordering::Relaxed); +357 self.max_gso_segments.store(BATCH_SIZE, Ordering::Relaxed); +358 } +359 +360 /// Returns whether Apple's fast UDP datapath is enabled for this socket. +361 #[cfg(apple_fast)] +362 pub fn is_apple_fast_path_enabled(&self) -> bool { +363 self.apple_fast_path.load(Ordering::Relaxed) +364 } 365 -366 // Some arguments to `sendmsg` are not supported. Switch to -367 // fallback mode and retry if we haven't already. -368 if e.raw_os_error() == Some(libc::EINVAL) && !state.sendmsg_einval() { -369 state.set_sendmsg_einval(); -370 prepare_msg( -371 transmit, -372 &dst_addr, -373 &mut msg_hdr, -374 &mut iovec, -375 &mut cmsgs, -376 encode_src_ip, -377 state.sendmsg_einval(), -378 ); -379 continue; -380 } -381 -382 return Err(e); -383 } -384 } -385 } -386} -387 -388#[cfg(apple_fast)] -389fn send(state: &UdpSocketState, io: SockRef<'_>, transmit: &Transmit<'_>) -> io::Result<()> { -390 let mut hdrs = unsafe { mem::zeroed::<[msghdr_x; BATCH_SIZE]>() }; -391 let mut iovs = unsafe { mem::zeroed::<[libc::iovec; BATCH_SIZE]>() }; -392 let mut ctrls = [cmsg::Aligned([0u8; CMSG_LEN]); BATCH_SIZE]; -393 let addr = socket2::SockAddr::from(transmit.destination); -394 let segment_size = transmit.segment_size.unwrap_or(transmit.contents.len()); -395 let mut cnt = 0; -396 debug_assert!(transmit.contents.len().div_ceil(segment_size) <= BATCH_SIZE); -397 for (i, chunk) in transmit -398 .contents -399 .chunks(segment_size) -400 .enumerate() -401 .take(BATCH_SIZE) -402 { -403 prepare_msg( -404 &Transmit { -405 destination: transmit.destination, -406 ecn: transmit.ecn, -407 contents: chunk, -408 segment_size: Some(chunk.len()), -409 src_ip: transmit.src_ip, -410 }, -411 &addr, -412 &mut hdrs[i], -413 &mut iovs[i], -414 &mut ctrls[i], -415 true, -416 state.sendmsg_einval(), -417 ); -418 hdrs[i].msg_datalen = chunk.len(); -419 cnt += 1; -420 } -421 loop { -422 let n = unsafe { sendmsg_x(io.as_raw_fd(), hdrs.as_ptr(), cnt as u32, 0) }; -423 -424 if n >= 0 { -425 return Ok(()); -426 } -427 -428 let e = io::Error::last_os_error(); -429 match e.kind() { -430 // Retry the transmission -431 io::ErrorKind::Interrupted => continue, -432 _ => return Err(e), -433 } -434 } -435} -436 -437#[cfg(any(target_os = "openbsd", target_os = "netbsd", apple_slow))] -438fn send(state: &UdpSocketState, io: SockRef<'_>, transmit: &Transmit<'_>) -> io::Result<()> { -439 let mut hdr: libc::msghdr = unsafe { mem::zeroed() }; -440 let mut iov: libc::iovec = unsafe { mem::zeroed() }; -441 let mut ctrl = cmsg::Aligned([0u8; CMSG_LEN]); -442 let addr = socket2::SockAddr::from(transmit.destination); -443 prepare_msg( -444 transmit, -445 &addr, -446 &mut hdr, -447 &mut iov, -448 &mut ctrl, -449 cfg!(apple) || cfg!(target_os = "openbsd") || cfg!(target_os = "netbsd"), -450 state.sendmsg_einval(), -451 ); -452 loop { -453 let n = unsafe { libc::sendmsg(io.as_raw_fd(), &hdr, 0) }; -454 -455 if n >= 0 { -456 return Ok(()); -457 } -458 -459 let e = io::Error::last_os_error(); -460 match e.kind() { -461 // Retry the transmission -462 io::ErrorKind::Interrupted => continue, -463 _ => return Err(e), -464 } -465 } -466} -467 -468#[cfg(not(any( -469 apple, -470 target_os = "openbsd", -471 target_os = "netbsd", -472 target_os = "dragonfly", -473 solarish -474)))] -475fn recv(io: SockRef<'_>, bufs: &mut [IoSliceMut<'_>], meta: &mut [RecvMeta]) -> io::Result<usize> { -476 let mut names = [MaybeUninit::<libc::sockaddr_storage>::uninit(); BATCH_SIZE]; -477 let mut ctrls = [cmsg::Aligned(MaybeUninit::<[u8; CMSG_LEN]>::uninit()); BATCH_SIZE]; -478 let mut hdrs = unsafe { mem::zeroed::<[libc::mmsghdr; BATCH_SIZE]>() }; -479 let max_msg_count = bufs.len().min(BATCH_SIZE); -480 for i in 0..max_msg_count { -481 prepare_recv( -482 &mut bufs[i], -483 &mut names[i], -484 &mut ctrls[i], -485 &mut hdrs[i].msg_hdr, -486 ); -487 } -488 let msg_count = loop { -489 let n = unsafe { -490 libc::recvmmsg( -491 io.as_raw_fd(), -492 hdrs.as_mut_ptr(), -493 bufs.len().min(BATCH_SIZE) as _, -494 0, -495 ptr::null_mut::<libc::timespec>(), -496 ) -497 }; -498 -499 if n >= 0 { -500 break n; -501 } -502 -503 let e = io::Error::last_os_error(); -504 match e.kind() { -505 // Retry receiving -506 io::ErrorKind::Interrupted => continue, -507 _ => return Err(e), -508 } -509 }; -510 for i in 0..(msg_count as usize) { -511 meta[i] = decode_recv(&names[i], &hdrs[i].msg_hdr, hdrs[i].msg_len as usize)?; -512 } -513 Ok(msg_count as usize) -514} -515 -516#[cfg(apple_fast)] -517fn recv(io: SockRef<'_>, bufs: &mut [IoSliceMut<'_>], meta: &mut [RecvMeta]) -> io::Result<usize> { -518 let mut names = [MaybeUninit::<libc::sockaddr_storage>::uninit(); BATCH_SIZE]; -519 // MacOS 10.15 `recvmsg_x` does not override the `msghdr_x` -520 // `msg_controllen`. Thus, after the call to `recvmsg_x`, one does not know -521 // which control messages have been written to. To prevent reading -522 // uninitialized memory, do not use `MaybeUninit` for `ctrls`, instead -523 // initialize `ctrls` with `0`s. A control message of all `0`s is -524 // automatically skipped by `libc::CMSG_NXTHDR`. -525 let mut ctrls = [cmsg::Aligned([0u8; CMSG_LEN]); BATCH_SIZE]; -526 let mut hdrs = unsafe { mem::zeroed::<[msghdr_x; BATCH_SIZE]>() }; -527 let max_msg_count = bufs.len().min(BATCH_SIZE); -528 for i in 0..max_msg_count { -529 prepare_recv(&mut bufs[i], &mut names[i], &mut ctrls[i], &mut hdrs[i]); -530 } -531 let msg_count = loop { -532 let n = unsafe { recvmsg_x(io.as_raw_fd(), hdrs.as_mut_ptr(), max_msg_count as _, 0) }; -533 -534 if n >= 0 { -535 break n; -536 } -537 -538 let e = io::Error::last_os_error(); -539 match e.kind() { -540 // Retry receiving -541 io::ErrorKind::Interrupted => continue, -542 _ => return Err(e), -543 } -544 }; -545 for i in 0..(msg_count as usize) { -546 meta[i] = decode_recv(&names[i], &hdrs[i], hdrs[i].msg_datalen as usize)?; -547 } -548 Ok(msg_count as usize) -549} -550 -551#[cfg(any( +366 /// Disables Apple's fast UDP datapath, reverting to `sendmsg`/`recvmsg`. +367 #[cfg(apple_fast)] +368 fn disable_apple_fast_path(&self) { +369 self.apple_fast_path.store(false, Ordering::Relaxed); +370 self.max_gso_segments.store(1, Ordering::Relaxed); +371 } +372 +373 /// Resolves an Apple fast-path function pointer via `resolver`, disabling the fast path if +374 /// the symbol is absent so that future calls use the slow path directly. +375 #[cfg(apple_fast)] +376 fn resolve_apple_fast_fn<T>(&self, resolver: fn() -> Option<T>) -> Option<T> { +377 let f = resolver(); +378 if f.is_none() { +379 self.disable_apple_fast_path(); +380 } +381 f +382 } +383} +384 +385#[cfg(not(any(apple, target_os = "openbsd", target_os = "netbsd")))] +386fn send( +387 #[allow(unused_variables)] // only used on Linux +388 state: &UdpSocketState, +389 io: SockRef<'_>, +390 transmit: &Transmit<'_>, +391) -> io::Result<()> { +392 #[allow(unused_mut)] // only mutable on FreeBSD +393 let mut encode_src_ip = true; +394 #[cfg(target_os = "freebsd")] +395 { +396 let addr = io.local_addr()?; +397 let is_ipv4 = addr.family() == libc::AF_INET as libc::sa_family_t; +398 if is_ipv4 { +399 if let Some(socket) = addr.as_socket_ipv4() { +400 encode_src_ip = socket.ip() == &Ipv4Addr::UNSPECIFIED; +401 } +402 } +403 } +404 let mut msg_hdr: libc::msghdr = unsafe { mem::zeroed() }; +405 let mut iovec: libc::iovec = unsafe { mem::zeroed() }; +406 let mut cmsgs = cmsg::Aligned([0u8; CMSG_LEN]); +407 let dst_addr = socket2::SockAddr::from(transmit.destination); +408 prepare_msg( +409 transmit, +410 &dst_addr, +411 &mut msg_hdr, +412 &mut iovec, +413 &mut cmsgs, +414 encode_src_ip, +415 state.sendmsg_einval(), +416 ); +417 +418 loop { +419 let n = unsafe { libc::sendmsg(io.as_raw_fd(), &msg_hdr, 0) }; +420 +421 if n >= 0 { +422 return Ok(()); +423 } +424 +425 let e = io::Error::last_os_error(); +426 match e.kind() { +427 // Retry the transmission +428 io::ErrorKind::Interrupted => continue, +429 io::ErrorKind::WouldBlock => return Err(e), +430 _ => { +431 // Some network adapters and drivers do not support GSO. Unfortunately, Linux +432 // offers no easy way for us to detect this short of an EIO or sometimes EINVAL +433 // when we try to actually send datagrams using it. +434 #[cfg(any(target_os = "linux", target_os = "android"))] +435 if let Some(libc::EIO) | Some(libc::EINVAL) = e.raw_os_error() { +436 // Prevent new transmits from being scheduled using GSO. Existing GSO transmits +437 // may already be in the pipeline, so we need to tolerate additional failures. +438 if state.max_gso_segments().get() > 1 { +439 crate::log::info!( +440 "`libc::sendmsg` failed with {e}; halting segmentation offload" +441 ); +442 state +443 .max_gso_segments +444 .store(1, std::sync::atomic::Ordering::Relaxed); +445 } +446 } +447 +448 // Some arguments to `sendmsg` are not supported. Switch to +449 // fallback mode and retry if we haven't already. +450 if e.raw_os_error() == Some(libc::EINVAL) && !state.sendmsg_einval() { +451 state.set_sendmsg_einval(); +452 prepare_msg( +453 transmit, +454 &dst_addr, +455 &mut msg_hdr, +456 &mut iovec, +457 &mut cmsgs, +458 encode_src_ip, +459 state.sendmsg_einval(), +460 ); +461 continue; +462 } +463 +464 return Err(e); +465 } +466 } +467 } +468} +469 +470#[cfg(apple_fast)] +471fn send(state: &UdpSocketState, io: SockRef<'_>, transmit: &Transmit<'_>) -> io::Result<()> { +472 if state.is_apple_fast_path_enabled() { +473 send_via_sendmsg_x(state, io, transmit) +474 } else { +475 send_single(state, io, transmit) +476 } +477} +478 +479/// Send using the fast `sendmsg_x` API. +480#[cfg(apple_fast)] +481fn send_via_sendmsg_x( +482 state: &UdpSocketState, +483 io: SockRef<'_>, +484 transmit: &Transmit<'_>, +485) -> io::Result<()> { +486 let mut hdrs = unsafe { mem::zeroed::<[msghdr_x; BATCH_SIZE]>() }; +487 let mut iovs = unsafe { mem::zeroed::<[libc::iovec; BATCH_SIZE]>() }; +488 let mut ctrls = [cmsg::Aligned([0u8; CMSG_LEN]); BATCH_SIZE]; +489 let addr = socket2::SockAddr::from(transmit.destination); +490 let segment_size = transmit.segment_size.unwrap_or(transmit.contents.len()); +491 let mut cnt = 0; +492 debug_assert!(transmit.contents.len().div_ceil(segment_size) <= BATCH_SIZE); +493 for (i, chunk) in transmit +494 .contents +495 .chunks(segment_size) +496 .enumerate() +497 .take(BATCH_SIZE) +498 { +499 prepare_msg_x( +500 &Transmit { +501 destination: transmit.destination, +502 ecn: transmit.ecn, +503 contents: chunk, +504 segment_size: Some(chunk.len()), +505 src_ip: transmit.src_ip, +506 }, +507 &addr, +508 &mut hdrs[i], +509 &mut iovs[i], +510 &mut ctrls[i], +511 true, +512 state.sendmsg_einval(), +513 ); +514 hdrs[i].msg_datalen = chunk.len(); +515 cnt += 1; +516 } +517 let Some(sendmsg_x) = state.resolve_apple_fast_fn(sendmsg_x_fn) else { +518 return send_single(state, io, transmit); +519 }; +520 retry_if_interrupted(|| unsafe { sendmsg_x(io.as_raw_fd(), hdrs.as_ptr(), cnt as u32, 0) })?; +521 Ok(()) +522} +523 +524#[cfg(any(target_os = "openbsd", target_os = "netbsd", apple_slow))] +525fn send(state: &UdpSocketState, io: SockRef<'_>, transmit: &Transmit<'_>) -> io::Result<()> { +526 send_single(state, io, transmit) +527} +528 +529#[cfg(any(target_os = "openbsd", target_os = "netbsd", apple))] +530#[cfg_attr(apple_fast, allow(dead_code))] // Unused when apple_fast is enabled +531fn send_single(state: &UdpSocketState, io: SockRef<'_>, transmit: &Transmit<'_>) -> io::Result<()> { +532 let mut hdr: libc::msghdr = unsafe { mem::zeroed() }; +533 let mut iov: libc::iovec = unsafe { mem::zeroed() }; +534 let mut ctrl = cmsg::Aligned([0u8; CMSG_LEN]); +535 let addr = socket2::SockAddr::from(transmit.destination); +536 prepare_msg( +537 transmit, +538 &addr, +539 &mut hdr, +540 &mut iov, +541 &mut ctrl, +542 cfg!(apple) || cfg!(target_os = "openbsd") || cfg!(target_os = "netbsd"), +543 state.sendmsg_einval(), +544 ); +545 retry_if_interrupted(|| unsafe { libc::sendmsg(io.as_raw_fd(), &hdr, 0) })?; +546 Ok(()) +547} +548 +549/// Receive using the batched `recvmmsg` syscall. +550#[cfg(not(any( +551 apple, 552 target_os = "openbsd", 553 target_os = "netbsd", 554 target_os = "dragonfly", -555 solarish, -556 apple_slow -557))] -558fn recv(io: SockRef<'_>, bufs: &mut [IoSliceMut<'_>], meta: &mut [RecvMeta]) -> io::Result<usize> { -559 let mut name = MaybeUninit::<libc::sockaddr_storage>::uninit(); -560 let mut ctrl = cmsg::Aligned(MaybeUninit::<[u8; CMSG_LEN]>::uninit()); -561 let mut hdr = unsafe { mem::zeroed::<libc::msghdr>() }; -562 prepare_recv(&mut bufs[0], &mut name, &mut ctrl, &mut hdr); -563 let n = loop { -564 let n = unsafe { libc::recvmsg(io.as_raw_fd(), &mut hdr, 0) }; -565 -566 if hdr.msg_flags & libc::MSG_TRUNC != 0 { -567 continue; -568 } -569 -570 if n >= 0 { -571 break n; -572 } -573 -574 let e = io::Error::last_os_error(); -575 match e.kind() { -576 // Retry receiving -577 io::ErrorKind::Interrupted => continue, -578 _ => return Err(e), -579 } -580 }; -581 meta[0] = decode_recv(&name, &hdr, n as usize)?; -582 Ok(1) -583} -584 -585const CMSG_LEN: usize = 88; -586 -587fn prepare_msg( -588 transmit: &Transmit<'_>, -589 dst_addr: &socket2::SockAddr, -590 #[cfg(not(apple_fast))] hdr: &mut libc::msghdr, -591 #[cfg(apple_fast)] hdr: &mut msghdr_x, -592 iov: &mut libc::iovec, -593 ctrl: &mut cmsg::Aligned<[u8; CMSG_LEN]>, -594 #[allow(unused_variables)] // only used on FreeBSD & macOS -595 encode_src_ip: bool, -596 sendmsg_einval: bool, -597) { -598 iov.iov_base = transmit.contents.as_ptr() as *const _ as *mut _; -599 iov.iov_len = transmit.contents.len(); -600 -601 // SAFETY: Casting the pointer to a mutable one is legal, -602 // as sendmsg is guaranteed to not alter the mutable pointer -603 // as per the POSIX spec. See the section on the sys/socket.h -604 // header for details. The type is only mutable in the first -605 // place because it is reused by recvmsg as well. -606 let name = dst_addr.as_ptr() as *mut libc::c_void; -607 let namelen = dst_addr.len(); -608 hdr.msg_name = name as *mut _; -609 hdr.msg_namelen = namelen; -610 hdr.msg_iov = iov; -611 hdr.msg_iovlen = 1; -612 -613 hdr.msg_control = ctrl.0.as_mut_ptr() as _; -614 hdr.msg_controllen = CMSG_LEN as _; -615 let mut encoder = unsafe { cmsg::Encoder::new(hdr) }; -616 let ecn = transmit.ecn.map_or(0, |x| x as libc::c_int); -617 // True for IPv4 or IPv4-Mapped IPv6 -618 let is_ipv4 = transmit.destination.is_ipv4() -619 || matches!(transmit.destination.ip(), IpAddr::V6(addr) if addr.to_ipv4_mapped().is_some()); -620 if is_ipv4 { -621 if !sendmsg_einval { -622 #[cfg(not(target_os = "netbsd"))] -623 { -624 encoder.push(libc::IPPROTO_IP, libc::IP_TOS, ecn as IpTosTy); -625 } -626 } -627 } else { -628 encoder.push(libc::IPPROTO_IPV6, libc::IPV6_TCLASS, ecn); -629 } -630 -631 if let Some(segment_size) = transmit.effective_segment_size() { -632 gso::set_segment_size(&mut encoder, segment_size as u16); -633 } -634 -635 if let Some(ip) = &transmit.src_ip { -636 match ip { -637 IpAddr::V4(v4) => { -638 #[cfg(any(target_os = "linux", target_os = "android"))] -639 { -640 let pktinfo = libc::in_pktinfo { -641 ipi_ifindex: 0, -642 ipi_spec_dst: libc::in_addr { -643 s_addr: u32::from_ne_bytes(v4.octets()), -644 }, -645 ipi_addr: libc::in_addr { s_addr: 0 }, -646 }; -647 encoder.push(libc::IPPROTO_IP, libc::IP_PKTINFO, pktinfo); -648 } -649 #[cfg(any(bsd, apple, solarish))] -650 { -651 if encode_src_ip { -652 let addr = libc::in_addr { -653 s_addr: u32::from_ne_bytes(v4.octets()), -654 }; -655 encoder.push(libc::IPPROTO_IP, libc::IP_RECVDSTADDR, addr); -656 } -657 } -658 } -659 IpAddr::V6(v6) => { -660 let pktinfo = libc::in6_pktinfo { -661 ipi6_ifindex: 0, -662 ipi6_addr: libc::in6_addr { -663 s6_addr: v6.octets(), -664 }, -665 }; -666 encoder.push(libc::IPPROTO_IPV6, libc::IPV6_PKTINFO, pktinfo); -667 } -668 } -669 } -670 -671 encoder.finish(); -672} -673 -674#[cfg(not(apple_fast))] -675fn prepare_recv( -676 buf: &mut IoSliceMut<'_>, -677 name: &mut MaybeUninit<libc::sockaddr_storage>, -678 ctrl: &mut cmsg::Aligned<MaybeUninit<[u8; CMSG_LEN]>>, -679 hdr: &mut libc::msghdr, -680) { -681 hdr.msg_name = name.as_mut_ptr() as _; -682 hdr.msg_namelen = mem::size_of::<libc::sockaddr_storage>() as _; -683 hdr.msg_iov = buf as *mut IoSliceMut<'_> as *mut libc::iovec; -684 hdr.msg_iovlen = 1; -685 hdr.msg_control = ctrl.0.as_mut_ptr() as _; -686 hdr.msg_controllen = CMSG_LEN as _; -687 hdr.msg_flags = 0; -688} -689 -690#[cfg(apple_fast)] -691fn prepare_recv( -692 buf: &mut IoSliceMut<'_>, -693 name: &mut MaybeUninit<libc::sockaddr_storage>, -694 ctrl: &mut cmsg::Aligned<[u8; CMSG_LEN]>, -695 hdr: &mut msghdr_x, -696) { -697 hdr.msg_name = name.as_mut_ptr() as _; -698 hdr.msg_namelen = mem::size_of::<libc::sockaddr_storage>() as _; -699 hdr.msg_iov = buf as *mut IoSliceMut<'_> as *mut libc::iovec; -700 hdr.msg_iovlen = 1; -701 hdr.msg_control = ctrl.0.as_mut_ptr() as _; -702 hdr.msg_controllen = CMSG_LEN as _; -703 hdr.msg_flags = 0; -704 hdr.msg_datalen = buf.len(); -705} -706 -707fn decode_recv<M: cmsg::MsgHdr<ControlMessage = libc::cmsghdr>>( -708 name: &MaybeUninit<libc::sockaddr_storage>, -709 hdr: &M, -710 len: usize, -711) -> io::Result<RecvMeta> { -712 let name = unsafe { name.assume_init() }; -713 let mut ecn_bits = 0; -714 let mut dst_ip = None; -715 let mut interface_index = None; -716 #[allow(unused_mut)] // only mutable on Linux -717 let mut stride = len; -718 -719 let cmsg_iter = unsafe { cmsg::Iter::new(hdr) }; -720 for cmsg in cmsg_iter { -721 match (cmsg.cmsg_level, cmsg.cmsg_type) { -722 (libc::IPPROTO_IP, libc::IP_TOS) => unsafe { -723 ecn_bits = cmsg::decode::<u8, libc::cmsghdr>(cmsg); -724 }, -725 // FreeBSD uses IP_RECVTOS here, and we can be liberal because cmsgs are opt-in. -726 #[cfg(not(any( -727 target_os = "openbsd", -728 target_os = "netbsd", -729 target_os = "dragonfly", -730 solarish -731 )))] -732 (libc::IPPROTO_IP, libc::IP_RECVTOS) => unsafe { -733 ecn_bits = cmsg::decode::<u8, libc::cmsghdr>(cmsg); -734 }, -735 (libc::IPPROTO_IPV6, libc::IPV6_TCLASS) => unsafe { -736 // Temporary hack around broken macos ABI. Remove once upstream fixes it. -737 // https://bugreport.apple.com/web/?problemID=48761855 -738 #[allow(clippy::unnecessary_cast)] // cmsg.cmsg_len defined as size_t -739 if cfg!(apple) -740 && cmsg.cmsg_len as usize == libc::CMSG_LEN(mem::size_of::<u8>() as _) as usize -741 { -742 ecn_bits = cmsg::decode::<u8, libc::cmsghdr>(cmsg); -743 } else { -744 ecn_bits = cmsg::decode::<libc::c_int, libc::cmsghdr>(cmsg) as u8; -745 } -746 }, -747 #[cfg(any(target_os = "linux", target_os = "android"))] -748 (libc::IPPROTO_IP, libc::IP_PKTINFO) => { -749 let pktinfo = unsafe { cmsg::decode::<libc::in_pktinfo, libc::cmsghdr>(cmsg) }; -750 dst_ip = Some(IpAddr::V4(Ipv4Addr::from( -751 pktinfo.ipi_addr.s_addr.to_ne_bytes(), -752 ))); -753 interface_index = Some(pktinfo.ipi_ifindex as u32); -754 } -755 #[cfg(any(bsd, apple))] -756 (libc::IPPROTO_IP, libc::IP_RECVDSTADDR) => { -757 let in_addr = unsafe { cmsg::decode::<libc::in_addr, libc::cmsghdr>(cmsg) }; -758 dst_ip = Some(IpAddr::V4(Ipv4Addr::from(in_addr.s_addr.to_ne_bytes()))); -759 } -760 (libc::IPPROTO_IPV6, libc::IPV6_PKTINFO) => { -761 let pktinfo = unsafe { cmsg::decode::<libc::in6_pktinfo, libc::cmsghdr>(cmsg) }; -762 dst_ip = Some(IpAddr::V6(Ipv6Addr::from(pktinfo.ipi6_addr.s6_addr))); -763 interface_index = Some(pktinfo.ipi6_ifindex as u32); -764 } -765 #[cfg(any(target_os = "linux", target_os = "android"))] -766 (libc::SOL_UDP, libc::UDP_GRO) => unsafe { -767 stride = cmsg::decode::<libc::c_int, libc::cmsghdr>(cmsg) as usize; -768 }, -769 _ => {} -770 } -771 } -772 -773 let addr = match libc::c_int::from(name.ss_family) { -774 libc::AF_INET => { -775 // Safety: if the ss_family field is AF_INET then storage must be a sockaddr_in. -776 let addr: &libc::sockaddr_in = -777 unsafe { &*(&name as *const _ as *const libc::sockaddr_in) }; -778 SocketAddr::V4(SocketAddrV4::new( -779 Ipv4Addr::from(addr.sin_addr.s_addr.to_ne_bytes()), -780 u16::from_be(addr.sin_port), -781 )) -782 } -783 libc::AF_INET6 => { -784 // Safety: if the ss_family field is AF_INET6 then storage must be a sockaddr_in6. -785 let addr: &libc::sockaddr_in6 = -786 unsafe { &*(&name as *const _ as *const libc::sockaddr_in6) }; -787 SocketAddr::V6(SocketAddrV6::new( -788 Ipv6Addr::from(addr.sin6_addr.s6_addr), -789 u16::from_be(addr.sin6_port), -790 addr.sin6_flowinfo, -791 addr.sin6_scope_id, -792 )) -793 } -794 f => { -795 return Err(io::Error::other(format!( -796 "expected AF_INET or AF_INET6, got {f} in decode_recv" -797 ))); -798 } -799 }; -800 -801 Ok(RecvMeta { -802 len, -803 stride, -804 addr, -805 ecn: EcnCodepoint::from_bits(ecn_bits), -806 dst_ip, -807 interface_index, -808 }) -809} -810 -811#[cfg(not(apple_slow))] -812// Chosen somewhat arbitrarily; might benefit from additional tuning. -813pub(crate) const BATCH_SIZE: usize = 32; -814 -815#[cfg(apple_slow)] -816pub(crate) const BATCH_SIZE: usize = 1; -817 -818#[cfg(any(target_os = "linux", target_os = "android"))] -819mod gso { -820 use super::*; -821 use std::{ffi::CStr, mem, str::FromStr, sync::OnceLock}; -822 -823 // Support for UDP GSO has been added to linux kernel in version 4.18 -824 // https://github.com/torvalds/linux/commit/cb586c63e3fc5b227c51fd8c4cb40b34d3750645 -825 const SUPPORTED_SINCE: KernelVersion = KernelVersion { -826 version: 4, -827 major_revision: 18, -828 }; +555 solarish +556)))] +557fn recv_via_recvmmsg( +558 io: SockRef<'_>, +559 bufs: &mut [IoSliceMut<'_>], +560 meta: &mut [RecvMeta], +561) -> io::Result<usize> { +562 let mut names = [MaybeUninit::<libc::sockaddr_storage>::uninit(); BATCH_SIZE]; +563 let mut ctrls = [cmsg::Aligned(MaybeUninit::<[u8; CMSG_LEN]>::uninit()); BATCH_SIZE]; +564 let mut hdrs = unsafe { mem::zeroed::<[libc::mmsghdr; BATCH_SIZE]>() }; +565 let max_msg_count = bufs.len().min(BATCH_SIZE); +566 for i in 0..max_msg_count { +567 prepare_recv( +568 &mut bufs[i], +569 &mut names[i], +570 &mut ctrls[i], +571 &mut hdrs[i].msg_hdr, +572 ); +573 } +574 let msg_count = retry_if_interrupted(|| unsafe { +575 libc::recvmmsg( +576 io.as_raw_fd(), +577 hdrs.as_mut_ptr(), +578 bufs.len().min(BATCH_SIZE) as _, +579 0, +580 ptr::null_mut::<libc::timespec>(), +581 ) as isize +582 })?; +583 for i in 0..(msg_count as usize) { +584 meta[i] = decode_recv(&names[i], &hdrs[i].msg_hdr, hdrs[i].msg_len as usize)?; +585 } +586 Ok(msg_count as usize) +587} +588 +589/// Receive using the fast `recvmsg_x` API. +590#[cfg(apple_fast)] +591fn recv_via_recvmsg_x( +592 state: &UdpSocketState, +593 io: SockRef<'_>, +594 bufs: &mut [IoSliceMut<'_>], +595 meta: &mut [RecvMeta], +596) -> io::Result<usize> { +597 let mut names = [MaybeUninit::<libc::sockaddr_storage>::uninit(); BATCH_SIZE]; +598 // MacOS 10.15 `recvmsg_x` does not override the `msghdr_x` +599 // `msg_controllen`. Thus, after the call to `recvmsg_x`, one does not know +600 // which control messages have been written to. To prevent reading +601 // uninitialized memory, do not use `MaybeUninit` for `ctrls`, instead +602 // initialize `ctrls` with `0`s. A control message of all `0`s is +603 // automatically skipped by `libc::CMSG_NXTHDR`. +604 let mut ctrls = [cmsg::Aligned([0u8; CMSG_LEN]); BATCH_SIZE]; +605 let mut hdrs = unsafe { mem::zeroed::<[msghdr_x; BATCH_SIZE]>() }; +606 let max_msg_count = bufs.len().min(BATCH_SIZE); +607 for i in 0..max_msg_count { +608 prepare_recv_x(&mut bufs[i], &mut names[i], &mut ctrls[i], &mut hdrs[i]); +609 } +610 let Some(recvmsg_x) = state.resolve_apple_fast_fn(recvmsg_x_fn) else { +611 return recv_single(io, bufs, meta); +612 }; +613 let msg_count = retry_if_interrupted(|| unsafe { +614 recvmsg_x(io.as_raw_fd(), hdrs.as_mut_ptr(), max_msg_count as _, 0) +615 })?; +616 for i in 0..(msg_count as usize) { +617 meta[i] = decode_recv(&names[i], &hdrs[i], hdrs[i].msg_datalen as usize)?; +618 } +619 Ok(msg_count as usize) +620} +621 +622/// Returns the `sendmsg_x` function pointer, resolving it via `dlsym` on first call. +623/// +624/// Returns `None` if the symbol is not available on the current OS version. +625#[cfg(apple_fast)] +626fn sendmsg_x_fn() -> Option<SendmsgXFn> { +627 static ADDR: std::sync::OnceLock<usize> = std::sync::OnceLock::new(); +628 // SAFETY: `resolve_symbol` only returns non-zero addresses obtained from `dlsym`, which +629 // guarantees a callable symbol whose type matches the declaration above. +630 resolve_symbol(&ADDR, c"sendmsg_x") +631 .map(|addr| unsafe { std::mem::transmute::<usize, SendmsgXFn>(addr) }) +632} +633 +634/// Returns the `recvmsg_x` function pointer, resolving it via `dlsym` on first call. +635/// +636/// Returns `None` if the symbol is not available on the current OS version. +637#[cfg(apple_fast)] +638fn recvmsg_x_fn() -> Option<RecvmsgXFn> { +639 static ADDR: std::sync::OnceLock<usize> = std::sync::OnceLock::new(); +640 // SAFETY: `resolve_symbol` only returns non-zero addresses obtained from `dlsym`, which +641 // guarantees a callable symbol whose type matches the declaration above. +642 resolve_symbol(&ADDR, c"recvmsg_x") +643 .map(|addr| unsafe { std::mem::transmute::<usize, RecvmsgXFn>(addr) }) +644} +645 +646#[cfg(apple_fast)] +647type SendmsgXFn = +648 unsafe extern "C" fn(libc::c_int, *const msghdr_x, libc::c_uint, libc::c_int) -> isize; +649#[cfg(apple_fast)] +650type RecvmsgXFn = +651 unsafe extern "C" fn(libc::c_int, *mut msghdr_x, libc::c_uint, libc::c_int) -> isize; +652 +653/// Resolves a symbol via `dlsym` on first call, caching the result. +654/// +655/// Returns `None` if the symbol is not available on the current OS version. +656#[cfg(apple_fast)] +657fn resolve_symbol(lock: &std::sync::OnceLock<usize>, name: &std::ffi::CStr) -> Option<usize> { +658 let addr = +659 *lock.get_or_init(|| unsafe { libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr()) as usize }); +660 (addr != 0).then_some(addr) +661} +662 +663#[cfg(any( +664 target_os = "openbsd", +665 target_os = "netbsd", +666 target_os = "dragonfly", +667 solarish, +668 apple +669))] +670#[cfg_attr(apple_fast, allow(dead_code))] // Unused when apple_fast is enabled +671fn recv_single( +672 io: SockRef<'_>, +673 bufs: &mut [IoSliceMut<'_>], +674 meta: &mut [RecvMeta], +675) -> io::Result<usize> { +676 let mut name = MaybeUninit::<libc::sockaddr_storage>::uninit(); +677 let mut ctrl = cmsg::Aligned(MaybeUninit::<[u8; CMSG_LEN]>::uninit()); +678 let mut hdr = unsafe { mem::zeroed::<libc::msghdr>() }; +679 prepare_recv(&mut bufs[0], &mut name, &mut ctrl, &mut hdr); +680 let n = loop { +681 let n = unsafe { libc::recvmsg(io.as_raw_fd(), &mut hdr, 0) }; +682 +683 if hdr.msg_flags & libc::MSG_TRUNC != 0 { +684 continue; +685 } +686 +687 if n >= 0 { +688 break n; +689 } +690 +691 let e = io::Error::last_os_error(); +692 match e.kind() { +693 // Retry receiving +694 io::ErrorKind::Interrupted => continue, +695 _ => return Err(e), +696 } +697 }; +698 meta[0] = decode_recv(&name, &hdr, n as usize)?; +699 Ok(1) +700} +701 +702const CMSG_LEN: usize = 88; +703 +704#[cfg_attr(apple_fast, allow(dead_code))] // Unused when apple_fast is enabled +705fn prepare_msg( +706 transmit: &Transmit<'_>, +707 dst_addr: &socket2::SockAddr, +708 hdr: &mut libc::msghdr, +709 iov: &mut libc::iovec, +710 ctrl: &mut cmsg::Aligned<[u8; CMSG_LEN]>, +711 #[allow(unused_variables)] // only used on FreeBSD & macOS +712 encode_src_ip: bool, +713 sendmsg_einval: bool, +714) { +715 iov.iov_base = transmit.contents.as_ptr() as *const _ as *mut _; +716 iov.iov_len = transmit.contents.len(); +717 +718 // SAFETY: Casting the pointer to a mutable one is legal, +719 // as sendmsg is guaranteed to not alter the mutable pointer +720 // as per the POSIX spec. See the section on the sys/socket.h +721 // header for details. The type is only mutable in the first +722 // place because it is reused by recvmsg as well. +723 let name = dst_addr.as_ptr() as *mut libc::c_void; +724 let namelen = dst_addr.len(); +725 hdr.msg_name = name as *mut _; +726 hdr.msg_namelen = namelen; +727 hdr.msg_iov = iov; +728 hdr.msg_iovlen = 1; +729 +730 hdr.msg_control = ctrl.0.as_mut_ptr() as _; +731 hdr.msg_controllen = CMSG_LEN as _; +732 let mut encoder = unsafe { cmsg::Encoder::new(hdr) }; +733 let ecn = transmit.ecn.map_or(0, |x| x as libc::c_int); +734 // True for IPv4 or IPv4-Mapped IPv6 +735 let is_ipv4 = transmit.destination.is_ipv4() +736 || matches!(transmit.destination.ip(), IpAddr::V6(addr) if addr.to_ipv4_mapped().is_some()); +737 if is_ipv4 { +738 if !sendmsg_einval { +739 #[cfg(not(target_os = "netbsd"))] +740 { +741 encoder.push(libc::IPPROTO_IP, libc::IP_TOS, ecn as IpTosTy); +742 } +743 } +744 } else { +745 encoder.push(libc::IPPROTO_IPV6, libc::IPV6_TCLASS, ecn); +746 } +747 +748 // On apple_fast, prepare_msg is only compiled for send_single (fallback path), while the main +749 // send path uses prepare_msg_x with msghdr_x. gso::set_segment_size has a different signature +750 // when apple_fast is enabled, and it's a no-op on non-Linux platforms anyway. +751 #[cfg(not(apple_fast))] +752 if let Some(segment_size) = transmit.effective_segment_size() { +753 gso::set_segment_size(&mut encoder, segment_size as u16); +754 } +755 +756 if let Some(ip) = &transmit.src_ip { +757 match ip { +758 IpAddr::V4(v4) => { +759 #[cfg(any(target_os = "linux", target_os = "android"))] +760 { +761 let pktinfo = libc::in_pktinfo { +762 ipi_ifindex: 0, +763 ipi_spec_dst: libc::in_addr { +764 s_addr: u32::from_ne_bytes(v4.octets()), +765 }, +766 ipi_addr: libc::in_addr { s_addr: 0 }, +767 }; +768 encoder.push(libc::IPPROTO_IP, libc::IP_PKTINFO, pktinfo); +769 } +770 #[cfg(any(bsd, apple, solarish))] +771 { +772 if encode_src_ip { +773 let addr = libc::in_addr { +774 s_addr: u32::from_ne_bytes(v4.octets()), +775 }; +776 encoder.push(libc::IPPROTO_IP, libc::IP_RECVDSTADDR, addr); +777 } +778 } +779 } +780 IpAddr::V6(v6) => { +781 let pktinfo = libc::in6_pktinfo { +782 ipi6_ifindex: 0, +783 ipi6_addr: libc::in6_addr { +784 s6_addr: v6.octets(), +785 }, +786 }; +787 encoder.push(libc::IPPROTO_IPV6, libc::IPV6_PKTINFO, pktinfo); +788 } +789 } +790 } +791 +792 encoder.finish(); +793} +794 +795/// Prepares an `msghdr_x` for use with `sendmsg_x`. +796#[cfg(apple_fast)] +797fn prepare_msg_x( +798 transmit: &Transmit<'_>, +799 dst_addr: &socket2::SockAddr, +800 hdr: &mut msghdr_x, +801 iov: &mut libc::iovec, +802 ctrl: &mut cmsg::Aligned<[u8; CMSG_LEN]>, +803 #[allow(unused_variables)] encode_src_ip: bool, +804 sendmsg_einval: bool, +805) { +806 iov.iov_base = transmit.contents.as_ptr() as *const _ as *mut _; +807 iov.iov_len = transmit.contents.len(); +808 +809 let name = dst_addr.as_ptr() as *mut libc::c_void; +810 let namelen = dst_addr.len(); +811 hdr.msg_name = name as *mut _; +812 hdr.msg_namelen = namelen; +813 hdr.msg_iov = iov; +814 hdr.msg_iovlen = 1; +815 +816 hdr.msg_control = ctrl.0.as_mut_ptr() as _; +817 hdr.msg_controllen = CMSG_LEN as _; +818 let mut encoder = unsafe { cmsg::Encoder::new(hdr) }; +819 let ecn = transmit.ecn.map_or(0, |x| x as libc::c_int); +820 let is_ipv4 = transmit.destination.is_ipv4() +821 || matches!(transmit.destination.ip(), IpAddr::V6(addr) if addr.to_ipv4_mapped().is_some()); +822 if is_ipv4 { +823 if !sendmsg_einval { +824 encoder.push(libc::IPPROTO_IP, libc::IP_TOS, ecn as IpTosTy); +825 } +826 } else { +827 encoder.push(libc::IPPROTO_IPV6, libc::IPV6_TCLASS, ecn); +828 } 829 -830 /// Checks whether GSO support is available by checking the kernel version followed by setting -831 /// the UDP_SEGMENT option on a socket -832 pub(crate) fn max_gso_segments() -> usize { -833 const GSO_SIZE: libc::c_int = 1500; -834 -835 if !SUPPORTED_BY_CURRENT_KERNEL.get_or_init(supported_by_current_kernel) { -836 return 1; -837 } -838 -839 let Ok(socket) = std::net::UdpSocket::bind("[::]:0") -840 .or_else(|_| std::net::UdpSocket::bind((Ipv4Addr::LOCALHOST, 0))) -841 else { -842 return 1; -843 }; -844 -845 // As defined in linux/udp.h -846 // #define UDP_MAX_SEGMENTS (1 << 6UL) -847 match set_socket_option(&socket, libc::SOL_UDP, libc::UDP_SEGMENT, GSO_SIZE) { -848 Ok(()) => 64, -849 Err(_e) => { -850 crate::log::debug!( -851 "failed to set `UDP_SEGMENT` socket option ({_e}); setting `max_gso_segments = 1`" -852 ); -853 -854 1 -855 } -856 } -857 } -858 -859 pub(crate) fn set_segment_size( -860 encoder: &mut cmsg::Encoder<'_, libc::msghdr>, -861 segment_size: u16, -862 ) { -863 encoder.push(libc::SOL_UDP, libc::UDP_SEGMENT, segment_size); -864 } -865 -866 // Avoid calling `supported_by_current_kernel` for each socket by using `OnceLock`. -867 static SUPPORTED_BY_CURRENT_KERNEL: OnceLock<bool> = OnceLock::new(); -868 -869 fn supported_by_current_kernel() -> bool { -870 let kernel_version_string = match kernel_version_string() { -871 Ok(kernel_version_string) => kernel_version_string, -872 Err(_e) => { -873 crate::log::warn!("GSO disabled: uname returned {_e}"); -874 return false; -875 } -876 }; -877 -878 let Some(kernel_version) = KernelVersion::from_str(&kernel_version_string) else { -879 crate::log::warn!( -880 "GSO disabled: failed to parse kernel version ({kernel_version_string})" -881 ); -882 return false; -883 }; -884 -885 if kernel_version < SUPPORTED_SINCE { -886 crate::log::info!("GSO disabled: kernel too old ({kernel_version_string}); need 4.18+",); -887 return false; -888 } -889 -890 true -891 } -892 -893 fn kernel_version_string() -> io::Result<String> { -894 let mut n = unsafe { mem::zeroed() }; -895 let r = unsafe { libc::uname(&mut n) }; -896 if r != 0 { -897 return Err(io::Error::last_os_error()); -898 } -899 Ok(unsafe { -900 CStr::from_ptr(n.release[..].as_ptr()) -901 .to_string_lossy() -902 .into_owned() -903 }) -904 } -905 -906 // https://www.linfo.org/kernel_version_numbering.html -907 #[derive(Eq, PartialEq, Ord, PartialOrd, Debug)] -908 struct KernelVersion { -909 version: u8, -910 major_revision: u8, -911 } -912 -913 impl KernelVersion { -914 fn from_str(release: &str) -> Option<Self> { -915 let mut split = release -916 .split_once('-') -917 .map(|pair| pair.0) -918 .unwrap_or(release) -919 .split('.'); -920 -921 let version = u8::from_str(split.next()?).ok()?; -922 let major_revision = u8::from_str(split.next()?).ok()?; -923 -924 Some(Self { -925 version, -926 major_revision, -927 }) -928 } -929 } -930 -931 #[cfg(test)] -932 mod test { -933 use super::*; -934 -935 #[test] -936 fn parse_current_kernel_version_release_string() { -937 let release = kernel_version_string().unwrap(); -938 KernelVersion::from_str(&release).unwrap(); -939 } -940 -941 #[test] -942 fn parse_kernel_version_release_string() { -943 // These are made up for the test -944 assert_eq!( -945 KernelVersion::from_str("4.14"), -946 Some(KernelVersion { -947 version: 4, -948 major_revision: 14 -949 }) -950 ); -951 assert_eq!( -952 KernelVersion::from_str("4.18"), -953 Some(KernelVersion { -954 version: 4, -955 major_revision: 18 -956 }) -957 ); -958 // These were seen in the wild -959 assert_eq!( -960 KernelVersion::from_str("4.14.186-27095505"), -961 Some(KernelVersion { -962 version: 4, -963 major_revision: 14 -964 }) -965 ); -966 assert_eq!( -967 KernelVersion::from_str("6.8.0-59-generic"), -968 Some(KernelVersion { -969 version: 6, -970 major_revision: 8 -971 }) -972 ); -973 } -974 } -975} -976 -977// On Apple platforms using the `sendmsg_x` call, UDP datagram segmentation is not -978// offloaded to the NIC or even the kernel, but instead done here in user space in -979// [`send`]) and then passed to the OS as individual `iovec`s (up to `BATCH_SIZE`). -980#[cfg(not(any(target_os = "linux", target_os = "android")))] -981mod gso { -982 use super::*; +830 if let Some(ip) = &transmit.src_ip { +831 match ip { +832 IpAddr::V4(v4) => { +833 if encode_src_ip { +834 let addr = libc::in_addr { +835 s_addr: u32::from_ne_bytes(v4.octets()), +836 }; +837 encoder.push(libc::IPPROTO_IP, libc::IP_RECVDSTADDR, addr); +838 } +839 } +840 IpAddr::V6(v6) => { +841 let pktinfo = libc::in6_pktinfo { +842 ipi6_ifindex: 0, +843 ipi6_addr: libc::in6_addr { +844 s6_addr: v6.octets(), +845 }, +846 }; +847 encoder.push(libc::IPPROTO_IPV6, libc::IPV6_PKTINFO, pktinfo); +848 } +849 } +850 } +851 +852 encoder.finish(); +853} +854 +855#[cfg_attr(apple_fast, allow(dead_code))] // Unused when apple_fast is enabled +856fn prepare_recv( +857 buf: &mut IoSliceMut<'_>, +858 name: &mut MaybeUninit<libc::sockaddr_storage>, +859 ctrl: &mut cmsg::Aligned<MaybeUninit<[u8; CMSG_LEN]>>, +860 hdr: &mut libc::msghdr, +861) { +862 hdr.msg_name = name.as_mut_ptr() as _; +863 hdr.msg_namelen = mem::size_of::<libc::sockaddr_storage>() as _; +864 hdr.msg_iov = buf as *mut IoSliceMut<'_> as *mut libc::iovec; +865 hdr.msg_iovlen = 1; +866 hdr.msg_control = ctrl.0.as_mut_ptr() as _; +867 hdr.msg_controllen = CMSG_LEN as _; +868 hdr.msg_flags = 0; +869} +870 +871/// Prepares an `msghdr_x` for receiving with `recvmsg_x`. +872#[cfg(apple_fast)] +873fn prepare_recv_x( +874 buf: &mut IoSliceMut<'_>, +875 name: &mut MaybeUninit<libc::sockaddr_storage>, +876 ctrl: &mut cmsg::Aligned<[u8; CMSG_LEN]>, +877 hdr: &mut msghdr_x, +878) { +879 hdr.msg_name = name.as_mut_ptr() as _; +880 hdr.msg_namelen = mem::size_of::<libc::sockaddr_storage>() as _; +881 hdr.msg_iov = buf as *mut IoSliceMut<'_> as *mut libc::iovec; +882 hdr.msg_iovlen = 1; +883 hdr.msg_control = ctrl.0.as_mut_ptr() as _; +884 hdr.msg_controllen = CMSG_LEN as _; +885 hdr.msg_flags = 0; +886 hdr.msg_datalen = buf.len(); +887} +888 +889fn decode_recv<M: cmsg::MsgHdr<ControlMessage = libc::cmsghdr>>( +890 name: &MaybeUninit<libc::sockaddr_storage>, +891 hdr: &M, +892 len: usize, +893) -> io::Result<RecvMeta> { +894 let name = unsafe { name.assume_init() }; +895 let mut ctrl = ControlMetadata { +896 ecn_bits: 0, +897 dst_ip: None, +898 interface_index: None, +899 stride: len, +900 }; +901 +902 let cmsg_iter = unsafe { cmsg::Iter::new(hdr) }; +903 for cmsg in cmsg_iter { +904 ctrl.decode(cmsg); +905 } +906 +907 Ok(RecvMeta { +908 len, +909 stride: ctrl.stride, +910 addr: decode_socket_addr(&name)?, +911 ecn: EcnCodepoint::from_bits(ctrl.ecn_bits), +912 dst_ip: ctrl.dst_ip, +913 interface_index: ctrl.interface_index, +914 }) +915} +916 +917/// Metadata decoded from control messages +918struct ControlMetadata { +919 ecn_bits: u8, +920 dst_ip: Option<IpAddr>, +921 interface_index: Option<u32>, +922 stride: usize, +923} +924 +925impl ControlMetadata { +926 /// Decodes a control message and updates the metadata state +927 fn decode(&mut self, cmsg: &libc::cmsghdr) { +928 match (cmsg.cmsg_level, cmsg.cmsg_type) { +929 (libc::IPPROTO_IP, libc::IP_TOS) => unsafe { +930 self.ecn_bits = cmsg::decode::<u8, libc::cmsghdr>(cmsg); +931 }, +932 // FreeBSD uses IP_RECVTOS here, and we can be liberal because cmsgs are opt-in. +933 #[cfg(not(any( +934 target_os = "openbsd", +935 target_os = "netbsd", +936 target_os = "dragonfly", +937 solarish +938 )))] +939 (libc::IPPROTO_IP, libc::IP_RECVTOS) => unsafe { +940 self.ecn_bits = cmsg::decode::<u8, libc::cmsghdr>(cmsg); +941 }, +942 (libc::IPPROTO_IPV6, libc::IPV6_TCLASS) => unsafe { +943 // Temporary hack around broken macos ABI. Remove once upstream fixes it. +944 // https://bugreport.apple.com/web/?problemID=48761855 +945 #[allow(clippy::unnecessary_cast)] // cmsg.cmsg_len defined as size_t +946 if cfg!(apple) +947 && cmsg.cmsg_len as usize == libc::CMSG_LEN(mem::size_of::<u8>() as _) as usize +948 { +949 self.ecn_bits = cmsg::decode::<u8, libc::cmsghdr>(cmsg); +950 } else { +951 self.ecn_bits = cmsg::decode::<libc::c_int, libc::cmsghdr>(cmsg) as u8; +952 } +953 }, +954 #[cfg(any(target_os = "linux", target_os = "android"))] +955 (libc::IPPROTO_IP, libc::IP_PKTINFO) => { +956 let pktinfo = unsafe { cmsg::decode::<libc::in_pktinfo, libc::cmsghdr>(cmsg) }; +957 self.dst_ip = Some(IpAddr::V4(Ipv4Addr::from( +958 pktinfo.ipi_addr.s_addr.to_ne_bytes(), +959 ))); +960 self.interface_index = Some(pktinfo.ipi_ifindex as u32); +961 } +962 #[cfg(any(bsd, apple))] +963 (libc::IPPROTO_IP, libc::IP_RECVDSTADDR) => { +964 let in_addr = unsafe { cmsg::decode::<libc::in_addr, libc::cmsghdr>(cmsg) }; +965 self.dst_ip = Some(IpAddr::V4(Ipv4Addr::from(in_addr.s_addr.to_ne_bytes()))); +966 } +967 (libc::IPPROTO_IPV6, libc::IPV6_PKTINFO) => { +968 let pktinfo = unsafe { cmsg::decode::<libc::in6_pktinfo, libc::cmsghdr>(cmsg) }; +969 self.dst_ip = Some(IpAddr::V6(Ipv6Addr::from(pktinfo.ipi6_addr.s6_addr))); +970 #[cfg_attr(not(target_os = "android"), expect(clippy::unnecessary_cast))] +971 { +972 self.interface_index = Some(pktinfo.ipi6_ifindex as u32); +973 } +974 } +975 #[cfg(any(target_os = "linux", target_os = "android"))] +976 (libc::SOL_UDP, libc::UDP_GRO) => unsafe { +977 self.stride = cmsg::decode::<libc::c_int, libc::cmsghdr>(cmsg) as usize; +978 }, +979 _ => {} +980 } +981 } +982} 983 -984 pub(super) fn max_gso_segments() -> usize { -985 #[cfg(apple_fast)] -986 { -987 BATCH_SIZE -988 } -989 #[cfg(not(apple_fast))] -990 { -991 1 -992 } -993 } -994 -995 pub(super) fn set_segment_size( -996 #[cfg(not(apple_fast))] _encoder: &mut cmsg::Encoder<'_, libc::msghdr>, -997 #[cfg(apple_fast)] _encoder: &mut cmsg::Encoder<'_, msghdr_x>, -998 _segment_size: u16, -999 ) { -1000 } -1001} -1002 -1003#[cfg(any(target_os = "linux", target_os = "android"))] -1004mod gro { -1005 use super::*; -1006 -1007 pub(crate) fn gro_segments() -> NonZeroUsize { -1008 let Ok(socket) = std::net::UdpSocket::bind("[::]:0") -1009 .or_else(|_| std::net::UdpSocket::bind((Ipv4Addr::LOCALHOST, 0))) -1010 else { -1011 return NonZeroUsize::MIN; -1012 }; -1013 -1014 // As defined in net/ipv4/udp_offload.c -1015 // #define UDP_GRO_CNT_MAX 64 -1016 // -1017 // NOTE: this MUST be set to UDP_GRO_CNT_MAX to ensure that the receive buffer size -1018 // (get_max_udp_payload_size() * gro_segments()) is large enough to hold the largest GRO -1019 // list the kernel might potentially produce. See -1020 // https://github.com/quinn-rs/quinn/pull/1354. -1021 match set_socket_option(&socket, libc::SOL_UDP, libc::UDP_GRO, OPTION_ON) { -1022 Ok(()) => NonZeroUsize::new(64).expect("known"), -1023 Err(_) => NonZeroUsize::MIN, -1024 } -1025 } -1026} -1027 -1028/// Returns whether the given socket option is supported on the current platform -1029/// -1030/// Yields `Ok(true)` if the option was set successfully, `Ok(false)` if setting -1031/// the option raised an `ENOPROTOOPT` or `EOPNOTSUPP` error, and `Err` for any other error. -1032fn set_socket_option_supported( -1033 socket: &impl AsRawFd, -1034 level: libc::c_int, -1035 name: libc::c_int, -1036 value: libc::c_int, -1037) -> io::Result<bool> { -1038 match set_socket_option(socket, level, name, value) { -1039 Ok(()) => Ok(true), -1040 Err(err) if err.raw_os_error() == Some(libc::ENOPROTOOPT) => Ok(false), -1041 Err(err) if err.raw_os_error() == Some(libc::EOPNOTSUPP) => Ok(false), -1042 Err(err) => Err(err), -1043 } -1044} -1045 -1046fn set_socket_option( -1047 socket: &impl AsRawFd, -1048 level: libc::c_int, -1049 name: libc::c_int, -1050 value: libc::c_int, -1051) -> io::Result<()> { -1052 let rc = unsafe { -1053 libc::setsockopt( -1054 socket.as_raw_fd(), -1055 level, -1056 name, -1057 &value as *const _ as _, -1058 mem::size_of_val(&value) as _, -1059 ) -1060 }; -1061 -1062 match rc == 0 { -1063 true => Ok(()), -1064 false => Err(io::Error::last_os_error()), -1065 } -1066} -1067 -1068const OPTION_ON: libc::c_int = 1; +984/// Decodes a `sockaddr_storage` into a `SocketAddr` +985fn decode_socket_addr(name: &libc::sockaddr_storage) -> io::Result<SocketAddr> { +986 match libc::c_int::from(name.ss_family) { +987 libc::AF_INET => { +988 // Safety: if the ss_family field is AF_INET then storage must be a sockaddr_in. +989 let addr: &libc::sockaddr_in = +990 unsafe { &*(name as *const _ as *const libc::sockaddr_in) }; +991 Ok(SocketAddr::V4(SocketAddrV4::new( +992 Ipv4Addr::from(addr.sin_addr.s_addr.to_ne_bytes()), +993 u16::from_be(addr.sin_port), +994 ))) +995 } +996 libc::AF_INET6 => { +997 // Safety: if the ss_family field is AF_INET6 then storage must be a sockaddr_in6. +998 let addr: &libc::sockaddr_in6 = +999 unsafe { &*(name as *const _ as *const libc::sockaddr_in6) }; +1000 Ok(SocketAddr::V6(SocketAddrV6::new( +1001 Ipv6Addr::from(addr.sin6_addr.s6_addr), +1002 u16::from_be(addr.sin6_port), +1003 addr.sin6_flowinfo, +1004 addr.sin6_scope_id, +1005 ))) +1006 } +1007 f => Err(io::Error::other(format!( +1008 "expected AF_INET or AF_INET6, got {f}" +1009 ))), +1010 } +1011} +1012 +1013#[cfg(not(apple_slow))] +1014// Chosen somewhat arbitrarily; might benefit from additional tuning. +1015pub(crate) const BATCH_SIZE: usize = 32; +1016 +1017#[cfg(apple_slow)] +1018pub(crate) const BATCH_SIZE: usize = 1; +1019 +1020#[cfg(any(target_os = "linux", target_os = "android"))] +1021mod gso { +1022 use super::*; +1023 use std::{ffi::CStr, mem, str::FromStr, sync::OnceLock}; +1024 +1025 // Support for UDP GSO has been added to linux kernel in version 4.18 +1026 // https://github.com/torvalds/linux/commit/cb586c63e3fc5b227c51fd8c4cb40b34d3750645 +1027 const SUPPORTED_SINCE: KernelVersion = KernelVersion { +1028 version: 4, +1029 major_revision: 18, +1030 }; +1031 +1032 /// Checks whether GSO support is available by checking the kernel version followed by setting +1033 /// the UDP_SEGMENT option on a socket +1034 pub(crate) fn max_gso_segments(socket: &impl AsRawFd) -> usize { +1035 const GSO_SIZE: libc::c_int = 1500; +1036 +1037 if !SUPPORTED_BY_CURRENT_KERNEL.get_or_init(supported_by_current_kernel) { +1038 return 1; +1039 } +1040 +1041 // As defined in linux/udp.h +1042 // #define UDP_MAX_SEGMENTS (1 << 6UL) +1043 match set_socket_option(socket, libc::SOL_UDP, libc::UDP_SEGMENT, GSO_SIZE) { +1044 Ok(()) => { +1045 // Disable GSO again globally to ensure we can selectively enable it via cmsg. +1046 // See: +1047 // - https://github.com/quinn-rs/quinn/issues/2575 +1048 // - https://man7.org/linux/man-pages/man7/udp.7.html +1049 let _ = set_socket_option(socket, libc::SOL_UDP, libc::UDP_SEGMENT, 0); +1050 +1051 64 +1052 } +1053 Err(_e) => { +1054 crate::log::debug!( +1055 "failed to set `UDP_SEGMENT` socket option ({_e}); setting `max_gso_segments = 1`" +1056 ); +1057 +1058 1 +1059 } +1060 } +1061 } +1062 +1063 pub(crate) fn set_segment_size( +1064 encoder: &mut cmsg::Encoder<'_, libc::msghdr>, +1065 segment_size: u16, +1066 ) { +1067 encoder.push(libc::SOL_UDP, libc::UDP_SEGMENT, segment_size); +1068 } 1069 -1070#[cfg(not(any(target_os = "linux", target_os = "android")))] -1071mod gro { -1072 use std::num::NonZeroUsize; -1073 -1074 pub(super) fn gro_segments() -> NonZeroUsize { -1075 NonZeroUsize::MIN -1076 } -1077} \ No newline at end of file +1070 // Avoid calling `supported_by_current_kernel` for each socket by using `OnceLock`. +1071 static SUPPORTED_BY_CURRENT_KERNEL: OnceLock<bool> = OnceLock::new(); +1072 +1073 fn supported_by_current_kernel() -> bool { +1074 let kernel_version_string = match kernel_version_string() { +1075 Ok(kernel_version_string) => kernel_version_string, +1076 Err(_e) => { +1077 crate::log::warn!("GSO disabled: uname returned {_e}"); +1078 return false; +1079 } +1080 }; +1081 +1082 let Some(kernel_version) = KernelVersion::from_str(&kernel_version_string) else { +1083 crate::log::warn!( +1084 "GSO disabled: failed to parse kernel version ({kernel_version_string})" +1085 ); +1086 return false; +1087 }; +1088 +1089 if kernel_version < SUPPORTED_SINCE { +1090 crate::log::info!("GSO disabled: kernel too old ({kernel_version_string}); need 4.18+",); +1091 return false; +1092 } +1093 +1094 true +1095 } +1096 +1097 fn kernel_version_string() -> io::Result<String> { +1098 let mut n = unsafe { mem::zeroed() }; +1099 let r = unsafe { libc::uname(&mut n) }; +1100 if r != 0 { +1101 return Err(io::Error::last_os_error()); +1102 } +1103 Ok(unsafe { +1104 CStr::from_ptr(n.release[..].as_ptr()) +1105 .to_string_lossy() +1106 .into_owned() +1107 }) +1108 } +1109 +1110 // https://www.linfo.org/kernel_version_numbering.html +1111 #[derive(Eq, PartialEq, Ord, PartialOrd, Debug)] +1112 struct KernelVersion { +1113 version: u8, +1114 major_revision: u8, +1115 } +1116 +1117 impl KernelVersion { +1118 fn from_str(release: &str) -> Option<Self> { +1119 let mut split = release +1120 .split_once('-') +1121 .map(|pair| pair.0) +1122 .unwrap_or(release) +1123 .split('.'); +1124 +1125 let version = u8::from_str(split.next()?).ok()?; +1126 let major_revision = u8::from_str(split.next()?).ok()?; +1127 +1128 Some(Self { +1129 version, +1130 major_revision, +1131 }) +1132 } +1133 } +1134 +1135 #[cfg(test)] +1136 mod test { +1137 use super::*; +1138 +1139 #[test] +1140 fn parse_current_kernel_version_release_string() { +1141 let release = kernel_version_string().unwrap(); +1142 KernelVersion::from_str(&release).unwrap(); +1143 } +1144 +1145 #[test] +1146 fn parse_kernel_version_release_string() { +1147 // These are made up for the test +1148 assert_eq!( +1149 KernelVersion::from_str("4.14"), +1150 Some(KernelVersion { +1151 version: 4, +1152 major_revision: 14 +1153 }) +1154 ); +1155 assert_eq!( +1156 KernelVersion::from_str("4.18"), +1157 Some(KernelVersion { +1158 version: 4, +1159 major_revision: 18 +1160 }) +1161 ); +1162 // These were seen in the wild +1163 assert_eq!( +1164 KernelVersion::from_str("4.14.186-27095505"), +1165 Some(KernelVersion { +1166 version: 4, +1167 major_revision: 14 +1168 }) +1169 ); +1170 assert_eq!( +1171 KernelVersion::from_str("6.8.0-59-generic"), +1172 Some(KernelVersion { +1173 version: 6, +1174 major_revision: 8 +1175 }) +1176 ); +1177 } +1178 } +1179} +1180 +1181// On Apple platforms using the `sendmsg_x` call, UDP datagram segmentation is not +1182// offloaded to the NIC or even the kernel, but instead done here in user space in +1183// [`send`]) and then passed to the OS as individual `iovec`s (up to `BATCH_SIZE`). +1184// The initial value is 1 (no batching); callers can enable batching via +1185// `UdpSocketState::set_apple_fast_path()` which updates `max_gso_segments`. +1186#[cfg(not(any(target_os = "linux", target_os = "android")))] +1187mod gso { +1188 use super::*; +1189 +1190 pub(super) fn max_gso_segments(_socket: &impl AsRawFd) -> usize { +1191 1 +1192 } +1193 +1194 #[cfg_attr(apple_fast, allow(dead_code))] // Unused when apple_fast is enabled +1195 pub(super) fn set_segment_size( +1196 #[cfg(not(apple_fast))] _encoder: &mut cmsg::Encoder<'_, libc::msghdr>, +1197 #[cfg(apple_fast)] _encoder: &mut cmsg::Encoder<'_, msghdr_x>, +1198 _segment_size: u16, +1199 ) { +1200 } +1201} +1202 +1203/// Returns whether the given socket option is supported on the current platform +1204/// +1205/// Yields `Ok(true)` if the option was set successfully, `Ok(false)` if setting +1206/// the option raised an `ENOPROTOOPT` or `EOPNOTSUPP` error, and `Err` for any other error. +1207fn set_socket_option_supported( +1208 socket: &impl AsRawFd, +1209 level: libc::c_int, +1210 name: libc::c_int, +1211 value: libc::c_int, +1212) -> io::Result<bool> { +1213 match set_socket_option(socket, level, name, value) { +1214 Ok(()) => Ok(true), +1215 Err(err) if err.raw_os_error() == Some(libc::ENOPROTOOPT) => Ok(false), +1216 Err(err) if err.raw_os_error() == Some(libc::EOPNOTSUPP) => Ok(false), +1217 Err(err) => Err(err), +1218 } +1219} +1220 +1221fn set_socket_option( +1222 socket: &impl AsRawFd, +1223 level: libc::c_int, +1224 name: libc::c_int, +1225 value: libc::c_int, +1226) -> io::Result<()> { +1227 let rc = unsafe { +1228 libc::setsockopt( +1229 socket.as_raw_fd(), +1230 level, +1231 name, +1232 &value as *const _ as _, +1233 mem::size_of_val(&value) as _, +1234 ) +1235 }; +1236 +1237 match rc == 0 { +1238 true => Ok(()), +1239 false => Err(io::Error::last_os_error()), +1240 } +1241} +1242 +1243const OPTION_ON: libc::c_int = 1; +1244 +1245/// Calls `f` in a loop, retrying on `EINTR`, and returns the non-negative result or the first +1246/// non-`EINTR` error. +1247fn retry_if_interrupted(mut f: impl FnMut() -> isize) -> io::Result<isize> { +1248 loop { +1249 let n = f(); +1250 if n >= 0 { +1251 return Ok(n); +1252 } +1253 let e = io::Error::last_os_error(); +1254 if e.kind() != io::ErrorKind::Interrupted { +1255 return Err(e); +1256 } +1257 } +1258} \ No newline at end of file diff --git a/pr/451/docs/src/perf/client.rs.html b/pr/451/docs/src/perf/client.rs.html index 4e2d81f3c..9037e80a5 100644 --- a/pr/451/docs/src/perf/client.rs.html +++ b/pr/451/docs/src/perf/client.rs.html @@ -1,5 +1,5 @@ client.rs - source

    perf/
    client.rs

    1#[cfg(feature = "json-output")]
    -2use std::path::PathBuf;
    +2use std::path::{Path, PathBuf};
     3use std::{
     4    net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
     5    sync::Arc,
    @@ -166,254 +166,261 @@
     166    let stats_fut = async {
     167        let interval_duration = Duration::from_secs(opt.interval);
     168
    -169        loop {
    -170            let start = Instant::now();
    -171            tokio::time::sleep(interval_duration).await;
    -172            {
    -173                stats.on_interval(start, &stream_stats);
    -174
    -175                stats.print();
    -176                if opt.common.conn_stats {
    -177                    println!("{:?}\n", connection.stats());
    -178                }
    -179            }
    -180        }
    -181    };
    -182
    -183    tokio::select! {
    -184        _ = drive_fut => {}
    -185        _ = stats_fut => {}
    -186        _ = tokio::signal::ctrl_c() => {
    -187            info!("shutting down");
    -188            connection.close(0u32.into(), b"interrupted");
    -189        }
    -190        // Add a small duration so the final interval can be reported
    -191        _ = tokio::time::sleep(Duration::from_secs(opt.duration) + Duration::from_millis(200)) => {
    -192            info!("shutting down");
    -193            connection.close(0u32.into(), b"done");
    -194        }
    -195    }
    -196
    -197    endpoint.wait_idle().await;
    -198
    -199    #[cfg(feature = "json-output")]
    -200    if let Some(path) = opt.json {
    -201        stats.print_json(path.as_path())?;
    +169        #[cfg(feature = "json-output")]
    +170        let allow_table_output = opt.json.clone().is_none_or(|path| path != Path::new("-"));
    +171        #[cfg(not(feature = "json-output"))]
    +172        let allow_table_output = true;
    +173
    +174        loop {
    +175            let start = Instant::now();
    +176            tokio::time::sleep(interval_duration).await;
    +177            {
    +178                stats.on_interval(start, &stream_stats);
    +179
    +180                if allow_table_output {
    +181                    stats.print();
    +182                    if opt.common.conn_stats {
    +183                        println!("{:?}\n", connection.stats());
    +184                    }
    +185                }
    +186            }
    +187        }
    +188    };
    +189
    +190    tokio::select! {
    +191        _ = drive_fut => {}
    +192        _ = stats_fut => {}
    +193        _ = tokio::signal::ctrl_c() => {
    +194            info!("shutting down");
    +195            connection.close(0u32.into(), b"interrupted");
    +196        }
    +197        // Add a small duration so the final interval can be reported
    +198        _ = tokio::time::sleep(Duration::from_secs(opt.duration) + Duration::from_millis(200)) => {
    +199            info!("shutting down");
    +200            connection.close(0u32.into(), b"done");
    +201        }
     202    }
     203
    -204    Ok(())
    -205}
    -206
    -207async fn drain_stream(
    -208    mut stream: noq::RecvStream,
    -209    download: u64,
    -210    stream_stats: OpenStreamStats,
    -211) -> Result<()> {
    -212    if download == 0 {
    -213        return Ok(());
    -214    }
    -215
    -216    #[rustfmt::skip]
    -217    let mut bufs = [
    -218        Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(),
    -219        Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(),
    -220        Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(),
    -221        Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(),
    -222        Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(),
    -223        Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(),
    -224        Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(),
    +204    endpoint.wait_idle().await;
    +205
    +206    #[cfg(feature = "json-output")]
    +207    if let Some(path) = opt.json {
    +208        stats.print_json(path.as_path())?;
    +209    }
    +210
    +211    Ok(())
    +212}
    +213
    +214async fn drain_stream(
    +215    mut stream: noq::RecvStream,
    +216    download: u64,
    +217    stream_stats: OpenStreamStats,
    +218) -> Result<()> {
    +219    if download == 0 {
    +220        return Ok(());
    +221    }
    +222
    +223    #[rustfmt::skip]
    +224    let mut bufs = [
     225        Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(),
    -226    ];
    -227    let download_start = Instant::now();
    -228    let recv_stream_stats = stream_stats.new_receiver(&stream, download);
    -229
    -230    let mut first_byte = true;
    -231
    -232    while let Some(size) = stream.read_chunks(&mut bufs[..]).await? {
    -233        if first_byte {
    -234            recv_stream_stats.on_first_byte(download_start.elapsed());
    -235            first_byte = false;
    -236        }
    -237        let bytes_received = bufs[..size].iter().map(|b| b.len()).sum();
    -238        recv_stream_stats.on_bytes(bytes_received);
    -239    }
    -240
    -241    if first_byte {
    -242        recv_stream_stats.on_first_byte(download_start.elapsed());
    -243    }
    -244    recv_stream_stats.finish(download_start.elapsed());
    -245
    -246    debug!("response finished on {}", stream.id());
    -247    Ok(())
    -248}
    -249
    -250async fn drive_uni(
    -251    connection: noq::Connection,
    -252    stream_stats: OpenStreamStats,
    -253    concurrency: u64,
    -254    upload: u64,
    -255    download: u64,
    -256) -> Result<()> {
    -257    if concurrency == 0 {
    -258        return Ok(());
    -259    }
    -260
    -261    let sem = Arc::new(Semaphore::new(concurrency as usize));
    -262
    -263    loop {
    -264        let permit = sem.clone().acquire_owned().await.unwrap();
    -265        let send = connection.open_uni().await?;
    -266        let stream_stats = stream_stats.clone();
    +226        Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(),
    +227        Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(),
    +228        Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(),
    +229        Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(),
    +230        Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(),
    +231        Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(),
    +232        Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(),
    +233    ];
    +234    let download_start = Instant::now();
    +235    let recv_stream_stats = stream_stats.new_receiver(&stream, download);
    +236
    +237    let mut first_byte = true;
    +238
    +239    while let Some(size) = stream.read_many_chunks(&mut bufs[..]).await? {
    +240        if first_byte {
    +241            recv_stream_stats.on_first_byte(download_start.elapsed());
    +242            first_byte = false;
    +243        }
    +244        let bytes_received = bufs[..size].iter().map(|b| b.len()).sum();
    +245        recv_stream_stats.on_bytes(bytes_received);
    +246    }
    +247
    +248    if first_byte {
    +249        recv_stream_stats.on_first_byte(download_start.elapsed());
    +250    }
    +251    recv_stream_stats.finish(download_start.elapsed());
    +252
    +253    debug!("response finished on {}", stream.id());
    +254    Ok(())
    +255}
    +256
    +257async fn drive_uni(
    +258    connection: noq::Connection,
    +259    stream_stats: OpenStreamStats,
    +260    concurrency: u64,
    +261    upload: u64,
    +262    download: u64,
    +263) -> Result<()> {
    +264    if concurrency == 0 {
    +265        return Ok(());
    +266    }
     267
    -268        debug!("sending request on {}", send.id());
    -269        let connection = connection.clone();
    -270        tokio::spawn(async move {
    -271            if let Err(e) = request_uni(send, connection, upload, download, stream_stats).await {
    -272                error!("sending request failed: {:#}", e);
    -273            }
    +268    let sem = Arc::new(Semaphore::new(concurrency as usize));
    +269
    +270    loop {
    +271        let permit = sem.clone().acquire_owned().await.unwrap();
    +272        let send = connection.open_uni().await?;
    +273        let stream_stats = stream_stats.clone();
     274
    -275            drop(permit);
    -276        });
    -277    }
    -278}
    -279
    -280async fn request_uni(
    -281    send: noq::SendStream,
    -282    conn: noq::Connection,
    -283    upload: u64,
    -284    download: u64,
    -285    stream_stats: OpenStreamStats,
    -286) -> Result<()> {
    -287    request(send, upload, download, stream_stats.clone()).await?;
    -288    let recv = conn.accept_uni().await?;
    -289    drain_stream(recv, download, stream_stats).await?;
    -290    Ok(())
    -291}
    -292
    -293async fn request(
    -294    mut send: noq::SendStream,
    -295    mut upload: u64,
    -296    download: u64,
    -297    stream_stats: OpenStreamStats,
    -298) -> Result<()> {
    -299    let upload_start = Instant::now();
    -300    send.write_all(&download.to_be_bytes()).await?;
    -301    if upload == 0 {
    -302        send.finish().unwrap();
    -303        return Ok(());
    -304    }
    -305
    -306    let send_stream_stats = stream_stats.new_sender(&send, upload);
    -307
    -308    static DATA: [u8; 1024 * 1024] = [42; 1024 * 1024];
    -309    while upload > 0 {
    -310        let chunk_len = upload.min(DATA.len() as u64);
    -311        send.write_chunk(Bytes::from_static(&DATA[..chunk_len as usize]))
    -312            .await
    -313            .context("sending response")?;
    -314        send_stream_stats.on_bytes(chunk_len as usize);
    -315        upload -= chunk_len;
    -316    }
    -317    send.finish().unwrap();
    -318    // Wait for stream to close
    -319    _ = send.stopped().await;
    -320    send_stream_stats.finish(upload_start.elapsed());
    -321
    -322    debug!("upload finished on {}", send.id());
    -323    Ok(())
    -324}
    -325
    -326async fn drive_bi(
    -327    connection: noq::Connection,
    -328    stream_stats: OpenStreamStats,
    -329    concurrency: u64,
    -330    upload: u64,
    -331    download: u64,
    -332) -> Result<()> {
    -333    if concurrency == 0 {
    -334        return Ok(());
    -335    }
    -336
    -337    let sem = Arc::new(Semaphore::new(concurrency as usize));
    -338
    -339    loop {
    -340        let permit = sem.clone().acquire_owned().await.unwrap();
    -341        let (send, recv) = connection.open_bi().await?;
    -342        let stream_stats = stream_stats.clone();
    +275        debug!("sending request on {}", send.id());
    +276        let connection = connection.clone();
    +277        tokio::spawn(async move {
    +278            if let Err(e) = request_uni(send, connection, upload, download, stream_stats).await {
    +279                error!("sending request failed: {:#}", e);
    +280            }
    +281
    +282            drop(permit);
    +283        });
    +284    }
    +285}
    +286
    +287async fn request_uni(
    +288    send: noq::SendStream,
    +289    conn: noq::Connection,
    +290    upload: u64,
    +291    download: u64,
    +292    stream_stats: OpenStreamStats,
    +293) -> Result<()> {
    +294    request(send, upload, download, stream_stats.clone()).await?;
    +295    let recv = conn.accept_uni().await?;
    +296    drain_stream(recv, download, stream_stats).await?;
    +297    Ok(())
    +298}
    +299
    +300async fn request(
    +301    mut send: noq::SendStream,
    +302    mut upload: u64,
    +303    download: u64,
    +304    stream_stats: OpenStreamStats,
    +305) -> Result<()> {
    +306    let upload_start = Instant::now();
    +307    send.write_all(&download.to_be_bytes()).await?;
    +308    if upload == 0 {
    +309        send.finish().unwrap();
    +310        return Ok(());
    +311    }
    +312
    +313    let send_stream_stats = stream_stats.new_sender(&send, upload);
    +314
    +315    static DATA: [u8; 1024 * 1024] = [42; 1024 * 1024];
    +316    while upload > 0 {
    +317        let chunk_len = upload.min(DATA.len() as u64);
    +318        send.write_chunk(Bytes::from_static(&DATA[..chunk_len as usize]))
    +319            .await
    +320            .context("sending response")?;
    +321        send_stream_stats.on_bytes(chunk_len as usize);
    +322        upload -= chunk_len;
    +323    }
    +324    send.finish().unwrap();
    +325    // Wait for stream to close
    +326    _ = send.stopped().await;
    +327    send_stream_stats.finish(upload_start.elapsed());
    +328
    +329    debug!("upload finished on {}", send.id());
    +330    Ok(())
    +331}
    +332
    +333async fn drive_bi(
    +334    connection: noq::Connection,
    +335    stream_stats: OpenStreamStats,
    +336    concurrency: u64,
    +337    upload: u64,
    +338    download: u64,
    +339) -> Result<()> {
    +340    if concurrency == 0 {
    +341        return Ok(());
    +342    }
     343
    -344        debug!("sending request on {}", send.id());
    -345        tokio::spawn(async move {
    -346            if let Err(e) = request_bi(send, recv, upload, download, stream_stats).await {
    -347                error!("request failed: {:#}", e);
    -348            }
    -349
    -350            drop(permit);
    -351        });
    -352    }
    -353}
    -354
    -355async fn request_bi(
    -356    send: noq::SendStream,
    -357    recv: noq::RecvStream,
    -358    upload: u64,
    -359    download: u64,
    -360    stream_stats: OpenStreamStats,
    -361) -> Result<()> {
    -362    request(send, upload, download, stream_stats.clone()).await?;
    -363    drain_stream(recv, download, stream_stats).await?;
    -364    Ok(())
    -365}
    -366
    -367#[derive(Debug)]
    -368struct SkipServerVerification(Arc<rustls::crypto::CryptoProvider>);
    -369
    -370impl SkipServerVerification {
    -371    fn new(provider: Arc<rustls::crypto::CryptoProvider>) -> Arc<Self> {
    -372        Arc::new(Self(provider))
    -373    }
    -374}
    -375
    -376impl rustls::client::danger::ServerCertVerifier for SkipServerVerification {
    -377    fn verify_server_cert(
    -378        &self,
    -379        _end_entity: &CertificateDer<'_>,
    -380        _intermediates: &[CertificateDer<'_>],
    -381        _server_name: &ServerName<'_>,
    -382        _ocsp: &[u8],
    -383        _now: UnixTime,
    -384    ) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
    -385        Ok(rustls::client::danger::ServerCertVerified::assertion())
    -386    }
    -387
    -388    fn verify_tls12_signature(
    -389        &self,
    -390        message: &[u8],
    -391        cert: &CertificateDer<'_>,
    -392        dss: &rustls::DigitallySignedStruct,
    -393    ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
    -394        rustls::crypto::verify_tls12_signature(
    -395            message,
    -396            cert,
    -397            dss,
    -398            &self.0.signature_verification_algorithms,
    -399        )
    -400    }
    -401
    -402    fn verify_tls13_signature(
    -403        &self,
    -404        message: &[u8],
    -405        cert: &CertificateDer<'_>,
    -406        dss: &rustls::DigitallySignedStruct,
    -407    ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
    -408        rustls::crypto::verify_tls13_signature(
    -409            message,
    -410            cert,
    -411            dss,
    -412            &self.0.signature_verification_algorithms,
    -413        )
    -414    }
    -415
    -416    fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
    -417        self.0.signature_verification_algorithms.supported_schemes()
    -418    }
    -419}
    \ No newline at end of file +344 let sem = Arc::new(Semaphore::new(concurrency as usize)); +345 +346 loop { +347 let permit = sem.clone().acquire_owned().await.unwrap(); +348 let (send, recv) = connection.open_bi().await?; +349 let stream_stats = stream_stats.clone(); +350 +351 debug!("sending request on {}", send.id()); +352 tokio::spawn(async move { +353 if let Err(e) = request_bi(send, recv, upload, download, stream_stats).await { +354 error!("request failed: {:#}", e); +355 } +356 +357 drop(permit); +358 }); +359 } +360} +361 +362async fn request_bi( +363 send: noq::SendStream, +364 recv: noq::RecvStream, +365 upload: u64, +366 download: u64, +367 stream_stats: OpenStreamStats, +368) -> Result<()> { +369 request(send, upload, download, stream_stats.clone()).await?; +370 drain_stream(recv, download, stream_stats).await?; +371 Ok(()) +372} +373 +374#[derive(Debug)] +375struct SkipServerVerification(Arc<rustls::crypto::CryptoProvider>); +376 +377impl SkipServerVerification { +378 fn new(provider: Arc<rustls::crypto::CryptoProvider>) -> Arc<Self> { +379 Arc::new(Self(provider)) +380 } +381} +382 +383impl rustls::client::danger::ServerCertVerifier for SkipServerVerification { +384 fn verify_server_cert( +385 &self, +386 _end_entity: &CertificateDer<'_>, +387 _intermediates: &[CertificateDer<'_>], +388 _server_name: &ServerName<'_>, +389 _ocsp: &[u8], +390 _now: UnixTime, +391 ) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> { +392 Ok(rustls::client::danger::ServerCertVerified::assertion()) +393 } +394 +395 fn verify_tls12_signature( +396 &self, +397 message: &[u8], +398 cert: &CertificateDer<'_>, +399 dss: &rustls::DigitallySignedStruct, +400 ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> { +401 rustls::crypto::verify_tls12_signature( +402 message, +403 cert, +404 dss, +405 &self.0.signature_verification_algorithms, +406 ) +407 } +408 +409 fn verify_tls13_signature( +410 &self, +411 message: &[u8], +412 cert: &CertificateDer<'_>, +413 dss: &rustls::DigitallySignedStruct, +414 ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> { +415 rustls::crypto::verify_tls13_signature( +416 message, +417 cert, +418 dss, +419 &self.0.signature_verification_algorithms, +420 ) +421 } +422 +423 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> { +424 self.0.signature_verification_algorithms.supported_schemes() +425 } +426} \ No newline at end of file diff --git a/pr/451/docs/src/perf/lib.rs.html b/pr/451/docs/src/perf/lib.rs.html index 4694f9e5a..41cb70712 100644 --- a/pr/451/docs/src/perf/lib.rs.html +++ b/pr/451/docs/src/perf/lib.rs.html @@ -198,7 +198,7 @@ 198#[derive(Clone, Copy, ValueEnum)] 199pub enum CongestionAlgorithm { 200 Cubic, -201 Bbr, +201 Bbr3, 202 NewReno, 203} 204 @@ -206,7 +206,7 @@ 206 pub fn build(self) -> Arc<dyn ControllerFactory + Send + Sync + 'static> { 207 match self { 208 Self::Cubic => Arc::new(congestion::CubicConfig::default()), -209 Self::Bbr => Arc::new(congestion::BbrConfig::default()), +209 Self::Bbr3 => Arc::new(congestion::Bbr3Config::default()), 210 Self::NewReno => Arc::new(congestion::NewRenoConfig::default()), 211 } 212 } diff --git a/pr/451/docs/src/perf/server.rs.html b/pr/451/docs/src/perf/server.rs.html index ef6348b3f..be272ccb4 100644 --- a/pr/451/docs/src/perf/server.rs.html +++ b/pr/451/docs/src/perf/server.rs.html @@ -101,102 +101,109 @@ 101async fn handle(handshake: noq::Incoming, opt: Arc<Opt>) -> Result<()> { 102 let connection = handshake.await.context("handshake failed")?; 103 -104 debug!("{} connected", connection.remote_address()); -105 tokio::try_join!( -106 drive_uni(connection.clone()), -107 drive_bi(connection.clone()), -108 conn_stats(connection, opt) -109 )?; -110 Ok(()) -111} -112 -113async fn conn_stats(connection: noq::Connection, opt: Arc<Opt>) -> Result<()> { -114 if opt.common.conn_stats { -115 loop { -116 tokio::time::sleep(Duration::from_secs(2)).await; -117 println!("{:?}\n", connection.stats()); -118 } -119 } -120 -121 Ok(()) -122} -123 -124async fn drive_uni(connection: noq::Connection) -> Result<()> { -125 while let Ok(stream) = connection.accept_uni().await { -126 let connection = connection.clone(); -127 tokio::spawn(async move { -128 if let Err(e) = handle_uni(connection, stream).await { -129 error!("request failed: {:#}", e); -130 } -131 }); -132 } -133 Ok(()) -134} -135 -136async fn handle_uni(connection: noq::Connection, stream: noq::RecvStream) -> Result<()> { -137 let bytes = read_req(stream).await?; -138 let response = connection.open_uni().await?; -139 respond(bytes, response).await?; +104 debug!( +105 "{} connected", +106 connection +107 .path(noq::PathId::ZERO) +108 .expect("path open after connect") +109 .remote_address() +110 .expect("path is alive") +111 ); +112 tokio::try_join!( +113 drive_uni(connection.clone()), +114 drive_bi(connection.clone()), +115 conn_stats(connection, opt) +116 )?; +117 Ok(()) +118} +119 +120async fn conn_stats(connection: noq::Connection, opt: Arc<Opt>) -> Result<()> { +121 if opt.common.conn_stats { +122 loop { +123 tokio::time::sleep(Duration::from_secs(2)).await; +124 println!("{:?}\n", connection.stats()); +125 } +126 } +127 +128 Ok(()) +129} +130 +131async fn drive_uni(connection: noq::Connection) -> Result<()> { +132 while let Ok(stream) = connection.accept_uni().await { +133 let connection = connection.clone(); +134 tokio::spawn(async move { +135 if let Err(e) = handle_uni(connection, stream).await { +136 error!("request failed: {:#}", e); +137 } +138 }); +139 } 140 Ok(()) 141} 142 -143async fn drive_bi(connection: noq::Connection) -> Result<()> { -144 while let Ok((send, recv)) = connection.accept_bi().await { -145 tokio::spawn(async move { -146 if let Err(e) = handle_bi(send, recv).await { -147 error!("request failed: {:#}", e); -148 } -149 }); -150 } -151 Ok(()) -152} -153 -154async fn handle_bi(send: noq::SendStream, recv: noq::RecvStream) -> Result<()> { -155 let bytes = read_req(recv).await?; -156 respond(bytes, send).await?; -157 Ok(()) -158} -159 -160async fn read_req(mut stream: noq::RecvStream) -> Result<u64> { -161 let mut buf = [0; 8]; -162 stream -163 .read_exact(&mut buf) -164 .await -165 .context("reading request")?; -166 let n = u64::from_be_bytes(buf); -167 debug!("got req for {} bytes on {}", n, stream.id()); -168 drain_stream(stream).await?; -169 Ok(n) -170} -171 -172async fn drain_stream(mut stream: noq::RecvStream) -> Result<()> { -173 #[rustfmt::skip] -174 let mut bufs = [ -175 Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(), -176 Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(), -177 Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(), -178 Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(), -179 Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(), -180 Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(), -181 Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(), +143async fn handle_uni(connection: noq::Connection, stream: noq::RecvStream) -> Result<()> { +144 let bytes = read_req(stream).await?; +145 let response = connection.open_uni().await?; +146 respond(bytes, response).await?; +147 Ok(()) +148} +149 +150async fn drive_bi(connection: noq::Connection) -> Result<()> { +151 while let Ok((send, recv)) = connection.accept_bi().await { +152 tokio::spawn(async move { +153 if let Err(e) = handle_bi(send, recv).await { +154 error!("request failed: {:#}", e); +155 } +156 }); +157 } +158 Ok(()) +159} +160 +161async fn handle_bi(send: noq::SendStream, recv: noq::RecvStream) -> Result<()> { +162 let bytes = read_req(recv).await?; +163 respond(bytes, send).await?; +164 Ok(()) +165} +166 +167async fn read_req(mut stream: noq::RecvStream) -> Result<u64> { +168 let mut buf = [0; 8]; +169 stream +170 .read_exact(&mut buf) +171 .await +172 .context("reading request")?; +173 let n = u64::from_be_bytes(buf); +174 debug!("got req for {} bytes on {}", n, stream.id()); +175 drain_stream(stream).await?; +176 Ok(n) +177} +178 +179async fn drain_stream(mut stream: noq::RecvStream) -> Result<()> { +180 #[rustfmt::skip] +181 let mut bufs = [ 182 Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(), -183 ]; -184 while stream.read_chunks(&mut bufs[..]).await?.is_some() {} -185 debug!("finished reading {}", stream.id()); -186 Ok(()) -187} -188 -189async fn respond(mut bytes: u64, mut stream: noq::SendStream) -> Result<()> { -190 static DATA: [u8; 1024 * 1024] = [42; 1024 * 1024]; -191 -192 while bytes > 0 { -193 let chunk_len = bytes.min(DATA.len() as u64); -194 stream -195 .write_chunk(Bytes::from_static(&DATA[..chunk_len as usize])) -196 .await -197 .context("sending response")?; -198 bytes -= chunk_len; -199 } -200 debug!("finished responding on {}", stream.id()); -201 Ok(()) -202} \ No newline at end of file +183 Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(), +184 Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(), +185 Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(), +186 Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(), +187 Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(), +188 Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(), +189 Bytes::new(), Bytes::new(), Bytes::new(), Bytes::new(), +190 ]; +191 while stream.read_many_chunks(&mut bufs[..]).await?.is_some() {} +192 debug!("finished reading {}", stream.id()); +193 Ok(()) +194} +195 +196async fn respond(mut bytes: u64, mut stream: noq::SendStream) -> Result<()> { +197 static DATA: [u8; 1024 * 1024] = [42; 1024 * 1024]; +198 +199 while bytes > 0 { +200 let chunk_len = bytes.min(DATA.len() as u64); +201 stream +202 .write_chunk(Bytes::from_static(&DATA[..chunk_len as usize])) +203 .await +204 .context("sending response")?; +205 bytes -= chunk_len; +206 } +207 debug!("finished responding on {}", stream.id()); +208 Ok(()) +209} \ No newline at end of file diff --git a/pr/451/docs/src/perf/stats.rs.html b/pr/451/docs/src/perf/stats.rs.html index 9b65dfe3c..4dbddb4ce 100644 --- a/pr/451/docs/src/perf/stats.rs.html +++ b/pr/451/docs/src/perf/stats.rs.html @@ -120,294 +120,293 @@ 120 121 #[cfg(feature = "json-output")] 122 pub fn print_json(&self, path: &Path) -> io::Result<()> { -123 match path { -124 path if path == Path::new("-") => json::print(self, std::io::stdout()), -125 _ => { -126 let file = File::create(path)?; -127 json::print(self, file) -128 } -129 } -130 Ok(()) -131 } -132} -133 -134/// Statistics for the currently open streams -135#[derive(Clone, Default)] -136pub struct OpenStreamStats(Arc<Mutex<Vec<Arc<StreamStats>>>>); -137 -138impl OpenStreamStats { -139 pub fn new_sender(&self, stream: &noq::SendStream, upload_size: u64) -> Arc<StreamStats> { -140 let send_stream_stats = StreamStats { -141 id: stream.id(), -142 request_size: upload_size, -143 bytes: Default::default(), -144 sender: true, -145 finished: Default::default(), -146 duration: Default::default(), -147 first_byte_latency: Default::default(), -148 }; -149 let send_stream_stats = Arc::new(send_stream_stats); -150 self.push(send_stream_stats.clone()); -151 send_stream_stats -152 } -153 -154 pub fn new_receiver(&self, stream: &noq::RecvStream, download_size: u64) -> Arc<StreamStats> { -155 let recv_stream_stats = StreamStats { -156 id: stream.id(), -157 request_size: download_size, -158 bytes: Default::default(), -159 sender: false, -160 finished: Default::default(), -161 duration: Default::default(), -162 first_byte_latency: Default::default(), -163 }; -164 let recv_stream_stats = Arc::new(recv_stream_stats); -165 self.push(recv_stream_stats.clone()); -166 recv_stream_stats -167 } -168 -169 fn push(&self, stream_stats: Arc<StreamStats>) { -170 self.0.lock().unwrap().push(stream_stats); -171 } -172} -173 -174pub struct StreamStats { -175 id: StreamId, -176 request_size: u64, -177 bytes: AtomicUsize, -178 sender: bool, -179 finished: AtomicBool, -180 duration: AtomicU64, -181 first_byte_latency: AtomicU64, -182} -183 -184impl StreamStats { -185 pub fn on_first_byte(&self, latency: Duration) { -186 self.first_byte_latency -187 .store(latency.as_micros() as u64, Ordering::SeqCst); -188 } -189 -190 pub fn on_bytes(&self, bytes: usize) { -191 self.bytes.fetch_add(bytes, Ordering::SeqCst); -192 } -193 -194 pub fn finish(&self, duration: Duration) { -195 self.duration -196 .store(duration.as_micros() as u64, Ordering::SeqCst); -197 self.finished.store(true, Ordering::SeqCst); -198 } -199} -200 -201struct Interval { -202 streams: Vec<StreamIntervalStats>, -203 period: IntervalPeriod, -204} -205 -206impl Interval { -207 fn new(start: Duration, end: Duration) -> Self { -208 let period = IntervalPeriod { -209 start: start.as_secs_f64(), -210 end: end.as_secs_f64(), -211 seconds: (end - start).as_secs_f64(), -212 }; -213 -214 Self { -215 streams: vec![], -216 period, -217 } -218 } -219 -220 fn record_stream_stats(&mut self, stream_stats: Arc<StreamStats>) { -221 let bytes = stream_stats.bytes.swap(0, Ordering::SeqCst); -222 self.streams.push(StreamIntervalStats { -223 id: stream_stats.id, -224 bytes, -225 sender: stream_stats.sender, -226 }) -227 } -228} -229 -230struct IntervalPeriod { -231 start: f64, -232 end: f64, -233 seconds: f64, -234} -235 -236struct StreamIntervalStats { -237 id: StreamId, -238 bytes: usize, -239 sender: bool, -240} -241 -242fn throughput_bytes_per_second(duration_in_micros: u64, size: u64) -> f64 { -243 (size as f64) / (duration_in_micros as f64 / 1000000.0) -244} -245 -246#[cfg(feature = "json-output")] -247mod json { -248 use crate::stats; -249 use crate::stats::{Stats, StreamIntervalStats}; -250 use noq::StreamId; -251 use serde::{self, Serialize, Serializer, ser::SerializeStruct}; -252 use std::io::Write; -253 use std::time::{SystemTime, UNIX_EPOCH}; -254 -255 pub(crate) fn print<W: Write>(stats: &Stats, out: W) { -256 let report = Report { -257 start: Start { -258 timestamp: stats.start, -259 }, -260 intervals: &stats -261 .intervals -262 .iter() -263 .map(Interval::from_stats_interval) -264 .collect(), -265 }; -266 -267 serde_json::to_writer(out, &report).unwrap(); -268 } -269 -270 #[derive(Serialize)] -271 struct Report<'a> { -272 start: Start, -273 intervals: &'a Vec<Interval>, -274 // TODO: add end stats -275 } -276 -277 #[derive(Serialize)] -278 struct Start { -279 #[serde(serialize_with = "serialize_timestamp")] -280 timestamp: SystemTime, -281 } -282 -283 fn serialize_timestamp<S>(time: &SystemTime, s: S) -> Result<S::Ok, S::Error> -284 where -285 S: serde::Serializer, -286 { -287 use serde::ser::SerializeMap; -288 let mut state = s.serialize_map(Some(1))?; -289 state.serialize_entry( -290 "timesecs", -291 &time.duration_since(UNIX_EPOCH).unwrap().as_secs(), -292 )?; -293 state.end() -294 } -295 -296 struct Interval { -297 streams: Vec<Stream>, -298 recv_sum: Sum, -299 send_sum: Sum, -300 } -301 -302 impl Interval { -303 fn from_stats_interval(interval: &stats::Interval) -> Self { -304 Self { -305 streams: interval -306 .streams -307 .iter() -308 .map(|stats| Stream::from_stream_interval_stats(stats, &interval.period)) -309 .collect(), -310 recv_sum: Sum::from_stream_interval_stats( -311 &interval.streams, -312 &interval.period, -313 false, -314 ), -315 send_sum: Sum::from_stream_interval_stats( -316 &interval.streams, -317 &interval.period, -318 true, -319 ), -320 } -321 } -322 } -323 -324 impl Serialize for Interval { -325 fn serialize<S>( -326 &self, -327 serializer: S, -328 ) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error> -329 where -330 S: Serializer, -331 { -332 let mut state = serializer.serialize_struct("Interval", 2)?; -333 state.serialize_field("streams", &self.streams)?; -334 // iperf3 outputs duplicate "sum" entries when run in bidirectional mode -335 // serde does not support duplicate keys, so only output one of the sums -336 if self.send_sum.bytes > 0 { -337 state.serialize_field("sum", &self.send_sum)?; -338 } else { -339 state.serialize_field("sum", &self.recv_sum)?; -340 } -341 state.end() -342 } -343 } -344 -345 #[derive(Serialize)] -346 struct Stream { -347 #[serde(serialize_with = "serialize_stream_id")] -348 id: StreamId, -349 start: f64, -350 end: f64, -351 seconds: f64, -352 bytes: usize, -353 bits_per_second: f64, -354 sender: bool, -355 } -356 -357 impl Stream { -358 fn from_stream_interval_stats( -359 stats: &stats::StreamIntervalStats, -360 period: &stats::IntervalPeriod, -361 ) -> Self { -362 let bits_per_second = stats.bytes as f64 * 8.0 / period.seconds; -363 -364 Self { -365 id: stats.id, -366 start: period.start, -367 end: period.end, -368 seconds: period.seconds, -369 bytes: stats.bytes, -370 bits_per_second, -371 sender: stats.sender, -372 } -373 } -374 } -375 -376 fn serialize_stream_id<S: Serializer>(id: &StreamId, serializer: S) -> Result<S::Ok, S::Error> { -377 serializer.serialize_u64(u64::from(*id)) -378 } -379 -380 #[derive(Serialize)] -381 struct Sum { -382 start: f64, -383 end: f64, -384 seconds: f64, -385 bytes: usize, -386 bits_per_second: f64, -387 sender: bool, -388 } -389 -390 impl Sum { -391 fn from_stream_interval_stats( -392 stats: &[StreamIntervalStats], -393 period: &stats::IntervalPeriod, -394 sender: bool, -395 ) -> Self { -396 let bytes = stats -397 .iter() -398 .filter(|stat| stat.sender == sender) -399 .map(|stat| stat.bytes) -400 .sum(); -401 let bits_per_second = bytes as f64 * 8.0 / period.seconds; -402 -403 Self { -404 start: period.start, -405 end: period.end, -406 seconds: period.seconds, -407 bytes, -408 bits_per_second, -409 sender, -410 } -411 } -412 } -413} \ No newline at end of file +123 if path == Path::new("-") { +124 json::print(self, std::io::stdout()); +125 } else { +126 let file = File::create(path)?; +127 json::print(self, file) +128 } +129 Ok(()) +130 } +131} +132 +133/// Statistics for the currently open streams +134#[derive(Clone, Default)] +135pub struct OpenStreamStats(Arc<Mutex<Vec<Arc<StreamStats>>>>); +136 +137impl OpenStreamStats { +138 pub fn new_sender(&self, stream: &noq::SendStream, upload_size: u64) -> Arc<StreamStats> { +139 let send_stream_stats = StreamStats { +140 id: stream.id(), +141 request_size: upload_size, +142 bytes: Default::default(), +143 sender: true, +144 finished: Default::default(), +145 duration: Default::default(), +146 first_byte_latency: Default::default(), +147 }; +148 let send_stream_stats = Arc::new(send_stream_stats); +149 self.push(send_stream_stats.clone()); +150 send_stream_stats +151 } +152 +153 pub fn new_receiver(&self, stream: &noq::RecvStream, download_size: u64) -> Arc<StreamStats> { +154 let recv_stream_stats = StreamStats { +155 id: stream.id(), +156 request_size: download_size, +157 bytes: Default::default(), +158 sender: false, +159 finished: Default::default(), +160 duration: Default::default(), +161 first_byte_latency: Default::default(), +162 }; +163 let recv_stream_stats = Arc::new(recv_stream_stats); +164 self.push(recv_stream_stats.clone()); +165 recv_stream_stats +166 } +167 +168 fn push(&self, stream_stats: Arc<StreamStats>) { +169 self.0.lock().unwrap().push(stream_stats); +170 } +171} +172 +173pub struct StreamStats { +174 id: StreamId, +175 request_size: u64, +176 bytes: AtomicUsize, +177 sender: bool, +178 finished: AtomicBool, +179 duration: AtomicU64, +180 first_byte_latency: AtomicU64, +181} +182 +183impl StreamStats { +184 pub fn on_first_byte(&self, latency: Duration) { +185 self.first_byte_latency +186 .store(latency.as_micros() as u64, Ordering::SeqCst); +187 } +188 +189 pub fn on_bytes(&self, bytes: usize) { +190 self.bytes.fetch_add(bytes, Ordering::SeqCst); +191 } +192 +193 pub fn finish(&self, duration: Duration) { +194 self.duration +195 .store(duration.as_micros() as u64, Ordering::SeqCst); +196 self.finished.store(true, Ordering::SeqCst); +197 } +198} +199 +200struct Interval { +201 streams: Vec<StreamIntervalStats>, +202 period: IntervalPeriod, +203} +204 +205impl Interval { +206 fn new(start: Duration, end: Duration) -> Self { +207 let period = IntervalPeriod { +208 start: start.as_secs_f64(), +209 end: end.as_secs_f64(), +210 seconds: (end - start).as_secs_f64(), +211 }; +212 +213 Self { +214 streams: vec![], +215 period, +216 } +217 } +218 +219 fn record_stream_stats(&mut self, stream_stats: Arc<StreamStats>) { +220 let bytes = stream_stats.bytes.swap(0, Ordering::SeqCst); +221 self.streams.push(StreamIntervalStats { +222 id: stream_stats.id, +223 bytes, +224 sender: stream_stats.sender, +225 }) +226 } +227} +228 +229struct IntervalPeriod { +230 start: f64, +231 end: f64, +232 seconds: f64, +233} +234 +235struct StreamIntervalStats { +236 id: StreamId, +237 bytes: usize, +238 sender: bool, +239} +240 +241fn throughput_bytes_per_second(duration_in_micros: u64, size: u64) -> f64 { +242 (size as f64) / (duration_in_micros as f64 / 1000000.0) +243} +244 +245#[cfg(feature = "json-output")] +246mod json { +247 use crate::stats; +248 use crate::stats::{Stats, StreamIntervalStats}; +249 use noq::StreamId; +250 use serde::{self, Serialize, Serializer, ser::SerializeStruct}; +251 use std::io::Write; +252 use std::time::{SystemTime, UNIX_EPOCH}; +253 +254 pub(crate) fn print<W: Write>(stats: &Stats, out: W) { +255 let report = Report { +256 start: Start { +257 timestamp: stats.start, +258 }, +259 intervals: &stats +260 .intervals +261 .iter() +262 .map(Interval::from_stats_interval) +263 .collect(), +264 }; +265 +266 serde_json::to_writer(out, &report).unwrap(); +267 } +268 +269 #[derive(Serialize)] +270 struct Report<'a> { +271 start: Start, +272 intervals: &'a Vec<Interval>, +273 // TODO: add end stats +274 } +275 +276 #[derive(Serialize)] +277 struct Start { +278 #[serde(serialize_with = "serialize_timestamp")] +279 timestamp: SystemTime, +280 } +281 +282 fn serialize_timestamp<S>(time: &SystemTime, s: S) -> Result<S::Ok, S::Error> +283 where +284 S: serde::Serializer, +285 { +286 use serde::ser::SerializeMap; +287 let mut state = s.serialize_map(Some(1))?; +288 state.serialize_entry( +289 "timesecs", +290 &time.duration_since(UNIX_EPOCH).unwrap().as_secs(), +291 )?; +292 state.end() +293 } +294 +295 struct Interval { +296 streams: Vec<Stream>, +297 recv_sum: Sum, +298 send_sum: Sum, +299 } +300 +301 impl Interval { +302 fn from_stats_interval(interval: &stats::Interval) -> Self { +303 Self { +304 streams: interval +305 .streams +306 .iter() +307 .map(|stats| Stream::from_stream_interval_stats(stats, &interval.period)) +308 .collect(), +309 recv_sum: Sum::from_stream_interval_stats( +310 &interval.streams, +311 &interval.period, +312 false, +313 ), +314 send_sum: Sum::from_stream_interval_stats( +315 &interval.streams, +316 &interval.period, +317 true, +318 ), +319 } +320 } +321 } +322 +323 impl Serialize for Interval { +324 fn serialize<S>( +325 &self, +326 serializer: S, +327 ) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error> +328 where +329 S: Serializer, +330 { +331 let mut state = serializer.serialize_struct("Interval", 2)?; +332 state.serialize_field("streams", &self.streams)?; +333 // iperf3 outputs duplicate "sum" entries when run in bidirectional mode +334 // serde does not support duplicate keys, so only output one of the sums +335 if self.send_sum.bytes > 0 { +336 state.serialize_field("sum", &self.send_sum)?; +337 } else { +338 state.serialize_field("sum", &self.recv_sum)?; +339 } +340 state.end() +341 } +342 } +343 +344 #[derive(Serialize)] +345 struct Stream { +346 #[serde(serialize_with = "serialize_stream_id")] +347 id: StreamId, +348 start: f64, +349 end: f64, +350 seconds: f64, +351 bytes: usize, +352 bits_per_second: f64, +353 sender: bool, +354 } +355 +356 impl Stream { +357 fn from_stream_interval_stats( +358 stats: &stats::StreamIntervalStats, +359 period: &stats::IntervalPeriod, +360 ) -> Self { +361 let bits_per_second = stats.bytes as f64 * 8.0 / period.seconds; +362 +363 Self { +364 id: stats.id, +365 start: period.start, +366 end: period.end, +367 seconds: period.seconds, +368 bytes: stats.bytes, +369 bits_per_second, +370 sender: stats.sender, +371 } +372 } +373 } +374 +375 fn serialize_stream_id<S: Serializer>(id: &StreamId, serializer: S) -> Result<S::Ok, S::Error> { +376 serializer.serialize_u64(u64::from(*id)) +377 } +378 +379 #[derive(Serialize)] +380 struct Sum { +381 start: f64, +382 end: f64, +383 seconds: f64, +384 bytes: usize, +385 bits_per_second: f64, +386 sender: bool, +387 } +388 +389 impl Sum { +390 fn from_stream_interval_stats( +391 stats: &[StreamIntervalStats], +392 period: &stats::IntervalPeriod, +393 sender: bool, +394 ) -> Self { +395 let bytes = stats +396 .iter() +397 .filter(|stat| stat.sender == sender) +398 .map(|stat| stat.bytes) +399 .sum(); +400 let bits_per_second = bytes as f64 * 8.0 / period.seconds; +401 +402 Self { +403 start: period.start, +404 end: period.end, +405 seconds: period.seconds, +406 bytes, +407 bits_per_second, +408 sender, +409 } +410 } +411 } +412} \ No newline at end of file diff --git a/pr/451/docs/trait.impl/core/clone/trait.Clone.js b/pr/451/docs/trait.impl/core/clone/trait.Clone.js index 37e224391..d04b2dbcf 100644 --- a/pr/451/docs/trait.impl/core/clone/trait.Clone.js +++ b/pr/451/docs/trait.impl/core/clone/trait.Clone.js @@ -1,9 +1,9 @@ (function() { - var implementors = Object.fromEntries([["bench",[["impl Clone for CipherSuite"],["impl Clone for RuntimeType"],["impl Clone for Opt"]]],["noq",[["impl Clone for ReadError"],["impl Clone for ReadExactError"],["impl Clone for ReadToEndError"],["impl Clone for ResetError"],["impl Clone for SendDatagramError"],["impl Clone for StoppedError"],["impl Clone for WriteError"],["impl Clone for Connection"],["impl Clone for Endpoint"],["impl Clone for EndpointStats"],["impl Clone for Lagged"],["impl Clone for Path"],["impl Clone for WeakConnectionHandle"],["impl Clone for WeakPathHandle"]]],["noq_proto",[["impl Clone for ClosePathError"],["impl Clone for ConfigError"],["impl Clone for ConnectError"],["impl Clone for ConnectionError"],["impl Clone for Dir"],["impl Clone for EcnCodepoint"],["impl Clone for FinishError"],["impl Clone for FrameType"],["impl Clone for LongType"],["impl Clone for MaybeFrame"],["impl Clone for PacketDecodeError"],["impl Clone for PathAbandonReason"],["impl Clone for PathError"],["impl Clone for PathEvent"],["impl Clone for PathStatus"],["impl Clone for ProtectedHeader"],["impl Clone for ReadError"],["impl Clone for ReadableError"],["impl Clone for SendDatagramError"],["impl Clone for SetPathStatusError"],["impl Clone for Side"],["impl Clone for WriteError"],["impl Clone for Event"],["impl Clone for Error"],["impl Clone for UnexpectedEnd"],["impl Clone for Bbr"],["impl Clone for BbrConfig"],["impl Clone for Cubic"],["impl Clone for CubicConfig"],["impl Clone for NewReno"],["impl Clone for NewRenoConfig"],["impl Clone for NoInitialCipherSuite"],["impl Clone for QuicClientConfig"],["impl Clone for QuicServerConfig"],["impl Clone for AckFrequencyConfig"],["impl Clone for ApplicationClose"],["impl Clone for ClientConfig"],["impl Clone for ClosedPath"],["impl Clone for ClosedStream"],["impl Clone for ConnectionClose"],["impl Clone for ConnectionHandle"],["impl Clone for ConnectionId"],["impl Clone for ConnectionStats"],["impl Clone for Datagram"],["impl Clone for DatagramInfo"],["impl Clone for EndpointConfig"],["impl Clone for FourTuple"],["impl Clone for FrameStats"],["impl Clone for IdleTimeout"],["impl Clone for InvalidCid"],["impl Clone for MtuDiscoveryConfig"],["impl Clone for MultipathNotNegotiated"],["impl Clone for PathId"],["impl Clone for PathStats"],["impl Clone for ProtectedInitialHeader"],["impl Clone for RandomConnectionIdGenerator"],["impl Clone for RttEstimator"],["impl Clone for ServerConfig"],["impl Clone for ShouldTransmit"],["impl Clone for StreamId"],["impl Clone for StreamInfo"],["impl Clone for TransportConfig"],["impl Clone for Error"],["impl Clone for Code"],["impl Clone for UdpStats"],["impl Clone for ValidationTokenConfig"],["impl Clone for VarInt"],["impl Clone for VarIntBoundsExceeded"],["impl Clone for Written"],["impl Clone for TransportParameters"]]],["noq_udp",[["impl Clone for EcnCodepoint"],["impl Clone for RecvMeta"],["impl<'a> Clone for Transmit<'a>"]]],["perf",[["impl Clone for CongestionAlgorithm"],["impl Clone for NoProtectionClientConfig"],["impl Clone for NoProtectionServerConfig"],["impl Clone for OpenStreamStats"]]]]); + var implementors = Object.fromEntries([["bench",[["impl Clone for CipherSuite"],["impl Clone for RuntimeType"],["impl Clone for Opt"]]],["noq",[["impl Clone for ReadError"],["impl Clone for ReadExactError"],["impl Clone for ReadToEndError"],["impl Clone for ResetError"],["impl Clone for SendDatagramError"],["impl Clone for StoppedError"],["impl Clone for WriteError"],["impl Clone for Closed"],["impl Clone for Connection"],["impl Clone for Endpoint"],["impl Clone for EndpointStats"],["impl Clone for Lagged"],["impl Clone for Path"],["impl Clone for WeakConnectionHandle"],["impl Clone for WeakPathHandle"]]],["noq_proto",[["impl Clone for ClosePathError"],["impl Clone for ConfigError"],["impl Clone for ConnectError"],["impl Clone for ConnectionError"],["impl Clone for Dir"],["impl Clone for EcnCodepoint"],["impl Clone for FinishError"],["impl Clone for FrameType"],["impl Clone for LongType"],["impl Clone for MaybeFrame"],["impl Clone for PacketDecodeError"],["impl Clone for PathAbandonReason"],["impl Clone for PathError"],["impl Clone for PathEvent"],["impl Clone for PathStatus"],["impl Clone for ProtectedHeader"],["impl Clone for ReadError"],["impl Clone for ReadableError"],["impl Clone for SendDatagramError"],["impl Clone for SetPathStatusError"],["impl Clone for Side"],["impl Clone for WriteError"],["impl Clone for Event"],["impl Clone for Error"],["impl Clone for UnexpectedEnd"],["impl Clone for Bbr3"],["impl Clone for Bbr3Config"],["impl Clone for Cubic"],["impl Clone for CubicConfig"],["impl Clone for NewReno"],["impl Clone for NewRenoConfig"],["impl Clone for NoInitialCipherSuite"],["impl Clone for QuicClientConfig"],["impl Clone for QuicServerConfig"],["impl Clone for AckFrequencyConfig"],["impl Clone for ApplicationClose"],["impl Clone for ClientConfig"],["impl Clone for ClosedPath"],["impl Clone for ClosedStream"],["impl Clone for ConnectionClose"],["impl Clone for ConnectionHandle"],["impl Clone for ConnectionId"],["impl Clone for ConnectionStats"],["impl Clone for Datagram"],["impl Clone for DatagramInfo"],["impl Clone for EndpointConfig"],["impl Clone for FourTuple"],["impl Clone for FrameStats"],["impl Clone for IdleTimeout"],["impl Clone for InvalidCid"],["impl Clone for MtuDiscoveryConfig"],["impl Clone for MultipathNotNegotiated"],["impl Clone for PathId"],["impl Clone for PathStats"],["impl Clone for ProtectedInitialHeader"],["impl Clone for RandomConnectionIdGenerator"],["impl Clone for RttEstimator"],["impl Clone for ServerConfig"],["impl Clone for ShouldTransmit"],["impl Clone for StreamId"],["impl Clone for StreamInfo"],["impl Clone for TransportConfig"],["impl Clone for Error"],["impl Clone for Code"],["impl Clone for UdpStats"],["impl Clone for ValidationTokenConfig"],["impl Clone for VarInt"],["impl Clone for VarIntBoundsExceeded"],["impl Clone for TransportParameters"]]],["noq_udp",[["impl Clone for EcnCodepoint"],["impl Clone for RecvMeta"],["impl<'a> Clone for Transmit<'a>"]]],["perf",[["impl Clone for CongestionAlgorithm"],["impl Clone for NoProtectionClientConfig"],["impl Clone for NoProtectionServerConfig"],["impl Clone for OpenStreamStats"]]]]); if (window.register_implementors) { window.register_implementors(implementors); } else { window.pending_implementors = implementors; } })() -//{"start":57,"fragment_lengths":[752,3559,19185,802,1214]} \ No newline at end of file +//{"start":57,"fragment_lengths":[752,3799,18936,802,1214]} \ No newline at end of file diff --git a/pr/451/docs/trait.impl/core/cmp/trait.Eq.js b/pr/451/docs/trait.impl/core/cmp/trait.Eq.js index b04d56555..5ba7db1b2 100644 --- a/pr/451/docs/trait.impl/core/cmp/trait.Eq.js +++ b/pr/451/docs/trait.impl/core/cmp/trait.Eq.js @@ -1,9 +1,9 @@ (function() { - var implementors = Object.fromEntries([["bench",[["impl Eq for CipherSuite"]]],["noq",[["impl Eq for ReadError"],["impl Eq for ReadExactError"],["impl Eq for ReadToEndError"],["impl Eq for ResetError"],["impl Eq for SendDatagramError"],["impl Eq for StoppedError"],["impl Eq for WriteError"],["impl Eq for Lagged"],["impl Eq for WeakPathHandle"]]],["noq_proto",[["impl Eq for ClosePathError"],["impl Eq for ConfigError"],["impl Eq for ConnectError"],["impl Eq for ConnectionError"],["impl Eq for Dir"],["impl Eq for EcnCodepoint"],["impl Eq for FinishError"],["impl Eq for FrameType"],["impl Eq for LongType"],["impl Eq for MaybeFrame"],["impl Eq for PacketDecodeError"],["impl Eq for PathAbandonReason"],["impl Eq for PathError"],["impl Eq for PathEvent"],["impl Eq for PathStatus"],["impl Eq for ReadError"],["impl Eq for ReadableError"],["impl Eq for SendDatagramError"],["impl Eq for SetPathStatusError"],["impl Eq for Side"],["impl Eq for StreamEvent"],["impl Eq for WriteError"],["impl Eq for Error"],["impl Eq for UnexpectedEnd"],["impl Eq for ExportKeyingMaterialError"],["impl Eq for ApplicationClose"],["impl Eq for Chunk"],["impl Eq for ClosedPath"],["impl Eq for ClosedStream"],["impl Eq for ConnectionClose"],["impl Eq for ConnectionHandle"],["impl Eq for ConnectionId"],["impl Eq for DatagramInfo"],["impl Eq for FourTuple"],["impl Eq for FrameStats"],["impl Eq for IdleTimeout"],["impl Eq for InvalidFrameId"],["impl Eq for PathId"],["impl Eq for PathStats"],["impl Eq for ShouldTransmit"],["impl Eq for StreamId"],["impl Eq for StreamInfo"],["impl Eq for Error"],["impl Eq for Code"],["impl Eq for UdpStats"],["impl Eq for VarInt"],["impl Eq for VarIntBoundsExceeded"],["impl Eq for Written"],["impl Eq for TransportParameters"]]],["noq_udp",[["impl Eq for EcnCodepoint"]]]]); + var implementors = Object.fromEntries([["bench",[["impl Eq for CipherSuite"]]],["noq",[["impl Eq for ReadError"],["impl Eq for ReadExactError"],["impl Eq for ReadToEndError"],["impl Eq for ResetError"],["impl Eq for SendDatagramError"],["impl Eq for StoppedError"],["impl Eq for WriteError"],["impl Eq for Lagged"],["impl Eq for WeakPathHandle"]]],["noq_proto",[["impl Eq for ClosePathError"],["impl Eq for ConfigError"],["impl Eq for ConnectError"],["impl Eq for ConnectionError"],["impl Eq for Dir"],["impl Eq for EcnCodepoint"],["impl Eq for FinishError"],["impl Eq for FrameType"],["impl Eq for LongType"],["impl Eq for MaybeFrame"],["impl Eq for PacketDecodeError"],["impl Eq for PathAbandonReason"],["impl Eq for PathError"],["impl Eq for PathEvent"],["impl Eq for PathStatus"],["impl Eq for ReadError"],["impl Eq for ReadableError"],["impl Eq for SendDatagramError"],["impl Eq for SetPathStatusError"],["impl Eq for Side"],["impl Eq for StreamEvent"],["impl Eq for WriteError"],["impl Eq for Error"],["impl Eq for UnexpectedEnd"],["impl Eq for ExportKeyingMaterialError"],["impl Eq for ApplicationClose"],["impl Eq for Chunk"],["impl Eq for ClosedPath"],["impl Eq for ClosedStream"],["impl Eq for ConnectionClose"],["impl Eq for ConnectionHandle"],["impl Eq for ConnectionId"],["impl Eq for DatagramInfo"],["impl Eq for FourTuple"],["impl Eq for FrameStats"],["impl Eq for IdleTimeout"],["impl Eq for InvalidFrameId"],["impl Eq for PathId"],["impl Eq for PathStats"],["impl Eq for ShouldTransmit"],["impl Eq for StreamId"],["impl Eq for StreamInfo"],["impl Eq for Error"],["impl Eq for Code"],["impl Eq for UdpStats"],["impl Eq for VarInt"],["impl Eq for VarIntBoundsExceeded"],["impl Eq for TransportParameters"]]],["noq_udp",[["impl Eq for EcnCodepoint"]]]]); if (window.register_implementors) { window.register_implementors(implementors); } else { window.pending_implementors = implementors; } })() -//{"start":57,"fragment_lengths":[251,2167,12519,261]} \ No newline at end of file +//{"start":57,"fragment_lengths":[251,2167,12277,261]} \ No newline at end of file diff --git a/pr/451/docs/trait.impl/core/cmp/trait.PartialEq.js b/pr/451/docs/trait.impl/core/cmp/trait.PartialEq.js index 440d06fcd..76cb60170 100644 --- a/pr/451/docs/trait.impl/core/cmp/trait.PartialEq.js +++ b/pr/451/docs/trait.impl/core/cmp/trait.PartialEq.js @@ -1,9 +1,9 @@ (function() { - var implementors = Object.fromEntries([["bench",[["impl PartialEq for CipherSuite"]]],["noq",[["impl PartialEq for ReadError"],["impl PartialEq for ReadExactError"],["impl PartialEq for ReadToEndError"],["impl PartialEq for ResetError"],["impl PartialEq for SendDatagramError"],["impl PartialEq for StoppedError"],["impl PartialEq for WriteError"],["impl PartialEq for Lagged"],["impl PartialEq for Path"],["impl PartialEq for WeakPathHandle"]]],["noq_proto",[["impl PartialEq for ClosePathError"],["impl PartialEq for ConfigError"],["impl PartialEq for ConnectError"],["impl PartialEq for ConnectionError"],["impl PartialEq for Dir"],["impl PartialEq for EcnCodepoint"],["impl PartialEq for FinishError"],["impl PartialEq for FrameType"],["impl PartialEq for LongType"],["impl PartialEq for MaybeFrame"],["impl PartialEq for PacketDecodeError"],["impl PartialEq for PathAbandonReason"],["impl PartialEq for PathError"],["impl PartialEq for PathEvent"],["impl PartialEq for PathStatus"],["impl PartialEq for ReadError"],["impl PartialEq for ReadableError"],["impl PartialEq for SendDatagramError"],["impl PartialEq for SetPathStatusError"],["impl PartialEq for Side"],["impl PartialEq for StreamEvent"],["impl PartialEq for WriteError"],["impl PartialEq for Error"],["impl PartialEq for UnexpectedEnd"],["impl PartialEq for ExportKeyingMaterialError"],["impl PartialEq for ApplicationClose"],["impl PartialEq for Chunk"],["impl PartialEq for ClosedPath"],["impl PartialEq for ClosedStream"],["impl PartialEq for ConnectionClose"],["impl PartialEq for ConnectionHandle"],["impl PartialEq for ConnectionId"],["impl PartialEq for DatagramInfo"],["impl PartialEq for FourTuple"],["impl PartialEq for FrameStats"],["impl PartialEq for IdleTimeout"],["impl PartialEq for InvalidFrameId"],["impl PartialEq for PathId"],["impl PartialEq for PathStats"],["impl PartialEq for ShouldTransmit"],["impl PartialEq for StreamId"],["impl PartialEq for StreamInfo"],["impl PartialEq for Error"],["impl PartialEq for Code"],["impl PartialEq for UdpStats"],["impl PartialEq for VarInt"],["impl PartialEq for VarIntBoundsExceeded"],["impl PartialEq for Written"],["impl PartialEq for TransportParameters"]]],["noq_udp",[["impl PartialEq for EcnCodepoint"]]]]); + var implementors = Object.fromEntries([["bench",[["impl PartialEq for CipherSuite"]]],["noq",[["impl PartialEq for ReadError"],["impl PartialEq for ReadExactError"],["impl PartialEq for ReadToEndError"],["impl PartialEq for ResetError"],["impl PartialEq for SendDatagramError"],["impl PartialEq for StoppedError"],["impl PartialEq for WriteError"],["impl PartialEq for Lagged"],["impl PartialEq for Path"],["impl PartialEq for WeakPathHandle"]]],["noq_proto",[["impl PartialEq for ClosePathError"],["impl PartialEq for ConfigError"],["impl PartialEq for ConnectError"],["impl PartialEq for ConnectionError"],["impl PartialEq for Dir"],["impl PartialEq for EcnCodepoint"],["impl PartialEq for FinishError"],["impl PartialEq for FrameType"],["impl PartialEq for LongType"],["impl PartialEq for MaybeFrame"],["impl PartialEq for PacketDecodeError"],["impl PartialEq for PathAbandonReason"],["impl PartialEq for PathError"],["impl PartialEq for PathEvent"],["impl PartialEq for PathStatus"],["impl PartialEq for ReadError"],["impl PartialEq for ReadableError"],["impl PartialEq for SendDatagramError"],["impl PartialEq for SetPathStatusError"],["impl PartialEq for Side"],["impl PartialEq for StreamEvent"],["impl PartialEq for WriteError"],["impl PartialEq for Error"],["impl PartialEq for UnexpectedEnd"],["impl PartialEq for ExportKeyingMaterialError"],["impl PartialEq for ApplicationClose"],["impl PartialEq for Chunk"],["impl PartialEq for ClosedPath"],["impl PartialEq for ClosedStream"],["impl PartialEq for ConnectionClose"],["impl PartialEq for ConnectionHandle"],["impl PartialEq for ConnectionId"],["impl PartialEq for DatagramInfo"],["impl PartialEq for FourTuple"],["impl PartialEq for FrameStats"],["impl PartialEq for IdleTimeout"],["impl PartialEq for InvalidFrameId"],["impl PartialEq for PathId"],["impl PartialEq for PathStats"],["impl PartialEq for ShouldTransmit"],["impl PartialEq for StreamId"],["impl PartialEq for StreamInfo"],["impl PartialEq for Error"],["impl PartialEq for Code"],["impl PartialEq for UdpStats"],["impl PartialEq for VarInt"],["impl PartialEq for VarIntBoundsExceeded"],["impl PartialEq for TransportParameters"]]],["noq_udp",[["impl PartialEq for EcnCodepoint"]]]]); if (window.register_implementors) { window.register_implementors(implementors); } else { window.pending_implementors = implementors; } })() -//{"start":57,"fragment_lengths":[272,2598,13548,282]} \ No newline at end of file +//{"start":57,"fragment_lengths":[272,2598,13285,282]} \ No newline at end of file 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 e421ac732..7ce0db891 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 From<Unspecified> for CryptoError"],["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<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,10369,599]} \ No newline at end of file +//{"start":57,"fragment_lengths":[5252,10067,599]} \ No newline at end of file diff --git a/pr/451/docs/trait.impl/core/default/trait.Default.js b/pr/451/docs/trait.impl/core/default/trait.Default.js index 59444fe8a..05bf32959 100644 --- a/pr/451/docs/trait.impl/core/default/trait.Default.js +++ b/pr/451/docs/trait.impl/core/default/trait.Default.js @@ -1,9 +1,9 @@ (function() { - var implementors = Object.fromEntries([["bench",[["impl Default for Stats"],["impl Default for StreamStats"]]],["bulk",[["impl Default for ClientStats"]]],["noq",[["impl Default for EndpointStats"]]],["noq_proto",[["impl Default for PathStatus"],["impl Default for BbrConfig"],["impl Default for ControllerMetrics"],["impl Default for CubicConfig"],["impl Default for NewRenoConfig"],["impl Default for AckFrequencyConfig"],["impl Default for BloomTokenLog"],["impl Default for ClosedPath"],["impl Default for ClosedStream"],["impl Default for ConnectionStats"],["impl Default for EndpointConfig"],["impl Default for FrameStats"],["impl Default for HashedConnectionIdGenerator"],["impl Default for IdleTimeout"],["impl Default for MtuDiscoveryConfig"],["impl Default for PathId"],["impl Default for PathStats"],["impl Default for RandomConnectionIdGenerator"],["impl Default for ShouldTransmit"],["impl Default for TokenMemoryCache"],["impl Default for TransportConfig"],["impl Default for UdpStats"],["impl Default for ValidationTokenConfig"],["impl Default for VarInt"],["impl Default for Written"]]],["noq_udp",[["impl Default for RecvMeta"]]],["perf",[["impl Default for OpenStreamStats"],["impl Default for Stats"]]]]); + var implementors = Object.fromEntries([["bench",[["impl Default for Stats"],["impl Default for StreamStats"]]],["bulk",[["impl Default for ClientStats"]]],["noq",[["impl Default for EndpointStats"]]],["noq_proto",[["impl Default for PathStatus"],["impl Default for Bbr3Config"],["impl Default for ControllerMetrics"],["impl Default for CubicConfig"],["impl Default for NewRenoConfig"],["impl Default for AckFrequencyConfig"],["impl Default for BloomTokenLog"],["impl Default for ClosedPath"],["impl Default for ClosedStream"],["impl Default for ConnectionStats"],["impl Default for EndpointConfig"],["impl Default for FrameStats"],["impl Default for HashedConnectionIdGenerator"],["impl Default for IdleTimeout"],["impl Default for MtuDiscoveryConfig"],["impl Default for PathId"],["impl Default for PathStats"],["impl Default for RandomConnectionIdGenerator"],["impl Default for ShouldTransmit"],["impl Default for TokenMemoryCache"],["impl Default for TransportConfig"],["impl Default for UdpStats"],["impl Default for ValidationTokenConfig"],["impl Default for VarInt"]]],["noq_udp",[["impl Default for RecvMeta"]]],["perf",[["impl Default for OpenStreamStats"],["impl Default for Stats"]]]]); if (window.register_implementors) { window.register_implementors(implementors); } else { window.pending_implementors = implementors; } })() -//{"start":57,"fragment_lengths":[557,278,281,7213,278,565]} \ No newline at end of file +//{"start":57,"fragment_lengths":[557,278,281,6951,278,565]} \ No newline at end of file diff --git a/pr/451/docs/trait.impl/core/fmt/trait.Debug.js b/pr/451/docs/trait.impl/core/fmt/trait.Debug.js index ef9f0a234..cd2ac759b 100644 --- a/pr/451/docs/trait.impl/core/fmt/trait.Debug.js +++ b/pr/451/docs/trait.impl/core/fmt/trait.Debug.js @@ -1,9 +1,9 @@ (function() { - var implementors = Object.fromEntries([["bench",[["impl Debug for CipherSuite"],["impl Debug for RuntimeType"],["impl Debug for TransferResult"],["impl Debug for Opt"]]],["certificate",[["impl Debug for SkipServerVerification"]]],["noq",[["impl Debug for ReadError"],["impl Debug for ReadExactError"],["impl Debug for ReadToEndError"],["impl Debug for ResetError"],["impl Debug for SendDatagramError"],["impl Debug for StoppedError"],["impl Debug for WriteError"],["impl Debug for Connecting"],["impl Debug for Connection"],["impl Debug for Endpoint"],["impl Debug for EndpointStats"],["impl Debug for Incoming"],["impl Debug for IncomingFuture"],["impl Debug for Lagged"],["impl Debug for NatTraversalUpdates"],["impl Debug for ObservedExternalAddr"],["impl Debug for Path"],["impl Debug for PathEvents"],["impl Debug for RecvStream"],["impl Debug for RetryError"],["impl Debug for SendStream"],["impl Debug for SmolRuntime"],["impl Debug for Stopped"],["impl Debug for TokioRuntime"],["impl Debug for UnorderedRecvStream"],["impl Debug for WeakConnectionHandle"],["impl Debug for WeakPathHandle"]]],["noq_proto",[["impl Debug for ClosePathError"],["impl Debug for ConfigError"],["impl Debug for ConnectError"],["impl Debug for ConnectionError"],["impl Debug for Dir"],["impl Debug for EcnCodepoint"],["impl Debug for Event"],["impl Debug for FinishError"],["impl Debug for FrameType"],["impl Debug for LongType"],["impl Debug for MaybeFrame"],["impl Debug for PacketDecodeError"],["impl Debug for PathAbandonReason"],["impl Debug for PathError"],["impl Debug for PathEvent"],["impl Debug for PathStatus"],["impl Debug for ProtectedHeader"],["impl Debug for ReadError"],["impl Debug for ReadableError"],["impl Debug for SendDatagramError"],["impl Debug for SetPathStatusError"],["impl Debug for Side"],["impl Debug for StreamEvent"],["impl Debug for WriteError"],["impl Debug for Error"],["impl Debug for Event"],["impl Debug for Error"],["impl Debug for UnexpectedEnd"],["impl Debug for Bbr"],["impl Debug for BbrConfig"],["impl Debug for ControllerMetrics"],["impl Debug for Cubic"],["impl Debug for CubicConfig"],["impl Debug for NewReno"],["impl Debug for NewRenoConfig"],["impl Debug for NoInitialCipherSuite"],["impl Debug for CryptoError"],["impl Debug for ExportKeyingMaterialError"],["impl Debug for UnsupportedVersion"],["impl Debug for AcceptError"],["impl Debug for AckFrequencyConfig"],["impl Debug for ApplicationClose"],["impl Debug for Chunk"],["impl Debug for ClientConfig"],["impl Debug for ClosedPath"],["impl Debug for ClosedStream"],["impl Debug for Connection"],["impl Debug for ConnectionClose"],["impl Debug for ConnectionEvent"],["impl Debug for ConnectionHandle"],["impl Debug for ConnectionId"],["impl Debug for ConnectionStats"],["impl Debug for Datagram"],["impl Debug for DatagramInfo"],["impl Debug for Endpoint"],["impl Debug for EndpointConfig"],["impl Debug for EndpointEvent"],["impl Debug for FourTuple"],["impl Debug for FrameStats"],["impl Debug for IdleTimeout"],["impl Debug for Incoming"],["impl Debug for InvalidCid"],["impl Debug for InvalidFrameId"],["impl Debug for MtuDiscoveryConfig"],["impl Debug for MultipathNotNegotiated"],["impl Debug for PartialDecode"],["impl Debug for PathId"],["impl Debug for PathStats"],["impl Debug for ProtectedInitialHeader"],["impl Debug for QlogFileFactory"],["impl Debug for RandomConnectionIdGenerator"],["impl Debug for RetryError"],["impl Debug for RttEstimator"],["impl Debug for ServerConfig"],["impl Debug for ShouldTransmit"],["impl Debug for StreamId"],["impl Debug for StreamInfo"],["impl Debug for TokenMemoryCache"],["impl Debug for Transmit"],["impl Debug for TransportConfig"],["impl Debug for Error"],["impl Debug for Code"],["impl Debug for UdpStats"],["impl Debug for ValidationTokenConfig"],["impl Debug for VarInt"],["impl Debug for VarIntBoundsExceeded"],["impl Debug for Written"],["impl Debug for TransportParameters"]]],["noq_udp",[["impl Debug for EcnCodepoint"],["impl Debug for RecvMeta"],["impl Debug for UdpSocketState"],["impl<'a> Debug for Transmit<'a>"]]]]); + var implementors = Object.fromEntries([["bench",[["impl Debug for CipherSuite"],["impl Debug for RuntimeType"],["impl Debug for TransferResult"],["impl Debug for Opt"]]],["certificate",[["impl Debug for SkipServerVerification"]]],["noq",[["impl Debug for ReadError"],["impl Debug for ReadExactError"],["impl Debug for ReadToEndError"],["impl Debug for ResetError"],["impl Debug for SendDatagramError"],["impl Debug for StoppedError"],["impl Debug for WriteError"],["impl Debug for Closed"],["impl Debug for Connecting"],["impl Debug for Connection"],["impl Debug for Endpoint"],["impl Debug for EndpointStats"],["impl Debug for Incoming"],["impl Debug for IncomingFuture"],["impl Debug for Lagged"],["impl Debug for NatTraversalUpdates"],["impl Debug for ObservedExternalAddr"],["impl Debug for Path"],["impl Debug for PathEvents"],["impl Debug for RecvStream"],["impl Debug for RetryError"],["impl Debug for SendStream"],["impl Debug for SmolRuntime"],["impl Debug for Stopped"],["impl Debug for TokioRuntime"],["impl Debug for UnorderedRecvStream"],["impl Debug for WeakConnectionHandle"],["impl Debug for WeakPathHandle"]]],["noq_proto",[["impl Debug for ClosePathError"],["impl Debug for ConfigError"],["impl Debug for ConnectError"],["impl Debug for ConnectionError"],["impl Debug for Dir"],["impl Debug for EcnCodepoint"],["impl Debug for Event"],["impl Debug for FinishError"],["impl Debug for FrameType"],["impl Debug for LongType"],["impl Debug for MaybeFrame"],["impl Debug for PacketDecodeError"],["impl Debug for PathAbandonReason"],["impl Debug for PathError"],["impl Debug for PathEvent"],["impl Debug for PathStatus"],["impl Debug for ProtectedHeader"],["impl Debug for ReadError"],["impl Debug for ReadableError"],["impl Debug for SendDatagramError"],["impl Debug for SetPathStatusError"],["impl Debug for Side"],["impl Debug for StreamEvent"],["impl Debug for WriteError"],["impl Debug for Error"],["impl Debug for Event"],["impl Debug for Error"],["impl Debug for UnexpectedEnd"],["impl Debug for Bbr3"],["impl Debug for Bbr3Config"],["impl Debug for Cubic"],["impl Debug for CubicConfig"],["impl Debug for NewReno"],["impl Debug for NewRenoConfig"],["impl Debug for NoInitialCipherSuite"],["impl Debug for CryptoError"],["impl Debug for ExportKeyingMaterialError"],["impl Debug for UnsupportedVersion"],["impl Debug for AcceptError"],["impl Debug for AckFrequencyConfig"],["impl Debug for ApplicationClose"],["impl Debug for Chunk"],["impl Debug for ClientConfig"],["impl Debug for ClosedPath"],["impl Debug for ClosedStream"],["impl Debug for Connection"],["impl Debug for ConnectionClose"],["impl Debug for ConnectionEvent"],["impl Debug for ConnectionHandle"],["impl Debug for ConnectionId"],["impl Debug for ConnectionStats"],["impl Debug for Datagram"],["impl Debug for DatagramInfo"],["impl Debug for Endpoint"],["impl Debug for EndpointConfig"],["impl Debug for EndpointEvent"],["impl Debug for FourTuple"],["impl Debug for FrameStats"],["impl Debug for IdleTimeout"],["impl Debug for Incoming"],["impl Debug for InvalidCid"],["impl Debug for InvalidFrameId"],["impl Debug for MtuDiscoveryConfig"],["impl Debug for MultipathNotNegotiated"],["impl Debug for PartialDecode"],["impl Debug for PathId"],["impl Debug for PathStats"],["impl Debug for ProtectedInitialHeader"],["impl Debug for QlogFileFactory"],["impl Debug for RandomConnectionIdGenerator"],["impl Debug for RetryError"],["impl Debug for RttEstimator"],["impl Debug for ServerConfig"],["impl Debug for ShouldTransmit"],["impl Debug for StreamId"],["impl Debug for StreamInfo"],["impl Debug for TokenMemoryCache"],["impl Debug for Transmit"],["impl Debug for TransportConfig"],["impl Debug for Error"],["impl Debug for Code"],["impl Debug for UdpStats"],["impl Debug for ValidationTokenConfig"],["impl Debug for VarInt"],["impl Debug for VarIntBoundsExceeded"],["impl Debug for TransportParameters"]]],["noq_udp",[["impl Debug for EcnCodepoint"],["impl Debug for RecvMeta"],["impl Debug for UdpSocketState"],["impl<'a> Debug for Transmit<'a>"]]]]); if (window.register_implementors) { window.register_implementors(implementors); } else { window.pending_implementors = implementors; } })() -//{"start":57,"fragment_lengths":[1017,318,6817,23688,1058]} \ No newline at end of file +//{"start":57,"fragment_lengths":[1017,318,7053,23139,1058]} \ No newline at end of file diff --git a/pr/451/docs/trait.impl/core/marker/trait.Copy.js b/pr/451/docs/trait.impl/core/marker/trait.Copy.js index 733a46e33..8b8375e3a 100644 --- a/pr/451/docs/trait.impl/core/marker/trait.Copy.js +++ b/pr/451/docs/trait.impl/core/marker/trait.Copy.js @@ -1,9 +1,9 @@ (function() { - var implementors = Object.fromEntries([["bench",[["impl Copy for CipherSuite"],["impl Copy for RuntimeType"],["impl Copy for Opt"]]],["noq",[["impl Copy for EndpointStats"]]],["noq_proto",[["impl Copy for Dir"],["impl Copy for EcnCodepoint"],["impl Copy for FrameType"],["impl Copy for LongType"],["impl Copy for MaybeFrame"],["impl Copy for PathError"],["impl Copy for PathStatus"],["impl Copy for Side"],["impl Copy for Error"],["impl Copy for UnexpectedEnd"],["impl Copy for ConnectionHandle"],["impl Copy for ConnectionId"],["impl Copy for DatagramInfo"],["impl Copy for FourTuple"],["impl Copy for FrameStats"],["impl Copy for IdleTimeout"],["impl Copy for InvalidCid"],["impl Copy for MultipathNotNegotiated"],["impl Copy for PathId"],["impl Copy for PathStats"],["impl Copy for RandomConnectionIdGenerator"],["impl Copy for RttEstimator"],["impl Copy for ShouldTransmit"],["impl Copy for StreamId"],["impl Copy for StreamInfo"],["impl Copy for Code"],["impl Copy for UdpStats"],["impl Copy for VarInt"],["impl Copy for VarIntBoundsExceeded"],["impl Copy for Written"],["impl Copy for TransportParameters"]]],["noq_udp",[["impl Copy for EcnCodepoint"],["impl Copy for RecvMeta"]]],["perf",[["impl Copy for CongestionAlgorithm"]]]]); + var implementors = Object.fromEntries([["bench",[["impl Copy for CipherSuite"],["impl Copy for RuntimeType"],["impl Copy for Opt"]]],["noq",[["impl Copy for EndpointStats"]]],["noq_proto",[["impl Copy for Dir"],["impl Copy for EcnCodepoint"],["impl Copy for FrameType"],["impl Copy for LongType"],["impl Copy for MaybeFrame"],["impl Copy for PathError"],["impl Copy for PathStatus"],["impl Copy for Side"],["impl Copy for Error"],["impl Copy for UnexpectedEnd"],["impl Copy for ConnectionHandle"],["impl Copy for ConnectionId"],["impl Copy for DatagramInfo"],["impl Copy for FourTuple"],["impl Copy for FrameStats"],["impl Copy for IdleTimeout"],["impl Copy for InvalidCid"],["impl Copy for MultipathNotNegotiated"],["impl Copy for PathId"],["impl Copy for PathStats"],["impl Copy for RandomConnectionIdGenerator"],["impl Copy for RttEstimator"],["impl Copy for ShouldTransmit"],["impl Copy for StreamId"],["impl Copy for StreamInfo"],["impl Copy for Code"],["impl Copy for UdpStats"],["impl Copy for VarInt"],["impl Copy for VarIntBoundsExceeded"],["impl Copy for TransportParameters"]]],["noq_udp",[["impl Copy for EcnCodepoint"],["impl Copy for RecvMeta"]]],["perf",[["impl Copy for CongestionAlgorithm"]]]]); if (window.register_implementors) { window.register_implementors(implementors); } else { window.pending_implementors = implementors; } })() -//{"start":57,"fragment_lengths":[749,270,8319,526,285]} \ No newline at end of file +//{"start":57,"fragment_lengths":[749,270,8065,526,285]} \ No newline at end of file diff --git a/pr/451/docs/trait.impl/core/marker/trait.Freeze.js b/pr/451/docs/trait.impl/core/marker/trait.Freeze.js index 5881f0079..9e93aef8b 100644 --- a/pr/451/docs/trait.impl/core/marker/trait.Freeze.js +++ b/pr/451/docs/trait.impl/core/marker/trait.Freeze.js @@ -1,9 +1,9 @@ (function() { - var implementors = Object.fromEntries([["bench",[["impl Freeze for CipherSuite",1,["bench::CipherSuite"]],["impl Freeze for RuntimeType",1,["bench::RuntimeType"]],["impl Freeze for Stats",1,["bench::stats::Stats"]],["impl Freeze for StreamStats",1,["bench::stats::StreamStats"]],["impl Freeze for TransferResult",1,["bench::stats::TransferResult"]],["impl Freeze for Opt",1,["bench::Opt"]]]],["bulk",[["impl Freeze for ClientStats",1,["bulk::ClientStats"]]]],["certificate",[["impl Freeze for SkipServerVerification",1,["certificate::SkipServerVerification"]]]],["noq",[["impl !Freeze for ReadError",1,["noq::recv_stream::ReadError"]],["impl !Freeze for ReadExactError",1,["noq::recv_stream::ReadExactError"]],["impl !Freeze for ReadToEndError",1,["noq::recv_stream::ReadToEndError"]],["impl !Freeze for ResetError",1,["noq::recv_stream::ResetError"]],["impl !Freeze for SendDatagramError",1,["noq::connection::SendDatagramError"]],["impl !Freeze for StoppedError",1,["noq::send_stream::StoppedError"]],["impl !Freeze for WriteError",1,["noq::send_stream::WriteError"]],["impl !Freeze for Incoming",1,["noq::incoming::Incoming"]],["impl !Freeze for IncomingFuture",1,["noq::incoming::IncomingFuture"]],["impl !Freeze for Stopped",1,["noq::send_stream::Stopped"]],["impl Freeze for AddressDiscovery",1,["noq::path::AddressDiscovery"]],["impl Freeze for Connecting",1,["noq::connection::Connecting"]],["impl Freeze for Connection",1,["noq::connection::Connection"]],["impl Freeze for Endpoint",1,["noq::endpoint::Endpoint"]],["impl Freeze for EndpointStats",1,["noq::endpoint::EndpointStats"]],["impl Freeze for Lagged",1,["noq::event_stream::Lagged"]],["impl Freeze for NatTraversalUpdates",1,["noq::event_stream::NatTraversalUpdates"]],["impl Freeze for ObservedExternalAddr",1,["noq::event_stream::ObservedExternalAddr"]],["impl Freeze for OnClosed",1,["noq::connection::OnClosed"]],["impl Freeze for OpenPath",1,["noq::path::OpenPath"]],["impl Freeze for Path",1,["noq::path::Path"]],["impl Freeze for PathEvents",1,["noq::event_stream::PathEvents"]],["impl Freeze for RecvStream",1,["noq::recv_stream::RecvStream"]],["impl Freeze for RetryError",1,["noq::incoming::RetryError"]],["impl Freeze for SendStream",1,["noq::send_stream::SendStream"]],["impl Freeze for SmolRuntime",1,["noq::runtime::smol::SmolRuntime"]],["impl Freeze for TokioRuntime",1,["noq::runtime::tokio::TokioRuntime"]],["impl Freeze for UnorderedRecvStream",1,["noq::recv_stream::UnorderedRecvStream"]],["impl Freeze for WeakConnectionHandle",1,["noq::connection::WeakConnectionHandle"]],["impl Freeze for WeakPathHandle",1,["noq::path::WeakPathHandle"]],["impl Freeze for ZeroRttAccepted",1,["noq::connection::ZeroRttAccepted"]],["impl<'a> !Freeze for Accept<'a>",1,["noq::endpoint::Accept"]],["impl<'a> !Freeze for AcceptBi<'a>",1,["noq::connection::AcceptBi"]],["impl<'a> !Freeze for AcceptUni<'a>",1,["noq::connection::AcceptUni"]],["impl<'a> !Freeze for OpenBi<'a>",1,["noq::connection::OpenBi"]],["impl<'a> !Freeze for OpenUni<'a>",1,["noq::connection::OpenUni"]],["impl<'a> !Freeze for ReadDatagram<'a>",1,["noq::connection::ReadDatagram"]],["impl<'a> !Freeze for SendDatagram<'a>",1,["noq::connection::SendDatagram"]]]],["noq_perf",[["impl Freeze for Commands",1,["noq_perf::Commands"]],["impl Freeze for Cli",1,["noq_perf::Cli"]]]],["noq_proto",[["impl !Freeze for ConnectionError",1,["noq_proto::connection::ConnectionError"]],["impl !Freeze for DatagramEvent",1,["noq_proto::endpoint::DatagramEvent"]],["impl !Freeze for Event",1,["noq_proto::connection::Event"]],["impl !Freeze for SendDatagramError",1,["noq_proto::connection::datagrams::SendDatagramError"]],["impl !Freeze for AcceptError",1,["noq_proto::endpoint::AcceptError"]],["impl !Freeze for ApplicationClose",1,["noq_proto::frame::ApplicationClose"]],["impl !Freeze for BloomTokenLog",1,["noq_proto::bloom_token_log::BloomTokenLog"]],["impl !Freeze for Chunk",1,["noq_proto::connection::assembler::Chunk"]],["impl !Freeze for Connection",1,["noq_proto::connection::Connection"]],["impl !Freeze for ConnectionClose",1,["noq_proto::frame::ConnectionClose"]],["impl !Freeze for Datagram",1,["noq_proto::frame::Datagram"]],["impl !Freeze for DecryptedInitial",1,["noq_proto::endpoint::DecryptedInitial"]],["impl !Freeze for Incoming",1,["noq_proto::endpoint::Incoming"]],["impl !Freeze for IncomingAlpns",1,["noq_proto::endpoint::IncomingAlpns"]],["impl !Freeze for TokenMemoryCache",1,["noq_proto::token_memory_cache::TokenMemoryCache"]],["impl Freeze for ClosePathError",1,["noq_proto::connection::ClosePathError"]],["impl Freeze for ConfigError",1,["noq_proto::config::ConfigError"]],["impl Freeze for ConnectError",1,["noq_proto::endpoint::ConnectError"]],["impl Freeze for Dir",1,["noq_proto::Dir"]],["impl Freeze for EcnCodepoint",1,["noq_proto::shared::EcnCodepoint"]],["impl Freeze for FinishError",1,["noq_proto::connection::streams::send::FinishError"]],["impl Freeze for FrameType",1,["noq_proto::frame::FrameType"]],["impl Freeze for LongType",1,["noq_proto::packet::LongType"]],["impl Freeze for MaybeFrame",1,["noq_proto::frame::MaybeFrame"]],["impl Freeze for PacketDecodeError",1,["noq_proto::packet::PacketDecodeError"]],["impl Freeze for PathAbandonReason",1,["noq_proto::connection::paths::PathAbandonReason"]],["impl Freeze for PathError",1,["noq_proto::connection::PathError"]],["impl Freeze for PathEvent",1,["noq_proto::connection::paths::PathEvent"]],["impl Freeze for PathStatus",1,["noq_proto::connection::paths::PathStatus"]],["impl Freeze for ProtectedHeader",1,["noq_proto::packet::ProtectedHeader"]],["impl Freeze for ReadError",1,["noq_proto::connection::streams::recv::ReadError"]],["impl Freeze for ReadableError",1,["noq_proto::connection::streams::recv::ReadableError"]],["impl Freeze for SetPathStatusError",1,["noq_proto::connection::paths::SetPathStatusError"]],["impl Freeze for Side",1,["noq_proto::Side"]],["impl Freeze for StreamEvent",1,["noq_proto::connection::streams::StreamEvent"]],["impl Freeze for WriteError",1,["noq_proto::connection::streams::send::WriteError"]],["impl Freeze for Error",1,["noq_proto::n0_nat_traversal::Error"]],["impl Freeze for Event",1,["noq_proto::n0_nat_traversal::Event"]],["impl Freeze for Error",1,["noq_proto::transport_parameters::Error"]],["impl Freeze for UnexpectedEnd",1,["noq_proto::coding::UnexpectedEnd"]],["impl Freeze for Bbr",1,["noq_proto::congestion::bbr::Bbr"]],["impl Freeze for BbrConfig",1,["noq_proto::congestion::bbr::BbrConfig"]],["impl Freeze for ControllerMetrics",1,["noq_proto::congestion::ControllerMetrics"]],["impl Freeze for Cubic",1,["noq_proto::congestion::cubic::Cubic"]],["impl Freeze for CubicConfig",1,["noq_proto::congestion::cubic::CubicConfig"]],["impl Freeze for NewReno",1,["noq_proto::congestion::new_reno::NewReno"]],["impl Freeze for NewRenoConfig",1,["noq_proto::congestion::new_reno::NewRenoConfig"]],["impl Freeze for HandshakeData",1,["noq_proto::crypto::rustls::HandshakeData"]],["impl Freeze for NoInitialCipherSuite",1,["noq_proto::crypto::rustls::NoInitialCipherSuite"]],["impl Freeze for QuicClientConfig",1,["noq_proto::crypto::rustls::QuicClientConfig"]],["impl Freeze for QuicServerConfig",1,["noq_proto::crypto::rustls::QuicServerConfig"]],["impl Freeze for TlsSession",1,["noq_proto::crypto::rustls::TlsSession"]],["impl Freeze for CryptoError",1,["noq_proto::crypto::CryptoError"]],["impl Freeze for ExportKeyingMaterialError",1,["noq_proto::crypto::ExportKeyingMaterialError"]],["impl Freeze for Keys",1,["noq_proto::crypto::Keys"]],["impl Freeze for UnsupportedVersion",1,["noq_proto::crypto::UnsupportedVersion"]],["impl Freeze for AckFrequencyConfig",1,["noq_proto::config::transport::AckFrequencyConfig"]],["impl Freeze for ClientConfig",1,["noq_proto::config::ClientConfig"]],["impl Freeze for ClosedPath",1,["noq_proto::connection::paths::ClosedPath"]],["impl Freeze for ClosedStream",1,["noq_proto::connection::streams::ClosedStream"]],["impl Freeze for ConnectionEvent",1,["noq_proto::shared::ConnectionEvent"]],["impl Freeze for ConnectionHandle",1,["noq_proto::endpoint::ConnectionHandle"]],["impl Freeze for ConnectionId",1,["noq_proto::shared::ConnectionId"]],["impl Freeze for ConnectionStats",1,["noq_proto::connection::stats::ConnectionStats"]],["impl Freeze for DatagramInfo",1,["noq_proto::frame::DatagramInfo"]],["impl Freeze for Endpoint",1,["noq_proto::endpoint::Endpoint"]],["impl Freeze for EndpointConfig",1,["noq_proto::config::EndpointConfig"]],["impl Freeze for EndpointEvent",1,["noq_proto::shared::EndpointEvent"]],["impl Freeze for FixedLengthConnectionIdParser",1,["noq_proto::packet::FixedLengthConnectionIdParser"]],["impl Freeze for FourTuple",1,["noq_proto::FourTuple"]],["impl Freeze for FrameStats",1,["noq_proto::connection::stats::FrameStats"]],["impl Freeze for HashedConnectionIdGenerator",1,["noq_proto::cid_generator::HashedConnectionIdGenerator"]],["impl Freeze for IdleTimeout",1,["noq_proto::config::transport::IdleTimeout"]],["impl Freeze for InvalidCid",1,["noq_proto::cid_generator::InvalidCid"]],["impl Freeze for InvalidFrameId",1,["noq_proto::frame::InvalidFrameId"]],["impl Freeze for MtuDiscoveryConfig",1,["noq_proto::config::transport::MtuDiscoveryConfig"]],["impl Freeze for MultipathNotNegotiated",1,["noq_proto::connection::MultipathNotNegotiated"]],["impl Freeze for NoneTokenLog",1,["noq_proto::token::NoneTokenLog"]],["impl Freeze for NoneTokenStore",1,["noq_proto::token::NoneTokenStore"]],["impl Freeze for PartialDecode",1,["noq_proto::packet::PartialDecode"]],["impl Freeze for PathId",1,["noq_proto::connection::paths::PathId"]],["impl Freeze for PathStats",1,["noq_proto::connection::stats::PathStats"]],["impl Freeze for ProtectedInitialHeader",1,["noq_proto::packet::ProtectedInitialHeader"]],["impl Freeze for QlogConfig",1,["noq_proto::config::qlog::QlogConfig"]],["impl Freeze for QlogFileFactory",1,["noq_proto::config::qlog::QlogFileFactory"]],["impl Freeze for RandomConnectionIdGenerator",1,["noq_proto::cid_generator::RandomConnectionIdGenerator"]],["impl Freeze for RetryError",1,["noq_proto::endpoint::RetryError"]],["impl Freeze for RttEstimator",1,["noq_proto::connection::paths::RttEstimator"]],["impl Freeze for ServerConfig",1,["noq_proto::config::ServerConfig"]],["impl Freeze for ShouldTransmit",1,["noq_proto::connection::streams::ShouldTransmit"]],["impl Freeze for StdSystemTime",1,["noq_proto::config::StdSystemTime"]],["impl Freeze for StreamId",1,["noq_proto::StreamId"]],["impl Freeze for StreamInfo",1,["noq_proto::frame::StreamInfo"]],["impl Freeze for TokenReuseError",1,["noq_proto::token::TokenReuseError"]],["impl Freeze for Transmit",1,["noq_proto::Transmit"]],["impl Freeze for TransportConfig",1,["noq_proto::config::transport::TransportConfig"]],["impl Freeze for Error",1,["noq_proto::transport_error::Error"]],["impl Freeze for Code",1,["noq_proto::transport_error::Code"]],["impl Freeze for UdpStats",1,["noq_proto::connection::stats::UdpStats"]],["impl Freeze for ValidationTokenConfig",1,["noq_proto::config::ValidationTokenConfig"]],["impl Freeze for VarInt",1,["noq_proto::varint::VarInt"]],["impl Freeze for VarIntBoundsExceeded",1,["noq_proto::varint::VarIntBoundsExceeded"]],["impl Freeze for Written",1,["noq_proto::connection::streams::send::Written"]],["impl Freeze for TransportParameters",1,["noq_proto::transport_parameters::TransportParameters"]],["impl<'a> Freeze for Chunks<'a>",1,["noq_proto::connection::streams::recv::Chunks"]],["impl<'a> Freeze for Datagrams<'a>",1,["noq_proto::connection::datagrams::Datagrams"]],["impl<'a> Freeze for RecvStream<'a>",1,["noq_proto::connection::streams::RecvStream"]],["impl<'a> Freeze for SendStream<'a>",1,["noq_proto::connection::streams::SendStream"]],["impl<'a> Freeze for Streams<'a>",1,["noq_proto::connection::streams::Streams"]],["impl<T> Freeze for KeyPair<T>
    where\n T: Freeze,
    ",1,["noq_proto::crypto::KeyPair"]]]],["noq_udp",[["impl !Freeze for UdpSocketState",1,["noq_udp::imp::UdpSocketState"]],["impl Freeze for EcnCodepoint",1,["noq_udp::EcnCodepoint"]],["impl Freeze for RecvMeta",1,["noq_udp::RecvMeta"]],["impl<'a> Freeze for Transmit<'a>",1,["noq_udp::Transmit"]],["impl<'a> Freeze for UdpSockRef<'a>",1,["noq_udp::UdpSockRef"]]]],["perf",[["impl !Freeze for StreamStats",1,["perf::stats::StreamStats"]],["impl Freeze for CongestionAlgorithm",1,["perf::CongestionAlgorithm"]],["impl Freeze for Opt",1,["perf::client::Opt"]],["impl Freeze for NoProtectionClientConfig",1,["perf::noprotection::NoProtectionClientConfig"]],["impl Freeze for NoProtectionServerConfig",1,["perf::noprotection::NoProtectionServerConfig"]],["impl Freeze for Opt",1,["perf::server::Opt"]],["impl Freeze for OpenStreamStats",1,["perf::stats::OpenStreamStats"]],["impl Freeze for Stats",1,["perf::stats::Stats"]],["impl Freeze for CommonOpt",1,["perf::CommonOpt"]]]]]); + var implementors = Object.fromEntries([["bench",[["impl Freeze for CipherSuite",1,["bench::CipherSuite"]],["impl Freeze for RuntimeType",1,["bench::RuntimeType"]],["impl Freeze for Stats",1,["bench::stats::Stats"]],["impl Freeze for StreamStats",1,["bench::stats::StreamStats"]],["impl Freeze for TransferResult",1,["bench::stats::TransferResult"]],["impl Freeze for Opt",1,["bench::Opt"]]]],["bulk",[["impl Freeze for ClientStats",1,["bulk::ClientStats"]]]],["certificate",[["impl Freeze for SkipServerVerification",1,["certificate::SkipServerVerification"]]]],["noq",[["impl !Freeze for ReadError",1,["noq::recv_stream::ReadError"]],["impl !Freeze for ReadExactError",1,["noq::recv_stream::ReadExactError"]],["impl !Freeze for ReadToEndError",1,["noq::recv_stream::ReadToEndError"]],["impl !Freeze for ResetError",1,["noq::recv_stream::ResetError"]],["impl !Freeze for SendDatagramError",1,["noq::connection::SendDatagramError"]],["impl !Freeze for StoppedError",1,["noq::send_stream::StoppedError"]],["impl !Freeze for WriteError",1,["noq::send_stream::WriteError"]],["impl !Freeze for Closed",1,["noq::connection::Closed"]],["impl !Freeze for Incoming",1,["noq::incoming::Incoming"]],["impl !Freeze for IncomingFuture",1,["noq::incoming::IncomingFuture"]],["impl !Freeze for Stopped",1,["noq::send_stream::Stopped"]],["impl Freeze for AddressDiscovery",1,["noq::path::AddressDiscovery"]],["impl Freeze for Connecting",1,["noq::connection::Connecting"]],["impl Freeze for Connection",1,["noq::connection::Connection"]],["impl Freeze for Endpoint",1,["noq::endpoint::Endpoint"]],["impl Freeze for EndpointStats",1,["noq::endpoint::EndpointStats"]],["impl Freeze for Lagged",1,["noq::event_stream::Lagged"]],["impl Freeze for NatTraversalUpdates",1,["noq::event_stream::NatTraversalUpdates"]],["impl Freeze for ObservedExternalAddr",1,["noq::event_stream::ObservedExternalAddr"]],["impl Freeze for OnClosed",1,["noq::connection::OnClosed"]],["impl Freeze for OpenPath",1,["noq::path::OpenPath"]],["impl Freeze for Path",1,["noq::path::Path"]],["impl Freeze for PathEvents",1,["noq::event_stream::PathEvents"]],["impl Freeze for RecvStream",1,["noq::recv_stream::RecvStream"]],["impl Freeze for RetryError",1,["noq::incoming::RetryError"]],["impl Freeze for SendStream",1,["noq::send_stream::SendStream"]],["impl Freeze for SmolRuntime",1,["noq::runtime::smol::SmolRuntime"]],["impl Freeze for TokioRuntime",1,["noq::runtime::tokio::TokioRuntime"]],["impl Freeze for UnorderedRecvStream",1,["noq::recv_stream::UnorderedRecvStream"]],["impl Freeze for WeakConnectionHandle",1,["noq::connection::WeakConnectionHandle"]],["impl Freeze for WeakPathHandle",1,["noq::path::WeakPathHandle"]],["impl Freeze for ZeroRttAccepted",1,["noq::connection::ZeroRttAccepted"]],["impl<'a> !Freeze for Accept<'a>",1,["noq::endpoint::Accept"]],["impl<'a> !Freeze for AcceptBi<'a>",1,["noq::connection::AcceptBi"]],["impl<'a> !Freeze for AcceptUni<'a>",1,["noq::connection::AcceptUni"]],["impl<'a> !Freeze for OpenBi<'a>",1,["noq::connection::OpenBi"]],["impl<'a> !Freeze for OpenUni<'a>",1,["noq::connection::OpenUni"]],["impl<'a> !Freeze for ReadDatagram<'a>",1,["noq::connection::ReadDatagram"]],["impl<'a> !Freeze for SendDatagram<'a>",1,["noq::connection::SendDatagram"]]]],["noq_perf",[["impl Freeze for Commands",1,["noq_perf::Commands"]],["impl Freeze for Cli",1,["noq_perf::Cli"]]]],["noq_proto",[["impl !Freeze for ConnectionError",1,["noq_proto::connection::ConnectionError"]],["impl !Freeze for DatagramEvent",1,["noq_proto::endpoint::DatagramEvent"]],["impl !Freeze for Event",1,["noq_proto::connection::Event"]],["impl !Freeze for SendDatagramError",1,["noq_proto::connection::datagrams::SendDatagramError"]],["impl !Freeze for AcceptError",1,["noq_proto::endpoint::AcceptError"]],["impl !Freeze for ApplicationClose",1,["noq_proto::frame::ApplicationClose"]],["impl !Freeze for BloomTokenLog",1,["noq_proto::bloom_token_log::BloomTokenLog"]],["impl !Freeze for Chunk",1,["noq_proto::connection::assembler::Chunk"]],["impl !Freeze for Connection",1,["noq_proto::connection::Connection"]],["impl !Freeze for ConnectionClose",1,["noq_proto::frame::ConnectionClose"]],["impl !Freeze for Datagram",1,["noq_proto::frame::Datagram"]],["impl !Freeze for DecryptedInitial",1,["noq_proto::endpoint::DecryptedInitial"]],["impl !Freeze for Incoming",1,["noq_proto::endpoint::Incoming"]],["impl !Freeze for IncomingAlpns",1,["noq_proto::endpoint::IncomingAlpns"]],["impl !Freeze for TokenMemoryCache",1,["noq_proto::token_memory_cache::TokenMemoryCache"]],["impl Freeze for ClosePathError",1,["noq_proto::connection::ClosePathError"]],["impl Freeze for ConfigError",1,["noq_proto::config::ConfigError"]],["impl Freeze for ConnectError",1,["noq_proto::endpoint::ConnectError"]],["impl Freeze for Dir",1,["noq_proto::Dir"]],["impl Freeze for EcnCodepoint",1,["noq_proto::shared::EcnCodepoint"]],["impl Freeze for FinishError",1,["noq_proto::connection::streams::send::FinishError"]],["impl Freeze for FrameType",1,["noq_proto::frame::FrameType"]],["impl Freeze for LongType",1,["noq_proto::packet::LongType"]],["impl Freeze for MaybeFrame",1,["noq_proto::frame::MaybeFrame"]],["impl Freeze for PacketDecodeError",1,["noq_proto::packet::PacketDecodeError"]],["impl Freeze for PathAbandonReason",1,["noq_proto::connection::paths::PathAbandonReason"]],["impl Freeze for PathError",1,["noq_proto::connection::PathError"]],["impl Freeze for PathEvent",1,["noq_proto::connection::paths::PathEvent"]],["impl Freeze for PathStatus",1,["noq_proto::connection::paths::PathStatus"]],["impl Freeze for ProtectedHeader",1,["noq_proto::packet::ProtectedHeader"]],["impl Freeze for ReadError",1,["noq_proto::connection::streams::recv::ReadError"]],["impl Freeze for ReadableError",1,["noq_proto::connection::streams::recv::ReadableError"]],["impl Freeze for SetPathStatusError",1,["noq_proto::connection::paths::SetPathStatusError"]],["impl Freeze for Side",1,["noq_proto::Side"]],["impl Freeze for StreamEvent",1,["noq_proto::connection::streams::StreamEvent"]],["impl Freeze for WriteError",1,["noq_proto::connection::streams::send::WriteError"]],["impl Freeze for Error",1,["noq_proto::n0_nat_traversal::Error"]],["impl Freeze for Event",1,["noq_proto::n0_nat_traversal::Event"]],["impl Freeze for Error",1,["noq_proto::transport_parameters::Error"]],["impl Freeze for UnexpectedEnd",1,["noq_proto::coding::UnexpectedEnd"]],["impl Freeze for Bbr3",1,["noq_proto::congestion::bbr3::Bbr3"]],["impl Freeze for Bbr3Config",1,["noq_proto::congestion::bbr3::Bbr3Config"]],["impl Freeze for ControllerMetrics",1,["noq_proto::congestion::ControllerMetrics"]],["impl Freeze for Cubic",1,["noq_proto::congestion::cubic::Cubic"]],["impl Freeze for CubicConfig",1,["noq_proto::congestion::cubic::CubicConfig"]],["impl Freeze for NewReno",1,["noq_proto::congestion::new_reno::NewReno"]],["impl Freeze for NewRenoConfig",1,["noq_proto::congestion::new_reno::NewRenoConfig"]],["impl Freeze for HandshakeData",1,["noq_proto::crypto::rustls::HandshakeData"]],["impl Freeze for NoInitialCipherSuite",1,["noq_proto::crypto::rustls::NoInitialCipherSuite"]],["impl Freeze for QuicClientConfig",1,["noq_proto::crypto::rustls::QuicClientConfig"]],["impl Freeze for QuicServerConfig",1,["noq_proto::crypto::rustls::QuicServerConfig"]],["impl Freeze for TlsSession",1,["noq_proto::crypto::rustls::TlsSession"]],["impl Freeze for CryptoError",1,["noq_proto::crypto::CryptoError"]],["impl Freeze for ExportKeyingMaterialError",1,["noq_proto::crypto::ExportKeyingMaterialError"]],["impl Freeze for Keys",1,["noq_proto::crypto::Keys"]],["impl Freeze for UnsupportedVersion",1,["noq_proto::crypto::UnsupportedVersion"]],["impl Freeze for AckFrequencyConfig",1,["noq_proto::config::transport::AckFrequencyConfig"]],["impl Freeze for ClientConfig",1,["noq_proto::config::ClientConfig"]],["impl Freeze for ClosedPath",1,["noq_proto::connection::paths::ClosedPath"]],["impl Freeze for ClosedStream",1,["noq_proto::connection::streams::ClosedStream"]],["impl Freeze for ConnectionEvent",1,["noq_proto::shared::ConnectionEvent"]],["impl Freeze for ConnectionHandle",1,["noq_proto::endpoint::ConnectionHandle"]],["impl Freeze for ConnectionId",1,["noq_proto::shared::ConnectionId"]],["impl Freeze for ConnectionStats",1,["noq_proto::connection::stats::ConnectionStats"]],["impl Freeze for DatagramInfo",1,["noq_proto::frame::DatagramInfo"]],["impl Freeze for Endpoint",1,["noq_proto::endpoint::Endpoint"]],["impl Freeze for EndpointConfig",1,["noq_proto::config::EndpointConfig"]],["impl Freeze for EndpointEvent",1,["noq_proto::shared::EndpointEvent"]],["impl Freeze for FixedLengthConnectionIdParser",1,["noq_proto::packet::FixedLengthConnectionIdParser"]],["impl Freeze for FourTuple",1,["noq_proto::FourTuple"]],["impl Freeze for FrameStats",1,["noq_proto::connection::stats::FrameStats"]],["impl Freeze for HashedConnectionIdGenerator",1,["noq_proto::cid_generator::HashedConnectionIdGenerator"]],["impl Freeze for IdleTimeout",1,["noq_proto::config::transport::IdleTimeout"]],["impl Freeze for InvalidCid",1,["noq_proto::cid_generator::InvalidCid"]],["impl Freeze for InvalidFrameId",1,["noq_proto::frame::InvalidFrameId"]],["impl Freeze for MtuDiscoveryConfig",1,["noq_proto::config::transport::MtuDiscoveryConfig"]],["impl Freeze for MultipathNotNegotiated",1,["noq_proto::connection::MultipathNotNegotiated"]],["impl Freeze for NoneTokenLog",1,["noq_proto::token::NoneTokenLog"]],["impl Freeze for NoneTokenStore",1,["noq_proto::token::NoneTokenStore"]],["impl Freeze for PartialDecode",1,["noq_proto::packet::PartialDecode"]],["impl Freeze for PathId",1,["noq_proto::connection::paths::PathId"]],["impl Freeze for PathStats",1,["noq_proto::connection::stats::PathStats"]],["impl Freeze for ProtectedInitialHeader",1,["noq_proto::packet::ProtectedInitialHeader"]],["impl Freeze for QlogConfig",1,["noq_proto::config::qlog::QlogConfig"]],["impl Freeze for QlogFileFactory",1,["noq_proto::config::qlog::QlogFileFactory"]],["impl Freeze for RandomConnectionIdGenerator",1,["noq_proto::cid_generator::RandomConnectionIdGenerator"]],["impl Freeze for RetryError",1,["noq_proto::endpoint::RetryError"]],["impl Freeze for RttEstimator",1,["noq_proto::connection::paths::RttEstimator"]],["impl Freeze for ServerConfig",1,["noq_proto::config::ServerConfig"]],["impl Freeze for ShouldTransmit",1,["noq_proto::connection::streams::ShouldTransmit"]],["impl Freeze for StdSystemTime",1,["noq_proto::config::StdSystemTime"]],["impl Freeze for StreamId",1,["noq_proto::StreamId"]],["impl Freeze for StreamInfo",1,["noq_proto::frame::StreamInfo"]],["impl Freeze for TokenReuseError",1,["noq_proto::token::TokenReuseError"]],["impl Freeze for Transmit",1,["noq_proto::Transmit"]],["impl Freeze for TransportConfig",1,["noq_proto::config::transport::TransportConfig"]],["impl Freeze for Error",1,["noq_proto::transport_error::Error"]],["impl Freeze for Code",1,["noq_proto::transport_error::Code"]],["impl Freeze for UdpStats",1,["noq_proto::connection::stats::UdpStats"]],["impl Freeze for ValidationTokenConfig",1,["noq_proto::config::ValidationTokenConfig"]],["impl Freeze for VarInt",1,["noq_proto::varint::VarInt"]],["impl Freeze for VarIntBoundsExceeded",1,["noq_proto::varint::VarIntBoundsExceeded"]],["impl Freeze for TransportParameters",1,["noq_proto::transport_parameters::TransportParameters"]],["impl<'a> Freeze for Chunks<'a>",1,["noq_proto::connection::streams::recv::Chunks"]],["impl<'a> Freeze for Datagrams<'a>",1,["noq_proto::connection::datagrams::Datagrams"]],["impl<'a> Freeze for RecvStream<'a>",1,["noq_proto::connection::streams::RecvStream"]],["impl<'a> Freeze for SendStream<'a>",1,["noq_proto::connection::streams::SendStream"]],["impl<'a> Freeze for Streams<'a>",1,["noq_proto::connection::streams::Streams"]],["impl<T> Freeze for KeyPair<T>
    where\n T: Freeze,
    ",1,["noq_proto::crypto::KeyPair"]]]],["noq_udp",[["impl !Freeze for UdpSocketState",1,["noq_udp::imp::UdpSocketState"]],["impl Freeze for EcnCodepoint",1,["noq_udp::EcnCodepoint"]],["impl Freeze for RecvMeta",1,["noq_udp::RecvMeta"]],["impl<'a> Freeze for Transmit<'a>",1,["noq_udp::Transmit"]],["impl<'a> Freeze for UdpSockRef<'a>",1,["noq_udp::UdpSockRef"]]]],["perf",[["impl !Freeze for StreamStats",1,["perf::stats::StreamStats"]],["impl Freeze for CongestionAlgorithm",1,["perf::CongestionAlgorithm"]],["impl Freeze for Opt",1,["perf::client::Opt"]],["impl Freeze for NoProtectionClientConfig",1,["perf::noprotection::NoProtectionClientConfig"]],["impl Freeze for NoProtectionServerConfig",1,["perf::noprotection::NoProtectionServerConfig"]],["impl Freeze for Opt",1,["perf::server::Opt"]],["impl Freeze for OpenStreamStats",1,["perf::stats::OpenStreamStats"]],["impl Freeze for Stats",1,["perf::stats::Stats"]],["impl Freeze for CommonOpt",1,["perf::CommonOpt"]]]]]); if (window.register_implementors) { window.register_implementors(implementors); } else { window.pending_implementors = implementors; } })() -//{"start":57,"fragment_lengths":[1749,297,369,11364,561,35860,1517,2824]} \ No newline at end of file +//{"start":57,"fragment_lengths":[1749,297,369,11640,561,35558,1517,2824]} \ No newline at end of file diff --git a/pr/451/docs/trait.impl/core/marker/trait.Send.js b/pr/451/docs/trait.impl/core/marker/trait.Send.js index 31cc8fd4a..d6bd494f2 100644 --- a/pr/451/docs/trait.impl/core/marker/trait.Send.js +++ b/pr/451/docs/trait.impl/core/marker/trait.Send.js @@ -1,9 +1,9 @@ (function() { - var implementors = Object.fromEntries([["bench",[["impl Send for CipherSuite",1,["bench::CipherSuite"]],["impl Send for RuntimeType",1,["bench::RuntimeType"]],["impl Send for Stats",1,["bench::stats::Stats"]],["impl Send for StreamStats",1,["bench::stats::StreamStats"]],["impl Send for TransferResult",1,["bench::stats::TransferResult"]],["impl Send for Opt",1,["bench::Opt"]]]],["bulk",[["impl Send for ClientStats",1,["bulk::ClientStats"]]]],["certificate",[["impl Send for SkipServerVerification",1,["certificate::SkipServerVerification"]]]],["noq",[["impl Send for ReadError",1,["noq::recv_stream::ReadError"]],["impl Send for ReadExactError",1,["noq::recv_stream::ReadExactError"]],["impl Send for ReadToEndError",1,["noq::recv_stream::ReadToEndError"]],["impl Send for ResetError",1,["noq::recv_stream::ResetError"]],["impl Send for SendDatagramError",1,["noq::connection::SendDatagramError"]],["impl Send for StoppedError",1,["noq::send_stream::StoppedError"]],["impl Send for WriteError",1,["noq::send_stream::WriteError"]],["impl Send for AddressDiscovery",1,["noq::path::AddressDiscovery"]],["impl Send for Connecting",1,["noq::connection::Connecting"]],["impl Send for Connection",1,["noq::connection::Connection"]],["impl Send for Endpoint",1,["noq::endpoint::Endpoint"]],["impl Send for EndpointStats",1,["noq::endpoint::EndpointStats"]],["impl Send for Incoming",1,["noq::incoming::Incoming"]],["impl Send for IncomingFuture",1,["noq::incoming::IncomingFuture"]],["impl Send for Lagged",1,["noq::event_stream::Lagged"]],["impl Send for NatTraversalUpdates",1,["noq::event_stream::NatTraversalUpdates"]],["impl Send for ObservedExternalAddr",1,["noq::event_stream::ObservedExternalAddr"]],["impl Send for OnClosed",1,["noq::connection::OnClosed"]],["impl Send for OpenPath",1,["noq::path::OpenPath"]],["impl Send for Path",1,["noq::path::Path"]],["impl Send for PathEvents",1,["noq::event_stream::PathEvents"]],["impl Send for RecvStream",1,["noq::recv_stream::RecvStream"]],["impl Send for RetryError",1,["noq::incoming::RetryError"]],["impl Send for SendStream",1,["noq::send_stream::SendStream"]],["impl Send for SmolRuntime",1,["noq::runtime::smol::SmolRuntime"]],["impl Send for Stopped",1,["noq::send_stream::Stopped"]],["impl Send for TokioRuntime",1,["noq::runtime::tokio::TokioRuntime"]],["impl Send for UnorderedRecvStream",1,["noq::recv_stream::UnorderedRecvStream"]],["impl Send for WeakConnectionHandle",1,["noq::connection::WeakConnectionHandle"]],["impl Send for WeakPathHandle",1,["noq::path::WeakPathHandle"]],["impl Send for ZeroRttAccepted",1,["noq::connection::ZeroRttAccepted"]],["impl<'a> Send for Accept<'a>",1,["noq::endpoint::Accept"]],["impl<'a> Send for AcceptBi<'a>",1,["noq::connection::AcceptBi"]],["impl<'a> Send for AcceptUni<'a>",1,["noq::connection::AcceptUni"]],["impl<'a> Send for OpenBi<'a>",1,["noq::connection::OpenBi"]],["impl<'a> Send for OpenUni<'a>",1,["noq::connection::OpenUni"]],["impl<'a> Send for ReadDatagram<'a>",1,["noq::connection::ReadDatagram"]],["impl<'a> Send for SendDatagram<'a>",1,["noq::connection::SendDatagram"]]]],["noq_perf",[["impl Send for Commands",1,["noq_perf::Commands"]],["impl Send for Cli",1,["noq_perf::Cli"]]]],["noq_proto",[["impl Send for ClosePathError",1,["noq_proto::connection::ClosePathError"]],["impl Send for ConfigError",1,["noq_proto::config::ConfigError"]],["impl Send for ConnectError",1,["noq_proto::endpoint::ConnectError"]],["impl Send for ConnectionError",1,["noq_proto::connection::ConnectionError"]],["impl Send for DatagramEvent",1,["noq_proto::endpoint::DatagramEvent"]],["impl Send for Dir",1,["noq_proto::Dir"]],["impl Send for EcnCodepoint",1,["noq_proto::shared::EcnCodepoint"]],["impl Send for Event",1,["noq_proto::connection::Event"]],["impl Send for FinishError",1,["noq_proto::connection::streams::send::FinishError"]],["impl Send for FrameType",1,["noq_proto::frame::FrameType"]],["impl Send for LongType",1,["noq_proto::packet::LongType"]],["impl Send for MaybeFrame",1,["noq_proto::frame::MaybeFrame"]],["impl Send for PacketDecodeError",1,["noq_proto::packet::PacketDecodeError"]],["impl Send for PathAbandonReason",1,["noq_proto::connection::paths::PathAbandonReason"]],["impl Send for PathError",1,["noq_proto::connection::PathError"]],["impl Send for PathEvent",1,["noq_proto::connection::paths::PathEvent"]],["impl Send for PathStatus",1,["noq_proto::connection::paths::PathStatus"]],["impl Send for ProtectedHeader",1,["noq_proto::packet::ProtectedHeader"]],["impl Send for ReadError",1,["noq_proto::connection::streams::recv::ReadError"]],["impl Send for ReadableError",1,["noq_proto::connection::streams::recv::ReadableError"]],["impl Send for SendDatagramError",1,["noq_proto::connection::datagrams::SendDatagramError"]],["impl Send for SetPathStatusError",1,["noq_proto::connection::paths::SetPathStatusError"]],["impl Send for Side",1,["noq_proto::Side"]],["impl Send for StreamEvent",1,["noq_proto::connection::streams::StreamEvent"]],["impl Send for WriteError",1,["noq_proto::connection::streams::send::WriteError"]],["impl Send for Error",1,["noq_proto::n0_nat_traversal::Error"]],["impl Send for Event",1,["noq_proto::n0_nat_traversal::Event"]],["impl Send for Error",1,["noq_proto::transport_parameters::Error"]],["impl Send for UnexpectedEnd",1,["noq_proto::coding::UnexpectedEnd"]],["impl Send for Bbr",1,["noq_proto::congestion::bbr::Bbr"]],["impl Send for BbrConfig",1,["noq_proto::congestion::bbr::BbrConfig"]],["impl Send for ControllerMetrics",1,["noq_proto::congestion::ControllerMetrics"]],["impl Send for Cubic",1,["noq_proto::congestion::cubic::Cubic"]],["impl Send for CubicConfig",1,["noq_proto::congestion::cubic::CubicConfig"]],["impl Send for NewReno",1,["noq_proto::congestion::new_reno::NewReno"]],["impl Send for NewRenoConfig",1,["noq_proto::congestion::new_reno::NewRenoConfig"]],["impl Send for HandshakeData",1,["noq_proto::crypto::rustls::HandshakeData"]],["impl Send for NoInitialCipherSuite",1,["noq_proto::crypto::rustls::NoInitialCipherSuite"]],["impl Send for QuicClientConfig",1,["noq_proto::crypto::rustls::QuicClientConfig"]],["impl Send for QuicServerConfig",1,["noq_proto::crypto::rustls::QuicServerConfig"]],["impl Send for TlsSession",1,["noq_proto::crypto::rustls::TlsSession"]],["impl Send for CryptoError",1,["noq_proto::crypto::CryptoError"]],["impl Send for ExportKeyingMaterialError",1,["noq_proto::crypto::ExportKeyingMaterialError"]],["impl Send for Keys",1,["noq_proto::crypto::Keys"]],["impl Send for UnsupportedVersion",1,["noq_proto::crypto::UnsupportedVersion"]],["impl Send for AcceptError",1,["noq_proto::endpoint::AcceptError"]],["impl Send for AckFrequencyConfig",1,["noq_proto::config::transport::AckFrequencyConfig"]],["impl Send for ApplicationClose",1,["noq_proto::frame::ApplicationClose"]],["impl Send for BloomTokenLog",1,["noq_proto::bloom_token_log::BloomTokenLog"]],["impl Send for Chunk",1,["noq_proto::connection::assembler::Chunk"]],["impl Send for ClientConfig",1,["noq_proto::config::ClientConfig"]],["impl Send for ClosedPath",1,["noq_proto::connection::paths::ClosedPath"]],["impl Send for ClosedStream",1,["noq_proto::connection::streams::ClosedStream"]],["impl Send for Connection",1,["noq_proto::connection::Connection"]],["impl Send for ConnectionClose",1,["noq_proto::frame::ConnectionClose"]],["impl Send for ConnectionEvent",1,["noq_proto::shared::ConnectionEvent"]],["impl Send for ConnectionHandle",1,["noq_proto::endpoint::ConnectionHandle"]],["impl Send for ConnectionId",1,["noq_proto::shared::ConnectionId"]],["impl Send for ConnectionStats",1,["noq_proto::connection::stats::ConnectionStats"]],["impl Send for Datagram",1,["noq_proto::frame::Datagram"]],["impl Send for DatagramInfo",1,["noq_proto::frame::DatagramInfo"]],["impl Send for DecryptedInitial",1,["noq_proto::endpoint::DecryptedInitial"]],["impl Send for Endpoint",1,["noq_proto::endpoint::Endpoint"]],["impl Send for EndpointConfig",1,["noq_proto::config::EndpointConfig"]],["impl Send for EndpointEvent",1,["noq_proto::shared::EndpointEvent"]],["impl Send for FixedLengthConnectionIdParser",1,["noq_proto::packet::FixedLengthConnectionIdParser"]],["impl Send for FourTuple",1,["noq_proto::FourTuple"]],["impl Send for FrameStats",1,["noq_proto::connection::stats::FrameStats"]],["impl Send for HashedConnectionIdGenerator",1,["noq_proto::cid_generator::HashedConnectionIdGenerator"]],["impl Send for IdleTimeout",1,["noq_proto::config::transport::IdleTimeout"]],["impl Send for Incoming",1,["noq_proto::endpoint::Incoming"]],["impl Send for IncomingAlpns",1,["noq_proto::endpoint::IncomingAlpns"]],["impl Send for InvalidCid",1,["noq_proto::cid_generator::InvalidCid"]],["impl Send for InvalidFrameId",1,["noq_proto::frame::InvalidFrameId"]],["impl Send for MtuDiscoveryConfig",1,["noq_proto::config::transport::MtuDiscoveryConfig"]],["impl Send for MultipathNotNegotiated",1,["noq_proto::connection::MultipathNotNegotiated"]],["impl Send for NoneTokenLog",1,["noq_proto::token::NoneTokenLog"]],["impl Send for NoneTokenStore",1,["noq_proto::token::NoneTokenStore"]],["impl Send for PartialDecode",1,["noq_proto::packet::PartialDecode"]],["impl Send for PathId",1,["noq_proto::connection::paths::PathId"]],["impl Send for PathStats",1,["noq_proto::connection::stats::PathStats"]],["impl Send for ProtectedInitialHeader",1,["noq_proto::packet::ProtectedInitialHeader"]],["impl Send for QlogConfig",1,["noq_proto::config::qlog::QlogConfig"]],["impl Send for QlogFileFactory",1,["noq_proto::config::qlog::QlogFileFactory"]],["impl Send for RandomConnectionIdGenerator",1,["noq_proto::cid_generator::RandomConnectionIdGenerator"]],["impl Send for RetryError",1,["noq_proto::endpoint::RetryError"]],["impl Send for RttEstimator",1,["noq_proto::connection::paths::RttEstimator"]],["impl Send for ServerConfig",1,["noq_proto::config::ServerConfig"]],["impl Send for ShouldTransmit",1,["noq_proto::connection::streams::ShouldTransmit"]],["impl Send for StdSystemTime",1,["noq_proto::config::StdSystemTime"]],["impl Send for StreamId",1,["noq_proto::StreamId"]],["impl Send for StreamInfo",1,["noq_proto::frame::StreamInfo"]],["impl Send for TokenMemoryCache",1,["noq_proto::token_memory_cache::TokenMemoryCache"]],["impl Send for TokenReuseError",1,["noq_proto::token::TokenReuseError"]],["impl Send for Transmit",1,["noq_proto::Transmit"]],["impl Send for TransportConfig",1,["noq_proto::config::transport::TransportConfig"]],["impl Send for Error",1,["noq_proto::transport_error::Error"]],["impl Send for Code",1,["noq_proto::transport_error::Code"]],["impl Send for UdpStats",1,["noq_proto::connection::stats::UdpStats"]],["impl Send for ValidationTokenConfig",1,["noq_proto::config::ValidationTokenConfig"]],["impl Send for VarInt",1,["noq_proto::varint::VarInt"]],["impl Send for VarIntBoundsExceeded",1,["noq_proto::varint::VarIntBoundsExceeded"]],["impl Send for Written",1,["noq_proto::connection::streams::send::Written"]],["impl Send for TransportParameters",1,["noq_proto::transport_parameters::TransportParameters"]],["impl<'a> Send for Chunks<'a>",1,["noq_proto::connection::streams::recv::Chunks"]],["impl<'a> Send for Datagrams<'a>",1,["noq_proto::connection::datagrams::Datagrams"]],["impl<'a> Send for RecvStream<'a>",1,["noq_proto::connection::streams::RecvStream"]],["impl<'a> Send for SendStream<'a>",1,["noq_proto::connection::streams::SendStream"]],["impl<'a> Send for Streams<'a>",1,["noq_proto::connection::streams::Streams"]],["impl<T> Send for KeyPair<T>
    where\n T: Send,
    ",1,["noq_proto::crypto::KeyPair"]]]],["noq_udp",[["impl Send for EcnCodepoint",1,["noq_udp::EcnCodepoint"]],["impl Send for RecvMeta",1,["noq_udp::RecvMeta"]],["impl Send for UdpSocketState",1,["noq_udp::imp::UdpSocketState"]],["impl<'a> Send for Transmit<'a>",1,["noq_udp::Transmit"]],["impl<'a> Send for UdpSockRef<'a>",1,["noq_udp::UdpSockRef"]]]],["perf",[["impl Send for CongestionAlgorithm",1,["perf::CongestionAlgorithm"]],["impl Send for Opt",1,["perf::client::Opt"]],["impl Send for NoProtectionClientConfig",1,["perf::noprotection::NoProtectionClientConfig"]],["impl Send for NoProtectionServerConfig",1,["perf::noprotection::NoProtectionServerConfig"]],["impl Send for Opt",1,["perf::server::Opt"]],["impl Send for OpenStreamStats",1,["perf::stats::OpenStreamStats"]],["impl Send for Stats",1,["perf::stats::Stats"]],["impl Send for StreamStats",1,["perf::stats::StreamStats"]],["impl Send for CommonOpt",1,["perf::CommonOpt"]]]]]); + var implementors = Object.fromEntries([["bench",[["impl Send for CipherSuite",1,["bench::CipherSuite"]],["impl Send for RuntimeType",1,["bench::RuntimeType"]],["impl Send for Stats",1,["bench::stats::Stats"]],["impl Send for StreamStats",1,["bench::stats::StreamStats"]],["impl Send for TransferResult",1,["bench::stats::TransferResult"]],["impl Send for Opt",1,["bench::Opt"]]]],["bulk",[["impl Send for ClientStats",1,["bulk::ClientStats"]]]],["certificate",[["impl Send for SkipServerVerification",1,["certificate::SkipServerVerification"]]]],["noq",[["impl Send for ReadError",1,["noq::recv_stream::ReadError"]],["impl Send for ReadExactError",1,["noq::recv_stream::ReadExactError"]],["impl Send for ReadToEndError",1,["noq::recv_stream::ReadToEndError"]],["impl Send for ResetError",1,["noq::recv_stream::ResetError"]],["impl Send for SendDatagramError",1,["noq::connection::SendDatagramError"]],["impl Send for StoppedError",1,["noq::send_stream::StoppedError"]],["impl Send for WriteError",1,["noq::send_stream::WriteError"]],["impl Send for AddressDiscovery",1,["noq::path::AddressDiscovery"]],["impl Send for Closed",1,["noq::connection::Closed"]],["impl Send for Connecting",1,["noq::connection::Connecting"]],["impl Send for Connection",1,["noq::connection::Connection"]],["impl Send for Endpoint",1,["noq::endpoint::Endpoint"]],["impl Send for EndpointStats",1,["noq::endpoint::EndpointStats"]],["impl Send for Incoming",1,["noq::incoming::Incoming"]],["impl Send for IncomingFuture",1,["noq::incoming::IncomingFuture"]],["impl Send for Lagged",1,["noq::event_stream::Lagged"]],["impl Send for NatTraversalUpdates",1,["noq::event_stream::NatTraversalUpdates"]],["impl Send for ObservedExternalAddr",1,["noq::event_stream::ObservedExternalAddr"]],["impl Send for OnClosed",1,["noq::connection::OnClosed"]],["impl Send for OpenPath",1,["noq::path::OpenPath"]],["impl Send for Path",1,["noq::path::Path"]],["impl Send for PathEvents",1,["noq::event_stream::PathEvents"]],["impl Send for RecvStream",1,["noq::recv_stream::RecvStream"]],["impl Send for RetryError",1,["noq::incoming::RetryError"]],["impl Send for SendStream",1,["noq::send_stream::SendStream"]],["impl Send for SmolRuntime",1,["noq::runtime::smol::SmolRuntime"]],["impl Send for Stopped",1,["noq::send_stream::Stopped"]],["impl Send for TokioRuntime",1,["noq::runtime::tokio::TokioRuntime"]],["impl Send for UnorderedRecvStream",1,["noq::recv_stream::UnorderedRecvStream"]],["impl Send for WeakConnectionHandle",1,["noq::connection::WeakConnectionHandle"]],["impl Send for WeakPathHandle",1,["noq::path::WeakPathHandle"]],["impl Send for ZeroRttAccepted",1,["noq::connection::ZeroRttAccepted"]],["impl<'a> Send for Accept<'a>",1,["noq::endpoint::Accept"]],["impl<'a> Send for AcceptBi<'a>",1,["noq::connection::AcceptBi"]],["impl<'a> Send for AcceptUni<'a>",1,["noq::connection::AcceptUni"]],["impl<'a> Send for OpenBi<'a>",1,["noq::connection::OpenBi"]],["impl<'a> Send for OpenUni<'a>",1,["noq::connection::OpenUni"]],["impl<'a> Send for ReadDatagram<'a>",1,["noq::connection::ReadDatagram"]],["impl<'a> Send for SendDatagram<'a>",1,["noq::connection::SendDatagram"]]]],["noq_perf",[["impl Send for Commands",1,["noq_perf::Commands"]],["impl Send for Cli",1,["noq_perf::Cli"]]]],["noq_proto",[["impl Send for ClosePathError",1,["noq_proto::connection::ClosePathError"]],["impl Send for ConfigError",1,["noq_proto::config::ConfigError"]],["impl Send for ConnectError",1,["noq_proto::endpoint::ConnectError"]],["impl Send for ConnectionError",1,["noq_proto::connection::ConnectionError"]],["impl Send for DatagramEvent",1,["noq_proto::endpoint::DatagramEvent"]],["impl Send for Dir",1,["noq_proto::Dir"]],["impl Send for EcnCodepoint",1,["noq_proto::shared::EcnCodepoint"]],["impl Send for Event",1,["noq_proto::connection::Event"]],["impl Send for FinishError",1,["noq_proto::connection::streams::send::FinishError"]],["impl Send for FrameType",1,["noq_proto::frame::FrameType"]],["impl Send for LongType",1,["noq_proto::packet::LongType"]],["impl Send for MaybeFrame",1,["noq_proto::frame::MaybeFrame"]],["impl Send for PacketDecodeError",1,["noq_proto::packet::PacketDecodeError"]],["impl Send for PathAbandonReason",1,["noq_proto::connection::paths::PathAbandonReason"]],["impl Send for PathError",1,["noq_proto::connection::PathError"]],["impl Send for PathEvent",1,["noq_proto::connection::paths::PathEvent"]],["impl Send for PathStatus",1,["noq_proto::connection::paths::PathStatus"]],["impl Send for ProtectedHeader",1,["noq_proto::packet::ProtectedHeader"]],["impl Send for ReadError",1,["noq_proto::connection::streams::recv::ReadError"]],["impl Send for ReadableError",1,["noq_proto::connection::streams::recv::ReadableError"]],["impl Send for SendDatagramError",1,["noq_proto::connection::datagrams::SendDatagramError"]],["impl Send for SetPathStatusError",1,["noq_proto::connection::paths::SetPathStatusError"]],["impl Send for Side",1,["noq_proto::Side"]],["impl Send for StreamEvent",1,["noq_proto::connection::streams::StreamEvent"]],["impl Send for WriteError",1,["noq_proto::connection::streams::send::WriteError"]],["impl Send for Error",1,["noq_proto::n0_nat_traversal::Error"]],["impl Send for Event",1,["noq_proto::n0_nat_traversal::Event"]],["impl Send for Error",1,["noq_proto::transport_parameters::Error"]],["impl Send for UnexpectedEnd",1,["noq_proto::coding::UnexpectedEnd"]],["impl Send for Bbr3",1,["noq_proto::congestion::bbr3::Bbr3"]],["impl Send for Bbr3Config",1,["noq_proto::congestion::bbr3::Bbr3Config"]],["impl Send for ControllerMetrics",1,["noq_proto::congestion::ControllerMetrics"]],["impl Send for Cubic",1,["noq_proto::congestion::cubic::Cubic"]],["impl Send for CubicConfig",1,["noq_proto::congestion::cubic::CubicConfig"]],["impl Send for NewReno",1,["noq_proto::congestion::new_reno::NewReno"]],["impl Send for NewRenoConfig",1,["noq_proto::congestion::new_reno::NewRenoConfig"]],["impl Send for HandshakeData",1,["noq_proto::crypto::rustls::HandshakeData"]],["impl Send for NoInitialCipherSuite",1,["noq_proto::crypto::rustls::NoInitialCipherSuite"]],["impl Send for QuicClientConfig",1,["noq_proto::crypto::rustls::QuicClientConfig"]],["impl Send for QuicServerConfig",1,["noq_proto::crypto::rustls::QuicServerConfig"]],["impl Send for TlsSession",1,["noq_proto::crypto::rustls::TlsSession"]],["impl Send for CryptoError",1,["noq_proto::crypto::CryptoError"]],["impl Send for ExportKeyingMaterialError",1,["noq_proto::crypto::ExportKeyingMaterialError"]],["impl Send for Keys",1,["noq_proto::crypto::Keys"]],["impl Send for UnsupportedVersion",1,["noq_proto::crypto::UnsupportedVersion"]],["impl Send for AcceptError",1,["noq_proto::endpoint::AcceptError"]],["impl Send for AckFrequencyConfig",1,["noq_proto::config::transport::AckFrequencyConfig"]],["impl Send for ApplicationClose",1,["noq_proto::frame::ApplicationClose"]],["impl Send for BloomTokenLog",1,["noq_proto::bloom_token_log::BloomTokenLog"]],["impl Send for Chunk",1,["noq_proto::connection::assembler::Chunk"]],["impl Send for ClientConfig",1,["noq_proto::config::ClientConfig"]],["impl Send for ClosedPath",1,["noq_proto::connection::paths::ClosedPath"]],["impl Send for ClosedStream",1,["noq_proto::connection::streams::ClosedStream"]],["impl Send for Connection",1,["noq_proto::connection::Connection"]],["impl Send for ConnectionClose",1,["noq_proto::frame::ConnectionClose"]],["impl Send for ConnectionEvent",1,["noq_proto::shared::ConnectionEvent"]],["impl Send for ConnectionHandle",1,["noq_proto::endpoint::ConnectionHandle"]],["impl Send for ConnectionId",1,["noq_proto::shared::ConnectionId"]],["impl Send for ConnectionStats",1,["noq_proto::connection::stats::ConnectionStats"]],["impl Send for Datagram",1,["noq_proto::frame::Datagram"]],["impl Send for DatagramInfo",1,["noq_proto::frame::DatagramInfo"]],["impl Send for DecryptedInitial",1,["noq_proto::endpoint::DecryptedInitial"]],["impl Send for Endpoint",1,["noq_proto::endpoint::Endpoint"]],["impl Send for EndpointConfig",1,["noq_proto::config::EndpointConfig"]],["impl Send for EndpointEvent",1,["noq_proto::shared::EndpointEvent"]],["impl Send for FixedLengthConnectionIdParser",1,["noq_proto::packet::FixedLengthConnectionIdParser"]],["impl Send for FourTuple",1,["noq_proto::FourTuple"]],["impl Send for FrameStats",1,["noq_proto::connection::stats::FrameStats"]],["impl Send for HashedConnectionIdGenerator",1,["noq_proto::cid_generator::HashedConnectionIdGenerator"]],["impl Send for IdleTimeout",1,["noq_proto::config::transport::IdleTimeout"]],["impl Send for Incoming",1,["noq_proto::endpoint::Incoming"]],["impl Send for IncomingAlpns",1,["noq_proto::endpoint::IncomingAlpns"]],["impl Send for InvalidCid",1,["noq_proto::cid_generator::InvalidCid"]],["impl Send for InvalidFrameId",1,["noq_proto::frame::InvalidFrameId"]],["impl Send for MtuDiscoveryConfig",1,["noq_proto::config::transport::MtuDiscoveryConfig"]],["impl Send for MultipathNotNegotiated",1,["noq_proto::connection::MultipathNotNegotiated"]],["impl Send for NoneTokenLog",1,["noq_proto::token::NoneTokenLog"]],["impl Send for NoneTokenStore",1,["noq_proto::token::NoneTokenStore"]],["impl Send for PartialDecode",1,["noq_proto::packet::PartialDecode"]],["impl Send for PathId",1,["noq_proto::connection::paths::PathId"]],["impl Send for PathStats",1,["noq_proto::connection::stats::PathStats"]],["impl Send for ProtectedInitialHeader",1,["noq_proto::packet::ProtectedInitialHeader"]],["impl Send for QlogConfig",1,["noq_proto::config::qlog::QlogConfig"]],["impl Send for QlogFileFactory",1,["noq_proto::config::qlog::QlogFileFactory"]],["impl Send for RandomConnectionIdGenerator",1,["noq_proto::cid_generator::RandomConnectionIdGenerator"]],["impl Send for RetryError",1,["noq_proto::endpoint::RetryError"]],["impl Send for RttEstimator",1,["noq_proto::connection::paths::RttEstimator"]],["impl Send for ServerConfig",1,["noq_proto::config::ServerConfig"]],["impl Send for ShouldTransmit",1,["noq_proto::connection::streams::ShouldTransmit"]],["impl Send for StdSystemTime",1,["noq_proto::config::StdSystemTime"]],["impl Send for StreamId",1,["noq_proto::StreamId"]],["impl Send for StreamInfo",1,["noq_proto::frame::StreamInfo"]],["impl Send for TokenMemoryCache",1,["noq_proto::token_memory_cache::TokenMemoryCache"]],["impl Send for TokenReuseError",1,["noq_proto::token::TokenReuseError"]],["impl Send for Transmit",1,["noq_proto::Transmit"]],["impl Send for TransportConfig",1,["noq_proto::config::transport::TransportConfig"]],["impl Send for Error",1,["noq_proto::transport_error::Error"]],["impl Send for Code",1,["noq_proto::transport_error::Code"]],["impl Send for UdpStats",1,["noq_proto::connection::stats::UdpStats"]],["impl Send for ValidationTokenConfig",1,["noq_proto::config::ValidationTokenConfig"]],["impl Send for VarInt",1,["noq_proto::varint::VarInt"]],["impl Send for VarIntBoundsExceeded",1,["noq_proto::varint::VarIntBoundsExceeded"]],["impl Send for TransportParameters",1,["noq_proto::transport_parameters::TransportParameters"]],["impl<'a> Send for Chunks<'a>",1,["noq_proto::connection::streams::recv::Chunks"]],["impl<'a> Send for Datagrams<'a>",1,["noq_proto::connection::datagrams::Datagrams"]],["impl<'a> Send for RecvStream<'a>",1,["noq_proto::connection::streams::RecvStream"]],["impl<'a> Send for SendStream<'a>",1,["noq_proto::connection::streams::SendStream"]],["impl<'a> Send for Streams<'a>",1,["noq_proto::connection::streams::Streams"]],["impl<T> Send for KeyPair<T>
    where\n T: Send,
    ",1,["noq_proto::crypto::KeyPair"]]]],["noq_udp",[["impl Send for EcnCodepoint",1,["noq_udp::EcnCodepoint"]],["impl Send for RecvMeta",1,["noq_udp::RecvMeta"]],["impl Send for UdpSocketState",1,["noq_udp::imp::UdpSocketState"]],["impl<'a> Send for Transmit<'a>",1,["noq_udp::Transmit"]],["impl<'a> Send for UdpSockRef<'a>",1,["noq_udp::UdpSockRef"]]]],["perf",[["impl Send for CongestionAlgorithm",1,["perf::CongestionAlgorithm"]],["impl Send for Opt",1,["perf::client::Opt"]],["impl Send for NoProtectionClientConfig",1,["perf::noprotection::NoProtectionClientConfig"]],["impl Send for NoProtectionServerConfig",1,["perf::noprotection::NoProtectionServerConfig"]],["impl Send for Opt",1,["perf::server::Opt"]],["impl Send for OpenStreamStats",1,["perf::stats::OpenStreamStats"]],["impl Send for Stats",1,["perf::stats::Stats"]],["impl Send for StreamStats",1,["perf::stats::StreamStats"]],["impl Send for CommonOpt",1,["perf::CommonOpt"]]]]]); if (window.register_implementors) { window.register_implementors(implementors); } else { window.pending_implementors = implementors; } })() -//{"start":57,"fragment_lengths":[1713,291,363,11119,549,35179,1486,2769]} \ No newline at end of file +//{"start":57,"fragment_lengths":[1713,291,363,11388,549,34883,1486,2769]} \ No newline at end of file diff --git a/pr/451/docs/trait.impl/core/marker/trait.StructuralPartialEq.js b/pr/451/docs/trait.impl/core/marker/trait.StructuralPartialEq.js index ab3fd56a4..0820ce5f1 100644 --- a/pr/451/docs/trait.impl/core/marker/trait.StructuralPartialEq.js +++ b/pr/451/docs/trait.impl/core/marker/trait.StructuralPartialEq.js @@ -1,9 +1,9 @@ (function() { - var implementors = Object.fromEntries([["bench",[["impl StructuralPartialEq for CipherSuite"]]],["noq",[["impl StructuralPartialEq for ReadError"],["impl StructuralPartialEq for ReadExactError"],["impl StructuralPartialEq for ReadToEndError"],["impl StructuralPartialEq for ResetError"],["impl StructuralPartialEq for SendDatagramError"],["impl StructuralPartialEq for StoppedError"],["impl StructuralPartialEq for WriteError"],["impl StructuralPartialEq for Lagged"]]],["noq_proto",[["impl StructuralPartialEq for ClosePathError"],["impl StructuralPartialEq for ConfigError"],["impl StructuralPartialEq for ConnectError"],["impl StructuralPartialEq for ConnectionError"],["impl StructuralPartialEq for Dir"],["impl StructuralPartialEq for EcnCodepoint"],["impl StructuralPartialEq for FinishError"],["impl StructuralPartialEq for FrameType"],["impl StructuralPartialEq for LongType"],["impl StructuralPartialEq for MaybeFrame"],["impl StructuralPartialEq for PacketDecodeError"],["impl StructuralPartialEq for PathAbandonReason"],["impl StructuralPartialEq for PathError"],["impl StructuralPartialEq for PathEvent"],["impl StructuralPartialEq for PathStatus"],["impl StructuralPartialEq for ReadError"],["impl StructuralPartialEq for ReadableError"],["impl StructuralPartialEq for SendDatagramError"],["impl StructuralPartialEq for SetPathStatusError"],["impl StructuralPartialEq for Side"],["impl StructuralPartialEq for StreamEvent"],["impl StructuralPartialEq for WriteError"],["impl StructuralPartialEq for Error"],["impl StructuralPartialEq for UnexpectedEnd"],["impl StructuralPartialEq for ExportKeyingMaterialError"],["impl StructuralPartialEq for ApplicationClose"],["impl StructuralPartialEq for Chunk"],["impl StructuralPartialEq for ClosedPath"],["impl StructuralPartialEq for ClosedStream"],["impl StructuralPartialEq for ConnectionClose"],["impl StructuralPartialEq for ConnectionHandle"],["impl StructuralPartialEq for ConnectionId"],["impl StructuralPartialEq for DatagramInfo"],["impl StructuralPartialEq for FourTuple"],["impl StructuralPartialEq for FrameStats"],["impl StructuralPartialEq for IdleTimeout"],["impl StructuralPartialEq for InvalidFrameId"],["impl StructuralPartialEq for PathId"],["impl StructuralPartialEq for PathStats"],["impl StructuralPartialEq for ShouldTransmit"],["impl StructuralPartialEq for StreamId"],["impl StructuralPartialEq for StreamInfo"],["impl StructuralPartialEq for Code"],["impl StructuralPartialEq for UdpStats"],["impl StructuralPartialEq for VarInt"],["impl StructuralPartialEq for VarIntBoundsExceeded"],["impl StructuralPartialEq for Written"],["impl StructuralPartialEq for TransportParameters"]]],["noq_udp",[["impl StructuralPartialEq for EcnCodepoint"]]]]); + var implementors = Object.fromEntries([["bench",[["impl StructuralPartialEq for CipherSuite"]]],["noq",[["impl StructuralPartialEq for ReadError"],["impl StructuralPartialEq for ReadExactError"],["impl StructuralPartialEq for ReadToEndError"],["impl StructuralPartialEq for ResetError"],["impl StructuralPartialEq for SendDatagramError"],["impl StructuralPartialEq for StoppedError"],["impl StructuralPartialEq for WriteError"],["impl StructuralPartialEq for Lagged"]]],["noq_proto",[["impl StructuralPartialEq for ClosePathError"],["impl StructuralPartialEq for ConfigError"],["impl StructuralPartialEq for ConnectError"],["impl StructuralPartialEq for ConnectionError"],["impl StructuralPartialEq for Dir"],["impl StructuralPartialEq for EcnCodepoint"],["impl StructuralPartialEq for FinishError"],["impl StructuralPartialEq for FrameType"],["impl StructuralPartialEq for LongType"],["impl StructuralPartialEq for MaybeFrame"],["impl StructuralPartialEq for PacketDecodeError"],["impl StructuralPartialEq for PathAbandonReason"],["impl StructuralPartialEq for PathError"],["impl StructuralPartialEq for PathEvent"],["impl StructuralPartialEq for PathStatus"],["impl StructuralPartialEq for ReadError"],["impl StructuralPartialEq for ReadableError"],["impl StructuralPartialEq for SendDatagramError"],["impl StructuralPartialEq for SetPathStatusError"],["impl StructuralPartialEq for Side"],["impl StructuralPartialEq for StreamEvent"],["impl StructuralPartialEq for WriteError"],["impl StructuralPartialEq for Error"],["impl StructuralPartialEq for UnexpectedEnd"],["impl StructuralPartialEq for ExportKeyingMaterialError"],["impl StructuralPartialEq for ApplicationClose"],["impl StructuralPartialEq for Chunk"],["impl StructuralPartialEq for ClosedPath"],["impl StructuralPartialEq for ClosedStream"],["impl StructuralPartialEq for ConnectionClose"],["impl StructuralPartialEq for ConnectionHandle"],["impl StructuralPartialEq for ConnectionId"],["impl StructuralPartialEq for DatagramInfo"],["impl StructuralPartialEq for FourTuple"],["impl StructuralPartialEq for FrameStats"],["impl StructuralPartialEq for IdleTimeout"],["impl StructuralPartialEq for InvalidFrameId"],["impl StructuralPartialEq for PathId"],["impl StructuralPartialEq for PathStats"],["impl StructuralPartialEq for ShouldTransmit"],["impl StructuralPartialEq for StreamId"],["impl StructuralPartialEq for StreamInfo"],["impl StructuralPartialEq for Code"],["impl StructuralPartialEq for UdpStats"],["impl StructuralPartialEq for VarInt"],["impl StructuralPartialEq for VarIntBoundsExceeded"],["impl StructuralPartialEq for TransportParameters"]]],["noq_udp",[["impl StructuralPartialEq for EcnCodepoint"]]]]); if (window.register_implementors) { window.register_implementors(implementors); } else { window.pending_implementors = implementors; } })() -//{"start":57,"fragment_lengths":[308,2372,15001,318]} \ No newline at end of file +//{"start":57,"fragment_lengths":[308,2372,14702,318]} \ No newline at end of file diff --git a/pr/451/docs/trait.impl/core/marker/trait.Sync.js b/pr/451/docs/trait.impl/core/marker/trait.Sync.js index 2db90824a..a7ad5d61d 100644 --- a/pr/451/docs/trait.impl/core/marker/trait.Sync.js +++ b/pr/451/docs/trait.impl/core/marker/trait.Sync.js @@ -1,9 +1,9 @@ (function() { - var implementors = Object.fromEntries([["bench",[["impl Sync for CipherSuite",1,["bench::CipherSuite"]],["impl Sync for RuntimeType",1,["bench::RuntimeType"]],["impl Sync for Stats",1,["bench::stats::Stats"]],["impl Sync for StreamStats",1,["bench::stats::StreamStats"]],["impl Sync for TransferResult",1,["bench::stats::TransferResult"]],["impl Sync for Opt",1,["bench::Opt"]]]],["bulk",[["impl Sync for ClientStats",1,["bulk::ClientStats"]]]],["certificate",[["impl Sync for SkipServerVerification",1,["certificate::SkipServerVerification"]]]],["noq",[["impl Sync for ReadError",1,["noq::recv_stream::ReadError"]],["impl Sync for ReadExactError",1,["noq::recv_stream::ReadExactError"]],["impl Sync for ReadToEndError",1,["noq::recv_stream::ReadToEndError"]],["impl Sync for ResetError",1,["noq::recv_stream::ResetError"]],["impl Sync for SendDatagramError",1,["noq::connection::SendDatagramError"]],["impl Sync for StoppedError",1,["noq::send_stream::StoppedError"]],["impl Sync for WriteError",1,["noq::send_stream::WriteError"]],["impl Sync for AddressDiscovery",1,["noq::path::AddressDiscovery"]],["impl Sync for Connecting",1,["noq::connection::Connecting"]],["impl Sync for Connection",1,["noq::connection::Connection"]],["impl Sync for Endpoint",1,["noq::endpoint::Endpoint"]],["impl Sync for EndpointStats",1,["noq::endpoint::EndpointStats"]],["impl Sync for Incoming",1,["noq::incoming::Incoming"]],["impl Sync for IncomingFuture",1,["noq::incoming::IncomingFuture"]],["impl Sync for Lagged",1,["noq::event_stream::Lagged"]],["impl Sync for NatTraversalUpdates",1,["noq::event_stream::NatTraversalUpdates"]],["impl Sync for ObservedExternalAddr",1,["noq::event_stream::ObservedExternalAddr"]],["impl Sync for OnClosed",1,["noq::connection::OnClosed"]],["impl Sync for OpenPath",1,["noq::path::OpenPath"]],["impl Sync for Path",1,["noq::path::Path"]],["impl Sync for PathEvents",1,["noq::event_stream::PathEvents"]],["impl Sync for RecvStream",1,["noq::recv_stream::RecvStream"]],["impl Sync for RetryError",1,["noq::incoming::RetryError"]],["impl Sync for SendStream",1,["noq::send_stream::SendStream"]],["impl Sync for SmolRuntime",1,["noq::runtime::smol::SmolRuntime"]],["impl Sync for Stopped",1,["noq::send_stream::Stopped"]],["impl Sync for TokioRuntime",1,["noq::runtime::tokio::TokioRuntime"]],["impl Sync for UnorderedRecvStream",1,["noq::recv_stream::UnorderedRecvStream"]],["impl Sync for WeakConnectionHandle",1,["noq::connection::WeakConnectionHandle"]],["impl Sync for WeakPathHandle",1,["noq::path::WeakPathHandle"]],["impl Sync for ZeroRttAccepted",1,["noq::connection::ZeroRttAccepted"]],["impl<'a> Sync for Accept<'a>",1,["noq::endpoint::Accept"]],["impl<'a> Sync for AcceptBi<'a>",1,["noq::connection::AcceptBi"]],["impl<'a> Sync for AcceptUni<'a>",1,["noq::connection::AcceptUni"]],["impl<'a> Sync for OpenBi<'a>",1,["noq::connection::OpenBi"]],["impl<'a> Sync for OpenUni<'a>",1,["noq::connection::OpenUni"]],["impl<'a> Sync for ReadDatagram<'a>",1,["noq::connection::ReadDatagram"]],["impl<'a> Sync for SendDatagram<'a>",1,["noq::connection::SendDatagram"]]]],["noq_perf",[["impl Sync for Commands",1,["noq_perf::Commands"]],["impl Sync for Cli",1,["noq_perf::Cli"]]]],["noq_proto",[["impl Sync for ClosePathError",1,["noq_proto::connection::ClosePathError"]],["impl Sync for ConfigError",1,["noq_proto::config::ConfigError"]],["impl Sync for ConnectError",1,["noq_proto::endpoint::ConnectError"]],["impl Sync for ConnectionError",1,["noq_proto::connection::ConnectionError"]],["impl Sync for DatagramEvent",1,["noq_proto::endpoint::DatagramEvent"]],["impl Sync for Dir",1,["noq_proto::Dir"]],["impl Sync for EcnCodepoint",1,["noq_proto::shared::EcnCodepoint"]],["impl Sync for Event",1,["noq_proto::connection::Event"]],["impl Sync for FinishError",1,["noq_proto::connection::streams::send::FinishError"]],["impl Sync for FrameType",1,["noq_proto::frame::FrameType"]],["impl Sync for LongType",1,["noq_proto::packet::LongType"]],["impl Sync for MaybeFrame",1,["noq_proto::frame::MaybeFrame"]],["impl Sync for PacketDecodeError",1,["noq_proto::packet::PacketDecodeError"]],["impl Sync for PathAbandonReason",1,["noq_proto::connection::paths::PathAbandonReason"]],["impl Sync for PathError",1,["noq_proto::connection::PathError"]],["impl Sync for PathEvent",1,["noq_proto::connection::paths::PathEvent"]],["impl Sync for PathStatus",1,["noq_proto::connection::paths::PathStatus"]],["impl Sync for ProtectedHeader",1,["noq_proto::packet::ProtectedHeader"]],["impl Sync for ReadError",1,["noq_proto::connection::streams::recv::ReadError"]],["impl Sync for ReadableError",1,["noq_proto::connection::streams::recv::ReadableError"]],["impl Sync for SendDatagramError",1,["noq_proto::connection::datagrams::SendDatagramError"]],["impl Sync for SetPathStatusError",1,["noq_proto::connection::paths::SetPathStatusError"]],["impl Sync for Side",1,["noq_proto::Side"]],["impl Sync for StreamEvent",1,["noq_proto::connection::streams::StreamEvent"]],["impl Sync for WriteError",1,["noq_proto::connection::streams::send::WriteError"]],["impl Sync for Error",1,["noq_proto::n0_nat_traversal::Error"]],["impl Sync for Event",1,["noq_proto::n0_nat_traversal::Event"]],["impl Sync for Error",1,["noq_proto::transport_parameters::Error"]],["impl Sync for UnexpectedEnd",1,["noq_proto::coding::UnexpectedEnd"]],["impl Sync for Bbr",1,["noq_proto::congestion::bbr::Bbr"]],["impl Sync for BbrConfig",1,["noq_proto::congestion::bbr::BbrConfig"]],["impl Sync for ControllerMetrics",1,["noq_proto::congestion::ControllerMetrics"]],["impl Sync for Cubic",1,["noq_proto::congestion::cubic::Cubic"]],["impl Sync for CubicConfig",1,["noq_proto::congestion::cubic::CubicConfig"]],["impl Sync for NewReno",1,["noq_proto::congestion::new_reno::NewReno"]],["impl Sync for NewRenoConfig",1,["noq_proto::congestion::new_reno::NewRenoConfig"]],["impl Sync for HandshakeData",1,["noq_proto::crypto::rustls::HandshakeData"]],["impl Sync for NoInitialCipherSuite",1,["noq_proto::crypto::rustls::NoInitialCipherSuite"]],["impl Sync for QuicClientConfig",1,["noq_proto::crypto::rustls::QuicClientConfig"]],["impl Sync for QuicServerConfig",1,["noq_proto::crypto::rustls::QuicServerConfig"]],["impl Sync for TlsSession",1,["noq_proto::crypto::rustls::TlsSession"]],["impl Sync for CryptoError",1,["noq_proto::crypto::CryptoError"]],["impl Sync for ExportKeyingMaterialError",1,["noq_proto::crypto::ExportKeyingMaterialError"]],["impl Sync for Keys",1,["noq_proto::crypto::Keys"]],["impl Sync for UnsupportedVersion",1,["noq_proto::crypto::UnsupportedVersion"]],["impl Sync for AcceptError",1,["noq_proto::endpoint::AcceptError"]],["impl Sync for AckFrequencyConfig",1,["noq_proto::config::transport::AckFrequencyConfig"]],["impl Sync for ApplicationClose",1,["noq_proto::frame::ApplicationClose"]],["impl Sync for BloomTokenLog",1,["noq_proto::bloom_token_log::BloomTokenLog"]],["impl Sync for Chunk",1,["noq_proto::connection::assembler::Chunk"]],["impl Sync for ClientConfig",1,["noq_proto::config::ClientConfig"]],["impl Sync for ClosedPath",1,["noq_proto::connection::paths::ClosedPath"]],["impl Sync for ClosedStream",1,["noq_proto::connection::streams::ClosedStream"]],["impl Sync for Connection",1,["noq_proto::connection::Connection"]],["impl Sync for ConnectionClose",1,["noq_proto::frame::ConnectionClose"]],["impl Sync for ConnectionEvent",1,["noq_proto::shared::ConnectionEvent"]],["impl Sync for ConnectionHandle",1,["noq_proto::endpoint::ConnectionHandle"]],["impl Sync for ConnectionId",1,["noq_proto::shared::ConnectionId"]],["impl Sync for ConnectionStats",1,["noq_proto::connection::stats::ConnectionStats"]],["impl Sync for Datagram",1,["noq_proto::frame::Datagram"]],["impl Sync for DatagramInfo",1,["noq_proto::frame::DatagramInfo"]],["impl Sync for DecryptedInitial",1,["noq_proto::endpoint::DecryptedInitial"]],["impl Sync for Endpoint",1,["noq_proto::endpoint::Endpoint"]],["impl Sync for EndpointConfig",1,["noq_proto::config::EndpointConfig"]],["impl Sync for EndpointEvent",1,["noq_proto::shared::EndpointEvent"]],["impl Sync for FixedLengthConnectionIdParser",1,["noq_proto::packet::FixedLengthConnectionIdParser"]],["impl Sync for FourTuple",1,["noq_proto::FourTuple"]],["impl Sync for FrameStats",1,["noq_proto::connection::stats::FrameStats"]],["impl Sync for HashedConnectionIdGenerator",1,["noq_proto::cid_generator::HashedConnectionIdGenerator"]],["impl Sync for IdleTimeout",1,["noq_proto::config::transport::IdleTimeout"]],["impl Sync for Incoming",1,["noq_proto::endpoint::Incoming"]],["impl Sync for IncomingAlpns",1,["noq_proto::endpoint::IncomingAlpns"]],["impl Sync for InvalidCid",1,["noq_proto::cid_generator::InvalidCid"]],["impl Sync for InvalidFrameId",1,["noq_proto::frame::InvalidFrameId"]],["impl Sync for MtuDiscoveryConfig",1,["noq_proto::config::transport::MtuDiscoveryConfig"]],["impl Sync for MultipathNotNegotiated",1,["noq_proto::connection::MultipathNotNegotiated"]],["impl Sync for NoneTokenLog",1,["noq_proto::token::NoneTokenLog"]],["impl Sync for NoneTokenStore",1,["noq_proto::token::NoneTokenStore"]],["impl Sync for PartialDecode",1,["noq_proto::packet::PartialDecode"]],["impl Sync for PathId",1,["noq_proto::connection::paths::PathId"]],["impl Sync for PathStats",1,["noq_proto::connection::stats::PathStats"]],["impl Sync for ProtectedInitialHeader",1,["noq_proto::packet::ProtectedInitialHeader"]],["impl Sync for QlogConfig",1,["noq_proto::config::qlog::QlogConfig"]],["impl Sync for QlogFileFactory",1,["noq_proto::config::qlog::QlogFileFactory"]],["impl Sync for RandomConnectionIdGenerator",1,["noq_proto::cid_generator::RandomConnectionIdGenerator"]],["impl Sync for RetryError",1,["noq_proto::endpoint::RetryError"]],["impl Sync for RttEstimator",1,["noq_proto::connection::paths::RttEstimator"]],["impl Sync for ServerConfig",1,["noq_proto::config::ServerConfig"]],["impl Sync for ShouldTransmit",1,["noq_proto::connection::streams::ShouldTransmit"]],["impl Sync for StdSystemTime",1,["noq_proto::config::StdSystemTime"]],["impl Sync for StreamId",1,["noq_proto::StreamId"]],["impl Sync for StreamInfo",1,["noq_proto::frame::StreamInfo"]],["impl Sync for TokenMemoryCache",1,["noq_proto::token_memory_cache::TokenMemoryCache"]],["impl Sync for TokenReuseError",1,["noq_proto::token::TokenReuseError"]],["impl Sync for Transmit",1,["noq_proto::Transmit"]],["impl Sync for TransportConfig",1,["noq_proto::config::transport::TransportConfig"]],["impl Sync for Error",1,["noq_proto::transport_error::Error"]],["impl Sync for Code",1,["noq_proto::transport_error::Code"]],["impl Sync for UdpStats",1,["noq_proto::connection::stats::UdpStats"]],["impl Sync for ValidationTokenConfig",1,["noq_proto::config::ValidationTokenConfig"]],["impl Sync for VarInt",1,["noq_proto::varint::VarInt"]],["impl Sync for VarIntBoundsExceeded",1,["noq_proto::varint::VarIntBoundsExceeded"]],["impl Sync for Written",1,["noq_proto::connection::streams::send::Written"]],["impl Sync for TransportParameters",1,["noq_proto::transport_parameters::TransportParameters"]],["impl<'a> Sync for Chunks<'a>",1,["noq_proto::connection::streams::recv::Chunks"]],["impl<'a> Sync for Datagrams<'a>",1,["noq_proto::connection::datagrams::Datagrams"]],["impl<'a> Sync for RecvStream<'a>",1,["noq_proto::connection::streams::RecvStream"]],["impl<'a> Sync for SendStream<'a>",1,["noq_proto::connection::streams::SendStream"]],["impl<'a> Sync for Streams<'a>",1,["noq_proto::connection::streams::Streams"]],["impl<T> Sync for KeyPair<T>
    where\n T: Sync,
    ",1,["noq_proto::crypto::KeyPair"]]]],["noq_udp",[["impl Sync for EcnCodepoint",1,["noq_udp::EcnCodepoint"]],["impl Sync for RecvMeta",1,["noq_udp::RecvMeta"]],["impl Sync for UdpSocketState",1,["noq_udp::imp::UdpSocketState"]],["impl<'a> Sync for Transmit<'a>",1,["noq_udp::Transmit"]],["impl<'a> Sync for UdpSockRef<'a>",1,["noq_udp::UdpSockRef"]]]],["perf",[["impl Sync for CongestionAlgorithm",1,["perf::CongestionAlgorithm"]],["impl Sync for Opt",1,["perf::client::Opt"]],["impl Sync for NoProtectionClientConfig",1,["perf::noprotection::NoProtectionClientConfig"]],["impl Sync for NoProtectionServerConfig",1,["perf::noprotection::NoProtectionServerConfig"]],["impl Sync for Opt",1,["perf::server::Opt"]],["impl Sync for OpenStreamStats",1,["perf::stats::OpenStreamStats"]],["impl Sync for Stats",1,["perf::stats::Stats"]],["impl Sync for StreamStats",1,["perf::stats::StreamStats"]],["impl Sync for CommonOpt",1,["perf::CommonOpt"]]]]]); + var implementors = Object.fromEntries([["bench",[["impl Sync for CipherSuite",1,["bench::CipherSuite"]],["impl Sync for RuntimeType",1,["bench::RuntimeType"]],["impl Sync for Stats",1,["bench::stats::Stats"]],["impl Sync for StreamStats",1,["bench::stats::StreamStats"]],["impl Sync for TransferResult",1,["bench::stats::TransferResult"]],["impl Sync for Opt",1,["bench::Opt"]]]],["bulk",[["impl Sync for ClientStats",1,["bulk::ClientStats"]]]],["certificate",[["impl Sync for SkipServerVerification",1,["certificate::SkipServerVerification"]]]],["noq",[["impl Sync for ReadError",1,["noq::recv_stream::ReadError"]],["impl Sync for ReadExactError",1,["noq::recv_stream::ReadExactError"]],["impl Sync for ReadToEndError",1,["noq::recv_stream::ReadToEndError"]],["impl Sync for ResetError",1,["noq::recv_stream::ResetError"]],["impl Sync for SendDatagramError",1,["noq::connection::SendDatagramError"]],["impl Sync for StoppedError",1,["noq::send_stream::StoppedError"]],["impl Sync for WriteError",1,["noq::send_stream::WriteError"]],["impl Sync for AddressDiscovery",1,["noq::path::AddressDiscovery"]],["impl Sync for Closed",1,["noq::connection::Closed"]],["impl Sync for Connecting",1,["noq::connection::Connecting"]],["impl Sync for Connection",1,["noq::connection::Connection"]],["impl Sync for Endpoint",1,["noq::endpoint::Endpoint"]],["impl Sync for EndpointStats",1,["noq::endpoint::EndpointStats"]],["impl Sync for Incoming",1,["noq::incoming::Incoming"]],["impl Sync for IncomingFuture",1,["noq::incoming::IncomingFuture"]],["impl Sync for Lagged",1,["noq::event_stream::Lagged"]],["impl Sync for NatTraversalUpdates",1,["noq::event_stream::NatTraversalUpdates"]],["impl Sync for ObservedExternalAddr",1,["noq::event_stream::ObservedExternalAddr"]],["impl Sync for OnClosed",1,["noq::connection::OnClosed"]],["impl Sync for OpenPath",1,["noq::path::OpenPath"]],["impl Sync for Path",1,["noq::path::Path"]],["impl Sync for PathEvents",1,["noq::event_stream::PathEvents"]],["impl Sync for RecvStream",1,["noq::recv_stream::RecvStream"]],["impl Sync for RetryError",1,["noq::incoming::RetryError"]],["impl Sync for SendStream",1,["noq::send_stream::SendStream"]],["impl Sync for SmolRuntime",1,["noq::runtime::smol::SmolRuntime"]],["impl Sync for Stopped",1,["noq::send_stream::Stopped"]],["impl Sync for TokioRuntime",1,["noq::runtime::tokio::TokioRuntime"]],["impl Sync for UnorderedRecvStream",1,["noq::recv_stream::UnorderedRecvStream"]],["impl Sync for WeakConnectionHandle",1,["noq::connection::WeakConnectionHandle"]],["impl Sync for WeakPathHandle",1,["noq::path::WeakPathHandle"]],["impl Sync for ZeroRttAccepted",1,["noq::connection::ZeroRttAccepted"]],["impl<'a> Sync for Accept<'a>",1,["noq::endpoint::Accept"]],["impl<'a> Sync for AcceptBi<'a>",1,["noq::connection::AcceptBi"]],["impl<'a> Sync for AcceptUni<'a>",1,["noq::connection::AcceptUni"]],["impl<'a> Sync for OpenBi<'a>",1,["noq::connection::OpenBi"]],["impl<'a> Sync for OpenUni<'a>",1,["noq::connection::OpenUni"]],["impl<'a> Sync for ReadDatagram<'a>",1,["noq::connection::ReadDatagram"]],["impl<'a> Sync for SendDatagram<'a>",1,["noq::connection::SendDatagram"]]]],["noq_perf",[["impl Sync for Commands",1,["noq_perf::Commands"]],["impl Sync for Cli",1,["noq_perf::Cli"]]]],["noq_proto",[["impl Sync for ClosePathError",1,["noq_proto::connection::ClosePathError"]],["impl Sync for ConfigError",1,["noq_proto::config::ConfigError"]],["impl Sync for ConnectError",1,["noq_proto::endpoint::ConnectError"]],["impl Sync for ConnectionError",1,["noq_proto::connection::ConnectionError"]],["impl Sync for DatagramEvent",1,["noq_proto::endpoint::DatagramEvent"]],["impl Sync for Dir",1,["noq_proto::Dir"]],["impl Sync for EcnCodepoint",1,["noq_proto::shared::EcnCodepoint"]],["impl Sync for Event",1,["noq_proto::connection::Event"]],["impl Sync for FinishError",1,["noq_proto::connection::streams::send::FinishError"]],["impl Sync for FrameType",1,["noq_proto::frame::FrameType"]],["impl Sync for LongType",1,["noq_proto::packet::LongType"]],["impl Sync for MaybeFrame",1,["noq_proto::frame::MaybeFrame"]],["impl Sync for PacketDecodeError",1,["noq_proto::packet::PacketDecodeError"]],["impl Sync for PathAbandonReason",1,["noq_proto::connection::paths::PathAbandonReason"]],["impl Sync for PathError",1,["noq_proto::connection::PathError"]],["impl Sync for PathEvent",1,["noq_proto::connection::paths::PathEvent"]],["impl Sync for PathStatus",1,["noq_proto::connection::paths::PathStatus"]],["impl Sync for ProtectedHeader",1,["noq_proto::packet::ProtectedHeader"]],["impl Sync for ReadError",1,["noq_proto::connection::streams::recv::ReadError"]],["impl Sync for ReadableError",1,["noq_proto::connection::streams::recv::ReadableError"]],["impl Sync for SendDatagramError",1,["noq_proto::connection::datagrams::SendDatagramError"]],["impl Sync for SetPathStatusError",1,["noq_proto::connection::paths::SetPathStatusError"]],["impl Sync for Side",1,["noq_proto::Side"]],["impl Sync for StreamEvent",1,["noq_proto::connection::streams::StreamEvent"]],["impl Sync for WriteError",1,["noq_proto::connection::streams::send::WriteError"]],["impl Sync for Error",1,["noq_proto::n0_nat_traversal::Error"]],["impl Sync for Event",1,["noq_proto::n0_nat_traversal::Event"]],["impl Sync for Error",1,["noq_proto::transport_parameters::Error"]],["impl Sync for UnexpectedEnd",1,["noq_proto::coding::UnexpectedEnd"]],["impl Sync for Bbr3",1,["noq_proto::congestion::bbr3::Bbr3"]],["impl Sync for Bbr3Config",1,["noq_proto::congestion::bbr3::Bbr3Config"]],["impl Sync for ControllerMetrics",1,["noq_proto::congestion::ControllerMetrics"]],["impl Sync for Cubic",1,["noq_proto::congestion::cubic::Cubic"]],["impl Sync for CubicConfig",1,["noq_proto::congestion::cubic::CubicConfig"]],["impl Sync for NewReno",1,["noq_proto::congestion::new_reno::NewReno"]],["impl Sync for NewRenoConfig",1,["noq_proto::congestion::new_reno::NewRenoConfig"]],["impl Sync for HandshakeData",1,["noq_proto::crypto::rustls::HandshakeData"]],["impl Sync for NoInitialCipherSuite",1,["noq_proto::crypto::rustls::NoInitialCipherSuite"]],["impl Sync for QuicClientConfig",1,["noq_proto::crypto::rustls::QuicClientConfig"]],["impl Sync for QuicServerConfig",1,["noq_proto::crypto::rustls::QuicServerConfig"]],["impl Sync for TlsSession",1,["noq_proto::crypto::rustls::TlsSession"]],["impl Sync for CryptoError",1,["noq_proto::crypto::CryptoError"]],["impl Sync for ExportKeyingMaterialError",1,["noq_proto::crypto::ExportKeyingMaterialError"]],["impl Sync for Keys",1,["noq_proto::crypto::Keys"]],["impl Sync for UnsupportedVersion",1,["noq_proto::crypto::UnsupportedVersion"]],["impl Sync for AcceptError",1,["noq_proto::endpoint::AcceptError"]],["impl Sync for AckFrequencyConfig",1,["noq_proto::config::transport::AckFrequencyConfig"]],["impl Sync for ApplicationClose",1,["noq_proto::frame::ApplicationClose"]],["impl Sync for BloomTokenLog",1,["noq_proto::bloom_token_log::BloomTokenLog"]],["impl Sync for Chunk",1,["noq_proto::connection::assembler::Chunk"]],["impl Sync for ClientConfig",1,["noq_proto::config::ClientConfig"]],["impl Sync for ClosedPath",1,["noq_proto::connection::paths::ClosedPath"]],["impl Sync for ClosedStream",1,["noq_proto::connection::streams::ClosedStream"]],["impl Sync for Connection",1,["noq_proto::connection::Connection"]],["impl Sync for ConnectionClose",1,["noq_proto::frame::ConnectionClose"]],["impl Sync for ConnectionEvent",1,["noq_proto::shared::ConnectionEvent"]],["impl Sync for ConnectionHandle",1,["noq_proto::endpoint::ConnectionHandle"]],["impl Sync for ConnectionId",1,["noq_proto::shared::ConnectionId"]],["impl Sync for ConnectionStats",1,["noq_proto::connection::stats::ConnectionStats"]],["impl Sync for Datagram",1,["noq_proto::frame::Datagram"]],["impl Sync for DatagramInfo",1,["noq_proto::frame::DatagramInfo"]],["impl Sync for DecryptedInitial",1,["noq_proto::endpoint::DecryptedInitial"]],["impl Sync for Endpoint",1,["noq_proto::endpoint::Endpoint"]],["impl Sync for EndpointConfig",1,["noq_proto::config::EndpointConfig"]],["impl Sync for EndpointEvent",1,["noq_proto::shared::EndpointEvent"]],["impl Sync for FixedLengthConnectionIdParser",1,["noq_proto::packet::FixedLengthConnectionIdParser"]],["impl Sync for FourTuple",1,["noq_proto::FourTuple"]],["impl Sync for FrameStats",1,["noq_proto::connection::stats::FrameStats"]],["impl Sync for HashedConnectionIdGenerator",1,["noq_proto::cid_generator::HashedConnectionIdGenerator"]],["impl Sync for IdleTimeout",1,["noq_proto::config::transport::IdleTimeout"]],["impl Sync for Incoming",1,["noq_proto::endpoint::Incoming"]],["impl Sync for IncomingAlpns",1,["noq_proto::endpoint::IncomingAlpns"]],["impl Sync for InvalidCid",1,["noq_proto::cid_generator::InvalidCid"]],["impl Sync for InvalidFrameId",1,["noq_proto::frame::InvalidFrameId"]],["impl Sync for MtuDiscoveryConfig",1,["noq_proto::config::transport::MtuDiscoveryConfig"]],["impl Sync for MultipathNotNegotiated",1,["noq_proto::connection::MultipathNotNegotiated"]],["impl Sync for NoneTokenLog",1,["noq_proto::token::NoneTokenLog"]],["impl Sync for NoneTokenStore",1,["noq_proto::token::NoneTokenStore"]],["impl Sync for PartialDecode",1,["noq_proto::packet::PartialDecode"]],["impl Sync for PathId",1,["noq_proto::connection::paths::PathId"]],["impl Sync for PathStats",1,["noq_proto::connection::stats::PathStats"]],["impl Sync for ProtectedInitialHeader",1,["noq_proto::packet::ProtectedInitialHeader"]],["impl Sync for QlogConfig",1,["noq_proto::config::qlog::QlogConfig"]],["impl Sync for QlogFileFactory",1,["noq_proto::config::qlog::QlogFileFactory"]],["impl Sync for RandomConnectionIdGenerator",1,["noq_proto::cid_generator::RandomConnectionIdGenerator"]],["impl Sync for RetryError",1,["noq_proto::endpoint::RetryError"]],["impl Sync for RttEstimator",1,["noq_proto::connection::paths::RttEstimator"]],["impl Sync for ServerConfig",1,["noq_proto::config::ServerConfig"]],["impl Sync for ShouldTransmit",1,["noq_proto::connection::streams::ShouldTransmit"]],["impl Sync for StdSystemTime",1,["noq_proto::config::StdSystemTime"]],["impl Sync for StreamId",1,["noq_proto::StreamId"]],["impl Sync for StreamInfo",1,["noq_proto::frame::StreamInfo"]],["impl Sync for TokenMemoryCache",1,["noq_proto::token_memory_cache::TokenMemoryCache"]],["impl Sync for TokenReuseError",1,["noq_proto::token::TokenReuseError"]],["impl Sync for Transmit",1,["noq_proto::Transmit"]],["impl Sync for TransportConfig",1,["noq_proto::config::transport::TransportConfig"]],["impl Sync for Error",1,["noq_proto::transport_error::Error"]],["impl Sync for Code",1,["noq_proto::transport_error::Code"]],["impl Sync for UdpStats",1,["noq_proto::connection::stats::UdpStats"]],["impl Sync for ValidationTokenConfig",1,["noq_proto::config::ValidationTokenConfig"]],["impl Sync for VarInt",1,["noq_proto::varint::VarInt"]],["impl Sync for VarIntBoundsExceeded",1,["noq_proto::varint::VarIntBoundsExceeded"]],["impl Sync for TransportParameters",1,["noq_proto::transport_parameters::TransportParameters"]],["impl<'a> Sync for Chunks<'a>",1,["noq_proto::connection::streams::recv::Chunks"]],["impl<'a> Sync for Datagrams<'a>",1,["noq_proto::connection::datagrams::Datagrams"]],["impl<'a> Sync for RecvStream<'a>",1,["noq_proto::connection::streams::RecvStream"]],["impl<'a> Sync for SendStream<'a>",1,["noq_proto::connection::streams::SendStream"]],["impl<'a> Sync for Streams<'a>",1,["noq_proto::connection::streams::Streams"]],["impl<T> Sync for KeyPair<T>
    where\n T: Sync,
    ",1,["noq_proto::crypto::KeyPair"]]]],["noq_udp",[["impl Sync for EcnCodepoint",1,["noq_udp::EcnCodepoint"]],["impl Sync for RecvMeta",1,["noq_udp::RecvMeta"]],["impl Sync for UdpSocketState",1,["noq_udp::imp::UdpSocketState"]],["impl<'a> Sync for Transmit<'a>",1,["noq_udp::Transmit"]],["impl<'a> Sync for UdpSockRef<'a>",1,["noq_udp::UdpSockRef"]]]],["perf",[["impl Sync for CongestionAlgorithm",1,["perf::CongestionAlgorithm"]],["impl Sync for Opt",1,["perf::client::Opt"]],["impl Sync for NoProtectionClientConfig",1,["perf::noprotection::NoProtectionClientConfig"]],["impl Sync for NoProtectionServerConfig",1,["perf::noprotection::NoProtectionServerConfig"]],["impl Sync for Opt",1,["perf::server::Opt"]],["impl Sync for OpenStreamStats",1,["perf::stats::OpenStreamStats"]],["impl Sync for Stats",1,["perf::stats::Stats"]],["impl Sync for StreamStats",1,["perf::stats::StreamStats"]],["impl Sync for CommonOpt",1,["perf::CommonOpt"]]]]]); if (window.register_implementors) { window.register_implementors(implementors); } else { window.pending_implementors = implementors; } })() -//{"start":57,"fragment_lengths":[1713,291,363,11119,549,35179,1486,2769]} \ No newline at end of file +//{"start":57,"fragment_lengths":[1713,291,363,11388,549,34883,1486,2769]} \ No newline at end of file diff --git a/pr/451/docs/trait.impl/core/marker/trait.Unpin.js b/pr/451/docs/trait.impl/core/marker/trait.Unpin.js index ab667fe44..9bb8b5955 100644 --- a/pr/451/docs/trait.impl/core/marker/trait.Unpin.js +++ b/pr/451/docs/trait.impl/core/marker/trait.Unpin.js @@ -1,9 +1,9 @@ (function() { - var implementors = Object.fromEntries([["bench",[["impl Unpin for CipherSuite",1,["bench::CipherSuite"]],["impl Unpin for RuntimeType",1,["bench::RuntimeType"]],["impl Unpin for Stats",1,["bench::stats::Stats"]],["impl Unpin for StreamStats",1,["bench::stats::StreamStats"]],["impl Unpin for TransferResult",1,["bench::stats::TransferResult"]],["impl Unpin for Opt",1,["bench::Opt"]]]],["bulk",[["impl Unpin for ClientStats",1,["bulk::ClientStats"]]]],["certificate",[["impl Unpin for SkipServerVerification",1,["certificate::SkipServerVerification"]]]],["noq",[["impl Unpin for ReadError",1,["noq::recv_stream::ReadError"]],["impl Unpin for ReadExactError",1,["noq::recv_stream::ReadExactError"]],["impl Unpin for ReadToEndError",1,["noq::recv_stream::ReadToEndError"]],["impl Unpin for ResetError",1,["noq::recv_stream::ResetError"]],["impl Unpin for SendDatagramError",1,["noq::connection::SendDatagramError"]],["impl Unpin for StoppedError",1,["noq::send_stream::StoppedError"]],["impl Unpin for WriteError",1,["noq::send_stream::WriteError"]],["impl Unpin for AddressDiscovery",1,["noq::path::AddressDiscovery"]],["impl Unpin for Connecting",1,["noq::connection::Connecting"]],["impl Unpin for Connection",1,["noq::connection::Connection"]],["impl Unpin for Endpoint",1,["noq::endpoint::Endpoint"]],["impl Unpin for EndpointStats",1,["noq::endpoint::EndpointStats"]],["impl Unpin for Incoming",1,["noq::incoming::Incoming"]],["impl Unpin for IncomingFuture",1,["noq::incoming::IncomingFuture"]],["impl Unpin for Lagged",1,["noq::event_stream::Lagged"]],["impl Unpin for NatTraversalUpdates",1,["noq::event_stream::NatTraversalUpdates"]],["impl Unpin for ObservedExternalAddr",1,["noq::event_stream::ObservedExternalAddr"]],["impl Unpin for OnClosed",1,["noq::connection::OnClosed"]],["impl Unpin for OpenPath",1,["noq::path::OpenPath"]],["impl Unpin for Path",1,["noq::path::Path"]],["impl Unpin for PathEvents",1,["noq::event_stream::PathEvents"]],["impl Unpin for RecvStream",1,["noq::recv_stream::RecvStream"]],["impl Unpin for RetryError",1,["noq::incoming::RetryError"]],["impl Unpin for SendStream",1,["noq::send_stream::SendStream"]],["impl Unpin for SmolRuntime",1,["noq::runtime::smol::SmolRuntime"]],["impl Unpin for TokioRuntime",1,["noq::runtime::tokio::TokioRuntime"]],["impl Unpin for UnorderedRecvStream",1,["noq::recv_stream::UnorderedRecvStream"]],["impl Unpin for WeakConnectionHandle",1,["noq::connection::WeakConnectionHandle"]],["impl Unpin for WeakPathHandle",1,["noq::path::WeakPathHandle"]],["impl Unpin for ZeroRttAccepted",1,["noq::connection::ZeroRttAccepted"]],["impl<'__pin> Unpin for Stopped
    where\n PinnedFieldsOf<__Origin<'__pin>>: Unpin,
    "],["impl<'__pin, 'a> Unpin for Accept<'a>
    where\n PinnedFieldsOf<__Origin<'__pin, 'a>>: Unpin,
    "],["impl<'__pin, 'a> Unpin for AcceptBi<'a>
    where\n PinnedFieldsOf<__Origin<'__pin, 'a>>: Unpin,
    "],["impl<'__pin, 'a> Unpin for AcceptUni<'a>
    where\n PinnedFieldsOf<__Origin<'__pin, 'a>>: Unpin,
    "],["impl<'__pin, 'a> Unpin for OpenBi<'a>
    where\n PinnedFieldsOf<__Origin<'__pin, 'a>>: Unpin,
    "],["impl<'__pin, 'a> Unpin for OpenUni<'a>
    where\n PinnedFieldsOf<__Origin<'__pin, 'a>>: Unpin,
    "],["impl<'__pin, 'a> Unpin for ReadDatagram<'a>
    where\n PinnedFieldsOf<__Origin<'__pin, 'a>>: Unpin,
    "],["impl<'__pin, 'a> Unpin for SendDatagram<'a>
    where\n PinnedFieldsOf<__Origin<'__pin, 'a>>: Unpin,
    "]]],["noq_perf",[["impl Unpin for Commands",1,["noq_perf::Commands"]],["impl Unpin for Cli",1,["noq_perf::Cli"]]]],["noq_proto",[["impl Unpin for ClosePathError",1,["noq_proto::connection::ClosePathError"]],["impl Unpin for ConfigError",1,["noq_proto::config::ConfigError"]],["impl Unpin for ConnectError",1,["noq_proto::endpoint::ConnectError"]],["impl Unpin for ConnectionError",1,["noq_proto::connection::ConnectionError"]],["impl Unpin for DatagramEvent",1,["noq_proto::endpoint::DatagramEvent"]],["impl Unpin for Dir",1,["noq_proto::Dir"]],["impl Unpin for EcnCodepoint",1,["noq_proto::shared::EcnCodepoint"]],["impl Unpin for Event",1,["noq_proto::connection::Event"]],["impl Unpin for FinishError",1,["noq_proto::connection::streams::send::FinishError"]],["impl Unpin for FrameType",1,["noq_proto::frame::FrameType"]],["impl Unpin for LongType",1,["noq_proto::packet::LongType"]],["impl Unpin for MaybeFrame",1,["noq_proto::frame::MaybeFrame"]],["impl Unpin for PacketDecodeError",1,["noq_proto::packet::PacketDecodeError"]],["impl Unpin for PathAbandonReason",1,["noq_proto::connection::paths::PathAbandonReason"]],["impl Unpin for PathError",1,["noq_proto::connection::PathError"]],["impl Unpin for PathEvent",1,["noq_proto::connection::paths::PathEvent"]],["impl Unpin for PathStatus",1,["noq_proto::connection::paths::PathStatus"]],["impl Unpin for ProtectedHeader",1,["noq_proto::packet::ProtectedHeader"]],["impl Unpin for ReadError",1,["noq_proto::connection::streams::recv::ReadError"]],["impl Unpin for ReadableError",1,["noq_proto::connection::streams::recv::ReadableError"]],["impl Unpin for SendDatagramError",1,["noq_proto::connection::datagrams::SendDatagramError"]],["impl Unpin for SetPathStatusError",1,["noq_proto::connection::paths::SetPathStatusError"]],["impl Unpin for Side",1,["noq_proto::Side"]],["impl Unpin for StreamEvent",1,["noq_proto::connection::streams::StreamEvent"]],["impl Unpin for WriteError",1,["noq_proto::connection::streams::send::WriteError"]],["impl Unpin for Error",1,["noq_proto::n0_nat_traversal::Error"]],["impl Unpin for Event",1,["noq_proto::n0_nat_traversal::Event"]],["impl Unpin for Error",1,["noq_proto::transport_parameters::Error"]],["impl Unpin for UnexpectedEnd",1,["noq_proto::coding::UnexpectedEnd"]],["impl Unpin for Bbr",1,["noq_proto::congestion::bbr::Bbr"]],["impl Unpin for BbrConfig",1,["noq_proto::congestion::bbr::BbrConfig"]],["impl Unpin for ControllerMetrics",1,["noq_proto::congestion::ControllerMetrics"]],["impl Unpin for Cubic",1,["noq_proto::congestion::cubic::Cubic"]],["impl Unpin for CubicConfig",1,["noq_proto::congestion::cubic::CubicConfig"]],["impl Unpin for NewReno",1,["noq_proto::congestion::new_reno::NewReno"]],["impl Unpin for NewRenoConfig",1,["noq_proto::congestion::new_reno::NewRenoConfig"]],["impl Unpin for HandshakeData",1,["noq_proto::crypto::rustls::HandshakeData"]],["impl Unpin for NoInitialCipherSuite",1,["noq_proto::crypto::rustls::NoInitialCipherSuite"]],["impl Unpin for QuicClientConfig",1,["noq_proto::crypto::rustls::QuicClientConfig"]],["impl Unpin for QuicServerConfig",1,["noq_proto::crypto::rustls::QuicServerConfig"]],["impl Unpin for TlsSession",1,["noq_proto::crypto::rustls::TlsSession"]],["impl Unpin for CryptoError",1,["noq_proto::crypto::CryptoError"]],["impl Unpin for ExportKeyingMaterialError",1,["noq_proto::crypto::ExportKeyingMaterialError"]],["impl Unpin for Keys",1,["noq_proto::crypto::Keys"]],["impl Unpin for UnsupportedVersion",1,["noq_proto::crypto::UnsupportedVersion"]],["impl Unpin for AcceptError",1,["noq_proto::endpoint::AcceptError"]],["impl Unpin for AckFrequencyConfig",1,["noq_proto::config::transport::AckFrequencyConfig"]],["impl Unpin for ApplicationClose",1,["noq_proto::frame::ApplicationClose"]],["impl Unpin for BloomTokenLog",1,["noq_proto::bloom_token_log::BloomTokenLog"]],["impl Unpin for Chunk",1,["noq_proto::connection::assembler::Chunk"]],["impl Unpin for ClientConfig",1,["noq_proto::config::ClientConfig"]],["impl Unpin for ClosedPath",1,["noq_proto::connection::paths::ClosedPath"]],["impl Unpin for ClosedStream",1,["noq_proto::connection::streams::ClosedStream"]],["impl Unpin for Connection",1,["noq_proto::connection::Connection"]],["impl Unpin for ConnectionClose",1,["noq_proto::frame::ConnectionClose"]],["impl Unpin for ConnectionEvent",1,["noq_proto::shared::ConnectionEvent"]],["impl Unpin for ConnectionHandle",1,["noq_proto::endpoint::ConnectionHandle"]],["impl Unpin for ConnectionId",1,["noq_proto::shared::ConnectionId"]],["impl Unpin for ConnectionStats",1,["noq_proto::connection::stats::ConnectionStats"]],["impl Unpin for Datagram",1,["noq_proto::frame::Datagram"]],["impl Unpin for DatagramInfo",1,["noq_proto::frame::DatagramInfo"]],["impl Unpin for DecryptedInitial",1,["noq_proto::endpoint::DecryptedInitial"]],["impl Unpin for Endpoint",1,["noq_proto::endpoint::Endpoint"]],["impl Unpin for EndpointConfig",1,["noq_proto::config::EndpointConfig"]],["impl Unpin for EndpointEvent",1,["noq_proto::shared::EndpointEvent"]],["impl Unpin for FixedLengthConnectionIdParser",1,["noq_proto::packet::FixedLengthConnectionIdParser"]],["impl Unpin for FourTuple",1,["noq_proto::FourTuple"]],["impl Unpin for FrameStats",1,["noq_proto::connection::stats::FrameStats"]],["impl Unpin for HashedConnectionIdGenerator",1,["noq_proto::cid_generator::HashedConnectionIdGenerator"]],["impl Unpin for IdleTimeout",1,["noq_proto::config::transport::IdleTimeout"]],["impl Unpin for Incoming",1,["noq_proto::endpoint::Incoming"]],["impl Unpin for IncomingAlpns",1,["noq_proto::endpoint::IncomingAlpns"]],["impl Unpin for InvalidCid",1,["noq_proto::cid_generator::InvalidCid"]],["impl Unpin for InvalidFrameId",1,["noq_proto::frame::InvalidFrameId"]],["impl Unpin for MtuDiscoveryConfig",1,["noq_proto::config::transport::MtuDiscoveryConfig"]],["impl Unpin for MultipathNotNegotiated",1,["noq_proto::connection::MultipathNotNegotiated"]],["impl Unpin for NoneTokenLog",1,["noq_proto::token::NoneTokenLog"]],["impl Unpin for NoneTokenStore",1,["noq_proto::token::NoneTokenStore"]],["impl Unpin for PartialDecode",1,["noq_proto::packet::PartialDecode"]],["impl Unpin for PathId",1,["noq_proto::connection::paths::PathId"]],["impl Unpin for PathStats",1,["noq_proto::connection::stats::PathStats"]],["impl Unpin for ProtectedInitialHeader",1,["noq_proto::packet::ProtectedInitialHeader"]],["impl Unpin for QlogConfig",1,["noq_proto::config::qlog::QlogConfig"]],["impl Unpin for QlogFileFactory",1,["noq_proto::config::qlog::QlogFileFactory"]],["impl Unpin for RandomConnectionIdGenerator",1,["noq_proto::cid_generator::RandomConnectionIdGenerator"]],["impl Unpin for RetryError",1,["noq_proto::endpoint::RetryError"]],["impl Unpin for RttEstimator",1,["noq_proto::connection::paths::RttEstimator"]],["impl Unpin for ServerConfig",1,["noq_proto::config::ServerConfig"]],["impl Unpin for ShouldTransmit",1,["noq_proto::connection::streams::ShouldTransmit"]],["impl Unpin for StdSystemTime",1,["noq_proto::config::StdSystemTime"]],["impl Unpin for StreamId",1,["noq_proto::StreamId"]],["impl Unpin for StreamInfo",1,["noq_proto::frame::StreamInfo"]],["impl Unpin for TokenMemoryCache",1,["noq_proto::token_memory_cache::TokenMemoryCache"]],["impl Unpin for TokenReuseError",1,["noq_proto::token::TokenReuseError"]],["impl Unpin for Transmit",1,["noq_proto::Transmit"]],["impl Unpin for TransportConfig",1,["noq_proto::config::transport::TransportConfig"]],["impl Unpin for Error",1,["noq_proto::transport_error::Error"]],["impl Unpin for Code",1,["noq_proto::transport_error::Code"]],["impl Unpin for UdpStats",1,["noq_proto::connection::stats::UdpStats"]],["impl Unpin for ValidationTokenConfig",1,["noq_proto::config::ValidationTokenConfig"]],["impl Unpin for VarInt",1,["noq_proto::varint::VarInt"]],["impl Unpin for VarIntBoundsExceeded",1,["noq_proto::varint::VarIntBoundsExceeded"]],["impl Unpin for Written",1,["noq_proto::connection::streams::send::Written"]],["impl Unpin for TransportParameters",1,["noq_proto::transport_parameters::TransportParameters"]],["impl<'a> Unpin for Chunks<'a>",1,["noq_proto::connection::streams::recv::Chunks"]],["impl<'a> Unpin for Datagrams<'a>",1,["noq_proto::connection::datagrams::Datagrams"]],["impl<'a> Unpin for RecvStream<'a>",1,["noq_proto::connection::streams::RecvStream"]],["impl<'a> Unpin for SendStream<'a>",1,["noq_proto::connection::streams::SendStream"]],["impl<'a> Unpin for Streams<'a>",1,["noq_proto::connection::streams::Streams"]],["impl<T> Unpin for KeyPair<T>
    where\n T: Unpin,
    ",1,["noq_proto::crypto::KeyPair"]]]],["noq_udp",[["impl Unpin for EcnCodepoint",1,["noq_udp::EcnCodepoint"]],["impl Unpin for RecvMeta",1,["noq_udp::RecvMeta"]],["impl Unpin for UdpSocketState",1,["noq_udp::imp::UdpSocketState"]],["impl<'a> Unpin for Transmit<'a>",1,["noq_udp::Transmit"]],["impl<'a> Unpin for UdpSockRef<'a>",1,["noq_udp::UdpSockRef"]]]],["perf",[["impl Unpin for CongestionAlgorithm",1,["perf::CongestionAlgorithm"]],["impl Unpin for Opt",1,["perf::client::Opt"]],["impl Unpin for NoProtectionClientConfig",1,["perf::noprotection::NoProtectionClientConfig"]],["impl Unpin for NoProtectionServerConfig",1,["perf::noprotection::NoProtectionServerConfig"]],["impl Unpin for Opt",1,["perf::server::Opt"]],["impl Unpin for OpenStreamStats",1,["perf::stats::OpenStreamStats"]],["impl Unpin for Stats",1,["perf::stats::Stats"]],["impl Unpin for StreamStats",1,["perf::stats::StreamStats"]],["impl Unpin for CommonOpt",1,["perf::CommonOpt"]]]]]); + var implementors = Object.fromEntries([["bench",[["impl Unpin for CipherSuite",1,["bench::CipherSuite"]],["impl Unpin for RuntimeType",1,["bench::RuntimeType"]],["impl Unpin for Stats",1,["bench::stats::Stats"]],["impl Unpin for StreamStats",1,["bench::stats::StreamStats"]],["impl Unpin for TransferResult",1,["bench::stats::TransferResult"]],["impl Unpin for Opt",1,["bench::Opt"]]]],["bulk",[["impl Unpin for ClientStats",1,["bulk::ClientStats"]]]],["certificate",[["impl Unpin for SkipServerVerification",1,["certificate::SkipServerVerification"]]]],["noq",[["impl Unpin for ReadError",1,["noq::recv_stream::ReadError"]],["impl Unpin for ReadExactError",1,["noq::recv_stream::ReadExactError"]],["impl Unpin for ReadToEndError",1,["noq::recv_stream::ReadToEndError"]],["impl Unpin for ResetError",1,["noq::recv_stream::ResetError"]],["impl Unpin for SendDatagramError",1,["noq::connection::SendDatagramError"]],["impl Unpin for StoppedError",1,["noq::send_stream::StoppedError"]],["impl Unpin for WriteError",1,["noq::send_stream::WriteError"]],["impl Unpin for AddressDiscovery",1,["noq::path::AddressDiscovery"]],["impl Unpin for Closed",1,["noq::connection::Closed"]],["impl Unpin for Connecting",1,["noq::connection::Connecting"]],["impl Unpin for Connection",1,["noq::connection::Connection"]],["impl Unpin for Endpoint",1,["noq::endpoint::Endpoint"]],["impl Unpin for EndpointStats",1,["noq::endpoint::EndpointStats"]],["impl Unpin for Incoming",1,["noq::incoming::Incoming"]],["impl Unpin for IncomingFuture",1,["noq::incoming::IncomingFuture"]],["impl Unpin for Lagged",1,["noq::event_stream::Lagged"]],["impl Unpin for NatTraversalUpdates",1,["noq::event_stream::NatTraversalUpdates"]],["impl Unpin for ObservedExternalAddr",1,["noq::event_stream::ObservedExternalAddr"]],["impl Unpin for OnClosed",1,["noq::connection::OnClosed"]],["impl Unpin for OpenPath",1,["noq::path::OpenPath"]],["impl Unpin for Path",1,["noq::path::Path"]],["impl Unpin for PathEvents",1,["noq::event_stream::PathEvents"]],["impl Unpin for RecvStream",1,["noq::recv_stream::RecvStream"]],["impl Unpin for RetryError",1,["noq::incoming::RetryError"]],["impl Unpin for SendStream",1,["noq::send_stream::SendStream"]],["impl Unpin for SmolRuntime",1,["noq::runtime::smol::SmolRuntime"]],["impl Unpin for TokioRuntime",1,["noq::runtime::tokio::TokioRuntime"]],["impl Unpin for UnorderedRecvStream",1,["noq::recv_stream::UnorderedRecvStream"]],["impl Unpin for WeakConnectionHandle",1,["noq::connection::WeakConnectionHandle"]],["impl Unpin for WeakPathHandle",1,["noq::path::WeakPathHandle"]],["impl Unpin for ZeroRttAccepted",1,["noq::connection::ZeroRttAccepted"]],["impl<'__pin> Unpin for Stopped
    where\n PinnedFieldsOf<__Origin<'__pin>>: Unpin,
    "],["impl<'__pin, 'a> Unpin for Accept<'a>
    where\n PinnedFieldsOf<__Origin<'__pin, 'a>>: Unpin,
    "],["impl<'__pin, 'a> Unpin for AcceptBi<'a>
    where\n PinnedFieldsOf<__Origin<'__pin, 'a>>: Unpin,
    "],["impl<'__pin, 'a> Unpin for AcceptUni<'a>
    where\n PinnedFieldsOf<__Origin<'__pin, 'a>>: Unpin,
    "],["impl<'__pin, 'a> Unpin for OpenBi<'a>
    where\n PinnedFieldsOf<__Origin<'__pin, 'a>>: Unpin,
    "],["impl<'__pin, 'a> Unpin for OpenUni<'a>
    where\n PinnedFieldsOf<__Origin<'__pin, 'a>>: Unpin,
    "],["impl<'__pin, 'a> Unpin for ReadDatagram<'a>
    where\n PinnedFieldsOf<__Origin<'__pin, 'a>>: Unpin,
    "],["impl<'__pin, 'a> Unpin for SendDatagram<'a>
    where\n PinnedFieldsOf<__Origin<'__pin, 'a>>: Unpin,
    "]]],["noq_perf",[["impl Unpin for Commands",1,["noq_perf::Commands"]],["impl Unpin for Cli",1,["noq_perf::Cli"]]]],["noq_proto",[["impl Unpin for ClosePathError",1,["noq_proto::connection::ClosePathError"]],["impl Unpin for ConfigError",1,["noq_proto::config::ConfigError"]],["impl Unpin for ConnectError",1,["noq_proto::endpoint::ConnectError"]],["impl Unpin for ConnectionError",1,["noq_proto::connection::ConnectionError"]],["impl Unpin for DatagramEvent",1,["noq_proto::endpoint::DatagramEvent"]],["impl Unpin for Dir",1,["noq_proto::Dir"]],["impl Unpin for EcnCodepoint",1,["noq_proto::shared::EcnCodepoint"]],["impl Unpin for Event",1,["noq_proto::connection::Event"]],["impl Unpin for FinishError",1,["noq_proto::connection::streams::send::FinishError"]],["impl Unpin for FrameType",1,["noq_proto::frame::FrameType"]],["impl Unpin for LongType",1,["noq_proto::packet::LongType"]],["impl Unpin for MaybeFrame",1,["noq_proto::frame::MaybeFrame"]],["impl Unpin for PacketDecodeError",1,["noq_proto::packet::PacketDecodeError"]],["impl Unpin for PathAbandonReason",1,["noq_proto::connection::paths::PathAbandonReason"]],["impl Unpin for PathError",1,["noq_proto::connection::PathError"]],["impl Unpin for PathEvent",1,["noq_proto::connection::paths::PathEvent"]],["impl Unpin for PathStatus",1,["noq_proto::connection::paths::PathStatus"]],["impl Unpin for ProtectedHeader",1,["noq_proto::packet::ProtectedHeader"]],["impl Unpin for ReadError",1,["noq_proto::connection::streams::recv::ReadError"]],["impl Unpin for ReadableError",1,["noq_proto::connection::streams::recv::ReadableError"]],["impl Unpin for SendDatagramError",1,["noq_proto::connection::datagrams::SendDatagramError"]],["impl Unpin for SetPathStatusError",1,["noq_proto::connection::paths::SetPathStatusError"]],["impl Unpin for Side",1,["noq_proto::Side"]],["impl Unpin for StreamEvent",1,["noq_proto::connection::streams::StreamEvent"]],["impl Unpin for WriteError",1,["noq_proto::connection::streams::send::WriteError"]],["impl Unpin for Error",1,["noq_proto::n0_nat_traversal::Error"]],["impl Unpin for Event",1,["noq_proto::n0_nat_traversal::Event"]],["impl Unpin for Error",1,["noq_proto::transport_parameters::Error"]],["impl Unpin for UnexpectedEnd",1,["noq_proto::coding::UnexpectedEnd"]],["impl Unpin for Bbr3",1,["noq_proto::congestion::bbr3::Bbr3"]],["impl Unpin for Bbr3Config",1,["noq_proto::congestion::bbr3::Bbr3Config"]],["impl Unpin for ControllerMetrics",1,["noq_proto::congestion::ControllerMetrics"]],["impl Unpin for Cubic",1,["noq_proto::congestion::cubic::Cubic"]],["impl Unpin for CubicConfig",1,["noq_proto::congestion::cubic::CubicConfig"]],["impl Unpin for NewReno",1,["noq_proto::congestion::new_reno::NewReno"]],["impl Unpin for NewRenoConfig",1,["noq_proto::congestion::new_reno::NewRenoConfig"]],["impl Unpin for HandshakeData",1,["noq_proto::crypto::rustls::HandshakeData"]],["impl Unpin for NoInitialCipherSuite",1,["noq_proto::crypto::rustls::NoInitialCipherSuite"]],["impl Unpin for QuicClientConfig",1,["noq_proto::crypto::rustls::QuicClientConfig"]],["impl Unpin for QuicServerConfig",1,["noq_proto::crypto::rustls::QuicServerConfig"]],["impl Unpin for TlsSession",1,["noq_proto::crypto::rustls::TlsSession"]],["impl Unpin for CryptoError",1,["noq_proto::crypto::CryptoError"]],["impl Unpin for ExportKeyingMaterialError",1,["noq_proto::crypto::ExportKeyingMaterialError"]],["impl Unpin for Keys",1,["noq_proto::crypto::Keys"]],["impl Unpin for UnsupportedVersion",1,["noq_proto::crypto::UnsupportedVersion"]],["impl Unpin for AcceptError",1,["noq_proto::endpoint::AcceptError"]],["impl Unpin for AckFrequencyConfig",1,["noq_proto::config::transport::AckFrequencyConfig"]],["impl Unpin for ApplicationClose",1,["noq_proto::frame::ApplicationClose"]],["impl Unpin for BloomTokenLog",1,["noq_proto::bloom_token_log::BloomTokenLog"]],["impl Unpin for Chunk",1,["noq_proto::connection::assembler::Chunk"]],["impl Unpin for ClientConfig",1,["noq_proto::config::ClientConfig"]],["impl Unpin for ClosedPath",1,["noq_proto::connection::paths::ClosedPath"]],["impl Unpin for ClosedStream",1,["noq_proto::connection::streams::ClosedStream"]],["impl Unpin for Connection",1,["noq_proto::connection::Connection"]],["impl Unpin for ConnectionClose",1,["noq_proto::frame::ConnectionClose"]],["impl Unpin for ConnectionEvent",1,["noq_proto::shared::ConnectionEvent"]],["impl Unpin for ConnectionHandle",1,["noq_proto::endpoint::ConnectionHandle"]],["impl Unpin for ConnectionId",1,["noq_proto::shared::ConnectionId"]],["impl Unpin for ConnectionStats",1,["noq_proto::connection::stats::ConnectionStats"]],["impl Unpin for Datagram",1,["noq_proto::frame::Datagram"]],["impl Unpin for DatagramInfo",1,["noq_proto::frame::DatagramInfo"]],["impl Unpin for DecryptedInitial",1,["noq_proto::endpoint::DecryptedInitial"]],["impl Unpin for Endpoint",1,["noq_proto::endpoint::Endpoint"]],["impl Unpin for EndpointConfig",1,["noq_proto::config::EndpointConfig"]],["impl Unpin for EndpointEvent",1,["noq_proto::shared::EndpointEvent"]],["impl Unpin for FixedLengthConnectionIdParser",1,["noq_proto::packet::FixedLengthConnectionIdParser"]],["impl Unpin for FourTuple",1,["noq_proto::FourTuple"]],["impl Unpin for FrameStats",1,["noq_proto::connection::stats::FrameStats"]],["impl Unpin for HashedConnectionIdGenerator",1,["noq_proto::cid_generator::HashedConnectionIdGenerator"]],["impl Unpin for IdleTimeout",1,["noq_proto::config::transport::IdleTimeout"]],["impl Unpin for Incoming",1,["noq_proto::endpoint::Incoming"]],["impl Unpin for IncomingAlpns",1,["noq_proto::endpoint::IncomingAlpns"]],["impl Unpin for InvalidCid",1,["noq_proto::cid_generator::InvalidCid"]],["impl Unpin for InvalidFrameId",1,["noq_proto::frame::InvalidFrameId"]],["impl Unpin for MtuDiscoveryConfig",1,["noq_proto::config::transport::MtuDiscoveryConfig"]],["impl Unpin for MultipathNotNegotiated",1,["noq_proto::connection::MultipathNotNegotiated"]],["impl Unpin for NoneTokenLog",1,["noq_proto::token::NoneTokenLog"]],["impl Unpin for NoneTokenStore",1,["noq_proto::token::NoneTokenStore"]],["impl Unpin for PartialDecode",1,["noq_proto::packet::PartialDecode"]],["impl Unpin for PathId",1,["noq_proto::connection::paths::PathId"]],["impl Unpin for PathStats",1,["noq_proto::connection::stats::PathStats"]],["impl Unpin for ProtectedInitialHeader",1,["noq_proto::packet::ProtectedInitialHeader"]],["impl Unpin for QlogConfig",1,["noq_proto::config::qlog::QlogConfig"]],["impl Unpin for QlogFileFactory",1,["noq_proto::config::qlog::QlogFileFactory"]],["impl Unpin for RandomConnectionIdGenerator",1,["noq_proto::cid_generator::RandomConnectionIdGenerator"]],["impl Unpin for RetryError",1,["noq_proto::endpoint::RetryError"]],["impl Unpin for RttEstimator",1,["noq_proto::connection::paths::RttEstimator"]],["impl Unpin for ServerConfig",1,["noq_proto::config::ServerConfig"]],["impl Unpin for ShouldTransmit",1,["noq_proto::connection::streams::ShouldTransmit"]],["impl Unpin for StdSystemTime",1,["noq_proto::config::StdSystemTime"]],["impl Unpin for StreamId",1,["noq_proto::StreamId"]],["impl Unpin for StreamInfo",1,["noq_proto::frame::StreamInfo"]],["impl Unpin for TokenMemoryCache",1,["noq_proto::token_memory_cache::TokenMemoryCache"]],["impl Unpin for TokenReuseError",1,["noq_proto::token::TokenReuseError"]],["impl Unpin for Transmit",1,["noq_proto::Transmit"]],["impl Unpin for TransportConfig",1,["noq_proto::config::transport::TransportConfig"]],["impl Unpin for Error",1,["noq_proto::transport_error::Error"]],["impl Unpin for Code",1,["noq_proto::transport_error::Code"]],["impl Unpin for UdpStats",1,["noq_proto::connection::stats::UdpStats"]],["impl Unpin for ValidationTokenConfig",1,["noq_proto::config::ValidationTokenConfig"]],["impl Unpin for VarInt",1,["noq_proto::varint::VarInt"]],["impl Unpin for VarIntBoundsExceeded",1,["noq_proto::varint::VarIntBoundsExceeded"]],["impl Unpin for TransportParameters",1,["noq_proto::transport_parameters::TransportParameters"]],["impl<'a> Unpin for Chunks<'a>",1,["noq_proto::connection::streams::recv::Chunks"]],["impl<'a> Unpin for Datagrams<'a>",1,["noq_proto::connection::datagrams::Datagrams"]],["impl<'a> Unpin for RecvStream<'a>",1,["noq_proto::connection::streams::RecvStream"]],["impl<'a> Unpin for SendStream<'a>",1,["noq_proto::connection::streams::SendStream"]],["impl<'a> Unpin for Streams<'a>",1,["noq_proto::connection::streams::Streams"]],["impl<T> Unpin for KeyPair<T>
    where\n T: Unpin,
    ",1,["noq_proto::crypto::KeyPair"]]]],["noq_udp",[["impl Unpin for EcnCodepoint",1,["noq_udp::EcnCodepoint"]],["impl Unpin for RecvMeta",1,["noq_udp::RecvMeta"]],["impl Unpin for UdpSocketState",1,["noq_udp::imp::UdpSocketState"]],["impl<'a> Unpin for Transmit<'a>",1,["noq_udp::Transmit"]],["impl<'a> Unpin for UdpSockRef<'a>",1,["noq_udp::UdpSockRef"]]]],["perf",[["impl Unpin for CongestionAlgorithm",1,["perf::CongestionAlgorithm"]],["impl Unpin for Opt",1,["perf::client::Opt"]],["impl Unpin for NoProtectionClientConfig",1,["perf::noprotection::NoProtectionClientConfig"]],["impl Unpin for NoProtectionServerConfig",1,["perf::noprotection::NoProtectionServerConfig"]],["impl Unpin for Opt",1,["perf::server::Opt"]],["impl Unpin for OpenStreamStats",1,["perf::stats::OpenStreamStats"]],["impl Unpin for Stats",1,["perf::stats::Stats"]],["impl Unpin for StreamStats",1,["perf::stats::StreamStats"]],["impl Unpin for CommonOpt",1,["perf::CommonOpt"]]]]]); if (window.register_implementors) { window.register_implementors(implementors); } else { window.pending_implementors = implementors; } })() -//{"start":57,"fragment_lengths":[1731,294,366,12841,555,35512,1501,2796]} \ No newline at end of file +//{"start":57,"fragment_lengths":[1731,294,366,13113,555,35213,1501,2796]} \ No newline at end of file diff --git a/pr/451/docs/trait.impl/core/marker/trait.UnsafeUnpin.js b/pr/451/docs/trait.impl/core/marker/trait.UnsafeUnpin.js index 0ba259e40..420fb8943 100644 --- a/pr/451/docs/trait.impl/core/marker/trait.UnsafeUnpin.js +++ b/pr/451/docs/trait.impl/core/marker/trait.UnsafeUnpin.js @@ -1,9 +1,9 @@ (function() { - var implementors = Object.fromEntries([["bench",[["impl UnsafeUnpin for CipherSuite",1,["bench::CipherSuite"]],["impl UnsafeUnpin for RuntimeType",1,["bench::RuntimeType"]],["impl UnsafeUnpin for Stats",1,["bench::stats::Stats"]],["impl UnsafeUnpin for StreamStats",1,["bench::stats::StreamStats"]],["impl UnsafeUnpin for TransferResult",1,["bench::stats::TransferResult"]],["impl UnsafeUnpin for Opt",1,["bench::Opt"]]]],["bulk",[["impl UnsafeUnpin for ClientStats",1,["bulk::ClientStats"]]]],["certificate",[["impl UnsafeUnpin for SkipServerVerification",1,["certificate::SkipServerVerification"]]]],["noq",[["impl !UnsafeUnpin for Stopped",1,["noq::send_stream::Stopped"]],["impl UnsafeUnpin for ReadError",1,["noq::recv_stream::ReadError"]],["impl UnsafeUnpin for ReadExactError",1,["noq::recv_stream::ReadExactError"]],["impl UnsafeUnpin for ReadToEndError",1,["noq::recv_stream::ReadToEndError"]],["impl UnsafeUnpin for ResetError",1,["noq::recv_stream::ResetError"]],["impl UnsafeUnpin for SendDatagramError",1,["noq::connection::SendDatagramError"]],["impl UnsafeUnpin for StoppedError",1,["noq::send_stream::StoppedError"]],["impl UnsafeUnpin for WriteError",1,["noq::send_stream::WriteError"]],["impl UnsafeUnpin for AddressDiscovery",1,["noq::path::AddressDiscovery"]],["impl UnsafeUnpin for Connecting",1,["noq::connection::Connecting"]],["impl UnsafeUnpin for Connection",1,["noq::connection::Connection"]],["impl UnsafeUnpin for Endpoint",1,["noq::endpoint::Endpoint"]],["impl UnsafeUnpin for EndpointStats",1,["noq::endpoint::EndpointStats"]],["impl UnsafeUnpin for Incoming",1,["noq::incoming::Incoming"]],["impl UnsafeUnpin for IncomingFuture",1,["noq::incoming::IncomingFuture"]],["impl UnsafeUnpin for Lagged",1,["noq::event_stream::Lagged"]],["impl UnsafeUnpin for NatTraversalUpdates",1,["noq::event_stream::NatTraversalUpdates"]],["impl UnsafeUnpin for ObservedExternalAddr",1,["noq::event_stream::ObservedExternalAddr"]],["impl UnsafeUnpin for OnClosed",1,["noq::connection::OnClosed"]],["impl UnsafeUnpin for OpenPath",1,["noq::path::OpenPath"]],["impl UnsafeUnpin for Path",1,["noq::path::Path"]],["impl UnsafeUnpin for PathEvents",1,["noq::event_stream::PathEvents"]],["impl UnsafeUnpin for RecvStream",1,["noq::recv_stream::RecvStream"]],["impl UnsafeUnpin for RetryError",1,["noq::incoming::RetryError"]],["impl UnsafeUnpin for SendStream",1,["noq::send_stream::SendStream"]],["impl UnsafeUnpin for SmolRuntime",1,["noq::runtime::smol::SmolRuntime"]],["impl UnsafeUnpin for TokioRuntime",1,["noq::runtime::tokio::TokioRuntime"]],["impl UnsafeUnpin for UnorderedRecvStream",1,["noq::recv_stream::UnorderedRecvStream"]],["impl UnsafeUnpin for WeakConnectionHandle",1,["noq::connection::WeakConnectionHandle"]],["impl UnsafeUnpin for WeakPathHandle",1,["noq::path::WeakPathHandle"]],["impl UnsafeUnpin for ZeroRttAccepted",1,["noq::connection::ZeroRttAccepted"]],["impl<'a> !UnsafeUnpin for Accept<'a>",1,["noq::endpoint::Accept"]],["impl<'a> !UnsafeUnpin for AcceptBi<'a>",1,["noq::connection::AcceptBi"]],["impl<'a> !UnsafeUnpin for AcceptUni<'a>",1,["noq::connection::AcceptUni"]],["impl<'a> !UnsafeUnpin for OpenBi<'a>",1,["noq::connection::OpenBi"]],["impl<'a> !UnsafeUnpin for OpenUni<'a>",1,["noq::connection::OpenUni"]],["impl<'a> !UnsafeUnpin for ReadDatagram<'a>",1,["noq::connection::ReadDatagram"]],["impl<'a> !UnsafeUnpin for SendDatagram<'a>",1,["noq::connection::SendDatagram"]]]],["noq_perf",[["impl UnsafeUnpin for Commands",1,["noq_perf::Commands"]],["impl UnsafeUnpin for Cli",1,["noq_perf::Cli"]]]],["noq_proto",[["impl UnsafeUnpin for ClosePathError",1,["noq_proto::connection::ClosePathError"]],["impl UnsafeUnpin for ConfigError",1,["noq_proto::config::ConfigError"]],["impl UnsafeUnpin for ConnectError",1,["noq_proto::endpoint::ConnectError"]],["impl UnsafeUnpin for ConnectionError",1,["noq_proto::connection::ConnectionError"]],["impl UnsafeUnpin for DatagramEvent",1,["noq_proto::endpoint::DatagramEvent"]],["impl UnsafeUnpin for Dir",1,["noq_proto::Dir"]],["impl UnsafeUnpin for EcnCodepoint",1,["noq_proto::shared::EcnCodepoint"]],["impl UnsafeUnpin for Event",1,["noq_proto::connection::Event"]],["impl UnsafeUnpin for FinishError",1,["noq_proto::connection::streams::send::FinishError"]],["impl UnsafeUnpin for FrameType",1,["noq_proto::frame::FrameType"]],["impl UnsafeUnpin for LongType",1,["noq_proto::packet::LongType"]],["impl UnsafeUnpin for MaybeFrame",1,["noq_proto::frame::MaybeFrame"]],["impl UnsafeUnpin for PacketDecodeError",1,["noq_proto::packet::PacketDecodeError"]],["impl UnsafeUnpin for PathAbandonReason",1,["noq_proto::connection::paths::PathAbandonReason"]],["impl UnsafeUnpin for PathError",1,["noq_proto::connection::PathError"]],["impl UnsafeUnpin for PathEvent",1,["noq_proto::connection::paths::PathEvent"]],["impl UnsafeUnpin for PathStatus",1,["noq_proto::connection::paths::PathStatus"]],["impl UnsafeUnpin for ProtectedHeader",1,["noq_proto::packet::ProtectedHeader"]],["impl UnsafeUnpin for ReadError",1,["noq_proto::connection::streams::recv::ReadError"]],["impl UnsafeUnpin for ReadableError",1,["noq_proto::connection::streams::recv::ReadableError"]],["impl UnsafeUnpin for SendDatagramError",1,["noq_proto::connection::datagrams::SendDatagramError"]],["impl UnsafeUnpin for SetPathStatusError",1,["noq_proto::connection::paths::SetPathStatusError"]],["impl UnsafeUnpin for Side",1,["noq_proto::Side"]],["impl UnsafeUnpin for StreamEvent",1,["noq_proto::connection::streams::StreamEvent"]],["impl UnsafeUnpin for WriteError",1,["noq_proto::connection::streams::send::WriteError"]],["impl UnsafeUnpin for Error",1,["noq_proto::n0_nat_traversal::Error"]],["impl UnsafeUnpin for Event",1,["noq_proto::n0_nat_traversal::Event"]],["impl UnsafeUnpin for Error",1,["noq_proto::transport_parameters::Error"]],["impl UnsafeUnpin for UnexpectedEnd",1,["noq_proto::coding::UnexpectedEnd"]],["impl UnsafeUnpin for Bbr",1,["noq_proto::congestion::bbr::Bbr"]],["impl UnsafeUnpin for BbrConfig",1,["noq_proto::congestion::bbr::BbrConfig"]],["impl UnsafeUnpin for ControllerMetrics",1,["noq_proto::congestion::ControllerMetrics"]],["impl UnsafeUnpin for Cubic",1,["noq_proto::congestion::cubic::Cubic"]],["impl UnsafeUnpin for CubicConfig",1,["noq_proto::congestion::cubic::CubicConfig"]],["impl UnsafeUnpin for NewReno",1,["noq_proto::congestion::new_reno::NewReno"]],["impl UnsafeUnpin for NewRenoConfig",1,["noq_proto::congestion::new_reno::NewRenoConfig"]],["impl UnsafeUnpin for HandshakeData",1,["noq_proto::crypto::rustls::HandshakeData"]],["impl UnsafeUnpin for NoInitialCipherSuite",1,["noq_proto::crypto::rustls::NoInitialCipherSuite"]],["impl UnsafeUnpin for QuicClientConfig",1,["noq_proto::crypto::rustls::QuicClientConfig"]],["impl UnsafeUnpin for QuicServerConfig",1,["noq_proto::crypto::rustls::QuicServerConfig"]],["impl UnsafeUnpin for TlsSession",1,["noq_proto::crypto::rustls::TlsSession"]],["impl UnsafeUnpin for CryptoError",1,["noq_proto::crypto::CryptoError"]],["impl UnsafeUnpin for ExportKeyingMaterialError",1,["noq_proto::crypto::ExportKeyingMaterialError"]],["impl UnsafeUnpin for Keys",1,["noq_proto::crypto::Keys"]],["impl UnsafeUnpin for UnsupportedVersion",1,["noq_proto::crypto::UnsupportedVersion"]],["impl UnsafeUnpin for AcceptError",1,["noq_proto::endpoint::AcceptError"]],["impl UnsafeUnpin for AckFrequencyConfig",1,["noq_proto::config::transport::AckFrequencyConfig"]],["impl UnsafeUnpin for ApplicationClose",1,["noq_proto::frame::ApplicationClose"]],["impl UnsafeUnpin for BloomTokenLog",1,["noq_proto::bloom_token_log::BloomTokenLog"]],["impl UnsafeUnpin for Chunk",1,["noq_proto::connection::assembler::Chunk"]],["impl UnsafeUnpin for ClientConfig",1,["noq_proto::config::ClientConfig"]],["impl UnsafeUnpin for ClosedPath",1,["noq_proto::connection::paths::ClosedPath"]],["impl UnsafeUnpin for ClosedStream",1,["noq_proto::connection::streams::ClosedStream"]],["impl UnsafeUnpin for Connection",1,["noq_proto::connection::Connection"]],["impl UnsafeUnpin for ConnectionClose",1,["noq_proto::frame::ConnectionClose"]],["impl UnsafeUnpin for ConnectionEvent",1,["noq_proto::shared::ConnectionEvent"]],["impl UnsafeUnpin for ConnectionHandle",1,["noq_proto::endpoint::ConnectionHandle"]],["impl UnsafeUnpin for ConnectionId",1,["noq_proto::shared::ConnectionId"]],["impl UnsafeUnpin for ConnectionStats",1,["noq_proto::connection::stats::ConnectionStats"]],["impl UnsafeUnpin for Datagram",1,["noq_proto::frame::Datagram"]],["impl UnsafeUnpin for DatagramInfo",1,["noq_proto::frame::DatagramInfo"]],["impl UnsafeUnpin for DecryptedInitial",1,["noq_proto::endpoint::DecryptedInitial"]],["impl UnsafeUnpin for Endpoint",1,["noq_proto::endpoint::Endpoint"]],["impl UnsafeUnpin for EndpointConfig",1,["noq_proto::config::EndpointConfig"]],["impl UnsafeUnpin for EndpointEvent",1,["noq_proto::shared::EndpointEvent"]],["impl UnsafeUnpin for FixedLengthConnectionIdParser",1,["noq_proto::packet::FixedLengthConnectionIdParser"]],["impl UnsafeUnpin for FourTuple",1,["noq_proto::FourTuple"]],["impl UnsafeUnpin for FrameStats",1,["noq_proto::connection::stats::FrameStats"]],["impl UnsafeUnpin for HashedConnectionIdGenerator",1,["noq_proto::cid_generator::HashedConnectionIdGenerator"]],["impl UnsafeUnpin for IdleTimeout",1,["noq_proto::config::transport::IdleTimeout"]],["impl UnsafeUnpin for Incoming",1,["noq_proto::endpoint::Incoming"]],["impl UnsafeUnpin for IncomingAlpns",1,["noq_proto::endpoint::IncomingAlpns"]],["impl UnsafeUnpin for InvalidCid",1,["noq_proto::cid_generator::InvalidCid"]],["impl UnsafeUnpin for InvalidFrameId",1,["noq_proto::frame::InvalidFrameId"]],["impl UnsafeUnpin for MtuDiscoveryConfig",1,["noq_proto::config::transport::MtuDiscoveryConfig"]],["impl UnsafeUnpin for MultipathNotNegotiated",1,["noq_proto::connection::MultipathNotNegotiated"]],["impl UnsafeUnpin for NoneTokenLog",1,["noq_proto::token::NoneTokenLog"]],["impl UnsafeUnpin for NoneTokenStore",1,["noq_proto::token::NoneTokenStore"]],["impl UnsafeUnpin for PartialDecode",1,["noq_proto::packet::PartialDecode"]],["impl UnsafeUnpin for PathId",1,["noq_proto::connection::paths::PathId"]],["impl UnsafeUnpin for PathStats",1,["noq_proto::connection::stats::PathStats"]],["impl UnsafeUnpin for ProtectedInitialHeader",1,["noq_proto::packet::ProtectedInitialHeader"]],["impl UnsafeUnpin for QlogConfig",1,["noq_proto::config::qlog::QlogConfig"]],["impl UnsafeUnpin for QlogFileFactory",1,["noq_proto::config::qlog::QlogFileFactory"]],["impl UnsafeUnpin for RandomConnectionIdGenerator",1,["noq_proto::cid_generator::RandomConnectionIdGenerator"]],["impl UnsafeUnpin for RetryError",1,["noq_proto::endpoint::RetryError"]],["impl UnsafeUnpin for RttEstimator",1,["noq_proto::connection::paths::RttEstimator"]],["impl UnsafeUnpin for ServerConfig",1,["noq_proto::config::ServerConfig"]],["impl UnsafeUnpin for ShouldTransmit",1,["noq_proto::connection::streams::ShouldTransmit"]],["impl UnsafeUnpin for StdSystemTime",1,["noq_proto::config::StdSystemTime"]],["impl UnsafeUnpin for StreamId",1,["noq_proto::StreamId"]],["impl UnsafeUnpin for StreamInfo",1,["noq_proto::frame::StreamInfo"]],["impl UnsafeUnpin for TokenMemoryCache",1,["noq_proto::token_memory_cache::TokenMemoryCache"]],["impl UnsafeUnpin for TokenReuseError",1,["noq_proto::token::TokenReuseError"]],["impl UnsafeUnpin for Transmit",1,["noq_proto::Transmit"]],["impl UnsafeUnpin for TransportConfig",1,["noq_proto::config::transport::TransportConfig"]],["impl UnsafeUnpin for Error",1,["noq_proto::transport_error::Error"]],["impl UnsafeUnpin for Code",1,["noq_proto::transport_error::Code"]],["impl UnsafeUnpin for UdpStats",1,["noq_proto::connection::stats::UdpStats"]],["impl UnsafeUnpin for ValidationTokenConfig",1,["noq_proto::config::ValidationTokenConfig"]],["impl UnsafeUnpin for VarInt",1,["noq_proto::varint::VarInt"]],["impl UnsafeUnpin for VarIntBoundsExceeded",1,["noq_proto::varint::VarIntBoundsExceeded"]],["impl UnsafeUnpin for Written",1,["noq_proto::connection::streams::send::Written"]],["impl UnsafeUnpin for TransportParameters",1,["noq_proto::transport_parameters::TransportParameters"]],["impl<'a> UnsafeUnpin for Chunks<'a>",1,["noq_proto::connection::streams::recv::Chunks"]],["impl<'a> UnsafeUnpin for Datagrams<'a>",1,["noq_proto::connection::datagrams::Datagrams"]],["impl<'a> UnsafeUnpin for RecvStream<'a>",1,["noq_proto::connection::streams::RecvStream"]],["impl<'a> UnsafeUnpin for SendStream<'a>",1,["noq_proto::connection::streams::SendStream"]],["impl<'a> UnsafeUnpin for Streams<'a>",1,["noq_proto::connection::streams::Streams"]],["impl<T> UnsafeUnpin for KeyPair<T>
    where\n T: UnsafeUnpin,
    ",1,["noq_proto::crypto::KeyPair"]]]],["noq_udp",[["impl UnsafeUnpin for EcnCodepoint",1,["noq_udp::EcnCodepoint"]],["impl UnsafeUnpin for RecvMeta",1,["noq_udp::RecvMeta"]],["impl UnsafeUnpin for UdpSocketState",1,["noq_udp::imp::UdpSocketState"]],["impl<'a> UnsafeUnpin for Transmit<'a>",1,["noq_udp::Transmit"]],["impl<'a> UnsafeUnpin for UdpSockRef<'a>",1,["noq_udp::UdpSockRef"]]]],["perf",[["impl UnsafeUnpin for CongestionAlgorithm",1,["perf::CongestionAlgorithm"]],["impl UnsafeUnpin for Opt",1,["perf::client::Opt"]],["impl UnsafeUnpin for NoProtectionClientConfig",1,["perf::noprotection::NoProtectionClientConfig"]],["impl UnsafeUnpin for NoProtectionServerConfig",1,["perf::noprotection::NoProtectionServerConfig"]],["impl UnsafeUnpin for Opt",1,["perf::server::Opt"]],["impl UnsafeUnpin for OpenStreamStats",1,["perf::stats::OpenStreamStats"]],["impl UnsafeUnpin for Stats",1,["perf::stats::Stats"]],["impl UnsafeUnpin for StreamStats",1,["perf::stats::StreamStats"]],["impl UnsafeUnpin for CommonOpt",1,["perf::CommonOpt"]]]]]); + var implementors = Object.fromEntries([["bench",[["impl UnsafeUnpin for CipherSuite",1,["bench::CipherSuite"]],["impl UnsafeUnpin for RuntimeType",1,["bench::RuntimeType"]],["impl UnsafeUnpin for Stats",1,["bench::stats::Stats"]],["impl UnsafeUnpin for StreamStats",1,["bench::stats::StreamStats"]],["impl UnsafeUnpin for TransferResult",1,["bench::stats::TransferResult"]],["impl UnsafeUnpin for Opt",1,["bench::Opt"]]]],["bulk",[["impl UnsafeUnpin for ClientStats",1,["bulk::ClientStats"]]]],["certificate",[["impl UnsafeUnpin for SkipServerVerification",1,["certificate::SkipServerVerification"]]]],["noq",[["impl !UnsafeUnpin for Stopped",1,["noq::send_stream::Stopped"]],["impl UnsafeUnpin for ReadError",1,["noq::recv_stream::ReadError"]],["impl UnsafeUnpin for ReadExactError",1,["noq::recv_stream::ReadExactError"]],["impl UnsafeUnpin for ReadToEndError",1,["noq::recv_stream::ReadToEndError"]],["impl UnsafeUnpin for ResetError",1,["noq::recv_stream::ResetError"]],["impl UnsafeUnpin for SendDatagramError",1,["noq::connection::SendDatagramError"]],["impl UnsafeUnpin for StoppedError",1,["noq::send_stream::StoppedError"]],["impl UnsafeUnpin for WriteError",1,["noq::send_stream::WriteError"]],["impl UnsafeUnpin for AddressDiscovery",1,["noq::path::AddressDiscovery"]],["impl UnsafeUnpin for Closed",1,["noq::connection::Closed"]],["impl UnsafeUnpin for Connecting",1,["noq::connection::Connecting"]],["impl UnsafeUnpin for Connection",1,["noq::connection::Connection"]],["impl UnsafeUnpin for Endpoint",1,["noq::endpoint::Endpoint"]],["impl UnsafeUnpin for EndpointStats",1,["noq::endpoint::EndpointStats"]],["impl UnsafeUnpin for Incoming",1,["noq::incoming::Incoming"]],["impl UnsafeUnpin for IncomingFuture",1,["noq::incoming::IncomingFuture"]],["impl UnsafeUnpin for Lagged",1,["noq::event_stream::Lagged"]],["impl UnsafeUnpin for NatTraversalUpdates",1,["noq::event_stream::NatTraversalUpdates"]],["impl UnsafeUnpin for ObservedExternalAddr",1,["noq::event_stream::ObservedExternalAddr"]],["impl UnsafeUnpin for OnClosed",1,["noq::connection::OnClosed"]],["impl UnsafeUnpin for OpenPath",1,["noq::path::OpenPath"]],["impl UnsafeUnpin for Path",1,["noq::path::Path"]],["impl UnsafeUnpin for PathEvents",1,["noq::event_stream::PathEvents"]],["impl UnsafeUnpin for RecvStream",1,["noq::recv_stream::RecvStream"]],["impl UnsafeUnpin for RetryError",1,["noq::incoming::RetryError"]],["impl UnsafeUnpin for SendStream",1,["noq::send_stream::SendStream"]],["impl UnsafeUnpin for SmolRuntime",1,["noq::runtime::smol::SmolRuntime"]],["impl UnsafeUnpin for TokioRuntime",1,["noq::runtime::tokio::TokioRuntime"]],["impl UnsafeUnpin for UnorderedRecvStream",1,["noq::recv_stream::UnorderedRecvStream"]],["impl UnsafeUnpin for WeakConnectionHandle",1,["noq::connection::WeakConnectionHandle"]],["impl UnsafeUnpin for WeakPathHandle",1,["noq::path::WeakPathHandle"]],["impl UnsafeUnpin for ZeroRttAccepted",1,["noq::connection::ZeroRttAccepted"]],["impl<'a> !UnsafeUnpin for Accept<'a>",1,["noq::endpoint::Accept"]],["impl<'a> !UnsafeUnpin for AcceptBi<'a>",1,["noq::connection::AcceptBi"]],["impl<'a> !UnsafeUnpin for AcceptUni<'a>",1,["noq::connection::AcceptUni"]],["impl<'a> !UnsafeUnpin for OpenBi<'a>",1,["noq::connection::OpenBi"]],["impl<'a> !UnsafeUnpin for OpenUni<'a>",1,["noq::connection::OpenUni"]],["impl<'a> !UnsafeUnpin for ReadDatagram<'a>",1,["noq::connection::ReadDatagram"]],["impl<'a> !UnsafeUnpin for SendDatagram<'a>",1,["noq::connection::SendDatagram"]]]],["noq_perf",[["impl UnsafeUnpin for Commands",1,["noq_perf::Commands"]],["impl UnsafeUnpin for Cli",1,["noq_perf::Cli"]]]],["noq_proto",[["impl UnsafeUnpin for ClosePathError",1,["noq_proto::connection::ClosePathError"]],["impl UnsafeUnpin for ConfigError",1,["noq_proto::config::ConfigError"]],["impl UnsafeUnpin for ConnectError",1,["noq_proto::endpoint::ConnectError"]],["impl UnsafeUnpin for ConnectionError",1,["noq_proto::connection::ConnectionError"]],["impl UnsafeUnpin for DatagramEvent",1,["noq_proto::endpoint::DatagramEvent"]],["impl UnsafeUnpin for Dir",1,["noq_proto::Dir"]],["impl UnsafeUnpin for EcnCodepoint",1,["noq_proto::shared::EcnCodepoint"]],["impl UnsafeUnpin for Event",1,["noq_proto::connection::Event"]],["impl UnsafeUnpin for FinishError",1,["noq_proto::connection::streams::send::FinishError"]],["impl UnsafeUnpin for FrameType",1,["noq_proto::frame::FrameType"]],["impl UnsafeUnpin for LongType",1,["noq_proto::packet::LongType"]],["impl UnsafeUnpin for MaybeFrame",1,["noq_proto::frame::MaybeFrame"]],["impl UnsafeUnpin for PacketDecodeError",1,["noq_proto::packet::PacketDecodeError"]],["impl UnsafeUnpin for PathAbandonReason",1,["noq_proto::connection::paths::PathAbandonReason"]],["impl UnsafeUnpin for PathError",1,["noq_proto::connection::PathError"]],["impl UnsafeUnpin for PathEvent",1,["noq_proto::connection::paths::PathEvent"]],["impl UnsafeUnpin for PathStatus",1,["noq_proto::connection::paths::PathStatus"]],["impl UnsafeUnpin for ProtectedHeader",1,["noq_proto::packet::ProtectedHeader"]],["impl UnsafeUnpin for ReadError",1,["noq_proto::connection::streams::recv::ReadError"]],["impl UnsafeUnpin for ReadableError",1,["noq_proto::connection::streams::recv::ReadableError"]],["impl UnsafeUnpin for SendDatagramError",1,["noq_proto::connection::datagrams::SendDatagramError"]],["impl UnsafeUnpin for SetPathStatusError",1,["noq_proto::connection::paths::SetPathStatusError"]],["impl UnsafeUnpin for Side",1,["noq_proto::Side"]],["impl UnsafeUnpin for StreamEvent",1,["noq_proto::connection::streams::StreamEvent"]],["impl UnsafeUnpin for WriteError",1,["noq_proto::connection::streams::send::WriteError"]],["impl UnsafeUnpin for Error",1,["noq_proto::n0_nat_traversal::Error"]],["impl UnsafeUnpin for Event",1,["noq_proto::n0_nat_traversal::Event"]],["impl UnsafeUnpin for Error",1,["noq_proto::transport_parameters::Error"]],["impl UnsafeUnpin for UnexpectedEnd",1,["noq_proto::coding::UnexpectedEnd"]],["impl UnsafeUnpin for Bbr3",1,["noq_proto::congestion::bbr3::Bbr3"]],["impl UnsafeUnpin for Bbr3Config",1,["noq_proto::congestion::bbr3::Bbr3Config"]],["impl UnsafeUnpin for ControllerMetrics",1,["noq_proto::congestion::ControllerMetrics"]],["impl UnsafeUnpin for Cubic",1,["noq_proto::congestion::cubic::Cubic"]],["impl UnsafeUnpin for CubicConfig",1,["noq_proto::congestion::cubic::CubicConfig"]],["impl UnsafeUnpin for NewReno",1,["noq_proto::congestion::new_reno::NewReno"]],["impl UnsafeUnpin for NewRenoConfig",1,["noq_proto::congestion::new_reno::NewRenoConfig"]],["impl UnsafeUnpin for HandshakeData",1,["noq_proto::crypto::rustls::HandshakeData"]],["impl UnsafeUnpin for NoInitialCipherSuite",1,["noq_proto::crypto::rustls::NoInitialCipherSuite"]],["impl UnsafeUnpin for QuicClientConfig",1,["noq_proto::crypto::rustls::QuicClientConfig"]],["impl UnsafeUnpin for QuicServerConfig",1,["noq_proto::crypto::rustls::QuicServerConfig"]],["impl UnsafeUnpin for TlsSession",1,["noq_proto::crypto::rustls::TlsSession"]],["impl UnsafeUnpin for CryptoError",1,["noq_proto::crypto::CryptoError"]],["impl UnsafeUnpin for ExportKeyingMaterialError",1,["noq_proto::crypto::ExportKeyingMaterialError"]],["impl UnsafeUnpin for Keys",1,["noq_proto::crypto::Keys"]],["impl UnsafeUnpin for UnsupportedVersion",1,["noq_proto::crypto::UnsupportedVersion"]],["impl UnsafeUnpin for AcceptError",1,["noq_proto::endpoint::AcceptError"]],["impl UnsafeUnpin for AckFrequencyConfig",1,["noq_proto::config::transport::AckFrequencyConfig"]],["impl UnsafeUnpin for ApplicationClose",1,["noq_proto::frame::ApplicationClose"]],["impl UnsafeUnpin for BloomTokenLog",1,["noq_proto::bloom_token_log::BloomTokenLog"]],["impl UnsafeUnpin for Chunk",1,["noq_proto::connection::assembler::Chunk"]],["impl UnsafeUnpin for ClientConfig",1,["noq_proto::config::ClientConfig"]],["impl UnsafeUnpin for ClosedPath",1,["noq_proto::connection::paths::ClosedPath"]],["impl UnsafeUnpin for ClosedStream",1,["noq_proto::connection::streams::ClosedStream"]],["impl UnsafeUnpin for Connection",1,["noq_proto::connection::Connection"]],["impl UnsafeUnpin for ConnectionClose",1,["noq_proto::frame::ConnectionClose"]],["impl UnsafeUnpin for ConnectionEvent",1,["noq_proto::shared::ConnectionEvent"]],["impl UnsafeUnpin for ConnectionHandle",1,["noq_proto::endpoint::ConnectionHandle"]],["impl UnsafeUnpin for ConnectionId",1,["noq_proto::shared::ConnectionId"]],["impl UnsafeUnpin for ConnectionStats",1,["noq_proto::connection::stats::ConnectionStats"]],["impl UnsafeUnpin for Datagram",1,["noq_proto::frame::Datagram"]],["impl UnsafeUnpin for DatagramInfo",1,["noq_proto::frame::DatagramInfo"]],["impl UnsafeUnpin for DecryptedInitial",1,["noq_proto::endpoint::DecryptedInitial"]],["impl UnsafeUnpin for Endpoint",1,["noq_proto::endpoint::Endpoint"]],["impl UnsafeUnpin for EndpointConfig",1,["noq_proto::config::EndpointConfig"]],["impl UnsafeUnpin for EndpointEvent",1,["noq_proto::shared::EndpointEvent"]],["impl UnsafeUnpin for FixedLengthConnectionIdParser",1,["noq_proto::packet::FixedLengthConnectionIdParser"]],["impl UnsafeUnpin for FourTuple",1,["noq_proto::FourTuple"]],["impl UnsafeUnpin for FrameStats",1,["noq_proto::connection::stats::FrameStats"]],["impl UnsafeUnpin for HashedConnectionIdGenerator",1,["noq_proto::cid_generator::HashedConnectionIdGenerator"]],["impl UnsafeUnpin for IdleTimeout",1,["noq_proto::config::transport::IdleTimeout"]],["impl UnsafeUnpin for Incoming",1,["noq_proto::endpoint::Incoming"]],["impl UnsafeUnpin for IncomingAlpns",1,["noq_proto::endpoint::IncomingAlpns"]],["impl UnsafeUnpin for InvalidCid",1,["noq_proto::cid_generator::InvalidCid"]],["impl UnsafeUnpin for InvalidFrameId",1,["noq_proto::frame::InvalidFrameId"]],["impl UnsafeUnpin for MtuDiscoveryConfig",1,["noq_proto::config::transport::MtuDiscoveryConfig"]],["impl UnsafeUnpin for MultipathNotNegotiated",1,["noq_proto::connection::MultipathNotNegotiated"]],["impl UnsafeUnpin for NoneTokenLog",1,["noq_proto::token::NoneTokenLog"]],["impl UnsafeUnpin for NoneTokenStore",1,["noq_proto::token::NoneTokenStore"]],["impl UnsafeUnpin for PartialDecode",1,["noq_proto::packet::PartialDecode"]],["impl UnsafeUnpin for PathId",1,["noq_proto::connection::paths::PathId"]],["impl UnsafeUnpin for PathStats",1,["noq_proto::connection::stats::PathStats"]],["impl UnsafeUnpin for ProtectedInitialHeader",1,["noq_proto::packet::ProtectedInitialHeader"]],["impl UnsafeUnpin for QlogConfig",1,["noq_proto::config::qlog::QlogConfig"]],["impl UnsafeUnpin for QlogFileFactory",1,["noq_proto::config::qlog::QlogFileFactory"]],["impl UnsafeUnpin for RandomConnectionIdGenerator",1,["noq_proto::cid_generator::RandomConnectionIdGenerator"]],["impl UnsafeUnpin for RetryError",1,["noq_proto::endpoint::RetryError"]],["impl UnsafeUnpin for RttEstimator",1,["noq_proto::connection::paths::RttEstimator"]],["impl UnsafeUnpin for ServerConfig",1,["noq_proto::config::ServerConfig"]],["impl UnsafeUnpin for ShouldTransmit",1,["noq_proto::connection::streams::ShouldTransmit"]],["impl UnsafeUnpin for StdSystemTime",1,["noq_proto::config::StdSystemTime"]],["impl UnsafeUnpin for StreamId",1,["noq_proto::StreamId"]],["impl UnsafeUnpin for StreamInfo",1,["noq_proto::frame::StreamInfo"]],["impl UnsafeUnpin for TokenMemoryCache",1,["noq_proto::token_memory_cache::TokenMemoryCache"]],["impl UnsafeUnpin for TokenReuseError",1,["noq_proto::token::TokenReuseError"]],["impl UnsafeUnpin for Transmit",1,["noq_proto::Transmit"]],["impl UnsafeUnpin for TransportConfig",1,["noq_proto::config::transport::TransportConfig"]],["impl UnsafeUnpin for Error",1,["noq_proto::transport_error::Error"]],["impl UnsafeUnpin for Code",1,["noq_proto::transport_error::Code"]],["impl UnsafeUnpin for UdpStats",1,["noq_proto::connection::stats::UdpStats"]],["impl UnsafeUnpin for ValidationTokenConfig",1,["noq_proto::config::ValidationTokenConfig"]],["impl UnsafeUnpin for VarInt",1,["noq_proto::varint::VarInt"]],["impl UnsafeUnpin for VarIntBoundsExceeded",1,["noq_proto::varint::VarIntBoundsExceeded"]],["impl UnsafeUnpin for TransportParameters",1,["noq_proto::transport_parameters::TransportParameters"]],["impl<'a> UnsafeUnpin for Chunks<'a>",1,["noq_proto::connection::streams::recv::Chunks"]],["impl<'a> UnsafeUnpin for Datagrams<'a>",1,["noq_proto::connection::datagrams::Datagrams"]],["impl<'a> UnsafeUnpin for RecvStream<'a>",1,["noq_proto::connection::streams::RecvStream"]],["impl<'a> UnsafeUnpin for SendStream<'a>",1,["noq_proto::connection::streams::SendStream"]],["impl<'a> UnsafeUnpin for Streams<'a>",1,["noq_proto::connection::streams::Streams"]],["impl<T> UnsafeUnpin for KeyPair<T>
    where\n T: UnsafeUnpin,
    ",1,["noq_proto::crypto::KeyPair"]]]],["noq_udp",[["impl UnsafeUnpin for EcnCodepoint",1,["noq_udp::EcnCodepoint"]],["impl UnsafeUnpin for RecvMeta",1,["noq_udp::RecvMeta"]],["impl UnsafeUnpin for UdpSocketState",1,["noq_udp::imp::UdpSocketState"]],["impl<'a> UnsafeUnpin for Transmit<'a>",1,["noq_udp::Transmit"]],["impl<'a> UnsafeUnpin for UdpSockRef<'a>",1,["noq_udp::UdpSockRef"]]]],["perf",[["impl UnsafeUnpin for CongestionAlgorithm",1,["perf::CongestionAlgorithm"]],["impl UnsafeUnpin for Opt",1,["perf::client::Opt"]],["impl UnsafeUnpin for NoProtectionClientConfig",1,["perf::noprotection::NoProtectionClientConfig"]],["impl UnsafeUnpin for NoProtectionServerConfig",1,["perf::noprotection::NoProtectionServerConfig"]],["impl UnsafeUnpin for Opt",1,["perf::server::Opt"]],["impl UnsafeUnpin for OpenStreamStats",1,["perf::stats::OpenStreamStats"]],["impl UnsafeUnpin for Stats",1,["perf::stats::Stats"]],["impl UnsafeUnpin for StreamStats",1,["perf::stats::StreamStats"]],["impl UnsafeUnpin for CommonOpt",1,["perf::CommonOpt"]]]]]); if (window.register_implementors) { window.register_implementors(implementors); } else { window.pending_implementors = implementors; } })() -//{"start":57,"fragment_lengths":[981,312,384,6491,591,21637,876,1671]} \ No newline at end of file +//{"start":57,"fragment_lengths":[981,312,384,6638,591,21463,876,1671]} \ No newline at end of file diff --git a/pr/451/docs/trait.impl/core/panic/unwind_safe/trait.RefUnwindSafe.js b/pr/451/docs/trait.impl/core/panic/unwind_safe/trait.RefUnwindSafe.js index 028b48137..8b824f855 100644 --- a/pr/451/docs/trait.impl/core/panic/unwind_safe/trait.RefUnwindSafe.js +++ b/pr/451/docs/trait.impl/core/panic/unwind_safe/trait.RefUnwindSafe.js @@ -1,9 +1,9 @@ (function() { - var implementors = Object.fromEntries([["bench",[["impl RefUnwindSafe for CipherSuite",1,["bench::CipherSuite"]],["impl RefUnwindSafe for RuntimeType",1,["bench::RuntimeType"]],["impl RefUnwindSafe for Stats",1,["bench::stats::Stats"]],["impl RefUnwindSafe for StreamStats",1,["bench::stats::StreamStats"]],["impl RefUnwindSafe for TransferResult",1,["bench::stats::TransferResult"]],["impl RefUnwindSafe for Opt",1,["bench::Opt"]]]],["bulk",[["impl RefUnwindSafe for ClientStats",1,["bulk::ClientStats"]]]],["certificate",[["impl !RefUnwindSafe for SkipServerVerification",1,["certificate::SkipServerVerification"]]]],["noq",[["impl !RefUnwindSafe for ReadError",1,["noq::recv_stream::ReadError"]],["impl !RefUnwindSafe for ReadExactError",1,["noq::recv_stream::ReadExactError"]],["impl !RefUnwindSafe for ReadToEndError",1,["noq::recv_stream::ReadToEndError"]],["impl !RefUnwindSafe for ResetError",1,["noq::recv_stream::ResetError"]],["impl !RefUnwindSafe for SendDatagramError",1,["noq::connection::SendDatagramError"]],["impl !RefUnwindSafe for StoppedError",1,["noq::send_stream::StoppedError"]],["impl !RefUnwindSafe for WriteError",1,["noq::send_stream::WriteError"]],["impl !RefUnwindSafe for AddressDiscovery",1,["noq::path::AddressDiscovery"]],["impl !RefUnwindSafe for Connecting",1,["noq::connection::Connecting"]],["impl !RefUnwindSafe for Endpoint",1,["noq::endpoint::Endpoint"]],["impl !RefUnwindSafe for Incoming",1,["noq::incoming::Incoming"]],["impl !RefUnwindSafe for IncomingFuture",1,["noq::incoming::IncomingFuture"]],["impl !RefUnwindSafe for NatTraversalUpdates",1,["noq::event_stream::NatTraversalUpdates"]],["impl !RefUnwindSafe for ObservedExternalAddr",1,["noq::event_stream::ObservedExternalAddr"]],["impl !RefUnwindSafe for OnClosed",1,["noq::connection::OnClosed"]],["impl !RefUnwindSafe for OpenPath",1,["noq::path::OpenPath"]],["impl !RefUnwindSafe for PathEvents",1,["noq::event_stream::PathEvents"]],["impl !RefUnwindSafe for RetryError",1,["noq::incoming::RetryError"]],["impl !RefUnwindSafe for Stopped",1,["noq::send_stream::Stopped"]],["impl !RefUnwindSafe for ZeroRttAccepted",1,["noq::connection::ZeroRttAccepted"]],["impl RefUnwindSafe for Connection",1,["noq::connection::Connection"]],["impl RefUnwindSafe for EndpointStats",1,["noq::endpoint::EndpointStats"]],["impl RefUnwindSafe for Lagged",1,["noq::event_stream::Lagged"]],["impl RefUnwindSafe for Path",1,["noq::path::Path"]],["impl RefUnwindSafe for RecvStream",1,["noq::recv_stream::RecvStream"]],["impl RefUnwindSafe for SendStream",1,["noq::send_stream::SendStream"]],["impl RefUnwindSafe for SmolRuntime",1,["noq::runtime::smol::SmolRuntime"]],["impl RefUnwindSafe for TokioRuntime",1,["noq::runtime::tokio::TokioRuntime"]],["impl RefUnwindSafe for UnorderedRecvStream",1,["noq::recv_stream::UnorderedRecvStream"]],["impl RefUnwindSafe for WeakConnectionHandle",1,["noq::connection::WeakConnectionHandle"]],["impl RefUnwindSafe for WeakPathHandle",1,["noq::path::WeakPathHandle"]],["impl<'a> !RefUnwindSafe for Accept<'a>",1,["noq::endpoint::Accept"]],["impl<'a> !RefUnwindSafe for AcceptBi<'a>",1,["noq::connection::AcceptBi"]],["impl<'a> !RefUnwindSafe for AcceptUni<'a>",1,["noq::connection::AcceptUni"]],["impl<'a> !RefUnwindSafe for OpenBi<'a>",1,["noq::connection::OpenBi"]],["impl<'a> !RefUnwindSafe for OpenUni<'a>",1,["noq::connection::OpenUni"]],["impl<'a> !RefUnwindSafe for ReadDatagram<'a>",1,["noq::connection::ReadDatagram"]],["impl<'a> !RefUnwindSafe for SendDatagram<'a>",1,["noq::connection::SendDatagram"]]]],["noq_perf",[["impl RefUnwindSafe for Commands",1,["noq_perf::Commands"]],["impl RefUnwindSafe for Cli",1,["noq_perf::Cli"]]]],["noq_proto",[["impl !RefUnwindSafe for ConnectionError",1,["noq_proto::connection::ConnectionError"]],["impl !RefUnwindSafe for DatagramEvent",1,["noq_proto::endpoint::DatagramEvent"]],["impl !RefUnwindSafe for Event",1,["noq_proto::connection::Event"]],["impl !RefUnwindSafe for QuicClientConfig",1,["noq_proto::crypto::rustls::QuicClientConfig"]],["impl !RefUnwindSafe for QuicServerConfig",1,["noq_proto::crypto::rustls::QuicServerConfig"]],["impl !RefUnwindSafe for TlsSession",1,["noq_proto::crypto::rustls::TlsSession"]],["impl !RefUnwindSafe for Keys",1,["noq_proto::crypto::Keys"]],["impl !RefUnwindSafe for AcceptError",1,["noq_proto::endpoint::AcceptError"]],["impl !RefUnwindSafe for ClientConfig",1,["noq_proto::config::ClientConfig"]],["impl !RefUnwindSafe for Connection",1,["noq_proto::connection::Connection"]],["impl !RefUnwindSafe for Endpoint",1,["noq_proto::endpoint::Endpoint"]],["impl !RefUnwindSafe for EndpointConfig",1,["noq_proto::config::EndpointConfig"]],["impl !RefUnwindSafe for Incoming",1,["noq_proto::endpoint::Incoming"]],["impl !RefUnwindSafe for QlogConfig",1,["noq_proto::config::qlog::QlogConfig"]],["impl !RefUnwindSafe for RetryError",1,["noq_proto::endpoint::RetryError"]],["impl !RefUnwindSafe for ServerConfig",1,["noq_proto::config::ServerConfig"]],["impl !RefUnwindSafe for TransportConfig",1,["noq_proto::config::transport::TransportConfig"]],["impl !RefUnwindSafe for Error",1,["noq_proto::transport_error::Error"]],["impl !RefUnwindSafe for ValidationTokenConfig",1,["noq_proto::config::ValidationTokenConfig"]],["impl RefUnwindSafe for ClosePathError",1,["noq_proto::connection::ClosePathError"]],["impl RefUnwindSafe for ConfigError",1,["noq_proto::config::ConfigError"]],["impl RefUnwindSafe for ConnectError",1,["noq_proto::endpoint::ConnectError"]],["impl RefUnwindSafe for Dir",1,["noq_proto::Dir"]],["impl RefUnwindSafe for EcnCodepoint",1,["noq_proto::shared::EcnCodepoint"]],["impl RefUnwindSafe for FinishError",1,["noq_proto::connection::streams::send::FinishError"]],["impl RefUnwindSafe for FrameType",1,["noq_proto::frame::FrameType"]],["impl RefUnwindSafe for LongType",1,["noq_proto::packet::LongType"]],["impl RefUnwindSafe for MaybeFrame",1,["noq_proto::frame::MaybeFrame"]],["impl RefUnwindSafe for PacketDecodeError",1,["noq_proto::packet::PacketDecodeError"]],["impl RefUnwindSafe for PathAbandonReason",1,["noq_proto::connection::paths::PathAbandonReason"]],["impl RefUnwindSafe for PathError",1,["noq_proto::connection::PathError"]],["impl RefUnwindSafe for PathEvent",1,["noq_proto::connection::paths::PathEvent"]],["impl RefUnwindSafe for PathStatus",1,["noq_proto::connection::paths::PathStatus"]],["impl RefUnwindSafe for ProtectedHeader",1,["noq_proto::packet::ProtectedHeader"]],["impl RefUnwindSafe for ReadError",1,["noq_proto::connection::streams::recv::ReadError"]],["impl RefUnwindSafe for ReadableError",1,["noq_proto::connection::streams::recv::ReadableError"]],["impl RefUnwindSafe for SendDatagramError",1,["noq_proto::connection::datagrams::SendDatagramError"]],["impl RefUnwindSafe for SetPathStatusError",1,["noq_proto::connection::paths::SetPathStatusError"]],["impl RefUnwindSafe for Side",1,["noq_proto::Side"]],["impl RefUnwindSafe for StreamEvent",1,["noq_proto::connection::streams::StreamEvent"]],["impl RefUnwindSafe for WriteError",1,["noq_proto::connection::streams::send::WriteError"]],["impl RefUnwindSafe for Error",1,["noq_proto::n0_nat_traversal::Error"]],["impl RefUnwindSafe for Event",1,["noq_proto::n0_nat_traversal::Event"]],["impl RefUnwindSafe for Error",1,["noq_proto::transport_parameters::Error"]],["impl RefUnwindSafe for UnexpectedEnd",1,["noq_proto::coding::UnexpectedEnd"]],["impl RefUnwindSafe for Bbr",1,["noq_proto::congestion::bbr::Bbr"]],["impl RefUnwindSafe for BbrConfig",1,["noq_proto::congestion::bbr::BbrConfig"]],["impl RefUnwindSafe for ControllerMetrics",1,["noq_proto::congestion::ControllerMetrics"]],["impl RefUnwindSafe for Cubic",1,["noq_proto::congestion::cubic::Cubic"]],["impl RefUnwindSafe for CubicConfig",1,["noq_proto::congestion::cubic::CubicConfig"]],["impl RefUnwindSafe for NewReno",1,["noq_proto::congestion::new_reno::NewReno"]],["impl RefUnwindSafe for NewRenoConfig",1,["noq_proto::congestion::new_reno::NewRenoConfig"]],["impl RefUnwindSafe for HandshakeData",1,["noq_proto::crypto::rustls::HandshakeData"]],["impl RefUnwindSafe for NoInitialCipherSuite",1,["noq_proto::crypto::rustls::NoInitialCipherSuite"]],["impl RefUnwindSafe for CryptoError",1,["noq_proto::crypto::CryptoError"]],["impl RefUnwindSafe for ExportKeyingMaterialError",1,["noq_proto::crypto::ExportKeyingMaterialError"]],["impl RefUnwindSafe for UnsupportedVersion",1,["noq_proto::crypto::UnsupportedVersion"]],["impl RefUnwindSafe for AckFrequencyConfig",1,["noq_proto::config::transport::AckFrequencyConfig"]],["impl RefUnwindSafe for ApplicationClose",1,["noq_proto::frame::ApplicationClose"]],["impl RefUnwindSafe for BloomTokenLog",1,["noq_proto::bloom_token_log::BloomTokenLog"]],["impl RefUnwindSafe for Chunk",1,["noq_proto::connection::assembler::Chunk"]],["impl RefUnwindSafe for ClosedPath",1,["noq_proto::connection::paths::ClosedPath"]],["impl RefUnwindSafe for ClosedStream",1,["noq_proto::connection::streams::ClosedStream"]],["impl RefUnwindSafe for ConnectionClose",1,["noq_proto::frame::ConnectionClose"]],["impl RefUnwindSafe for ConnectionEvent",1,["noq_proto::shared::ConnectionEvent"]],["impl RefUnwindSafe for ConnectionHandle",1,["noq_proto::endpoint::ConnectionHandle"]],["impl RefUnwindSafe for ConnectionId",1,["noq_proto::shared::ConnectionId"]],["impl RefUnwindSafe for ConnectionStats",1,["noq_proto::connection::stats::ConnectionStats"]],["impl RefUnwindSafe for Datagram",1,["noq_proto::frame::Datagram"]],["impl RefUnwindSafe for DatagramInfo",1,["noq_proto::frame::DatagramInfo"]],["impl RefUnwindSafe for DecryptedInitial",1,["noq_proto::endpoint::DecryptedInitial"]],["impl RefUnwindSafe for EndpointEvent",1,["noq_proto::shared::EndpointEvent"]],["impl RefUnwindSafe for FixedLengthConnectionIdParser",1,["noq_proto::packet::FixedLengthConnectionIdParser"]],["impl RefUnwindSafe for FourTuple",1,["noq_proto::FourTuple"]],["impl RefUnwindSafe for FrameStats",1,["noq_proto::connection::stats::FrameStats"]],["impl RefUnwindSafe for HashedConnectionIdGenerator",1,["noq_proto::cid_generator::HashedConnectionIdGenerator"]],["impl RefUnwindSafe for IdleTimeout",1,["noq_proto::config::transport::IdleTimeout"]],["impl RefUnwindSafe for IncomingAlpns",1,["noq_proto::endpoint::IncomingAlpns"]],["impl RefUnwindSafe for InvalidCid",1,["noq_proto::cid_generator::InvalidCid"]],["impl RefUnwindSafe for InvalidFrameId",1,["noq_proto::frame::InvalidFrameId"]],["impl RefUnwindSafe for MtuDiscoveryConfig",1,["noq_proto::config::transport::MtuDiscoveryConfig"]],["impl RefUnwindSafe for MultipathNotNegotiated",1,["noq_proto::connection::MultipathNotNegotiated"]],["impl RefUnwindSafe for NoneTokenLog",1,["noq_proto::token::NoneTokenLog"]],["impl RefUnwindSafe for NoneTokenStore",1,["noq_proto::token::NoneTokenStore"]],["impl RefUnwindSafe for PartialDecode",1,["noq_proto::packet::PartialDecode"]],["impl RefUnwindSafe for PathId",1,["noq_proto::connection::paths::PathId"]],["impl RefUnwindSafe for PathStats",1,["noq_proto::connection::stats::PathStats"]],["impl RefUnwindSafe for ProtectedInitialHeader",1,["noq_proto::packet::ProtectedInitialHeader"]],["impl RefUnwindSafe for QlogFileFactory",1,["noq_proto::config::qlog::QlogFileFactory"]],["impl RefUnwindSafe for RandomConnectionIdGenerator",1,["noq_proto::cid_generator::RandomConnectionIdGenerator"]],["impl RefUnwindSafe for RttEstimator",1,["noq_proto::connection::paths::RttEstimator"]],["impl RefUnwindSafe for ShouldTransmit",1,["noq_proto::connection::streams::ShouldTransmit"]],["impl RefUnwindSafe for StdSystemTime",1,["noq_proto::config::StdSystemTime"]],["impl RefUnwindSafe for StreamId",1,["noq_proto::StreamId"]],["impl RefUnwindSafe for StreamInfo",1,["noq_proto::frame::StreamInfo"]],["impl RefUnwindSafe for TokenMemoryCache",1,["noq_proto::token_memory_cache::TokenMemoryCache"]],["impl RefUnwindSafe for TokenReuseError",1,["noq_proto::token::TokenReuseError"]],["impl RefUnwindSafe for Transmit",1,["noq_proto::Transmit"]],["impl RefUnwindSafe for Code",1,["noq_proto::transport_error::Code"]],["impl RefUnwindSafe for UdpStats",1,["noq_proto::connection::stats::UdpStats"]],["impl RefUnwindSafe for VarInt",1,["noq_proto::varint::VarInt"]],["impl RefUnwindSafe for VarIntBoundsExceeded",1,["noq_proto::varint::VarIntBoundsExceeded"]],["impl RefUnwindSafe for Written",1,["noq_proto::connection::streams::send::Written"]],["impl RefUnwindSafe for TransportParameters",1,["noq_proto::transport_parameters::TransportParameters"]],["impl<'a> !RefUnwindSafe for Datagrams<'a>",1,["noq_proto::connection::datagrams::Datagrams"]],["impl<'a> !RefUnwindSafe for SendStream<'a>",1,["noq_proto::connection::streams::SendStream"]],["impl<'a> !RefUnwindSafe for Streams<'a>",1,["noq_proto::connection::streams::Streams"]],["impl<'a> RefUnwindSafe for Chunks<'a>",1,["noq_proto::connection::streams::recv::Chunks"]],["impl<'a> RefUnwindSafe for RecvStream<'a>",1,["noq_proto::connection::streams::RecvStream"]],["impl<T> RefUnwindSafe for KeyPair<T>
    where\n T: RefUnwindSafe,
    ",1,["noq_proto::crypto::KeyPair"]]]],["noq_udp",[["impl RefUnwindSafe for EcnCodepoint",1,["noq_udp::EcnCodepoint"]],["impl RefUnwindSafe for RecvMeta",1,["noq_udp::RecvMeta"]],["impl RefUnwindSafe for UdpSocketState",1,["noq_udp::imp::UdpSocketState"]],["impl<'a> RefUnwindSafe for Transmit<'a>",1,["noq_udp::Transmit"]],["impl<'a> RefUnwindSafe for UdpSockRef<'a>",1,["noq_udp::UdpSockRef"]]]],["perf",[["impl !RefUnwindSafe for NoProtectionClientConfig",1,["perf::noprotection::NoProtectionClientConfig"]],["impl !RefUnwindSafe for NoProtectionServerConfig",1,["perf::noprotection::NoProtectionServerConfig"]],["impl RefUnwindSafe for CongestionAlgorithm",1,["perf::CongestionAlgorithm"]],["impl RefUnwindSafe for Opt",1,["perf::client::Opt"]],["impl RefUnwindSafe for Opt",1,["perf::server::Opt"]],["impl RefUnwindSafe for OpenStreamStats",1,["perf::stats::OpenStreamStats"]],["impl RefUnwindSafe for Stats",1,["perf::stats::Stats"]],["impl RefUnwindSafe for StreamStats",1,["perf::stats::StreamStats"]],["impl RefUnwindSafe for CommonOpt",1,["perf::CommonOpt"]]]]]); + var implementors = Object.fromEntries([["bench",[["impl RefUnwindSafe for CipherSuite",1,["bench::CipherSuite"]],["impl RefUnwindSafe for RuntimeType",1,["bench::RuntimeType"]],["impl RefUnwindSafe for Stats",1,["bench::stats::Stats"]],["impl RefUnwindSafe for StreamStats",1,["bench::stats::StreamStats"]],["impl RefUnwindSafe for TransferResult",1,["bench::stats::TransferResult"]],["impl RefUnwindSafe for Opt",1,["bench::Opt"]]]],["bulk",[["impl RefUnwindSafe for ClientStats",1,["bulk::ClientStats"]]]],["certificate",[["impl !RefUnwindSafe for SkipServerVerification",1,["certificate::SkipServerVerification"]]]],["noq",[["impl !RefUnwindSafe for ReadError",1,["noq::recv_stream::ReadError"]],["impl !RefUnwindSafe for ReadExactError",1,["noq::recv_stream::ReadExactError"]],["impl !RefUnwindSafe for ReadToEndError",1,["noq::recv_stream::ReadToEndError"]],["impl !RefUnwindSafe for ResetError",1,["noq::recv_stream::ResetError"]],["impl !RefUnwindSafe for SendDatagramError",1,["noq::connection::SendDatagramError"]],["impl !RefUnwindSafe for StoppedError",1,["noq::send_stream::StoppedError"]],["impl !RefUnwindSafe for WriteError",1,["noq::send_stream::WriteError"]],["impl !RefUnwindSafe for AddressDiscovery",1,["noq::path::AddressDiscovery"]],["impl !RefUnwindSafe for Closed",1,["noq::connection::Closed"]],["impl !RefUnwindSafe for Connecting",1,["noq::connection::Connecting"]],["impl !RefUnwindSafe for Endpoint",1,["noq::endpoint::Endpoint"]],["impl !RefUnwindSafe for Incoming",1,["noq::incoming::Incoming"]],["impl !RefUnwindSafe for IncomingFuture",1,["noq::incoming::IncomingFuture"]],["impl !RefUnwindSafe for NatTraversalUpdates",1,["noq::event_stream::NatTraversalUpdates"]],["impl !RefUnwindSafe for ObservedExternalAddr",1,["noq::event_stream::ObservedExternalAddr"]],["impl !RefUnwindSafe for OnClosed",1,["noq::connection::OnClosed"]],["impl !RefUnwindSafe for OpenPath",1,["noq::path::OpenPath"]],["impl !RefUnwindSafe for PathEvents",1,["noq::event_stream::PathEvents"]],["impl !RefUnwindSafe for RetryError",1,["noq::incoming::RetryError"]],["impl !RefUnwindSafe for Stopped",1,["noq::send_stream::Stopped"]],["impl !RefUnwindSafe for ZeroRttAccepted",1,["noq::connection::ZeroRttAccepted"]],["impl RefUnwindSafe for Connection",1,["noq::connection::Connection"]],["impl RefUnwindSafe for EndpointStats",1,["noq::endpoint::EndpointStats"]],["impl RefUnwindSafe for Lagged",1,["noq::event_stream::Lagged"]],["impl RefUnwindSafe for Path",1,["noq::path::Path"]],["impl RefUnwindSafe for RecvStream",1,["noq::recv_stream::RecvStream"]],["impl RefUnwindSafe for SendStream",1,["noq::send_stream::SendStream"]],["impl RefUnwindSafe for SmolRuntime",1,["noq::runtime::smol::SmolRuntime"]],["impl RefUnwindSafe for TokioRuntime",1,["noq::runtime::tokio::TokioRuntime"]],["impl RefUnwindSafe for UnorderedRecvStream",1,["noq::recv_stream::UnorderedRecvStream"]],["impl RefUnwindSafe for WeakConnectionHandle",1,["noq::connection::WeakConnectionHandle"]],["impl RefUnwindSafe for WeakPathHandle",1,["noq::path::WeakPathHandle"]],["impl<'a> !RefUnwindSafe for Accept<'a>",1,["noq::endpoint::Accept"]],["impl<'a> !RefUnwindSafe for AcceptBi<'a>",1,["noq::connection::AcceptBi"]],["impl<'a> !RefUnwindSafe for AcceptUni<'a>",1,["noq::connection::AcceptUni"]],["impl<'a> !RefUnwindSafe for OpenBi<'a>",1,["noq::connection::OpenBi"]],["impl<'a> !RefUnwindSafe for OpenUni<'a>",1,["noq::connection::OpenUni"]],["impl<'a> !RefUnwindSafe for ReadDatagram<'a>",1,["noq::connection::ReadDatagram"]],["impl<'a> !RefUnwindSafe for SendDatagram<'a>",1,["noq::connection::SendDatagram"]]]],["noq_perf",[["impl RefUnwindSafe for Commands",1,["noq_perf::Commands"]],["impl RefUnwindSafe for Cli",1,["noq_perf::Cli"]]]],["noq_proto",[["impl !RefUnwindSafe for ConnectionError",1,["noq_proto::connection::ConnectionError"]],["impl !RefUnwindSafe for DatagramEvent",1,["noq_proto::endpoint::DatagramEvent"]],["impl !RefUnwindSafe for Event",1,["noq_proto::connection::Event"]],["impl !RefUnwindSafe for QuicClientConfig",1,["noq_proto::crypto::rustls::QuicClientConfig"]],["impl !RefUnwindSafe for QuicServerConfig",1,["noq_proto::crypto::rustls::QuicServerConfig"]],["impl !RefUnwindSafe for TlsSession",1,["noq_proto::crypto::rustls::TlsSession"]],["impl !RefUnwindSafe for Keys",1,["noq_proto::crypto::Keys"]],["impl !RefUnwindSafe for AcceptError",1,["noq_proto::endpoint::AcceptError"]],["impl !RefUnwindSafe for ClientConfig",1,["noq_proto::config::ClientConfig"]],["impl !RefUnwindSafe for Connection",1,["noq_proto::connection::Connection"]],["impl !RefUnwindSafe for Endpoint",1,["noq_proto::endpoint::Endpoint"]],["impl !RefUnwindSafe for EndpointConfig",1,["noq_proto::config::EndpointConfig"]],["impl !RefUnwindSafe for Incoming",1,["noq_proto::endpoint::Incoming"]],["impl !RefUnwindSafe for QlogConfig",1,["noq_proto::config::qlog::QlogConfig"]],["impl !RefUnwindSafe for RetryError",1,["noq_proto::endpoint::RetryError"]],["impl !RefUnwindSafe for ServerConfig",1,["noq_proto::config::ServerConfig"]],["impl !RefUnwindSafe for TransportConfig",1,["noq_proto::config::transport::TransportConfig"]],["impl !RefUnwindSafe for Error",1,["noq_proto::transport_error::Error"]],["impl !RefUnwindSafe for ValidationTokenConfig",1,["noq_proto::config::ValidationTokenConfig"]],["impl RefUnwindSafe for ClosePathError",1,["noq_proto::connection::ClosePathError"]],["impl RefUnwindSafe for ConfigError",1,["noq_proto::config::ConfigError"]],["impl RefUnwindSafe for ConnectError",1,["noq_proto::endpoint::ConnectError"]],["impl RefUnwindSafe for Dir",1,["noq_proto::Dir"]],["impl RefUnwindSafe for EcnCodepoint",1,["noq_proto::shared::EcnCodepoint"]],["impl RefUnwindSafe for FinishError",1,["noq_proto::connection::streams::send::FinishError"]],["impl RefUnwindSafe for FrameType",1,["noq_proto::frame::FrameType"]],["impl RefUnwindSafe for LongType",1,["noq_proto::packet::LongType"]],["impl RefUnwindSafe for MaybeFrame",1,["noq_proto::frame::MaybeFrame"]],["impl RefUnwindSafe for PacketDecodeError",1,["noq_proto::packet::PacketDecodeError"]],["impl RefUnwindSafe for PathAbandonReason",1,["noq_proto::connection::paths::PathAbandonReason"]],["impl RefUnwindSafe for PathError",1,["noq_proto::connection::PathError"]],["impl RefUnwindSafe for PathEvent",1,["noq_proto::connection::paths::PathEvent"]],["impl RefUnwindSafe for PathStatus",1,["noq_proto::connection::paths::PathStatus"]],["impl RefUnwindSafe for ProtectedHeader",1,["noq_proto::packet::ProtectedHeader"]],["impl RefUnwindSafe for ReadError",1,["noq_proto::connection::streams::recv::ReadError"]],["impl RefUnwindSafe for ReadableError",1,["noq_proto::connection::streams::recv::ReadableError"]],["impl RefUnwindSafe for SendDatagramError",1,["noq_proto::connection::datagrams::SendDatagramError"]],["impl RefUnwindSafe for SetPathStatusError",1,["noq_proto::connection::paths::SetPathStatusError"]],["impl RefUnwindSafe for Side",1,["noq_proto::Side"]],["impl RefUnwindSafe for StreamEvent",1,["noq_proto::connection::streams::StreamEvent"]],["impl RefUnwindSafe for WriteError",1,["noq_proto::connection::streams::send::WriteError"]],["impl RefUnwindSafe for Error",1,["noq_proto::n0_nat_traversal::Error"]],["impl RefUnwindSafe for Event",1,["noq_proto::n0_nat_traversal::Event"]],["impl RefUnwindSafe for Error",1,["noq_proto::transport_parameters::Error"]],["impl RefUnwindSafe for UnexpectedEnd",1,["noq_proto::coding::UnexpectedEnd"]],["impl RefUnwindSafe for Bbr3",1,["noq_proto::congestion::bbr3::Bbr3"]],["impl RefUnwindSafe for Bbr3Config",1,["noq_proto::congestion::bbr3::Bbr3Config"]],["impl RefUnwindSafe for ControllerMetrics",1,["noq_proto::congestion::ControllerMetrics"]],["impl RefUnwindSafe for Cubic",1,["noq_proto::congestion::cubic::Cubic"]],["impl RefUnwindSafe for CubicConfig",1,["noq_proto::congestion::cubic::CubicConfig"]],["impl RefUnwindSafe for NewReno",1,["noq_proto::congestion::new_reno::NewReno"]],["impl RefUnwindSafe for NewRenoConfig",1,["noq_proto::congestion::new_reno::NewRenoConfig"]],["impl RefUnwindSafe for HandshakeData",1,["noq_proto::crypto::rustls::HandshakeData"]],["impl RefUnwindSafe for NoInitialCipherSuite",1,["noq_proto::crypto::rustls::NoInitialCipherSuite"]],["impl RefUnwindSafe for CryptoError",1,["noq_proto::crypto::CryptoError"]],["impl RefUnwindSafe for ExportKeyingMaterialError",1,["noq_proto::crypto::ExportKeyingMaterialError"]],["impl RefUnwindSafe for UnsupportedVersion",1,["noq_proto::crypto::UnsupportedVersion"]],["impl RefUnwindSafe for AckFrequencyConfig",1,["noq_proto::config::transport::AckFrequencyConfig"]],["impl RefUnwindSafe for ApplicationClose",1,["noq_proto::frame::ApplicationClose"]],["impl RefUnwindSafe for BloomTokenLog",1,["noq_proto::bloom_token_log::BloomTokenLog"]],["impl RefUnwindSafe for Chunk",1,["noq_proto::connection::assembler::Chunk"]],["impl RefUnwindSafe for ClosedPath",1,["noq_proto::connection::paths::ClosedPath"]],["impl RefUnwindSafe for ClosedStream",1,["noq_proto::connection::streams::ClosedStream"]],["impl RefUnwindSafe for ConnectionClose",1,["noq_proto::frame::ConnectionClose"]],["impl RefUnwindSafe for ConnectionEvent",1,["noq_proto::shared::ConnectionEvent"]],["impl RefUnwindSafe for ConnectionHandle",1,["noq_proto::endpoint::ConnectionHandle"]],["impl RefUnwindSafe for ConnectionId",1,["noq_proto::shared::ConnectionId"]],["impl RefUnwindSafe for ConnectionStats",1,["noq_proto::connection::stats::ConnectionStats"]],["impl RefUnwindSafe for Datagram",1,["noq_proto::frame::Datagram"]],["impl RefUnwindSafe for DatagramInfo",1,["noq_proto::frame::DatagramInfo"]],["impl RefUnwindSafe for DecryptedInitial",1,["noq_proto::endpoint::DecryptedInitial"]],["impl RefUnwindSafe for EndpointEvent",1,["noq_proto::shared::EndpointEvent"]],["impl RefUnwindSafe for FixedLengthConnectionIdParser",1,["noq_proto::packet::FixedLengthConnectionIdParser"]],["impl RefUnwindSafe for FourTuple",1,["noq_proto::FourTuple"]],["impl RefUnwindSafe for FrameStats",1,["noq_proto::connection::stats::FrameStats"]],["impl RefUnwindSafe for HashedConnectionIdGenerator",1,["noq_proto::cid_generator::HashedConnectionIdGenerator"]],["impl RefUnwindSafe for IdleTimeout",1,["noq_proto::config::transport::IdleTimeout"]],["impl RefUnwindSafe for IncomingAlpns",1,["noq_proto::endpoint::IncomingAlpns"]],["impl RefUnwindSafe for InvalidCid",1,["noq_proto::cid_generator::InvalidCid"]],["impl RefUnwindSafe for InvalidFrameId",1,["noq_proto::frame::InvalidFrameId"]],["impl RefUnwindSafe for MtuDiscoveryConfig",1,["noq_proto::config::transport::MtuDiscoveryConfig"]],["impl RefUnwindSafe for MultipathNotNegotiated",1,["noq_proto::connection::MultipathNotNegotiated"]],["impl RefUnwindSafe for NoneTokenLog",1,["noq_proto::token::NoneTokenLog"]],["impl RefUnwindSafe for NoneTokenStore",1,["noq_proto::token::NoneTokenStore"]],["impl RefUnwindSafe for PartialDecode",1,["noq_proto::packet::PartialDecode"]],["impl RefUnwindSafe for PathId",1,["noq_proto::connection::paths::PathId"]],["impl RefUnwindSafe for PathStats",1,["noq_proto::connection::stats::PathStats"]],["impl RefUnwindSafe for ProtectedInitialHeader",1,["noq_proto::packet::ProtectedInitialHeader"]],["impl RefUnwindSafe for QlogFileFactory",1,["noq_proto::config::qlog::QlogFileFactory"]],["impl RefUnwindSafe for RandomConnectionIdGenerator",1,["noq_proto::cid_generator::RandomConnectionIdGenerator"]],["impl RefUnwindSafe for RttEstimator",1,["noq_proto::connection::paths::RttEstimator"]],["impl RefUnwindSafe for ShouldTransmit",1,["noq_proto::connection::streams::ShouldTransmit"]],["impl RefUnwindSafe for StdSystemTime",1,["noq_proto::config::StdSystemTime"]],["impl RefUnwindSafe for StreamId",1,["noq_proto::StreamId"]],["impl RefUnwindSafe for StreamInfo",1,["noq_proto::frame::StreamInfo"]],["impl RefUnwindSafe for TokenMemoryCache",1,["noq_proto::token_memory_cache::TokenMemoryCache"]],["impl RefUnwindSafe for TokenReuseError",1,["noq_proto::token::TokenReuseError"]],["impl RefUnwindSafe for Transmit",1,["noq_proto::Transmit"]],["impl RefUnwindSafe for Code",1,["noq_proto::transport_error::Code"]],["impl RefUnwindSafe for UdpStats",1,["noq_proto::connection::stats::UdpStats"]],["impl RefUnwindSafe for VarInt",1,["noq_proto::varint::VarInt"]],["impl RefUnwindSafe for VarIntBoundsExceeded",1,["noq_proto::varint::VarIntBoundsExceeded"]],["impl RefUnwindSafe for TransportParameters",1,["noq_proto::transport_parameters::TransportParameters"]],["impl<'a> !RefUnwindSafe for Datagrams<'a>",1,["noq_proto::connection::datagrams::Datagrams"]],["impl<'a> !RefUnwindSafe for SendStream<'a>",1,["noq_proto::connection::streams::SendStream"]],["impl<'a> !RefUnwindSafe for Streams<'a>",1,["noq_proto::connection::streams::Streams"]],["impl<'a> RefUnwindSafe for Chunks<'a>",1,["noq_proto::connection::streams::recv::Chunks"]],["impl<'a> RefUnwindSafe for RecvStream<'a>",1,["noq_proto::connection::streams::RecvStream"]],["impl<T> RefUnwindSafe for KeyPair<T>
    where\n T: RefUnwindSafe,
    ",1,["noq_proto::crypto::KeyPair"]]]],["noq_udp",[["impl RefUnwindSafe for EcnCodepoint",1,["noq_udp::EcnCodepoint"]],["impl RefUnwindSafe for RecvMeta",1,["noq_udp::RecvMeta"]],["impl RefUnwindSafe for UdpSocketState",1,["noq_udp::imp::UdpSocketState"]],["impl<'a> RefUnwindSafe for Transmit<'a>",1,["noq_udp::Transmit"]],["impl<'a> RefUnwindSafe for UdpSockRef<'a>",1,["noq_udp::UdpSockRef"]]]],["perf",[["impl !RefUnwindSafe for NoProtectionClientConfig",1,["perf::noprotection::NoProtectionClientConfig"]],["impl !RefUnwindSafe for NoProtectionServerConfig",1,["perf::noprotection::NoProtectionServerConfig"]],["impl RefUnwindSafe for CongestionAlgorithm",1,["perf::CongestionAlgorithm"]],["impl RefUnwindSafe for Opt",1,["perf::client::Opt"]],["impl RefUnwindSafe for Opt",1,["perf::server::Opt"]],["impl RefUnwindSafe for OpenStreamStats",1,["perf::stats::OpenStreamStats"]],["impl RefUnwindSafe for Stats",1,["perf::stats::Stats"]],["impl RefUnwindSafe for StreamStats",1,["perf::stats::StreamStats"]],["impl RefUnwindSafe for CommonOpt",1,["perf::CommonOpt"]]]]]); if (window.register_implementors) { window.register_implementors(implementors); } else { window.pending_implementors = implementors; } })() -//{"start":57,"fragment_lengths":[2013,341,414,13046,649,40751,1736,3221]} \ No newline at end of file +//{"start":57,"fragment_lengths":[2013,341,414,13366,649,40405,1736,3221]} \ No newline at end of file diff --git a/pr/451/docs/trait.impl/core/panic/unwind_safe/trait.UnwindSafe.js b/pr/451/docs/trait.impl/core/panic/unwind_safe/trait.UnwindSafe.js index 61556ea3f..2b78edca3 100644 --- a/pr/451/docs/trait.impl/core/panic/unwind_safe/trait.UnwindSafe.js +++ b/pr/451/docs/trait.impl/core/panic/unwind_safe/trait.UnwindSafe.js @@ -1,9 +1,9 @@ (function() { - var implementors = Object.fromEntries([["bench",[["impl UnwindSafe for CipherSuite",1,["bench::CipherSuite"]],["impl UnwindSafe for RuntimeType",1,["bench::RuntimeType"]],["impl UnwindSafe for Stats",1,["bench::stats::Stats"]],["impl UnwindSafe for StreamStats",1,["bench::stats::StreamStats"]],["impl UnwindSafe for TransferResult",1,["bench::stats::TransferResult"]],["impl UnwindSafe for Opt",1,["bench::Opt"]]]],["bulk",[["impl UnwindSafe for ClientStats",1,["bulk::ClientStats"]]]],["certificate",[["impl !UnwindSafe for SkipServerVerification",1,["certificate::SkipServerVerification"]]]],["noq",[["impl !UnwindSafe for ReadError",1,["noq::recv_stream::ReadError"]],["impl !UnwindSafe for ReadExactError",1,["noq::recv_stream::ReadExactError"]],["impl !UnwindSafe for ReadToEndError",1,["noq::recv_stream::ReadToEndError"]],["impl !UnwindSafe for ResetError",1,["noq::recv_stream::ResetError"]],["impl !UnwindSafe for SendDatagramError",1,["noq::connection::SendDatagramError"]],["impl !UnwindSafe for StoppedError",1,["noq::send_stream::StoppedError"]],["impl !UnwindSafe for WriteError",1,["noq::send_stream::WriteError"]],["impl !UnwindSafe for AddressDiscovery",1,["noq::path::AddressDiscovery"]],["impl !UnwindSafe for Connecting",1,["noq::connection::Connecting"]],["impl !UnwindSafe for Endpoint",1,["noq::endpoint::Endpoint"]],["impl !UnwindSafe for Incoming",1,["noq::incoming::Incoming"]],["impl !UnwindSafe for IncomingFuture",1,["noq::incoming::IncomingFuture"]],["impl !UnwindSafe for NatTraversalUpdates",1,["noq::event_stream::NatTraversalUpdates"]],["impl !UnwindSafe for ObservedExternalAddr",1,["noq::event_stream::ObservedExternalAddr"]],["impl !UnwindSafe for OnClosed",1,["noq::connection::OnClosed"]],["impl !UnwindSafe for OpenPath",1,["noq::path::OpenPath"]],["impl !UnwindSafe for PathEvents",1,["noq::event_stream::PathEvents"]],["impl !UnwindSafe for RetryError",1,["noq::incoming::RetryError"]],["impl !UnwindSafe for Stopped",1,["noq::send_stream::Stopped"]],["impl !UnwindSafe for ZeroRttAccepted",1,["noq::connection::ZeroRttAccepted"]],["impl UnwindSafe for Connection",1,["noq::connection::Connection"]],["impl UnwindSafe for EndpointStats",1,["noq::endpoint::EndpointStats"]],["impl UnwindSafe for Lagged",1,["noq::event_stream::Lagged"]],["impl UnwindSafe for Path",1,["noq::path::Path"]],["impl UnwindSafe for RecvStream",1,["noq::recv_stream::RecvStream"]],["impl UnwindSafe for SendStream",1,["noq::send_stream::SendStream"]],["impl UnwindSafe for SmolRuntime",1,["noq::runtime::smol::SmolRuntime"]],["impl UnwindSafe for TokioRuntime",1,["noq::runtime::tokio::TokioRuntime"]],["impl UnwindSafe for UnorderedRecvStream",1,["noq::recv_stream::UnorderedRecvStream"]],["impl UnwindSafe for WeakConnectionHandle",1,["noq::connection::WeakConnectionHandle"]],["impl UnwindSafe for WeakPathHandle",1,["noq::path::WeakPathHandle"]],["impl<'a> !UnwindSafe for Accept<'a>",1,["noq::endpoint::Accept"]],["impl<'a> !UnwindSafe for AcceptBi<'a>",1,["noq::connection::AcceptBi"]],["impl<'a> !UnwindSafe for AcceptUni<'a>",1,["noq::connection::AcceptUni"]],["impl<'a> !UnwindSafe for OpenBi<'a>",1,["noq::connection::OpenBi"]],["impl<'a> !UnwindSafe for OpenUni<'a>",1,["noq::connection::OpenUni"]],["impl<'a> !UnwindSafe for ReadDatagram<'a>",1,["noq::connection::ReadDatagram"]],["impl<'a> !UnwindSafe for SendDatagram<'a>",1,["noq::connection::SendDatagram"]]]],["noq_perf",[["impl UnwindSafe for Commands",1,["noq_perf::Commands"]],["impl UnwindSafe for Cli",1,["noq_perf::Cli"]]]],["noq_proto",[["impl !UnwindSafe for ConnectionError",1,["noq_proto::connection::ConnectionError"]],["impl !UnwindSafe for DatagramEvent",1,["noq_proto::endpoint::DatagramEvent"]],["impl !UnwindSafe for Event",1,["noq_proto::connection::Event"]],["impl !UnwindSafe for QuicClientConfig",1,["noq_proto::crypto::rustls::QuicClientConfig"]],["impl !UnwindSafe for QuicServerConfig",1,["noq_proto::crypto::rustls::QuicServerConfig"]],["impl !UnwindSafe for TlsSession",1,["noq_proto::crypto::rustls::TlsSession"]],["impl !UnwindSafe for Keys",1,["noq_proto::crypto::Keys"]],["impl !UnwindSafe for AcceptError",1,["noq_proto::endpoint::AcceptError"]],["impl !UnwindSafe for ClientConfig",1,["noq_proto::config::ClientConfig"]],["impl !UnwindSafe for Connection",1,["noq_proto::connection::Connection"]],["impl !UnwindSafe for Endpoint",1,["noq_proto::endpoint::Endpoint"]],["impl !UnwindSafe for EndpointConfig",1,["noq_proto::config::EndpointConfig"]],["impl !UnwindSafe for Incoming",1,["noq_proto::endpoint::Incoming"]],["impl !UnwindSafe for QlogConfig",1,["noq_proto::config::qlog::QlogConfig"]],["impl !UnwindSafe for RetryError",1,["noq_proto::endpoint::RetryError"]],["impl !UnwindSafe for ServerConfig",1,["noq_proto::config::ServerConfig"]],["impl !UnwindSafe for TransportConfig",1,["noq_proto::config::transport::TransportConfig"]],["impl !UnwindSafe for Error",1,["noq_proto::transport_error::Error"]],["impl !UnwindSafe for ValidationTokenConfig",1,["noq_proto::config::ValidationTokenConfig"]],["impl UnwindSafe for ClosePathError",1,["noq_proto::connection::ClosePathError"]],["impl UnwindSafe for ConfigError",1,["noq_proto::config::ConfigError"]],["impl UnwindSafe for ConnectError",1,["noq_proto::endpoint::ConnectError"]],["impl UnwindSafe for Dir",1,["noq_proto::Dir"]],["impl UnwindSafe for EcnCodepoint",1,["noq_proto::shared::EcnCodepoint"]],["impl UnwindSafe for FinishError",1,["noq_proto::connection::streams::send::FinishError"]],["impl UnwindSafe for FrameType",1,["noq_proto::frame::FrameType"]],["impl UnwindSafe for LongType",1,["noq_proto::packet::LongType"]],["impl UnwindSafe for MaybeFrame",1,["noq_proto::frame::MaybeFrame"]],["impl UnwindSafe for PacketDecodeError",1,["noq_proto::packet::PacketDecodeError"]],["impl UnwindSafe for PathAbandonReason",1,["noq_proto::connection::paths::PathAbandonReason"]],["impl UnwindSafe for PathError",1,["noq_proto::connection::PathError"]],["impl UnwindSafe for PathEvent",1,["noq_proto::connection::paths::PathEvent"]],["impl UnwindSafe for PathStatus",1,["noq_proto::connection::paths::PathStatus"]],["impl UnwindSafe for ProtectedHeader",1,["noq_proto::packet::ProtectedHeader"]],["impl UnwindSafe for ReadError",1,["noq_proto::connection::streams::recv::ReadError"]],["impl UnwindSafe for ReadableError",1,["noq_proto::connection::streams::recv::ReadableError"]],["impl UnwindSafe for SendDatagramError",1,["noq_proto::connection::datagrams::SendDatagramError"]],["impl UnwindSafe for SetPathStatusError",1,["noq_proto::connection::paths::SetPathStatusError"]],["impl UnwindSafe for Side",1,["noq_proto::Side"]],["impl UnwindSafe for StreamEvent",1,["noq_proto::connection::streams::StreamEvent"]],["impl UnwindSafe for WriteError",1,["noq_proto::connection::streams::send::WriteError"]],["impl UnwindSafe for Error",1,["noq_proto::n0_nat_traversal::Error"]],["impl UnwindSafe for Event",1,["noq_proto::n0_nat_traversal::Event"]],["impl UnwindSafe for Error",1,["noq_proto::transport_parameters::Error"]],["impl UnwindSafe for UnexpectedEnd",1,["noq_proto::coding::UnexpectedEnd"]],["impl UnwindSafe for Bbr",1,["noq_proto::congestion::bbr::Bbr"]],["impl UnwindSafe for BbrConfig",1,["noq_proto::congestion::bbr::BbrConfig"]],["impl UnwindSafe for ControllerMetrics",1,["noq_proto::congestion::ControllerMetrics"]],["impl UnwindSafe for Cubic",1,["noq_proto::congestion::cubic::Cubic"]],["impl UnwindSafe for CubicConfig",1,["noq_proto::congestion::cubic::CubicConfig"]],["impl UnwindSafe for NewReno",1,["noq_proto::congestion::new_reno::NewReno"]],["impl UnwindSafe for NewRenoConfig",1,["noq_proto::congestion::new_reno::NewRenoConfig"]],["impl UnwindSafe for HandshakeData",1,["noq_proto::crypto::rustls::HandshakeData"]],["impl UnwindSafe for NoInitialCipherSuite",1,["noq_proto::crypto::rustls::NoInitialCipherSuite"]],["impl UnwindSafe for CryptoError",1,["noq_proto::crypto::CryptoError"]],["impl UnwindSafe for ExportKeyingMaterialError",1,["noq_proto::crypto::ExportKeyingMaterialError"]],["impl UnwindSafe for UnsupportedVersion",1,["noq_proto::crypto::UnsupportedVersion"]],["impl UnwindSafe for AckFrequencyConfig",1,["noq_proto::config::transport::AckFrequencyConfig"]],["impl UnwindSafe for ApplicationClose",1,["noq_proto::frame::ApplicationClose"]],["impl UnwindSafe for BloomTokenLog",1,["noq_proto::bloom_token_log::BloomTokenLog"]],["impl UnwindSafe for Chunk",1,["noq_proto::connection::assembler::Chunk"]],["impl UnwindSafe for ClosedPath",1,["noq_proto::connection::paths::ClosedPath"]],["impl UnwindSafe for ClosedStream",1,["noq_proto::connection::streams::ClosedStream"]],["impl UnwindSafe for ConnectionClose",1,["noq_proto::frame::ConnectionClose"]],["impl UnwindSafe for ConnectionEvent",1,["noq_proto::shared::ConnectionEvent"]],["impl UnwindSafe for ConnectionHandle",1,["noq_proto::endpoint::ConnectionHandle"]],["impl UnwindSafe for ConnectionId",1,["noq_proto::shared::ConnectionId"]],["impl UnwindSafe for ConnectionStats",1,["noq_proto::connection::stats::ConnectionStats"]],["impl UnwindSafe for Datagram",1,["noq_proto::frame::Datagram"]],["impl UnwindSafe for DatagramInfo",1,["noq_proto::frame::DatagramInfo"]],["impl UnwindSafe for DecryptedInitial",1,["noq_proto::endpoint::DecryptedInitial"]],["impl UnwindSafe for EndpointEvent",1,["noq_proto::shared::EndpointEvent"]],["impl UnwindSafe for FixedLengthConnectionIdParser",1,["noq_proto::packet::FixedLengthConnectionIdParser"]],["impl UnwindSafe for FourTuple",1,["noq_proto::FourTuple"]],["impl UnwindSafe for FrameStats",1,["noq_proto::connection::stats::FrameStats"]],["impl UnwindSafe for HashedConnectionIdGenerator",1,["noq_proto::cid_generator::HashedConnectionIdGenerator"]],["impl UnwindSafe for IdleTimeout",1,["noq_proto::config::transport::IdleTimeout"]],["impl UnwindSafe for IncomingAlpns",1,["noq_proto::endpoint::IncomingAlpns"]],["impl UnwindSafe for InvalidCid",1,["noq_proto::cid_generator::InvalidCid"]],["impl UnwindSafe for InvalidFrameId",1,["noq_proto::frame::InvalidFrameId"]],["impl UnwindSafe for MtuDiscoveryConfig",1,["noq_proto::config::transport::MtuDiscoveryConfig"]],["impl UnwindSafe for MultipathNotNegotiated",1,["noq_proto::connection::MultipathNotNegotiated"]],["impl UnwindSafe for NoneTokenLog",1,["noq_proto::token::NoneTokenLog"]],["impl UnwindSafe for NoneTokenStore",1,["noq_proto::token::NoneTokenStore"]],["impl UnwindSafe for PartialDecode",1,["noq_proto::packet::PartialDecode"]],["impl UnwindSafe for PathId",1,["noq_proto::connection::paths::PathId"]],["impl UnwindSafe for PathStats",1,["noq_proto::connection::stats::PathStats"]],["impl UnwindSafe for ProtectedInitialHeader",1,["noq_proto::packet::ProtectedInitialHeader"]],["impl UnwindSafe for QlogFileFactory",1,["noq_proto::config::qlog::QlogFileFactory"]],["impl UnwindSafe for RandomConnectionIdGenerator",1,["noq_proto::cid_generator::RandomConnectionIdGenerator"]],["impl UnwindSafe for RttEstimator",1,["noq_proto::connection::paths::RttEstimator"]],["impl UnwindSafe for ShouldTransmit",1,["noq_proto::connection::streams::ShouldTransmit"]],["impl UnwindSafe for StdSystemTime",1,["noq_proto::config::StdSystemTime"]],["impl UnwindSafe for StreamId",1,["noq_proto::StreamId"]],["impl UnwindSafe for StreamInfo",1,["noq_proto::frame::StreamInfo"]],["impl UnwindSafe for TokenMemoryCache",1,["noq_proto::token_memory_cache::TokenMemoryCache"]],["impl UnwindSafe for TokenReuseError",1,["noq_proto::token::TokenReuseError"]],["impl UnwindSafe for Transmit",1,["noq_proto::Transmit"]],["impl UnwindSafe for Code",1,["noq_proto::transport_error::Code"]],["impl UnwindSafe for UdpStats",1,["noq_proto::connection::stats::UdpStats"]],["impl UnwindSafe for VarInt",1,["noq_proto::varint::VarInt"]],["impl UnwindSafe for VarIntBoundsExceeded",1,["noq_proto::varint::VarIntBoundsExceeded"]],["impl UnwindSafe for Written",1,["noq_proto::connection::streams::send::Written"]],["impl UnwindSafe for TransportParameters",1,["noq_proto::transport_parameters::TransportParameters"]],["impl<'a> !UnwindSafe for Chunks<'a>",1,["noq_proto::connection::streams::recv::Chunks"]],["impl<'a> !UnwindSafe for Datagrams<'a>",1,["noq_proto::connection::datagrams::Datagrams"]],["impl<'a> !UnwindSafe for RecvStream<'a>",1,["noq_proto::connection::streams::RecvStream"]],["impl<'a> !UnwindSafe for SendStream<'a>",1,["noq_proto::connection::streams::SendStream"]],["impl<'a> !UnwindSafe for Streams<'a>",1,["noq_proto::connection::streams::Streams"]],["impl<T> UnwindSafe for KeyPair<T>
    where\n T: UnwindSafe,
    ",1,["noq_proto::crypto::KeyPair"]]]],["noq_udp",[["impl UnwindSafe for EcnCodepoint",1,["noq_udp::EcnCodepoint"]],["impl UnwindSafe for RecvMeta",1,["noq_udp::RecvMeta"]],["impl UnwindSafe for UdpSocketState",1,["noq_udp::imp::UdpSocketState"]],["impl<'a> UnwindSafe for Transmit<'a>",1,["noq_udp::Transmit"]],["impl<'a> UnwindSafe for UdpSockRef<'a>",1,["noq_udp::UdpSockRef"]]]],["perf",[["impl !UnwindSafe for NoProtectionClientConfig",1,["perf::noprotection::NoProtectionClientConfig"]],["impl !UnwindSafe for NoProtectionServerConfig",1,["perf::noprotection::NoProtectionServerConfig"]],["impl UnwindSafe for CongestionAlgorithm",1,["perf::CongestionAlgorithm"]],["impl UnwindSafe for Opt",1,["perf::client::Opt"]],["impl UnwindSafe for Opt",1,["perf::server::Opt"]],["impl UnwindSafe for OpenStreamStats",1,["perf::stats::OpenStreamStats"]],["impl UnwindSafe for Stats",1,["perf::stats::Stats"]],["impl UnwindSafe for StreamStats",1,["perf::stats::StreamStats"]],["impl UnwindSafe for CommonOpt",1,["perf::CommonOpt"]]]]]); + var implementors = Object.fromEntries([["bench",[["impl UnwindSafe for CipherSuite",1,["bench::CipherSuite"]],["impl UnwindSafe for RuntimeType",1,["bench::RuntimeType"]],["impl UnwindSafe for Stats",1,["bench::stats::Stats"]],["impl UnwindSafe for StreamStats",1,["bench::stats::StreamStats"]],["impl UnwindSafe for TransferResult",1,["bench::stats::TransferResult"]],["impl UnwindSafe for Opt",1,["bench::Opt"]]]],["bulk",[["impl UnwindSafe for ClientStats",1,["bulk::ClientStats"]]]],["certificate",[["impl !UnwindSafe for SkipServerVerification",1,["certificate::SkipServerVerification"]]]],["noq",[["impl !UnwindSafe for ReadError",1,["noq::recv_stream::ReadError"]],["impl !UnwindSafe for ReadExactError",1,["noq::recv_stream::ReadExactError"]],["impl !UnwindSafe for ReadToEndError",1,["noq::recv_stream::ReadToEndError"]],["impl !UnwindSafe for ResetError",1,["noq::recv_stream::ResetError"]],["impl !UnwindSafe for SendDatagramError",1,["noq::connection::SendDatagramError"]],["impl !UnwindSafe for StoppedError",1,["noq::send_stream::StoppedError"]],["impl !UnwindSafe for WriteError",1,["noq::send_stream::WriteError"]],["impl !UnwindSafe for AddressDiscovery",1,["noq::path::AddressDiscovery"]],["impl !UnwindSafe for Closed",1,["noq::connection::Closed"]],["impl !UnwindSafe for Connecting",1,["noq::connection::Connecting"]],["impl !UnwindSafe for Endpoint",1,["noq::endpoint::Endpoint"]],["impl !UnwindSafe for Incoming",1,["noq::incoming::Incoming"]],["impl !UnwindSafe for IncomingFuture",1,["noq::incoming::IncomingFuture"]],["impl !UnwindSafe for NatTraversalUpdates",1,["noq::event_stream::NatTraversalUpdates"]],["impl !UnwindSafe for ObservedExternalAddr",1,["noq::event_stream::ObservedExternalAddr"]],["impl !UnwindSafe for OnClosed",1,["noq::connection::OnClosed"]],["impl !UnwindSafe for OpenPath",1,["noq::path::OpenPath"]],["impl !UnwindSafe for PathEvents",1,["noq::event_stream::PathEvents"]],["impl !UnwindSafe for RetryError",1,["noq::incoming::RetryError"]],["impl !UnwindSafe for Stopped",1,["noq::send_stream::Stopped"]],["impl !UnwindSafe for ZeroRttAccepted",1,["noq::connection::ZeroRttAccepted"]],["impl UnwindSafe for Connection",1,["noq::connection::Connection"]],["impl UnwindSafe for EndpointStats",1,["noq::endpoint::EndpointStats"]],["impl UnwindSafe for Lagged",1,["noq::event_stream::Lagged"]],["impl UnwindSafe for Path",1,["noq::path::Path"]],["impl UnwindSafe for RecvStream",1,["noq::recv_stream::RecvStream"]],["impl UnwindSafe for SendStream",1,["noq::send_stream::SendStream"]],["impl UnwindSafe for SmolRuntime",1,["noq::runtime::smol::SmolRuntime"]],["impl UnwindSafe for TokioRuntime",1,["noq::runtime::tokio::TokioRuntime"]],["impl UnwindSafe for UnorderedRecvStream",1,["noq::recv_stream::UnorderedRecvStream"]],["impl UnwindSafe for WeakConnectionHandle",1,["noq::connection::WeakConnectionHandle"]],["impl UnwindSafe for WeakPathHandle",1,["noq::path::WeakPathHandle"]],["impl<'a> !UnwindSafe for Accept<'a>",1,["noq::endpoint::Accept"]],["impl<'a> !UnwindSafe for AcceptBi<'a>",1,["noq::connection::AcceptBi"]],["impl<'a> !UnwindSafe for AcceptUni<'a>",1,["noq::connection::AcceptUni"]],["impl<'a> !UnwindSafe for OpenBi<'a>",1,["noq::connection::OpenBi"]],["impl<'a> !UnwindSafe for OpenUni<'a>",1,["noq::connection::OpenUni"]],["impl<'a> !UnwindSafe for ReadDatagram<'a>",1,["noq::connection::ReadDatagram"]],["impl<'a> !UnwindSafe for SendDatagram<'a>",1,["noq::connection::SendDatagram"]]]],["noq_perf",[["impl UnwindSafe for Commands",1,["noq_perf::Commands"]],["impl UnwindSafe for Cli",1,["noq_perf::Cli"]]]],["noq_proto",[["impl !UnwindSafe for ConnectionError",1,["noq_proto::connection::ConnectionError"]],["impl !UnwindSafe for DatagramEvent",1,["noq_proto::endpoint::DatagramEvent"]],["impl !UnwindSafe for Event",1,["noq_proto::connection::Event"]],["impl !UnwindSafe for QuicClientConfig",1,["noq_proto::crypto::rustls::QuicClientConfig"]],["impl !UnwindSafe for QuicServerConfig",1,["noq_proto::crypto::rustls::QuicServerConfig"]],["impl !UnwindSafe for TlsSession",1,["noq_proto::crypto::rustls::TlsSession"]],["impl !UnwindSafe for Keys",1,["noq_proto::crypto::Keys"]],["impl !UnwindSafe for AcceptError",1,["noq_proto::endpoint::AcceptError"]],["impl !UnwindSafe for ClientConfig",1,["noq_proto::config::ClientConfig"]],["impl !UnwindSafe for Connection",1,["noq_proto::connection::Connection"]],["impl !UnwindSafe for Endpoint",1,["noq_proto::endpoint::Endpoint"]],["impl !UnwindSafe for EndpointConfig",1,["noq_proto::config::EndpointConfig"]],["impl !UnwindSafe for Incoming",1,["noq_proto::endpoint::Incoming"]],["impl !UnwindSafe for QlogConfig",1,["noq_proto::config::qlog::QlogConfig"]],["impl !UnwindSafe for RetryError",1,["noq_proto::endpoint::RetryError"]],["impl !UnwindSafe for ServerConfig",1,["noq_proto::config::ServerConfig"]],["impl !UnwindSafe for TransportConfig",1,["noq_proto::config::transport::TransportConfig"]],["impl !UnwindSafe for Error",1,["noq_proto::transport_error::Error"]],["impl !UnwindSafe for ValidationTokenConfig",1,["noq_proto::config::ValidationTokenConfig"]],["impl UnwindSafe for ClosePathError",1,["noq_proto::connection::ClosePathError"]],["impl UnwindSafe for ConfigError",1,["noq_proto::config::ConfigError"]],["impl UnwindSafe for ConnectError",1,["noq_proto::endpoint::ConnectError"]],["impl UnwindSafe for Dir",1,["noq_proto::Dir"]],["impl UnwindSafe for EcnCodepoint",1,["noq_proto::shared::EcnCodepoint"]],["impl UnwindSafe for FinishError",1,["noq_proto::connection::streams::send::FinishError"]],["impl UnwindSafe for FrameType",1,["noq_proto::frame::FrameType"]],["impl UnwindSafe for LongType",1,["noq_proto::packet::LongType"]],["impl UnwindSafe for MaybeFrame",1,["noq_proto::frame::MaybeFrame"]],["impl UnwindSafe for PacketDecodeError",1,["noq_proto::packet::PacketDecodeError"]],["impl UnwindSafe for PathAbandonReason",1,["noq_proto::connection::paths::PathAbandonReason"]],["impl UnwindSafe for PathError",1,["noq_proto::connection::PathError"]],["impl UnwindSafe for PathEvent",1,["noq_proto::connection::paths::PathEvent"]],["impl UnwindSafe for PathStatus",1,["noq_proto::connection::paths::PathStatus"]],["impl UnwindSafe for ProtectedHeader",1,["noq_proto::packet::ProtectedHeader"]],["impl UnwindSafe for ReadError",1,["noq_proto::connection::streams::recv::ReadError"]],["impl UnwindSafe for ReadableError",1,["noq_proto::connection::streams::recv::ReadableError"]],["impl UnwindSafe for SendDatagramError",1,["noq_proto::connection::datagrams::SendDatagramError"]],["impl UnwindSafe for SetPathStatusError",1,["noq_proto::connection::paths::SetPathStatusError"]],["impl UnwindSafe for Side",1,["noq_proto::Side"]],["impl UnwindSafe for StreamEvent",1,["noq_proto::connection::streams::StreamEvent"]],["impl UnwindSafe for WriteError",1,["noq_proto::connection::streams::send::WriteError"]],["impl UnwindSafe for Error",1,["noq_proto::n0_nat_traversal::Error"]],["impl UnwindSafe for Event",1,["noq_proto::n0_nat_traversal::Event"]],["impl UnwindSafe for Error",1,["noq_proto::transport_parameters::Error"]],["impl UnwindSafe for UnexpectedEnd",1,["noq_proto::coding::UnexpectedEnd"]],["impl UnwindSafe for Bbr3",1,["noq_proto::congestion::bbr3::Bbr3"]],["impl UnwindSafe for Bbr3Config",1,["noq_proto::congestion::bbr3::Bbr3Config"]],["impl UnwindSafe for ControllerMetrics",1,["noq_proto::congestion::ControllerMetrics"]],["impl UnwindSafe for Cubic",1,["noq_proto::congestion::cubic::Cubic"]],["impl UnwindSafe for CubicConfig",1,["noq_proto::congestion::cubic::CubicConfig"]],["impl UnwindSafe for NewReno",1,["noq_proto::congestion::new_reno::NewReno"]],["impl UnwindSafe for NewRenoConfig",1,["noq_proto::congestion::new_reno::NewRenoConfig"]],["impl UnwindSafe for HandshakeData",1,["noq_proto::crypto::rustls::HandshakeData"]],["impl UnwindSafe for NoInitialCipherSuite",1,["noq_proto::crypto::rustls::NoInitialCipherSuite"]],["impl UnwindSafe for CryptoError",1,["noq_proto::crypto::CryptoError"]],["impl UnwindSafe for ExportKeyingMaterialError",1,["noq_proto::crypto::ExportKeyingMaterialError"]],["impl UnwindSafe for UnsupportedVersion",1,["noq_proto::crypto::UnsupportedVersion"]],["impl UnwindSafe for AckFrequencyConfig",1,["noq_proto::config::transport::AckFrequencyConfig"]],["impl UnwindSafe for ApplicationClose",1,["noq_proto::frame::ApplicationClose"]],["impl UnwindSafe for BloomTokenLog",1,["noq_proto::bloom_token_log::BloomTokenLog"]],["impl UnwindSafe for Chunk",1,["noq_proto::connection::assembler::Chunk"]],["impl UnwindSafe for ClosedPath",1,["noq_proto::connection::paths::ClosedPath"]],["impl UnwindSafe for ClosedStream",1,["noq_proto::connection::streams::ClosedStream"]],["impl UnwindSafe for ConnectionClose",1,["noq_proto::frame::ConnectionClose"]],["impl UnwindSafe for ConnectionEvent",1,["noq_proto::shared::ConnectionEvent"]],["impl UnwindSafe for ConnectionHandle",1,["noq_proto::endpoint::ConnectionHandle"]],["impl UnwindSafe for ConnectionId",1,["noq_proto::shared::ConnectionId"]],["impl UnwindSafe for ConnectionStats",1,["noq_proto::connection::stats::ConnectionStats"]],["impl UnwindSafe for Datagram",1,["noq_proto::frame::Datagram"]],["impl UnwindSafe for DatagramInfo",1,["noq_proto::frame::DatagramInfo"]],["impl UnwindSafe for DecryptedInitial",1,["noq_proto::endpoint::DecryptedInitial"]],["impl UnwindSafe for EndpointEvent",1,["noq_proto::shared::EndpointEvent"]],["impl UnwindSafe for FixedLengthConnectionIdParser",1,["noq_proto::packet::FixedLengthConnectionIdParser"]],["impl UnwindSafe for FourTuple",1,["noq_proto::FourTuple"]],["impl UnwindSafe for FrameStats",1,["noq_proto::connection::stats::FrameStats"]],["impl UnwindSafe for HashedConnectionIdGenerator",1,["noq_proto::cid_generator::HashedConnectionIdGenerator"]],["impl UnwindSafe for IdleTimeout",1,["noq_proto::config::transport::IdleTimeout"]],["impl UnwindSafe for IncomingAlpns",1,["noq_proto::endpoint::IncomingAlpns"]],["impl UnwindSafe for InvalidCid",1,["noq_proto::cid_generator::InvalidCid"]],["impl UnwindSafe for InvalidFrameId",1,["noq_proto::frame::InvalidFrameId"]],["impl UnwindSafe for MtuDiscoveryConfig",1,["noq_proto::config::transport::MtuDiscoveryConfig"]],["impl UnwindSafe for MultipathNotNegotiated",1,["noq_proto::connection::MultipathNotNegotiated"]],["impl UnwindSafe for NoneTokenLog",1,["noq_proto::token::NoneTokenLog"]],["impl UnwindSafe for NoneTokenStore",1,["noq_proto::token::NoneTokenStore"]],["impl UnwindSafe for PartialDecode",1,["noq_proto::packet::PartialDecode"]],["impl UnwindSafe for PathId",1,["noq_proto::connection::paths::PathId"]],["impl UnwindSafe for PathStats",1,["noq_proto::connection::stats::PathStats"]],["impl UnwindSafe for ProtectedInitialHeader",1,["noq_proto::packet::ProtectedInitialHeader"]],["impl UnwindSafe for QlogFileFactory",1,["noq_proto::config::qlog::QlogFileFactory"]],["impl UnwindSafe for RandomConnectionIdGenerator",1,["noq_proto::cid_generator::RandomConnectionIdGenerator"]],["impl UnwindSafe for RttEstimator",1,["noq_proto::connection::paths::RttEstimator"]],["impl UnwindSafe for ShouldTransmit",1,["noq_proto::connection::streams::ShouldTransmit"]],["impl UnwindSafe for StdSystemTime",1,["noq_proto::config::StdSystemTime"]],["impl UnwindSafe for StreamId",1,["noq_proto::StreamId"]],["impl UnwindSafe for StreamInfo",1,["noq_proto::frame::StreamInfo"]],["impl UnwindSafe for TokenMemoryCache",1,["noq_proto::token_memory_cache::TokenMemoryCache"]],["impl UnwindSafe for TokenReuseError",1,["noq_proto::token::TokenReuseError"]],["impl UnwindSafe for Transmit",1,["noq_proto::Transmit"]],["impl UnwindSafe for Code",1,["noq_proto::transport_error::Code"]],["impl UnwindSafe for UdpStats",1,["noq_proto::connection::stats::UdpStats"]],["impl UnwindSafe for VarInt",1,["noq_proto::varint::VarInt"]],["impl UnwindSafe for VarIntBoundsExceeded",1,["noq_proto::varint::VarIntBoundsExceeded"]],["impl UnwindSafe for TransportParameters",1,["noq_proto::transport_parameters::TransportParameters"]],["impl<'a> !UnwindSafe for Chunks<'a>",1,["noq_proto::connection::streams::recv::Chunks"]],["impl<'a> !UnwindSafe for Datagrams<'a>",1,["noq_proto::connection::datagrams::Datagrams"]],["impl<'a> !UnwindSafe for RecvStream<'a>",1,["noq_proto::connection::streams::RecvStream"]],["impl<'a> !UnwindSafe for SendStream<'a>",1,["noq_proto::connection::streams::SendStream"]],["impl<'a> !UnwindSafe for Streams<'a>",1,["noq_proto::connection::streams::Streams"]],["impl<T> UnwindSafe for KeyPair<T>
    where\n T: UnwindSafe,
    ",1,["noq_proto::crypto::KeyPair"]]]],["noq_udp",[["impl UnwindSafe for EcnCodepoint",1,["noq_udp::EcnCodepoint"]],["impl UnwindSafe for RecvMeta",1,["noq_udp::RecvMeta"]],["impl UnwindSafe for UdpSocketState",1,["noq_udp::imp::UdpSocketState"]],["impl<'a> UnwindSafe for Transmit<'a>",1,["noq_udp::Transmit"]],["impl<'a> UnwindSafe for UdpSockRef<'a>",1,["noq_udp::UdpSockRef"]]]],["perf",[["impl !UnwindSafe for NoProtectionClientConfig",1,["perf::noprotection::NoProtectionClientConfig"]],["impl !UnwindSafe for NoProtectionServerConfig",1,["perf::noprotection::NoProtectionServerConfig"]],["impl UnwindSafe for CongestionAlgorithm",1,["perf::CongestionAlgorithm"]],["impl UnwindSafe for Opt",1,["perf::client::Opt"]],["impl UnwindSafe for Opt",1,["perf::server::Opt"]],["impl UnwindSafe for OpenStreamStats",1,["perf::stats::OpenStreamStats"]],["impl UnwindSafe for Stats",1,["perf::stats::Stats"]],["impl UnwindSafe for StreamStats",1,["perf::stats::StreamStats"]],["impl UnwindSafe for CommonOpt",1,["perf::CommonOpt"]]]]]); if (window.register_implementors) { window.register_implementors(implementors); } else { window.pending_implementors = implementors; } })() -//{"start":57,"fragment_lengths":[1959,332,405,12704,631,39754,1691,3140]} \ No newline at end of file +//{"start":57,"fragment_lengths":[1959,332,405,13015,631,39417,1691,3140]} \ No newline at end of file