From 372c435aaa74ec9b05995657b9dfe45453ee5231 Mon Sep 17 00:00:00 2001 From: Frando Date: Mon, 4 May 2026 12:20:06 +0000 Subject: [PATCH] deploy: 032e293c0085493a463184f113e282303e877a12 --- pr/617/docs/noq/congestion/index.html | 2 +- pr/617/docs/noq/crypto/index.html | 2 +- pr/617/docs/noq/enum.ClosePathError.html | 6 +- pr/617/docs/noq/enum.ConfigError.html | 6 +- pr/617/docs/noq/enum.ConnectionError.html | 6 +- pr/617/docs/noq/enum.Dir.html | 16 +- pr/617/docs/noq/enum.PathError.html | 6 +- pr/617/docs/noq/enum.PathEvent.html | 6 +- pr/617/docs/noq/enum.PathStatus.html | 6 +- pr/617/docs/noq/enum.SendDatagramError.html | 6 +- pr/617/docs/noq/enum.SetPathStatusError.html | 6 +- pr/617/docs/noq/enum.Side.html | 20 +- pr/617/docs/noq/struct.AcceptBi.html | 6 +- pr/617/docs/noq/struct.AcceptUni.html | 6 +- .../docs/noq/struct.AckFrequencyConfig.html | 10 +- pr/617/docs/noq/struct.AddressDiscovery.html | 4 +- pr/617/docs/noq/struct.ClientConfig.html | 18 +- pr/617/docs/noq/struct.Closed.html | 4 +- pr/617/docs/noq/struct.ClosedPath.html | 6 +- pr/617/docs/noq/struct.Connecting.html | 6 +- pr/617/docs/noq/struct.Connection.html | 98 +- pr/617/docs/noq/struct.EndpointConfig.html | 22 +- pr/617/docs/noq/struct.IdleTimeout.html | 14 +- .../docs/noq/struct.MtuDiscoveryConfig.html | 12 +- pr/617/docs/noq/struct.OnClosed.html | 4 +- pr/617/docs/noq/struct.OpenBi.html | 6 +- pr/617/docs/noq/struct.OpenUni.html | 6 +- pr/617/docs/noq/struct.Path.html | 11 +- pr/617/docs/noq/struct.ReadDatagram.html | 6 +- pr/617/docs/noq/struct.SendDatagram.html | 6 +- pr/617/docs/noq/struct.ServerConfig.html | 32 +- pr/617/docs/noq/struct.StdSystemTime.html | 4 +- pr/617/docs/noq/struct.StreamId.html | 26 +- pr/617/docs/noq/struct.Transmit.html | 4 +- pr/617/docs/noq/struct.TransportConfig.html | 73 +- .../noq/struct.ValidationTokenConfig.html | 10 +- pr/617/docs/noq/struct.VarInt.html | 2 +- .../docs/noq/struct.VarIntBoundsExceeded.html | 2 +- .../docs/noq/struct.WeakConnectionHandle.html | 10 +- pr/617/docs/noq/struct.WeakPathHandle.html | 10 +- pr/617/docs/noq/struct.ZeroRttAccepted.html | 4 +- pr/617/docs/noq/trait.NetworkChangeHint.html | 4 +- pr/617/docs/noq/trait.TimeSource.html | 6 +- pr/617/docs/noq_proto/all.html | 2 +- .../docs/noq_proto/bench_exports/index.html | 2 +- .../noq_proto/coding/trait.Decodable.html | 2 +- .../noq_proto/coding/trait.Encodable.html | 2 +- ...NCURRENT_MULTIPATH_PATHS_WHEN_ENABLED.html | 11 - ...NCURRENT_MULTIPATH_PATHS_WHEN_ENABLED.html | 3 - .../constant.DEFAULT_SUPPORTED_VERSIONS.html | 2 +- .../docs/noq_proto/enum.ClosePathError.html | 6 +- pr/617/docs/noq_proto/enum.ConfigError.html | 6 +- .../docs/noq_proto/enum.ConnectionError.html | 6 +- pr/617/docs/noq_proto/enum.Dir.html | 16 +- pr/617/docs/noq_proto/enum.Event.html | 4 +- .../noq_proto/enum.PathAbandonReason.html | 6 +- pr/617/docs/noq_proto/enum.PathError.html | 6 +- pr/617/docs/noq_proto/enum.PathEvent.html | 6 +- pr/617/docs/noq_proto/enum.PathStatus.html | 6 +- .../noq_proto/enum.SetPathStatusError.html | 6 +- pr/617/docs/noq_proto/enum.Side.html | 20 +- pr/617/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/617/docs/noq_proto/sidebar-items.js | 2 +- .../noq_proto/struct.AckFrequencyConfig.html | 10 +- .../docs/noq_proto/struct.ClientConfig.html | 18 +- pr/617/docs/noq_proto/struct.ClosedPath.html | 6 +- pr/617/docs/noq_proto/struct.Connection.html | 70 +- .../docs/noq_proto/struct.EndpointConfig.html | 22 +- pr/617/docs/noq_proto/struct.FourTuple.html | 20 +- pr/617/docs/noq_proto/struct.IdleTimeout.html | 12 +- .../noq_proto/struct.MtuDiscoveryConfig.html | 12 +- .../struct.MultipathNotNegotiated.html | 4 +- .../docs/noq_proto/struct.RttEstimator.html | 10 +- .../docs/noq_proto/struct.ServerConfig.html | 32 +- .../docs/noq_proto/struct.StdSystemTime.html | 4 +- pr/617/docs/noq_proto/struct.StreamId.html | 22 +- pr/617/docs/noq_proto/struct.Transmit.html | 4 +- .../noq_proto/struct.TransportConfig.html | 73 +- .../docs/noq_proto/struct.TransportError.html | 2 +- .../struct.ValidationTokenConfig.html | 10 +- pr/617/docs/noq_proto/struct.VarInt.html | 2 +- .../struct.VarIntBoundsExceeded.html | 2 +- .../noq_proto/trait.NetworkChangeHint.html | 4 +- pr/617/docs/noq_proto/trait.TimeSource.html | 6 +- pr/617/docs/perf/server/index.html | 2 +- pr/617/docs/search.index/001728ca1382.js | 1 + pr/617/docs/search.index/001d41e9dc67.js | 1 + pr/617/docs/search.index/0098faffe0aa.js | 1 - pr/617/docs/search.index/00c273ace618.js | 1 + pr/617/docs/search.index/00ea8f3dbdef.js | 1 + pr/617/docs/search.index/00fb5c4f9c23.js | 1 + pr/617/docs/search.index/0107dc62ab6c.js | 1 - pr/617/docs/search.index/017426982d62.js | 1 + pr/617/docs/search.index/020540c03066.js | 1 + pr/617/docs/search.index/0219603ee875.js | 1 + pr/617/docs/search.index/041663251e8f.js | 1 - pr/617/docs/search.index/04a9e87be06a.js | 1 - pr/617/docs/search.index/0505a70598b7.js | 1 + pr/617/docs/search.index/051240e58a38.js | 1 + pr/617/docs/search.index/05a01e5a13f7.js | 1 - pr/617/docs/search.index/05b03b52ea2f.js | 1 - pr/617/docs/search.index/05e227d0495f.js | 1 + pr/617/docs/search.index/0647d1d32d25.js | 1 - pr/617/docs/search.index/066cb4cb59fe.js | 1 - pr/617/docs/search.index/07060b7a880b.js | 1 + pr/617/docs/search.index/071e1fe55efd.js | 1 + pr/617/docs/search.index/0817d1ec6cb9.js | 1 - .../{6ed509a41675.js => 08621659568f.js} | 2 +- pr/617/docs/search.index/09846932e7be.js | 1 - pr/617/docs/search.index/09cc9a7ef753.js | 1 - pr/617/docs/search.index/09efdd050b38.js | 1 + pr/617/docs/search.index/0a0be613805b.js | 1 + pr/617/docs/search.index/0a2dff3b40f0.js | 1 - pr/617/docs/search.index/0a32807f6fd5.js | 1 - pr/617/docs/search.index/0a98bd6387de.js | 1 - pr/617/docs/search.index/0afefb419068.js | 1 + pr/617/docs/search.index/0b31dbd8cc61.js | 1 + pr/617/docs/search.index/0b75363a70af.js | 1 - pr/617/docs/search.index/0bd1edc62be1.js | 1 - pr/617/docs/search.index/0c36c2caebf1.js | 1 + pr/617/docs/search.index/0c38ce31a369.js | 1 + pr/617/docs/search.index/0c58728aab63.js | 1 + pr/617/docs/search.index/0c88bb59b14a.js | 1 + pr/617/docs/search.index/0ca11bedf490.js | 1 + pr/617/docs/search.index/0cbfae16bec4.js | 1 + pr/617/docs/search.index/0d06702b2cc6.js | 1 + pr/617/docs/search.index/0d83091b253d.js | 1 + pr/617/docs/search.index/0db99ab57aac.js | 1 + pr/617/docs/search.index/0decf51a5366.js | 1 + pr/617/docs/search.index/0e10c6390bcf.js | 1 - pr/617/docs/search.index/0e64f8544bfd.js | 1 - pr/617/docs/search.index/0ecb7a74954b.js | 1 - pr/617/docs/search.index/0fd086f0ef1b.js | 1 - .../{71c0c54d2614.js => 10ffbbec645d.js} | 2 +- pr/617/docs/search.index/11205106a64b.js | 1 + pr/617/docs/search.index/113486b6b2b6.js | 1 + pr/617/docs/search.index/11a240e9b947.js | 1 - pr/617/docs/search.index/120270aa39e9.js | 1 + pr/617/docs/search.index/1231139faa22.js | 1 + pr/617/docs/search.index/1271000c9004.js | 1 + pr/617/docs/search.index/13257449bc05.js | 1 - pr/617/docs/search.index/137d1f70f3ef.js | 1 - pr/617/docs/search.index/13df067efa5b.js | 1 - pr/617/docs/search.index/13f38e008740.js | 1 + .../{01cc8d107b7f.js => 140acd1805c2.js} | 2 +- pr/617/docs/search.index/143875586e56.js | 1 - pr/617/docs/search.index/1454cc4b3625.js | 1 - pr/617/docs/search.index/146012214b23.js | 1 - pr/617/docs/search.index/156802473293.js | 1 + pr/617/docs/search.index/158eff2eda3b.js | 1 + pr/617/docs/search.index/160defe62b6f.js | 1 + pr/617/docs/search.index/1613c157a7c0.js | 1 - pr/617/docs/search.index/16335499b785.js | 1 - pr/617/docs/search.index/1642b10854ec.js | 1 + pr/617/docs/search.index/164abac8a96b.js | 1 - pr/617/docs/search.index/16666d274533.js | 1 - pr/617/docs/search.index/16b34983ecba.js | 1 + pr/617/docs/search.index/17b20931cf6c.js | 1 + pr/617/docs/search.index/17c3fc246899.js | 1 + pr/617/docs/search.index/1808c73b09a0.js | 1 + pr/617/docs/search.index/182b5b46d5d5.js | 1 + pr/617/docs/search.index/1853a3dd5558.js | 1 - pr/617/docs/search.index/1880a3564175.js | 1 + pr/617/docs/search.index/18b064acc7a2.js | 1 + pr/617/docs/search.index/18f4af644129.js | 1 + pr/617/docs/search.index/18fd1a9e7934.js | 1 + pr/617/docs/search.index/197b84d1f6e5.js | 1 - pr/617/docs/search.index/19bb8e539b3c.js | 1 - pr/617/docs/search.index/1a335dc013ee.js | 1 + pr/617/docs/search.index/1a7331c9f293.js | 1 + pr/617/docs/search.index/1abe42839e69.js | 1 + pr/617/docs/search.index/1b3f0c6a1d7d.js | 1 + pr/617/docs/search.index/1b4c79c0be67.js | 1 - pr/617/docs/search.index/1b9b9d2cb1f9.js | 1 - pr/617/docs/search.index/1baaec618882.js | 1 - pr/617/docs/search.index/1bc38f904b32.js | 1 + pr/617/docs/search.index/1bcb042010cd.js | 1 + pr/617/docs/search.index/1c486df13d0f.js | 1 - pr/617/docs/search.index/1ca93b71869b.js | 1 - pr/617/docs/search.index/1cbfb40fb014.js | 1 - pr/617/docs/search.index/1cc6eea1be2c.js | 1 + pr/617/docs/search.index/1d2539aa0107.js | 1 + pr/617/docs/search.index/1d44eb8c93b9.js | 1 - pr/617/docs/search.index/1d4595df1786.js | 1 - pr/617/docs/search.index/1d498b21b79f.js | 1 - pr/617/docs/search.index/1d5569503590.js | 1 + pr/617/docs/search.index/1d607b1ec238.js | 1 + pr/617/docs/search.index/1d879b498dfa.js | 1 + pr/617/docs/search.index/1d93d8252622.js | 1 - pr/617/docs/search.index/1dbef74dd164.js | 1 + pr/617/docs/search.index/1df29bac508a.js | 1 - pr/617/docs/search.index/1e64655eaedf.js | 1 + pr/617/docs/search.index/1ed131c81757.js | 1 + pr/617/docs/search.index/1ee5b86102ad.js | 1 + pr/617/docs/search.index/1f32f40ccbcb.js | 1 + pr/617/docs/search.index/1f8e6a0bbf2b.js | 1 - pr/617/docs/search.index/1fa23924c3c7.js | 1 + pr/617/docs/search.index/2003f002ccdb.js | 1 + pr/617/docs/search.index/20727d2fe054.js | 1 - pr/617/docs/search.index/20ef81a14d6e.js | 1 + pr/617/docs/search.index/218e52b0339e.js | 1 - pr/617/docs/search.index/2206d66e8e6a.js | 1 - pr/617/docs/search.index/22934d7a650c.js | 1 + pr/617/docs/search.index/22d494009ab1.js | 1 + pr/617/docs/search.index/2303707d866b.js | 1 - pr/617/docs/search.index/236fc2dd2763.js | 1 - pr/617/docs/search.index/23798fef204d.js | 1 - pr/617/docs/search.index/23e4988aecac.js | 1 + pr/617/docs/search.index/23f7516e5703.js | 1 - pr/617/docs/search.index/243d6718115c.js | 1 - pr/617/docs/search.index/2488a011453a.js | 1 + pr/617/docs/search.index/24b2ddc4a0dd.js | 1 - pr/617/docs/search.index/24df159f5f12.js | 1 + pr/617/docs/search.index/250ec7c859d8.js | 1 + pr/617/docs/search.index/2593c24072ce.js | 1 - pr/617/docs/search.index/25a024fd26b3.js | 1 + pr/617/docs/search.index/272a63220c2a.js | 1 - pr/617/docs/search.index/2794b2ea6f8a.js | 1 - pr/617/docs/search.index/27966aefced8.js | 1 + pr/617/docs/search.index/27ee8910beb2.js | 1 - pr/617/docs/search.index/27ff847ec176.js | 1 + pr/617/docs/search.index/27ffa050a6cf.js | 1 - .../{53b036251557.js => 2867eb6253a6.js} | 2 +- pr/617/docs/search.index/28ae7e2c3eff.js | 1 + pr/617/docs/search.index/28b28f3de641.js | 1 - pr/617/docs/search.index/28c932d1d3a3.js | 1 + pr/617/docs/search.index/28ca64a40b02.js | 1 - pr/617/docs/search.index/2922cc5fe9b0.js | 1 - pr/617/docs/search.index/292ab4b4dd12.js | 1 - pr/617/docs/search.index/2935d9f9f689.js | 1 + pr/617/docs/search.index/298a2d10a108.js | 1 + pr/617/docs/search.index/2a4f58d36642.js | 1 + pr/617/docs/search.index/2a689e7ce38c.js | 1 + pr/617/docs/search.index/2a7bc2b190a6.js | 1 - pr/617/docs/search.index/2a90a7e1d3b9.js | 1 + pr/617/docs/search.index/2ad2d4f46aea.js | 1 - pr/617/docs/search.index/2b2a88985dd7.js | 1 - pr/617/docs/search.index/2b3c097bd30b.js | 1 + pr/617/docs/search.index/2b605a22de5b.js | 1 + pr/617/docs/search.index/2b616085d3bb.js | 1 - .../{134492284a6c.js => 2b670142def8.js} | 2 +- pr/617/docs/search.index/2b69804711dc.js | 1 - pr/617/docs/search.index/2b6f9f07c43a.js | 1 + pr/617/docs/search.index/2b87673d150b.js | 1 + pr/617/docs/search.index/2c0afee3955b.js | 1 - pr/617/docs/search.index/2cc429d48879.js | 1 - pr/617/docs/search.index/2d360f1667cc.js | 1 + pr/617/docs/search.index/2d83fa4de460.js | 1 + pr/617/docs/search.index/2dbb149f52d2.js | 1 - pr/617/docs/search.index/2ea9dee8417b.js | 1 - pr/617/docs/search.index/2ec9baa2b225.js | 1 + pr/617/docs/search.index/2f23c7c0d6ce.js | 1 + pr/617/docs/search.index/2f5deadfbad9.js | 1 + pr/617/docs/search.index/2f978aecd3a2.js | 1 - pr/617/docs/search.index/3039810e4520.js | 1 + pr/617/docs/search.index/30554a2e2f98.js | 1 - pr/617/docs/search.index/308477dce192.js | 1 - pr/617/docs/search.index/309101a2ab26.js | 1 - pr/617/docs/search.index/30bdf72af4ad.js | 1 + pr/617/docs/search.index/30c32768f920.js | 1 - pr/617/docs/search.index/3109aa229dc6.js | 1 - pr/617/docs/search.index/3142e6000013.js | 1 + pr/617/docs/search.index/314abf4dfb52.js | 1 - pr/617/docs/search.index/31587e8a08ed.js | 1 + pr/617/docs/search.index/316c499513de.js | 1 + pr/617/docs/search.index/31793053504c.js | 1 + pr/617/docs/search.index/323ee7233560.js | 1 + pr/617/docs/search.index/32695d4910d9.js | 1 - pr/617/docs/search.index/32824d81361a.js | 1 - pr/617/docs/search.index/32893117deca.js | 1 - pr/617/docs/search.index/32b279bb23c7.js | 1 + pr/617/docs/search.index/32ca2ee3c5d5.js | 1 - pr/617/docs/search.index/3339b459bd03.js | 1 - pr/617/docs/search.index/334f97bd1666.js | 1 - pr/617/docs/search.index/336e3a26ae11.js | 1 - pr/617/docs/search.index/337b7c87a50a.js | 1 + pr/617/docs/search.index/33ec105d3348.js | 1 + pr/617/docs/search.index/343a021f5f79.js | 1 + pr/617/docs/search.index/3451b6eeceed.js | 1 - pr/617/docs/search.index/345d3d0c7b44.js | 1 - pr/617/docs/search.index/347cdc137d51.js | 1 + pr/617/docs/search.index/34893b3ba8c0.js | 1 - pr/617/docs/search.index/34ab39043216.js | 1 + pr/617/docs/search.index/354e3e954960.js | 1 - pr/617/docs/search.index/35dceadd9c46.js | 1 + pr/617/docs/search.index/360e8453d77c.js | 1 - pr/617/docs/search.index/36800abc79d6.js | 1 - pr/617/docs/search.index/373c5d490117.js | 1 - pr/617/docs/search.index/3749fa2bd5f0.js | 1 - pr/617/docs/search.index/3765f6d962ab.js | 1 - pr/617/docs/search.index/38356d26d54e.js | 1 - pr/617/docs/search.index/38e03ed6ba8b.js | 1 + pr/617/docs/search.index/38e697a6153c.js | 1 + pr/617/docs/search.index/3995e07bb8ad.js | 1 + pr/617/docs/search.index/399d9099d408.js | 1 - pr/617/docs/search.index/39d5be6cac82.js | 1 + pr/617/docs/search.index/3a68c92a1996.js | 1 + pr/617/docs/search.index/3a70741f4557.js | 1 - pr/617/docs/search.index/3a88f1aefd64.js | 1 - pr/617/docs/search.index/3ab1ec480043.js | 1 - pr/617/docs/search.index/3ace8391b89d.js | 1 - pr/617/docs/search.index/3ae3a44a9dc2.js | 1 + pr/617/docs/search.index/3b7ea92b577a.js | 1 - pr/617/docs/search.index/3bdcf6ef8822.js | 1 - .../{718da5763b19.js => 3c097b312dbc.js} | 2 +- pr/617/docs/search.index/3c4c9ddb2e7a.js | 1 + pr/617/docs/search.index/3c80b5c41902.js | 1 + pr/617/docs/search.index/3cb92ce7537c.js | 1 - pr/617/docs/search.index/3cea80f3171e.js | 1 + pr/617/docs/search.index/3d02f4499ad1.js | 1 + pr/617/docs/search.index/3dd7e7b549d9.js | 1 + pr/617/docs/search.index/3dda4acb68c1.js | 1 - pr/617/docs/search.index/3e1261c016d6.js | 1 - pr/617/docs/search.index/3f252083b9d3.js | 1 - pr/617/docs/search.index/402299f94e1a.js | 1 - pr/617/docs/search.index/402f200cf6ef.js | 1 - pr/617/docs/search.index/409d99102e7c.js | 1 + pr/617/docs/search.index/413ca794b790.js | 1 - pr/617/docs/search.index/414c4c18736c.js | 1 + pr/617/docs/search.index/41a331e65a3c.js | 1 - pr/617/docs/search.index/41d5f31e57ca.js | 1 + pr/617/docs/search.index/41ecd022fb75.js | 1 - pr/617/docs/search.index/42424466438e.js | 1 - pr/617/docs/search.index/42c3eb12af93.js | 1 + pr/617/docs/search.index/42ce13ba00cf.js | 1 + pr/617/docs/search.index/42d8f48ef067.js | 1 + pr/617/docs/search.index/42deef5af9ea.js | 1 + pr/617/docs/search.index/4314812a0c42.js | 1 + pr/617/docs/search.index/4399c1a05fce.js | 1 + pr/617/docs/search.index/444cc3e74ea7.js | 1 - pr/617/docs/search.index/44610b5c95d7.js | 1 + pr/617/docs/search.index/44dab7c8fd35.js | 1 + pr/617/docs/search.index/4591acbbfc75.js | 1 + pr/617/docs/search.index/45ca0364edb8.js | 1 - pr/617/docs/search.index/45e1f3021919.js | 1 + pr/617/docs/search.index/45e3802642d1.js | 1 + pr/617/docs/search.index/46027ae6580d.js | 1 + pr/617/docs/search.index/4634daa4cefd.js | 1 + pr/617/docs/search.index/466cc5f494e9.js | 1 - pr/617/docs/search.index/46edaca34104.js | 1 - pr/617/docs/search.index/473145eca66b.js | 1 - pr/617/docs/search.index/4752f68ec378.js | 1 + pr/617/docs/search.index/479912951a6e.js | 1 + pr/617/docs/search.index/47cc59b03f79.js | 1 - pr/617/docs/search.index/4848ba157001.js | 1 - pr/617/docs/search.index/492c490fc1ab.js | 1 - pr/617/docs/search.index/497fe4bb728c.js | 1 - pr/617/docs/search.index/4989563e348d.js | 1 - pr/617/docs/search.index/49ba647cb014.js | 1 - pr/617/docs/search.index/49fd4004edab.js | 1 + pr/617/docs/search.index/4a0397aaa53b.js | 1 + pr/617/docs/search.index/4a3344dc82ef.js | 1 + pr/617/docs/search.index/4a8e85bccb70.js | 1 - pr/617/docs/search.index/4abf3fbcd294.js | 1 + pr/617/docs/search.index/4acd6eae406c.js | 1 - pr/617/docs/search.index/4ad27c6c71e0.js | 1 + pr/617/docs/search.index/4b1651402510.js | 1 + pr/617/docs/search.index/4b2657879883.js | 1 + pr/617/docs/search.index/4b4d3233065c.js | 1 + pr/617/docs/search.index/4b50d365f4e7.js | 1 - pr/617/docs/search.index/4b5746dbe11e.js | 1 - pr/617/docs/search.index/4b9183eec264.js | 1 + pr/617/docs/search.index/4bd2750f696a.js | 1 - pr/617/docs/search.index/4c12fa9ec5af.js | 1 + pr/617/docs/search.index/4c1740d83ed2.js | 1 - pr/617/docs/search.index/4c35289cde0f.js | 1 + pr/617/docs/search.index/4c69b395ac8f.js | 1 - pr/617/docs/search.index/4c92d522b4cf.js | 1 - pr/617/docs/search.index/4d199ba2a246.js | 1 + pr/617/docs/search.index/4dfb324aed51.js | 1 + pr/617/docs/search.index/4ecbb5c4c72a.js | 1 + pr/617/docs/search.index/4f4bed7058fd.js | 1 + pr/617/docs/search.index/4f9cbcececbe.js | 1 - pr/617/docs/search.index/5004f9821b1b.js | 1 - pr/617/docs/search.index/501c6dcd1fda.js | 1 - pr/617/docs/search.index/51193234a828.js | 1 - pr/617/docs/search.index/5136cd716661.js | 1 + pr/617/docs/search.index/515482f97850.js | 1 - pr/617/docs/search.index/51e00cb41a33.js | 1 - pr/617/docs/search.index/5200cabaa1d5.js | 1 + pr/617/docs/search.index/5289f2209b5b.js | 1 - pr/617/docs/search.index/5294b97e6b02.js | 1 - pr/617/docs/search.index/52babe817b07.js | 1 - pr/617/docs/search.index/532728edef14.js | 1 - pr/617/docs/search.index/53976a6f19cd.js | 1 - pr/617/docs/search.index/546ec0de9ebd.js | 1 - pr/617/docs/search.index/54a8e6164f40.js | 1 - pr/617/docs/search.index/54a9637d5185.js | 1 + pr/617/docs/search.index/54ec492054f5.js | 1 - pr/617/docs/search.index/5512bb91ed8f.js | 1 - pr/617/docs/search.index/5530a91156e7.js | 1 + pr/617/docs/search.index/55351f3455a2.js | 1 - pr/617/docs/search.index/55ee716c76ca.js | 1 - pr/617/docs/search.index/55ffe625f527.js | 1 + pr/617/docs/search.index/574d85053847.js | 1 - pr/617/docs/search.index/577af22cc871.js | 1 - pr/617/docs/search.index/57b2f70c36c8.js | 1 - pr/617/docs/search.index/57c59a197af7.js | 1 - pr/617/docs/search.index/57e10538b61a.js | 1 - pr/617/docs/search.index/5875637aff25.js | 1 + pr/617/docs/search.index/58ae9e43f3d7.js | 1 - pr/617/docs/search.index/58d0d4294971.js | 1 + pr/617/docs/search.index/58dd78d0ece0.js | 1 + pr/617/docs/search.index/58f211c07279.js | 1 + pr/617/docs/search.index/593ba01b5f99.js | 1 + pr/617/docs/search.index/59b253409373.js | 1 + pr/617/docs/search.index/5a2b259ae98b.js | 1 + .../{668c314f2126.js => 5ae92656f48a.js} | 2 +- pr/617/docs/search.index/5b1ddb314b3e.js | 1 - .../{572b61c02809.js => 5b87cca78f8f.js} | 2 +- pr/617/docs/search.index/5b984867710b.js | 1 - pr/617/docs/search.index/5bca52d11800.js | 1 - pr/617/docs/search.index/5be8c1468ebe.js | 1 + pr/617/docs/search.index/5bf73eb997f1.js | 1 - pr/617/docs/search.index/5c4cb1a8cdbb.js | 1 + pr/617/docs/search.index/5cafc27ea81e.js | 1 + pr/617/docs/search.index/5cb3ebce9a92.js | 1 + pr/617/docs/search.index/5d371c6e539d.js | 1 - pr/617/docs/search.index/5dbe95a54095.js | 1 - pr/617/docs/search.index/5dcfa0230fa2.js | 1 - pr/617/docs/search.index/5ddbacd9a3f7.js | 1 - pr/617/docs/search.index/5e33d538e99d.js | 1 + pr/617/docs/search.index/5e6b62b9a81e.js | 1 + pr/617/docs/search.index/5ef223a8d6a1.js | 1 + pr/617/docs/search.index/5fb3c8092292.js | 1 + pr/617/docs/search.index/5fe64baffbcd.js | 1 - pr/617/docs/search.index/6065d8a3f7a7.js | 1 - pr/617/docs/search.index/60d75a7999e9.js | 1 + pr/617/docs/search.index/61c4bbfdf792.js | 1 + pr/617/docs/search.index/61dd5d3444c7.js | 1 - pr/617/docs/search.index/62a8eb8e83c2.js | 1 + pr/617/docs/search.index/63058cf605b0.js | 1 - pr/617/docs/search.index/636e1a4d8b0b.js | 1 - pr/617/docs/search.index/646de36a32e1.js | 1 - pr/617/docs/search.index/64b6efff3a2a.js | 1 + pr/617/docs/search.index/64df930283be.js | 1 + pr/617/docs/search.index/64ef428f9034.js | 1 + pr/617/docs/search.index/64fd4fee47f7.js | 1 - pr/617/docs/search.index/6777b8890d6d.js | 1 + pr/617/docs/search.index/6778b40d0be8.js | 1 + pr/617/docs/search.index/679932c21ba7.js | 1 - pr/617/docs/search.index/67ebc6510c69.js | 1 - pr/617/docs/search.index/6831cbf8bafb.js | 1 - pr/617/docs/search.index/689383818a1f.js | 1 - pr/617/docs/search.index/68d782416b4d.js | 1 - pr/617/docs/search.index/690e4297f1b1.js | 1 + pr/617/docs/search.index/6967dd890c86.js | 1 - pr/617/docs/search.index/6a14e49e8487.js | 1 + pr/617/docs/search.index/6a4963efb35c.js | 1 - pr/617/docs/search.index/6a6edeb452a8.js | 1 - pr/617/docs/search.index/6b2c05d48848.js | 1 - pr/617/docs/search.index/6b510b6efde3.js | 1 - pr/617/docs/search.index/6b7abdba88fe.js | 1 - pr/617/docs/search.index/6bb4fe213d80.js | 1 + pr/617/docs/search.index/6bbd9d9cc477.js | 1 - pr/617/docs/search.index/6bc61df8f07f.js | 1 + pr/617/docs/search.index/6bf86b6dc333.js | 1 - pr/617/docs/search.index/6c95a9f746d9.js | 1 - pr/617/docs/search.index/6cacde78e373.js | 1 + pr/617/docs/search.index/6d1a04586937.js | 1 + .../{1c685467eb29.js => 6df6929b7791.js} | 2 +- pr/617/docs/search.index/6df9e397525a.js | 1 + pr/617/docs/search.index/6e094e6c5da3.js | 1 + pr/617/docs/search.index/6e27b20dd6b6.js | 1 - pr/617/docs/search.index/6e48771c696f.js | 1 - pr/617/docs/search.index/6e4cb0afff2c.js | 1 - pr/617/docs/search.index/6e8315600389.js | 1 - pr/617/docs/search.index/6e8cdd1201da.js | 1 + pr/617/docs/search.index/6eba048936de.js | 1 - pr/617/docs/search.index/6ec3fb3fd992.js | 1 + pr/617/docs/search.index/6ee512238957.js | 1 + pr/617/docs/search.index/6f53f353729b.js | 1 + pr/617/docs/search.index/6f6c17668170.js | 1 + pr/617/docs/search.index/6f86e944f992.js | 1 + pr/617/docs/search.index/6fb11194cbac.js | 1 - pr/617/docs/search.index/6fdce15328df.js | 1 + pr/617/docs/search.index/6ff044a53b85.js | 1 - pr/617/docs/search.index/7021fc091b39.js | 1 + pr/617/docs/search.index/703f5cade60e.js | 1 - pr/617/docs/search.index/70830eb0fe35.js | 1 - pr/617/docs/search.index/70937cc739a3.js | 1 - pr/617/docs/search.index/70a2231ef1be.js | 1 - pr/617/docs/search.index/70a6f2d585ca.js | 1 - pr/617/docs/search.index/70a97ca4cdef.js | 1 + pr/617/docs/search.index/70f6f7a9ec4a.js | 1 - pr/617/docs/search.index/7157aa96e738.js | 1 + pr/617/docs/search.index/7193e1d068da.js | 1 - pr/617/docs/search.index/71d44b922c39.js | 1 - pr/617/docs/search.index/72676d5cbaf8.js | 1 + pr/617/docs/search.index/72de5c3ebdcb.js | 1 - pr/617/docs/search.index/72f13898206e.js | 1 - pr/617/docs/search.index/731453b1decc.js | 1 + pr/617/docs/search.index/7375756c2f33.js | 1 + pr/617/docs/search.index/740867f5f36d.js | 1 + pr/617/docs/search.index/747e275b3069.js | 1 + pr/617/docs/search.index/752f6f507e62.js | 1 - pr/617/docs/search.index/755bb5c9b7cf.js | 1 + pr/617/docs/search.index/756205ce70fb.js | 1 - pr/617/docs/search.index/75c296e3af6c.js | 1 + pr/617/docs/search.index/7653e5adb45d.js | 1 + pr/617/docs/search.index/768dee78c1da.js | 1 - pr/617/docs/search.index/76e5a08e87b8.js | 1 - pr/617/docs/search.index/7713a6d48c1d.js | 1 - pr/617/docs/search.index/7735482e90b3.js | 1 - pr/617/docs/search.index/7778f4540e60.js | 1 + pr/617/docs/search.index/77ae1f56a954.js | 1 - pr/617/docs/search.index/77e67fb66bc4.js | 1 - pr/617/docs/search.index/7811875cd00f.js | 1 + pr/617/docs/search.index/783c8a1a67c6.js | 1 - pr/617/docs/search.index/78db9c8703f7.js | 1 - pr/617/docs/search.index/79f5b506de04.js | 1 + pr/617/docs/search.index/7a17df9d410e.js | 1 + pr/617/docs/search.index/7acb48b1a833.js | 1 - pr/617/docs/search.index/7ad1f024ed61.js | 1 + pr/617/docs/search.index/7b8c3c69a10a.js | 1 + pr/617/docs/search.index/7b927b2e19c3.js | 1 + pr/617/docs/search.index/7bb93f30bb12.js | 1 - pr/617/docs/search.index/7beb472ea224.js | 1 + pr/617/docs/search.index/7c776e35ecc3.js | 1 + pr/617/docs/search.index/7cfe15e65b11.js | 1 + pr/617/docs/search.index/7d878a92407a.js | 1 + pr/617/docs/search.index/7deb33a6ac15.js | 1 + pr/617/docs/search.index/7e12e28816c8.js | 1 + pr/617/docs/search.index/7ed09b774fa7.js | 1 - pr/617/docs/search.index/7eda4a96e99d.js | 1 - pr/617/docs/search.index/7ee2eb9126d0.js | 1 - pr/617/docs/search.index/7f22e53fd5b4.js | 1 + pr/617/docs/search.index/7f43d011c020.js | 1 - pr/617/docs/search.index/7f5b04ac747f.js | 1 - pr/617/docs/search.index/7f660a8647b9.js | 1 + pr/617/docs/search.index/7f7a7539772c.js | 1 + pr/617/docs/search.index/7fc5a0d8a50f.js | 1 - pr/617/docs/search.index/desc/ac16b357e09e.js | 1 - pr/617/docs/search.index/desc/c4e11ee91647.js | 1 + .../docs/search.index/entry/55a8046a740f.js | 1 - .../docs/search.index/entry/d607f4be9e29.js | 1 + .../search.index/function/5a519e950cb8.js | 1 + .../search.index/function/c06b3b2c2e2b.js | 1 - .../generic_inverted_index/c475b6aeacd1.js | 1 + .../generic_inverted_index/f9c98603db6c.js | 1 - .../name/{56eac45ecfe0.js => d0f424d3d1a6.js} | 2 +- .../{6387bfcfcd90.js => aae5a537ba3b.js} | 2 +- pr/617/docs/search.index/root.js | 2 +- pr/617/docs/search.index/type/6f91bb67b10d.js | 1 - pr/617/docs/search.index/type/c2fb7140b1bd.js | 1 + pr/617/docs/src/noq/connection.rs.html | 3390 +++-- pr/617/docs/src/noq/path.rs.html | 273 +- pr/617/docs/src/noq_proto/config/mod.rs.html | 1277 +- .../src/noq_proto/config/transport.rs.html | 1761 ++- .../docs/src/noq_proto/connection/mod.rs.html | 12536 ++++++++-------- .../src/noq_proto/connection/paths.rs.html | 1239 +- .../src/noq_proto/connection/spaces.rs.html | 3105 ++-- pr/617/docs/src/noq_proto/frame.rs.html | 993 +- pr/617/docs/src/noq_proto/lib.rs.html | 828 +- .../src/noq_proto/n0_nat_traversal.rs.html | 1589 +- pr/617/docs/src/perf/server.rs.html | 197 +- 559 files changed, 14372 insertions(+), 14283 deletions(-) delete mode 100644 pr/617/docs/noq_proto/config/transport/constant.DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED.html delete mode 100644 pr/617/docs/noq_proto/constant.DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED.html create mode 100644 pr/617/docs/search.index/001728ca1382.js create mode 100644 pr/617/docs/search.index/001d41e9dc67.js delete mode 100644 pr/617/docs/search.index/0098faffe0aa.js create mode 100644 pr/617/docs/search.index/00c273ace618.js create mode 100644 pr/617/docs/search.index/00ea8f3dbdef.js create mode 100644 pr/617/docs/search.index/00fb5c4f9c23.js delete mode 100644 pr/617/docs/search.index/0107dc62ab6c.js create mode 100644 pr/617/docs/search.index/017426982d62.js create mode 100644 pr/617/docs/search.index/020540c03066.js create mode 100644 pr/617/docs/search.index/0219603ee875.js delete mode 100644 pr/617/docs/search.index/041663251e8f.js delete mode 100644 pr/617/docs/search.index/04a9e87be06a.js create mode 100644 pr/617/docs/search.index/0505a70598b7.js create mode 100644 pr/617/docs/search.index/051240e58a38.js delete mode 100644 pr/617/docs/search.index/05a01e5a13f7.js delete mode 100644 pr/617/docs/search.index/05b03b52ea2f.js create mode 100644 pr/617/docs/search.index/05e227d0495f.js delete mode 100644 pr/617/docs/search.index/0647d1d32d25.js delete mode 100644 pr/617/docs/search.index/066cb4cb59fe.js create mode 100644 pr/617/docs/search.index/07060b7a880b.js create mode 100644 pr/617/docs/search.index/071e1fe55efd.js delete mode 100644 pr/617/docs/search.index/0817d1ec6cb9.js rename pr/617/docs/search.index/{6ed509a41675.js => 08621659568f.js} (53%) delete mode 100644 pr/617/docs/search.index/09846932e7be.js delete mode 100644 pr/617/docs/search.index/09cc9a7ef753.js create mode 100644 pr/617/docs/search.index/09efdd050b38.js create mode 100644 pr/617/docs/search.index/0a0be613805b.js delete mode 100644 pr/617/docs/search.index/0a2dff3b40f0.js delete mode 100644 pr/617/docs/search.index/0a32807f6fd5.js delete mode 100644 pr/617/docs/search.index/0a98bd6387de.js create mode 100644 pr/617/docs/search.index/0afefb419068.js create mode 100644 pr/617/docs/search.index/0b31dbd8cc61.js delete mode 100644 pr/617/docs/search.index/0b75363a70af.js delete mode 100644 pr/617/docs/search.index/0bd1edc62be1.js create mode 100644 pr/617/docs/search.index/0c36c2caebf1.js create mode 100644 pr/617/docs/search.index/0c38ce31a369.js create mode 100644 pr/617/docs/search.index/0c58728aab63.js create mode 100644 pr/617/docs/search.index/0c88bb59b14a.js create mode 100644 pr/617/docs/search.index/0ca11bedf490.js create mode 100644 pr/617/docs/search.index/0cbfae16bec4.js create mode 100644 pr/617/docs/search.index/0d06702b2cc6.js create mode 100644 pr/617/docs/search.index/0d83091b253d.js create mode 100644 pr/617/docs/search.index/0db99ab57aac.js create mode 100644 pr/617/docs/search.index/0decf51a5366.js delete mode 100644 pr/617/docs/search.index/0e10c6390bcf.js delete mode 100644 pr/617/docs/search.index/0e64f8544bfd.js delete mode 100644 pr/617/docs/search.index/0ecb7a74954b.js delete mode 100644 pr/617/docs/search.index/0fd086f0ef1b.js rename pr/617/docs/search.index/{71c0c54d2614.js => 10ffbbec645d.js} (53%) create mode 100644 pr/617/docs/search.index/11205106a64b.js create mode 100644 pr/617/docs/search.index/113486b6b2b6.js delete mode 100644 pr/617/docs/search.index/11a240e9b947.js create mode 100644 pr/617/docs/search.index/120270aa39e9.js create mode 100644 pr/617/docs/search.index/1231139faa22.js create mode 100644 pr/617/docs/search.index/1271000c9004.js delete mode 100644 pr/617/docs/search.index/13257449bc05.js delete mode 100644 pr/617/docs/search.index/137d1f70f3ef.js delete mode 100644 pr/617/docs/search.index/13df067efa5b.js create mode 100644 pr/617/docs/search.index/13f38e008740.js rename pr/617/docs/search.index/{01cc8d107b7f.js => 140acd1805c2.js} (67%) delete mode 100644 pr/617/docs/search.index/143875586e56.js delete mode 100644 pr/617/docs/search.index/1454cc4b3625.js delete mode 100644 pr/617/docs/search.index/146012214b23.js create mode 100644 pr/617/docs/search.index/156802473293.js create mode 100644 pr/617/docs/search.index/158eff2eda3b.js create mode 100644 pr/617/docs/search.index/160defe62b6f.js delete mode 100644 pr/617/docs/search.index/1613c157a7c0.js delete mode 100644 pr/617/docs/search.index/16335499b785.js create mode 100644 pr/617/docs/search.index/1642b10854ec.js delete mode 100644 pr/617/docs/search.index/164abac8a96b.js delete mode 100644 pr/617/docs/search.index/16666d274533.js create mode 100644 pr/617/docs/search.index/16b34983ecba.js create mode 100644 pr/617/docs/search.index/17b20931cf6c.js create mode 100644 pr/617/docs/search.index/17c3fc246899.js create mode 100644 pr/617/docs/search.index/1808c73b09a0.js create mode 100644 pr/617/docs/search.index/182b5b46d5d5.js delete mode 100644 pr/617/docs/search.index/1853a3dd5558.js create mode 100644 pr/617/docs/search.index/1880a3564175.js create mode 100644 pr/617/docs/search.index/18b064acc7a2.js create mode 100644 pr/617/docs/search.index/18f4af644129.js create mode 100644 pr/617/docs/search.index/18fd1a9e7934.js delete mode 100644 pr/617/docs/search.index/197b84d1f6e5.js delete mode 100644 pr/617/docs/search.index/19bb8e539b3c.js create mode 100644 pr/617/docs/search.index/1a335dc013ee.js create mode 100644 pr/617/docs/search.index/1a7331c9f293.js create mode 100644 pr/617/docs/search.index/1abe42839e69.js create mode 100644 pr/617/docs/search.index/1b3f0c6a1d7d.js delete mode 100644 pr/617/docs/search.index/1b4c79c0be67.js delete mode 100644 pr/617/docs/search.index/1b9b9d2cb1f9.js delete mode 100644 pr/617/docs/search.index/1baaec618882.js create mode 100644 pr/617/docs/search.index/1bc38f904b32.js create mode 100644 pr/617/docs/search.index/1bcb042010cd.js delete mode 100644 pr/617/docs/search.index/1c486df13d0f.js delete mode 100644 pr/617/docs/search.index/1ca93b71869b.js delete mode 100644 pr/617/docs/search.index/1cbfb40fb014.js create mode 100644 pr/617/docs/search.index/1cc6eea1be2c.js create mode 100644 pr/617/docs/search.index/1d2539aa0107.js delete mode 100644 pr/617/docs/search.index/1d44eb8c93b9.js delete mode 100644 pr/617/docs/search.index/1d4595df1786.js delete mode 100644 pr/617/docs/search.index/1d498b21b79f.js create mode 100644 pr/617/docs/search.index/1d5569503590.js create mode 100644 pr/617/docs/search.index/1d607b1ec238.js create mode 100644 pr/617/docs/search.index/1d879b498dfa.js delete mode 100644 pr/617/docs/search.index/1d93d8252622.js create mode 100644 pr/617/docs/search.index/1dbef74dd164.js delete mode 100644 pr/617/docs/search.index/1df29bac508a.js create mode 100644 pr/617/docs/search.index/1e64655eaedf.js create mode 100644 pr/617/docs/search.index/1ed131c81757.js create mode 100644 pr/617/docs/search.index/1ee5b86102ad.js create mode 100644 pr/617/docs/search.index/1f32f40ccbcb.js delete mode 100644 pr/617/docs/search.index/1f8e6a0bbf2b.js create mode 100644 pr/617/docs/search.index/1fa23924c3c7.js create mode 100644 pr/617/docs/search.index/2003f002ccdb.js delete mode 100644 pr/617/docs/search.index/20727d2fe054.js create mode 100644 pr/617/docs/search.index/20ef81a14d6e.js delete mode 100644 pr/617/docs/search.index/218e52b0339e.js delete mode 100644 pr/617/docs/search.index/2206d66e8e6a.js create mode 100644 pr/617/docs/search.index/22934d7a650c.js create mode 100644 pr/617/docs/search.index/22d494009ab1.js delete mode 100644 pr/617/docs/search.index/2303707d866b.js delete mode 100644 pr/617/docs/search.index/236fc2dd2763.js delete mode 100644 pr/617/docs/search.index/23798fef204d.js create mode 100644 pr/617/docs/search.index/23e4988aecac.js delete mode 100644 pr/617/docs/search.index/23f7516e5703.js delete mode 100644 pr/617/docs/search.index/243d6718115c.js create mode 100644 pr/617/docs/search.index/2488a011453a.js delete mode 100644 pr/617/docs/search.index/24b2ddc4a0dd.js create mode 100644 pr/617/docs/search.index/24df159f5f12.js create mode 100644 pr/617/docs/search.index/250ec7c859d8.js delete mode 100644 pr/617/docs/search.index/2593c24072ce.js create mode 100644 pr/617/docs/search.index/25a024fd26b3.js delete mode 100644 pr/617/docs/search.index/272a63220c2a.js delete mode 100644 pr/617/docs/search.index/2794b2ea6f8a.js create mode 100644 pr/617/docs/search.index/27966aefced8.js delete mode 100644 pr/617/docs/search.index/27ee8910beb2.js create mode 100644 pr/617/docs/search.index/27ff847ec176.js delete mode 100644 pr/617/docs/search.index/27ffa050a6cf.js rename pr/617/docs/search.index/{53b036251557.js => 2867eb6253a6.js} (55%) create mode 100644 pr/617/docs/search.index/28ae7e2c3eff.js delete mode 100644 pr/617/docs/search.index/28b28f3de641.js create mode 100644 pr/617/docs/search.index/28c932d1d3a3.js delete mode 100644 pr/617/docs/search.index/28ca64a40b02.js delete mode 100644 pr/617/docs/search.index/2922cc5fe9b0.js delete mode 100644 pr/617/docs/search.index/292ab4b4dd12.js create mode 100644 pr/617/docs/search.index/2935d9f9f689.js create mode 100644 pr/617/docs/search.index/298a2d10a108.js create mode 100644 pr/617/docs/search.index/2a4f58d36642.js create mode 100644 pr/617/docs/search.index/2a689e7ce38c.js delete mode 100644 pr/617/docs/search.index/2a7bc2b190a6.js create mode 100644 pr/617/docs/search.index/2a90a7e1d3b9.js delete mode 100644 pr/617/docs/search.index/2ad2d4f46aea.js delete mode 100644 pr/617/docs/search.index/2b2a88985dd7.js create mode 100644 pr/617/docs/search.index/2b3c097bd30b.js create mode 100644 pr/617/docs/search.index/2b605a22de5b.js delete mode 100644 pr/617/docs/search.index/2b616085d3bb.js rename pr/617/docs/search.index/{134492284a6c.js => 2b670142def8.js} (55%) delete mode 100644 pr/617/docs/search.index/2b69804711dc.js create mode 100644 pr/617/docs/search.index/2b6f9f07c43a.js create mode 100644 pr/617/docs/search.index/2b87673d150b.js delete mode 100644 pr/617/docs/search.index/2c0afee3955b.js delete mode 100644 pr/617/docs/search.index/2cc429d48879.js create mode 100644 pr/617/docs/search.index/2d360f1667cc.js create mode 100644 pr/617/docs/search.index/2d83fa4de460.js delete mode 100644 pr/617/docs/search.index/2dbb149f52d2.js delete mode 100644 pr/617/docs/search.index/2ea9dee8417b.js create mode 100644 pr/617/docs/search.index/2ec9baa2b225.js create mode 100644 pr/617/docs/search.index/2f23c7c0d6ce.js create mode 100644 pr/617/docs/search.index/2f5deadfbad9.js delete mode 100644 pr/617/docs/search.index/2f978aecd3a2.js create mode 100644 pr/617/docs/search.index/3039810e4520.js delete mode 100644 pr/617/docs/search.index/30554a2e2f98.js delete mode 100644 pr/617/docs/search.index/308477dce192.js delete mode 100644 pr/617/docs/search.index/309101a2ab26.js create mode 100644 pr/617/docs/search.index/30bdf72af4ad.js delete mode 100644 pr/617/docs/search.index/30c32768f920.js delete mode 100644 pr/617/docs/search.index/3109aa229dc6.js create mode 100644 pr/617/docs/search.index/3142e6000013.js delete mode 100644 pr/617/docs/search.index/314abf4dfb52.js create mode 100644 pr/617/docs/search.index/31587e8a08ed.js create mode 100644 pr/617/docs/search.index/316c499513de.js create mode 100644 pr/617/docs/search.index/31793053504c.js create mode 100644 pr/617/docs/search.index/323ee7233560.js delete mode 100644 pr/617/docs/search.index/32695d4910d9.js delete mode 100644 pr/617/docs/search.index/32824d81361a.js delete mode 100644 pr/617/docs/search.index/32893117deca.js create mode 100644 pr/617/docs/search.index/32b279bb23c7.js delete mode 100644 pr/617/docs/search.index/32ca2ee3c5d5.js delete mode 100644 pr/617/docs/search.index/3339b459bd03.js delete mode 100644 pr/617/docs/search.index/334f97bd1666.js delete mode 100644 pr/617/docs/search.index/336e3a26ae11.js create mode 100644 pr/617/docs/search.index/337b7c87a50a.js create mode 100644 pr/617/docs/search.index/33ec105d3348.js create mode 100644 pr/617/docs/search.index/343a021f5f79.js delete mode 100644 pr/617/docs/search.index/3451b6eeceed.js delete mode 100644 pr/617/docs/search.index/345d3d0c7b44.js create mode 100644 pr/617/docs/search.index/347cdc137d51.js delete mode 100644 pr/617/docs/search.index/34893b3ba8c0.js create mode 100644 pr/617/docs/search.index/34ab39043216.js delete mode 100644 pr/617/docs/search.index/354e3e954960.js create mode 100644 pr/617/docs/search.index/35dceadd9c46.js delete mode 100644 pr/617/docs/search.index/360e8453d77c.js delete mode 100644 pr/617/docs/search.index/36800abc79d6.js delete mode 100644 pr/617/docs/search.index/373c5d490117.js delete mode 100644 pr/617/docs/search.index/3749fa2bd5f0.js delete mode 100644 pr/617/docs/search.index/3765f6d962ab.js delete mode 100644 pr/617/docs/search.index/38356d26d54e.js create mode 100644 pr/617/docs/search.index/38e03ed6ba8b.js create mode 100644 pr/617/docs/search.index/38e697a6153c.js create mode 100644 pr/617/docs/search.index/3995e07bb8ad.js delete mode 100644 pr/617/docs/search.index/399d9099d408.js create mode 100644 pr/617/docs/search.index/39d5be6cac82.js create mode 100644 pr/617/docs/search.index/3a68c92a1996.js delete mode 100644 pr/617/docs/search.index/3a70741f4557.js delete mode 100644 pr/617/docs/search.index/3a88f1aefd64.js delete mode 100644 pr/617/docs/search.index/3ab1ec480043.js delete mode 100644 pr/617/docs/search.index/3ace8391b89d.js create mode 100644 pr/617/docs/search.index/3ae3a44a9dc2.js delete mode 100644 pr/617/docs/search.index/3b7ea92b577a.js delete mode 100644 pr/617/docs/search.index/3bdcf6ef8822.js rename pr/617/docs/search.index/{718da5763b19.js => 3c097b312dbc.js} (57%) create mode 100644 pr/617/docs/search.index/3c4c9ddb2e7a.js create mode 100644 pr/617/docs/search.index/3c80b5c41902.js delete mode 100644 pr/617/docs/search.index/3cb92ce7537c.js create mode 100644 pr/617/docs/search.index/3cea80f3171e.js create mode 100644 pr/617/docs/search.index/3d02f4499ad1.js create mode 100644 pr/617/docs/search.index/3dd7e7b549d9.js delete mode 100644 pr/617/docs/search.index/3dda4acb68c1.js delete mode 100644 pr/617/docs/search.index/3e1261c016d6.js delete mode 100644 pr/617/docs/search.index/3f252083b9d3.js delete mode 100644 pr/617/docs/search.index/402299f94e1a.js delete mode 100644 pr/617/docs/search.index/402f200cf6ef.js create mode 100644 pr/617/docs/search.index/409d99102e7c.js delete mode 100644 pr/617/docs/search.index/413ca794b790.js create mode 100644 pr/617/docs/search.index/414c4c18736c.js delete mode 100644 pr/617/docs/search.index/41a331e65a3c.js create mode 100644 pr/617/docs/search.index/41d5f31e57ca.js delete mode 100644 pr/617/docs/search.index/41ecd022fb75.js delete mode 100644 pr/617/docs/search.index/42424466438e.js create mode 100644 pr/617/docs/search.index/42c3eb12af93.js create mode 100644 pr/617/docs/search.index/42ce13ba00cf.js create mode 100644 pr/617/docs/search.index/42d8f48ef067.js create mode 100644 pr/617/docs/search.index/42deef5af9ea.js create mode 100644 pr/617/docs/search.index/4314812a0c42.js create mode 100644 pr/617/docs/search.index/4399c1a05fce.js delete mode 100644 pr/617/docs/search.index/444cc3e74ea7.js create mode 100644 pr/617/docs/search.index/44610b5c95d7.js create mode 100644 pr/617/docs/search.index/44dab7c8fd35.js create mode 100644 pr/617/docs/search.index/4591acbbfc75.js delete mode 100644 pr/617/docs/search.index/45ca0364edb8.js create mode 100644 pr/617/docs/search.index/45e1f3021919.js create mode 100644 pr/617/docs/search.index/45e3802642d1.js create mode 100644 pr/617/docs/search.index/46027ae6580d.js create mode 100644 pr/617/docs/search.index/4634daa4cefd.js delete mode 100644 pr/617/docs/search.index/466cc5f494e9.js delete mode 100644 pr/617/docs/search.index/46edaca34104.js delete mode 100644 pr/617/docs/search.index/473145eca66b.js create mode 100644 pr/617/docs/search.index/4752f68ec378.js create mode 100644 pr/617/docs/search.index/479912951a6e.js delete mode 100644 pr/617/docs/search.index/47cc59b03f79.js delete mode 100644 pr/617/docs/search.index/4848ba157001.js delete mode 100644 pr/617/docs/search.index/492c490fc1ab.js delete mode 100644 pr/617/docs/search.index/497fe4bb728c.js delete mode 100644 pr/617/docs/search.index/4989563e348d.js delete mode 100644 pr/617/docs/search.index/49ba647cb014.js create mode 100644 pr/617/docs/search.index/49fd4004edab.js create mode 100644 pr/617/docs/search.index/4a0397aaa53b.js create mode 100644 pr/617/docs/search.index/4a3344dc82ef.js delete mode 100644 pr/617/docs/search.index/4a8e85bccb70.js create mode 100644 pr/617/docs/search.index/4abf3fbcd294.js delete mode 100644 pr/617/docs/search.index/4acd6eae406c.js create mode 100644 pr/617/docs/search.index/4ad27c6c71e0.js create mode 100644 pr/617/docs/search.index/4b1651402510.js create mode 100644 pr/617/docs/search.index/4b2657879883.js create mode 100644 pr/617/docs/search.index/4b4d3233065c.js delete mode 100644 pr/617/docs/search.index/4b50d365f4e7.js delete mode 100644 pr/617/docs/search.index/4b5746dbe11e.js create mode 100644 pr/617/docs/search.index/4b9183eec264.js delete mode 100644 pr/617/docs/search.index/4bd2750f696a.js create mode 100644 pr/617/docs/search.index/4c12fa9ec5af.js delete mode 100644 pr/617/docs/search.index/4c1740d83ed2.js create mode 100644 pr/617/docs/search.index/4c35289cde0f.js delete mode 100644 pr/617/docs/search.index/4c69b395ac8f.js delete mode 100644 pr/617/docs/search.index/4c92d522b4cf.js create mode 100644 pr/617/docs/search.index/4d199ba2a246.js create mode 100644 pr/617/docs/search.index/4dfb324aed51.js create mode 100644 pr/617/docs/search.index/4ecbb5c4c72a.js create mode 100644 pr/617/docs/search.index/4f4bed7058fd.js delete mode 100644 pr/617/docs/search.index/4f9cbcececbe.js delete mode 100644 pr/617/docs/search.index/5004f9821b1b.js delete mode 100644 pr/617/docs/search.index/501c6dcd1fda.js delete mode 100644 pr/617/docs/search.index/51193234a828.js create mode 100644 pr/617/docs/search.index/5136cd716661.js delete mode 100644 pr/617/docs/search.index/515482f97850.js delete mode 100644 pr/617/docs/search.index/51e00cb41a33.js create mode 100644 pr/617/docs/search.index/5200cabaa1d5.js delete mode 100644 pr/617/docs/search.index/5289f2209b5b.js delete mode 100644 pr/617/docs/search.index/5294b97e6b02.js delete mode 100644 pr/617/docs/search.index/52babe817b07.js delete mode 100644 pr/617/docs/search.index/532728edef14.js delete mode 100644 pr/617/docs/search.index/53976a6f19cd.js delete mode 100644 pr/617/docs/search.index/546ec0de9ebd.js delete mode 100644 pr/617/docs/search.index/54a8e6164f40.js create mode 100644 pr/617/docs/search.index/54a9637d5185.js delete mode 100644 pr/617/docs/search.index/54ec492054f5.js delete mode 100644 pr/617/docs/search.index/5512bb91ed8f.js create mode 100644 pr/617/docs/search.index/5530a91156e7.js delete mode 100644 pr/617/docs/search.index/55351f3455a2.js delete mode 100644 pr/617/docs/search.index/55ee716c76ca.js create mode 100644 pr/617/docs/search.index/55ffe625f527.js delete mode 100644 pr/617/docs/search.index/574d85053847.js delete mode 100644 pr/617/docs/search.index/577af22cc871.js delete mode 100644 pr/617/docs/search.index/57b2f70c36c8.js delete mode 100644 pr/617/docs/search.index/57c59a197af7.js delete mode 100644 pr/617/docs/search.index/57e10538b61a.js create mode 100644 pr/617/docs/search.index/5875637aff25.js delete mode 100644 pr/617/docs/search.index/58ae9e43f3d7.js create mode 100644 pr/617/docs/search.index/58d0d4294971.js create mode 100644 pr/617/docs/search.index/58dd78d0ece0.js create mode 100644 pr/617/docs/search.index/58f211c07279.js create mode 100644 pr/617/docs/search.index/593ba01b5f99.js create mode 100644 pr/617/docs/search.index/59b253409373.js create mode 100644 pr/617/docs/search.index/5a2b259ae98b.js rename pr/617/docs/search.index/{668c314f2126.js => 5ae92656f48a.js} (53%) delete mode 100644 pr/617/docs/search.index/5b1ddb314b3e.js rename pr/617/docs/search.index/{572b61c02809.js => 5b87cca78f8f.js} (52%) delete mode 100644 pr/617/docs/search.index/5b984867710b.js delete mode 100644 pr/617/docs/search.index/5bca52d11800.js create mode 100644 pr/617/docs/search.index/5be8c1468ebe.js delete mode 100644 pr/617/docs/search.index/5bf73eb997f1.js create mode 100644 pr/617/docs/search.index/5c4cb1a8cdbb.js create mode 100644 pr/617/docs/search.index/5cafc27ea81e.js create mode 100644 pr/617/docs/search.index/5cb3ebce9a92.js delete mode 100644 pr/617/docs/search.index/5d371c6e539d.js delete mode 100644 pr/617/docs/search.index/5dbe95a54095.js delete mode 100644 pr/617/docs/search.index/5dcfa0230fa2.js delete mode 100644 pr/617/docs/search.index/5ddbacd9a3f7.js create mode 100644 pr/617/docs/search.index/5e33d538e99d.js create mode 100644 pr/617/docs/search.index/5e6b62b9a81e.js create mode 100644 pr/617/docs/search.index/5ef223a8d6a1.js create mode 100644 pr/617/docs/search.index/5fb3c8092292.js delete mode 100644 pr/617/docs/search.index/5fe64baffbcd.js delete mode 100644 pr/617/docs/search.index/6065d8a3f7a7.js create mode 100644 pr/617/docs/search.index/60d75a7999e9.js create mode 100644 pr/617/docs/search.index/61c4bbfdf792.js delete mode 100644 pr/617/docs/search.index/61dd5d3444c7.js create mode 100644 pr/617/docs/search.index/62a8eb8e83c2.js delete mode 100644 pr/617/docs/search.index/63058cf605b0.js delete mode 100644 pr/617/docs/search.index/636e1a4d8b0b.js delete mode 100644 pr/617/docs/search.index/646de36a32e1.js create mode 100644 pr/617/docs/search.index/64b6efff3a2a.js create mode 100644 pr/617/docs/search.index/64df930283be.js create mode 100644 pr/617/docs/search.index/64ef428f9034.js delete mode 100644 pr/617/docs/search.index/64fd4fee47f7.js create mode 100644 pr/617/docs/search.index/6777b8890d6d.js create mode 100644 pr/617/docs/search.index/6778b40d0be8.js delete mode 100644 pr/617/docs/search.index/679932c21ba7.js delete mode 100644 pr/617/docs/search.index/67ebc6510c69.js delete mode 100644 pr/617/docs/search.index/6831cbf8bafb.js delete mode 100644 pr/617/docs/search.index/689383818a1f.js delete mode 100644 pr/617/docs/search.index/68d782416b4d.js create mode 100644 pr/617/docs/search.index/690e4297f1b1.js delete mode 100644 pr/617/docs/search.index/6967dd890c86.js create mode 100644 pr/617/docs/search.index/6a14e49e8487.js delete mode 100644 pr/617/docs/search.index/6a4963efb35c.js delete mode 100644 pr/617/docs/search.index/6a6edeb452a8.js delete mode 100644 pr/617/docs/search.index/6b2c05d48848.js delete mode 100644 pr/617/docs/search.index/6b510b6efde3.js delete mode 100644 pr/617/docs/search.index/6b7abdba88fe.js create mode 100644 pr/617/docs/search.index/6bb4fe213d80.js delete mode 100644 pr/617/docs/search.index/6bbd9d9cc477.js create mode 100644 pr/617/docs/search.index/6bc61df8f07f.js delete mode 100644 pr/617/docs/search.index/6bf86b6dc333.js delete mode 100644 pr/617/docs/search.index/6c95a9f746d9.js create mode 100644 pr/617/docs/search.index/6cacde78e373.js create mode 100644 pr/617/docs/search.index/6d1a04586937.js rename pr/617/docs/search.index/{1c685467eb29.js => 6df6929b7791.js} (62%) create mode 100644 pr/617/docs/search.index/6df9e397525a.js create mode 100644 pr/617/docs/search.index/6e094e6c5da3.js delete mode 100644 pr/617/docs/search.index/6e27b20dd6b6.js delete mode 100644 pr/617/docs/search.index/6e48771c696f.js delete mode 100644 pr/617/docs/search.index/6e4cb0afff2c.js delete mode 100644 pr/617/docs/search.index/6e8315600389.js create mode 100644 pr/617/docs/search.index/6e8cdd1201da.js delete mode 100644 pr/617/docs/search.index/6eba048936de.js create mode 100644 pr/617/docs/search.index/6ec3fb3fd992.js create mode 100644 pr/617/docs/search.index/6ee512238957.js create mode 100644 pr/617/docs/search.index/6f53f353729b.js create mode 100644 pr/617/docs/search.index/6f6c17668170.js create mode 100644 pr/617/docs/search.index/6f86e944f992.js delete mode 100644 pr/617/docs/search.index/6fb11194cbac.js create mode 100644 pr/617/docs/search.index/6fdce15328df.js delete mode 100644 pr/617/docs/search.index/6ff044a53b85.js create mode 100644 pr/617/docs/search.index/7021fc091b39.js delete mode 100644 pr/617/docs/search.index/703f5cade60e.js delete mode 100644 pr/617/docs/search.index/70830eb0fe35.js delete mode 100644 pr/617/docs/search.index/70937cc739a3.js delete mode 100644 pr/617/docs/search.index/70a2231ef1be.js delete mode 100644 pr/617/docs/search.index/70a6f2d585ca.js create mode 100644 pr/617/docs/search.index/70a97ca4cdef.js delete mode 100644 pr/617/docs/search.index/70f6f7a9ec4a.js create mode 100644 pr/617/docs/search.index/7157aa96e738.js delete mode 100644 pr/617/docs/search.index/7193e1d068da.js delete mode 100644 pr/617/docs/search.index/71d44b922c39.js create mode 100644 pr/617/docs/search.index/72676d5cbaf8.js delete mode 100644 pr/617/docs/search.index/72de5c3ebdcb.js delete mode 100644 pr/617/docs/search.index/72f13898206e.js create mode 100644 pr/617/docs/search.index/731453b1decc.js create mode 100644 pr/617/docs/search.index/7375756c2f33.js create mode 100644 pr/617/docs/search.index/740867f5f36d.js create mode 100644 pr/617/docs/search.index/747e275b3069.js delete mode 100644 pr/617/docs/search.index/752f6f507e62.js create mode 100644 pr/617/docs/search.index/755bb5c9b7cf.js delete mode 100644 pr/617/docs/search.index/756205ce70fb.js create mode 100644 pr/617/docs/search.index/75c296e3af6c.js create mode 100644 pr/617/docs/search.index/7653e5adb45d.js delete mode 100644 pr/617/docs/search.index/768dee78c1da.js delete mode 100644 pr/617/docs/search.index/76e5a08e87b8.js delete mode 100644 pr/617/docs/search.index/7713a6d48c1d.js delete mode 100644 pr/617/docs/search.index/7735482e90b3.js create mode 100644 pr/617/docs/search.index/7778f4540e60.js delete mode 100644 pr/617/docs/search.index/77ae1f56a954.js delete mode 100644 pr/617/docs/search.index/77e67fb66bc4.js create mode 100644 pr/617/docs/search.index/7811875cd00f.js delete mode 100644 pr/617/docs/search.index/783c8a1a67c6.js delete mode 100644 pr/617/docs/search.index/78db9c8703f7.js create mode 100644 pr/617/docs/search.index/79f5b506de04.js create mode 100644 pr/617/docs/search.index/7a17df9d410e.js delete mode 100644 pr/617/docs/search.index/7acb48b1a833.js create mode 100644 pr/617/docs/search.index/7ad1f024ed61.js create mode 100644 pr/617/docs/search.index/7b8c3c69a10a.js create mode 100644 pr/617/docs/search.index/7b927b2e19c3.js delete mode 100644 pr/617/docs/search.index/7bb93f30bb12.js create mode 100644 pr/617/docs/search.index/7beb472ea224.js create mode 100644 pr/617/docs/search.index/7c776e35ecc3.js create mode 100644 pr/617/docs/search.index/7cfe15e65b11.js create mode 100644 pr/617/docs/search.index/7d878a92407a.js create mode 100644 pr/617/docs/search.index/7deb33a6ac15.js create mode 100644 pr/617/docs/search.index/7e12e28816c8.js delete mode 100644 pr/617/docs/search.index/7ed09b774fa7.js delete mode 100644 pr/617/docs/search.index/7eda4a96e99d.js delete mode 100644 pr/617/docs/search.index/7ee2eb9126d0.js create mode 100644 pr/617/docs/search.index/7f22e53fd5b4.js delete mode 100644 pr/617/docs/search.index/7f43d011c020.js delete mode 100644 pr/617/docs/search.index/7f5b04ac747f.js create mode 100644 pr/617/docs/search.index/7f660a8647b9.js create mode 100644 pr/617/docs/search.index/7f7a7539772c.js delete mode 100644 pr/617/docs/search.index/7fc5a0d8a50f.js delete mode 100644 pr/617/docs/search.index/desc/ac16b357e09e.js create mode 100644 pr/617/docs/search.index/desc/c4e11ee91647.js delete mode 100644 pr/617/docs/search.index/entry/55a8046a740f.js create mode 100644 pr/617/docs/search.index/entry/d607f4be9e29.js create mode 100644 pr/617/docs/search.index/function/5a519e950cb8.js delete mode 100644 pr/617/docs/search.index/function/c06b3b2c2e2b.js create mode 100644 pr/617/docs/search.index/generic_inverted_index/c475b6aeacd1.js delete mode 100644 pr/617/docs/search.index/generic_inverted_index/f9c98603db6c.js rename pr/617/docs/search.index/name/{56eac45ecfe0.js => d0f424d3d1a6.js} (59%) rename pr/617/docs/search.index/normalizedName/{6387bfcfcd90.js => aae5a537ba3b.js} (60%) delete mode 100644 pr/617/docs/search.index/type/6f91bb67b10d.js create mode 100644 pr/617/docs/search.index/type/c2fb7140b1bd.js diff --git a/pr/617/docs/noq/congestion/index.html b/pr/617/docs/noq/congestion/index.html index 2b99d73eb..621fdeb4e 100644 --- a/pr/617/docs/noq/congestion/index.html +++ b/pr/617/docs/noq/congestion/index.html @@ -1,4 +1,4 @@ -noq::congestion - Rust

Module congestion

Module congestion 

Source
Expand description

Logic for controlling the rate at which data is sent

+noq::congestion - Rust

Module congestion

Module congestion 

Source
Expand description

Logic for controlling the rate at which data is sent

Structs§

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

Traits§

Controller
Common interface for different congestion controllers
ControllerFactory
Constructs controllers on demand
\ No newline at end of file diff --git a/pr/617/docs/noq/crypto/index.html b/pr/617/docs/noq/crypto/index.html index c8af4df9e..f1c0b453d 100644 --- a/pr/617/docs/noq/crypto/index.html +++ b/pr/617/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/617/docs/noq/enum.ClosePathError.html b/pr/617/docs/noq/enum.ClosePathError.html index e09239da0..53efcf582 100644 --- a/pr/617/docs/noq/enum.ClosePathError.html +++ b/pr/617/docs/noq/enum.ClosePathError.html @@ -1,4 +1,4 @@ -ClosePathError in noq - Rust

ClosePathError

Enum ClosePathError 

Source
pub enum ClosePathError {
+ClosePathError in noq - Rust

ClosePathError

Enum ClosePathError 

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

ClosedPath

The path is already closed or was never opened

§

LastOpenPath

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

Use Connection::close to end the connection instead.

-

Trait Implementations§

Source§

impl Clone for ClosePathError

Source§

fn clone(&self) -> ClosePathError

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for ClosePathError

Source§

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

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

impl Display for ClosePathError

Source§

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

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

impl Error for ClosePathError

1.30.0 · Source§

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

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

fn description(&self) -> &str

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

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

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

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

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

impl PartialEq for ClosePathError

Source§

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

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

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

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

impl Eq for ClosePathError

Source§

impl StructuralPartialEq for ClosePathError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for ClosePathError

Source§

fn clone(&self) -> ClosePathError

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for ClosePathError

Source§

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

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

impl Display for ClosePathError

Source§

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

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

impl Error for ClosePathError

1.30.0 · Source§

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

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

fn description(&self) -> &str

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

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

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

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

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

impl PartialEq for ClosePathError

Source§

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

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

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

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

impl Eq for ClosePathError

Source§

impl StructuralPartialEq for ClosePathError

Auto Trait Implementations§

Blanket Implementations§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

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

Source§

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

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/617/docs/noq/enum.ConfigError.html b/pr/617/docs/noq/enum.ConfigError.html index e04597bbe..5e526b4b2 100644 --- a/pr/617/docs/noq/enum.ConfigError.html +++ b/pr/617/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/617/docs/noq/enum.ConnectionError.html b/pr/617/docs/noq/enum.ConnectionError.html index d9e87d540..21a2c4a10 100644 --- a/pr/617/docs/noq/enum.ConnectionError.html +++ b/pr/617/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/617/docs/noq/enum.Dir.html b/pr/617/docs/noq/enum.Dir.html index 43d1fb0f4..5e2e27f31 100644 --- a/pr/617/docs/noq/enum.Dir.html +++ b/pr/617/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/617/docs/noq/enum.PathError.html b/pr/617/docs/noq/enum.PathError.html index b2129d2f5..715d048e9 100644 --- a/pr/617/docs/noq/enum.PathError.html +++ b/pr/617/docs/noq/enum.PathError.html @@ -1,4 +1,4 @@ -PathError in noq - Rust

PathError

Enum PathError 

Source
pub enum PathError {
+PathError in noq - Rust

PathError

Enum PathError 

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

RemoteCidsExhausted

No remote CIDs available to open a new path

§

ValidationFailed

Path could not be validated and will be abandoned

§

InvalidRemoteAddress(SocketAddr)

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

-

Trait Implementations§

Source§

impl Clone for PathError

Source§

fn clone(&self) -> PathError

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for PathError

Source§

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

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

impl Display for PathError

Source§

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

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

impl Error for PathError

1.30.0 · Source§

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

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

fn description(&self) -> &str

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

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

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

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

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

impl PartialEq for PathError

Source§

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

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

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

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

impl Copy for PathError

Source§

impl Eq for PathError

Source§

impl StructuralPartialEq for PathError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for PathError

Source§

fn clone(&self) -> PathError

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for PathError

Source§

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

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

impl Display for PathError

Source§

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

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

impl Error for PathError

1.30.0 · Source§

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

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

fn description(&self) -> &str

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

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

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

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

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

impl PartialEq for PathError

Source§

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

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

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

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

impl Copy for PathError

Source§

impl Eq for PathError

Source§

impl StructuralPartialEq for PathError

Auto Trait Implementations§

Blanket Implementations§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

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

Source§

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

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/617/docs/noq/enum.PathEvent.html b/pr/617/docs/noq/enum.PathEvent.html index da4b6dad6..6a54cac03 100644 --- a/pr/617/docs/noq/enum.PathEvent.html +++ b/pr/617/docs/noq/enum.PathEvent.html @@ -1,4 +1,4 @@ -PathEvent in noq - Rust

PathEvent

Enum PathEvent 

Source
pub enum PathEvent {
+PathEvent in noq - Rust

PathEvent

Enum PathEvent 

Source
pub enum PathEvent {
     Opened {
         id: PathId,
     },
@@ -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<(), Error>

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

impl PartialEq for PathEvent

Source§

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

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

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

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

impl Eq for PathEvent

Source§

impl StructuralPartialEq for PathEvent

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for PathEvent

Source§

fn clone(&self) -> PathEvent

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for PathEvent

Source§

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

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

impl PartialEq for PathEvent

Source§

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

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

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

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

impl Eq for PathEvent

Source§

impl StructuralPartialEq for PathEvent

Auto Trait Implementations§

Blanket Implementations§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

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

Source§

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

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/617/docs/noq/enum.PathStatus.html b/pr/617/docs/noq/enum.PathStatus.html index 4422c371b..9298a8940 100644 --- a/pr/617/docs/noq/enum.PathStatus.html +++ b/pr/617/docs/noq/enum.PathStatus.html @@ -1,4 +1,4 @@ -PathStatus in noq - Rust

PathStatus

Enum PathStatus 

Source
pub enum PathStatus {
+PathStatus in noq - Rust

PathStatus

Enum PathStatus 

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

The QUIC-MULTIPATH path status

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

§

Backup

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

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

-

Trait Implementations§

Source§

impl Clone for PathStatus

Source§

fn clone(&self) -> PathStatus

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for PathStatus

Source§

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

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

impl Default for PathStatus

Source§

fn default() -> PathStatus

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

impl PartialEq for PathStatus

Source§

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

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

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

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

impl Copy for PathStatus

Source§

impl Eq for PathStatus

Source§

impl StructuralPartialEq for PathStatus

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for PathStatus

Source§

fn clone(&self) -> PathStatus

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for PathStatus

Source§

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

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

impl Default for PathStatus

Source§

fn default() -> PathStatus

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

impl PartialEq for PathStatus

Source§

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

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

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

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

impl Copy for PathStatus

Source§

impl Eq for PathStatus

Source§

impl StructuralPartialEq for PathStatus

Auto Trait Implementations§

Blanket Implementations§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

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

Source§

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

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/617/docs/noq/enum.SendDatagramError.html b/pr/617/docs/noq/enum.SendDatagramError.html index 4ac61c63c..dbf07e2a6 100644 --- a/pr/617/docs/noq/enum.SendDatagramError.html +++ b/pr/617/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/617/docs/noq/enum.SetPathStatusError.html b/pr/617/docs/noq/enum.SetPathStatusError.html index 2a9557440..3bb2857be 100644 --- a/pr/617/docs/noq/enum.SetPathStatusError.html +++ b/pr/617/docs/noq/enum.SetPathStatusError.html @@ -1,11 +1,11 @@ -SetPathStatusError in noq - Rust

SetPathStatusError

Enum SetPathStatusError 

Source
pub enum SetPathStatusError {
+SetPathStatusError in noq - Rust

SetPathStatusError

Enum SetPathStatusError 

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

Error from setting path status

Variants§

§

ClosedPath

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

§

MultipathNotNegotiated

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

-

Trait Implementations§

Source§

impl Clone for SetPathStatusError

Source§

fn clone(&self) -> SetPathStatusError

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for SetPathStatusError

Source§

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

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

impl Display for SetPathStatusError

Source§

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

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

impl Error for SetPathStatusError

1.30.0 · Source§

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

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

fn description(&self) -> &str

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

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

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

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

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

impl PartialEq for SetPathStatusError

Source§

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

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

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

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

impl Eq for SetPathStatusError

Source§

impl StructuralPartialEq for SetPathStatusError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for SetPathStatusError

Source§

fn clone(&self) -> SetPathStatusError

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for SetPathStatusError

Source§

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

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

impl Display for SetPathStatusError

Source§

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

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

impl Error for SetPathStatusError

1.30.0 · Source§

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

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

fn description(&self) -> &str

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

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

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

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

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

impl PartialEq for SetPathStatusError

Source§

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

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

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

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

impl Eq for SetPathStatusError

Source§

impl StructuralPartialEq for SetPathStatusError

Auto Trait Implementations§

Blanket Implementations§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

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

Source§

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

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/617/docs/noq/enum.Side.html b/pr/617/docs/noq/enum.Side.html index 211e245d6..2a88b859f 100644 --- a/pr/617/docs/noq/enum.Side.html +++ b/pr/617/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/617/docs/noq/struct.AcceptBi.html b/pr/617/docs/noq/struct.AcceptBi.html index e2b6da590..213f9b881 100644 --- a/pr/617/docs/noq/struct.AcceptBi.html +++ b/pr/617/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/617/docs/noq/struct.AcceptUni.html b/pr/617/docs/noq/struct.AcceptUni.html index 725d4cf6f..485306ebe 100644 --- a/pr/617/docs/noq/struct.AcceptUni.html +++ b/pr/617/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/617/docs/noq/struct.AckFrequencyConfig.html b/pr/617/docs/noq/struct.AckFrequencyConfig.html index 2d3d900e9..20a7cff00 100644 --- a/pr/617/docs/noq/struct.AckFrequencyConfig.html +++ b/pr/617/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/617/docs/noq/struct.AddressDiscovery.html b/pr/617/docs/noq/struct.AddressDiscovery.html index b14c5a0cd..22a2d8d82 100644 --- a/pr/617/docs/noq/struct.AddressDiscovery.html +++ b/pr/617/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/617/docs/noq/struct.ClientConfig.html b/pr/617/docs/noq/struct.ClientConfig.html index 8253ad80d..db7008c26 100644 --- a/pr/617/docs/noq/struct.ClientConfig.html +++ b/pr/617/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/617/docs/noq/struct.Closed.html b/pr/617/docs/noq/struct.Closed.html index 51bccab3a..4bd279633 100644 --- a/pr/617/docs/noq/struct.Closed.html +++ b/pr/617/docs/noq/struct.Closed.html @@ -1,4 +1,4 @@ -Closed in noq - Rust

Closed

Struct Closed 

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

Closed

Struct Closed 

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

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

-

Trait Implementations§

Source§

impl Clone for Closed

Source§

fn clone(&self) -> Closed

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for Closed

Source§

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

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl !Freeze for Closed

§

impl !RefUnwindSafe for Closed

§

impl Send for Closed

§

impl Sync for Closed

§

impl Unpin for Closed

§

impl !UnwindSafe for Closed

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for Closed

Source§

fn clone(&self) -> Closed

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for Closed

Source§

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

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl !Freeze for Closed

§

impl !RefUnwindSafe for Closed

§

impl Send for Closed

§

impl Sync for Closed

§

impl Unpin for Closed

§

impl !UnwindSafe for Closed

Blanket Implementations§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

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

Source§

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

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/617/docs/noq/struct.ClosedPath.html b/pr/617/docs/noq/struct.ClosedPath.html index 38e0c7a7a..5c7e83667 100644 --- a/pr/617/docs/noq/struct.ClosedPath.html +++ b/pr/617/docs/noq/struct.ClosedPath.html @@ -1,6 +1,6 @@ -ClosedPath in noq - Rust

ClosedPath

Struct ClosedPath 

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

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

-

Trait Implementations§

Source§

impl Clone for ClosedPath

Source§

fn clone(&self) -> ClosedPath

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for ClosedPath

Source§

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

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

impl Default for ClosedPath

Source§

fn default() -> ClosedPath

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

impl Display for ClosedPath

Source§

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

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

impl Error for ClosedPath

1.30.0 · Source§

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

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

fn description(&self) -> &str

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

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

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

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

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

impl PartialEq for ClosedPath

Source§

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

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

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

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

impl Eq for ClosedPath

Source§

impl StructuralPartialEq for ClosedPath

Auto Trait Implementations§

Blanket Implementations§

Source§

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

ClosedPath

Struct ClosedPath 

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

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

+

Trait Implementations§

Source§

impl Clone for ClosedPath

Source§

fn clone(&self) -> ClosedPath

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for ClosedPath

Source§

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

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

impl Default for ClosedPath

Source§

fn default() -> ClosedPath

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

impl Display for ClosedPath

Source§

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

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

impl Error for ClosedPath

1.30.0 · Source§

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

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

fn description(&self) -> &str

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

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

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

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

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

impl PartialEq for ClosedPath

Source§

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

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

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

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

impl Eq for ClosedPath

Source§

impl StructuralPartialEq for ClosedPath

Auto Trait Implementations§

Blanket Implementations§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

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

Source§

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

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/617/docs/noq/struct.Connecting.html b/pr/617/docs/noq/struct.Connecting.html index fbd5cd159..989576043 100644 --- a/pr/617/docs/noq/struct.Connecting.html +++ b/pr/617/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

+

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 @@ -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/617/docs/noq/struct.Connection.html b/pr/617/docs/noq/struct.Connection.html index fac0582c9..3d80f160f 100644 --- a/pr/617/docs/noq/struct.Connection.html +++ b/pr/617/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,29 +8,29 @@ 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, ) -> 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, @@ -45,34 +45,34 @@ path opens. The P 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.

+
Source

pub fn path(&self, id: PathId) -> Option<Path>

Returns the Path structure of an open path

+
Source

pub fn path_events(&self) -> PathEvents

A stream of PathEvents for all paths in this connection.

The stream will yield a PathEvent whenever there is a change in the state of any path in this connection. The events need to be processed immediately, since there isn’t an unbounded buffer for them.

If processing of events lags behind too much, you will get an error of type crate::Lagged indicating how many events were lost. The stream continues after a lag, delivering the oldest retained message next.

-
Source

pub fn nat_traversal_updates(&self) -> NatTraversalUpdates

A stream of NAT traversal updates for this connection.

+
Source

pub fn nat_traversal_updates(&self) -> NatTraversalUpdates

A stream of NAT traversal updates for this connection.

The events need to be processed immediately, since there isn’t an unbounded buffer for them.

If processing of events lags behind too much, you will get an error of type crate::Lagged indicating how many events were lost. The stream continues after a lag, delivering the oldest retained message next.

-
Source

pub async fn closed(&self) -> ConnectionError

Wait for the connection to be closed for any reason

+
Source

pub async fn closed(&self) -> ConnectionError

Wait for the connection to be closed for any reason

Despite the return type’s name, closed connections are often not an error condition at the application layer. Cases that might be routine include ConnectionError::LocallyClosed and ConnectionError::ApplicationClosed.

-
Source

pub fn on_closed(&self) -> OnClosed

Wait for the connection to be closed without keeping a strong reference to the connection

+
Source

pub fn on_closed(&self) -> OnClosed

Wait for the connection to be closed without keeping a strong reference to the connection

Returns a future that resolves, once the connection is closed, to a Closed struct describing the close reason and final connection and per-path statistics.

Calling Self::closed keeps the connection alive until it is either closed locally via Connection::close or closed by the remote peer. This function instead does not keep the connection itself alive, so if all other clones of the connection are dropped, the connection will be closed implicitly even if there are futures returned from this function still being awaited.

-
Source

pub fn close_reason(&self) -> Option<ConnectionError>

Whether the connection is closed, and why.

+
Source

pub fn close_reason(&self) -> Option<ConnectionError>

Whether the connection is closed, and why.

The close_reason is always set to Some(ConnectionError) when a socket is closed; whether it was closed manually by calling Connection::close() or due to an internal error (such as an idle timeout or the peer closing the connection).

Note: when the connection is closed, connection.close_reason().is_some() will always be true.

-
Source

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

Close the connection immediately.

+
Source

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

Close the connection immediately.

Pending operations will fail immediately with ConnectionError::LocallyClosed. No more data is sent to the peer and the peer may drop buffered data upon receiving the CONNECTION_CLOSE frame.

@@ -93,7 +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. @@ -101,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], @@ -161,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

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

@@ -188,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 diff --git a/pr/617/docs/noq/struct.EndpointConfig.html b/pr/617/docs/noq/struct.EndpointConfig.html index 5db8f93f3..e8d394ce3 100644 --- a/pr/617/docs/noq/struct.EndpointConfig.html +++ b/pr/617/docs/noq/struct.EndpointConfig.html @@ -1,7 +1,7 @@ -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( +

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

@@ -10,9 +10,9 @@ generator which will be used to generate the CIDs used for incoming packets on a 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).

@@ -21,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/617/docs/noq/struct.IdleTimeout.html b/pr/617/docs/noq/struct.IdleTimeout.html index ace1e130b..7a08b236d 100644 --- a/pr/617/docs/noq/struct.IdleTimeout.html +++ b/pr/617/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/617/docs/noq/struct.MtuDiscoveryConfig.html b/pr/617/docs/noq/struct.MtuDiscoveryConfig.html index 07fb6950d..648670fe2 100644 --- a/pr/617/docs/noq/struct.MtuDiscoveryConfig.html +++ b/pr/617/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/617/docs/noq/struct.OnClosed.html b/pr/617/docs/noq/struct.OnClosed.html index ca36093fb..c8a15b42b 100644 --- a/pr/617/docs/noq/struct.OnClosed.html +++ b/pr/617/docs/noq/struct.OnClosed.html @@ -1,6 +1,6 @@ -OnClosed in noq - Rust

OnClosed

Struct OnClosed 

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

Future returned by Connection::on_closed

+OnClosed in noq - Rust

OnClosed

Struct OnClosed 

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

Future returned by Connection::on_closed

Resolves to Closed.

-

Trait Implementations§

Source§

impl Drop for OnClosed

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

impl Future for OnClosed

Source§

type Output = Closed

The type of value produced on completion.
Source§

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

Attempts to resolve the future to a final value, registering +

Trait Implementations§

Source§

impl Drop for OnClosed

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

impl Future for OnClosed

Source§

type Output = Closed

The type of value produced on completion.
Source§

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

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

Auto Trait Implementations§

Blanket Implementations§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where diff --git a/pr/617/docs/noq/struct.OpenBi.html b/pr/617/docs/noq/struct.OpenBi.html index 18eeb3fea..2766d6778 100644 --- a/pr/617/docs/noq/struct.OpenBi.html +++ b/pr/617/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/617/docs/noq/struct.OpenUni.html b/pr/617/docs/noq/struct.OpenUni.html index 8cfbb03f4..72438d7de 100644 --- a/pr/617/docs/noq/struct.OpenUni.html +++ b/pr/617/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/617/docs/noq/struct.Path.html b/pr/617/docs/noq/struct.Path.html index ace476fdb..93edafae5 100644 --- a/pr/617/docs/noq/struct.Path.html +++ b/pr/617/docs/noq/struct.Path.html @@ -1,7 +1,7 @@ -Path in noq - Rust

Path

Struct Path 

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

An open network transmission within a multipath-enabled connection.

+Path in noq - Rust

Path

Struct Path 

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

An open network transmission within a multipath-enabled connection.

As long as a Path or WeakPathHandle is alive, it is ensured that the PathStats for this path are not dropped even after the path is abandoned.

-

Implementations§

Source§

impl Path

Source

pub fn weak_handle(&self) -> WeakPathHandle

Returns a WeakPathHandle for this path.

+

Implementations§

Source§

impl Path

Source

pub fn weak_handle(&self) -> WeakPathHandle

Returns a WeakPathHandle for this path.

Holding a WeakPathHandle does not keep a connection alive, but ensures that the path’s stats are not dropped until the underlying connection is dropped, even if the path is abandoned.

@@ -28,8 +28,11 @@ returned.

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 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) -> 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, 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/617/docs/noq/struct.ReadDatagram.html b/pr/617/docs/noq/struct.ReadDatagram.html index 31a72092e..70bb548fe 100644 --- a/pr/617/docs/noq/struct.ReadDatagram.html +++ b/pr/617/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/617/docs/noq/struct.SendDatagram.html b/pr/617/docs/noq/struct.SendDatagram.html index 4b857dd58..a5850da73 100644 --- a/pr/617/docs/noq/struct.SendDatagram.html +++ b/pr/617/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/617/docs/noq/struct.ServerConfig.html b/pr/617/docs/noq/struct.ServerConfig.html index c0e24bf70..6612433e8 100644 --- a/pr/617/docs/noq/struct.ServerConfig.html +++ b/pr/617/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/617/docs/noq/struct.StdSystemTime.html b/pr/617/docs/noq/struct.StdSystemTime.html index 28fde3d83..7207e17ec 100644 --- a/pr/617/docs/noq/struct.StdSystemTime.html +++ b/pr/617/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/617/docs/noq/struct.StreamId.html b/pr/617/docs/noq/struct.StreamId.html index 025377af9..136727fbc 100644 --- a/pr/617/docs/noq/struct.StreamId.html +++ b/pr/617/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/617/docs/noq/struct.Transmit.html b/pr/617/docs/noq/struct.Transmit.html index d24c3a2a7..3d3bd2b84 100644 --- a/pr/617/docs/noq/struct.Transmit.html +++ b/pr/617/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/617/docs/noq/struct.TransportConfig.html b/pr/617/docs/noq/struct.TransportConfig.html index c2a78a2a5..1196843ed 100644 --- a/pr/617/docs/noq/struct.TransportConfig.html +++ b/pr/617/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,16 +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 max_outgoing_bytes_per_second( +

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

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

@@ -118,11 +118,11 @@ extension and may use it in other ways.

None to disable, which is the default. Only one side of any given connection needs keep-alive enabled for the connection to be preserved. Must be set lower than the idle_timeout of both peers to be effective.

-
Source

pub fn crypto_buffer_size(&mut self, value: usize) -> &mut TransportConfig

Maximum quantity of out-of-order crypto layer data to buffer

-
Source

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

Whether the implementation is permitted to set the spin bit on this connection

+
Source

pub fn crypto_buffer_size(&mut self, value: usize) -> &mut TransportConfig

Maximum quantity of out-of-order crypto layer data to buffer

+
Source

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

Whether the implementation is permitted to set the spin bit on this connection

This allows passive observers to easily judge the round trip time of a connection, which can be useful for network administration but sacrifices a small amount of privacy.

-
Source

pub fn datagram_receive_buffer_size( +

Source

pub fn datagram_receive_buffer_size( &mut self, value: Option<usize>, ) -> &mut TransportConfig

Maximum number of incoming application datagram bytes to buffer, or None to disable @@ -130,7 +130,7 @@ incoming datagrams

The peer is forbidden to send single datagrams larger than this size. If the aggregate size of all datagrams that have been received from the peer but not consumed by the application exceeds this value, old datagrams are dropped until it is no longer exceeded.

-
Source

pub fn datagram_send_buffer_size( +

Source

pub fn datagram_send_buffer_size( &mut self, value: usize, ) -> &mut TransportConfig

Maximum number of outgoing application datagram bytes to buffer

@@ -138,7 +138,7 @@ exceeds this value, old datagrams are dropped until it is no longer exceeded.

-
Source

pub fn congestion_controller_factory( +

Source

pub fn congestion_controller_factory( &mut self, factory: Arc<dyn ControllerFactory + Send + Sync>, ) -> &mut TransportConfig

How to construct new congestion::Controllers

@@ -146,7 +146,7 @@ sent, older datagrams are dropped until sufficient space is available.

e.g. a congestion::NewRenoConfig.

§Example
let mut config = TransportConfig::default();
-config.congestion_controller_factory(Arc::new(congestion::NewRenoConfig::default()));
Source

pub fn enable_segmentation_offload( +config.congestion_controller_factory(Arc::new(congestion::NewRenoConfig::default()));

Source

pub fn enable_segmentation_offload( &mut self, enabled: bool, ) -> &mut TransportConfig

Whether to use “Generic Segmentation Offload” to accelerate transmits, when supported by the @@ -157,13 +157,13 @@ headers, such as when transmitting bulk data on a connection. However, it is not by all network interface drivers or packet inspection tools. noq-udp will attempt to disable GSO automatically when unavailable, but this can lead to spurious packet loss at startup, temporarily degrading performance.

-
Source

pub fn send_observed_address_reports( +

Source

pub fn send_observed_address_reports( &mut self, enabled: bool, ) -> &mut TransportConfig

Whether to send observed address reports to peers.

This will aid peers in inferring their reachable address, which in most NATd networks will not be easily available to them.

-
Source

pub fn receive_observed_address_reports( +

Source

pub fn receive_observed_address_reports( &mut self, enabled: bool, ) -> &mut TransportConfig

Whether to receive observed address reports from other peers.

@@ -171,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.

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

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

@@ -199,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 @@ -208,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/617/docs/noq/struct.ValidationTokenConfig.html b/pr/617/docs/noq/struct.ValidationTokenConfig.html index 20e445767..3bc8c6025 100644 --- a/pr/617/docs/noq/struct.ValidationTokenConfig.html +++ b/pr/617/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/617/docs/noq/struct.VarInt.html b/pr/617/docs/noq/struct.VarInt.html index 32293cea6..80de7d25d 100644 --- a/pr/617/docs/noq/struct.VarInt.html +++ b/pr/617/docs/noq/struct.VarInt.html @@ -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/617/docs/noq/struct.VarIntBoundsExceeded.html b/pr/617/docs/noq/struct.VarIntBoundsExceeded.html index 7e0659378..fe818f3e1 100644 --- a/pr/617/docs/noq/struct.VarIntBoundsExceeded.html +++ b/pr/617/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, +

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/617/docs/noq/struct.WeakConnectionHandle.html b/pr/617/docs/noq/struct.WeakConnectionHandle.html index 31a4bdd9a..0e9c6b259 100644 --- a/pr/617/docs/noq/struct.WeakConnectionHandle.html +++ b/pr/617/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/617/docs/noq/struct.WeakPathHandle.html b/pr/617/docs/noq/struct.WeakPathHandle.html index 967321d0a..9e6d5a2bd 100644 --- a/pr/617/docs/noq/struct.WeakPathHandle.html +++ b/pr/617/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) -> 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 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/617/docs/noq/struct.ZeroRttAccepted.html b/pr/617/docs/noq/struct.ZeroRttAccepted.html index 1ac0d725e..f6332f3cd 100644 --- a/pr/617/docs/noq/struct.ZeroRttAccepted.html +++ b/pr/617/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/617/docs/noq/trait.NetworkChangeHint.html b/pr/617/docs/noq/trait.NetworkChangeHint.html index cc5304fe1..0aec70ef6 100644 --- a/pr/617/docs/noq/trait.NetworkChangeHint.html +++ b/pr/617/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/617/docs/noq/trait.TimeSource.html b/pr/617/docs/noq/trait.TimeSource.html index c2870e881..c4cd574b0 100644 --- a/pr/617/docs/noq/trait.TimeSource.html +++ b/pr/617/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/617/docs/noq_proto/all.html b/pr/617/docs/noq_proto/all.html index 96afe1a04..f1cf42d02 100644 --- a/pr/617/docs/noq_proto/all.html +++ b/pr/617/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/617/docs/noq_proto/bench_exports/index.html b/pr/617/docs/noq_proto/bench_exports/index.html index 35bd9286a..270e1a5d0 100644 --- a/pr/617/docs/noq_proto/bench_exports/index.html +++ b/pr/617/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/617/docs/noq_proto/coding/trait.Decodable.html b/pr/617/docs/noq_proto/coding/trait.Decodable.html index c300b3913..e741a553f 100644 --- a/pr/617/docs/noq_proto/coding/trait.Decodable.html +++ b/pr/617/docs/noq_proto/coding/trait.Decodable.html @@ -3,4 +3,4 @@ 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/617/docs/noq_proto/coding/trait.Encodable.html b/pr/617/docs/noq_proto/coding/trait.Encodable.html index 93edc10a9..d82dd4b78 100644 --- a/pr/617/docs/noq_proto/coding/trait.Encodable.html +++ b/pr/617/docs/noq_proto/coding/trait.Encodable.html @@ -3,4 +3,4 @@ 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/617/docs/noq_proto/config/transport/constant.DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED.html b/pr/617/docs/noq_proto/config/transport/constant.DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED.html deleted file mode 100644 index 50d5e1b71..000000000 --- a/pr/617/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/617/docs/noq_proto/constant.DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED.html b/pr/617/docs/noq_proto/constant.DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED.html deleted file mode 100644 index 64c977b13..000000000 --- a/pr/617/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/617/docs/noq_proto/constant.DEFAULT_SUPPORTED_VERSIONS.html b/pr/617/docs/noq_proto/constant.DEFAULT_SUPPORTED_VERSIONS.html index 5dd9370be..0d9f7a4a4 100644 --- a/pr/617/docs/noq_proto/constant.DEFAULT_SUPPORTED_VERSIONS.html +++ b/pr/617/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/617/docs/noq_proto/enum.ClosePathError.html b/pr/617/docs/noq_proto/enum.ClosePathError.html index e68df36cb..674f4afce 100644 --- a/pr/617/docs/noq_proto/enum.ClosePathError.html +++ b/pr/617/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/617/docs/noq_proto/enum.ConfigError.html b/pr/617/docs/noq_proto/enum.ConfigError.html index 7da16b806..58f4a2ee9 100644 --- a/pr/617/docs/noq_proto/enum.ConfigError.html +++ b/pr/617/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/617/docs/noq_proto/enum.ConnectionError.html b/pr/617/docs/noq_proto/enum.ConnectionError.html index b31769cab..b666fb893 100644 --- a/pr/617/docs/noq_proto/enum.ConnectionError.html +++ b/pr/617/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/617/docs/noq_proto/enum.Dir.html b/pr/617/docs/noq_proto/enum.Dir.html index c957b33fa..cf0912c23 100644 --- a/pr/617/docs/noq_proto/enum.Dir.html +++ b/pr/617/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/617/docs/noq_proto/enum.Event.html b/pr/617/docs/noq_proto/enum.Event.html index 6a13295b5..67fb62955 100644 --- a/pr/617/docs/noq_proto/enum.Event.html +++ b/pr/617/docs/noq_proto/enum.Event.html @@ -1,4 +1,4 @@ -Event in noq_proto - Rust

Event

Enum Event 

Source
pub enum Event {
+Event in noq_proto - Rust

Event

Enum Event 

Source
pub enum Event {
     HandshakeDataReady,
     Connected,
     HandshakeConfirmed,
@@ -25,7 +25,7 @@ fail with §

DatagramsUnblocked

One or more application datagrams have been sent after blocking

§

Path(PathEvent)

(Multi)Path events

§

NatTraversal(Event)

n0’s nat traversal events

-

Trait Implementations§

Source§

impl Debug for Event

Source§

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

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

impl From<PathEvent> for Event

Source§

fn from(source: PathEvent) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

§

impl !Freeze for Event

§

impl !RefUnwindSafe for Event

§

impl Send for Event

§

impl Sync for Event

§

impl Unpin for Event

§

impl !UnwindSafe for Event

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Debug for Event

Source§

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

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

impl From<PathEvent> for Event

Source§

fn from(source: PathEvent) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

§

impl !Freeze for Event

§

impl !RefUnwindSafe for Event

§

impl Send for Event

§

impl Sync for Event

§

impl Unpin for Event

§

impl !UnwindSafe for Event

Blanket Implementations§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

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

Source§

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

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

diff --git a/pr/617/docs/noq_proto/enum.PathAbandonReason.html b/pr/617/docs/noq_proto/enum.PathAbandonReason.html index e54f8f350..70bfb7c0d 100644 --- a/pr/617/docs/noq_proto/enum.PathAbandonReason.html +++ b/pr/617/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/617/docs/noq_proto/enum.PathError.html b/pr/617/docs/noq_proto/enum.PathError.html index bc3433a7d..8663c4a99 100644 --- a/pr/617/docs/noq_proto/enum.PathError.html +++ b/pr/617/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/617/docs/noq_proto/enum.PathEvent.html b/pr/617/docs/noq_proto/enum.PathEvent.html index 1ec3fa5f5..d0b801650 100644 --- a/pr/617/docs/noq_proto/enum.PathEvent.html +++ b/pr/617/docs/noq_proto/enum.PathEvent.html @@ -1,4 +1,4 @@ -PathEvent in noq_proto - Rust

PathEvent

Enum PathEvent 

Source
pub enum PathEvent {
+PathEvent in noq_proto - Rust

PathEvent

Enum PathEvent 

Source
pub enum PathEvent {
     Opened {
         id: PathId,
     },
@@ -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/617/docs/noq_proto/enum.PathStatus.html b/pr/617/docs/noq_proto/enum.PathStatus.html index 7c6f1e7a2..e4267f123 100644 --- a/pr/617/docs/noq_proto/enum.PathStatus.html +++ b/pr/617/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/617/docs/noq_proto/enum.SetPathStatusError.html b/pr/617/docs/noq_proto/enum.SetPathStatusError.html index 47648ab96..ad26c2c8b 100644 --- a/pr/617/docs/noq_proto/enum.SetPathStatusError.html +++ b/pr/617/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/617/docs/noq_proto/enum.Side.html b/pr/617/docs/noq_proto/enum.Side.html index f528c5187..ae15eea78 100644 --- a/pr/617/docs/noq_proto/enum.Side.html +++ b/pr/617/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/617/docs/noq_proto/index.html b/pr/617/docs/noq_proto/index.html index f94efac20..1f6514d4b 100644 --- a/pr/617/docs/noq_proto/index.html +++ b/pr/617/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
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_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/617/docs/noq_proto/n0_nat_traversal/enum.Error.html b/pr/617/docs/noq_proto/n0_nat_traversal/enum.Error.html index 8b860033b..2594b1512 100644 --- a/pr/617/docs/noq_proto/n0_nat_traversal/enum.Error.html +++ b/pr/617/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/617/docs/noq_proto/n0_nat_traversal/enum.Event.html b/pr/617/docs/noq_proto/n0_nat_traversal/enum.Event.html index ba59da0d3..bdab40223 100644 --- a/pr/617/docs/noq_proto/n0_nat_traversal/enum.Event.html +++ b/pr/617/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/617/docs/noq_proto/n0_nat_traversal/index.html b/pr/617/docs/noq_proto/n0_nat_traversal/index.html index 4b718cd34..755b5b90d 100644 --- a/pr/617/docs/noq_proto/n0_nat_traversal/index.html +++ b/pr/617/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/617/docs/noq_proto/sidebar-items.js b/pr/617/docs/noq_proto/sidebar-items.js index e9a741d67..ab221b145 100644 --- a/pr/617/docs/noq_proto/sidebar-items.js +++ b/pr/617/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","Written"],"trait":["ConnectionIdGenerator","ConnectionIdParser","NetworkChangeHint","QlogFactory","TimeSource","TokenLog","TokenStore"]}; \ No newline at end of file diff --git a/pr/617/docs/noq_proto/struct.AckFrequencyConfig.html b/pr/617/docs/noq_proto/struct.AckFrequencyConfig.html index 195803191..17d6e33a1 100644 --- a/pr/617/docs/noq_proto/struct.AckFrequencyConfig.html +++ b/pr/617/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/617/docs/noq_proto/struct.ClientConfig.html b/pr/617/docs/noq_proto/struct.ClientConfig.html index 7038d8566..02efc801d 100644 --- a/pr/617/docs/noq_proto/struct.ClientConfig.html +++ b/pr/617/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/617/docs/noq_proto/struct.ClosedPath.html b/pr/617/docs/noq_proto/struct.ClosedPath.html index 15d3ebfbd..2825df62c 100644 --- a/pr/617/docs/noq_proto/struct.ClosedPath.html +++ b/pr/617/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/617/docs/noq_proto/struct.Connection.html b/pr/617/docs/noq_proto/struct.Connection.html index 3f95ea67d..d065af718 100644 --- a/pr/617/docs/noq_proto/struct.Connection.html +++ b/pr/617/docs/noq_proto/struct.Connection.html @@ -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
  • @@ -124,42 +124,42 @@ idle time. Setting None disables the timeout and stops the timer.

    max_datagrams specifies how many datagrams can be returned inside a single Transmit using GSO. This must be at least 1.

    -
Source

pub fn handle_event(&mut self, event: ConnectionEvent)

Process ConnectionEvents generated by the associated Endpoint

+
Source

pub fn handle_event(&mut self, event: ConnectionEvent)

Process ConnectionEvents generated by the associated Endpoint

Will execute protocol logic upon receipt of a connection event, in turn preparing signals (including application Events, EndpointEvents and outgoing datagrams) that should be extracted through the relevant methods.

-
Source

pub fn handle_timeout(&mut self, now: Instant)

Process timer expirations

+
Source

pub fn handle_timeout(&mut self, now: Instant)

Process timer expirations

Executes protocol logic, potentially preparing signals (including application Events, EndpointEvents and outgoing datagrams) that should be extracted through the relevant methods.

It is most efficient to call this immediately after the system clock reaches the latest Instant that was output by poll_timeout; however spurious extra calls will simply no-op and therefore are safe.

-
Source

pub fn close(&mut self, now: Instant, error_code: VarInt, reason: Bytes)

Close a connection immediately

+
Source

pub fn close(&mut self, now: Instant, error_code: VarInt, reason: Bytes)

Close a connection immediately

This does not ensure delivery of outstanding data. It is the application’s responsibility to call this only when all important communications have been completed, e.g. by calling SendStream::finish on outstanding streams and waiting for the corresponding StreamEvent::Finished event.

If Streams::send_streams returns 0, all outstanding stream data has been delivered. There may still be data from the peer that has not been received.

-
Source

pub fn datagrams(&mut self) -> Datagrams<'_>

Control datagrams

-
Source

pub fn stats(&mut self) -> ConnectionStats

Returns connection statistics

-
Source

pub fn path_stats(&mut self, path_id: PathId) -> Option<PathStats>

Returns path statistics

-
Source

pub fn ping(&mut self)

Ping the remote endpoint

+
Source

pub fn datagrams(&mut self) -> Datagrams<'_>

Control datagrams

+
Source

pub fn stats(&mut self) -> ConnectionStats

Returns connection statistics

+
Source

pub fn path_stats(&mut self, path_id: PathId) -> Option<PathStats>

Returns path statistics

+
Source

pub fn ping(&mut self)

Ping the remote endpoint

Causes an ACK-eliciting packet to be transmitted on the connection.

-
Source

pub fn ping_path(&mut self, path: PathId) -> Result<(), ClosedPath>

Ping the remote endpoint over a specific path

+
Source

pub fn ping_path(&mut self, path: PathId) -> Result<(), ClosedPath>

Ping the remote endpoint over a specific path

Causes an ACK-eliciting packet to be transmitted on the path.

-
Source

pub fn force_key_update(&mut self)

Update traffic keys spontaneously

+
Source

pub fn force_key_update(&mut self)

Update traffic keys spontaneously

This can be useful for testing key updates, as they otherwise only happen infrequently.

-
Source

pub fn crypto_session(&self) -> &dyn Session

Get a session reference

-
Source

pub fn is_handshaking(&self) -> bool

Whether the connection is in the process of being established

+
Source

pub fn crypto_session(&self) -> &dyn Session

Get a session reference

+
Source

pub fn is_handshaking(&self) -> bool

Whether the connection is in the process of being established

If this returns false, the connection may be either established or closed, signaled by the emission of a Connected or ConnectionLost event respectively. Note that locally-initiated closes via close() do not emit a ConnectionLost event.

For an established connection this essentially means the handshake is completed, but not necessarily yet confirmed.

-
Source

pub fn is_closed(&self) -> bool

Whether the connection is closed

+
Source

pub fn is_closed(&self) -> bool

Whether the connection is closed

Closed connections cannot transport any further data. A connection becomes closed when either peer application intentionally closes it, or when either transport layer detects an error such as a time-out or certificate validation failure.

@@ -167,24 +167,24 @@ error such as a time-out or certificate validation failure.

connection is closed by the peer or due to an error. When the local application closes the connection via close(), no ConnectionLost event is emitted; instead, pending operations fail with ConnectionError::LocallyClosed.

-
Source

pub fn is_drained(&self) -> bool

Whether there is no longer any need to keep the connection around

+
Source

pub fn is_drained(&self) -> bool

Whether there is no longer any need to keep the connection around

Closed connections become drained after a brief timeout to absorb any remaining in-flight packets from the peer. All drained connections have been closed.

-
Source

pub fn accepted_0rtt(&self) -> bool

For clients, if the peer accepted the 0-RTT data packets

+
Source

pub fn accepted_0rtt(&self) -> bool

For clients, if the peer accepted the 0-RTT data packets

The value is meaningless until after the handshake completes.

-
Source

pub fn has_0rtt(&self) -> bool

Whether 0-RTT is/was possible during the handshake

-
Source

pub fn has_pending_retransmits(&self) -> bool

Whether there are any pending retransmits

-
Source

pub fn side(&self) -> Side

Look up whether we’re the client or server of this Connection

-
Source

pub fn path_observed_address( +

Source

pub fn has_0rtt(&self) -> bool

Whether 0-RTT is/was possible during the handshake

+
Source

pub fn has_pending_retransmits(&self) -> bool

Whether there are any pending retransmits

+
Source

pub fn side(&self) -> Side

Look up whether we’re the client or server of this Connection

+
Source

pub fn path_observed_address( &self, path_id: PathId, ) -> Result<Option<SocketAddr>, ClosedPath>

Get the address observed by the remote over the given path

-
Source

pub fn rtt(&self, path_id: PathId) -> Option<Duration>

Current best estimate of this connection’s latency (round-trip-time)

-
Source

pub fn congestion_state(&self, path_id: PathId) -> Option<&dyn Controller>

Current state of this connection’s congestion controller, for debugging purposes

-
Source

pub fn set_max_concurrent_streams(&mut self, dir: Dir, count: VarInt)

Modify the number of remotely initiated streams that may be concurrently open

+
Source

pub fn rtt(&self, path_id: PathId) -> Option<Duration>

Current best estimate of this connection’s latency (round-trip-time)

+
Source

pub fn congestion_state(&self, path_id: PathId) -> Option<&dyn Controller>

Current state of this connection’s congestion controller, for debugging purposes

+
Source

pub fn set_max_concurrent_streams(&mut self, dir: Dir, count: VarInt)

Modify the number of remotely initiated streams that may be concurrently open

No streams may be opened by the peer unless fewer than count are already open. Large counts increase both minimum and worst-case memory consumption.

-
Source

pub fn set_max_concurrent_paths( +

Source

pub fn set_max_concurrent_paths( &mut self, now: Instant, count: NonZeroU32, @@ -195,16 +195,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, @@ -219,26 +219,26 @@ paths as non recoverable when necessary accelerates connectivity re-establishmen allow it altogether.

The optional hint allows callers to indicate when paths are non-recoverable and should be migrated to new a PathId.

-

Source

pub fn current_mtu(&self) -> u16

Storage size required for the largest packet that can be transmitted on all currently +

Source

pub fn current_mtu(&self) -> u16

Storage size required for the largest packet that can be transmitted on all currently available paths

Buffers passed to Connection::poll_transmit should be at least this large.

When multipath is enabled, this value is the minimum MTU across all available paths.

-
Source

pub fn add_nat_traversal_address( +

Source

pub fn add_nat_traversal_address( &mut self, address: SocketAddr, ) -> Result<(), Error>

Add addresses the local endpoint considers are reachable for nat traversal.

-
Source

pub fn remove_nat_traversal_address( +

Source

pub fn remove_nat_traversal_address( &mut self, address: SocketAddr, ) -> Result<(), Error>

Removes an address the endpoing no longer considers reachable for nat traversal

Addresses not present in the set will be silently ignored.

-
Source

pub fn get_local_nat_traversal_addresses( +

Source

pub fn get_local_nat_traversal_addresses( &self, ) -> Result<Vec<SocketAddr>, Error>

Get the current local nat traversal addresses

-
Source

pub fn get_remote_nat_traversal_addresses( +

Source

pub fn get_remote_nat_traversal_addresses( &self, ) -> Result<Vec<SocketAddr>, Error>

Get the currently advertised nat traversal addresses by the server

-
Source

pub fn initiate_nat_traversal_round( +

Source

pub fn initiate_nat_traversal_round( &mut self, now: Instant, ) -> Result<Vec<SocketAddr>, Error>

Initiates a new nat traversal round

@@ -249,7 +249,7 @@ round is initiated, the previous one is cancelled.

4-tuple.

Returns the server addresses that are now being probed. If addresses fail due to spurious errors, these might succeed later and not be returned in this set.

-

Trait Implementations§

Source§

impl Debug for Connection

Source§

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

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Debug for Connection

Source§

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

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

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

Source§

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

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

diff --git a/pr/617/docs/noq_proto/struct.EndpointConfig.html b/pr/617/docs/noq_proto/struct.EndpointConfig.html index 04aaa93c5..aa3d4102c 100644 --- a/pr/617/docs/noq_proto/struct.EndpointConfig.html +++ b/pr/617/docs/noq_proto/struct.EndpointConfig.html @@ -1,7 +1,7 @@ -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( +

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

Supply a custom connection ID generator factory

@@ -10,9 +10,9 @@ generator which will be used to generate the CIDs used for incoming packets on a 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/617/docs/noq_proto/struct.FourTuple.html b/pr/617/docs/noq_proto/struct.FourTuple.html index 2c9867791..d6ce62208 100644 --- a/pr/617/docs/noq_proto/struct.FourTuple.html +++ b/pr/617/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/617/docs/noq_proto/struct.IdleTimeout.html b/pr/617/docs/noq_proto/struct.IdleTimeout.html index ce0e6f05a..eb7412f9c 100644 --- a/pr/617/docs/noq_proto/struct.IdleTimeout.html +++ b/pr/617/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/617/docs/noq_proto/struct.MtuDiscoveryConfig.html b/pr/617/docs/noq_proto/struct.MtuDiscoveryConfig.html index 653aee9a0..dc874a022 100644 --- a/pr/617/docs/noq_proto/struct.MtuDiscoveryConfig.html +++ b/pr/617/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/617/docs/noq_proto/struct.MultipathNotNegotiated.html b/pr/617/docs/noq_proto/struct.MultipathNotNegotiated.html index dff3248d2..586d2dc1d 100644 --- a/pr/617/docs/noq_proto/struct.MultipathNotNegotiated.html +++ b/pr/617/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/617/docs/noq_proto/struct.RttEstimator.html b/pr/617/docs/noq_proto/struct.RttEstimator.html index d6c03ab12..50807c4f0 100644 --- a/pr/617/docs/noq_proto/struct.RttEstimator.html +++ b/pr/617/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/617/docs/noq_proto/struct.ServerConfig.html b/pr/617/docs/noq_proto/struct.ServerConfig.html index 25d72ffb1..8ce6583a1 100644 --- a/pr/617/docs/noq_proto/struct.ServerConfig.html +++ b/pr/617/docs/noq_proto/struct.ServerConfig.html @@ -1,4 +1,4 @@ -ServerConfig in noq_proto - Rust

    ServerConfig

    Struct ServerConfig 

    Source
    pub struct ServerConfig {
    +ServerConfig in noq_proto - Rust

    ServerConfig

    Struct ServerConfig 

    Source
    pub struct ServerConfig {
         pub transport: Arc<TransportConfig>,
         pub crypto: Arc<dyn ServerConfig>,
         pub validation_token: ValidationTokenConfig,
    @@ -9,32 +9,32 @@
     
    §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>, ) -> Self

    Create a default config with a particular handshake token key

    -
    Source

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

    Set a custom TransportConfig

    -
    Source

    pub fn validation_token_config( +

    Source

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

    Set a custom TransportConfig

    +
    Source

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

    Set a custom ValidationTokenConfig

    -
    Source

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

    Private key used to authenticate data included in handshake tokens

    -
    Source

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

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

    +
    Source

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

    Private key used to authenticate data included in handshake tokens

    +
    Source

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

    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 Self

    Whether to allow clients to migrate to new addresses

    +
    Source

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

    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 Self

    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 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/617/docs/noq_proto/struct.StdSystemTime.html b/pr/617/docs/noq_proto/struct.StdSystemTime.html index 35fcfd6ce..75901cff4 100644 --- a/pr/617/docs/noq_proto/struct.StdSystemTime.html +++ b/pr/617/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/617/docs/noq_proto/struct.StreamId.html b/pr/617/docs/noq_proto/struct.StreamId.html index f29c468b9..e8c6a9239 100644 --- a/pr/617/docs/noq_proto/struct.StreamId.html +++ b/pr/617/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/617/docs/noq_proto/struct.Transmit.html b/pr/617/docs/noq_proto/struct.Transmit.html index 34a9b53fa..0d8f9acb1 100644 --- a/pr/617/docs/noq_proto/struct.Transmit.html +++ b/pr/617/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/617/docs/noq_proto/struct.TransportConfig.html b/pr/617/docs/noq_proto/struct.TransportConfig.html index 58407d73d..1605c75cf 100644 --- a/pr/617/docs/noq_proto/struct.TransportConfig.html +++ b/pr/617/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,19 +92,19 @@ frequency QUIC extension.

    Defaults to None, which disables controlling the peer’s acknowledgement frequency. Even if set to None, the local side still supports the acknowledgement frequency QUIC extension and may use it in other ways.

    -
    Source

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

    Configures an outbound rate limit (in bytes per second) for each connection.

    +
    Source

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

    Configures an outbound rate limit (in bytes per second) for each connection.

    Defaults to None, which disables rate limiting.

    -
    Source

    pub fn persistent_congestion_threshold(&mut self, value: u32) -> &mut Self

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

    -
    Source

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

    Period of inactivity before sending a keep-alive packet

    +
    Source

    pub fn persistent_congestion_threshold(&mut self, value: u32) -> &mut Self

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

    +
    Source

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

    Period of inactivity before sending a keep-alive packet

    Keep-alive packets prevent an inactive but otherwise healthy connection from timing out.

    None to disable, which is the default. Only one side of any given connection needs keep-alive enabled for the connection to be preserved. Must be set lower than the idle_timeout of both peers to be effective.

    -
    Source

    pub fn crypto_buffer_size(&mut self, value: usize) -> &mut Self

    Maximum quantity of out-of-order crypto layer data to buffer

    -
    Source

    pub fn allow_spin(&mut self, value: bool) -> &mut Self

    Whether the implementation is permitted to set the spin bit on this connection

    +
    Source

    pub fn crypto_buffer_size(&mut self, value: usize) -> &mut Self

    Maximum quantity of out-of-order crypto layer data to buffer

    +
    Source

    pub fn allow_spin(&mut self, value: bool) -> &mut Self

    Whether the implementation is permitted to set the spin bit on this connection

    This allows passive observers to easily judge the round trip time of a connection, which can be useful for network administration but sacrifices a small amount of privacy.

    -
    Source

    pub fn datagram_receive_buffer_size( +

    Source

    pub fn datagram_receive_buffer_size( &mut self, value: Option<usize>, ) -> &mut Self

    Maximum number of incoming application datagram bytes to buffer, or None to disable @@ -112,12 +112,12 @@ incoming datagrams

    The peer is forbidden to send single datagrams larger than this size. If the aggregate size of all datagrams that have been received from the peer but not consumed by the application exceeds this value, old datagrams are dropped until it is no longer exceeded.

    -
    Source

    pub fn datagram_send_buffer_size(&mut self, value: usize) -> &mut Self

    Maximum number of outgoing application datagram bytes to buffer

    +
    Source

    pub fn datagram_send_buffer_size(&mut self, value: usize) -> &mut Self

    Maximum number of outgoing application datagram bytes to buffer

    While datagrams are sent ASAP, it is possible for an application to generate data faster than the link, or even the underlying hardware, can transmit them. This limits the amount of memory that may be consumed in that case. When the send buffer is full and a new datagram is sent, older datagrams are dropped until sufficient space is available.

    -
    Source

    pub fn congestion_controller_factory( +

    Source

    pub fn congestion_controller_factory( &mut self, factory: Arc<dyn ControllerFactory + Send + Sync + 'static>, ) -> &mut Self

    How to construct new congestion::Controllers

    @@ -125,7 +125,7 @@ sent, older datagrams are dropped until sufficient space is available.

    e.g. a congestion::NewRenoConfig.

    §Example
    let mut config = TransportConfig::default();
    -config.congestion_controller_factory(Arc::new(congestion::NewRenoConfig::default()));
    Source

    pub fn enable_segmentation_offload(&mut self, enabled: bool) -> &mut Self

    Whether to use “Generic Segmentation Offload” to accelerate transmits, when supported by the +config.congestion_controller_factory(Arc::new(congestion::NewRenoConfig::default()));

    Source

    pub fn enable_segmentation_offload(&mut self, enabled: bool) -> &mut Self

    Whether to use “Generic Segmentation Offload” to accelerate transmits, when supported by the environment

    Defaults to true.

    GSO dramatically reduces CPU consumption when sending large numbers of packets with the same @@ -133,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.

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

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

    @@ -169,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 @@ -178,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/617/docs/noq_proto/struct.TransportError.html b/pr/617/docs/noq_proto/struct.TransportError.html index 93b3c1836..aa1d581d9 100644 --- a/pr/617/docs/noq_proto/struct.TransportError.html +++ b/pr/617/docs/noq_proto/struct.TransportError.html @@ -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/617/docs/noq_proto/struct.ValidationTokenConfig.html b/pr/617/docs/noq_proto/struct.ValidationTokenConfig.html index c683e4df1..fa5a375d5 100644 --- a/pr/617/docs/noq_proto/struct.ValidationTokenConfig.html +++ b/pr/617/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/617/docs/noq_proto/struct.VarInt.html b/pr/617/docs/noq_proto/struct.VarInt.html index 8184c92e8..31657b5da 100644 --- a/pr/617/docs/noq_proto/struct.VarInt.html +++ b/pr/617/docs/noq_proto/struct.VarInt.html @@ -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/617/docs/noq_proto/struct.VarIntBoundsExceeded.html b/pr/617/docs/noq_proto/struct.VarIntBoundsExceeded.html index 1bf7b895c..123347fa5 100644 --- a/pr/617/docs/noq_proto/struct.VarIntBoundsExceeded.html +++ b/pr/617/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, +

    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/617/docs/noq_proto/trait.NetworkChangeHint.html b/pr/617/docs/noq_proto/trait.NetworkChangeHint.html index a24e61e42..3cbdfde6f 100644 --- a/pr/617/docs/noq_proto/trait.NetworkChangeHint.html +++ b/pr/617/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/617/docs/noq_proto/trait.TimeSource.html b/pr/617/docs/noq_proto/trait.TimeSource.html index b8ceeabbc..5c0e68db2 100644 --- a/pr/617/docs/noq_proto/trait.TimeSource.html +++ b/pr/617/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/617/docs/perf/server/index.html b/pr/617/docs/perf/server/index.html index de30e6257..4aa8e181b 100644 --- a/pr/617/docs/perf/server/index.html +++ b/pr/617/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/617/docs/search.index/001728ca1382.js b/pr/617/docs/search.index/001728ca1382.js new file mode 100644 index 000000000..f59106c92 --- /dev/null +++ b/pr/617/docs/search.index/001728ca1382.js @@ -0,0 +1 @@ +rn_("AQEAOjAAAAEAAAAAAAgAEAAAABkJhxAyETMRNBEKFAsUlhSbFAEBAqBAAAAUfqAwAAAROGVo9REJAAAuADADAQCABQECAPTnEQAAAQABAAEAE4ECsCAR6wABYXPzgAJubwECAPTCEAAAAQABAAEAEwACoNAAABSDY3PyMhAAAAEA84ADZWlv") \ No newline at end of file diff --git a/pr/617/docs/search.index/001d41e9dc67.js b/pr/617/docs/search.index/001d41e9dc67.js new file mode 100644 index 000000000..edb1e1219 --- /dev/null +++ b/pr/617/docs/search.index/001d41e9dc67.js @@ -0,0 +1 @@ +rn_("AQ8A9DUUAAABAE8AAQABBAD0kxAAAAEAtwABAAECADowAAABAAAAAAAJABAAAADuEe8RCxMME/kT+hNQFFEUyxTMFPMAAW/yggkAAAEAU4ADsQAUmQABaG9ww4CEoBAAABNrsIATrgABBQAG") \ No newline at end of file diff --git a/pr/617/docs/search.index/0098faffe0aa.js b/pr/617/docs/search.index/0098faffe0aa.js deleted file mode 100644 index c0da0d3d8..000000000 --- a/pr/617/docs/search.index/0098faffe0aa.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIAOzAAAAEAABYACQBpEAUAexEBAA0SAgDiEgEAExMEABAUAQA6FAAAaRQAAIgUAAAjgAOh8AAAFNmw8BTMAAFjZmcjgAKwUBPLAAFtbgEFAPMQFAAAAQB3APMEAWMBmxEAAAEBAbBwFG0AAXA7MAAAAQAACQACAOMHBwDhEAEAAQIAOzAAAAEAAAsAAgDjEwUAoRQFAPMBAW07MAAAAQAADQAEAOAMAAB9EQAA+xIFAF8UBQATAAKgMAAADORlc/IREgAAAQABggKwcBRJAAGgQAAAEShpc/cBAICGsJAUKgABkEkI") \ No newline at end of file diff --git a/pr/617/docs/search.index/00c273ace618.js b/pr/617/docs/search.index/00c273ace618.js new file mode 100644 index 000000000..4d55b5bbf --- /dev/null +++ b/pr/617/docs/search.index/00c273ace618.js @@ -0,0 +1 @@ +rn_("AQQA894RAAABAAEAAYICsEAS2AABsEAS2gABNDYBCwDz1hMAAGwAAQABBgD0xBQAAAEACgABAPMCAXIBIBQAAPMCAWU7MAAAAQAABQACAI8QAwBAEQEAE4ACoFAAABMNZHTzAIQRAQH3/xAAAAEAbgAdAAEAAQA9AQ==") \ No newline at end of file diff --git a/pr/617/docs/search.index/00ea8f3dbdef.js b/pr/617/docs/search.index/00ea8f3dbdef.js new file mode 100644 index 000000000..4a8c500b5 --- /dev/null +++ b/pr/617/docs/search.index/00ea8f3dbdef.js @@ -0,0 +1 @@ +rn_("AQIA9bsRAAABAAEAAQABAAGAArBgEyYAAaAgAAAREG95AQABoMAAABRLafR9EwAACAAcABsAAQMA9KITAAABAAEAAQABBgD0txQAAAEAAQABAAECAPQOEQAAAQCaAAEAAQIBoEAAABNUcjswAAABAAAHAAIADBEBAKMRBQDzgAJhb2cHAACHsIATUwBasKATygABDDAHOzAAAAEAAA4ABACWBAUALQwAANYRAwC9EgMApwAAgIWg8AAAE92wUBBfAAGgkAAAEZqUgQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/00fb5c4f9c23.js b/pr/617/docs/search.index/00fb5c4f9c23.js new file mode 100644 index 000000000..e3090590f --- /dev/null +++ b/pr/617/docs/search.index/00fb5c4f9c23.js @@ -0,0 +1 @@ +rn_("AQUAOzAAAAEAAAkBAgD0DAEArg4HAQEBAaAAAAATW3P1xwQAABsL8gMBAAQAAQECoGAAABPcoOAAABQ5ZXQ7MAAAAQAACAABAM4ECAABgAOwIBKnAAGwsBLhAAGwsBLjAAFiY3PzgARjbHR2") \ No newline at end of file diff --git a/pr/617/docs/search.index/0107dc62ab6c.js b/pr/617/docs/search.index/0107dc62ab6c.js deleted file mode 100644 index a3c982573..000000000 --- a/pr/617/docs/search.index/0107dc62ab6c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEBoGAAABSoc/LsEQAAAQABCAD1UxQAAAEAAQABAAEAowCEsCAJgAABoIAAABMBMEAIOzAAAAEAAAUAAgDyAQAAqhAEAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/017426982d62.js b/pr/617/docs/search.index/017426982d62.js new file mode 100644 index 000000000..597cabcef --- /dev/null +++ b/pr/617/docs/search.index/017426982d62.js @@ -0,0 +1 @@ +rn_("AQABsPAUwQABazswAAABAAAIAAMAKBQAAC0UBgBYFAAAEwACsQAUqgABZG7y6xIAAAEAE4ACsBAMZAABZW8BBQA7MAAAAQAADQABAKwRDQABAgDzFxAAAAEAAQABAgD0ExAAAAEAAQABAAEDADswAAABAAAIAAEAyRAIAAGAAqBAAAARILDAFGwAAWVzAQEAOzAAAAEAABMAAQA0EBMAI4ACsJAT1AABaW03NgAAiqAQAAAMN6BQAAAUN7BwFBUAAaAgAAANAZxVETswAAABAAAGAAMAYAcAAPkQAwDLEgEA84ACbHQ=") \ No newline at end of file diff --git a/pr/617/docs/search.index/020540c03066.js b/pr/617/docs/search.index/020540c03066.js new file mode 100644 index 000000000..e6bedd7d0 --- /dev/null +++ b/pr/617/docs/search.index/020540c03066.js @@ -0,0 +1 @@ +rn_("AQgA844UAAABADsAI4ACsAAQ7gABbW4BgAOgQAAAE2uwIBQpAAGgIAAAD+doaXkBCQGggAAAFMdmAaYTAAABAwGwABRBAFBz8r0QAAArA0OAA7AgD+MBJ7BQEs0AAWFvcgECAPXrEAAAAQABAF4CAQDzAANpdHU6MAAAAQAAAAAAEQAQAAAAiQc8CQQNSBDGEPERABM5EzoT7BPtE/MTHRQeFD8UQBSSFJMUAQYA9CITAAABAJgBAQATAgKwYBMkAAFmbfLyDAAAAQAjggKwQBNRAAFlbAGAAqAwAAAUcbCwE8IAAWFsAQYA8/EPAADhAAEAAQUA9BMUAAABAFAAAQDzAgFj8hcTAAABAAECAaAwAAAQh2EBZwwAAAGAArBQETsAAbAgD78AAWNu84ACbnQBBQA7MAAAAQAADgACAPgMAAAaEA0AAQIAOzAAAAEAAAcAAQBjCQcA5wUAAAmgMAAADGagsAAAFD+ggAAAEbqw0BROAAEwYWZpa21ydXY7MAAAAQAAbQIFAAACAABtA+4AgAcAABML7gAYDo0AAQEA8woQAAABAAEAE4ACsMAUyAABdHUBAwDz0RIAAAEAAQABAQD1TxEAAAEAAQABAAEAAQEBsDAP+gDwdPZyBwAAAQCOAPwHqwD+AWOAA7CQFBUAAWFtdAEDAaBAAAAUdWXylxQAAAEAE4ACsGAUywABZXIBAwGgAAAAE/NzAQkRAAABAgA6MAAAAQAAAAAACgAQAAAAYQliCTEQfxOAE7ATsRNdFLUUzRTOFIMABKCAAAASq7BwEX4AAbAgER8A8GZucnYBAAgAAAGBAqAAAAAJPKAwAAAP53N0xwEAgMawABRMAAGwkBKpAAGgEAAAB1gAEogoAQIAOzAAAAEAAA0ABgBpEAUAexEBAOESAQAPFAEAaBQAAIcUAAATggKw8BTLAAFmZwGAArCAFJ4AAaBgAAAUxmRzE4ACsMAT2gABaW8BCAD1UhQAAAEAAQABAAEA8wIBZvWqEAAAAQABAAEAAQAjgAOgIAAAD+GwoBScAAFkcnT3+wcAj6AwAAAP15fXDzswAAABAABVAR4AiAQBALUEAgCABQIAqgUCAM8FAQDHBgQASQcAAGEHAAD8BwEAEgkCAEAJAQA6DAMAaAwBAJAMAwD0DAEArg4HAQAQAQB/EAIAxxABANoQAgAIEQEAmxEAAM4RAwD4EQEAsxIAAPMSAAAqEwEAkhMBALQTBwCsFAUA") \ No newline at end of file diff --git a/pr/617/docs/search.index/0219603ee875.js b/pr/617/docs/search.index/0219603ee875.js new file mode 100644 index 000000000..87c1a94d9 --- /dev/null +++ b/pr/617/docs/search.index/0219603ee875.js @@ -0,0 +1 @@ +rn_("AYMCsQAUzQABsNAUvQABa20BCwDzDxQAAAEAdwAzAYSgYAAAEqahAAAAFIkEgAM7MAAAAQAANgACAFkHAACsBzUA") \ No newline at end of file diff --git a/pr/617/docs/search.index/041663251e8f.js b/pr/617/docs/search.index/041663251e8f.js deleted file mode 100644 index 6626a8e76..000000000 --- a/pr/617/docs/search.index/041663251e8f.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsAAT9QABsAAT9wABNDYBBgD0xRQAAAEACgABAPMCAnJ28wYQAAABABoE8wIBZTswAAABAAAFAAIAjxADAEARAQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/04a9e87be06a.js b/pr/617/docs/search.index/04a9e87be06a.js deleted file mode 100644 index 4cbed15cf..000000000 --- a/pr/617/docs/search.index/04a9e87be06a.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9jARAAABAJQDAQAKAAEAAQAAOzAAAAEAAAYAAgCdEwAABRQFACOIArBAFH0AAWVzAQ0BsEAUdwABZfU7FAAAAQABAAEAAQABBQA7MAAAAQAACAADAA0SAgATEwQAOhQAANcBAACFbkh3HGlvFIQC89sMAAABAAEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/0505a70598b7.js b/pr/617/docs/search.index/0505a70598b7.js new file mode 100644 index 000000000..b56e41154 --- /dev/null +++ b/pr/617/docs/search.index/0505a70598b7.js @@ -0,0 +1 @@ +rn_("AQIBsEATyAABZfLNBgAAAQABBADzfxAAAAEAAQBDAAOwQBMsAAGxABQPAAFlZnA7MAAAAQAAJAAGABkCAADcBgwA6gYMALYPBgAgEQAAbBMBAGOAA7AwDCMABWFlaQ==") \ No newline at end of file diff --git a/pr/617/docs/search.index/051240e58a38.js b/pr/617/docs/search.index/051240e58a38.js new file mode 100644 index 000000000..d78ada258 --- /dev/null +++ b/pr/617/docs/search.index/051240e58a38.js @@ -0,0 +1 @@ +rn_("AQYAOzAAAAEAAAUAAQBuEwUAg4YEsFATNwABsHATngABoNAAABR+Ym5vdhOAArAQEwkAAXByAQABoDAAABH/dAG9EwAA8wIBc/K4EAAACQMTgAKgYAAAEKlldQECAPYbDQAAAQA+AwEALgEBAAGAArAAEvQAAYAOqzTwAWVzAQEA82EHAAAxDAEAY4ADsIAUEQABYm5084ADYW9yARQAOzAAAAEAAAUAAQCsFAUAAQAAOzAAAAEAAAYAAgCcEwAABBQFACOIArBAFHwAAWVzAQQBsDATbAABY/OWEQAAAQABAAEFADswAAABAAAIAAMADBICABITBAA5FAAA8wIDY2Vw89sMAAABAAEAY4ADoGAAABAEZW91AQABsGAUHQABZQFXFAAA8wIBc/YzCQAAAQABACAIPwAjAQEIAPPREgAAAQABAEOAA6AQAAAToKDgAAAUKGRvcwECAaBQAAARlXIBNhEAAAGAAqBAAAAQtaBwAAAUaWFlAQAAOzAAAAEAAAsAAgD/BgUA7RIFAPOAAnR2IwACsBAH/AABbnL2KAkAAFcAFAcBALcAAQDzAIcwkQw6MAAAAQAAAAAACAAQAAAAygTLBBMRFBEHEwgTjhOPE4kU") \ No newline at end of file diff --git a/pr/617/docs/search.index/05a01e5a13f7.js b/pr/617/docs/search.index/05a01e5a13f7.js deleted file mode 100644 index 0fd6a7d5f..000000000 --- a/pr/617/docs/search.index/05a01e5a13f7.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUBsEAS1QABZTswAAABAAAFAAIA+RIBAFoUAwABggKgkAAAFEegkAAAFEgyMwEAA6AgAAASsaAgAAAJCLCAFMAAAWltcDswAAABAAAOAAYAnwQAANcEBQB4CQAAxQ8DABoUAAAsFAEA8wABcwFCEQAAAQUA9KMTAAABAAEAAQABAAD1iwwAAAEAAQABAAEA84ACZ2tzgISwsBP+AAERSAABBQGgQAAAEgpl8xYNAAATBAEAE4ACoDAAAAxEZXIBgAOwUBRAAAGgEAAADAygcAAAE5JnbnJHAQAAhaBgAAAUG6BQAAAUc6CgAAAUDRUBBDswAAABAAARAAQAAQIBAMkQCADGEQMA0hICAAGFArBwFCcAAbAwE3oAAWVzEwADsEAP6AABoGAAABGcYmVzOzAAAAEAABIABACvBQEA0QYKAGEQAQC5EAMAAQMA9H0QAAABAAQAAQABCgGgQAAAFHZl8pgUAAABABOAArBgEswAAWNp84ACZHLzAAJlbzswAAABAAAJAAIApgcFAOsHAwABAgGwMBFoAAFuOzAAAAEAAAkAAQACDAkAAQAA9L4QAAABAAEAAQABAQA7MAAAAQAA7wACAM4FAAAPCO4AYwADsBATOAABZWlzOjAAAAEAAAAAAA4AEAAAABsJVglXCVgJWQkrDCwMvQ++D+cP+g/qEBARnxOgE+OAhLAgB2IAARFABAGBhLAwDBgAAaBAAAAM4aCAAAARb7BADOIAAQAAHQEFAPXvEwAAAQABAHoAAQATgAKgAAAABwVhZwEDAPSqEgAAAQBrAQEA84ACaW8BAQA7MAAAAQAA7wACAIAHAAATC+4AAQgBsEAUdwABZfU7FAAAAQABAAEAAQDzgQJndAEBAaBwAAASEGk7MAAAAQAAAQECAH8HAAASCgABAQUA89oQAAABAAEA84ACYW8TgAKgQAAADENydQGAAqAAAAAHZKAQAAAJGmR4EwACsCANDQABZXLyKxAAAAEAE4ACsLAUwgABcngBCgD0qhMAAAEAAQABABOAArAQESQCO2FlAQQA9LkQAAABAAEAAQABBAA6MAAAAQAAAAAABwAQAAAAShNLE1kTWhPfE+AT8hPzEwEDAPPmEgAAAQCqANOAhLBAERYAAQFIAce/AwCPoIAAABLosGAM/AABsEAMbQABsTAUlQABP6kfOzAAAAEAAEsAIgB7AAAAgAAAAMAEAQDMBAAAhgUBAEoHAQBaBwAAXgcBAE8JAABjCQcAegkBABcMAAAuDAEANQwBAEkMBwBtDAEAiQwBANgMAQD2DwEAZhABAOYQAQDzEAEACxEAAC0RAgA9EQEATxEEAHMRAADAEQMABRIBAMISAAAjEwEAJxMBAFUTAAC8FAEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/05b03b52ea2f.js b/pr/617/docs/search.index/05b03b52ea2f.js deleted file mode 100644 index 1619ea49c..000000000 --- a/pr/617/docs/search.index/05b03b52ea2f.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQgA9AsUAAABAIsABQATAAOwkBR0AAGwUBNQAWdnbHA7MAAAAQAAEgAJAHkQAgD+EQEAPxMBAGATAQCXEwEA/BMBADYUAQBFFAEAhhQBAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/05e227d0495f.js b/pr/617/docs/search.index/05e227d0495f.js new file mode 100644 index 000000000..0f93eef73 --- /dev/null +++ b/pr/617/docs/search.index/05e227d0495f.js @@ -0,0 +1 @@ +rn_("AYACoGAAABQaoKAAABQMYWkBCwD0qRMAAAEAAQABAAEEAPS5EAAAAQABAAEAI4ACsEARFgABbHLzAANjZHMBCxEAAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/0647d1d32d25.js b/pr/617/docs/search.index/0647d1d32d25.js deleted file mode 100644 index 56be1ac2b..000000000 --- a/pr/617/docs/search.index/0647d1d32d25.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQcA9BQSAAABAAEAAQAjAAKgcAAAEhNhcgGEBwAAAQAA9jARAAABAJQDAQAKAAEAAQAAOzAAAAEAAAYAAgCdEwAABRQFACOIArBAFH0AAWVzAQ0BsEAUdwABZfU7FAAAAQABAAEAAQABBQA7MAAAAQAACAADAA0SAgATEwQAOhQAANcDAACGbkh3HGlvFIQG9cIEAAABABgIAQABAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/066cb4cb59fe.js b/pr/617/docs/search.index/066cb4cb59fe.js deleted file mode 100644 index 89d6414cf..000000000 --- a/pr/617/docs/search.index/066cb4cb59fe.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9DYTAAABAJAAAQABAwD0ahMAAAEAmAABAPMFAmJ1OjAAAAEAAAAAAAcAEAAAAJUQlhBNEU4RARICEkETQhMBAgD0hREAAAEAfgEBAAEFAPOLEQAAAQABAAEEAPTdEgAAAQCmAAEA84CEEQkA") \ No newline at end of file diff --git a/pr/617/docs/search.index/07060b7a880b.js b/pr/617/docs/search.index/07060b7a880b.js new file mode 100644 index 000000000..e5ed71e6b --- /dev/null +++ b/pr/617/docs/search.index/07060b7a880b.js @@ -0,0 +1 @@ +rn_("AQAEoAAAABCpsCARXAABoFAAABIIoEAAABRLZGxzdjswAAABAAATAAYAsgUAAEMMAAD4DAAAGhANAIARAABGFAEAAQIA9OcRAAABAAEAAQAThAKwIBHrAAFhc/OAAmFl") \ No newline at end of file diff --git a/pr/617/docs/search.index/071e1fe55efd.js b/pr/617/docs/search.index/071e1fe55efd.js new file mode 100644 index 000000000..9e1bdd5a0 --- /dev/null +++ b/pr/617/docs/search.index/071e1fe55efd.js @@ -0,0 +1 @@ +rn_("AQIA9RATAAABAPAAgAABABOAArAwE0IBSWlwAQYA874TAAABAAEA8wICYnMBUBAAAPMBAWYBcAcAAAECAPRpEwAAAQCYAAEAM4ADsEATNwABaXV5") \ No newline at end of file diff --git a/pr/617/docs/search.index/0817d1ec6cb9.js b/pr/617/docs/search.index/0817d1ec6cb9.js deleted file mode 100644 index 353b90f7b..000000000 --- a/pr/617/docs/search.index/0817d1ec6cb9.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYkCsCAUggABoDAAABSMaXATAAOgIAAAEz6ggAAAE1FlaXX1IgwAADkAAQABAAEAAYYCsJAUqQABoAAAABLLYmQBgAKwMBMyAAGgMAAAEQhydtcAAAKFoDAAABB8oDAAABOIEAETOjAAAAEAAAAAAAkAEAAAAAsJDAkNCT0JPgmXEJ4QWBFZEQ4TAQMBsDATTAABc/PaEAAAAQABAAEAADswAAABAAAKAAMAghECAM0TAwC4FAMAlwEAAIWgIAAABbWwIAgNAAFQGAH1iwwAAAEAAQABAAEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/6ed509a41675.js b/pr/617/docs/search.index/08621659568f.js similarity index 53% rename from pr/617/docs/search.index/6ed509a41675.js rename to pr/617/docs/search.index/08621659568f.js index f369ce61f..e1cd0b466 100644 --- a/pr/617/docs/search.index/6ed509a41675.js +++ b/pr/617/docs/search.index/08621659568f.js @@ -1 +1 @@ -rn_("AYACoIAAABLosHAUQAABYXMBAAGggAAAFMhm8goJAACdCgEHAPV1EwAAAQABAAEAAQAjgAKgUAAAEXNhb/MAAmZzOzAAAAEAAAgABABXBwAAigcFABUNAACrEQAA84ACZHk=") \ No newline at end of file +rn_("AYACoIAAABLnsHAUPwABYXMBAAGggAAAFMdm8goJAACcCgEHAPV0EwAAAQABAAEAAQAjgAKgUAAAEXNhb/MAAmZzOzAAAAEAAAgABABXBwAAigcFABUNAACrEQAA84ACZHk=") \ No newline at end of file diff --git a/pr/617/docs/search.index/09846932e7be.js b/pr/617/docs/search.index/09846932e7be.js deleted file mode 100644 index cb7f0cdc3..000000000 --- a/pr/617/docs/search.index/09846932e7be.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYQCsAARjgABsAARkAABNDbzAAFh8oYMAAABACOAArCQEZQBJGx0AYACsHATqAABsIAT4QABY3MBAAA6MAAAAQAAAAAACQAQAAAA7xHwEQwTDRP6E/sTURRSFMwUzRTzgAJkZScBAICFoNAAABQhsBAEwgABoDAAABCIAggL") \ No newline at end of file diff --git a/pr/617/docs/search.index/09cc9a7ef753.js b/pr/617/docs/search.index/09cc9a7ef753.js deleted file mode 100644 index 71c5b71f1..000000000 --- a/pr/617/docs/search.index/09cc9a7ef753.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUAOzAAAAEAAAYAAQBvEAYAAQMAOzAAAAEAAAUAAQDnDAUAYwADoJAAABOGaHBzOjAAAAEAAAAAAA8AEAAAAK0FrgXMBiIJagx3DBYRFxHIEskSHxMgEyETIhNOE08T") \ No newline at end of file diff --git a/pr/617/docs/search.index/09efdd050b38.js b/pr/617/docs/search.index/09efdd050b38.js new file mode 100644 index 000000000..c2593c520 --- /dev/null +++ b/pr/617/docs/search.index/09efdd050b38.js @@ -0,0 +1 @@ +rn_("AQUA9KITAAABAAEAAQABAAD1iwwAAAEAAQABAAEA84ACZ2sTgAKwsBP9AAFhbwGAAqAgAAAMDKBAAAAMRGV0AYUCsHAUJgABsDATeQABZXMTAAOwQA/oAAGgYAAAEZxiZXM7MAAAAQAAEgAEAK8FAQDRBgoAYRABALkQAwABAwD0fRAAAAEABAABAAEKAaBAAAAUdWXylxQAAAEAE4ACsGASywABY2nzgAJkcvMAAmVvOzAAAAEAAAkAAgCmBwUA6wcDAAECAbAwEWgAAW47MAAAAQAACQABAAIMCQDzgAJvdAEDADswAAABAADvAAIAgAcAABML7gABAgGgcAAAEg9pOzAAAAEAAAEBAgB/BwAAEgoAAROAAqBAAAAMQ3J1AQQA9FgTAAABAJgAAQABAwDz5RIAAAEAqgDzgAJhb9cHAACIsEANDQABsTAUlAABNIEWOzAAAAEAAB4ADQDABAEAhgUBABcMAABJDAcAbQwBAPYPAQDmEAEAcxEAAMARAwAEEgEAwRIAACITAQC7FAEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/0a0be613805b.js b/pr/617/docs/search.index/0a0be613805b.js new file mode 100644 index 000000000..6043247e0 --- /dev/null +++ b/pr/617/docs/search.index/0a0be613805b.js @@ -0,0 +1 @@ +rn_("AQIBoDAAABCHYQFnDAAAAYECoCAAABEKsFASzQABYW8BAwGgAAAAE/NzAQkRAAAjgAKgAAAACABydmMAA7BQEXsAAWNlczswAAABAAAIAAQAEgkCAEAJAQB/EAIAsxIAAAGAArDQFL8AAaCQAAATvGJoJwEAgIWwUBE7AAGw8BPUAAGwIA+/AAFEIQTzgAJudA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/0a2dff3b40f0.js b/pr/617/docs/search.index/0a2dff3b40f0.js deleted file mode 100644 index 4d70f5a18..000000000 --- a/pr/617/docs/search.index/0a2dff3b40f0.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9L4QAAABAAEAAQDzAgFz9VYJAAABAAEAAQCOBgEGAPQUEgAAAQABAAEAAQQBoEAAABQ1ZfKrFAAAAQABAQA7MAAAAQAABgABANIUBgDzBgFl9IAUAAABADMAAQAzBgOwYBOWAIphcnXzixAAAHsCAQDzgAJhZQEDAPTGEQAAAQABAAEAAQIA9NwPAAABAE0EAQABggKgEAAAE2ywQBMMAAFhcwGAA7AAB4IAAaAAAAAMKbBwEJwAAW5yc+cHAACHoHAAABITkQVBOjAAAAEAAAAAAAwAEAAAAOcB6AHpAeoBhAfvB/AHXwlmDBcQGBAZECwR") \ No newline at end of file diff --git a/pr/617/docs/search.index/0a32807f6fd5.js b/pr/617/docs/search.index/0a32807f6fd5.js deleted file mode 100644 index 9a693fce1..000000000 --- a/pr/617/docs/search.index/0a32807f6fd5.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsFARFgABoLAAABQNY3MBBADz1A8AAAEAAQABgAKgUAAAEOWgYAAAEVVpcgGAAqAgAAAMhaBgAAARVGVoAQEA84EMAAABAAEAE4ECoBAAAAyEYWMBgAKwEBPYAAGwMA0nAAFlcyMAAqBAAAAUdmV38pgUAAABAAEDADswAAABAAAFAAIAfQwDAGgRAQABBgD0IxMAAAEAmAEBABMAArBgEyUAAWZt8vIMAAABAAEBAPQhEQAAAQCwAAEAI4ACoLAAABP0ZGcBggKgoAAAE1agQAAAEIRvdvOAA2ducgGAAqBAAAATHKAwAAAP0Whstz8AAIqwQA/SAAE0YFc7MAAAAQAAGgAEAAsCAABtBA4AAgwJAN8MAAA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/0a98bd6387de.js b/pr/617/docs/search.index/0a98bd6387de.js deleted file mode 100644 index 1f7f1673a..000000000 --- a/pr/617/docs/search.index/0a98bd6387de.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEA9HEHAACACOEAAQABBQD0WhQAAAEAAQABAAEFAPRKEwAAAQCUAAEA8wICY3X1ZhAAAAEAxgABAAEA84ACbnM=") \ No newline at end of file diff --git a/pr/617/docs/search.index/0afefb419068.js b/pr/617/docs/search.index/0afefb419068.js new file mode 100644 index 000000000..fbb530749 --- /dev/null +++ b/pr/617/docs/search.index/0afefb419068.js @@ -0,0 +1 @@ +rn_("AQAAOjAAAAEAAAAAAAoAEAAAAPYP9w/mEOcQcxEEEgUSIhMjE7sUvBQBBADz8Q8AAOEAAQDzAAFx9xsNAAABAD4DAQDNAGEAAQDzAAJleTowAAABAAAAAAAOABAAAAAZCWQMZQyHEJwQnRAyETMRNBEKFAsUGxQcFJYUmxQ=") \ No newline at end of file diff --git a/pr/617/docs/search.index/0b31dbd8cc61.js b/pr/617/docs/search.index/0b31dbd8cc61.js new file mode 100644 index 000000000..d1624f051 --- /dev/null +++ b/pr/617/docs/search.index/0b31dbd8cc61.js @@ -0,0 +1 @@ +rn_("AQUA9NwSAAABAKYAAQABCAD0mBMAAAEAiAABADMBA7BAE2cAAWFiZTowAAABAAAAAAAMABAAAAA7BzwHPQcFDQYN2w9JEJgQmRCaEJsQtBKGEwEAADswAAABAAAiAA4AxQQBAIwFCgA2CQEAPgwBAPYMAQCrDgAAzA8EAOMPAAACEAEA9xABAAoRAACdEQAA+hEBAP8RAAABgQKwABGOAAGwABGQAAE0NiOAArCAFJwAAW9wAQABsEATYwABZfOZEQAAWwEBALcBAICFoGAAABE2EAEL") \ No newline at end of file diff --git a/pr/617/docs/search.index/0b75363a70af.js b/pr/617/docs/search.index/0b75363a70af.js deleted file mode 100644 index 0d4deea58..000000000 --- a/pr/617/docs/search.index/0b75363a70af.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIBsEAUjQABdDswAAABAAANAAYADxMDAHoTAQDqEwEAARQBAIIUAQCpFAEAE4ACsDATQwFJaXABBQDzvxMAAAEAAQATgAKgMAAAFBpleVMCA7BwFCcAAWJlcwFQEAAAEwACoCAAAAWoZm31gwAAAOwGAQDbAQEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/0bd1edc62be1.js b/pr/617/docs/search.index/0bd1edc62be1.js deleted file mode 100644 index f64d1c9b1..000000000 --- a/pr/617/docs/search.index/0bd1edc62be1.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQBoEAAABQ1ZfKrFAAAAQABAQA7MAAAAQAABgABANIUBgDzBgFl9IAUAAABADMAAQAzAwOwYBOWAIphcnXzixAAAHsCAQAjgAKAEYsAEAFkcg==") \ No newline at end of file diff --git a/pr/617/docs/search.index/0c36c2caebf1.js b/pr/617/docs/search.index/0c36c2caebf1.js new file mode 100644 index 000000000..1dc77c2af --- /dev/null +++ b/pr/617/docs/search.index/0c36c2caebf1.js @@ -0,0 +1 @@ +rn_("AQYA9CITAAABAJgBAQATAgKwYBMkAAFmbfLyDAAAAQAjggKwQBNRAAFlbAEFAPQTFAAAAQBQAAEA8wMBY/IXEwAAAQABgQKgAAAACTygMAAAD+dzdBOAAqAQAAAHWG5404CEsHAUywABkEAB") \ No newline at end of file diff --git a/pr/617/docs/search.index/0c38ce31a369.js b/pr/617/docs/search.index/0c38ce31a369.js new file mode 100644 index 000000000..55c66f423 --- /dev/null +++ b/pr/617/docs/search.index/0c38ce31a369.js @@ -0,0 +1 @@ +rn_("AYMCoJAAABRGoJAAABRHMjMBgAKgUAAAFHKggAAAE5FjZfMAAnN08koHAAABAAEAAaCwAAAT3GLzZhMAAC4BAQAjgAKwABAEA2FkcgEAAbCAFEwAAXDySBQAAAEAQwADoCAAABFwoEAAABLGZGVz8wkQAABNAwEAE4ICoCAAABQ4ZWnzgAJjZQEEADswAAABAAAJAAQAbAkBAK8QAwDxEAEA/RMBALOAhKCQAAAUphABQg==") \ No newline at end of file diff --git a/pr/617/docs/search.index/0c58728aab63.js b/pr/617/docs/search.index/0c58728aab63.js new file mode 100644 index 000000000..27ec0d0b2 --- /dev/null +++ b/pr/617/docs/search.index/0c58728aab63.js @@ -0,0 +1 @@ +rn_("AYICsEAUcwABsGATRwABZXIBBQD0rxAAAAEAAQABAPOAAmZtAQYAOjAAAAEAAAAAAAgAEAAAAO8P8A+iEQETAhP/E4gUvRS+FAEAArCwFBcAAaFAAAAUtmVn9IUHAAABAAEAAQDDAISgsAAAE+iwQBDHAAEUIAT3+gYAAOQFgQMBADoBLwEUAWOAA7AwECsAAWFlaQ==") \ No newline at end of file diff --git a/pr/617/docs/search.index/0c88bb59b14a.js b/pr/617/docs/search.index/0c88bb59b14a.js new file mode 100644 index 000000000..1fdeeaf2e --- /dev/null +++ b/pr/617/docs/search.index/0c88bb59b14a.js @@ -0,0 +1 @@ +rn_("AYACsCATUwBaoFAAABFCbXNDgAOw4BQVAAFBTEwYc2xkZ3IBAwDz6BIAAAEAAQAjgAKwMBQbAAFjaTOAA7AADRQDaGFpbw==") \ No newline at end of file diff --git a/pr/617/docs/search.index/0ca11bedf490.js b/pr/617/docs/search.index/0ca11bedf490.js new file mode 100644 index 000000000..738f74507 --- /dev/null +++ b/pr/617/docs/search.index/0ca11bedf490.js @@ -0,0 +1 @@ +rn_("AYQCoCAAABE3oDAAABGdaXQBAQA7MAAAAQAAjgACAH0HAACECY0A8wACZWz1uAQAAMkC0wlTAgEAAQIAOzAAAAEAAO8AAgAeDAAAKQ3uAMcAAACFsLATUQABsBAT1wABsDANJwABFDACOzAAAAEAABsBCwDgAQYApwUAANUF7gBMBwcAUxACAKwRDQDbEQIAtRIBADsTAQCMEwEAIxQCAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/0cbfae16bec4.js b/pr/617/docs/search.index/0cbfae16bec4.js new file mode 100644 index 000000000..06e6b7d26 --- /dev/null +++ b/pr/617/docs/search.index/0cbfae16bec4.js @@ -0,0 +1 @@ +rn_("AQEA9qUFAAABADYNAQCmAAEAAQAAOzAAAAEAAAcAAwARAgEAegwBAC0QAwABgwKwcBOnAAGwgBPgAAFjcwEFAPRZFAAAAQABAAEAAQUA9EkTAAABAJQAAQDzCAJjdfVmEAAAAQDGAAEAAQABAgD0zhEAAAEAAQABABMAAqAwAAARmWV09NQQAAABAAEAAQABAwD0yhEAAAEAAQABAAGAAqAAAAAQpaAAAAAQpjQ28wMBdgETDQAAlwEAAIawoBMsAAGwIAw1AAGwgBMLAAEJDAY6MAAAAQAAAAAACQAQAAAAEAcRBxIHRgwIEG8RuhK7ErwSbhQBAgD0NRMAAAEAkAABAAEDAPRpEwAAAQCYAAEA8wACYnU6MAAAAQAAAAAAFQAQAAAAVAdVB1YH7A/tD5UQlhC2ELcQTRFOEQASARIzEzQTQBNBE1ATexN8E8QTxRMBAQD0oQUAAAEAYAcBALcAAICFsQAUUAABsAAHXgABDAhEAQgAOzAAAAEAAAUAAQDtEgUA1w0AAIiwQBE9AAGgIAAADR25AAs7MAAAAQAADwAFAH0AAQAIBwcAbQcBAIUQAQA5EQEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/0d06702b2cc6.js b/pr/617/docs/search.index/0d06702b2cc6.js new file mode 100644 index 000000000..a080026a7 --- /dev/null +++ b/pr/617/docs/search.index/0d06702b2cc6.js @@ -0,0 +1 @@ +rn_("AQcA9BMSAAABAAEAAQAjAQKgcAAAEhJhcgGEBwAAAQMAOzAAAAEAAAgAAgC2DwYAbBMBAPMAAmN0OzAAAAEAAFcAJwCpBAsAcwUAAB4JAQA4DAEAQAwCAAANAAAMDQIAHg0BAOAPAADlDwEA6A8BAO4PAAB2EAIAfRABAIIQAQCnEAAAHxEAACURAABxEQEAfhEBAJIRAQCWEQIA9hEBAAkSAgAPEgAAxhIAAM8SAQDUEgEAHBMBACwTAQAxEwEAURMBAGETAwBnEwEAyBMBANwTAABKFAAAcxQEAHwUAgA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/0d83091b253d.js b/pr/617/docs/search.index/0d83091b253d.js new file mode 100644 index 000000000..9c2aa5f32 --- /dev/null +++ b/pr/617/docs/search.index/0d83091b253d.js @@ -0,0 +1 @@ +rn_("AYkCsCAUgQABoDAAABSLaXATAAOgIAAAEz2ggAAAE1BlaXX1IgwAADkAAQABAAEAAYYCsJAUqAABoAAAABLKYmQBgAKwMBMxAAGgMAAAEQhydtcAAACFoDAAABB8oDAAABOHEAETOjAAAAEAAAAAAAkAEAAAAAsJDAkNCT0JPgmXEJ4QWBFZEQ0T") \ No newline at end of file diff --git a/pr/617/docs/search.index/0db99ab57aac.js b/pr/617/docs/search.index/0db99ab57aac.js new file mode 100644 index 000000000..0ee4e7f46 --- /dev/null +++ b/pr/617/docs/search.index/0db99ab57aac.js @@ -0,0 +1 @@ +rn_("AYACsFAUJgABoAAAAAkaY3QBAQD0ohMAAAEAAQABAAEDAPQADQAA3AagAAEAAYACoBAAAAyIoFAAABKzY2QBAgDzDgkAAFEDAQDzAAFt86gEAADJAAEAAYECoBAAABNVoHAAABOdYWPzAAFy82QHAABWCiAAZw8AgIiwMA/kAM+gMAAAFKYpMCM=") \ No newline at end of file diff --git a/pr/617/docs/search.index/0decf51a5366.js b/pr/617/docs/search.index/0decf51a5366.js new file mode 100644 index 000000000..e11c5c3a9 --- /dev/null +++ b/pr/617/docs/search.index/0decf51a5366.js @@ -0,0 +1 @@ +rn_("AQMBoEAAABRKZfJ4FAAAAQABAAD0HhMAAAEAAQABAPMEAWT0whIAAAEAAQABAKcAAAKFsKAUZgABsEATHAABsHAUDQABFYgAATgUAAABAAGwgBRMAAFw8kgUAAABAEMAA6AgAAARcKBAAAASxmRlc/MJEAAATQMBAPOAAmVp") \ No newline at end of file diff --git a/pr/617/docs/search.index/0e10c6390bcf.js b/pr/617/docs/search.index/0e10c6390bcf.js deleted file mode 100644 index 014020f94..000000000 --- a/pr/617/docs/search.index/0e10c6390bcf.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsHAUqQABoAAAAASnZmcBAQA7MAAAAQAABwABAFMMBwABAQGgcAAAErJhAR0HAAAjgAKhAAAAFLNkbnOAhKBgAAASsQBBSA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/0e64f8544bfd.js b/pr/617/docs/search.index/0e64f8544bfd.js deleted file mode 100644 index 0c179875b..000000000 --- a/pr/617/docs/search.index/0e64f8544bfd.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAEoAAAABCpsCARXAABoFAAABIJoEAAABRMZGxzdjswAAABAAATAAYAsgUAAEMMAAD4DAAAGhANAIARAABHFAEAAQIA9OgRAAABAAEAAQAThAKwIBHsAAFhc/OAAmFl") \ No newline at end of file diff --git a/pr/617/docs/search.index/0ecb7a74954b.js b/pr/617/docs/search.index/0ecb7a74954b.js deleted file mode 100644 index 7e7a55587..000000000 --- a/pr/617/docs/search.index/0ecb7a74954b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQkA848UAAABADsAAYADoEAAABNssCAUKgABoCAAAA/naGl5AQkBoIAAABTIZgGnEwAAAQMBsAAUQgBQc/K9EAAALAMjgAKgIAAAD+NhcgECAPXrEAAAAQABAF8CAQDzAANpdHU6MAAAAQAAAAAAEAAQAAAAiQc8CQQNSBDGEAETOhM7E+0T7hP0Ex4UHxRAFEEUkxSUFAGAAqAwAAAUcrCwE8MAAWFsAQwBoKAAABTZd/LJFAAAAQABAQDzChAAAAEAAQDzgAJ0dQEDAPPSEgAAAQABAAEBAPVPEQAAAQABAAEAAQAjgAKwkBQWAAFhdAEEAaBAAAAUdmXymBQAAAEAAQIAOzAAAAEAAAsABQBpEAUA4hIBABAUAQBpFAAAiBQAABOCArDwFMwAAWZnAYECsIAUnwABoGAAABTHZHMBCAD1UxQAAAEAAQABAAEA8wIBZvWqEAAAAQABAAEAAQAjgAOgIAAAD+GwoBSdAAFkcnR37wEAjbAQFE0AAbDgFE8AAZfVDjswAAABAAA9ARMAiAQBALUEAgCqBQIAxwYEAPwHAQA6DAMAaAwBAPQMAQCuDgcBABABAMcQAQDaEAIACBEBAJsRAADOEQMA+REBAPQSAAC1EwcArRQFAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/0fd086f0ef1b.js b/pr/617/docs/search.index/0fd086f0ef1b.js deleted file mode 100644 index a853d9db3..000000000 --- a/pr/617/docs/search.index/0fd086f0ef1b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQkA848UAAABADsAAQ0BoKAAABTZd/LJFAAAAQABgQKwgBSfAAGgYAAAFMdkczcBAIWFoEAAABRysGAUQgBQApAK") \ No newline at end of file diff --git a/pr/617/docs/search.index/71c0c54d2614.js b/pr/617/docs/search.index/10ffbbec645d.js similarity index 53% rename from pr/617/docs/search.index/71c0c54d2614.js rename to pr/617/docs/search.index/10ffbbec645d.js index 2bbb3a298..24352ce11 100644 --- a/pr/617/docs/search.index/71c0c54d2614.js +++ b/pr/617/docs/search.index/10ffbbec645d.js @@ -1 +1 @@ -rn_("AQYAOzAAAAEAAAkAAQBeEQkAAQIAOzAAAAEAABMAAQA0EBMAE4GEoFAAABEgoBAAAAw3oCAAAA0BHEAAE4ACsFATkwABYW4zgAOwMA/cAAFpc3Q=") \ No newline at end of file +rn_("AQYAOzAAAAEAAAkAAQBeEQkAAQIAOzAAAAEAABMAAQA0EBMAE4GEoFAAABEgoBAAAAw3oCAAAA0BHEAAE4ACsFATkgABYW4zgAOwMA/cAAFpc3Q=") \ No newline at end of file diff --git a/pr/617/docs/search.index/11205106a64b.js b/pr/617/docs/search.index/11205106a64b.js new file mode 100644 index 000000000..97eaa00a5 --- /dev/null +++ b/pr/617/docs/search.index/11205106a64b.js @@ -0,0 +1 @@ +rn_("AQMBoEAAABIJZfMWDQAAEwQBAAEGAPQmEQAAAQC+AAEAE4EDoAAAABFusSAUswABYWRuAQUA9JgQAAABAAEAAQABFAD10xQAAAEAAQABAAEAAQUBsIAUbwABdjswAAABAAAHAAIA4REDANATAwAjgAKwcBIGAAFiZFcCAAGGsMATmgABoKAAABO9oJAAABOGBeAC9SAHAAABAAEA8AiUBAEGAPPlEgAAAQCqAPOAA3J0dgEDADswAAABAAAHAAIAzBMDALcUAwDzAAFzAXIUAADzAAFzAQgSAADHAgCAh6AQAAAJB6AwAAAMUrAAE4EAAaAQAAAM+xNADg==") \ No newline at end of file diff --git a/pr/617/docs/search.index/113486b6b2b6.js b/pr/617/docs/search.index/113486b6b2b6.js new file mode 100644 index 000000000..7be9f45fa --- /dev/null +++ b/pr/617/docs/search.index/113486b6b2b6.js @@ -0,0 +1 @@ +rn_("AQUA9FkUAAABAAEAAQABBQD0SRMAAAEAlAABAPMAAmN19WYQAAABAMYAAQABAAEJAPOOFAAAAQA7AAGBArCAFJ4AAaBgAAAUxmRzFwEAhIWw0BTIAAGgQAAAFHGwYBRBAFACkAojgAKgcAAAEW9kcgELAaBAAAAUdWXylxQAAAEAE4ACsDAQggABZHLzAgFl9OsHAAABAAEAAQAjgAKwIAwYAAFleQEKAPPREgAAAQABAAELADswAAABAAAHAAEAtBMHAAECAPQTFAAAAQBQAAEAtwAAgIWwcBMHAAGw0BS7AAFgIAYBBgDz3hIAAAEAAQABAwGwQBSMAAF08ukTAAABACOFAqBAAAAUGWJzIwIDsEAREwABoFAAABGAYWJm9xcJAAABAEgAeAcBAGIAAQDzgAJpcAEEAPN/EAAAAQABAPMAAXA7MAAAAQAADAABAOoGDAAjgAOgMAAADCiwcBPIAAFhZWnzgANkbW4BAAA6MAAAAQAAAAAABwAQAAAAIgl3DMcSyBIeEx8TIBMhE/MCAWQ7MAAAAQAACQADAPIRAwDCEgMA9hIBACOAAqCQAAAUaGlvAYADsDATewABgBO+ABABsFARMAABY3N48wABZQHNBAAA5xUAgImgAAAADIigQAAADOGwQAziAAGQSF0=") \ No newline at end of file diff --git a/pr/617/docs/search.index/11a240e9b947.js b/pr/617/docs/search.index/11a240e9b947.js deleted file mode 100644 index 1d612ab3b..000000000 --- a/pr/617/docs/search.index/11a240e9b947.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIAOzAAAAEAAAUAAQChFAUA8w0BbTswAAABAAAFAAEAXxQFACMDhLBAEtUAAaEAAAAUp7AAE5YAijAIAjswAAABAAAYAAYAQxEHAPkSAQB1EwQAUxQEAFoUAwDIFAAA") \ No newline at end of file diff --git a/pr/617/docs/search.index/120270aa39e9.js b/pr/617/docs/search.index/120270aa39e9.js new file mode 100644 index 000000000..f041ed596 --- /dev/null +++ b/pr/617/docs/search.index/120270aa39e9.js @@ -0,0 +1 @@ +rn_("AQAA9W4JAAABAAEAAQABACOAAqCgAAAUpmZwRwAAAIWwMA/DAAGgUAAAEIiwcBEWAAGxQBTRAAGBKQDyegUAAAEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/1231139faa22.js b/pr/617/docs/search.index/1231139faa22.js new file mode 100644 index 000000000..681835143 --- /dev/null +++ b/pr/617/docs/search.index/1231139faa22.js @@ -0,0 +1 @@ +rn_("AQcA9BMSAAABAAEAAQAjAAKgcAAAEhJhcgGEBwAAAQAA9jARAAABAJMDAQAKAAEAAQAAOzAAAAEAAAYAAgCcEwAABBQFACOIArBAFHwAAWVzAQ0BsEAUdgABZfU6FAAAAQABAAEAAQABBQA7MAAAAQAACAADAAwSAgASEwQAORQAANcDAACGHYebSY36FIQG9cIEAAABABgIAQABAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/1271000c9004.js b/pr/617/docs/search.index/1271000c9004.js new file mode 100644 index 000000000..80a902dcd --- /dev/null +++ b/pr/617/docs/search.index/1271000c9004.js @@ -0,0 +1 @@ +rn_("AQYA9MwTAAABAAEAAQABDwD0txQAAAEAAQABAAEAAbDwFMEAAWs7MAAAAQAACAADACgUAAAtFAYAWBQAAAECAPSFEAAAAQCzAAEA9wAAAIWxABSqAAGJMADy6xIAAAEAE4ACsBAMZAABZW8BBQA7MAAAAQAADQABAKwRDQABAgDzFxAAAAEAAQABAgD0ExAAAAEAAQABAAEDADswAAABAAAIAAEAyRAIAAGAAqAAAAAMN7AwFHYAAXFyAYACoEAAABEgsMAUbAABZXMBAQA7MAAAAQAAEwABADQQEwAjgAKwkBPUAAFpbXc2AACKoFAAABQ3sHAUFQABoCAAAA0BnFUROzAAAAEAAAsABABgBwAA+RADAMsSAQA6FAQAAQIBsDARhwABcAGyEgAAs4CEoCAAABHxAigE") \ No newline at end of file diff --git a/pr/617/docs/search.index/13257449bc05.js b/pr/617/docs/search.index/13257449bc05.js deleted file mode 100644 index c2bc96331..000000000 --- a/pr/617/docs/search.index/13257449bc05.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEBoEAAABGZc/VGDAAAKQVMAQEAAQATggKgIAAADR1lcgECAPT+DwAAAQD2AAEAI4ECoBAAAAxFZWkTgAKg8AAAFDlucjMAA7AwCTYAAWVvdAHuEQAA") \ No newline at end of file diff --git a/pr/617/docs/search.index/137d1f70f3ef.js b/pr/617/docs/search.index/137d1f70f3ef.js deleted file mode 100644 index 658f2405d..000000000 --- a/pr/617/docs/search.index/137d1f70f3ef.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIBsEAUjQABdDswAAABAAANAAYADxMDAHoTAQDqEwEAARQBAIIUAQCpFAEAE4ACsDATQwFJaXABBQDzvxMAAAEAAQATgAKgMAAAFBpleVMAA7BwFCcAAWJlcwFQEAAA") \ No newline at end of file diff --git a/pr/617/docs/search.index/13df067efa5b.js b/pr/617/docs/search.index/13df067efa5b.js deleted file mode 100644 index 1baa15a0a..000000000 --- a/pr/617/docs/search.index/13df067efa5b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9IkTAAABAAEAAQATAAKwgBNdAAFzdvR8CQAAAQCRCgEAE4ACsDARHQABbG4BgAKgAAAAEKWgAAAAEKY0NvMDAXYBEw0AABMAAqBAAAARmWFz90YMAADCA2cBTAEBAAEAsgEBAgD0NhMAAAEAkAABAAEDAPRqEwAAAQCYAAEA8wACYnU6MAAAAQAAAAAAEwAQAAAAVAdVB1YH7A/tD5UQlhC2ELcQTRFOEQESAhI0EzUTQRNCE1ETxRPGE/OAAmR0E4ACoCAAAA0dZXIBBwGgQAAAFEtl8nkUAAABAAEAAPVuCQAAAQABAAEAAQAjgAKgoAAAFKdmcEcAAACFsDAPwwABoFAAABCIsHARFgABsUAU0gABgSkA8noFAAABAPOAAmx0lwEAgIaxMBRNAAGxEBR7AAGgsAAAE2wRRQg=") \ No newline at end of file diff --git a/pr/617/docs/search.index/13f38e008740.js b/pr/617/docs/search.index/13f38e008740.js new file mode 100644 index 000000000..1b014d0bb --- /dev/null +++ b/pr/617/docs/search.index/13f38e008740.js @@ -0,0 +1 @@ +rn_("AQUA9BMUAAABAFAAAQDzAwFj8hcTAAABACOAArBwFMsAAWhvAYICsAAS9AABsAAR+gABZXNDAAOgoAAAFGmgIAAAE6BlaXT1BxMAAAEAhgABAPoAAYACsHAS6wABoLAAABQoZW8BAgA7MAAAAQAABgABAHQRBgABAgGgQAAAFEpl8ngUAAABAPOAAmlvAQgA9AoUAAABAIsABQATAQOwkBRzAAGwUBNPAWdnbHA7MAAAAQAAEgAJAHkQAgD9EQEAPhMBAF8TAQCWEwEA+xMBADUUAQBEFAEAhRQBAAGAArBAEskBFKAgAAAROGFpAQMA8+gSAAABAAEAI4ECsDAUGwABY2kBAAD0ORMAAAEAWAEBAPMDAXM7MAAAAQAADwADAPgRAQC0EwcArBQFAAEDAPX2EQAAAQAlAQEAYQHzgAJydgEAADswAAABAAAFAAIAxxIBAB4TAwDzAgFkOzAAAAEAAAkAAwDyEQMAwhIDAPYSAQAjgAKgkAAAFGhpbxOAArDwFLsAAWxvAQUA9IQUAAAMACIAEQAjgAKgYAAAE5RncyOAArCQFGYAAWJs9xcDAI+wcBIQAAGwYBScAAGwcBQNAAGwYBNNAAGwUBM7AAG1+Rc7MAAAAQAAPAAYAL0FAAB0BwEAAAkFABEJAAA/CQAAIQwAAG8MAQDvDAIAGQ0BAKcOAwCsDgEADRAEAN8QAQAcEQAAPxEAAF4RCQCcEQAAnhEDAPARAABFEwAAXBMBAG4TBQB+EwAAOBQAAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/01cc8d107b7f.js b/pr/617/docs/search.index/140acd1805c2.js similarity index 67% rename from pr/617/docs/search.index/01cc8d107b7f.js rename to pr/617/docs/search.index/140acd1805c2.js index 057bf19d2..b83576a35 100644 --- a/pr/617/docs/search.index/01cc8d107b7f.js +++ b/pr/617/docs/search.index/140acd1805c2.js @@ -1 +1 @@ -rn_("AQMBoEAAABGSZQHaDwAAAQIA80AMAAABAAEAIwACsCAMLgABa3L2+wYAAAEAGQruAAEANALzAAJyePL+CAAAAQA=") \ No newline at end of file +rn_("AQMBoEAAABGSZQHaDwAAAQIA80AMAAABAAEAIwACsCAMLgABa3L2+wYAAAEAGQrtAAEANALzAAJyePL+CAAAAQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/143875586e56.js b/pr/617/docs/search.index/143875586e56.js deleted file mode 100644 index df50b5ddc..000000000 --- a/pr/617/docs/search.index/143875586e56.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMA9JAMAAABAAEAAQABAAGwUBRPAAFyAcQEAABTAYSwQBLQAAGwQBSNAAGYAAQ6MAAAAQAAAAAAKAAQAAAAjgSPBJAEnAUcCR0JQgl1CeQM3w9OEE8QXBCMEOsQ7BDtEDcRsRIPExATERMSEz4TTBNNE3oTexPqE+sT7xPwE/ETARQCFGsUbBSCFIMUqRSqFA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/1454cc4b3625.js b/pr/617/docs/search.index/1454cc4b3625.js deleted file mode 100644 index 47bb86239..000000000 --- a/pr/617/docs/search.index/1454cc4b3625.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEA9EUHAAABAAEAAQABAQGgQAAAEZlz9UYMAAApBUwBAQABABOCAqAgAAANHWVyI4ADoEAAAAxFsDAJNgABZW90AQAA9EEHAAABAAEAAQABAgD0sxMAAAEADwABABOAArBAFG0AAWJ284ACb3gBCgD17xMAAAEAAQB6AAEAAYECoCAAABEKsFASzgABYW8BBAGgAAAAE/RzAQkRAABjAwOwUBF7AAFjZXP2EgkAAAEAAQAsAAEAcwkBAQA7MAAAAQAABQABAOcMBQAjgAKwMBNQAWdydPMAhgHAB/LxAQAACgY=") \ No newline at end of file diff --git a/pr/617/docs/search.index/146012214b23.js b/pr/617/docs/search.index/146012214b23.js deleted file mode 100644 index 9b7327b72..000000000 --- a/pr/617/docs/search.index/146012214b23.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9bsRAAABAAEAAQABAAGAArBgEycAAaAgAAAREG95AQABoMAAABRMafR+EwAACAAcABsAAQYA9LgUAAABAAEAAQABAgD0DhEAAAEAmgABAAECAaBAAAATVXI7MAAAAQAABwACAAwRAQCjEQUA84ACYW9nAwAAhrCAE1QAWrCgE8sAAQwwBjswAAABAAAIAAMALQwAANYRAwC+EgMA84ICZWkjAAKggAAAFMhmaPIKCQAAnQoBBQDzLxMAAAEAAQABAQA7MAAAAQAABQACABEMAwAHEgEAUwADsDAUZwABZXBy9PQPAAABAO4AAQABAAOwQBQsAAGhQAAAFJygUAAAEf1iY2YBEwcAAPOAAm1uAQGEoLAAABPpsEAQxwABsMAUGAABoGAAABGiFCAE9voGAABlCQEAOgEwARQBAQUAOzAAAAEAAAUAAQDuEgUAAQcA9XUTAAABAAEAAQABACOAAqBQAAARc2Fv8wCFIEkCOzAAAAEAAAgABABXBwAAigcFABUNAACrEQAA") \ No newline at end of file diff --git a/pr/617/docs/search.index/156802473293.js b/pr/617/docs/search.index/156802473293.js new file mode 100644 index 000000000..81827e888 --- /dev/null +++ b/pr/617/docs/search.index/156802473293.js @@ -0,0 +1 @@ +rn_("AQEAOjAAAAEAAAAAAAcAEAAAAF8TYBP7E/wTNRQ2FIUUhhTzBgFpAX4TAABDgAOwABS/AAGwUBNTAFpkZm4BAAGwQBMsAAFlOzAAAAEAAA0AAgDcBgwAIBEAACOAAqAwAAAMI2FlNwAAgMWwUBOnAAGwEBROAAGwYBOuAAEAApgI") \ No newline at end of file diff --git a/pr/617/docs/search.index/158eff2eda3b.js b/pr/617/docs/search.index/158eff2eda3b.js new file mode 100644 index 000000000..109366af8 --- /dev/null +++ b/pr/617/docs/search.index/158eff2eda3b.js @@ -0,0 +1 @@ +rn_("AQYA9MwTAAABAAEAAQABDwD0txQAAAEAAQABAPOHAmhtAQAA9B4TAAABAAEAAQDzBAFk9MISAAABAAEAAQAjh4SwoBRmAAGwQBMcAAGwcBQNAAEVgAABAAGgQAAAEKdlOzAAAAEAAAcAAgB2BwUABwgBABOAArBQEF0AAWRyAQkA9e4TAAABAAEAegABALcCAICGsCASzQABR1L2jsN4EUkB") \ No newline at end of file diff --git a/pr/617/docs/search.index/160defe62b6f.js b/pr/617/docs/search.index/160defe62b6f.js new file mode 100644 index 000000000..d868860af --- /dev/null +++ b/pr/617/docs/search.index/160defe62b6f.js @@ -0,0 +1 @@ +rn_("AQAAOzAAAAEAAAYAAgCcEwAABBQFACOIArBAFHwAAWVzAQQBsDATbAABY/OWEQAAAQABAAEFADswAAABAAAIAAMADBICABITBAA5FAAA8wEDY2Vw89sMAAABAAEAAQIA9igJAABXABQHAQC3AAEA84ACbnI=") \ No newline at end of file diff --git a/pr/617/docs/search.index/1613c157a7c0.js b/pr/617/docs/search.index/1613c157a7c0.js deleted file mode 100644 index 0a68d6735..000000000 --- a/pr/617/docs/search.index/1613c157a7c0.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYMCoJAAABRHoJAAABRIMjMBgAKgUAAAFHOggAAAE5JjZfMAAnN08koHAAABAAEAAaCwAAAT3WLzZxMAAC4BAQAjgAKwABAEA2JkcgEAAbCAFE0AAXDySRQAAAEAQwADoCAAABFwoEAAABLHZGVz8wkQAABOAwEAE4ICoCAAABQ5ZWnzgAJjZQEEADswAAABAAAJAAQAbAkBAK8QAwDxEAEA/hMBALOAhKCQAAAUpxABQg==") \ No newline at end of file diff --git a/pr/617/docs/search.index/16335499b785.js b/pr/617/docs/search.index/16335499b785.js deleted file mode 100644 index 67f6c133b..000000000 --- a/pr/617/docs/search.index/16335499b785.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQkA848UAAABADsAAYACoEAAABNssCAUKgABaGkBDAGgoAAAFNl38skUAAABAAEBAPMKEAAAAQABAPOAAnR1AYECsIAUnwABoGAAABTHZHM3AwCAhqBAAAAUcrBgFEIAUAKQDgEAAbBQER0AAWP0ZwcAAAEAAQANAsOAhKAQAAAMiKBQAAAStAxABA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/1642b10854ec.js b/pr/617/docs/search.index/1642b10854ec.js new file mode 100644 index 000000000..88aac0b2a --- /dev/null +++ b/pr/617/docs/search.index/1642b10854ec.js @@ -0,0 +1 @@ +rn_("AQMA9JAMAAABAAEAAQABAAGwUBROAAFyAcQEAABTAYSwQBLPAAGwQBSMAAGYAAQ6MAAAAQAAAAAAKAAQAAAAjgSPBJAEnAUcCR0JQgl1CeQM3w9OEE8QXBCMEOsQ7BDtEDcRsBIOEw8TEBMREz0TSxNME3kTehPpE+oT7hPvE/ATABQBFGoUaxSBFIIUqBSpFA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/164abac8a96b.js b/pr/617/docs/search.index/164abac8a96b.js deleted file mode 100644 index de89ebd3c..000000000 --- a/pr/617/docs/search.index/164abac8a96b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsHARhwABsCAUbQABYWQBBQD0WRMAAAEAmAABAPMAAWMBwhIAAEOAA7AwE68AAbCgFE8AAWFzeQEGAPQmEQAAAQC/AAEAk4CEoAAAABFusSAUtAABCSAB84ACZWk=") \ No newline at end of file diff --git a/pr/617/docs/search.index/16666d274533.js b/pr/617/docs/search.index/16666d274533.js deleted file mode 100644 index 55c088991..000000000 --- a/pr/617/docs/search.index/16666d274533.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQA9L4QAAABAAEAAQABAAA6MAAAAQAAAAAACgAQAAAA9g/3D+YQ5xBzEQUSBhIjEyQTvBS9FPMBAXnyHBQAAAEAAQADoKAAABRqoCAAABOhsDAR+wABZWl08ggTAAABAAEDADswAAABAAAGAAEAdBEGAAEIAPQLFAAAAQCLAAUAEwEDsJAUdAABsFATUAFnZ2xwOzAAAAEAABIACQB5EAIA/hEBAD8TAQBgEwEAlxMBAPwTAQA2FAEARRQBAIYUAQABgAKwMBH3AAGwMBH5AAFydgECAbAAEsgAAWQ7MAAAAQAABQACAPMRAwD3EgEAI4ECoJAAABRpaW8BBwD0hRQAAAwAIgARAHcWAAGJsFAUHAABsFASygEUsGATTgABtQkDOjAAAAEAAAAAAA4AEAAAAO8M8AzxDKwOrQ7fEOAQHBE/EZ4RnxGgEaERRhN/E0OAA6AgAAAM4aAQAAAM7m1ubwEDAPR2EAAAAQABABoBAYACoLAAABQ4sDATggABbnRDAAOgIAAAESuwUBIDAAFlaGk7MAAAAQAABgADACAMAAAUEgMA4RMBAPOAAmRyhwsAAAqAAfgAEAGAAfsAEAGgUAAAEZugUAAAFG+gMAAAEKmgMAAAELQwMWNlaW9zdHV39aQHAAABADUI2AJbAQ==") \ No newline at end of file diff --git a/pr/617/docs/search.index/16b34983ecba.js b/pr/617/docs/search.index/16b34983ecba.js new file mode 100644 index 000000000..054061d52 --- /dev/null +++ b/pr/617/docs/search.index/16b34983ecba.js @@ -0,0 +1 @@ +rn_("AQAA9DkTAAABAFgBAQDzBAFzOzAAAAEAAA0AAgC0EwcArBQFAEcAAAGGoIAAABOUsQAUuwABsEAS9AABsIAUywABsFATOwABFQAW9KcOAAABAAEAAQABBQDzDxAAAAEAAQBjgISwQBCcAAGgAAAABukA8AA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/17b20931cf6c.js b/pr/617/docs/search.index/17b20931cf6c.js new file mode 100644 index 000000000..b2bf3b6cf --- /dev/null +++ b/pr/617/docs/search.index/17b20931cf6c.js @@ -0,0 +1 @@ +rn_("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") \ No newline at end of file diff --git a/pr/617/docs/search.index/17c3fc246899.js b/pr/617/docs/search.index/17c3fc246899.js new file mode 100644 index 000000000..55e7e38d4 --- /dev/null +++ b/pr/617/docs/search.index/17c3fc246899.js @@ -0,0 +1 @@ +rn_("AQADoKAAABRpoCAAABOgsDAR+gABZWl08gcTAAABAAEDADswAAABAAAGAAEAdBEGAAEIAPQKFAAAAQCLAAUAEwEDsJAUcwABsFATTwFnZ2xwOzAAAAEAABIACQB5EAIA/REBAD4TAQBfEwEAlhMBAPsTAQA1FAEARBQBAIUUAQABgAKwMBH2AAGwMBH4AAFydgECAbAAEscAAWQ7MAAAAQAABQACAPIRAwD2EgEAI4ECoJAAABRoaW8BBwD0hBQAAAwAIgARAHcLAAGIsFASyQEUsGATTQABlQkDOjAAAAEAAAAAAA4AEAAAAO8M8AzxDKwOrQ7fEOAQHBE/EZ4RnxGgEaERRRN+EyOAAqAQAAAM7m5v") \ No newline at end of file diff --git a/pr/617/docs/search.index/1808c73b09a0.js b/pr/617/docs/search.index/1808c73b09a0.js new file mode 100644 index 000000000..e85d59043 --- /dev/null +++ b/pr/617/docs/search.index/1808c73b09a0.js @@ -0,0 +1 @@ +rn_("AYACsRAUqgABsBAMZAABZW8BBQA7MAAAAQAADQABAKwRDQABAgDzFxAAAAEAAQABAgD0ExAAAAEAAQABAAEDADswAAABAAAIAAEAyRAIAIcHAACHsKAT1AABsNAUbAABoFAAABQ3jBQROzAAAAEAAAYAAwBgBwAA+RADAMsSAQDzgAJsdBOAA6AgAAAM3rCgFFAAAWFkbg==") \ No newline at end of file diff --git a/pr/617/docs/search.index/182b5b46d5d5.js b/pr/617/docs/search.index/182b5b46d5d5.js new file mode 100644 index 000000000..e7255c1ba --- /dev/null +++ b/pr/617/docs/search.index/182b5b46d5d5.js @@ -0,0 +1 @@ +rn_("AQUAOzAAAAEAAAcAAwDbEQIAjBMBACMUAgABBADz3hEAAAEAAQAjAAKgYAAAEw1hcvP/EAAAAQDKAQGAAqEQAAAUsrCAFKgAAWl1NwQAgoexUBTPAAGgAAAAEN6wcBPsAAGhMAAAFMMKQBsTgAKwEBMZAAFpcg==") \ No newline at end of file diff --git a/pr/617/docs/search.index/1853a3dd5558.js b/pr/617/docs/search.index/1853a3dd5558.js deleted file mode 100644 index 251ac429d..000000000 --- a/pr/617/docs/search.index/1853a3dd5558.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEBsFAP6gABY/SkBAAAAQABAMoAAQABoIAAABTIZgGnEwAA8wYBc/IVDQAAlgQzAAOgYAAAFKhjaXPy7BEAAAEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/1880a3564175.js b/pr/617/docs/search.index/1880a3564175.js new file mode 100644 index 000000000..2b48428c4 --- /dev/null +++ b/pr/617/docs/search.index/1880a3564175.js @@ -0,0 +1 @@ +rn_("AQIA9MARAAABAAEAAQABAQDz3hEAAAEAAQATgAIvI8fA1s5lcwEKAPTQEwAAAQABAAEAAYACsCAUTgABoGAAABOdb3YBgAKwMBLWAAGgAAAAE1VjZPOAhQUQBg==") \ No newline at end of file diff --git a/pr/617/docs/search.index/18b064acc7a2.js b/pr/617/docs/search.index/18b064acc7a2.js new file mode 100644 index 000000000..abdf8e204 --- /dev/null +++ b/pr/617/docs/search.index/18b064acc7a2.js @@ -0,0 +1 @@ +rn_("AYMCoFAAABEkoKAAABNea3ABAAGwQBKpAAFs9HoJAAABAHgHAQABCAD0vRIAAAEAAQABAAEEAbAAEP0AAXMBZRAAAAECAaBQAAATG2MB3wwAALcAAACFsEANHgABoDAAAAwhlQABOzAAAAEAAAgABADJBAAAnQUCAOIMAQDUDwIAJwEAgIWwMAk5AAGgEAAAE5SwIAeCAAGRAEQBEAGwABSSAAFzOzAAAAEAAAUAAQCsFAUAAQMA9HoJAAABAHgHAQBjgAOgAAAADCFucnUBAAKw0BPaAAGgMAAAD9diavaABQAAAQABAE0AAQB5AQEAAbCAE/kAAXQ7MAAAAQAARQAEAM0FAAAkCQAALgkAAJUMQgABCQDz3hIAAAEAAQDzgANjZ3QBgwKgkAAAFEagkAAAFEcyMwGAAqBQAAAUcqCAAAATkWNl8wACc3TySgcAAAEAAQABoLAAABPcYvNmEwAALgEBACOAAqAAAAATZWRyI4ACoFAAABQ4Y2XzgAJpeQEEAPVPEQAAAQABAAEAAQDzAQFuAUAHAAABAQA7MAAAAQAACQACAPEHBwAkEwEAAQYA9MwTAAABAAEAAQABDwD0txQAAAEAAQABAPOBAmhtAQABsEAUdgABZfU6FAAAAQABAAEAAQBTgAOgIAAAEfFsbnTXbQCAy6AgAAAUp7BgEOEAAbBAEA0AAcAbOAo=") \ No newline at end of file diff --git a/pr/617/docs/search.index/18f4af644129.js b/pr/617/docs/search.index/18f4af644129.js new file mode 100644 index 000000000..831a01c1f --- /dev/null +++ b/pr/617/docs/search.index/18f4af644129.js @@ -0,0 +1 @@ +rn_("AQAA9jARAAABAJMDAQAKAAEAAQAAOzAAAAEAAAYAAgCcEwAABBQFACOIArBAFHwAAWVzAQ0BsEAUdgABZfU6FAAAAQABAAEAAQABBQA7MAAAAQAACAADAAwSAgASEwQAORQAANcBAACFHYebSY36FIQC89sMAAABAAEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/18fd1a9e7934.js b/pr/617/docs/search.index/18fd1a9e7934.js new file mode 100644 index 000000000..28bbee401 --- /dev/null +++ b/pr/617/docs/search.index/18fd1a9e7934.js @@ -0,0 +1 @@ +rn_("AQABsIAUTAABcPJIFAAAAQBDBQOgIAAAEXCgQAAAEsZkZXPzCRAAAE0DAQABAgA7MAAAAQAAEwABADQQEwATgYSgUAAAESCgEAAADDegIAAADQEcQAAzgAOwUBOSAAFhZm4=") \ No newline at end of file diff --git a/pr/617/docs/search.index/197b84d1f6e5.js b/pr/617/docs/search.index/197b84d1f6e5.js deleted file mode 100644 index 1558be2e9..000000000 --- a/pr/617/docs/search.index/197b84d1f6e5.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYMCsCARlAEkoJAAABQ6bXABggKwoBQqAAGwoBQsAAFjcgECAaBAAAARk2UBSxAAADOAhLAgEREAAbAgD8oAAVAUAPOAAm9y") \ No newline at end of file diff --git a/pr/617/docs/search.index/19bb8e539b3c.js b/pr/617/docs/search.index/19bb8e539b3c.js deleted file mode 100644 index a0196aa57..000000000 --- a/pr/617/docs/search.index/19bb8e539b3c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQA9o8UAAABAA8AAQAnAAQAAQMA89ISAAABAAEAI4ECsSAUzAABY2sTgAKgMAAAFEtuclMAA6BAAAAMMGVvcvQ4EQAAggG/AQEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/1a335dc013ee.js b/pr/617/docs/search.index/1a335dc013ee.js new file mode 100644 index 000000000..4c32846b8 --- /dev/null +++ b/pr/617/docs/search.index/1a335dc013ee.js @@ -0,0 +1 @@ +rn_("AQAA9IgTAAABAAEAAQABBADz1A8AAAEAAQABgAKgUAAAEOWgYAAAEVVpcgGAAqAgAAAMhaBgAAARVGVoAQEA84EMAAABAAEAE4ECoBAAAAyEYWMBAwA7MAAAAQAABQACAH0MAwBoEQEAAQYA9CITAAABAJgBAQATAAKwYBMkAAFmbfLyDAAAAQAjgAKgwAAAE/NucqcPAACIoEAAAA/RsEAP0gABNCAXOzAAAAEAABkAAwALAgAAbQQOAAIMCQABgAOwcBOnAAGwMBNRAAGwgBPgAAFjcnMBAAGwIBOnAAFvOjAAAAEAAAAAAAkAEAAAAO4R7xELEwwT+RP6E1AUURTLFMwUAYACsCAJMQABsDATXAABcnT3BAAAyKAgAAAHfqBgAAAP/bCAE1wAAUAGkgn2FwIAAHoC6wQBAJAKAQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/1a7331c9f293.js b/pr/617/docs/search.index/1a7331c9f293.js new file mode 100644 index 000000000..871dd5fc5 --- /dev/null +++ b/pr/617/docs/search.index/1a7331c9f293.js @@ -0,0 +1 @@ +rn_("AQEAOzAAAAEAAAYAAQB0EQYAAQAA9y8JAAABAF0HAQBzAAEAFQEBggKgIAAAETegMAAAEZ1pdPOAA2FncwEBAaBAAAAUSmXyeBQAAAEAI4ACsFAT/QABZnPzgAJpbw==") \ No newline at end of file diff --git a/pr/617/docs/search.index/1abe42839e69.js b/pr/617/docs/search.index/1abe42839e69.js new file mode 100644 index 000000000..aeae05e49 --- /dev/null +++ b/pr/617/docs/search.index/1abe42839e69.js @@ -0,0 +1 @@ +rn_("AQABoJAAABOFaPJNEwAAAQDzAgFk8h4HAAABAAECADswAAABAAAFAAEAoBQFAPMLAW07MAAAAQAABQABAF4UBQAjgAKwIAz8AAFhbwECAPMOCQAAUQMBAAGKArAAE/QAAbAAE/YAATQ2I4ECoDAAAAyIZXIBgAKwQBIEAAEtg/pN5GBjdbcGAACHsBAQYwABsFAUegABEVEJ9Z4EAAAKAMkAAQCrBw==") \ No newline at end of file diff --git a/pr/617/docs/search.index/1b3f0c6a1d7d.js b/pr/617/docs/search.index/1b3f0c6a1d7d.js new file mode 100644 index 000000000..57847bc65 --- /dev/null +++ b/pr/617/docs/search.index/1b3f0c6a1d7d.js @@ -0,0 +1 @@ +rn_("AYACoCAAAA/joDAAABPoYXLzAAF0AYkHAAABBAGgQAAAFHVl8pcUAAABAAEFADswAAABAAALAAUAaRAFAOESAQAPFAEAaBQAAIcUAADjAISgMAAAD+GFAAI7MAAAAQAACwAFAKoFAgDHBgQAaAwBAJsRAADzEgAA") \ No newline at end of file diff --git a/pr/617/docs/search.index/1b4c79c0be67.js b/pr/617/docs/search.index/1b4c79c0be67.js deleted file mode 100644 index 136f313e8..000000000 --- a/pr/617/docs/search.index/1b4c79c0be67.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQYA9J4RAAABAAEAAQABAgA7MAAAAQAACgAEAJ8QBQDkEgEARBMBAGoUAADzgAJmbgEBADswAAABAAAIAAIAtg8GAG0TAQDzgAJkbgEBAPM4BwAAAQABAAGGAqAAAAATZqAAAAATZ2Ry8wEBdgGSEwAAQwADsIAS+QABsLAUmgABa2xyOzAAAAEAAAwAAwAvBwgAFw0BAFgQAQABAgD1AggAAAEAAQABAAEA8wCEFEEAAYEAAAA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/1b9b9d2cb1f9.js b/pr/617/docs/search.index/1b9b9d2cb1f9.js deleted file mode 100644 index a0e544128..000000000 --- a/pr/617/docs/search.index/1b9b9d2cb1f9.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9bsRAAABAAEAAQABAAGAArBgEycAAaAgAAAREG95AQABoMAAABRMafR+EwAACAAcABsAAQYA9LgUAAABAAEAAQABAgD0DhEAAAEAmgABAAECAaBAAAATVXI7MAAAAQAABwACAAwRAQCjEQUA84ACYW9nAwAAhrCAE1QAWrCgE8sAAQwwBjswAAABAAAIAAMALQwAANYRAwC+EgMA84ACZWk=") \ No newline at end of file diff --git a/pr/617/docs/search.index/1baaec618882.js b/pr/617/docs/search.index/1baaec618882.js deleted file mode 100644 index fb15fb05f..000000000 --- a/pr/617/docs/search.index/1baaec618882.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQBoEAAABQ1ZfKrFAAAAQABAQA7MAAAAQAABgABANIUBgDzBgFl9IAUAAABADMAAQAzBQOwYBOWAIphcnXzixAAAHsCAQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/1bc38f904b32.js b/pr/617/docs/search.index/1bc38f904b32.js new file mode 100644 index 000000000..d83f7cfb6 --- /dev/null +++ b/pr/617/docs/search.index/1bc38f904b32.js @@ -0,0 +1 @@ +rn_("AYkCsCAUgQABoDAAABSLaXATAAOgIAAAEz2ggAAAE1BlaXX1IgwAADkAAQABAAEAAYYCsJAUqAABoAAAABLKYmQBAgD0KwwAAAEAkQMBAAEAAPRHCQAAAQABAAEAAQAAOzAAAAEAAAYAAgCcEwAABBQFACOBArBAFHwAAWVz84ADZXh5AQAA9EMJAAABAAEAAQAjgAKwIBMxAAFyeEcBAADGsMATyAABoBAAABGBsEAM9gABoDAAABEIAESAC/R0BQAAAQCHBwEA1wAAAIWgMAAAEHygMAAAE4cQARM6MAAAAQAAAAAACgAQAAAACwkMCQ0JPQk+CTAMlxCeEFgRWRENEw==") \ No newline at end of file diff --git a/pr/617/docs/search.index/1bcb042010cd.js b/pr/617/docs/search.index/1bcb042010cd.js new file mode 100644 index 000000000..d54e635d9 --- /dev/null +++ b/pr/617/docs/search.index/1bcb042010cd.js @@ -0,0 +1 @@ +rn_("AQAAOzAAAAEAAAYAAgDGEQMA0RICAAEKAbBAFHYAAWX1OhQAAAEAAQABAAEA8wACaXP0NQwAAAEAUwABACOBAqDgAAAUXWV5") \ No newline at end of file diff --git a/pr/617/docs/search.index/1c486df13d0f.js b/pr/617/docs/search.index/1c486df13d0f.js deleted file mode 100644 index 415d47ea7..000000000 --- a/pr/617/docs/search.index/1c486df13d0f.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9BoRAAABAJQBAQAjgQKgYAAAE1FiaQEAAPQCEAAAAQD0AAkBI4ECsFATZAABZXMTgAKAB1sfUAFhcgGJArAgFIIAAaAwAAAUjGlwAQIA9DYTAAABAJAAAQABAwD0ahMAAAEAmAABAPOFAmJ1AQIA9IURAAABAH4BAQDzgAJpbLcBAACFoCAAABM+EgEMOjAAAAEAAAAAABYAEAAAAHwHCQgKCAsIDAgiDFsMXAxdDF4MKhDwEIITgxO+E0IUjxSQFJIUnxSgFMcUyxQ=") \ No newline at end of file diff --git a/pr/617/docs/search.index/1ca93b71869b.js b/pr/617/docs/search.index/1ca93b71869b.js deleted file mode 100644 index 98dd6b19d..000000000 --- a/pr/617/docs/search.index/1ca93b71869b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABoJAAABOGaPJOEwAAAQDzAgFk8h4HAAABAAECADswAAABAAAFAAEAoRQFAPMLAW07MAAAAQAABQABAF8UBQAjgAKwIAz8AAFhbwECAPMOCQAAUQMBAAGKArAAE/UAAbAAE/cAATQ2I4ECoDAAAAyIZXIBgAKwQBIFAAF1YgXOcPtjdbcGAACHsBAQYwABsFAUewABEVEJ9Z4EAAAKAMkAAQCrBw==") \ No newline at end of file diff --git a/pr/617/docs/search.index/1cbfb40fb014.js b/pr/617/docs/search.index/1cbfb40fb014.js deleted file mode 100644 index fbb4af44f..000000000 --- a/pr/617/docs/search.index/1cbfb40fb014.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsIATGgABsKATkwABaXQBgAKw4BSVAAGwQBBjAAFscgEGAPT5EAAAAQABAAEARwUAAIeg0AAAFG+wUBBhAAGgAAAAE5Ww8BRJAAEFkQPyBRAAAK8A") \ No newline at end of file diff --git a/pr/617/docs/search.index/1cc6eea1be2c.js b/pr/617/docs/search.index/1cc6eea1be2c.js new file mode 100644 index 000000000..7e9d25e62 --- /dev/null +++ b/pr/617/docs/search.index/1cc6eea1be2c.js @@ -0,0 +1 @@ +rn_("AQQBoEAAABQ0ZfKqFAAAAQABAQA7MAAAAQAABgABANEUBgDzBgFl9H8UAAABADMAAQAzAQOwYBOVAIphcnXzixAAAHoCAQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/1d2539aa0107.js b/pr/617/docs/search.index/1d2539aa0107.js new file mode 100644 index 000000000..c771a0456 --- /dev/null +++ b/pr/617/docs/search.index/1d2539aa0107.js @@ -0,0 +1 @@ +rn_("AYACsFARFgABoLAAABQMY3MBBADz1A8AAAEAAQABgAKgUAAAEOWgYAAAEVVpcgGAAqAgAAAMhaBgAAARVGVoAQEA84EMAAABAAEAE4ECoBAAAAyEYWMBgAKwEBPXAAGwMA0nAAFlcyMAAqBAAAAUdWV38pcUAAABAAEDADswAAABAAAFAAIAfQwDAGgRAQABBgD0IhMAAAEAmAEBABMAArBgEyQAAWZt8vIMAAABAAEBAPQhEQAAAQCwAAEAI4ACoLAAABPzZGcBggKgoAAAE1WgQAAAEIRvdvOAA2ducgGAAqBAAAATG6AwAAAP0Whstz8AAIqwQA/SAAE0YFc7MAAAAQAAGgAEAAsCAABtBA4AAgwJAN8MAAA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/1d44eb8c93b9.js b/pr/617/docs/search.index/1d44eb8c93b9.js deleted file mode 100644 index 64a170f96..000000000 --- a/pr/617/docs/search.index/1d44eb8c93b9.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9VMQAAABAAEAYQIBAAEEAaAAAAAQ8HP1Dw0AAAEAAQBAAwEAAQIA9bsRAAABAAEAAQABAAGAArBgEycAAaAgAAAREG95AQABoMAAABRMafR+EwAACAAcABsAAQYA9LgUAAABAAEAAQABAgD0DhEAAAEAmgABAAECAaBAAAATVXI7MAAAAQAABwACAAwRAQCjEQUA84ACYW9nAwAAhrCAE1QAWrCgE8sAAQwwBjswAAABAAAIAAMALQwAANYRAwC+EgMA84ICZWlHAQAAhbBwE9sAAaCAAAAUyKBAAAAQCLBABPQKCQAA6AYBALQDAYECoCAAABEKsFASzgABYW8BBAGgAAAAE/RzAQkRAABjBAOwUBF7AAFjZXP2EgkAAAEAAQAsAAEAcwkBAAA7MAAAAQAAJAAIAEwHBwBTEAIArBENANsRAgC2EgEAPBMBAI0TAQAkFAIAAQUA8y8TAAABAAEAAQEAOzAAAAEAAAUAAgARDAMABxIBAFMAA7AwFGcAAWVwcvT0DwAAAQDuAAEAAQADsEAULAABoUAAABScoFAAABH9YmNmARMHAADjgISxQBTFAAECMBABAYSgsAAAE+mwQBDHAAGwwBQYAAGgYAAAEaIUIAT2+gYAAGUJAQA6ATABFAEBAwA7MAAAAQAABQABAO4SBQABAAGgoAAAFFhy9P4PAAABAPYAAQDzgQJncwEHAPV1EwAAAQABAAEAAQABgAKgQAAAEXOwUBFaAAFjafOAAmFvAQABoCAAABGTcvNFDAAAhANsAUOAhKAAAAAUNaAwAAAUNYAFdgAdNQggIQEDArBAEzIAAaAwAAATiGV38p4QAAC7ABODAqBwAAATwmdvAQIA9RETAAABAPAAgAABABOAArAwE0MBSWlwAQYA878TAAABAAEA8wUCYnMBUBAAAAECAPQtEAAAAQABAAEAI4ACoGAAABKyYmP3XwEAzbBgEO4AAaCQAAASs8BOchU6MAAAAQAAAAAAGAAQAAAAGgIbAoUEhgSHBB0HVweKB4sHjAeNB44HjwcMDIUMFQ1LEF0QXhArEasRqxSsFMAUwRQ=") \ No newline at end of file diff --git a/pr/617/docs/search.index/1d4595df1786.js b/pr/617/docs/search.index/1d4595df1786.js deleted file mode 100644 index 17b142521..000000000 --- a/pr/617/docs/search.index/1d4595df1786.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYQCsAARjgABsAARkAABNDbzAgFh8oYMAAABAAEAAPbXEgAAAQBwAAEAUQEBAPMAAWTz5hIAAAEAqgBjgAOgIAAACQlhZW8=") \ No newline at end of file diff --git a/pr/617/docs/search.index/1d498b21b79f.js b/pr/617/docs/search.index/1d498b21b79f.js deleted file mode 100644 index d6b7b150a..000000000 --- a/pr/617/docs/search.index/1d498b21b79f.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9DYTAAABAJAAAQABAwD0ahMAAAEAmAABAPMCAmJ1OjAAAAEAAAAAABMAEAAAAFQHVQdWB+wP7Q+VEJYQthC3EE0RThEBEgISNBM1E0ETQhNRE8UTxhM=") \ No newline at end of file diff --git a/pr/617/docs/search.index/1d5569503590.js b/pr/617/docs/search.index/1d5569503590.js new file mode 100644 index 000000000..0d3c10fc3 --- /dev/null +++ b/pr/617/docs/search.index/1d5569503590.js @@ -0,0 +1 @@ +rn_("AYMCsCARlAEjoJAAABQ5bXABggKwoBQpAAGwoBQrAAFjcgECAaBAAAARk2UBSxAAADOAhLAgEREAAbAgD8oAAVAUAPOAAm9y") \ No newline at end of file diff --git a/pr/617/docs/search.index/1d607b1ec238.js b/pr/617/docs/search.index/1d607b1ec238.js new file mode 100644 index 000000000..265da520e --- /dev/null +++ b/pr/617/docs/search.index/1d607b1ec238.js @@ -0,0 +1 @@ +rn_("AQIAOzAAAAEAABEABQCyBQAAQwwAAPgMAAAaEA0AgBEAAAGEArDQFL8AAaCQAAATvGJoAYQCsAARjgABsAARkAABNDaXBAAAh6AgAAAR7aAwAAARVKAwAAASubBwD/IAActACDswAAABAAASARQAuQEAAKgFAAAICQAADgkAACsJAgAeDAAAXwwBAIYMAQD7DAAAKQ3uAN4PAADvDwEA5RAAAKIRAAABEwEA4hMFAP8TAACIFAAAoBQFAL0UAQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/1d879b498dfa.js b/pr/617/docs/search.index/1d879b498dfa.js new file mode 100644 index 000000000..f85bdfc3e --- /dev/null +++ b/pr/617/docs/search.index/1d879b498dfa.js @@ -0,0 +1 @@ +rn_("AQMBsDATbAABY/OWEQAAAQABAAEDAYATjgAQ+nM7MAAAAQAABQABAG4TBQAjggKgkAAAFHJpcwcBAIDFsFATNwABoKAAABPZsHATngABoNAAABR+gAEYCPOAAmRy") \ No newline at end of file diff --git a/pr/617/docs/search.index/1d93d8252622.js b/pr/617/docs/search.index/1d93d8252622.js deleted file mode 100644 index 8fbab90a6..000000000 --- a/pr/617/docs/search.index/1d93d8252622.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABsGAUHgABZQFYFAAAAQYA9EgTAAABAFEBAQABBAD0YhMAAAEAEQEBAPMAA2VyczowAAABAAAAAAAJABAAAAAzCTQJNQlVEZQRqhKrErgSFhQXFA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/1dbef74dd164.js b/pr/617/docs/search.index/1dbef74dd164.js new file mode 100644 index 000000000..b3921ecd5 --- /dev/null +++ b/pr/617/docs/search.index/1dbef74dd164.js @@ -0,0 +1 @@ +rn_("AQECsCANAgABsAAMegABZXPybQcAAAEAAQUA8y4TAAABAAEA8wQBcvT0DwAAAQDuAAEAE4ACoLAAABRuYXPzgAJiYw==") \ No newline at end of file diff --git a/pr/617/docs/search.index/1df29bac508a.js b/pr/617/docs/search.index/1df29bac508a.js deleted file mode 100644 index 7835ae949..000000000 --- a/pr/617/docs/search.index/1df29bac508a.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9BoRAAABAJQBAQAjgQKgYAAAE1FiaQEAAPQCEAAAAQD0AAkBI4ICsFATZAABZXMBiQKwIBSCAAGgMAAAFIxpcAECAPQ2EwAAAQCQAAEAAQMA9GoTAAABAJgAAQDzhQJidQECAPSFEQAAAQB+AQEA84ACaWy3AQAAhaAgAAATPhIBDDowAAABAAAAAAAUABAAAAB8BwkICggLCAwIIgxbDFwMXQxeDCoQ8BC+E0IUjxSQFJIUnxSgFMcUyxQBBQDzJBQAAAEAAQBDhgOwkBSpAAGgAAAAEstiZHcBgAKwAAkpAAGwIA0UA2hkbgEDAPMAEAAAAQAHAQECAPYrDAAAAQCRAwEA4QMBAAEAAPRHCQAAAQABAAEAAQAAOzAAAAEAAAYAAgCdEwAABRQFACOBArBAFH0AAWVz84ADZXh5AQAA9EMJAAABAAEAAQBDgAOgQAAAFKewIBMyAAFlcngBgAKgQAAADP6wQBArAAFnbCcaAADJoJAAABN/sMATyQABoBAAABGBoDAAABQbsEAM9gABAEegCzowAAABAAAAAAAJABAAAAB0BXUF/Az9DBIN/A9WEFcQKRMqEwEGAPQ0EwAAAQCQAAEA8wMBYvQYEQAAAQCUAQEAtwwAAImgcAAAErmwkBNiAAGwYBBOAAGgMAAAE4g4iRM6MAAAAQAAAAAALwAQAAAAyQXKBcsFzAU+Bz8HCwkMCQ0JPQk+CTAMRAwJDQoNCw0PDRANEQ34D/kPTBBNEFEQUhBvEHAQcRByEHMQdBB1EJcQnhC4EL0Q6BDpEFgRWREOE54TwhPpE4UUkRSzFMQU") \ No newline at end of file diff --git a/pr/617/docs/search.index/1e64655eaedf.js b/pr/617/docs/search.index/1e64655eaedf.js new file mode 100644 index 000000000..c016632c4 --- /dev/null +++ b/pr/617/docs/search.index/1e64655eaedf.js @@ -0,0 +1 @@ +rn_("AQEBsIATewABdfJUCQAAAQAjgAKwEAlSAAEzNgEGAPMBEQAAAQAVAROAArAAD8oAAW91AQIA81sHAAD1AQEAAQMA894SAAABAAEAI4ACsAAR1AABaGkBAAA7MAAAAQAAEwABAJAHEwAjgAKgMAAADO1hZfOAAnJ0E4ACsDATygABYWwBAAGggAAAFMdm8goJAACcCgEHAPV0EwAAAQABAAEAAQAjgAKgUAAAEXNhb/MCAmZzOzAAAAEAAAgABABXBwAAigcFABUNAACrEQAAE4ACoEAAAAwfYW8BAwGwQBSMAAF08ukTAAABACOFAqBAAAAUGWJzIwADsEAREwABoFAAABGAYWJm9xcJAAABAEgAeAcBAGIAAQATgQKgUAAAEg9ndAGAAqBAAAAR/KAgAAAT+GFpAQIA9E0JAAABAAgIAQABAgDzYQwAAAEAAQATgAOwEAlLAAGwMBQrAAFpdXkBAgA7MAAAAQAACwACAOITBQCgFAUA8wgBbTswAAABAAAMAAMAfREAAPoSBQBeFAUAAYECoIAAABLnoHAAABQ/YXPzgAJucrfbAQCNoUAAABSboCAAAAlPoFAAABASN5UPOzAAAAEAADcCBQD+AQAAJAIzARMHAAB/BwAAEgoAAQ==") \ No newline at end of file diff --git a/pr/617/docs/search.index/1ed131c81757.js b/pr/617/docs/search.index/1ed131c81757.js new file mode 100644 index 000000000..e63dc38b5 --- /dev/null +++ b/pr/617/docs/search.index/1ed131c81757.js @@ -0,0 +1 @@ +rn_("AQAA9EUHAAABAAEAAQABhQKwcBQmAAGwMBN5AAFlcxMAA7BAD+gAAaBgAAARnGJlczswAAABAAASAAQArwUBANEGCgBhEAEAuRADAAECAPO9BQAAUAoBAAEDAPXgDwAAnQABAAQAAQABCgGgQAAAFHVl8pcUAAABACMABKAwAAARLKCQAAATobBgEssAATBjaGn1EhIAAJcBAQABAAEA84ACZHLnBAAAh7AQCQYDQrAgDBUBC6AwAAAQBRJBODswAAABAAASAAYAjQACAKAFAADRBQMApgcFAOsHAwAfDAAA") \ No newline at end of file diff --git a/pr/617/docs/search.index/1ee5b86102ad.js b/pr/617/docs/search.index/1ee5b86102ad.js new file mode 100644 index 000000000..63a950588 --- /dev/null +++ b/pr/617/docs/search.index/1ee5b86102ad.js @@ -0,0 +1 @@ +rn_("AQMBoAAAABNbc/TiDwAA8gMBAAQAAQECsGAUTgABsEAUjAABZHQ7MAAAAQAADQAGAA4TAwB5EwEA6RMBAAAUAQCBFAEAqBQBAAEBA7BwEhAAAbCAEusAAYATjgAQ+m1uczswAAABAAALAAIAAAkFAG4TBQCjgISwsBPXAAGxcBTLAAEIQIIBgAKwMBS/AAGxABROAAFjbmcBAACFHMbuob4ssDATQgFJEYEIOzAAAAEAAAYAAgCcEwAABBQFAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/1f32f40ccbcb.js b/pr/617/docs/search.index/1f32f40ccbcb.js new file mode 100644 index 000000000..460466545 --- /dev/null +++ b/pr/617/docs/search.index/1f32f40ccbcb.js @@ -0,0 +1 @@ +rn_("AQEBoEAAABGZc/VGDAAAKQVLAQEAAQATggKgIAAADR1lcgECAPT+DwAAAQD2AAEAI4ECoBAAAAxFZWkTgAKg8AAAFDhucjMAA7AwCTYAAWVvdAHtEQAA") \ No newline at end of file diff --git a/pr/617/docs/search.index/1f8e6a0bbf2b.js b/pr/617/docs/search.index/1f8e6a0bbf2b.js deleted file mode 100644 index ccc21f221..000000000 --- a/pr/617/docs/search.index/1f8e6a0bbf2b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsHAS7AABoLAAABQpZW8jAQOwcBIRAAGAE48AEPptbnM7MAAAAQAAGgAGAHQHAQAACQUAGQ0BAF4RCQCcEQAAbxMFAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/1fa23924c3c7.js b/pr/617/docs/search.index/1fa23924c3c7.js new file mode 100644 index 000000000..fea742f05 --- /dev/null +++ b/pr/617/docs/search.index/1fa23924c3c7.js @@ -0,0 +1 @@ +rn_("AQsBoEAAABRKZfJ4FAAAAQABBQD0ExIAAAEAAQABAAGDArAAEP8AAbBQE4wAAWR3E4ACoKAAABQAZXYBAgD0GhEAAAEAkwEBAAEAAPQCEAAAAQD0AAgBI4ICsFATYwABZXMBAgD0NRMAAAEAkAABAAEDAPRpEwAAAQCYAAEA84UCYnUBAgD0hREAAAEAfQEBAPOAAmls8wADYnR1OjAAAAEAAAAAAA8AEAAAAHwHCQgKCAsIDAgqEPAQvRNBFI4UjxSRFJ4UnxTGFMoUAQwA8yMUAAABAAEAAYACsAAJKQABoCAAAA0UZG4BBgD0MxMAAAEAkAABAPMDAWL0GBEAAAEAkwEBAJcGAACHsEAQAAABoHAAABK4sJATYQABOAkDOjAAAAEAAAAAACQAEAAAAMkFygXLBcwFPgc/B0QMCQ0KDQsNDw0QDREN+A/5D0wQTRBREFIQbxBwEHEQchBzEHQQdRC4EL0Q6BDpEJ0TwRPoE4QUkBSyFMMUAQMA9EcTAAABAFEBAQDzgAJjbbcCAIDGoDAAAAkHsYAU0wABQIUECA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/2003f002ccdb.js b/pr/617/docs/search.index/2003f002ccdb.js new file mode 100644 index 000000000..28b0fb4e7 --- /dev/null +++ b/pr/617/docs/search.index/2003f002ccdb.js @@ -0,0 +1 @@ +rn_("AQ0A9e4TAAABAAEAegABAAEHAPPWEwAAbAABACOAAqEQAAAUKG5yAQcA9MYRAAABAAEAAQAzAASgIAAACTiw4BQdAAExZWlyOjAAAAEAAAAAAAkAEAAAAGEEYgRYBxoJfQx+DH8MgAxoEWkRAQIA9BoRAAABAJMBAQABAwD0hREAAAEAfQEBAPMAAmJ1ASoQAAABAwD0GBEAAAEAkwEBAPOEAmRzAQIA8xwJAAABAFgA84ACaXQBGADz1RQAAAEAAQAjgQKwwBPsAAFjbQENAbBAFHYAAWX1OhQAAAEAAQABAAEAE4ICsAARMAABa3MBBAD0vhAAAAEAAQABAAGBAqAgAAARK7BAE4EAAWVp84ICZXQBBAGwYBLWAAFy9DIMAAABAPUDAQDzgANhZW8BCQD2ARMAAAEA/QCJADUAAQATgAKwYBDYAAFkbgEGADswAAABAAAFAAIAAxEDAJoTAQABAwGgcAAAEz1n9GUHAAABAA0CAQABCQDzjhQAAAEAOwABgQKwgBSeAAGgYAAAFMZkcxcBAIiFsNAUyAABoEAAABRxsGAUQQBQApAKAQQA9NoTAAABAEsAAQBTgAOwkBTBAAFlaG8BAAGgQAAAEZJlAdoPAAAjgQKwUBE9AAFrdHfsAwCOoZAAABS2sLATSwABoKAAABMAsVAUvwAB/cEPOzAAAAEAACEACACCAAAAiAADAHYFAQBBBwcAQwkHAFMMBwBjEAEAqxIAAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/20727d2fe054.js b/pr/617/docs/search.index/20727d2fe054.js deleted file mode 100644 index e6c092eb6..000000000 --- a/pr/617/docs/search.index/20727d2fe054.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQcAOzAAAAEAAI4AAgAHEQAAGRKNAAEDADswAAABAACNAAEAGA6NAAECADswAAABAAAHAAEAUwwHAPMAA2JpdDswAAABAACOAAIAogQAAN4EjQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/20ef81a14d6e.js b/pr/617/docs/search.index/20ef81a14d6e.js new file mode 100644 index 000000000..bf89e2258 --- /dev/null +++ b/pr/617/docs/search.index/20ef81a14d6e.js @@ -0,0 +1 @@ +rn_("AQACoCAAABOgsDAR+gABaXTyBxMAAAEAAQMAOzAAAAEAAAYAAQB0EQYAAQgA9AoUAAABAIsABQATAQOwkBRzAAGwUBNPAWdnbHA7MAAAAQAAEgAJAHkQAgD9EQEAPhMBAF8TAQCWEwEA+xMBADUUAQBEFAEAhRQBAAGAArAwEfYAAbAwEfgAAXJ2AQQBsAASxwABZDswAAABAAAFAAIA8hEDAPYSAQC3BQABh7BQEskBFLBgE00AAZQJAzowAAABAAAAAAALABAAAADvDPAM8QysDq0OHBGeEZ8RoBGhEUUTfhMjgAKgEAAADO5ubwEDAPN2EAAAAQABACOAAqAAAAAMIGRyYwGEoFAAABGboDAAABC0FAEQ8qQHAAABABOAArBgEusAAWNnE4ADoLAAABQooEAAAA6rZW9z") \ No newline at end of file diff --git a/pr/617/docs/search.index/218e52b0339e.js b/pr/617/docs/search.index/218e52b0339e.js deleted file mode 100644 index 06f40cb3f..000000000 --- a/pr/617/docs/search.index/218e52b0339e.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9MUPAAABAAEAAQDzAgFzAUIRAAABAAKwgBSfAAGgYAAAFMdkc/IGCQAAQgOTgISwwBPbAAGgEAAAEe4AwUA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/2206d66e8e6a.js b/pr/617/docs/search.index/2206d66e8e6a.js deleted file mode 100644 index 19506b6f9..000000000 --- a/pr/617/docs/search.index/2206d66e8e6a.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYICsCARVgABsDAR1AABZXABBQD09g8AAAEA7wABAFOAA7CQEgUAAWFpcgGBArCgFCoAAbCgFCwAAWNyxwAAAIWwUA0HAAGgIAAACX6gIAAADDA8AAE6MAAAAQAAAAAACgAQAAAAtgG3AXwEBQcvCTAJjRCOEAERAhEYEg==") \ No newline at end of file diff --git a/pr/617/docs/search.index/22934d7a650c.js b/pr/617/docs/search.index/22934d7a650c.js new file mode 100644 index 000000000..de3c3906a --- /dev/null +++ b/pr/617/docs/search.index/22934d7a650c.js @@ -0,0 +1 @@ +rn_("AQAA9BoRAAABAJMBAQAjgQKgYAAAE1BiaQEAAPQCEAAAAQD0AAgBI4ECsFATYwABZXMTgAKAB1sfUAFhcgGJArAgFIEAAaAwAAAUi2lwAQIA9DUTAAABAJAAAQABAwD0aRMAAAEAmAABAPOFAmJ1AQIA9IURAAABAH0BAQDzgAJpbLcBAACFoCAAABM9EgEMOjAAAAEAAAAAABYAEAAAAHwHCQgKCAsIDAgiDFsMXAxdDF4MKhDwEIETghO9E0EUjhSPFJEUnhSfFMYUyhQ=") \ No newline at end of file diff --git a/pr/617/docs/search.index/22d494009ab1.js b/pr/617/docs/search.index/22d494009ab1.js new file mode 100644 index 000000000..959ba6607 --- /dev/null +++ b/pr/617/docs/search.index/22d494009ab1.js @@ -0,0 +1 @@ +rn_("AQIBsEAUjAABdDswAAABAAANAAYADhMDAHkTAQDpEwEAABQBAIEUAQCoFAEAE4ACsDATQgFJaXABBQDzvhMAAAEAAQATgAKgMAAAFBlleVMCA7BwFCYAAWJlcwFQEAAAEwACoCAAAAWoZm31gwAAAOwGAQDbAQEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/2303707d866b.js b/pr/617/docs/search.index/2303707d866b.js deleted file mode 100644 index e3ca5e070..000000000 --- a/pr/617/docs/search.index/2303707d866b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQkBoIAAABTIZgGnEwAAAQIA9esQAAABAAEAXwIBAPMAAml1OjAAAAEAAAAAAAcAEAAAAMYQARPtE+4THhQfFEAUQRQBCAD1UxQAAAEAAQABAAEA8wIBZvWqEAAAAQABAAEAAQATgAKwoBSdAAFydPMCAmFz89oQAAABAAEAAQQBsEAUdwABZfU7FAAAAQABAAEAAQABAgA7MAAAAQAACwACAOMTBQChFAUA8wMBbTswAAABAAANAAQA4AwAAH0RAAD7EgUAXxQFAPOBAmNl84ACYWU=") \ No newline at end of file diff --git a/pr/617/docs/search.index/236fc2dd2763.js b/pr/617/docs/search.index/236fc2dd2763.js deleted file mode 100644 index cc659cdb9..000000000 --- a/pr/617/docs/search.index/236fc2dd2763.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQA894RAAABAAEAAYICsEAS2QABsEAS2wABNDYBCwDz1xMAAGwAAQABBgD0xRQAAAEACgABAPMCAXIBIRQAAPMCAWU7MAAAAQAABQACAI8QAwBAEQEAE4ACoFAAABMOZHTzAIQRAQH3/xAAAAEAbgAdAAEAAQA+AQ==") \ No newline at end of file diff --git a/pr/617/docs/search.index/23798fef204d.js b/pr/617/docs/search.index/23798fef204d.js deleted file mode 100644 index f453aae60..000000000 --- a/pr/617/docs/search.index/23798fef204d.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUAOzAAAAEAAAkBAgD0DAEArg4HAQEBAaAAAAATXHP1xwQAABsL8wMBAAQAAQECoGAAABPdoOAAABQ6ZXQ7MAAAAQAACAABAM4ECAABgAOwIBKoAAGwsBLiAAGwsBLkAAFiY3PzgARjbHR2") \ No newline at end of file diff --git a/pr/617/docs/search.index/23e4988aecac.js b/pr/617/docs/search.index/23e4988aecac.js new file mode 100644 index 000000000..24b7f4d13 --- /dev/null +++ b/pr/617/docs/search.index/23e4988aecac.js @@ -0,0 +1 @@ +rn_("AQkBoEAAABR1ZfKXFAAAAQABgAKwAAz5AAGggAAAE9lkcAEDAaAAAAAQSnPy2A8AAAEAAQIDsGATVgABsNAUeAABoIAAABP4aXJzAYMUAAD3AACAhaEwAAAUlpBJAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/23f7516e5703.js b/pr/617/docs/search.index/23f7516e5703.js deleted file mode 100644 index e4f2d9217..000000000 --- a/pr/617/docs/search.index/23f7516e5703.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEBoEAAABR2ZfKYFAAAAQABgAOgMAAAErOw4BRRAAGwkBSaAAFhY22HAQCAhrAQET0AAbAQEVwAAaCgAAAUhKDwAAAUpzFJAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/243d6718115c.js b/pr/617/docs/search.index/243d6718115c.js deleted file mode 100644 index 41c55e583..000000000 --- a/pr/617/docs/search.index/243d6718115c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIBoQAAABSRZPJqBwAADwIBBgGgMAAAEgB0Ab4TAAABAwGwUBMKAAFl9SMJAABVAwEAEAQBAAEBADswAAABAAAFAAEAtwUFAAEBAaEAAAAUhWTytgUAAEgClwcAAIewoBMIAAGg4AAAFFgegArzYwQAAAEAAQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/2488a011453a.js b/pr/617/docs/search.index/2488a011453a.js new file mode 100644 index 000000000..376f6575b --- /dev/null +++ b/pr/617/docs/search.index/2488a011453a.js @@ -0,0 +1 @@ +rn_("AQACoCAAABOgsDAR+gABaXTyBxMAAAEAAQMAOzAAAAEAAAYAAQB0EQYAAQgA9AoUAAABAIsABQATAQOwkBRzAAGwUBNPAWdnbHA7MAAAAQAAEgAJAHkQAgD9EQEAPhMBAF8TAQCWEwEA+xMBADUUAQBEFAEAhRQBAAGAArAwEfYAAbAwEfgAAXJ2AQQBsAASxwABZDswAAABAAAFAAIA8hEDAPYSAQC3BQABh7BQEskBFLBgE00AAZQJAzowAAABAAAAAAALABAAAADvDPAM8QysDq0OHBGeEZ8RoBGhEUUTfhMjgAKgEAAADO5ubwEDAPN2EAAAAQABACOAAqAAAAAMIGRyYwCEoFAAABGboDAAABC0FAEQ8qQHAAABAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/24b2ddc4a0dd.js b/pr/617/docs/search.index/24b2ddc4a0dd.js deleted file mode 100644 index 29bc1768e..000000000 --- a/pr/617/docs/search.index/24b2ddc4a0dd.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIAOzAAAAEAAO8AAgAeDAAAKQ3uAPMAAW07MAAAAQAA7wACAKcFAADVBe4AAQMAOzAAAAEAAAgAAgC2DwYAbRMBAPMAAWM7MAAAAQAAVwAnAKkECwBzBQAAHgkBADgMAQBADAIAAA0AAAwNAgAeDQEA4A8AAOUPAQDoDwEA7g8AAHYQAgB9EAEAghABAKcQAAAfEQAAJREAAHERAQB+EQEAkhEBAJYRAgD3EQEAChICABASAADHEgAA0BIBANUSAQAdEwEALRMBADITAQBSEwEAYhMDAGgTAQDJEwEA3RMAAEsUAAB0FAQAfRQCAPOAAnJ3AQkA848UAAABADsAAYACoEAAABNssCAUKgABaGkBDAGgoAAAFNl38skUAAABAAEBAPMKEAAAAQABAPOAAnR1AYECsIAUnwABoGAAABTHZHM3AwCBhqBAAAAUcrBgFEIAUAKQDgGIArAAE/UAAbAAE/cAATQ2M4ADoDAAABGVZG5zYwADoBAAAATMYWVvAYcAAAA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/24df159f5f12.js b/pr/617/docs/search.index/24df159f5f12.js new file mode 100644 index 000000000..d7cc6b5dc --- /dev/null +++ b/pr/617/docs/search.index/24df159f5f12.js @@ -0,0 +1 @@ +rn_("AQQA9hsNAAABAD4DAQAuAQEAAQoA8+USAAABAKoAAQEA9y8JAAABAF0HAQBzAAEAFQEjgAKwYBP9AAFpbwECAPTYDAAAAQBNBgEAAQUA9BMUAAABAFAAAQD3CAAAiLCQE8oAAaAgAAAM2rCQEgoAAQTcAzowAAABAAAAAAAIABAAAACZBSAJIQl0DHUMERESERcTGBM=") \ No newline at end of file diff --git a/pr/617/docs/search.index/250ec7c859d8.js b/pr/617/docs/search.index/250ec7c859d8.js new file mode 100644 index 000000000..729d00837 --- /dev/null +++ b/pr/617/docs/search.index/250ec7c859d8.js @@ -0,0 +1 @@ +rn_("AQMBgBOOABD6czswAAABAAAFAAEAbhMFACOCAqCQAAAUcmlzAQEA9DUTAAABAJAAAQAjgAKwoBQbAAFjbgGEArCgFCkAAbCgFCsAAWNyAQIAOzAAAAEAAAYAAQBvEAYAE4ICoCAAABQMZXUBAgDz3hEAAAEAAQABgAKwIBROAAGgYAAAE51vdmOBA6AQAAATVWFjcwEDADswAAABAAAFAAEA5wwFAAEBAPMrCQAAAQABAAEBAPQOEQAAAQCaAAEAU4ADoMAAABR+bnB1AYACsDAQKwABoWAAABS2YWUBgQKwQBLYAAGwQBLaAAE0NgEFAaBQAAAUN2jyyxIAAAEAAQUA9IUQAAABALMAAQBzgISwMBOBAAEBoAQBCADzfRMAAAgAHAATgAKwQBICAAFhZQEKAPPWEwAAbAABAAEAAaAgAAAQp3IBcBEAAAEAAPTaEwAAAQBLAAEApwEAgIWgMAAAEQugEAAAESsJICEBCQGgQAAAFHVl8pcUAAABAEOAhKAgAAAUg6CQAAAT2aEwAAAUlhBJAAECAPRpEwAAAQCYAAEAE4ACsEATNwABaXkBAQKgMAAAEw2gQAAAE1RncjswAAABAAAHAAIADBEBAKMRBQABgAKwABP0AAGwABP2AAE0NgEGAPTEFAAAAQAKAAEA8wICcnYBIBQAAPMCAWU7MAAAAQAABQACAI8QAwBAEQEA84ACZHTnrT8AEqAwAAARLKBAAAAM/rDwFMEAAbBwE54AATBhYmNlZ2hpa2xub3Byc3R1djswAAABAAAbAUUAxAQAAK0FAQC1BQAAtwUFAMwGAAAuBwAAVAcCAGQHAAB2BwUAfQcAAOIHAAAHCAEADwkBABUJAQAiCQAAgAkBAIQJjQARDAMAIAwAACQMAwAxDAAAagwAAHcMAADBDwEA7A8BAAQQAABoEAAAlRABAKkQAAC2EAEA/xABAAMRAwAWEQEATREBAG4RAACLEQIAuhEAANoRAAD8EQAAABIBAAYSAQASEgQAuBIAAMcSAQDKEgAA1hIBAOgSAgAeEwMAMxMBAEATAQBHEwEATRMBAFATAABTEwAAZRMAAHsTAQCaEwEAqRMEAMQTAQDXEwEA2hMBAOATAQDrEwAAJhQCAC0UBwBMFAEAWBQAAG8UAQCZFAEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/2593c24072ce.js b/pr/617/docs/search.index/2593c24072ce.js deleted file mode 100644 index 808a848d3..000000000 --- a/pr/617/docs/search.index/2593c24072ce.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBoAAAABNcc/TiDwAA8wMBAAQAAQECsGAUTwABsEAUjQABZHQ7MAAAAQAADQAGAA8TAwB6EwEA6hMBAAEUAQCCFAEAqRQBAAEBA7BwEhEAAbCAEuwAAYATjwAQ+m1uczswAAABAAALAAIAAAkFAG8TBQCjgISwsBPYAAGxcBTMAAEIQIIBgAKwMBTAAAGxABRPAAFjbmcBAACFQkJEZkOOsDATQwFJEYEIOzAAAAEAAAYAAgCdEwAABRQFAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/25a024fd26b3.js b/pr/617/docs/search.index/25a024fd26b3.js new file mode 100644 index 000000000..edf0fe0e5 --- /dev/null +++ b/pr/617/docs/search.index/25a024fd26b3.js @@ -0,0 +1 @@ +rn_("AYACsHAR7gABsCAP+gDwaHkBBQD0Fw0AAAEAQAMBAAEAAPRxDAAAAQABAG4AAQIAOjAAAAEAAAAAAAkAEAAAAO8P8A/lEKIRARMCE/8TiBS9FL4UAQMA8ysJAAABAAEA1wMAAIagQAAAEgkYQAc6MAAAAQAAAAAAFwAQAAAAcgdzBwEIOAlsCW0JFg3cD90P/Q+oEK8QsBCxELIQ8RDyECkRKhGmEv0T/hMpFCoU") \ No newline at end of file diff --git a/pr/617/docs/search.index/272a63220c2a.js b/pr/617/docs/search.index/272a63220c2a.js deleted file mode 100644 index 666f09be1..000000000 --- a/pr/617/docs/search.index/272a63220c2a.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYICoFAAABL0oQAAABSoY3QjAAKwQA/lAAFldTswAAABAAA3ABYAzwYBAOUMAQAHDQEA6g8BAGkQBQCfEAUAHREBAHsRAQANEgIA4hIDABMTBABEEwEAShMBAFkTAQDfEwEA8hMBABAUAQAUFAEAOhQAAGUUAQBpFAEAiBQAAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/2794b2ea6f8a.js b/pr/617/docs/search.index/2794b2ea6f8a.js deleted file mode 100644 index 45b99e776..000000000 --- a/pr/617/docs/search.index/2794b2ea6f8a.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMAOzAAAAEAAAUAAQDuEgUAAQQBoFAAABQ4aPLMEgAAAQABAAKwQBPJAAGgoAAAFFhlcjowAAABAAAAAAAHABAAAADNBs4GawdsB/4P/w/1EPYQ84ADZ2lz") \ No newline at end of file diff --git a/pr/617/docs/search.index/27966aefced8.js b/pr/617/docs/search.index/27966aefced8.js new file mode 100644 index 000000000..880838b26 --- /dev/null +++ b/pr/617/docs/search.index/27966aefced8.js @@ -0,0 +1 @@ +rn_("AQQBoEAAABQ0ZfKqFAAAAQABAQA7MAAAAQAABgABANEUBgDzBgFl9H8UAAABADMAAQAzAwOwYBOVAIphcnXzixAAAHoCAQAjgAKAEYsAEAFkcg==") \ No newline at end of file diff --git a/pr/617/docs/search.index/27ee8910beb2.js b/pr/617/docs/search.index/27ee8910beb2.js deleted file mode 100644 index 884de0694..000000000 --- a/pr/617/docs/search.index/27ee8910beb2.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUAOzAAAAEAAAcAAwDbEQIAjRMBACQUAgABBwD2sRMAAAEArABYABgAAQABBADz3hEAAAEAAQAjAAKgYAAAEw5hcvP/EAAAAQDLAQGAAqEQAAAUs7CAFKkAAWl1dwgAAIixUBTQAAGgAAAAEN6wcBPtAAGhMAAAFMQKQRvyawkAAEoHAYECoFAAABEkoKAAABNfa3ABAwA7MAAAAQAACQECAPQMAQCuDgcB84ACbHTzgAJhZQ==") \ No newline at end of file diff --git a/pr/617/docs/search.index/27ff847ec176.js b/pr/617/docs/search.index/27ff847ec176.js new file mode 100644 index 000000000..738e5e9d8 --- /dev/null +++ b/pr/617/docs/search.index/27ff847ec176.js @@ -0,0 +1 @@ +rn_("AQAA9L4QAAABAAEAAQDzAgFz9VYJAAABAAEAAQCOBgEGAPQTEgAAAQABAAEAAQQBoEAAABQ0ZfKqFAAAAQABAQA7MAAAAQAABgABANEUBgDzBgFl9H8UAAABADMAAQAzBgOwYBOVAIphcnXzixAAAHoCAQDzgAJhZQEDAPTGEQAAAQABAAEAAQIA9NwPAAABAEwEAQABggKgEAAAE2uwQBMLAAFhcwGAA7AAB4IAAaAAAAAMKbBwEJwAAW5yc+cHAACHoHAAABISkQVBOjAAAAEAAAAAAAwAEAAAAOcB6AHpAeoBhAfvB/AHXwlmDBcQGBAZECwR") \ No newline at end of file diff --git a/pr/617/docs/search.index/27ffa050a6cf.js b/pr/617/docs/search.index/27ffa050a6cf.js deleted file mode 100644 index 9446986a3..000000000 --- a/pr/617/docs/search.index/27ffa050a6cf.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYQCoCAAABE3oDAAABGdaXQBAQA7MAAAAQAAjgACAH0HAACECY0A8wACZWz1uAQAAMkC0wlUAgEAAQIAOzAAAAEAAO8AAgAeDAAAKQ3uAMcAAACFsLATUgABsBAT2AABsDANJwABFDACOzAAAAEAABsBCwDgAQYApwUAANUF7gBMBwcAUxACAKwRDQDbEQIAthIBADwTAQCNEwEAJBQCAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/53b036251557.js b/pr/617/docs/search.index/2867eb6253a6.js similarity index 55% rename from pr/617/docs/search.index/53b036251557.js rename to pr/617/docs/search.index/2867eb6253a6.js index e531a0982..0fb46dfb9 100644 --- a/pr/617/docs/search.index/53b036251557.js +++ b/pr/617/docs/search.index/2867eb6253a6.js @@ -1 +1 @@ -rn_("AYECsDAQTgABsBATywABc3QBAwDz3xIAAAEAAQAjgAKwABHUAAFoaQEAADswAAABAAATAAEAkAcTACOAAqAwAAAM7WFl84ACcnTzAAJhbDswAAABAAAoAAMAmwATAK8BAAA0EBMA") \ No newline at end of file +rn_("AYECsDAQTgABsBATygABc3QBAwDz3hIAAAEAAQAjgAKwABHUAAFoaQEAADswAAABAAATAAEAkAcTACOAAqAwAAAM7WFl84ACcnTzAAJhbDswAAABAAAoAAMAmwATAK8BAAA0EBMA") \ No newline at end of file diff --git a/pr/617/docs/search.index/28ae7e2c3eff.js b/pr/617/docs/search.index/28ae7e2c3eff.js new file mode 100644 index 000000000..fbf2795b7 --- /dev/null +++ b/pr/617/docs/search.index/28ae7e2c3eff.js @@ -0,0 +1 @@ +rn_("AQQBoEAAABGTZQFLEAAAAQgA9NYRAAABAAEAAQABAwGgQAAAEZJlAdoPAAABAgDzQAwAAAEAAQAjAAKwIAwuAAFrcvb7BgAAAQAZCu0AAQA0AvOAAnJ4AQMBsIAUbwABdjswAAABAAALAAMAJhEBAOERBQDQEwMAI4ACsGAQlwDBYXIBAwGgAAAAEEpz8tgPAAABACOAAqCwAAAT+GVoAQMBoEAAABElZfIlCQAAAQDXCwAAiqAgAAAHcLCwE0cAAaCAAAAR2qAAAAATVJ8ITDowAAABAAAAAAAKABAAAAAMAn0EfgR/BBgMGQyBDIIMgwxVE2sT") \ No newline at end of file diff --git a/pr/617/docs/search.index/28b28f3de641.js b/pr/617/docs/search.index/28b28f3de641.js deleted file mode 100644 index 61f3486bd..000000000 --- a/pr/617/docs/search.index/28b28f3de641.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAAOzAAAAEAAAYAAgCdEwAABRQFACOIArBAFH0AAWVzAQQBsDATbQABY/OWEQAAAQABAAEFADswAAABAAAIAAMADRICABMTBAA6FAAA8wEDY2Vw89sMAAABAAEAAQIA9igJAABXABQHAQC3AAEA84ACbnI=") \ No newline at end of file diff --git a/pr/617/docs/search.index/28c932d1d3a3.js b/pr/617/docs/search.index/28c932d1d3a3.js new file mode 100644 index 000000000..dd1aae0da --- /dev/null +++ b/pr/617/docs/search.index/28c932d1d3a3.js @@ -0,0 +1 @@ +rn_("AQABsIAT+QABdAEkCQAAAQkA894SAAABAAEA84ACY2cBgwKgkAAAFEagkAAAFEcyMwGAAqBQAAAUcqCAAAATkWNl8wACc3TySgcAAAEAAQABoLAAABPcYvNmEwAALgEBACOAAqAAAAATZWRyI4ACoFAAABQ4Y2XzgAJpeQEGAPVPEQAAAQABAAEAAQABAQGwQBR2AAFl9ToUAAABAAEAAQABAPcAAICFoBAAAAwhKWAA") \ No newline at end of file diff --git a/pr/617/docs/search.index/28ca64a40b02.js b/pr/617/docs/search.index/28ca64a40b02.js deleted file mode 100644 index 2241b97c3..000000000 --- a/pr/617/docs/search.index/28ca64a40b02.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQACoCAAABOhsDAR+wABaXTyCBMAAAEAAQMAOzAAAAEAAAYAAQB0EQYAAQgA9AsUAAABAIsABQATAQOwkBR0AAGwUBNQAWdnbHA7MAAAAQAAEgAJAHkQAgD+EQEAPxMBAGATAQCXEwEA/BMBADYUAQBFFAEAhhQBAAGAArAwEfcAAbAwEfkAAXJ2AQQBsAASyAABZDswAAABAAAFAAIA8xEDAPcSAQC3BQABh7BQEsoBFLBgE04AAZQJAzowAAABAAAAAAALABAAAADvDPAM8QysDq0OHBGeEZ8RoBGhEUYTfxMjgAKgEAAADO5ubwEDAPN2EAAAAQABACOAAqAAAAAMIGRyYwGEoFAAABGboDAAABC0FAEQ8qQHAAABABOAArBgEuwAAWNnE4ADoLAAABQpoEAAAA6rZW9z") \ No newline at end of file diff --git a/pr/617/docs/search.index/2922cc5fe9b0.js b/pr/617/docs/search.index/2922cc5fe9b0.js deleted file mode 100644 index 75742a502..000000000 --- a/pr/617/docs/search.index/2922cc5fe9b0.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsAARjgABsAARkAABNDYBBAGgoAAAFNl38skUAAABAAELADswAAABAAAGAAEALhQGACOAAqCAAAAUWWVvYwIDoGAAABPsZW5wATQMAADzAAJhdgHpBgAA") \ No newline at end of file diff --git a/pr/617/docs/search.index/292ab4b4dd12.js b/pr/617/docs/search.index/292ab4b4dd12.js deleted file mode 100644 index b5279e28e..000000000 --- a/pr/617/docs/search.index/292ab4b4dd12.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEA9HEHAACACOEAAQABBQD0WhQAAAEAAQABAAEFAPRKEwAAAQCUAAEA8wICY3X1ZhAAAAEAxgABAAEAYwAEoDAAAAxHoHAAABHyZW5zdvOEEAAAnwAkAg==") \ No newline at end of file diff --git a/pr/617/docs/search.index/2935d9f9f689.js b/pr/617/docs/search.index/2935d9f9f689.js new file mode 100644 index 000000000..74fadb01e --- /dev/null +++ b/pr/617/docs/search.index/2935d9f9f689.js @@ -0,0 +1 @@ +rn_("AYACsRAUqgABsBAMZAABZW8BBQA7MAAAAQAADQABAKwRDQABAgDzFxAAAAEAAQABAgD0ExAAAAEAAQABAAEDADswAAABAAAIAAEAyRAIAIcHAACHsKAT1AABsNAUbAABoFAAABQ3jBQROzAAAAEAAAYAAwBgBwAA+RADAMsSAQDzgQJsdPMAAWk7MAAAAQAAjwADAJ0EAAAHEQAAGBKNAAEFAPOOFAAAAQA7AAEAAaBAAAARC2XzFQkAAAEAogkTgAKgMAAAD+5kcvMAAWXz9wYAAAEAAQDzgAJodAEBADswAAABAACOAAIABxEAABgSjQATgAKgUAAAEDFpdAEIAPQKFAAAAQCLAAUAEwCEsPAUkAAzsJAUcwABsFATTwFnQIkAOzAAAAEAABIACQB5EAIA/REBAD4TAQBfEwEAlhMBAPsTAQA1FAEARBQBAIUUAQDXAQAAhrCAEcQAAaBAAAAJOAgwJjowAAABAAAAAAANABAAAACSAJMAagd5CRUMIA0aERsRrhKvEjUTNhPGE8cT") \ No newline at end of file diff --git a/pr/617/docs/search.index/298a2d10a108.js b/pr/617/docs/search.index/298a2d10a108.js new file mode 100644 index 000000000..04a571750 --- /dev/null +++ b/pr/617/docs/search.index/298a2d10a108.js @@ -0,0 +1 @@ +rn_("AQMAOzAAAAEAAEMAAgAuCQAAlQxCAAEFAPTUEAAAAQABAAEAE4ACsLATsgABYWwBBAD0xQ8AAAEAAQABAHMABKAQAAAQiGJwcnY7MAAAAQAACgACAHQRBgDKEQMAAYACsIARIwIjsOAUGwABaXIBAwGwQBFxAAFlAaYOAAABBQA7MAAAAQAABgABAG8QBgABAwA7MAAAAQAABQABAOcMBQDzAAJwczowAAABAAAAAAANABAAAACtBa4FzAYiCWoMdwwWERcRxxLIEh4THxMgEyETVwAAAIWwUBDGAjqwUA/kAM+gQAAAEZxIgAM7MAAAAQAADwAGAKMEAABsBQMA0Q8AAPIRAwDCEgMA9hIBANMAhKBgAAAM+xAIBvOwAQAAAQApCw==") \ No newline at end of file diff --git a/pr/617/docs/search.index/2a4f58d36642.js b/pr/617/docs/search.index/2a4f58d36642.js new file mode 100644 index 000000000..804bf0f55 --- /dev/null +++ b/pr/617/docs/search.index/2a4f58d36642.js @@ -0,0 +1 @@ +rn_("AQIA9DUTAAABAJAAAQABAgD0DhEAAAEAmgABAAEDAPRpEwAAAQCYAAEAAQIBoEAAABNUcjswAAABAAAHAAIADBEBAKMRBQDzAIQDQAg6MAAAAQAAAAAAFQAQAAAAVAdVB1YH7A/tD5UQlhC2ELcQTRFOEQASARIzEzQTQBNBE1ATexN8E8QTxRM=") \ No newline at end of file diff --git a/pr/617/docs/search.index/2a689e7ce38c.js b/pr/617/docs/search.index/2a689e7ce38c.js new file mode 100644 index 000000000..35d01b68f --- /dev/null +++ b/pr/617/docs/search.index/2a689e7ce38c.js @@ -0,0 +1 @@ +rn_("AQAAOjAAAAEAAAAAAAoAEAAAAPYP9w/mEOcQcxEEEgUSIhMjE7sUvBTzAQF58hsUAAABACMAA6BAAAARkqBQAAAUbmVvc/LaDwAA1wI=") \ No newline at end of file diff --git a/pr/617/docs/search.index/2a7bc2b190a6.js b/pr/617/docs/search.index/2a7bc2b190a6.js deleted file mode 100644 index 172ee2b41..000000000 --- a/pr/617/docs/search.index/2a7bc2b190a6.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYADsAAT9QABsAAT9wABsHAUcAABNDZhAQQA9MUUAAABAAoAAQAjgQKgQAAAEW5tcAEBADswAAABAAAIAAMA6BIAAFsTAADSFAYAxwEAAMWgQAAAEGiggAAAEsJABoAIOzAAAAEAABcACAAGEAEAJhEBAGoRAwDhEQYA0RMDACEUAACAFAEAtBQBAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/2a90a7e1d3b9.js b/pr/617/docs/search.index/2a90a7e1d3b9.js new file mode 100644 index 000000000..4b66cf467 --- /dev/null +++ b/pr/617/docs/search.index/2a90a7e1d3b9.js @@ -0,0 +1 @@ +rn_("AQUA9BMUAAABAFAAAQDzBAFj8hcTAAABAAEFAPPoEgAAAQABAPOCAmZ0AQABsAAUbwABZPQNDAAAAQABAAEA84ACZWkTgAKw4BR6AAF0dg==") \ No newline at end of file diff --git a/pr/617/docs/search.index/2ad2d4f46aea.js b/pr/617/docs/search.index/2ad2d4f46aea.js deleted file mode 100644 index 9a2da5e63..000000000 --- a/pr/617/docs/search.index/2ad2d4f46aea.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABsIAUTQABcPJJFAAAAQBDBQOgIAAAEXCgQAAAEsdkZXPzCRAAAE4DAQABAgA7MAAAAQAAEwABADQQEwATgYSgUAAAESCgEAAADDegIAAADQEcQAAzgAOwUBOTAAFhZm4=") \ No newline at end of file diff --git a/pr/617/docs/search.index/2b2a88985dd7.js b/pr/617/docs/search.index/2b2a88985dd7.js deleted file mode 100644 index 61e4c3db4..000000000 --- a/pr/617/docs/search.index/2b2a88985dd7.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMA9hINAADqAloAAQDSAgEAAQQAOzAAAAEAAAoABACfEAUA5BIBAEQTAQBqFAAAAYYCoAAAABNmoAAAABNnZHLzAgF2AZITAADzgAJlbycBAADHoDAAAA/7oHAAABK0oIAAABQ5sNAT2AABoLAAABQ5QEUICTswAAABAAAJAAMAxgUCACkHBABrDAEAI4ACS1dG2+EeZHIBAAA6MAAAAQAAAAAACgAQAAAAYQliCTEQgBOBE7ETshNeFLYUzhTPFBOAAqAwAAAQtWx084ACYWU=") \ No newline at end of file diff --git a/pr/617/docs/search.index/2b3c097bd30b.js b/pr/617/docs/search.index/2b3c097bd30b.js new file mode 100644 index 000000000..9abd3ded0 --- /dev/null +++ b/pr/617/docs/search.index/2b3c097bd30b.js @@ -0,0 +1 @@ +rn_("AQABoLAAABPcYvNmEwAALgEBAAGAAqAgAAAP46AwAAAT6GFy8wABdAGJBwAAAQQBoEAAABR1ZfKXFAAAAQABBQA7MAAAAQAACwAFAGkQBQDhEgEADxQBAGgUAACHFAAA4wGEoDAAAA/hhQACOzAAAAEAAAsABQCqBQIAxwYEAGgMAQCbEQAA8xIAAGOAA7AAEAQDYWRucg==") \ No newline at end of file diff --git a/pr/617/docs/search.index/2b605a22de5b.js b/pr/617/docs/search.index/2b605a22de5b.js new file mode 100644 index 000000000..a3bd60d23 --- /dev/null +++ b/pr/617/docs/search.index/2b605a22de5b.js @@ -0,0 +1 @@ +rn_("AQIA9fYRAAABACUBAQBhAQEBAPcyEQAAAQABANYCAQCLAAUAAQIA9MIQAAABAAEAAQATgAKg0AAAFINjc/OAAmVvI4ACsAAHXAABbHTzAAJhcvK/DwAAAQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/2b616085d3bb.js b/pr/617/docs/search.index/2b616085d3bb.js deleted file mode 100644 index 68ad6cd5a..000000000 --- a/pr/617/docs/search.index/2b616085d3bb.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQgBoIAAABTIZgGnEwAAE4ACoOAAABSKYnABAwGwABRCAFBz9vgPAAABAMQAKwABAAADAYACsEASzgABoBAAABEocHIBgQOgAAAAEZmwAA/jASegEAAAD9tlc3XzgANhb3IBAgD16xAAAAEAAQBfAgEAAQQAOzAAAAEAAAkAAQBeEQkAAQAA9M4RAAABAAEAAQABgAKgkAAAFAKwUBK2AAFid/OAAmR0o4CEsFAUHgABsJATRAABQCADVwMAAIagcAAAE/mgQAAAFG8wQQw7MAAAAQAAQgAhAMUEAQCMBQoAiQcAADYJAQA8CQAAPgwBAGEMAgD2DAEABA0AAKsOAADMDwQA4w8AAAIQAQBIEAAAxhAAANQQAwD3EAEAChEAADARAQCdEQAA8hEAAPsRAQAAEgAAARMAADoTAQDtEwEA9BMAAB4UAQBAFAEAWBQAAJMUAQDFFAEA0BQBAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/134492284a6c.js b/pr/617/docs/search.index/2b670142def8.js similarity index 55% rename from pr/617/docs/search.index/134492284a6c.js rename to pr/617/docs/search.index/2b670142def8.js index 3006cc308..513d9954c 100644 --- a/pr/617/docs/search.index/134492284a6c.js +++ b/pr/617/docs/search.index/2b670142def8.js @@ -1 +1 @@ -rn_("AQgA9IUQAAABALMAAQABAAGggAAAFMhm8goJAACdCgEHAPV1EwAAAQABAAEAAQAjgAKgUAAAEXNhb/MCAmZzOzAAAAEAAAgABABXBwAAigcFABUNAACrEQAAM4ADoEAAAAwfYWVv") \ No newline at end of file +rn_("AQgA9IUQAAABALMAAQABAAGggAAAFMdm8goJAACcCgEHAPV0EwAAAQABAAEAAQAjgAKgUAAAEXNhb/MCAmZzOzAAAAEAAAgABABXBwAAigcFABUNAACrEQAAM4ADoEAAAAwfYWVv") \ No newline at end of file diff --git a/pr/617/docs/search.index/2b69804711dc.js b/pr/617/docs/search.index/2b69804711dc.js deleted file mode 100644 index f1725367f..000000000 --- a/pr/617/docs/search.index/2b69804711dc.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQ0A9e8TAAABAAEAegABAAECAPQaEQAAAQCUAQEAAQMA9IURAAABAH4BAQDzAAJidQEqEAAAAQMA9BgRAAABAJQBAQDzhAJkcwECAPMcCQAAAQBYAPOAAml0ARgA89YUAAABAAEAI4ECsMAT7QABY20BBQGwYBLXAAFy9DIMAAABAPUDAQABCQD2AhMAAAEA/QCJADUAAQATgAKwYBDYAAFkbgEDAaBwAAATPmf0ZQcAAAEADQIBAAEJAPOPFAAAAQA7AAGBArCAFJ8AAaBgAAAUx2RzFwEAiYWw0BTJAAGgQAAAFHKwYBRCAFACkApnugAAjLBwET0AAbCwE0wAAaCgAAATAbFQFMAAAbDwFB4AAW3BD/SIAAAAAQABAAEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/2b6f9f07c43a.js b/pr/617/docs/search.index/2b6f9f07c43a.js new file mode 100644 index 000000000..6af9f56f1 --- /dev/null +++ b/pr/617/docs/search.index/2b6f9f07c43a.js @@ -0,0 +1 @@ +rn_("AYACsHAUqAABoAAAAASnZmcBAQA7MAAAAQAABwABAFMMBwABAQGgcAAAErFhAR0HAAAjgAKhAAAAFLJkbnOAhKBgAAASsABBSA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/2b87673d150b.js b/pr/617/docs/search.index/2b87673d150b.js new file mode 100644 index 000000000..9c201e7e6 --- /dev/null +++ b/pr/617/docs/search.index/2b87673d150b.js @@ -0,0 +1 @@ +rn_("AQwA9NATAAABAAEAAQABBgDzghEAAAEAAQABggKwYBNWAAGw0BR4AAFpchOAArAQDPkAAWVpFwMAg4ixMBSXAAGgYAAAEYGgUAAAERWwkBMoAAGwQBERAAG0EAfzAAFh9E0JAAABAAgIAQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/2c0afee3955b.js b/pr/617/docs/search.index/2c0afee3955b.js deleted file mode 100644 index 29eb9c0eb..000000000 --- a/pr/617/docs/search.index/2c0afee3955b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQsA9LMTAAABAA8AAQABBAGwQBNoAAFlAbUSAADzAAF0AXIUAAABDQDziRQAADUAAQABEADzthQAABgAAQDzgQNrbXMBAAA7MAAAAQAABgACAJ0TAAAFFAUAI4YCsEAUfQABZXMBBAD01BAAAAEAAQABAPOAA2NydDOAA7BgEPMAAWFscg==") \ No newline at end of file diff --git a/pr/617/docs/search.index/2cc429d48879.js b/pr/617/docs/search.index/2cc429d48879.js deleted file mode 100644 index c1c236bcc..000000000 --- a/pr/617/docs/search.index/2cc429d48879.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBsEARcQABZQGmDgAAAQUAOzAAAAEAAAYAAQBvEAYAAQMAOzAAAAEAAAUAAQDnDAUA8wACcHM6MAAAAQAAAAAADQAQAAAArQWuBcwGIglqDHcMFhEXEcgSyRIfEyATIRMiEzMAA7BQD+QAz2RwcjswAAABAAAPAAYAowQAAGwFAwDRDwAA8xEDAMMSAwD3EgEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/2d360f1667cc.js b/pr/617/docs/search.index/2d360f1667cc.js new file mode 100644 index 000000000..a568ba86a --- /dev/null +++ b/pr/617/docs/search.index/2d360f1667cc.js @@ -0,0 +1 @@ +rn_("AQIA9DUTAAABAJAAAQABAwD0aRMAAAEAmAABAPMCAmJ1OjAAAAEAAAAAABMAEAAAAFQHVQdWB+wP7Q+VEJYQthC3EE0RThEAEgESMxM0E0ATQRNQE8QTxRM=") \ No newline at end of file diff --git a/pr/617/docs/search.index/2d83fa4de460.js b/pr/617/docs/search.index/2d83fa4de460.js new file mode 100644 index 000000000..90d6b8ecf --- /dev/null +++ b/pr/617/docs/search.index/2d83fa4de460.js @@ -0,0 +1 @@ +rn_("AYMCsQAUzQABsNAUvQABa20BCwDzDxQAAAEAdwAzAoSgYAAAEqahAAAAFIkEgAM7MAAAAQAANgACAFkHAACsBzUA") \ No newline at end of file diff --git a/pr/617/docs/search.index/2dbb149f52d2.js b/pr/617/docs/search.index/2dbb149f52d2.js deleted file mode 100644 index ce135a1b4..000000000 --- a/pr/617/docs/search.index/2dbb149f52d2.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9bsRAAABAAEAAQABAAGAArBgEycAAaAgAAAREG95AQABoMAAABRMafR+EwAACAAcABsAAQMA9KMTAAABAAEAAQABBgD0uBQAAAEAAQABAAECAPQOEQAAAQCaAAEAAQIBoEAAABNVcjswAAABAAAHAAIADBEBAKMRBQDzgAJhb2cHAACHsIATVABasKATywABDDAHOzAAAAEAAA4ABACWBAUALQwAANYRAwC+EgMApwAAgIWg8AAAE96wUBBfAAGgkAAAEZqUgQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/2ea9dee8417b.js b/pr/617/docs/search.index/2ea9dee8417b.js deleted file mode 100644 index ae981dd6f..000000000 --- a/pr/617/docs/search.index/2ea9dee8417b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABoCAAAAz7bQGIBQAAAQAA9B8TAAABAAEAAQDzBAFk9MMSAAABAAEAAQAjhISwoBRnAAGwQBMdAAGwcBQOAAEVgAAzgAOw0BRRAAFhZWk=") \ No newline at end of file diff --git a/pr/617/docs/search.index/2ec9baa2b225.js b/pr/617/docs/search.index/2ec9baa2b225.js new file mode 100644 index 000000000..6e42695d6 --- /dev/null +++ b/pr/617/docs/search.index/2ec9baa2b225.js @@ -0,0 +1 @@ +rn_("AQgA9VIUAAABAAEAAQABAPMAAWY7MAAAAQAABQACAPIBAACqEAQAAQIAOzAAAAEAAAsAAgDiEwUAoBQFAPMGAW07MAAAAQAADQAEAOAMAAB9EQAA+hIFAF4UBQBjgAOwAAkxAAFlbXM=") \ No newline at end of file diff --git a/pr/617/docs/search.index/2f23c7c0d6ce.js b/pr/617/docs/search.index/2f23c7c0d6ce.js new file mode 100644 index 000000000..a1393d449 --- /dev/null +++ b/pr/617/docs/search.index/2f23c7c0d6ce.js @@ -0,0 +1 @@ +rn_("AYADsAAT9AABsAAT9gABsHAUbwABNDZhAQQA9MQUAAABAAoAAQAjgQKgQAAAEW5tcAEBADswAAABAAAIAAMA5xIAAFoTAADRFAYAxwEAAMWgQAAAEGiggAAAEsFABoAIOzAAAAEAABYACAAGEAEAJhEBAGoRAwDhEQUA0BMDACAUAAB/FAEAsxQBAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/2f5deadfbad9.js b/pr/617/docs/search.index/2f5deadfbad9.js new file mode 100644 index 000000000..25b515d9f --- /dev/null +++ b/pr/617/docs/search.index/2f5deadfbad9.js @@ -0,0 +1 @@ +rn_("AYACsHARhwABsCAUbAABYWQBBQD0WBMAAAEAmAABAPMAAWMBwRIAAEOAA7AwE64AAbCgFE4AAWFzeQEGAPQmEQAAAQC+AAEAk4CEoAAAABFusSAUswABCSAB84ACZWk=") \ No newline at end of file diff --git a/pr/617/docs/search.index/2f978aecd3a2.js b/pr/617/docs/search.index/2f978aecd3a2.js deleted file mode 100644 index 61f6a8f41..000000000 --- a/pr/617/docs/search.index/2f978aecd3a2.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEAOzAAAAEAAAYAAQB0EQYAAQAA9y8JAAABAF0HAQBzAAEAFgEBggKgIAAAETegMAAAEZ1pdPOAA2FncwEBAaBAAAAUS2XyeRQAAAEAI4ACsFAT/gABZnPzgAJpbw==") \ No newline at end of file diff --git a/pr/617/docs/search.index/3039810e4520.js b/pr/617/docs/search.index/3039810e4520.js new file mode 100644 index 000000000..abacd894f --- /dev/null +++ b/pr/617/docs/search.index/3039810e4520.js @@ -0,0 +1 @@ +rn_("AQQBoEAAABQ0ZfKqFAAAAQABAQA7MAAAAQAABgABANEUBgDzBgFl9H8UAAABADMAAQBTAISgIAAACWuwYBOVAIoBAQnzixAAAHoCAQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/30554a2e2f98.js b/pr/617/docs/search.index/30554a2e2f98.js deleted file mode 100644 index 1221c84e2..000000000 --- a/pr/617/docs/search.index/30554a2e2f98.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsRAUqwABsBAMZAABZW8BBQA7MAAAAQAADQABAKwRDQABAgDzFxAAAAEAAQABAgD0ExAAAAEAAQABAAEDADswAAABAAAIAAEAyRAIAIcHAACHsKAT1QABsNAUbQABoFAAABQ4jBQROzAAAAEAAAYAAwBgBwAA+RADAMwSAQDzgAJsdBOAA6AgAAAM3rCgFFEAAWFkbg==") \ No newline at end of file diff --git a/pr/617/docs/search.index/308477dce192.js b/pr/617/docs/search.index/308477dce192.js deleted file mode 100644 index 3742ca2f8..000000000 --- a/pr/617/docs/search.index/308477dce192.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEA9VoJAAABAAEAAQABAPMAAWnz/gwAAMQHAQABAQGgQAAAFDVl8qsUAAABAAEEAqAgAAAR7qAwAAASumJoAd4PAADnAACAxaAQAAAM/6AwAAAQjAQQKAE=") \ No newline at end of file diff --git a/pr/617/docs/search.index/309101a2ab26.js b/pr/617/docs/search.index/309101a2ab26.js deleted file mode 100644 index 61b349d19..000000000 --- a/pr/617/docs/search.index/309101a2ab26.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEA9QIIAAABAAEAAQABAAEAAqAwAAARLKCQAAATojBo9RMSAACXAQEAAQABABOAAqAwAAAP4GRyIwKEsBAJBgNCsCAMFQELoDAAABAFEgAY9aAFAAAxAAEAAQABADOAhLCgEuIAAbBAD+oAARBJAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/30bdf72af4ad.js b/pr/617/docs/search.index/30bdf72af4ad.js new file mode 100644 index 000000000..085845da9 --- /dev/null +++ b/pr/617/docs/search.index/30bdf72af4ad.js @@ -0,0 +1 @@ +rn_("AQEAOjAAAAEAAAAAAAkAEAAAAO8P8A/lEKIRARMCE/8TiBS9FL4UAYACsOAT+QABsDAUEQABZHDDgISxQBTPAAGgkAAAE70CgAk=") \ No newline at end of file diff --git a/pr/617/docs/search.index/30c32768f920.js b/pr/617/docs/search.index/30c32768f920.js deleted file mode 100644 index 9e9d0b1ce..000000000 --- a/pr/617/docs/search.index/30c32768f920.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9E0JAAABAAgIAQABCQGggAAAFMhmAacTAAABAgD16xAAAAEAAQBfAgEA8wACaXU6MAAAAQAAAAAABwAQAAAAxhABE+0T7hMeFB8UQBRBFAEIAPVTFAAAAQABAAEAAQDzAgFm9aoQAAABAAEAAQABABOAArCgFJ0AAXJ08wACYXPz2hAAAAEAAQDzgAJ0dg==") \ No newline at end of file diff --git a/pr/617/docs/search.index/3109aa229dc6.js b/pr/617/docs/search.index/3109aa229dc6.js deleted file mode 100644 index 7b9b49af7..000000000 --- a/pr/617/docs/search.index/3109aa229dc6.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQYAOzAAAAEAAAkAAQBeEQkAI4ADsGATkwABsDAP3AABaXN0AQgA9VMUAAABAAEAAQABAPMBAWb1qhAAAAEAAQABAAEAY4ADoDAAABNVZWd0") \ No newline at end of file diff --git a/pr/617/docs/search.index/3142e6000013.js b/pr/617/docs/search.index/3142e6000013.js new file mode 100644 index 000000000..ccde0d3d6 --- /dev/null +++ b/pr/617/docs/search.index/3142e6000013.js @@ -0,0 +1 @@ +rn_("AQoA9LITAAABAA8AAQABgwKgIAAAEFygMAAAEPhpdAEAAPQeEwAAAQABAAEA8wQBZPTCEgAAAQABAAEAI4aEsKAUZgABsEATHAABsHAUDQABFYAAAQACsDAQTgABsBATygABc3TymwUAAI8GAYACsMAUlAABoAAAAAdadHnzgIURwQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/314abf4dfb52.js b/pr/617/docs/search.index/314abf4dfb52.js deleted file mode 100644 index fe7d5b860..000000000 --- a/pr/617/docs/search.index/314abf4dfb52.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9BQUAAABAFAAAQDzBAFj8hgTAAABAAEFAPPpEgAAAQABAPOCAmZ0AQABsAAUcAABZPQNDAAAAQABAAEA84ACZWkTgAKw4BR7AAF0dg==") \ No newline at end of file diff --git a/pr/617/docs/search.index/31587e8a08ed.js b/pr/617/docs/search.index/31587e8a08ed.js new file mode 100644 index 000000000..ed183e3d7 --- /dev/null +++ b/pr/617/docs/search.index/31587e8a08ed.js @@ -0,0 +1 @@ +rn_("AQEA9HEHAACACOEAAQABBQD0WRQAAAEAAQABAAEFAPRJEwAAAQCUAAEA8wICY3X1ZhAAAAEAxgABAAEA84ACbnM=") \ No newline at end of file diff --git a/pr/617/docs/search.index/316c499513de.js b/pr/617/docs/search.index/316c499513de.js new file mode 100644 index 000000000..5ff5df56a --- /dev/null +++ b/pr/617/docs/search.index/316c499513de.js @@ -0,0 +1 @@ +rn_("AQUA9VMQAAABAAEAYAIBAAEEAaAAAAAQ8HP1Dw0AAAEAAQBAAwEAAQIA9bsRAAABAAEAAQABAAGAArBgEyYAAaAgAAAREG95AQABoMAAABRLafR9EwAACAAcABsAAQYA9LcUAAABAAEAAQABAgD0DhEAAAEAmgABAAECAaBAAAATVHI7MAAAAQAABwACAAwRAQCjEQUA84ACYW9nAwAAhrCAE1MAWrCgE8oAAQwwBjswAAABAAAIAAMALQwAANYRAwC9EgMA84ICZWlHAQAAhbBwE9oAAaCAAAAUx6BAAAAQCLBABPQKCQAA6AYBALMDAYECoCAAABEKsFASzQABYW8BBAGgAAAAE/NzAQkRAABjBAOwUBF7AAFjZXP2EgkAAAEAAQAsAAEAcgkBAAA7MAAAAQAAJAAIAEwHBwBTEAIArBENANsRAgC1EgEAOxMBAIwTAQAjFAIAAQUA8y4TAAABAAEAAQEAOzAAAAEAAAUAAgARDAMABhIBAFMAA7AwFGYAAWVwcvT0DwAAAQDuAAEAAQADsEAUKwABoUAAABSboFAAABH8YmNmARMHAADjgISxQBTEAAECMBABAYSgsAAAE+iwQBDHAAGwwBQXAAGgYAAAEaIUIAT2+gYAAGUJAQA6AS8BFAEBAwA7MAAAAQAABQABAO0SBQABAAGgoAAAFFdy9P4PAAABAPYAAQDzgQJncwEHAPV0EwAAAQABAAEAAQABgAKgQAAAEXOwUBFaAAFjafOAAmFvAQABoCAAABGTcvNFDAAAhANsAUOAhKAAAAAUNKAwAAAUNIAFdgAdNAggIQEDArBAEzEAAaAwAAATh2V38p4QAAC7ABODAqBwAAATwWdvAQIA9RATAAABAPAAgAABABOAArAwE0IBSWlwAQYA874TAAABAAEA8wUCYnMBUBAAAAECAPQtEAAAAQABAAEAI4ACoGAAABKxYmP3XwEAzbBgEO4AAaCQAAASssBOchU6MAAAAQAAAAAAGAAQAAAAGgIbAoUEhgSHBB0HVweKB4sHjAeNB44HjwcMDIUMFQ1LEF0QXhArEasRqhSrFL8UwBQ=") \ No newline at end of file diff --git a/pr/617/docs/search.index/31793053504c.js b/pr/617/docs/search.index/31793053504c.js new file mode 100644 index 000000000..ccf049172 --- /dev/null +++ b/pr/617/docs/search.index/31793053504c.js @@ -0,0 +1 @@ +rn_("AQIA9MARAAABAAEAAQABCgD00BMAAAEAAQABAPOBAm10AQYA84IRAAABAAEAAYICsGATVgABsNAUeAABaXITgAKwEAz5AAFlaQEFAPQTFAAAAQBQAAEA8wQBY/IXEwAAAQABBQDz6BIAAAEAAQDzggJmdAEAAbAAFG8AAWT0DQwAAAEAAQABAPOBAmVpNwYAAImxMBSXAAGgYAAAEYGgUAAAERWwkBMoAAGwQBERAAG1EAcBSxQAAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/323ee7233560.js b/pr/617/docs/search.index/323ee7233560.js new file mode 100644 index 000000000..f5479ad7c --- /dev/null +++ b/pr/617/docs/search.index/323ee7233560.js @@ -0,0 +1 @@ +rn_("AQACoDAAABEsoJAAABOhMGj1EhIAAJcBAQABAAEAE4ACoDAAAA/gZHIjAISwEAkGA0KwIAwVAQugMAAAEAUSABj1oAUAADEAAQABAAEAAQIA9MQUAAABAAoAAQABAgDzfxAAAAEAAQDzgAJpcgEBArBAEXEAAaBQAAAThmVz9KYOAADhAgEAkgIBCwD2sBMAAAEArABYABgAAQDzgAJsdNOAhKCQAAATXgFEBA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/32695d4910d9.js b/pr/617/docs/search.index/32695d4910d9.js deleted file mode 100644 index 483b79f2c..000000000 --- a/pr/617/docs/search.index/32695d4910d9.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsRAUqwABsBAMZAABZW8BBQA7MAAAAQAADQABAKwRDQABAgDzFxAAAAEAAQABAgD0ExAAAAEAAQABAAEDADswAAABAAAIAAEAyRAIAIcHAACHsKAT1QABsNAUbQABoFAAABQ4jBQROzAAAAEAAAYAAwBgBwAA+RADAMwSAQDzgQJsdPMAAWk7MAAAAQAAjwADAJ0EAAAHEQAAGRKNAAEFAPOPFAAAAQA7AAEAAaBAAAARC2XzFQkAAAEAowkTgAKgMAAAD+5kcvMAAWXz9wYAAAEAAQDzgAJodAEBADswAAABAACOAAIABxEAABkSjQATgAKgUAAAEDFpdAEIAPQLFAAAAQCLAAUAEwCEsPAUkQAzsJAUdAABsFATUAFnQIkAOzAAAAEAABIACQB5EAIA/hEBAD8TAQBgEwEAlxMBAPwTAQA2FAEARRQBAIYUAQDXAQAAhrCAEcQAAaBAAAAJOAgwJjowAAABAAAAAAANABAAAACSAJMAagd5CRUMIA0aERsRrxKwEjYTNxPHE8gT") \ No newline at end of file diff --git a/pr/617/docs/search.index/32824d81361a.js b/pr/617/docs/search.index/32824d81361a.js deleted file mode 100644 index 277e6e6cc..000000000 --- a/pr/617/docs/search.index/32824d81361a.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9DYTAAABAJAAAQABAgD0DhEAAAEAmgABAAEDAPRqEwAAAQCYAAEAAQIBoEAAABNVcjswAAABAAAHAAIADBEBAKMRBQDzAIQDQAg6MAAAAQAAAAAAFQAQAAAAVAdVB1YH7A/tD5UQlhC2ELcQTRFOEQESAhI0EzUTQRNCE1ETfBN9E8UTxhM=") \ No newline at end of file diff --git a/pr/617/docs/search.index/32893117deca.js b/pr/617/docs/search.index/32893117deca.js deleted file mode 100644 index 64ebf6c5a..000000000 --- a/pr/617/docs/search.index/32893117deca.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQcA9rETAAABAKwAWAAYAAEA8wEBaQFrCQAAAQAA9W4JAAABAAEAAQABAEcBAIDGZG3jajLhoKAAABSnsGAUFgABoBAAAAlCAAogig==") \ No newline at end of file diff --git a/pr/617/docs/search.index/32b279bb23c7.js b/pr/617/docs/search.index/32b279bb23c7.js new file mode 100644 index 000000000..f515f3851 --- /dev/null +++ b/pr/617/docs/search.index/32b279bb23c7.js @@ -0,0 +1 @@ +rn_("AQABsKAS4wABZTswAAABAAAJAAIA8QcHACQTAQABAAGgIAAADPttAYgFAAABgAKwoBLhAAGwQA/qAAFsbwEAAPQeEwAAAQABAAEA8wQBZPTCEgAAAQABAAEAIwKEsKAUZgABsEATHAABsHAUDQABFYAAATgUAAABAAGwgBRMAAFw8kgUAAABAEMAA6AgAAARcKBAAAASxmRlc/MJEAAATQMBAPOBAmVp1wIAAIawIBKnAAGw0BRQAAEXAQL0pAQAAAEAAQDKAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/32ca2ee3c5d5.js b/pr/617/docs/search.index/32ca2ee3c5d5.js deleted file mode 100644 index d270dbef6..000000000 --- a/pr/617/docs/search.index/32ca2ee3c5d5.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABoEAAABC9dPcEDQAARAPyAgEAuQCfAAEA8wABczswAAABAAAcAAgAtQQCADoMAwAAEAEAxxABAAgRAQD5EQEAtRMHAK0UBQAjgAKxEBS0AAFhdA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/3339b459bd03.js b/pr/617/docs/search.index/3339b459bd03.js deleted file mode 100644 index f95a373a7..000000000 --- a/pr/617/docs/search.index/3339b459bd03.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQYA9CYRAAABAL8AAQATgQOgAAAAEW6xIBS0AAFhZG4BBQD0mBAAAAEAAQABAAEUAPXUFAAAAQABAAEAAQABBQGwgBRwAAF2OzAAAAEAAAgAAgDhEQQA0RMDACOAArBwEgcAAWJkVwIAAYawwBObAAGgoAAAE76gkAAAE4cF4AL1IAcAAAEAAQDwCJUEAQYA8+YSAAABAKoAAQABoGAAAA/9cgGRBAAA1wEAgMWgMAAADB8AAIoK") \ No newline at end of file diff --git a/pr/617/docs/search.index/334f97bd1666.js b/pr/617/docs/search.index/334f97bd1666.js deleted file mode 100644 index a8179e37c..000000000 --- a/pr/617/docs/search.index/334f97bd1666.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBoEAAABIKZfMWDQAAEwQBAAEGAPQmEQAAAQC/AAEAE4EDoAAAABFusSAUtAABYWRuAQUA9JgQAAABAAEAAQABFAD11BQAAAEAAQABAAEAAQUBsIAUcAABdjswAAABAAAIAAIA4REEANETAwAjgAKwcBIHAAFiZFcCAAGGsMATmwABoKAAABO+oJAAABOHBeAC9SAHAAABAAEA8AiVBAEGAPPmEgAAAQCqAPOAA3J0dgEDADswAAABAAAHAAIAzRMDALgUAwDzAAFzAXMUAADzAAFzAQkSAADHAgCAh6AQAAAJB6AwAAAMUrAAE4IAAaAQAAAM+xNADg==") \ No newline at end of file diff --git a/pr/617/docs/search.index/336e3a26ae11.js b/pr/617/docs/search.index/336e3a26ae11.js deleted file mode 100644 index b9c25b915..000000000 --- a/pr/617/docs/search.index/336e3a26ae11.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBoEAAABRLZfJ5FAAAAQABAAD0HxMAAAEAAQABAPMEAWT0wxIAAAEAAQABAKcAAAKFsKAUZwABsEATHQABsHAUDgABFYgAATkUAAABAAGwgBRNAAFw8kkUAAABAEMAA6AgAAARcKBAAAASx2Rlc/MJEAAATgMBAPOAAmVp") \ No newline at end of file diff --git a/pr/617/docs/search.index/337b7c87a50a.js b/pr/617/docs/search.index/337b7c87a50a.js new file mode 100644 index 000000000..e86fa5dba --- /dev/null +++ b/pr/617/docs/search.index/337b7c87a50a.js @@ -0,0 +1 @@ +rn_("AQIA9DUTAAABAJAAAQABAwD0aRMAAAEAmAABAPMFAmJ1OjAAAAEAAAAAAAcAEAAAAJUQlhBNEU4RABIBEkATQRMBAgD0hREAAAEAfQEBAAEFAPOLEQAAAQABAAEEAPTcEgAAAQCmAAEA84CEEQkA") \ No newline at end of file diff --git a/pr/617/docs/search.index/33ec105d3348.js b/pr/617/docs/search.index/33ec105d3348.js new file mode 100644 index 000000000..ba73f48a9 --- /dev/null +++ b/pr/617/docs/search.index/33ec105d3348.js @@ -0,0 +1 @@ +rn_("AQkA844UAAABADsAAYACoEAAABNrsCAUKQABaGkBAQDzChAAAAEAAQATgAKwwBTIAAF0dQGBArCAFJ4AAaBgAAAUxmRzNwMAgIagQAAAFHGwYBRBAFACkA4BAAGwUBEdAAFj9GcHAAABAAEADQLDgISgEAAADIigUAAAErMMQAQ=") \ No newline at end of file diff --git a/pr/617/docs/search.index/343a021f5f79.js b/pr/617/docs/search.index/343a021f5f79.js new file mode 100644 index 000000000..c3bf94b40 --- /dev/null +++ b/pr/617/docs/search.index/343a021f5f79.js @@ -0,0 +1 @@ +rn_("AQsA9LITAAABAA8AAQABBAGwQBNnAAFlAbQSAADzAAF0AXEUAAABDQDziBQAADUAAQABEADztRQAABgAAQDzgQNrbXMBAAA7MAAAAQAABgACAJwTAAAEFAUAI4YCsEAUfAABZXMBBAD01BAAAAEAAQABAPOAA2NydDOAA7BgEPMAAWFscg==") \ No newline at end of file diff --git a/pr/617/docs/search.index/3451b6eeceed.js b/pr/617/docs/search.index/3451b6eeceed.js deleted file mode 100644 index 252f68bc6..000000000 --- a/pr/617/docs/search.index/3451b6eeceed.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAAOzAAAAEAAAYAAgCdEwAABRQFACOIArBAFH0AAWVzAQQBsDATbQABY/OWEQAAAQABAAEFADswAAABAAAIAAMADRICABMTBAA6FAAA8wMDY2Vw89sMAAABAAEAAQABsGAUHgABZQFYFAAA8wIBc/YzCQAAAQABACAIPwAkAQECAaBQAAARlXIBNhEAAPOAA2ZtcA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/345d3d0c7b44.js b/pr/617/docs/search.index/345d3d0c7b44.js deleted file mode 100644 index 546fd3807..000000000 --- a/pr/617/docs/search.index/345d3d0c7b44.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEAOjAAAAEAAAAAAAcAEAAAAGATYRP8E/0TNhQ3FIYUhxTzBgFpAX8TAABDgAOwABTAAAGwUBNUAFpkZm4BAAGwQBMtAAFlOzAAAAEAAA0AAgDcBgwAIBEAACOAAqAwAAAMI2FlNwAAgMWwUBOoAAGwEBRPAAGwYBOvAAEAApgI") \ No newline at end of file diff --git a/pr/617/docs/search.index/347cdc137d51.js b/pr/617/docs/search.index/347cdc137d51.js new file mode 100644 index 000000000..f5aa20474 --- /dev/null +++ b/pr/617/docs/search.index/347cdc137d51.js @@ -0,0 +1 @@ +rn_("AYQCsCARlAEjoJAAABQ5bXABgAKgMAAAESCgUAAAE9xjcpcAAACFsCATpwABsMAUbAABoGAAABHwEEAOOzAAAAEAABIABgDOBAgA7hEBAAsTAQD5EwEAUBQBAMsUAQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/34893b3ba8c0.js b/pr/617/docs/search.index/34893b3ba8c0.js deleted file mode 100644 index 63d126c3b..000000000 --- a/pr/617/docs/search.index/34893b3ba8c0.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQYAOzAAAAEAAAUAAQBvEwUAg4YEsFATOAABsHATnwABoNAAABR/Ym5vdhOAArAQEwoAAXByAQABoDAAABIAdAG+EwAA8wIBc/K4EAAACgMTgAKgYAAAEKlldQECAPYbDQAAAQA+AwEALgEBAAGAArAAEvUAAYAOqzUAAWVzAQEA82EHAAAyDAEAY4ADsIAUEgABYm5084ADYW9yARQAOzAAAAEAAAUAAQCtFAUAAQAAOzAAAAEAAAYAAgCdEwAABRQFACOIArBAFH0AAWVzAQQBsDATbQABY/OWEQAAAQABAAEFADswAAABAAAIAAMADRICABMTBAA6FAAA8wIDY2Vw89sMAAABAAEAY4ADoGAAABAEZW91AQABsGAUHgABZQFYFAAA8wIBc/YzCQAAAQABACAIPwAkAQEIAPPSEgAAAQABAEOAA6AQAAAToaDgAAAUKWRvcwECAaBQAAARlXIBNhEAAAGAAqBAAAAQtaBwAAAUamFlAQAAOzAAAAEAAAsAAgD/BgUA7hIFAPOAAnR2IwACsBAH/AABbnL2KAkAAFcAFAcBALcAAQDzAIcwkQw6MAAAAQAAAAAACAAQAAAAygTLBBMRFBEIEwkTjxOQE4oU") \ No newline at end of file diff --git a/pr/617/docs/search.index/34ab39043216.js b/pr/617/docs/search.index/34ab39043216.js new file mode 100644 index 000000000..492f3f3ac --- /dev/null +++ b/pr/617/docs/search.index/34ab39043216.js @@ -0,0 +1 @@ +rn_("AQMA9hINAADqAloAAQDRAgEAAQQAOzAAAAEAAAoABACfEAUA4xIBAEMTAQBpFAAAAYYCoAAAABNloAAAABNmZHLzAgF2AZETAADzgAJlbycBAADHoDAAAA/7oHAAABKzoIAAABQ4sNAT1wABoLAAABQ4QEUICTswAAABAAAJAAMAxgUCACkHBABrDAEAI4ACQs4TugDPZHIBAAA6MAAAAQAAAAAACgAQAAAAYQliCTEQfxOAE7ATsRNdFLUUzRTOFBOAAqAwAAAQtWx0w4AEoAAAABCloAAAABCmNDZhZQ==") \ No newline at end of file diff --git a/pr/617/docs/search.index/354e3e954960.js b/pr/617/docs/search.index/354e3e954960.js deleted file mode 100644 index 09062220e..000000000 --- a/pr/617/docs/search.index/354e3e954960.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQwBoKAAABTZd/LJFAAAAQABAQDzChAAAAEAAQABiYSggAAAFMegQAAAFHKgYAAAFJKgkAAAFMsCgAoTgYSgoAAAFACxoBTXAAGw0BRrAAEEAQnzgANhdHU=") \ No newline at end of file diff --git a/pr/617/docs/search.index/35dceadd9c46.js b/pr/617/docs/search.index/35dceadd9c46.js new file mode 100644 index 000000000..a2ac18a27 --- /dev/null +++ b/pr/617/docs/search.index/35dceadd9c46.js @@ -0,0 +1 @@ +rn_("AQMA89ESAAABAAEAI4ECsSAUywABY2sBBAGgQAAAEgll8xYNAAATBAEAAQAAOzAAAAEAAAUAAgADEQMAmhMBAHOAhKAwAAAUSggwAQ==") \ No newline at end of file diff --git a/pr/617/docs/search.index/360e8453d77c.js b/pr/617/docs/search.index/360e8453d77c.js deleted file mode 100644 index 4dc51f8aa..000000000 --- a/pr/617/docs/search.index/360e8453d77c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQcA9e8TAAABAAEAegABAAEHAPPaEAAAAQABAAGAAqAAAAAHZKAQAAAJGmR48wEBZfIrEAAAAQDnAAAAhbBgDPwAAbBADG0AATIIAfTMBAAAjgLjCQEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/36800abc79d6.js b/pr/617/docs/search.index/36800abc79d6.js deleted file mode 100644 index 42d455220..000000000 --- a/pr/617/docs/search.index/36800abc79d6.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIAOzAAAAEAAAYAAgDABQUAlREAAAEAAPQ6EwAAAQBYAQEA8wQBczswAAABAAANAAIAtRMHAK0UBQBHAAABhqCAAAATlbEAFLwAAbBAEvUAAbCAFMwAAbBQEzwAARUAFvSnDgAAAQABAAEAAQUA8w8QAAABAAEAY4ADsEAQnAABbW5vAQEA9JgQAAABAAEAAQAjgQKwAAz2AAFzdfMAAmFpOjAAAAEAAAAAAAcAEAAAAHEH8Q/SENMQoxOkE6UTphMBgAOwABP1AAGwABP3AAGwcBRwAAE0NmEBBAD0xRQAAAEACgABACOBAqBAAAARbm1wAQIAOzAAAAEAAA4ABAB0BwEAGQ0BAF4RCQCcEQAAAQEAOzAAAAEAAAgAAwDoEgAAWxMAANIUBgABAQA7MAAAAQAACgADAIIRAgDNEwMAuBQDACOAAqAwAAAQaGRn1wMAAMaggAAAEsJARoAIOzAAAAEAABkACQAGEAEAJhEBAFoRAQBqEQMA4REGANETAwAhFAAAgBQBALQUAQABAwD2KAkAAFcAFAcBALcAAQAjgAOwcBTAAAGwcBRNAAFlb3IBBAGwsBSaAAFs9BcNAAABAEADAQABAgA7MAAAAQAADAADAAgJAADjEwUAoRQFAAEBAPNOEAAAAQBiAiOAAqCwAAAUTGd6AQIAOjAAAAEAAAAAAAkAEAAAAO8R8BEMEw0T+hP7E1EUUhTMFM0U8wABb/KCCQAAAQABBQD0+A8AAAEA7wABAIMAhKBAAAANDLBwE4AAAbAgDIkAARAFAjswAAABAAAJAAMAvAQDAIMFAgDeEQIAAYACoCAAABE1sCAUTQABZHQBhAKwsBSdAAGwABPtAAFhc9OAhKCwAAAUhEAoBAGAAqAwAAASCbBgEA0AAWhyR/8BAM2gIAAABaOgIAAABaSwcBPhAAEDZzQHOzAAAAEAADoAGQCfBAAA1wQFAHgJAADgDAAAxQ8DAL4QAwB9EQAA6BIAAPsSBQBbEwEAfhMAAIYTAACWEwAAohMAALMTAQC9EwAAvxMCAMMTAQAaFAAAIBQAACwUAQBJFAEAXxQFAHMUAADSFAYA") \ No newline at end of file diff --git a/pr/617/docs/search.index/373c5d490117.js b/pr/617/docs/search.index/373c5d490117.js deleted file mode 100644 index c37c8ab12..000000000 --- a/pr/617/docs/search.index/373c5d490117.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABsIAT+gABdAEkCQAAAQkA898SAAABAAEA84ACY2cBgwKgkAAAFEegkAAAFEgyMwGAAqBQAAAUc6CAAAATkmNl8wACc3TySgcAAAEAAQABoLAAABPdYvNnEwAALgEBACOAAqAAAAATZmRyI4ACoFAAABQ5Y2XzgAJpeQEGAPVPEQAAAQABAAEAAQABAQGwQBR3AAFl9TsUAAABAAEAAQABAPcAAICFoBAAAAwhKWAA") \ No newline at end of file diff --git a/pr/617/docs/search.index/3749fa2bd5f0.js b/pr/617/docs/search.index/3749fa2bd5f0.js deleted file mode 100644 index ac47496c8..000000000 --- a/pr/617/docs/search.index/3749fa2bd5f0.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYECoBAAABNWoHAAABOeYWMBhwKwABP1AAGwABP3AAE0NlMAhKCQAAATfrCwFBwAAYEACfUuBwAANgBFCREBIAA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/3765f6d962ab.js b/pr/617/docs/search.index/3765f6d962ab.js deleted file mode 100644 index cfaa3975c..000000000 --- a/pr/617/docs/search.index/3765f6d962ab.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIBsEATyQABZfLNBgAAAQABBADzfxAAAAEAAQBDAAOwQBMtAAGxABQQAAFlZnA7MAAAAQAAJAAGABkCAADcBgwA6gYMALYPBgAgEQAAbRMBAGOAA7AwDCMABWFlaQ==") \ No newline at end of file diff --git a/pr/617/docs/search.index/38356d26d54e.js b/pr/617/docs/search.index/38356d26d54e.js deleted file mode 100644 index 4f7a2b88b..000000000 --- a/pr/617/docs/search.index/38356d26d54e.js +++ /dev/null @@ -1 +0,0 @@ -rn_("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") \ No newline at end of file diff --git a/pr/617/docs/search.index/38e03ed6ba8b.js b/pr/617/docs/search.index/38e03ed6ba8b.js new file mode 100644 index 000000000..843238bf2 --- /dev/null +++ b/pr/617/docs/search.index/38e03ed6ba8b.js @@ -0,0 +1 @@ +rn_("AQAA9E0JAAABAAgIAQABCQGggAAAFMdmAaYTAAABAgD16xAAAAEAAQBeAgEA8wACaXU6MAAAAQAAAAAABwAQAAAAxhAAE+wT7RMdFB4UPxRAFAEIAPVSFAAAAQABAAEAAQDzAgFm9aoQAAABAAEAAQABABOAArCgFJwAAXJ08wACYXPz2hAAAAEAAQDzgAJ0dg==") \ No newline at end of file diff --git a/pr/617/docs/search.index/38e697a6153c.js b/pr/617/docs/search.index/38e697a6153c.js new file mode 100644 index 000000000..38f907446 --- /dev/null +++ b/pr/617/docs/search.index/38e697a6153c.js @@ -0,0 +1 @@ +rn_("AQgA9AoUAAABAIsABQATAAOwkBRzAAGwUBNPAWdnbHA7MAAAAQAAEgAJAHkQAgD9EQEAPhMBAF8TAQCWEwEA+xMBADUUAQBEFAEAhRQBAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/3995e07bb8ad.js b/pr/617/docs/search.index/3995e07bb8ad.js new file mode 100644 index 000000000..d0f5e3bb4 --- /dev/null +++ b/pr/617/docs/search.index/3995e07bb8ad.js @@ -0,0 +1 @@ +rn_("AQMA9CAJAAABAFMDAQABAwDzcQwAAAEAAQDzgAJpbwEBAbBQER0AAWP0ZwcAAAEAAQANAiOBAqBQAAAQ3mNuAQQAOzAAAAEAAAcAAwDbEQIAjBMBACMUAgABAQA6MAAAAQAAAAAABwAQAAAA/RH+ET4TPxOWE5cTRBRFFPMJAWkBHBEAANcCAACHsMATgQABoFAAAA/JoRAAABRuFAEXOzAAAAEAACIAAQC6ASIA") \ No newline at end of file diff --git a/pr/617/docs/search.index/399d9099d408.js b/pr/617/docs/search.index/399d9099d408.js deleted file mode 100644 index 0afd197da..000000000 --- a/pr/617/docs/search.index/399d9099d408.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9DoTAAABAFgBAQDzBAFzOzAAAAEAAA0AAgC1EwcArRQFAEcAAAWGoIAAABOVsQAUvAABsEAS9QABsIAUzAABsFATPAABFQAW9KcOAAABAAEAAQBDAISgIAAACTug0AAAE6GgMAAACRsVAATzAwIAAAEAegU=") \ No newline at end of file diff --git a/pr/617/docs/search.index/39d5be6cac82.js b/pr/617/docs/search.index/39d5be6cac82.js new file mode 100644 index 000000000..2144645c8 --- /dev/null +++ b/pr/617/docs/search.index/39d5be6cac82.js @@ -0,0 +1 @@ +rn_("AQIAOjAAAAEAAAAAAAgAEAAAACsMLAy9D74P+g/qEBARnhOfEwEAAPRHCQAAAQABAAEAAQUBsDATKgABaPMXFAAAAQByABODAqAwAAAQ3WNwAQMA9PgPAAABAO8AAQABgQKgAAAAEZmgEAAAD9tldfOAAmFyAQAA9M4RAAABAAEAAQABgAKgkAAAFAGwUBK1AAFid/OAAmR0E4ACsJATQwABbnLzAAJldPTUEAAAAQABAAEAAQEA9YsMAAABAAEAAQABABOAArCwE/0AAWFvIwACsEANDQABZXQ7MAAAAQAACQAEAMAEAQCGBQEAbQwBAMARAwABAgA7MAAAAQAACQACAPEHBwAkEwEAAYACsJATjAABoKAAABQAZXYTgAKxgBTTAAFjbQGCArBAFCsAAaBQAAAR/GJmE4ACoEAAABRXZHMjgAKg0AAAFKdjbvOAhBEBQAEFAbBAE2cAAWUBtBIAAEOCA6EAAAAUtaDQAAAUiGttcxOAArBgEPMAAWFyAQUAOzAAAAEAAI4AAgAHEQAAGBKNAAGCA7AgEyYAAbAgEI0AAbBAEYkAAWtsc1OAA7AgFIwAAWt0dQGJhKCAAAAUxqBAAAAUcaBgAAAUkaCQAAAUygKAChOCA6CgAAAT/7DQFGoAAWNpdQEBAPTKEQAAAQABAAEAQ4ADsXAU0QABoNAAABRAY29zI4ACsGAQ8QABaW8BAAD0NQwAAAEAUwABACOBAqDgAAAUXWV5AQ0AOjAAAAEAAAAAAAcAEAAAAF8TYBP7E/wTNRQ2FIUUhhQBgQKwQBN/AAGgwAAAFD9lbwECADowAAABAAAAAAAIABAAAADvD/APohEBEwIT/xOIFL0UvhTzAQFvOzAAAAEAAAkABABsCQEArxADAPEQAQD9EwEA84ADbW5yAQAAOzAAAAEAAAYAAgCcEwAABBQFAEOAA7BAFHwAAaAgAAANDGVycyMAArAwE+AAAWNy8soTAAABAAGBArCAEywAAaEAAAAUh2NmAQsA9IgTAAABAAEAAQABgAKgAAAAEKWgAAAAEKY0NvMCAXYBEw0AAPMAAmRsAScJAADX/jkAkrCgFCkAAbBwEe4AAbCAEwsAAaCwAAATRZ3dfzswAAABAAAXAAsArAEBALwEAwCDBQIABgcBABAHAgBGDAAACBAAAG8RAADeEQIAuhICAG4UAAA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/3a68c92a1996.js b/pr/617/docs/search.index/3a68c92a1996.js new file mode 100644 index 000000000..6ce95f3a5 --- /dev/null +++ b/pr/617/docs/search.index/3a68c92a1996.js @@ -0,0 +1 @@ +rn_("AQAA9DkTAAABAFgBAQDzBAFzOzAAAAEAAA0AAgC0EwcArBQFAEcAAAWGoIAAABOUsQAUuwABsEAS9AABsIAUywABsFATOwABFQAW9KcOAAABAAEAAQBDAISgIAAACTug0AAAE6CgMAAACRsVAATzAwIAAAEAegU=") \ No newline at end of file diff --git a/pr/617/docs/search.index/3a70741f4557.js b/pr/617/docs/search.index/3a70741f4557.js deleted file mode 100644 index c32ec02bc..000000000 --- a/pr/617/docs/search.index/3a70741f4557.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACoCAAAA/joDAAABPpYXLzAAF0AYkHAAABBAGgQAAAFHZl8pgUAAABAAEFADswAAABAAALAAUAaRAFAOISAQAQFAEAaRQAAIgUAADjAISgMAAAD+GFAAI7MAAAAQAACwAFAKoFAgDHBgQAaAwBAJsRAAD0EgAA") \ No newline at end of file diff --git a/pr/617/docs/search.index/3a88f1aefd64.js b/pr/617/docs/search.index/3a88f1aefd64.js deleted file mode 100644 index f2c7c0105..000000000 --- a/pr/617/docs/search.index/3a88f1aefd64.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQADoKAAABRqoCAAABOhsDAR+wABZWl08ggTAAABAAEDADswAAABAAAGAAEAdBEGAAEIAPQLFAAAAQCLAAUAEwEDsJAUdAABsFATUAFnZ2xwOzAAAAEAABIACQB5EAIA/hEBAD8TAQBgEwEAlxMBAPwTAQA2FAEARRQBAIYUAQABgAKwMBH3AAGwMBH5AAFydgECAbAAEsgAAWQ7MAAAAQAABQACAPMRAwD3EgEAI4ECoJAAABRpaW8BBwD0hRQAAAwAIgARAHcLAAGIsFASygEUsGATTgABlQkDOjAAAAEAAAAAAA4AEAAAAO8M8AzxDKwOrQ7fEOAQHBE/EZ4RnxGgEaERRhN/EyOAAqAQAAAM7m5v") \ No newline at end of file diff --git a/pr/617/docs/search.index/3ab1ec480043.js b/pr/617/docs/search.index/3ab1ec480043.js deleted file mode 100644 index b4b54b10a..000000000 --- a/pr/617/docs/search.index/3ab1ec480043.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUAOzAAAAEAAAcAAwDbEQIAjRMBACQUAgABBADz3hEAAAEAAQAjAAKgYAAAEw5hcvP/EAAAAQDLAQGAAqEQAAAUs7CAFKkAAWl1NwQAgYexUBTQAAGgAAAAEN6wcBPtAAGhMAAAFMQKQBsjgAKgMAAAEqxudg==") \ No newline at end of file diff --git a/pr/617/docs/search.index/3ace8391b89d.js b/pr/617/docs/search.index/3ace8391b89d.js deleted file mode 100644 index 0725e8991..000000000 --- a/pr/617/docs/search.index/3ace8391b89d.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMAOzAAAAEAAI4AAgB9BwAAhAmNAAGEArBwFEkAAaBAAAARKGlzAQcA8wERAAABABYBI4ACoLAAABLoYXQBAAOwMAkxAAGwMBOoAAGgIAAAB35hZG8BFwIAADcDAICGoMAAABRBsAAUEgABAJgV") \ No newline at end of file diff --git a/pr/617/docs/search.index/3ae3a44a9dc2.js b/pr/617/docs/search.index/3ae3a44a9dc2.js new file mode 100644 index 000000000..127050f4d --- /dev/null +++ b/pr/617/docs/search.index/3ae3a44a9dc2.js @@ -0,0 +1 @@ +rn_("AQcA9e4TAAABAAEAegABAAEHAPPaEAAAAQABAAGAAqAAAAAHZKAQAAAJGmR48wEBZfIrEAAAAQDnAAAAhbBgDPwAAbBADG0AATIIAfTMBAAAjgLjCQEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/3b7ea92b577a.js b/pr/617/docs/search.index/3b7ea92b577a.js deleted file mode 100644 index 2a2334ab3..000000000 --- a/pr/617/docs/search.index/3b7ea92b577a.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9MARAAABAAEAAQABAQDz3hEAAAEAAQATgAIqe8KxkKZlcwEKAPTREwAAAQABAAEAAYACsCAUTwABoGAAABOeb3YBgAKwMBLXAAGgAAAAE1ZjZPOAhQUQBg==") \ No newline at end of file diff --git a/pr/617/docs/search.index/3bdcf6ef8822.js b/pr/617/docs/search.index/3bdcf6ef8822.js deleted file mode 100644 index fb6665e74..000000000 --- a/pr/617/docs/search.index/3bdcf6ef8822.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIAOjAAAAEAAAAAAA4AEAAAAB4NHw0lEQoSCxIMEscS0BLREtUS1hItEy4TZBNlEwEJAPOLEQAAAQABAAEKAPPfEgAAAQABAAEGAPVPEQAAAQABAAEAAQDzgAJkbnMAhLEgFE8AAQARA/I4EwAAAQABDQD2sRMAAAEArABYABgAAQABgAKwoBPJAAGgEAAADEdjdQEBAPTbEwAAAQBLAAEA8wABZfKACQAAAQDXAQCAxqAgAAAJB7AwEMcAAQACrAg=") \ No newline at end of file diff --git a/pr/617/docs/search.index/718da5763b19.js b/pr/617/docs/search.index/3c097b312dbc.js similarity index 57% rename from pr/617/docs/search.index/718da5763b19.js rename to pr/617/docs/search.index/3c097b312dbc.js index 727f6a9c3..cf151da7d 100644 --- a/pr/617/docs/search.index/718da5763b19.js +++ b/pr/617/docs/search.index/3c097b312dbc.js @@ -1 +1 @@ -rn_("AQIA9M4RAAABAAEAAQDzAAFl9NQQAAABAAEAAQABAwD0yhEAAAEAAQABAMcAAACFsKATLQABsCAMNQABsIATDAABCAwG8xAHAAABAAEA") \ No newline at end of file +rn_("AQIA9M4RAAABAAEAAQDzAAFl9NQQAAABAAEAAQABAwD0yhEAAAEAAQABAMcAAACFsKATLAABsCAMNQABsIATCwABCAwG8xAHAAABAAEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/3c4c9ddb2e7a.js b/pr/617/docs/search.index/3c4c9ddb2e7a.js new file mode 100644 index 000000000..8551d2360 --- /dev/null +++ b/pr/617/docs/search.index/3c4c9ddb2e7a.js @@ -0,0 +1 @@ +rn_("AYACsAAQMgABsCAMbwABZWkBiQKwIBSBAAGgMAAAFItpcBMAA6AgAAATPaCAAAATUGVpdfUiDAAAOQABAAEAAQABhgKwkBSoAAGgAAAAEspiZAGAArAwEzEAAaAwAAARCHJ21wAAAIWgMAAAEHygMAAAE4cQARM6MAAAAQAAAAAACgAQAAAACwkMCQ0JPQk+CTAMlxCeEFgRWRENE2OAA7AQCP4AAWRtdA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/3c80b5c41902.js b/pr/617/docs/search.index/3c80b5c41902.js new file mode 100644 index 000000000..ace36b28c --- /dev/null +++ b/pr/617/docs/search.index/3c80b5c41902.js @@ -0,0 +1 @@ +rn_("AQYAOzAAAAEAAAkAAQBeEQkAI4ADsGATkgABsDAP3AABaXN0AQgA9VIUAAABAAEAAQABAPMBAWb1qhAAAAEAAQABAAEAY4ADoDAAABNUZWd0") \ No newline at end of file diff --git a/pr/617/docs/search.index/3cb92ce7537c.js b/pr/617/docs/search.index/3cb92ce7537c.js deleted file mode 100644 index e332f9a14..000000000 --- a/pr/617/docs/search.index/3cb92ce7537c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQoA9LMTAAABAA8AAQABgwKgIAAAEFygMAAAEPhpdAEAAPQfEwAAAQABAAEA8wQBZPTDEgAAAQABAAEAI4aEsKAUZwABsEATHQABsHAUDgABFYAAAQACsDAQTgABsBATywABc3TymwUAAI8GAYACsMAUlQABoAAAAAdadHnzgIURwQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/3cea80f3171e.js b/pr/617/docs/search.index/3cea80f3171e.js new file mode 100644 index 000000000..d2a6cb4ff --- /dev/null +++ b/pr/617/docs/search.index/3cea80f3171e.js @@ -0,0 +1 @@ +rn_("AQQA9hsNAAABAD4DAQAuAQEAAQoA8+USAAABAKoAAQEA9y8JAAABAF0HAQBzAAEAFQEjgAKwYBP9AAFpbwECAPTYDAAAAQBNBgEAAQUA9BMUAAABAFAAAQD3BAAAh6AgAAAM2rCQEgoAAQScAzowAAABAAAAAAAIABAAAACZBSAJIQl0DHUMERESERcTGBM=") \ No newline at end of file diff --git a/pr/617/docs/search.index/3d02f4499ad1.js b/pr/617/docs/search.index/3d02f4499ad1.js new file mode 100644 index 000000000..577574db7 --- /dev/null +++ b/pr/617/docs/search.index/3d02f4499ad1.js @@ -0,0 +1 @@ +rn_("AQcAOzAAAAEAAI4AAgAHEQAAGBKNAAEDADswAAABAACNAAEAGA6NAAECADswAAABAAAHAAEAUwwHAPMAA2JpdDswAAABAACOAAIAogQAAN4EjQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/3dd7e7b549d9.js b/pr/617/docs/search.index/3dd7e7b549d9.js new file mode 100644 index 000000000..5e7077dcb --- /dev/null +++ b/pr/617/docs/search.index/3dd7e7b549d9.js @@ -0,0 +1 @@ +rn_("AQEA9VoJAAABAAEAAQABAPMAAWnz/gwAAMMHAQABAQGgQAAAFDRl8qoUAAABAAEEAqAgAAAR7aAwAAASuWJoAd4PAADnAACAxaAQAAAM/6AwAAAQjAQQKAE=") \ No newline at end of file diff --git a/pr/617/docs/search.index/3dda4acb68c1.js b/pr/617/docs/search.index/3dda4acb68c1.js deleted file mode 100644 index 2bb5fdfcc..000000000 --- a/pr/617/docs/search.index/3dda4acb68c1.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQkA848UAAABADsAAQ0BoKAAABTZd/LJFAAAAQABgQKwgBSfAAGgYAAAFMdkczcBAIaFoEAAABRysGAUQgBQApAK") \ No newline at end of file diff --git a/pr/617/docs/search.index/3e1261c016d6.js b/pr/617/docs/search.index/3e1261c016d6.js deleted file mode 100644 index dc9ec5a45..000000000 --- a/pr/617/docs/search.index/3e1261c016d6.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9DoTAAABAFgBAQDzBAFzOzAAAAEAAA0AAgC1EwcArRQFAEcAAASGoIAAABOVsQAUvAABsEAS9QABsIAUzAABsFATPAABFQAW9KcOAAABAAEAAQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/3f252083b9d3.js b/pr/617/docs/search.index/3f252083b9d3.js deleted file mode 100644 index 87e686712..000000000 --- a/pr/617/docs/search.index/3f252083b9d3.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAAOzAAAAEAAAYAAgDGEQMA0hICAAEKAbBAFHcAAWX1OxQAAAEAAQABAAEA8wACaXP0NQwAAAEAUwABACOBAqDgAAAUXmV5") \ No newline at end of file diff --git a/pr/617/docs/search.index/402299f94e1a.js b/pr/617/docs/search.index/402299f94e1a.js deleted file mode 100644 index 43a4e2caf..000000000 --- a/pr/617/docs/search.index/402299f94e1a.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQ0A9e8TAAABAAEAegABAAEHAPPXEwAAbAABACOAAqEQAAAUKW5yAQcA9MYRAAABAAEAAQAzAASgIAAACTiw4BQeAAExZWlyOjAAAAEAAAAAAAkAEAAAAGEEYgRYBxoJfQx+DH8MgAxoEWkRAQIA9BoRAAABAJQBAQABAwD0hREAAAEAfgEBAPMAAmJ1ASoQAAABAwD0GBEAAAEAlAEBAPOEAmRzAQIA8xwJAAABAFgA84ACaXQBGADz1hQAAAEAAQAjgQKwwBPtAAFjbQENAbBAFHcAAWX1OxQAAAEAAQABAAEAE4ICsAARMAABa3MBBAD0vhAAAAEAAQABAAGBAqAgAAARK7BAE4IAAWVp84ICZXQBBAGwYBLXAAFy9DIMAAABAPUDAQDzgANhZW8BCQD2AhMAAAEA/QCJADUAAQATgAKwYBDYAAFkbgEGADswAAABAAAFAAIAAxEDAJsTAQABAwGgcAAAEz5n9GUHAAABAA0CAQABCQDzjxQAAAEAOwABgQKwgBSfAAGgYAAAFMdkcxcBAIiFsNAUyQABoEAAABRysGAUQgBQApAKAQQA9NsTAAABAEsAAQBTgAOwkBTCAAFlaG8BAAGgQAAAEZJlAdoPAAAjgQKwUBE9AAFrdHfsAwCOoZAAABS3sLATTAABoKAAABMBsVAUwAAB/cEPOzAAAAEAACEACACCAAAAiAADAHYFAQBBBwcAQwkHAFMMBwBjEAEArBIAAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/402f200cf6ef.js b/pr/617/docs/search.index/402f200cf6ef.js deleted file mode 100644 index 2f4b0d93c..000000000 --- a/pr/617/docs/search.index/402f200cf6ef.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9IkTAAABAAEAAQDzBgFz8g4UAAABAAEBAbBQEwoAAWX3IwkAAFUDAQAQBAEA3QMBAAEDAPTOEQAAAQABAAEAAQMA9MoRAAABAAEAAQDzgwJsc/OAAmN0U4ADoBAAAAzaYW9y") \ No newline at end of file diff --git a/pr/617/docs/search.index/409d99102e7c.js b/pr/617/docs/search.index/409d99102e7c.js new file mode 100644 index 000000000..301e80ac0 --- /dev/null +++ b/pr/617/docs/search.index/409d99102e7c.js @@ -0,0 +1 @@ +rn_("AYACsAARjgABsAARkAABNDYBCwA7MAAAAQAABgABAC0UBgAjgAKggAAAFFhlbyMCA6BgAAAT67BAFMgAAWVucAE0DAAA8wACYXYB6QYAAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/413ca794b790.js b/pr/617/docs/search.index/413ca794b790.js deleted file mode 100644 index d9c18e3ea..000000000 --- a/pr/617/docs/search.index/413ca794b790.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQACoCAAABOhsDAR+wABaXTyCBMAAAEAAQMAOzAAAAEAAAYAAQB0EQYAAQgA9AsUAAABAIsABQATAQOwkBR0AAGwUBNQAWdnbHA7MAAAAQAAEgAJAHkQAgD+EQEAPxMBAGATAQCXEwEA/BMBADYUAQBFFAEAhhQBAAGAArAwEfcAAbAwEfkAAXJ2AQQBsAASyAABZDswAAABAAAFAAIA8xEDAPcSAQC3BQABh7BQEsoBFLBgE04AAZQJAzowAAABAAAAAAALABAAAADvDPAM8QysDq0OHBGeEZ8RoBGhEUYTfxMjgAKgEAAADO5ubwEDAPN2EAAAAQABACOAAqAAAAAMIGRyYwCEoFAAABGboDAAABC0FAEQ8qQHAAABAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/414c4c18736c.js b/pr/617/docs/search.index/414c4c18736c.js new file mode 100644 index 000000000..312b0fda7 --- /dev/null +++ b/pr/617/docs/search.index/414c4c18736c.js @@ -0,0 +1 @@ +rn_("AYICoFAAABLzoQAAABSnY3QjAAKwQA/lAAFldTswAAABAAA3ABYAzwYBAOUMAQAHDQEA6g8BAGkQBQCfEAUAHREBAHsRAQAMEgIA4RIDABITBABDEwEASRMBAFgTAQDeEwEA8RMBAA8UAQATFAEAORQAAGQUAQBoFAEAhxQAAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/41a331e65a3c.js b/pr/617/docs/search.index/41a331e65a3c.js deleted file mode 100644 index 0caae780b..000000000 --- a/pr/617/docs/search.index/41a331e65a3c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIBoKAAABN+ZPL9BgAAAQABgQKgMAAAFISgUAAAFA1jcwEEA7AgEVwAAaBQAAASCaBAAAAUTGxzdvJHFAAAAQDzAANhZW87MAAAAQAACAADAIwEAQAWDAAAIQ0FAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/41d5f31e57ca.js b/pr/617/docs/search.index/41d5f31e57ca.js new file mode 100644 index 000000000..8f2ccccce --- /dev/null +++ b/pr/617/docs/search.index/41d5f31e57ca.js @@ -0,0 +1 @@ +rn_("AYACoAAAABCloAAAABCmNDYBgAOwABP0AAGwABP2AAGwcBRvAAE0NmEBBAD0xBQAAAEACgABACOBAqBAAAARbm1wAQEAOzAAAAEAAAgAAwDnEgAAWhMAANEUBgDHAQACxaBAAAAQaKCAAAASwUAGgAg7MAAAAQAAFgAIAAYQAQAmEQEAahEDAOERBQDQEwMAIBQAAH8UAQCzFAEA8wACZXY6MAAAAQAAAAAAFQAQAAAAEwIUAhUCtAUTDcMPxA/hD/sPjxCQEJEQkhBAEUER2BLZEtoS2xLWE0IUQxQBgQKwcBFqAAGwcBFsAAE0NiOAArAQB2sAAW5wAQMCsEATMQABoDAAABOHZXf0lxAAAAcAugABABOBAqBwAAATwWdvAQQAOzAAAAEAAAYAAgAWDAAAIQ0FAGOAA7BQEAYAAWRzdFcBAACFoBAAABBosSAUfwABESEBOzAAAAEAAAoAAwB/AAAAlQAFAOcRAwA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/41ecd022fb75.js b/pr/617/docs/search.index/41ecd022fb75.js deleted file mode 100644 index 0d3d49316..000000000 --- a/pr/617/docs/search.index/41ecd022fb75.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABsKAS5AABZTswAAABAAAJAAIA8QcHACUTAQABAAGgIAAADPttAYgFAAABgAKwoBLiAAGwQA/qAAFsbwEAAPQfEwAAAQABAAEA8wQBZPTDEgAAAQABAAEAIwKEsKAUZwABsEATHQABsHAUDgABFYAAATkUAAABAAGwgBRNAAFw8kkUAAABAEMAA6AgAAARcKBAAAASx2Rlc/MJEAAATgMBAPOBAmVp1wIAAIawIBKoAAGw0BRRAAEXAQL0pAQAAAEAAQDKAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/42424466438e.js b/pr/617/docs/search.index/42424466438e.js deleted file mode 100644 index fa0704bd8..000000000 --- a/pr/617/docs/search.index/42424466438e.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQBoEAAABQ1ZfKrFAAAAQABAQA7MAAAAQAABgABANIUBgDzBgFl9IAUAAABADMAAQAzAQOwYBOWAIphcnXzixAAAHsCAQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/42c3eb12af93.js b/pr/617/docs/search.index/42c3eb12af93.js new file mode 100644 index 000000000..ef658806f --- /dev/null +++ b/pr/617/docs/search.index/42c3eb12af93.js @@ -0,0 +1 @@ +rn_("AQACoCAAABOgsDAR+gABaXTyBxMAAAEAAQMAOzAAAAEAAAYAAQB0EQYAAQgA9AoUAAABAIsABQATAQOwkBRzAAGwUBNPAWdnbHA7MAAAAQAAEgAJAHkQAgD9EQEAPhMBAF8TAQCWEwEA+xMBADUUAQBEFAEAhRQBAAGAArAwEfYAAbAwEfgAAXJ2AQQBsAASxwABZDswAAABAAAFAAIA8hEDAPYSAQC3BQABh7BQEskBFLBgE00AAZQJAzowAAABAAAAAAALABAAAADvDPAM8QysDq0OHBGeEZ8RoBGhEUUTfhMjgAKgEAAADO5ubwEDAPN2EAAAAQABACOAAqAAAAAMIGRyYwGEoFAAABGboDAAABC0FAEQ8qQHAAABABOAA7BgEusAAbAAD78AAWNnchOAA6CwAAAUKKBAAAAOq2Vvcw==") \ No newline at end of file diff --git a/pr/617/docs/search.index/42ce13ba00cf.js b/pr/617/docs/search.index/42ce13ba00cf.js new file mode 100644 index 000000000..81124a38c --- /dev/null +++ b/pr/617/docs/search.index/42ce13ba00cf.js @@ -0,0 +1 @@ +rn_("AYICsEAS2AABsEAS2gABNDYBCwDz1hMAAGwAAQABBgD0xBQAAAEACgABAPMCAXIBIBQAAPMDAWU7MAAAAQAABQACAI8QAwBAEQEA84ADYWVp") \ No newline at end of file diff --git a/pr/617/docs/search.index/42d8f48ef067.js b/pr/617/docs/search.index/42d8f48ef067.js new file mode 100644 index 000000000..d7979086e --- /dev/null +++ b/pr/617/docs/search.index/42d8f48ef067.js @@ -0,0 +1 @@ +rn_("AQMA83YQAAABAAEAAYACoLAAABQ3sDATgQABbnRDAAOgIAAAESuwUBICAAFlaGk7MAAAAQAABgADACAMAAATEgMA4BMBAPOAAmRy") \ No newline at end of file diff --git a/pr/617/docs/search.index/42deef5af9ea.js b/pr/617/docs/search.index/42deef5af9ea.js new file mode 100644 index 000000000..71bffcb21 --- /dev/null +++ b/pr/617/docs/search.index/42deef5af9ea.js @@ -0,0 +1 @@ +rn_("AYACoFAAABFVoFAAABKmYXUBgwKxABTNAAGw0BS9AAFrbQELADswAAABAAAGAAEALRQGACOAAqCAAAAUWGVvIwMDoGAAABPrsEAUyAABZW5wATQMAAAjgAKgQAAAEOVtcAELAPMPFAAAAQB3APcAAACFoQAAABSJBIEDOzAAAAEAAD0ABADABQUAWQcAAKwHNQCVEQAA") \ No newline at end of file diff --git a/pr/617/docs/search.index/4314812a0c42.js b/pr/617/docs/search.index/4314812a0c42.js new file mode 100644 index 000000000..0a56ff6d5 --- /dev/null +++ b/pr/617/docs/search.index/4314812a0c42.js @@ -0,0 +1 @@ +rn_("AQkBoIAAABTHZgGmEwAAAQIA9esQAAABAAEAXgIBAPMAAml1OjAAAAEAAAAAAAcAEAAAAMYQABPsE+0THRQeFD8UQBQBCAD1UhQAAAEAAQABAAEA8wIBZvWqEAAAAQABAAEAAQATgAKwoBScAAFydPMCAmFz89oQAAABAAEAAQQBsEAUdgABZfU6FAAAAQABAAEAAQABAgA7MAAAAQAACwACAOITBQCgFAUA8wMBbTswAAABAAANAAQA4AwAAH0RAAD6EgUAXhQFAPOBAmNl84ACYWU=") \ No newline at end of file diff --git a/pr/617/docs/search.index/4399c1a05fce.js b/pr/617/docs/search.index/4399c1a05fce.js new file mode 100644 index 000000000..34026253f --- /dev/null +++ b/pr/617/docs/search.index/4399c1a05fce.js @@ -0,0 +1 @@ +rn_("AYACsIATGQABsKATkgABaXQBgAKw4BSUAAGwQBBjAAFscgEGAPT5EAAAAQABAAEARwUAAIeg0AAAFG6wUBBhAAGgAAAAE5Sw8BRIAAEFkQPyBRAAAK8A") \ No newline at end of file diff --git a/pr/617/docs/search.index/444cc3e74ea7.js b/pr/617/docs/search.index/444cc3e74ea7.js deleted file mode 100644 index 7112b9c08..000000000 --- a/pr/617/docs/search.index/444cc3e74ea7.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYICsEAUdAABsGATSAABZXIBBQD0rxAAAAEAAQABAPOAAmZtAQYAOjAAAAEAAAAAAAgAEAAAAO8P8A+iEQITAxMAFIkUvhS/FAEAArCwFBgAAaFAAAAUt2Vn9IUHAAABAAEAAQDDAISgsAAAE+mwQBDHAAEUIAT3+gYAAOQFgQMBADoBMAEUAWOAA7AwECsAAWFlaQ==") \ No newline at end of file diff --git a/pr/617/docs/search.index/44610b5c95d7.js b/pr/617/docs/search.index/44610b5c95d7.js new file mode 100644 index 000000000..c50cb2a02 --- /dev/null +++ b/pr/617/docs/search.index/44610b5c95d7.js @@ -0,0 +1 @@ +rn_("AYECoBAAABNVoHAAABOdYWMBhwKwABP0AAGwABP2AAE0NlMAhKCQAAATfbCwFBsAAYEACfUuBwAANgBFCREBIAA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/44dab7c8fd35.js b/pr/617/docs/search.index/44dab7c8fd35.js new file mode 100644 index 000000000..a87808910 --- /dev/null +++ b/pr/617/docs/search.index/44dab7c8fd35.js @@ -0,0 +1 @@ +rn_("AQIBsEAUjAABdDswAAABAAANAAYADhMDAHkTAQDpEwEAABQBAIEUAQCoFAEAE4ACsDATQgFJaXABBQDzvhMAAAEAAQATgAKgMAAAFBlleVMBA7BwFCYAAWJlcwFQEAAAE4ADsDAUnAABsCAHBgABZWxz") \ No newline at end of file diff --git a/pr/617/docs/search.index/4591acbbfc75.js b/pr/617/docs/search.index/4591acbbfc75.js new file mode 100644 index 000000000..e57c47ff2 --- /dev/null +++ b/pr/617/docs/search.index/4591acbbfc75.js @@ -0,0 +1 @@ +rn_("AQsA9IQUAAAMACIAEQABCgA6MAAAAQAAAAAABwAQAAAAXxNgE/sT/BM1FDYUhRSGFPOBAm90EwCEsCATUwBasDARWgABoFAAABFCEDACOzAAAAEAAAgABACFAAEArwADAGIHAQB+CQAA") \ No newline at end of file diff --git a/pr/617/docs/search.index/45ca0364edb8.js b/pr/617/docs/search.index/45ca0364edb8.js deleted file mode 100644 index 85866c7db..000000000 --- a/pr/617/docs/search.index/45ca0364edb8.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsAAQMgABsCAMbwABZWkBiQKwIBSCAAGgMAAAFIxpcBMAA6AgAAATPqCAAAATUWVpdfUiDAAAOQABAAEAAQABhgKwkBSpAAGgAAAAEstiZAGAArAwEzIAAaAwAAARCHJ21wAAAIWgMAAAEHygMAAAE4gQARM6MAAAAQAAAAAACgAQAAAACwkMCQ0JPQk+CTAMlxCeEFgRWREOE2OAA7AQCP4AAWRtdA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/45e1f3021919.js b/pr/617/docs/search.index/45e1f3021919.js new file mode 100644 index 000000000..ee2349a46 --- /dev/null +++ b/pr/617/docs/search.index/45e1f3021919.js @@ -0,0 +1 @@ +rn_("AQAAOzAAAAEAAAYAAgCcEwAABBQFACOIArBAFHwAAWVzAQQBsDATbAABY/OWEQAAAQABAAEFADswAAABAAAIAAMADBICABITBAA5FAAA8wMDY2Vw89sMAAABAAEAAQABsGAUHQABZQFXFAAA8wIBc/YzCQAAAQABACAIPwAjAQECAaBQAAARlXIBNhEAAPOAA2ZtcA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/45e3802642d1.js b/pr/617/docs/search.index/45e3802642d1.js new file mode 100644 index 000000000..47752bd23 --- /dev/null +++ b/pr/617/docs/search.index/45e3802642d1.js @@ -0,0 +1 @@ +rn_("AQUA9NwSAAABAKYAAQABCAD0mBMAAAEAiAABADMAA7BAE2cAAWFiZTowAAABAAAAAAAMABAAAAA7BzwHPQcFDQYN2w9JEJgQmRCaEJsQtBKGEw==") \ No newline at end of file diff --git a/pr/617/docs/search.index/46027ae6580d.js b/pr/617/docs/search.index/46027ae6580d.js new file mode 100644 index 000000000..dd9fdab75 --- /dev/null +++ b/pr/617/docs/search.index/46027ae6580d.js @@ -0,0 +1 @@ +rn_("AQMA9hINAADqAloAAQDRAgEAAQQAOzAAAAEAAAoABACfEAUA4xIBAEMTAQBpFAAAAYYCoAAAABNloAAAABNmZHLzAgF2AZETAADzgAJlbycBAADHoDAAAA/7oHAAABKzoIAAABQ4sNAT1wABoLAAABQ4QEUICTswAAABAAAJAAMAxgUCACkHBABrDAEAI4ACQs4TugDPZHIBAAA6MAAAAQAAAAAACgAQAAAAYQliCTEQfxOAE7ATsRNdFLUUzRTOFBOAAqAwAAAQtWx084ACYWU=") \ No newline at end of file diff --git a/pr/617/docs/search.index/4634daa4cefd.js b/pr/617/docs/search.index/4634daa4cefd.js new file mode 100644 index 000000000..02b580e0e --- /dev/null +++ b/pr/617/docs/search.index/4634daa4cefd.js @@ -0,0 +1 @@ +rn_("AQIBoQAAABSQZPJqBwAADwIBBgGgMAAAEf90Ab0TAAABAwGwUBMJAAFl9SMJAABVAwEAEAQBAAEBADswAAABAAAFAAEAtwUFAAEBAaEAAAAUhGTytgUAAEgClwcAAIewoBMHAAGg4AAAFFcegArzYwQAAAEAAQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/466cc5f494e9.js b/pr/617/docs/search.index/466cc5f494e9.js deleted file mode 100644 index d5f725f2e..000000000 --- a/pr/617/docs/search.index/466cc5f494e9.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9BQUAAABAFAAAQDzAwFj8hgTAAABACOAArBwFMwAAWhvAYICsAAS9QABsAAR+wABZXNDAAOgoAAAFGqgIAAAE6FlaXT1CBMAAAEAhgABAPoAAYACsHAS7AABoLAAABQpZW8BAgA7MAAAAQAABgABAHQRBgABAgGgQAAAFEtl8nkUAAABAPOAAmlvAQgA9AsUAAABAIsABQATAQOwkBR0AAGwUBNQAWdnbHA7MAAAAQAAEgAJAHkQAgD+EQEAPxMBAGATAQCXEwEA/BMBADYUAQBFFAEAhhQBAAGAArBAEsoBFKAgAAAROGFpAQMA8+kSAAABAAEAI4ECsDAUHAABY2kBAAD0OhMAAAEAWAEBAPMDAXM7MAAAAQAADwADAPkRAQC1EwcArRQFAAEDAPX3EQAAAQAlAQEAYQHzgAJydgEAADswAAABAAAFAAIAyBIBAB8TAwDzAgFkOzAAAAEAAAkAAwDzEQMAwxIDAPcSAQAjgAKgkAAAFGlpbxOAArDwFLwAAWxvAQUA9IUUAAAMACIAEQAjgAKgYAAAE5VncyOAArCQFGcAAWJs9xcDAI+wcBIRAAGwYBSdAAGwcBQOAAGwYBNOAAGwUBM8AAG1+Rc7MAAAAQAAPAAYAL0FAAB0BwEAAAkFABEJAAA/CQAAIQwAAG8MAQDvDAIAGQ0BAKcOAwCsDgEADRAEAN8QAQAcEQAAPxEAAF4RCQCcEQAAnhEDAPERAABGEwAAXRMBAG8TBQB/EwAAORQAAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/46edaca34104.js b/pr/617/docs/search.index/46edaca34104.js deleted file mode 100644 index 6f028beff..000000000 --- a/pr/617/docs/search.index/46edaca34104.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9W4JAAABAAEAAQABACOAAqCgAAAUp2ZwRwAAAIWwMA/DAAGgUAAAEIiwcBEWAAGxQBTSAAGBKQDyegUAAAEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/473145eca66b.js b/pr/617/docs/search.index/473145eca66b.js deleted file mode 100644 index a85396f68..000000000 --- a/pr/617/docs/search.index/473145eca66b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQECsCANAgABsAAMegABZXPybQcAAAEAAQUA8y8TAAABAAEA8wQBcvT0DwAAAQDuAAEAE4ACoLAAABRvYXPzgAJiYw==") \ No newline at end of file diff --git a/pr/617/docs/search.index/4752f68ec378.js b/pr/617/docs/search.index/4752f68ec378.js new file mode 100644 index 000000000..c944580a0 --- /dev/null +++ b/pr/617/docs/search.index/4752f68ec378.js @@ -0,0 +1 @@ +rn_("AQMBgBOOABD6czswAAABAAAFAAEAbhMFACOCAqCQAAAUcmlzBwEAhMWwUBM3AAGgoAAAE9mwcBOeAAGg0AAAFH6AARgI") \ No newline at end of file diff --git a/pr/617/docs/search.index/479912951a6e.js b/pr/617/docs/search.index/479912951a6e.js new file mode 100644 index 000000000..0a4919c53 --- /dev/null +++ b/pr/617/docs/search.index/479912951a6e.js @@ -0,0 +1 @@ +rn_("AYMCsQAUzQABsNAUvQABa20BCwA7MAAAAQAABgABAC0UBgAjgAKggAAAFFhlbyMEA6BgAAAT67BAFMgAAWVucAE0DAAAAQsA8w8UAAABAHcAdwAAAIWgYAAAEqahAAAAFIkEgQM7MAAAAQAAPQAEAMAFBQBZBwAArAc1AJURAABDgAOwgBGUASOgAAAAAhhka3Q=") \ No newline at end of file diff --git a/pr/617/docs/search.index/47cc59b03f79.js b/pr/617/docs/search.index/47cc59b03f79.js deleted file mode 100644 index e84a50832..000000000 --- a/pr/617/docs/search.index/47cc59b03f79.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBgBOPABD6czswAAABAAAFAAEAbxMFACOCAqCQAAAUc2lzAQEA9DYTAAABAJAAAQAjgAKwoBQcAAFjbgGEArCgFCoAAbCgFCwAAWNyAQIAOzAAAAEAAAYAAQBvEAYAE4ICoCAAABQNZXUBAgDz3hEAAAEAAQABgAKwIBRPAAGgYAAAE55vdmOBA6AQAAATVmFjcwEDADswAAABAAAFAAEA5wwFAAEBAPMrCQAAAQABAAEBAPQOEQAAAQCaAAEAU4ADoMAAABR/bnB1AYACsDAQKwABoWAAABS3YWUBgQKwQBLZAAGwQBLbAAE0NgEFAaBQAAAUOGjyzBIAAAEAAQUA9IUQAAABALMAAQBzgISwMBOCAAEBoAQBCADzfhMAAAgAHAATgAKwQBIDAAFhZQEKAPPXEwAAbAABAAEAAaAgAAAQp3IBcBEAAAEAAPTbEwAAAQBLAAEApwEAgIWgMAAAEQugEAAAESsJICEBCQGgQAAAFHZl8pgUAAABAEOAhKAgAAAUhKCQAAAT2qEwAAAUlxBJAAECAPRqEwAAAQCYAAEAE4ACsEATOAABaXkBAQKgMAAAEw6gQAAAE1VncjswAAABAAAHAAIADBEBAKMRBQABgAKwABP1AAGwABP3AAE0NgEGAPTFFAAAAQAKAAEA8wICcnYBIRQAAPMCAWU7MAAAAQAABQACAI8QAwBAEQEA84ACZHTnrT8AEqAwAAARLKBAAAAM/rDwFMIAAbBwE58AATBhYmNlZ2hpa2xub3Byc3R1djswAAABAAAcAUYAxAQAAK0FAQC1BQAAtwUFAMwGAAAuBwAAVAcCAGQHAAB2BwUAfQcAAOIHAAAHCAEADwkBABUJAQAiCQAAgAkBAIQJjQARDAMAIAwAACQMAwAxDAAAagwAAHcMAADBDwEA7A8BAAQQAABoEAAAlRABAKkQAAC2EAEA/xABAAMRAwAWEQEATREBAG4RAACLEQIAuhEAANoRAAD9EQAAARIBAAcSAQATEgQAuRIAAMgSAQDLEgAA1xIBAOkSAgAfEwMANBMBAEETAQBIEwEAThMBAFETAABUEwAAZhMAAHwTAQCbEwEAqhMEAMUTAQDYEwEA2xMBAOETAQDsEwAAJxQCAC4UBwBNFAEAWRQAAHAUAQCaFAEA2RQAAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/4848ba157001.js b/pr/617/docs/search.index/4848ba157001.js deleted file mode 100644 index 2accf975b..000000000 --- a/pr/617/docs/search.index/4848ba157001.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYMCoFAAABEkoKAAABNfa3ABAAGwQBKqAAFs9HoJAAABAHgHAQABCAD0vhIAAAEAAQABAAEEAbAAEP0AAXMBZRAAAAECAaBQAAATHGMB3wwAALcAAACFsEANHgABoDAAAAwhlQABOzAAAAEAAAgABADJBAAAnQUCAOIMAQDUDwIAJwEAgIWwMAk5AAGgEAAAE5WwIAeCAAGRAEQBEAGwABSTAAFzOzAAAAEAAAUAAQCtFAUAAQMA9HoJAAABAHgHAQBjgAOgAAAADCFucnUBAAKw0BPbAAGgMAAAD9diavaABQAAAQABAE0AAQB5AQEAAbCAE/oAAXQ7MAAAAQAARQAEAM0FAAAkCQAALgkAAJUMQgABCQDz3xIAAAEAAQDzgANjZ3QBgwKgkAAAFEegkAAAFEgyMwGAAqBQAAAUc6CAAAATkmNl8wACc3TySgcAAAEAAQABoLAAABPdYvNnEwAALgEBACOAAqAAAAATZmRyI4ACoFAAABQ5Y2XzgAJpeQEEAPVPEQAAAQABAAEAAQDzAQFuAUAHAAABAQA7MAAAAQAACQACAPEHBwAlEwEAAQYA9M0TAAABAAEAAQABDwD0uBQAAAEAAQABAPOBAmhtAQABsEAUdwABZfU7FAAAAQABAAEAAQBTgAOgIAAAEfJsbnTXbQCAy6AgAAAUqLBgEOEAAbBAEA0AAcAbOAo=") \ No newline at end of file diff --git a/pr/617/docs/search.index/492c490fc1ab.js b/pr/617/docs/search.index/492c490fc1ab.js deleted file mode 100644 index 8f2f8396f..000000000 --- a/pr/617/docs/search.index/492c490fc1ab.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYQCsCARlAEkoJAAABQ6bXABgAKgMAAAESCgUAAAE91jcpcAAACFsCATqAABsMAUbQABoGAAABHxEEAOOzAAAAEAABIABgDOBAgA7xEBAAwTAQD6EwEAURQBAMwUAQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/497fe4bb728c.js b/pr/617/docs/search.index/497fe4bb728c.js deleted file mode 100644 index 2e616ca66..000000000 --- a/pr/617/docs/search.index/497fe4bb728c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQACoCAAABHuoDAAABK6YmgB3g8AAAETAbAAFJMAAXM7MAAAAQAABQABAK0UBQABBwDzuxIAAAEAAQDzgANkcnQ=") \ No newline at end of file diff --git a/pr/617/docs/search.index/4989563e348d.js b/pr/617/docs/search.index/4989563e348d.js deleted file mode 100644 index 2d65b9937..000000000 --- a/pr/617/docs/search.index/4989563e348d.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQ8A9DYUAAABAE8AAQABBAD0kxAAAAEAtwABAAECADowAAABAAAAAAAJABAAAADvEfARDBMNE/oT+xNRFFIUzBTNFPMAAW/yggkAAAEAU4ADsQAUmgABaG9ww4CEoBAAABNssIATrwABBQAG") \ No newline at end of file diff --git a/pr/617/docs/search.index/49ba647cb014.js b/pr/617/docs/search.index/49ba647cb014.js deleted file mode 100644 index 31fa0f892..000000000 --- a/pr/617/docs/search.index/49ba647cb014.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIAOjAAAAEAAAAAAAgAEAAAACsMLAy9D74P+g/qEBARnxOgEwEAAPRHCQAAAQABAAEAAQUBsDATKwABaPMYFAAAAQByABODAqAwAAAQ3WNwAQMA9PgPAAABAO8AAQABgQKgAAAAEZmgEAAAD9tldfOAAmFyAQAA9M4RAAABAAEAAQABgAKgkAAAFAKwUBK2AAFid/OAAmR0E4ACsJATRAABbnLzAAJldPTUEAAAAQABAAEAAQEA9YsMAAABAAEAAQABABOAArCwE/4AAWFvIwACsEANDQABZXQ7MAAAAQAACQAEAMAEAQCGBQEAbQwBAMARAwABAgA7MAAAAQAACQACAPEHBwAlEwEAAYACsJATjQABoKAAABQBZXYTgAKxgBTUAAFjbQGCArBAFCwAAaBQAAAR/WJmE4ACoEAAABRYZHMjgAKg0AAAFKhjbvOAhBEBQAEFAbBAE2gAAWUBtRIAAEOCA6EAAAAUtqDQAAAUiWttcxOAArBgEPMAAWFyAQUAOzAAAAEAAI4AAgAHEQAAGRKNAAGCA7AgEycAAbAgEI0AAbBAEYkAAWtsc1OAA7AgFI0AAWt0dQGJhKCAAAAUx6BAAAAUcqBgAAAUkqCQAAAUywKAChOChKCgAAAUALGgFNcAAbDQFGsAAQQBCQEBAPTKEQAAAQABAAEAQ4ADsXAU0gABoNAAABRBY29zI4ACsGAQ8QABaW8BAAD0NQwAAAEAUwABACOBAqDgAAAUXmV5AQ0AOjAAAAEAAAAAAAcAEAAAAGATYRP8E/0TNhQ3FIYUhxQBgQKwQBOAAAGgwAAAFEBlbwECADowAAABAAAAAAAIABAAAADvD/APohECEwMTABSJFL4UvxTzAQFvOzAAAAEAAAkABABsCQEArxADAPEQAQD+EwEA84ADbW5yAQAAOzAAAAEAAAYAAgCdEwAABRQFAEOAA7BAFH0AAaAgAAANDGVycyMAArAwE+EAAWNy8ssTAAABAAGBArCAEy0AAaEAAAAUiGNmAQsA9IkTAAABAAEAAQABgAKgAAAAEKWgAAAAEKY0NvMCAXYBEw0AAPMAAmRsAScJAADX/jkAkrCgFCoAAbBwEe8AAbCAEwwAAaCwAAATRp3dfzswAAABAAAXAAsArAEBALwEAwCDBQIABgcBABAHAgBGDAAACBAAAG8RAADeEQIAuxICAG8UAAA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/49fd4004edab.js b/pr/617/docs/search.index/49fd4004edab.js new file mode 100644 index 000000000..9fe72b8be --- /dev/null +++ b/pr/617/docs/search.index/49fd4004edab.js @@ -0,0 +1 @@ +rn_("AQ0A9e4TAAABAAEAegABAAECAPQaEQAAAQCTAQEAAQMA9IURAAABAH0BAQDzAAJidQEqEAAAAQMA9BgRAAABAJMBAQDzhAJkcwECAPMcCQAAAQBYAPOAAml0ARgA89UUAAABAAEAI4ECsMAT7AABY20BBQGwYBLWAAFy9DIMAAABAPUDAQABCQD2ARMAAAEA/QCJADUAAQATgAKwYBDYAAFkbgEDAaBwAAATPWf0ZQcAAAEADQIBAAEJAPOOFAAAAQA7AAGBArCAFJ4AAaBgAAAUxmRzFwEAiYWw0BTIAAGgQAAAFHGwYBRBAFACkApnugAAjLBwET0AAbCwE0sAAaCgAAATALFQFL8AAbDwFB0AAW3BD/SIAAAAAQABAAEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/4a0397aaa53b.js b/pr/617/docs/search.index/4a0397aaa53b.js new file mode 100644 index 000000000..f47b0f281 --- /dev/null +++ b/pr/617/docs/search.index/4a0397aaa53b.js @@ -0,0 +1 @@ +rn_("AQIAOjAAAAEAAAAAAA4AEAAAAB4NHw0lEQkSChILEsYSzxLQEtQS1RIsEy0TYxNkEwEJAPOLEQAAAQABAAEKAPPeEgAAAQABAAEGAPVPEQAAAQABAAEAAQDzgAJkbnMAhLEgFE4AAQARA/I3EwAAAQABDQD2sBMAAAEArABYABgAAQABgAKwoBPIAAGgEAAADEdjdQEBAPTaEwAAAQBLAAEA8wABZfKACQAAAQDXAQCAxqAgAAAJB7AwEMcAAQACrAg=") \ No newline at end of file diff --git a/pr/617/docs/search.index/4a3344dc82ef.js b/pr/617/docs/search.index/4a3344dc82ef.js new file mode 100644 index 000000000..4344b1ea9 --- /dev/null +++ b/pr/617/docs/search.index/4a3344dc82ef.js @@ -0,0 +1 @@ +rn_("AQUBsEAS1AABZfL4EgAAAQABAAA7MAAAAQAAEQAEAAECAQDJEAgAxhEDANESAgABCgGwQBR2AAFl9ToUAAABAAEAAQABAEcGAACHsCARJAI6sMAUwQABoCAAAAcFsDAHYgABGIkKOjAAAAEAAAAAAAwAEAAAAF4HXwdPCS4MLww1DDYMiQyKDNgM2QwmEycT") \ No newline at end of file diff --git a/pr/617/docs/search.index/4a8e85bccb70.js b/pr/617/docs/search.index/4a8e85bccb70.js deleted file mode 100644 index 7e66c014d..000000000 --- a/pr/617/docs/search.index/4a8e85bccb70.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9BQUAAABAFAAAQDzAwFj8hgTAAABACOAArBwFMwAAWhvAYICsAAS9QABsAAR+wABZXNDAAOgoAAAFGqgIAAAE6FlaXTyCBMAAAEAAQIAOzAAAAEAAAYAAQB0EQYAAQIBoEAAABRLZfJ5FAAAAQDzgAJpbwEIAPQLFAAAAQCLAAUAEwEDsJAUdAABsFATUAFnZ2xwOzAAAAEAABIACQB5EAIA/hEBAD8TAQBgEwEAlxMBAPwTAQA2FAEARRQBAIYUAQABgAKwQBLKARSgIAAAEThhaQEDAPPpEgAAAQABACOBArAwFBwAAWNpAQAA9DoTAAABAFgBAQDzAwFzOzAAAAEAAA8AAwD5EQEAtRMHAK0UBQABAwD19xEAAAEAJQEBAGEB84ACcnYBAAA7MAAAAQAABQACAMgSAQAfEwMA8wIBZDswAAABAAAJAAMA8xEDAMMSAwD3EgEAI4ACoJAAABRpaW8TgAKw8BS8AAFsbwEFAPSFFAAADAAiABEAI4ACoGAAABOVZ3MjgAKwkBRnAAFibPfHAACNsGAUnQABsHAUDgABsGATTgABsFATPAABtckXOjAAAAEAAAAAACAAEAAAAL0FEQk/CW8McAzvDPAM8QynDqgOqQ6qDqwOrQ4NEA4QDxAQEBEQ3xDgEBwRPxGeEZ8RoBGhEfERRhNdE14TfxM5FA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/4abf3fbcd294.js b/pr/617/docs/search.index/4abf3fbcd294.js new file mode 100644 index 000000000..2c5216e82 --- /dev/null +++ b/pr/617/docs/search.index/4abf3fbcd294.js @@ -0,0 +1 @@ +rn_("AQIA9bsRAAABAAEAAQABAAGAArBgEyYAAaAgAAAREG95AQABoMAAABRLafR9EwAACAAcABsAAQYA9LcUAAABAAEAAQABAgD0DhEAAAEAmgABAAECAaBAAAATVHI7MAAAAQAABwACAAwRAQCjEQUA84ACYW9nAwAAhrCAE1MAWrCgE8oAAQwwBjswAAABAAAIAAMALQwAANYRAwC9EgMA84ACZWk=") \ No newline at end of file diff --git a/pr/617/docs/search.index/4acd6eae406c.js b/pr/617/docs/search.index/4acd6eae406c.js deleted file mode 100644 index f4e9d6b33..000000000 --- a/pr/617/docs/search.index/4acd6eae406c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYYCoAAAABNmoAAAABNnZHIBgAKgcAAAE/mgMAAAEShmdAELAaBAAAAUS2XyeRQAAAEAI4ECsFAQ/wABY3YBAAGwgBRNAAFw8kkUAAABAEMGA6AgAAARcKBAAAASx2Rlc/MJEAAATgMBAPMABGlyc3Y7MAAAAQAADwAFAP8GBQDuEgUAGhMBAJITAACEFAAA") \ No newline at end of file diff --git a/pr/617/docs/search.index/4ad27c6c71e0.js b/pr/617/docs/search.index/4ad27c6c71e0.js new file mode 100644 index 000000000..2323b9c5c --- /dev/null +++ b/pr/617/docs/search.index/4ad27c6c71e0.js @@ -0,0 +1 @@ +rn_("AQEA9HEHAACACOEAAQABBQD0WRQAAAEAAQABAAEFAPRJEwAAAQCUAAEA8wICY3X1ZhAAAAEAxgABAAEAYwAEoDAAAAxHoHAAABHxZW5zdvOEEAAAnwAjAg==") \ No newline at end of file diff --git a/pr/617/docs/search.index/4b1651402510.js b/pr/617/docs/search.index/4b1651402510.js new file mode 100644 index 000000000..25abcb337 --- /dev/null +++ b/pr/617/docs/search.index/4b1651402510.js @@ -0,0 +1 @@ +rn_("AQEAOzAAAAEAAAcAAQBjCQcAAQIA91wQAADbALcCAQABAHoAAQATgAKwMBD4AKVpdPMAAXPynBQAAAEAM4ADsRAUDQABZG5w") \ No newline at end of file diff --git a/pr/617/docs/search.index/4b2657879883.js b/pr/617/docs/search.index/4b2657879883.js new file mode 100644 index 000000000..9ca2adaf1 --- /dev/null +++ b/pr/617/docs/search.index/4b2657879883.js @@ -0,0 +1 @@ +rn_("AQEA8woQAAABAAEAAYmEoIAAABTGoEAAABRxoGAAABSRoJAAABTKAoAKE4EDoKAAABP/sNAUagABY2l1M4ADsMAUyAABYXR1") \ No newline at end of file diff --git a/pr/617/docs/search.index/4b4d3233065c.js b/pr/617/docs/search.index/4b4d3233065c.js new file mode 100644 index 000000000..cb134846f --- /dev/null +++ b/pr/617/docs/search.index/4b4d3233065c.js @@ -0,0 +1 @@ +rn_("AYICsCARVgABsDAR1AABZXABBQD09g8AAAEA7wABAFOAA7CQEgQAAWFpcgGBArCgFCkAAbCgFCsAAWNyxwAAAIWwUA0HAAGgIAAACX6gIAAADDA8AAE6MAAAAQAAAAAACgAQAAAAtgG3AXwEBQcvCTAJjRCOEAERAhEXEg==") \ No newline at end of file diff --git a/pr/617/docs/search.index/4b50d365f4e7.js b/pr/617/docs/search.index/4b50d365f4e7.js deleted file mode 100644 index 371ca8997..000000000 --- a/pr/617/docs/search.index/4b50d365f4e7.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEAOjAAAAEAAAAAAAkAEAAAAO8P8A/lEKIRAhMDEwAUiRS+FL8UAYACsOAT+gABsDAUEgABZHDDgISxQBTQAAGgkAAAE74CgAk=") \ No newline at end of file diff --git a/pr/617/docs/search.index/4b5746dbe11e.js b/pr/617/docs/search.index/4b5746dbe11e.js deleted file mode 100644 index 6659dd25c..000000000 --- a/pr/617/docs/search.index/4b5746dbe11e.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYICsEAS2QABsEAS2wABNDYBCwDz1xMAAGwAAQABBgD0xRQAAAEACgABAPMCAXIBIRQAAPMDAWU7MAAAAQAABQACAI8QAwBAEQEA84ADYWVp") \ No newline at end of file diff --git a/pr/617/docs/search.index/4b9183eec264.js b/pr/617/docs/search.index/4b9183eec264.js new file mode 100644 index 000000000..a820ee59a --- /dev/null +++ b/pr/617/docs/search.index/4b9183eec264.js @@ -0,0 +1 @@ +rn_("AYACsAAT9AABsAAT9gABNDYBBgD0xBQAAAEACgABAPMCAnJ28wYQAAABABkE8wIBZTswAAABAAAFAAIAjxADAEARAQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/4bd2750f696a.js b/pr/617/docs/search.index/4bd2750f696a.js deleted file mode 100644 index 6d91de647..000000000 --- a/pr/617/docs/search.index/4bd2750f696a.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9N0SAAABAKYAAQABCAD0mRMAAAEAiAABADMBA7BAE2gAAWFiZfUFDQAAAQBDA2wC0gABAAA7MAAAAQAABgACAD4MAQDMDwQA84ACc3UjgQKwgBOTAAFydAGAArBwExoAAaAQAAAM5G56AQYA9JUQAAABALcAAQBzAISgoAAAFNkCART1fgUAAAEA8w5XAAEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/4c12fa9ec5af.js b/pr/617/docs/search.index/4c12fa9ec5af.js new file mode 100644 index 000000000..538314806 --- /dev/null +++ b/pr/617/docs/search.index/4c12fa9ec5af.js @@ -0,0 +1 @@ +rn_("AQMAOzAAAAEAAI4AAgB9BwAAhAmNAAGEArBwFEgAAaBAAAARKGlzAQcA8wERAAABABUBI4ACoLAAABLnYXQBAAOwMAkxAAGwMBOnAAGgIAAAB35hZG8BFwIAADcDAICGoMAAABRAsAAUEQABAJgV") \ No newline at end of file diff --git a/pr/617/docs/search.index/4c1740d83ed2.js b/pr/617/docs/search.index/4c1740d83ed2.js deleted file mode 100644 index 8fa60cbcb..000000000 --- a/pr/617/docs/search.index/4c1740d83ed2.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQgA9PoTAAABAFYAAQABAgGgYAAAFKhz8uwRAAABAAGAArAgEDIAAaCQAAAUWGFlAQcBsEAUdwABZfU7FAAAAQABAAEAAQABAwGwQBSNAAF08uoTAAABACOEAqBAAAAUGmJzE4ACsMAUwAABdXkBAQD06BEAAAEAAQABABOAArAwERMAAWRsN3QAAIuwQBPJAAGgUAAAEYCgkAAAE72wUBMaAAGgMAAADHyzmAc7MAAAAQAAYgARAAkCAQBpBAMAzQUAAM0GAQBrBwEAFwkBACQJAAAuCQAAYAkAAJUMQgDuDAAA/g8BANgQAQD1EAEAOxEBALsRBADOEgEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/4c35289cde0f.js b/pr/617/docs/search.index/4c35289cde0f.js new file mode 100644 index 000000000..d6a842588 --- /dev/null +++ b/pr/617/docs/search.index/4c35289cde0f.js @@ -0,0 +1 @@ +rn_("AQUAOzAAAAEAAAYAAQBvEAYAAQMAOzAAAAEAAAUAAQDnDAUAYwADoJAAABOFaHBzOjAAAAEAAAAAAA8AEAAAAK0FrgXMBiIJagx3DBYRFxHHEsgSHhMfEyATIRNNE04T") \ No newline at end of file diff --git a/pr/617/docs/search.index/4c69b395ac8f.js b/pr/617/docs/search.index/4c69b395ac8f.js deleted file mode 100644 index 5dadecaa7..000000000 --- a/pr/617/docs/search.index/4c69b395ac8f.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQACoCAAABOhsDAR+wABaXTyCBMAAAEAAQMAOzAAAAEAAAYAAQB0EQYAAQgA9AsUAAABAIsABQATAQOwkBR0AAGwUBNQAWdnbHA7MAAAAQAAEgAJAHkQAgD+EQEAPxMBAGATAQCXEwEA/BMBADYUAQBFFAEAhhQBAAGAArAwEfcAAbAwEfkAAXJ2AQQBsAASyAABZDswAAABAAAFAAIA8xEDAPcSAQC3BQABh7BQEsoBFLBgE04AAZQJAzowAAABAAAAAAALABAAAADvDPAM8QysDq0OHBGeEZ8RoBGhEUYTfxMjgAKgEAAADO5ubwEDAPN2EAAAAQABACOAAqAAAAAMIGRyYwGEoFAAABGboDAAABC0FAEQ8qQHAAABABOAA7BgEuwAAbAAD78AAWNnchOAA6CwAAAUKaBAAAAOq2Vvcw==") \ No newline at end of file diff --git a/pr/617/docs/search.index/4c92d522b4cf.js b/pr/617/docs/search.index/4c92d522b4cf.js deleted file mode 100644 index 6d6210300..000000000 --- a/pr/617/docs/search.index/4c92d522b4cf.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQwA9NETAAABAAEAAQABBgDzghEAAAEAAQABggKwYBNXAAGw0BR5AAFpchOAArAQDPkAAWVpFwMAg4ixMBSYAAGgYAAAEYGgUAAAERWwkBMpAAGwQBERAAG0EAfzAAFh9E0JAAABAAgIAQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/4d199ba2a246.js b/pr/617/docs/search.index/4d199ba2a246.js new file mode 100644 index 000000000..2c84fc1ef --- /dev/null +++ b/pr/617/docs/search.index/4d199ba2a246.js @@ -0,0 +1 @@ +rn_("AQEA9U8RAAABAAEAAQABACOBArCQFBUAAWF0Q4ADsRAUlwABsIAT1wABY290EwACsAAUbAABbnL2DQIAAAEAfAIBALUCYAw=") \ No newline at end of file diff --git a/pr/617/docs/search.index/4dfb324aed51.js b/pr/617/docs/search.index/4dfb324aed51.js new file mode 100644 index 000000000..8ba5f7ccd --- /dev/null +++ b/pr/617/docs/search.index/4dfb324aed51.js @@ -0,0 +1 @@ +rn_("AQAA9DkTAAABAFgBAQDzBAFzOzAAAAEAAA0AAgC0EwcArBQFAEcAAASGoIAAABOUsQAUuwABsEAS9AABsIAUywABsFATOwABFQAW9KcOAAABAAEAAQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/4ecbb5c4c72a.js b/pr/617/docs/search.index/4ecbb5c4c72a.js new file mode 100644 index 000000000..1db053713 --- /dev/null +++ b/pr/617/docs/search.index/4ecbb5c4c72a.js @@ -0,0 +1 @@ +rn_("AYQCsAARjgABsAARkAABNDbzAgFh8oYMAAABAAEAAPbWEgAAAQBwAAEAUQEBAPMAAWTz5RIAAAEAqgBjgAOgIAAACQlhZW8=") \ No newline at end of file diff --git a/pr/617/docs/search.index/4f4bed7058fd.js b/pr/617/docs/search.index/4f4bed7058fd.js new file mode 100644 index 000000000..aba205e97 --- /dev/null +++ b/pr/617/docs/search.index/4f4bed7058fd.js @@ -0,0 +1 @@ +rn_("AQMBoAAAABNbc/TiDwAA8gMBAAQAAQECsGAUTgABsEAUjAABZHQ7MAAAAQAADQAGAA4TAwB5EwEA6RMBAAAUAQCBFAEAqBQBAAEBA7BwEhAAAbCAEusAAYATjgAQ+m1uczswAAABAAALAAIAAAkFAG4TBQCjgISwsBPXAAGxcBTLAAEIQIIBAIWgcAAAErCwMBS/AAGgcAAAER+xABROAAGwABNPAWcGIQHyfAUAAAEAZwQAAIccxu6hviygEAAABbGwMBNCAUmgQAAAEUIRwQw7MAAAAQAABgACAJwTAAAEFAUA") \ No newline at end of file diff --git a/pr/617/docs/search.index/4f9cbcececbe.js b/pr/617/docs/search.index/4f9cbcececbe.js deleted file mode 100644 index fab6efd8b..000000000 --- a/pr/617/docs/search.index/4f9cbcececbe.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACoFAAABFVoFAAABKnYXUBgwKxABTOAAGw0BS+AAFrbQEEAaCgAAAU2XfyyRQAAAEAAQsAOzAAAAEAAAYAAQAuFAYAI4ACoIAAABRZZW9jAwOgYAAAE+xlbnABNAwAACOAAqBAAAAQ5W1wAQoA8xAUAAABAHcAE4ACohAAABTZbG/3AAAAhaEAAAAUigSBAzswAAABAAA9AAQAwAUFAFkHAACsBzUAlREAAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/5004f9821b1b.js b/pr/617/docs/search.index/5004f9821b1b.js deleted file mode 100644 index f44418f52..000000000 --- a/pr/617/docs/search.index/5004f9821b1b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9M4RAAABAAEAAQATAAKgMAAAEZlldPTUEAAAAQABAAEAAQMA9MoRAAABAAEAAQABgAKgAAAAEKWgAAAAEKY0NvMDAXYBEw0AAJcCAACIsKATLQABsCAMNQABsGATJwABsIATDAABoCAAABEQCUxGOjAAAAEAAAAAAAkAEAAAABAHEQcSB0YMCBBvEbsSvBK9Em8U") \ No newline at end of file diff --git a/pr/617/docs/search.index/501c6dcd1fda.js b/pr/617/docs/search.index/501c6dcd1fda.js deleted file mode 100644 index f187f88b5..000000000 --- a/pr/617/docs/search.index/501c6dcd1fda.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIAOzAAAAEAAAUAAQChFAUA8w0BbTswAAABAAAFAAEAXxQFAEcAAACFsAAUcAABsEAS1QABoQAAABSnsAATlgCKOAgCOzAAAAEAABwABwANDAMAQxEHAPkSAQB1EwQAUxQEAFoUAwDIFAAA") \ No newline at end of file diff --git a/pr/617/docs/search.index/51193234a828.js b/pr/617/docs/search.index/51193234a828.js deleted file mode 100644 index 147756883..000000000 --- a/pr/617/docs/search.index/51193234a828.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9JgQAAABAAEAAQABEwD11BQAAAEAAQABAAEAE4ACoMAAABNbYW8BAgKgsAAAFGmgsAAAFGpjc/PfEAAAAQBfAAGBArBQEgMAAaDAAAAUOGhp84ACZWkjgAKwsBObAAFpdAEFAbCAFHAAAXY7MAAAAQAACAACAOERBADREwMAI4ACsHASBwABYmR3AgAAhqCgAAATvqCQAAAThwXgAvUgBwAAAQABAPAIlQQ=") \ No newline at end of file diff --git a/pr/617/docs/search.index/5136cd716661.js b/pr/617/docs/search.index/5136cd716661.js new file mode 100644 index 000000000..370a4ab76 --- /dev/null +++ b/pr/617/docs/search.index/5136cd716661.js @@ -0,0 +1 @@ +rn_("AQIAOzAAAAEAAAUAAgCQDAMAKhMBAAECAPOLEQAAAQABAAEGAPS3FAAAAQABAAEA8wYBbfS9EgAAAQABAAEA84ACbnbzgAJhaQ==") \ No newline at end of file diff --git a/pr/617/docs/search.index/515482f97850.js b/pr/617/docs/search.index/515482f97850.js deleted file mode 100644 index c59165743..000000000 --- a/pr/617/docs/search.index/515482f97850.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMA89ISAAABAAEAI4ECsSAUzAABY2sBBAGgQAAAEgpl8xYNAAATBAEAAQAAOzAAAAEAAAUAAgADEQMAmxMBAHOAhKAwAAAUSwgwAQ==") \ No newline at end of file diff --git a/pr/617/docs/search.index/51e00cb41a33.js b/pr/617/docs/search.index/51e00cb41a33.js deleted file mode 100644 index 259d83c41..000000000 --- a/pr/617/docs/search.index/51e00cb41a33.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9fcRAAABACUBAQBhAQEBAPcyEQAAAQABANcCAQCLAAUAAQIA9MIQAAABAAEAAQATgAKg0AAAFIRjc/OAAmVvI4ACsAAHXAABbHTzAAJhcvK/DwAAAQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/5200cabaa1d5.js b/pr/617/docs/search.index/5200cabaa1d5.js new file mode 100644 index 000000000..9b81594d2 --- /dev/null +++ b/pr/617/docs/search.index/5200cabaa1d5.js @@ -0,0 +1 @@ +rn_("AQQAOjAAAAEAAAAAAAcAEAAAAOwP7Q+2ELcQMxM0E8QTxRMBAIihEAAAFIqACQ8AGtugQAAAESWwYBR6AAGgEAAADJSwUBQRAAGwMBGHAAGxIBScAAE5wQI6MAAAAQAAAAAAEQAQAAAAJQkmCTkJOgkaDBsMHAwdDCMMKAztDLISmBOZE64TrxMhFCIUM4ADoIAAABQaZW95") \ No newline at end of file diff --git a/pr/617/docs/search.index/5289f2209b5b.js b/pr/617/docs/search.index/5289f2209b5b.js deleted file mode 100644 index 1f3ebf01f..000000000 --- a/pr/617/docs/search.index/5289f2209b5b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9RETAAABAPAAgAABABOAArAwE0MBSWlwAQYA878TAAABAAEA8wICYnMBUBAAAPMBAWYBcAcAAAECAPRqEwAAAQCYAAEAM4ADsEATOAABaXV5") \ No newline at end of file diff --git a/pr/617/docs/search.index/5294b97e6b02.js b/pr/617/docs/search.index/5294b97e6b02.js deleted file mode 100644 index e14d1214c..000000000 --- a/pr/617/docs/search.index/5294b97e6b02.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQBsEAUdwABZfU7FAAAAQABAAEAAQABAgA7MAAAAQAACwACAOMTBQChFAUA8wEBbTswAAABAAANAAQA4AwAAH0RAAD7EgUAXxQFAPMBAWXyERIAAAEAM4CEsBAQnAABoCAAAAwpFEAE") \ No newline at end of file diff --git a/pr/617/docs/search.index/52babe817b07.js b/pr/617/docs/search.index/52babe817b07.js deleted file mode 100644 index 8499f4196..000000000 --- a/pr/617/docs/search.index/52babe817b07.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAAOjAAAAEAAAAAAAoAEAAAAPYP9w/mEOcQcxEFEgYSIxMkE7wUvRQBBADz8Q8AAOEAAQDzAAFx9xsNAAABAD4DAQDNAGEAAQDzAAJleTowAAABAAAAAAAOABAAAAAZCWQMZQyHEJwQnRAyETMRNBELFAwUHBQdFJcUnBQ=") \ No newline at end of file diff --git a/pr/617/docs/search.index/532728edef14.js b/pr/617/docs/search.index/532728edef14.js deleted file mode 100644 index 087e3ca7a..000000000 --- a/pr/617/docs/search.index/532728edef14.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQYA9CMTAAABAJgBAQATAgKwYBMlAAFmbfLyDAAAAQAjggKwQBNSAAFlbAEFAPQUFAAAAQBQAAEA8wMBY/IYEwAAAQABgQKgAAAACTygMAAAD+dzdBOAAqAQAAAHWG5404CEsHAUzAABkEAB") \ No newline at end of file diff --git a/pr/617/docs/search.index/53976a6f19cd.js b/pr/617/docs/search.index/53976a6f19cd.js deleted file mode 100644 index b9cf49d8d..000000000 --- a/pr/617/docs/search.index/53976a6f19cd.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9A8JAAABANwK7QAnAAAAyqEQAAAUi6BAAAARJbBgFHsAAaAQAAAMlLBQFBIAAbAwEYcAAaAAAAAMUrEgFJ0AAbBAESMCJEBOsAk6MAAAAQAAAAAAFAAQAAAAJQkmCTkJOgkaDBsMHAwdDCMMKAztDLMS3xLgEuESmROaE68TsBMiFCMU") \ No newline at end of file diff --git a/pr/617/docs/search.index/546ec0de9ebd.js b/pr/617/docs/search.index/546ec0de9ebd.js deleted file mode 100644 index b380cd6cf..000000000 --- a/pr/617/docs/search.index/546ec0de9ebd.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMA9hINAADqAloAAQDSAgEAAQQAOzAAAAEAAAoABACfEAUA5BIBAEQTAQBqFAAAAYYCoAAAABNmoAAAABNnZHLzAgF2AZITAADzgAJlbycBAADHoDAAAA/7oHAAABK0oIAAABQ5sNAT2AABoLAAABQ5QEUICTswAAABAAAJAAMAxgUCACkHBABrDAEAI4ACS1dG2+EeZHIBAAA6MAAAAQAAAAAACgAQAAAAYQliCTEQgBOBE7ETshNeFLYUzhTPFBOAAqAwAAAQtWx0w4AEoAAAABCloAAAABCmNDZhZQ==") \ No newline at end of file diff --git a/pr/617/docs/search.index/54a8e6164f40.js b/pr/617/docs/search.index/54a8e6164f40.js deleted file mode 100644 index 0e1915cbb..000000000 --- a/pr/617/docs/search.index/54a8e6164f40.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQkBoEAAABR2ZfKYFAAAAQABgAKwAAz5AAGggAAAE9pkcAEDAaAAAAAQSnPy2A8AAAEAAQIDsGATVwABsNAUeQABoIAAABP5aXJzAYQUAAD3AACAhaEwAAAUl5BJAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/54a9637d5185.js b/pr/617/docs/search.index/54a9637d5185.js new file mode 100644 index 000000000..edf3c6d3f --- /dev/null +++ b/pr/617/docs/search.index/54a9637d5185.js @@ -0,0 +1 @@ +rn_("AQoBoIAAABTHZgGmEwAAAQgA9VIUAAABAAEAAQABAPMDAWb1qhAAAAEAAQABAAEA84MCYXMBAgKgIAAAEe2gMAAAErliaAHeDwAAI4ACoIAAABNaYXDzAAJobQGnBAAA") \ No newline at end of file diff --git a/pr/617/docs/search.index/54ec492054f5.js b/pr/617/docs/search.index/54ec492054f5.js deleted file mode 100644 index bea6df6e8..000000000 --- a/pr/617/docs/search.index/54ec492054f5.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUBsEAS1QABZfL5EgAAAQABAAA7MAAAAQAAEQAEAAECAQDJEAgAxhEDANISAgABCgGwQBR3AAFl9TsUAAABAAEAAQABAEcGAACHsCARJAI7sMAUwgABoCAAAAcFsDAHYgABGIkKOjAAAAEAAAAAAAwAEAAAAF4HXwdPCS4MLww1DDYMiQyKDNgM2QwnEygT") \ No newline at end of file diff --git a/pr/617/docs/search.index/5512bb91ed8f.js b/pr/617/docs/search.index/5512bb91ed8f.js deleted file mode 100644 index 5d0bf7b4f..000000000 --- a/pr/617/docs/search.index/5512bb91ed8f.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYkCsCAUggABoDAAABSMaXATAAOgIAAAEz6ggAAAE1FlaXX1IgwAADkAAQABAAEAAYYCsJAUqQABoAAAABLLYmQBAgD0KwwAAAEAkQMBAAEAAPRHCQAAAQABAAEAAQAAOzAAAAEAAAYAAgCdEwAABRQFACOBArBAFH0AAWVz84ADZXh5AQAA9EMJAAABAAEAAQAjgAKwIBMyAAFyeEcBAADGsMATyQABoBAAABGBsEAM9gABoDAAABEIAESAC/R0BQAAAQCHBwEA1wAAAIWgMAAAEHygMAAAE4gQARM6MAAAAQAAAAAACgAQAAAACwkMCQ0JPQk+CTAMlxCeEFgRWREOEw==") \ No newline at end of file diff --git a/pr/617/docs/search.index/5530a91156e7.js b/pr/617/docs/search.index/5530a91156e7.js new file mode 100644 index 000000000..f7b5bdcd0 --- /dev/null +++ b/pr/617/docs/search.index/5530a91156e7.js @@ -0,0 +1 @@ +rn_("AQACoCAAABHtoDAAABK5YmgB3g8AAAETAbAAFJIAAXM7MAAAAQAABQABAKwUBQABBwDzuhIAAAEAAQDzgANkcnQ=") \ No newline at end of file diff --git a/pr/617/docs/search.index/55351f3455a2.js b/pr/617/docs/search.index/55351f3455a2.js deleted file mode 100644 index 661364c3e..000000000 --- a/pr/617/docs/search.index/55351f3455a2.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEAOzAAAAEAAAcAAQBjCQcAAQIA91wQAADbALgCAQABAHoAAQATgAKwMBD4AKVpdPMAAXPynRQAAAEAM4ADsRAUDgABZG5w") \ No newline at end of file diff --git a/pr/617/docs/search.index/55ee716c76ca.js b/pr/617/docs/search.index/55ee716c76ca.js deleted file mode 100644 index 3bd7bfc74..000000000 --- a/pr/617/docs/search.index/55ee716c76ca.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYMCsQAUzgABsNAUvgABa20BBAGgoAAAFNl38skUAAABAAELADswAAABAAAGAAEALhQGACOAAqCAAAAUWWVvYwQDoGAAABPsZW5wATQMAAABCgDzEBQAAAEAdwATgAKiEAAAFNlsb3cAAACFoGAAABKnoQAAABSKBIEDOzAAAAEAAD0ABADABQUAWQcAAKwHNQCVEQAAQ4ADsIARlAEkoAAAAAIYZGt0") \ No newline at end of file diff --git a/pr/617/docs/search.index/55ffe625f527.js b/pr/617/docs/search.index/55ffe625f527.js new file mode 100644 index 000000000..99f199059 --- /dev/null +++ b/pr/617/docs/search.index/55ffe625f527.js @@ -0,0 +1 @@ +rn_("AQYA9PkTAAABAFYAAQAjgAKhEAAAFLZjchOAAqAgAAAJG2h5AQEBoGAAABSnc/LrEQAAAQAjgAKwIAmAAAFldQGAArAgEDIAAaCQAAAUV2FlAQcBsEAUdgABZfU6FAAAAQABAAEAAQABAAD0ORMAAAEAWAEBAPMEAXM7MAAAAQAADQACALQTBwCsFAUARwAABIaggAAAE5SxABS7AAGwQBL0AAGwgBTLAAGwUBM7AAEVABb0pw4AAAEAAQABAIMAhLBAFMEAAbAwEyoAAbAwE8gAAcAAAzowAAABAAAAAAAJABAAAAC7BCERIhHEEcUR0hHTERcUGBSKFAEDAbBAFIwAAXTy6RMAAAEAI4QCoEAAABQZYnMTgAKwwBS/AAF1eQGAAqAAAAAJO7AgERMAAWdwAQEA9OcRAAABAAEAAQDzgAJkbLfoAACMoNAAABOgoFAAABGAoJAAABO8sFATGQABoDAAAAx8t5gHOzAAAAEAAGUAEwADAgEACQIBAGkEAwDNBQAAzQYBAGsHAQB+BwAAFwkBACQJAAAuCQAAYAkAAJUMQgDuDAAA/g8BANgQAQD1EAEAOxEBALsRBADNEgEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/574d85053847.js b/pr/617/docs/search.index/574d85053847.js deleted file mode 100644 index 4083af5a0..000000000 --- a/pr/617/docs/search.index/574d85053847.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEA9qUFAAABADcNAQCmAAEAAQAAOzAAAAEAAAcAAwARAgEAegwBAC0QAwABgwKwcBOoAAGwgBPhAAFjcwEFAPRaFAAAAQABAAEAAQUA9EoTAAABAJQAAQDzCAJjdfVmEAAAAQDGAAEAAQABAgD0zhEAAAEAAQABABMAAqAwAAARmWV09NQQAAABAAEAAQABAwD0yhEAAAEAAQABAAGAAqAAAAAQpaAAAAAQpjQ28wMBdgETDQAAlwEAAIawoBMtAAGwIAw1AAGwgBMMAAEJDAY6MAAAAQAAAAAACQAQAAAAEAcRBxIHRgwIEG8RuxK8Er0SbxQBAgD0NhMAAAEAkAABAAEDAPRqEwAAAQCYAAEA8wACYnU6MAAAAQAAAAAAFQAQAAAAVAdVB1YH7A/tD5UQlhC2ELcQTRFOEQESAhI0EzUTQRNCE1ETfBN9E8UTxhMBAQD0oQUAAAEAYAcBALcAAICFsQAUUQABsAAHXgABDAhEAQgAOzAAAAEAAAUAAQDuEgUA1w0AAIiwQBE9AAGgIAAADR25AAs7MAAAAQAADwAFAH0AAQAIBwcAbQcBAIUQAQA5EQEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/577af22cc871.js b/pr/617/docs/search.index/577af22cc871.js deleted file mode 100644 index 132b4d18a..000000000 --- a/pr/617/docs/search.index/577af22cc871.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEA9zIRAAABAAEA1wIBAIsABQABAgD0whAAAAEAAQABABOAAqDQAAAUhGNz84ACZW8jgQKwAAdcAAFsdAEAADswAAABAAAFAAEAtwUFAFOAhKBQAAAU2bCgFBgAAQggBQ==") \ No newline at end of file diff --git a/pr/617/docs/search.index/57b2f70c36c8.js b/pr/617/docs/search.index/57b2f70c36c8.js deleted file mode 100644 index ae7f73b28..000000000 --- a/pr/617/docs/search.index/57b2f70c36c8.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMAOzAAAAEAAAkABABsCQEArxADAPEQAQD+EwEAAQIBsEAUjQABdDswAAABAAANAAYADxMDAHoTAQDqEwEAARQBAIIUAQCpFAEAE4ACsDATQwFJaXABiAKwABP1AAGwABP3AAE0NhOBAqAwAAARlWRzAYACoCAAAAyIsGAUJwABbngBBQDzvxMAAAEAAQATgAKgMAAAFBplefMAhBIAA/JQEAAA5gDzgAJydA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/57c59a197af7.js b/pr/617/docs/search.index/57c59a197af7.js deleted file mode 100644 index be124dee0..000000000 --- a/pr/617/docs/search.index/57c59a197af7.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9BoRAAABAJQBAQABAAD0AhAAAAEA9AAJASOCArBQE2QAAWVzAQIA9DYTAAABAJAAAQABAwD0ahMAAAEAmAABAPOFAmJ1AQIA9IURAAABAH4BAQDzgAJpbPMAA2J0dTowAAABAAAAAAAPABAAAAB8BwkICggLCAwIKhDwEL4TQhSPFJAUkhSfFKAUxxTLFAEMAPMkFAAAAQABAAGAArAACSkAAaAgAAANFGRuAQYA9DQTAAABAJAAAQDzAwFi9BgRAAABAJQBAQCXBgAAh7BAEAAAAaBwAAASubCQE2IAATgJAzowAAABAAAAAAAkABAAAADJBcoFywXMBT4HPwdEDAkNCg0LDQ8NEA0RDfgP+Q9MEE0QURBSEG8QcBBxEHIQcxB0EHUQuBC9EOgQ6RCeE8IT6ROFFJEUsxTEFA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/57e10538b61a.js b/pr/617/docs/search.index/57e10538b61a.js deleted file mode 100644 index 765cad9f2..000000000 --- a/pr/617/docs/search.index/57e10538b61a.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEAOjAAAAEAAAAAAAgAEAAAABkJhxAyETMRNBELFAwUlxScFAECAPTCEAAAAQABAAEAE4ACoNAAABSEY3NTgAOgcAAAFH9laW+DgISwIBOvAAGgIAAAD/uwAAdcAAEKCAQ=") \ No newline at end of file diff --git a/pr/617/docs/search.index/5875637aff25.js b/pr/617/docs/search.index/5875637aff25.js new file mode 100644 index 000000000..719504bb4 --- /dev/null +++ b/pr/617/docs/search.index/5875637aff25.js @@ -0,0 +1 @@ +rn_("AQMBsEARcQABZQGmDgAAAQUAOzAAAAEAAAYAAQBvEAYAAQMAOzAAAAEAAAUAAQDnDAUA8wACcHM6MAAAAQAAAAAADQAQAAAArQWuBcwGIglqDHcMFhEXEccSyBIeEx8TIBMhEzMAA7BQD+QAz2RwcjswAAABAAAPAAYAowQAAGwFAwDRDwAA8hEDAMISAwD2EgEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/58ae9e43f3d7.js b/pr/617/docs/search.index/58ae9e43f3d7.js deleted file mode 100644 index d541cfcc3..000000000 --- a/pr/617/docs/search.index/58ae9e43f3d7.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9DoTAAABAFgBAQDzBAFzOzAAAAEAAA0AAgC1EwcArRQFAEcAAAGGoIAAABOVsQAUvAABsEAS9QABsIAUzAABsFATPAABFQAW9KcOAAABAAEAAQABBQDzDxAAAAEAAQBjgISwQBCcAAGgAAAABukA8AA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/58d0d4294971.js b/pr/617/docs/search.index/58d0d4294971.js new file mode 100644 index 000000000..32db2c656 --- /dev/null +++ b/pr/617/docs/search.index/58d0d4294971.js @@ -0,0 +1 @@ +rn_("AQgA9PkTAAABAFYAAQABAgGgYAAAFKdz8usRAAABAAGAArAgEDIAAaCQAAAUV2FlAQcBsEAUdgABZfU6FAAAAQABAAEAAQABAwGwQBSMAAF08ukTAAABACOEAqBAAAAUGWJzE4ACsMAUvwABdXkBAQD05xEAAAEAAQABABOAArAwERMAAWRsN3QAAIuwQBPIAAGgUAAAEYCgkAAAE7ywUBMZAAGgMAAADHyzmAc7MAAAAQAAYgARAAkCAQBpBAMAzQUAAM0GAQBrBwEAFwkBACQJAAAuCQAAYAkAAJUMQgDuDAAA/g8BANgQAQD1EAEAOxEBALsRBADNEgEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/58dd78d0ece0.js b/pr/617/docs/search.index/58dd78d0ece0.js new file mode 100644 index 000000000..9ec2c8184 --- /dev/null +++ b/pr/617/docs/search.index/58dd78d0ece0.js @@ -0,0 +1 @@ +rn_("AQUA9JgQAAABAAEAAQABEwD10xQAAAEAAQABAAEAE4ACoMAAABNaYW8BAgKgsAAAFGigsAAAFGljc/PfEAAAAQBfAAGBArBQEgIAAaDAAAAUN2hp84ACZWkjgAKwsBOaAAFpdAEFAbCAFG8AAXY7MAAAAQAABwACAOERAwDQEwMAI4ACsHASBgABYmR3AgAAhqCgAAATvaCQAAAThgXgAvUgBwAAAQABAPAIlAQ=") \ No newline at end of file diff --git a/pr/617/docs/search.index/58f211c07279.js b/pr/617/docs/search.index/58f211c07279.js new file mode 100644 index 000000000..ac8648382 --- /dev/null +++ b/pr/617/docs/search.index/58f211c07279.js @@ -0,0 +1 @@ +rn_("AQUA9NwSAAABAKYAAQABCAD0mBMAAAEAiAABADMBA7BAE2cAAWFiZfUFDQAAAQBDA2sC0gABAAA7MAAAAQAABgACAD4MAQDMDwQA84ACc3UjgQKwgBOSAAFydAEGAPSVEAAAAQC3AAEAUwADsIATGQABYml09X4FAAABAPIOVwABAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/593ba01b5f99.js b/pr/617/docs/search.index/593ba01b5f99.js new file mode 100644 index 000000000..c9b111609 --- /dev/null +++ b/pr/617/docs/search.index/593ba01b5f99.js @@ -0,0 +1 @@ +rn_("AQIBsEAUjAABdDswAAABAAANAAYADhMDAHkTAQDpEwEAABQBAIEUAQCoFAEAE4ACsDATQgFJaXABBQDzvhMAAAEAAQATgAKgMAAAFBlleVMAA7BwFCYAAWJlcwFQEAAA") \ No newline at end of file diff --git a/pr/617/docs/search.index/59b253409373.js b/pr/617/docs/search.index/59b253409373.js new file mode 100644 index 000000000..631b0a753 --- /dev/null +++ b/pr/617/docs/search.index/59b253409373.js @@ -0,0 +1 @@ +rn_("AQIAOzAAAAEAAAUAAQCgFAUA8w0BbTswAAABAAAFAAEAXhQFACMDhLBAEtQAAaEAAAAUprAAE5UAijAIAjswAAABAAAYAAYAQxEHAPgSAQB0EwQAUhQEAFkUAwDHFAAA") \ No newline at end of file diff --git a/pr/617/docs/search.index/5a2b259ae98b.js b/pr/617/docs/search.index/5a2b259ae98b.js new file mode 100644 index 000000000..ba13a873b --- /dev/null +++ b/pr/617/docs/search.index/5a2b259ae98b.js @@ -0,0 +1 @@ +rn_("AQAA9BoRAAABAJMBAQAjgQKgYAAAE1BiaQEAAPQCEAAAAQD0AAgBI4ICsFATYwABZXMBiQKwIBSBAAGgMAAAFItpcAECAPQ1EwAAAQCQAAEAAQMA9GkTAAABAJgAAQDzhQJidQECAPSFEQAAAQB9AQEA84ACaWy3AQAAhaAgAAATPRIBDDowAAABAAAAAAAUABAAAAB8BwkICggLCAwIIgxbDFwMXQxeDCoQ8BC9E0EUjhSPFJEUnhSfFMYUyhQBBQDzIxQAAAEAAQBDhgOwkBSoAAGgAAAAEspiZHcBgAKwAAkpAAGwIA0UA2hkbgEDAPMAEAAAAQAHAQECAPYrDAAAAQCRAwEA4AMBAAEAAPRHCQAAAQABAAEAAQAAOzAAAAEAAAYAAgCcEwAABBQFACOBArBAFHwAAWVz84ADZXh5AQAA9EMJAAABAAEAAQBDgAOgQAAAFKawIBMxAAFlcngBgAKgQAAADP6wQBArAAFnbCcaAADJoJAAABN+sMATyAABoBAAABGBoDAAABQasEAM9gABAEegCzowAAABAAAAAAAJABAAAAB0BXUF/Az9DBIN/A9WEFcQKBMpEwEGAPQzEwAAAQCQAAEA8wMBYvQYEQAAAQCTAQEAtwwAAImgcAAAEriwkBNhAAGwYBBOAAGgMAAAE4c4iRM6MAAAAQAAAAAALwAQAAAAyQXKBcsFzAU+Bz8HCwkMCQ0JPQk+CTAMRAwJDQoNCw0PDRANEQ34D/kPTBBNEFEQUhBvEHAQcRByEHMQdBB1EJcQnhC4EL0Q6BDpEFgRWRENE50TwRPoE4QUkBSyFMMU") \ No newline at end of file diff --git a/pr/617/docs/search.index/668c314f2126.js b/pr/617/docs/search.index/5ae92656f48a.js similarity index 53% rename from pr/617/docs/search.index/668c314f2126.js rename to pr/617/docs/search.index/5ae92656f48a.js index 437ac6ada..d8f004780 100644 --- a/pr/617/docs/search.index/668c314f2126.js +++ b/pr/617/docs/search.index/5ae92656f48a.js @@ -1 +1 @@ -rn_("AYQCoCAAABE3oDAAABGdaXQBAQA7MAAAAQAAjgACAH0HAACECY0AYwADsAACGgABZGVsOjAAAAEAAAAAAAcAEAAAALgEiQWKBYsFgQdUEagTqRM=") \ No newline at end of file +rn_("AYQCoCAAABE3oDAAABGdaXQBAQA7MAAAAQAAjgACAH0HAACECY0AYwADsAACGgABZGVsOjAAAAEAAAAAAAcAEAAAALgEiQWKBYsFgQdUEacTqBM=") \ No newline at end of file diff --git a/pr/617/docs/search.index/5b1ddb314b3e.js b/pr/617/docs/search.index/5b1ddb314b3e.js deleted file mode 100644 index cd6f2b43a..000000000 --- a/pr/617/docs/search.index/5b1ddb314b3e.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBoAAAABP0cwEJEQAAAQMA9JgQAAABAAEAAQATgAKwUBCcAAFhaQGAArBwFMAAAbBwFE0AAWVyAYACoLAAABSEoDAAABE1bG6XAQAAhaAgAAASsaAgAAAJCBCRBDowAAABAAAAAAATABAAAACfBNcE2ATZBNoE2wTcBHgJxQ/GD8cPyA9cE5YTGhQgFCwULRRJFEoUAQIAOjAAAAEAAAAAAAoAEAAAAGEJYgkxEIATgROxE7ITXhS2FM4UzxQBAAA7MAAAAQAABgACAJ0TAAAFFAUAAQIBsDATbQABYzowAAABAAAAAAATABAAAAAMDeAPdhB3EHgQfRB+EIIQgxAfEZIRlhGXEZgREBJiE2MTSxR0FHUUAQMA89cTAABsAAEAE4ECsEARfgABYWUTgAKgsAAAFIthZQEBAbBAFHcAAWX1OxQAAAEAAQABAAEAE4ACoBAAAAwhYW+HDwAAyLAwCRkHbrBAFH0AAaCAAAASrEBMiAnzAAgAACkEPgABgAKggAAAE4egkAAAE19hawEDAPPSEgAAAQABACOAhLEgFMwAAaAAAAAJPKAwAAAP5wQEBgEIAPXUFAAAAQABAAEAAQATigKwQBSrAAFhcpMAhKDAAAATW6EAAAAUKQFABvKCBwAAAQABAwGgQAAAEgpl8xYNAAATBAEAAQUA9M0TAAABAAEAAQBTgAOwABOCAAFlc3QBBgD0uBQAAAEAAQABAPMIAW30vhIAAAEAAQABAAECADswAAABAAAFAAEAoRQFAPMNAW07MAAAAQAABQABAF8UBQABBAD3Hg0AAAEAqAUOAAEAjgABAAEDAYATjwAQ+nM7MAAAAQAABQABAG8TBQAjggKgkAAAFHNpcwEFAaBQAAAUOGjyzBIAAAEAE4ACsDATggABbnQBgAKggAAAE6KwQBIDAAFhZQGAAqAQAAARK6AAAAARcG5yAQkBoEAAABR2ZfKYFAAAAQAjgAKgkAAAE9ppbMcjAAAKoDAAABEssFATOAABsPAUwgABsHATnwABoNAAABR/MGJjZWhpa25vdjswAAABAAAeAAsAIAwAAAMRAwATEgQAmxMBAKoTAwDhEwEAKRQAAC4UBgBNFAEAWRQAAHAUAQABAgKgsAAAFGmgsAAAFGpjc/PfEAAAAQBfAAEBArBQEgMAAaDAAAAUOGhp8uETAAABAPOAAmVpAQEBsJATmwABc/TCEAAAAQABAAEAAQMBsAAQ/QABcwFlEAAA84ADaGl0AQEBoFAAABMcYwHfDAAAAQUBsIAUcAABdjswAAABAAAIAAIA4REEANETAwABAQD0hRAAAAEAswABAOOAhLBwEgcAAQ4IAPdlDwDRsJASqgABoFAAABEkoQAAABSnoKAAABO+oBAAAAdYQD+/KTswAAABAAA8ABkAxwQAAMkEAACdBQIAIAcCAA0MAwDiDAEA1A8CAOIPAAAJEAAAEhAAADIQAQBCEQgAmREAAOwSAQD1EgEA+RIBAFcTAQB1EwQAyxMBANUTAQDaEwAAUxQEAFoUAwCnFAAAyBQAAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/572b61c02809.js b/pr/617/docs/search.index/5b87cca78f8f.js similarity index 52% rename from pr/617/docs/search.index/572b61c02809.js rename to pr/617/docs/search.index/5b87cca78f8f.js index ddd621e01..0bbfef12a 100644 --- a/pr/617/docs/search.index/572b61c02809.js +++ b/pr/617/docs/search.index/5b87cca78f8f.js @@ -1 +1 @@ -rn_("AQMBsAANBANEc/Q6DAAAAQABAAEAAQIA9DgMAAABADgFAQAjAAKgQAAADQBlcvK5BAAAAQABgAKgQAAAE+ywMBMKAAFhcwECAaBAAAAUdmXymBQAAAEA84AEbG5ydg==") \ No newline at end of file +rn_("AQMBsAANBANEc/Q6DAAAAQABAAEAAQIA9DgMAAABADgFAQAjAAKgQAAADQBlcvK5BAAAAQABgAKgQAAAE+uwMBMJAAFhcwECAaBAAAAUdWXylxQAAAEA84AEbG5ydg==") \ No newline at end of file diff --git a/pr/617/docs/search.index/5b984867710b.js b/pr/617/docs/search.index/5b984867710b.js deleted file mode 100644 index 4533d3fe2..000000000 --- a/pr/617/docs/search.index/5b984867710b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIAOzAAAAEAAAUAAgCQDAMAKxMBAAECAPOLEQAAAQABAAEGAPS4FAAAAQABAAEA8wYBbfS+EgAAAQABAAEA84ACbnbzgAJhaQ==") \ No newline at end of file diff --git a/pr/617/docs/search.index/5bca52d11800.js b/pr/617/docs/search.index/5bca52d11800.js deleted file mode 100644 index 93fe560b1..000000000 --- a/pr/617/docs/search.index/5bca52d11800.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACoGAAABQboKAAABQNYWkBCwD0qhMAAAEAAQABAAEEAPS5EAAAAQABAAEAI4ACsEARFgABbHLzAANjZHMBCxEAAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/5be8c1468ebe.js b/pr/617/docs/search.index/5be8c1468ebe.js new file mode 100644 index 000000000..bd60e5b06 --- /dev/null +++ b/pr/617/docs/search.index/5be8c1468ebe.js @@ -0,0 +1 @@ +rn_("AQcA9KITAAABAAEAAQDzBQF0OzAAAAEAAAgAAgAXDAAASQwHAAEFAPO+EwAAAQABADcAAICFsDAQXQABoKAAABPcoDAAABQZEEFI") \ No newline at end of file diff --git a/pr/617/docs/search.index/5bf73eb997f1.js b/pr/617/docs/search.index/5bf73eb997f1.js deleted file mode 100644 index 61726b0b5..000000000 --- a/pr/617/docs/search.index/5bf73eb997f1.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsFAUJwABoAAAAAkaY3QBAQD0oxMAAAEAAQABAAEDAPQADQAA3QagAAEAAYACoBAAAAyIoFAAABK0Y2QBAgDzDgkAAFEDAQDzAAFt86gEAADJAAEAAYECoBAAABNWoHAAABOeYWPzAAFy82QHAABWCiAAZw8AgIiwMA/kAM+gMAAAFKcpMCM=") \ No newline at end of file diff --git a/pr/617/docs/search.index/5c4cb1a8cdbb.js b/pr/617/docs/search.index/5c4cb1a8cdbb.js new file mode 100644 index 000000000..eb97997bf --- /dev/null +++ b/pr/617/docs/search.index/5c4cb1a8cdbb.js @@ -0,0 +1 @@ +rn_("AQUAOzAAAAEAAAcAAwDbEQIAjBMBACMUAgABBwD2sBMAAAEArABYABgAAQABBADz3hEAAAEAAQAjAAKgYAAAEw1hcvP/EAAAAQDKAQGAAqEQAAAUsrCAFKgAAWl1dwgAAIixUBTPAAGgAAAAEN6wcBPsAAGhMAAAFMMKQRvyawkAAEoHAYECoFAAABEkoKAAABNea3ABAwA7MAAAAQAACQECAPQMAQCuDgcB84ACbHTzgAJhZQ==") \ No newline at end of file diff --git a/pr/617/docs/search.index/5cafc27ea81e.js b/pr/617/docs/search.index/5cafc27ea81e.js new file mode 100644 index 000000000..2dfc621fe --- /dev/null +++ b/pr/617/docs/search.index/5cafc27ea81e.js @@ -0,0 +1 @@ +rn_("AQUA9NwSAAABAKYAAQABCAD0mBMAAAEAiAABADMBA7BAE2cAAWFiZfUFDQAAAQBDA2sC0gABAAA7MAAAAQAABgACAD4MAQDMDwQA84ACc3UjgQKwgBOSAAFydAGAArBwExkAAaAQAAAM5G56AQYA9JUQAAABALcAAQDzAANiaXT1fgUAAAEA8g5XAAEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/5cb3ebce9a92.js b/pr/617/docs/search.index/5cb3ebce9a92.js new file mode 100644 index 000000000..6041f3659 --- /dev/null +++ b/pr/617/docs/search.index/5cb3ebce9a92.js @@ -0,0 +1 @@ +rn_("AQEA9zIRAAABAAEA1gIBAIsABQABAgD0whAAAAEAAQABABOAAqDQAAAUg2Nz84ACZW8jgQKwAAdcAAFsdAEAADswAAABAAAFAAEAtwUFADOAA7CgFBcAAWRydA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/5d371c6e539d.js b/pr/617/docs/search.index/5d371c6e539d.js deleted file mode 100644 index 34580c1e3..000000000 --- a/pr/617/docs/search.index/5d371c6e539d.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACoAAAABCloAAAABCmNDYBgAOwABP1AAGwABP3AAGwcBRwAAE0NmEBBAD0xRQAAAEACgABACOBAqBAAAARbm1wAQEAOzAAAAEAAAgAAwDoEgAAWxMAANIUBgDHAQACxaBAAAAQaKCAAAASwkAGgAg7MAAAAQAAFwAIAAYQAQAmEQEAahEDAOERBgDREwMAIRQAAIAUAQC0FAEA8wACZXY6MAAAAQAAAAAAFQAQAAAAEwIUAhUCtAUTDcMPxA/hD/sPjxCQEJEQkhBAEUER2RLaEtsS3BLXE0MURBQBgQKwcBFqAAGwcBFsAAE0NiOAArAQB2sAAW5wAQMCsEATMgABoDAAABOIZXf0lxAAAAcAugABABOBAqBwAAATwmdvAQQAOzAAAAEAAAYAAgAWDAAAIQ0FAGOAA7BQEAYAAWRzdFcBAACFoBAAABBosSAUgAABESEBOzAAAAEAAAoAAwB/AAAAlQAFAOgRAwA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/5dbe95a54095.js b/pr/617/docs/search.index/5dbe95a54095.js deleted file mode 100644 index 79cdc350f..000000000 --- a/pr/617/docs/search.index/5dbe95a54095.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQsBoEAAABRLZfJ5FAAAAQABBQD0FBIAAAEAAQABAAGDArAAEP8AAbBQE40AAWR3E4ACoKAAABQBZXYBAgD0GhEAAAEAlAEBAAEAAPQCEAAAAQD0AAkBI4ICsFATZAABZXMBAgD0NhMAAAEAkAABAAEDAPRqEwAAAQCYAAEA84UCYnUBAgD0hREAAAEAfgEBAPOAAmls8wADYnR1OjAAAAEAAAAAAA8AEAAAAHwHCQgKCAsIDAgqEPAQvhNCFI8UkBSSFJ8UoBTHFMsUAQwA8yQUAAABAAEAAYACsAAJKQABoCAAAA0UZG4BBgD0NBMAAAEAkAABAPMDAWL0GBEAAAEAlAEBAJcGAACHsEAQAAABoHAAABK5sJATYgABOAkDOjAAAAEAAAAAACQAEAAAAMkFygXLBcwFPgc/B0QMCQ0KDQsNDw0QDREN+A/5D0wQTRBREFIQbxBwEHEQchBzEHQQdRC4EL0Q6BDpEJ4TwhPpE4UUkRSzFMQUAQMA9EgTAAABAFEBAQDzgAJjbbcCAIDGoDAAAAkHsYAU1AABQIUECA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/5dcfa0230fa2.js b/pr/617/docs/search.index/5dcfa0230fa2.js deleted file mode 100644 index 6633f92b0..000000000 --- a/pr/617/docs/search.index/5dcfa0230fa2.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9KMTAAABAAEAAQABAAD1iwwAAAEAAQABAAEA84ACZ2sTgAKwsBP+AAFhbwGAAqAgAAAMDKBAAAAMRGV0AYUCsHAUJwABsDATegABZXMTAAOwQA/oAAGgYAAAEZxiZXM7MAAAAQAAEgAEAK8FAQDRBgoAYRABALkQAwABAwD0fRAAAAEABAABAAEKAaBAAAAUdmXymBQAAAEAE4ACsGASzAABY2nzgAJkcvMAAmVvOzAAAAEAAAkAAgCmBwUA6wcDAAECAbAwEWgAAW47MAAAAQAACQABAAIMCQDzgAJvdAEDADswAAABAADvAAIAgAcAABML7gABAgGgcAAAEhBpOzAAAAEAAAEBAgB/BwAAEgoAAROAAqBAAAAMQ3J1AQQA9FkTAAABAJgAAQABAwDz5hIAAAEAqgDzgAJhb9cHAACIsEANDQABsTAUlQABNIEWOzAAAAEAAB4ADQDABAEAhgUBABcMAABJDAcAbQwBAPYPAQDmEAEAcxEAAMARAwAFEgEAwhIAACMTAQC8FAEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/5ddbacd9a3f7.js b/pr/617/docs/search.index/5ddbacd9a3f7.js deleted file mode 100644 index cd7bd4111..000000000 --- a/pr/617/docs/search.index/5ddbacd9a3f7.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIAOzAAAAEAABEABQCyBQAAQwwAAPgMAAAaEA0AgBEAAAGEArDQFMAAAaCQAAATvWJoAYQCsAARjgABsAARkAABNDaXBAAAh6AgAAAR7qAwAAARVKAwAAASurBwD/IAActACDswAAABAAASARQAuQEAAKgFAAAICQAADgkAACsJAgAeDAAAXwwBAIYMAQD7DAAAKQ3uAN4PAADvDwEA5RAAAKIRAAACEwEA4xMFAAAUAACJFAAAoRQFAL4UAQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/5e33d538e99d.js b/pr/617/docs/search.index/5e33d538e99d.js new file mode 100644 index 000000000..a3a8ed03c --- /dev/null +++ b/pr/617/docs/search.index/5e33d538e99d.js @@ -0,0 +1 @@ +rn_("AQMAOzAAAAEAAAkABABsCQEArxADAPEQAQD9EwEAAQIBsEAUjAABdDswAAABAAANAAYADhMDAHkTAQDpEwEAABQBAIEUAQCoFAEAE4ACsDATQgFJaXABiAKwABP0AAGwABP2AAE0NhOBAqAwAAARlWRzAYACoCAAAAyIsGAUJgABbngBBQDzvhMAAAEAAQATgAKgMAAAFBllefMAhBIAA/JQEAAA5gDzgAJydA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/5e6b62b9a81e.js b/pr/617/docs/search.index/5e6b62b9a81e.js new file mode 100644 index 000000000..92f5dcaf3 --- /dev/null +++ b/pr/617/docs/search.index/5e6b62b9a81e.js @@ -0,0 +1 @@ +rn_("AQAA9IgTAAABAAEAAQATAgKwgBNcAAFzdvR8CQAAAQCQCgEA8wABYwF2DAAAAQICoLAAABRooLAAABRpY3Pz3xAAAAEAXwABgQKwUBICAAGgwAAAFDdoafOCAmVp84ACZW8=") \ No newline at end of file diff --git a/pr/617/docs/search.index/5ef223a8d6a1.js b/pr/617/docs/search.index/5ef223a8d6a1.js new file mode 100644 index 000000000..e06bcbca1 --- /dev/null +++ b/pr/617/docs/search.index/5ef223a8d6a1.js @@ -0,0 +1 @@ +rn_("AQUAOzAAAAEAAAcAAwDbEQIAjBMBACMUAgABBADz3hEAAAEAAQAjAAKgYAAAEw1hcvP/EAAAAQDKAQGAAqEQAAAUsrCAFKgAAWl1NwQAgYexUBTPAAGgAAAAEN6wcBPsAAGhMAAAFMMKQBsBAAA7MAAAAQAAjQABABgOjQABAgD2sBMAAAEArABYABgAAQAjgQKgIAAAER9ydvOAAmVvI4ACoCAAABKrZHQBAAA7MAAAAQAA8QAEAM4FAAAPCO4AlAwAAIERAADzgANkbnY=") \ No newline at end of file diff --git a/pr/617/docs/search.index/5fb3c8092292.js b/pr/617/docs/search.index/5fb3c8092292.js new file mode 100644 index 000000000..8b747718c --- /dev/null +++ b/pr/617/docs/search.index/5fb3c8092292.js @@ -0,0 +1 @@ +rn_("AQAA9IgTAAABAAEAAQDzBgFz8g0UAAABAAEBAbBQEwkAAWX3IwkAAFUDAQAQBAEA3AMBAAEDAPTOEQAAAQABAAEAAQMA9MoRAAABAAEAAQDzgwJsc/OAAmN0U4ADoBAAAAzaYW9y") \ No newline at end of file diff --git a/pr/617/docs/search.index/5fe64baffbcd.js b/pr/617/docs/search.index/5fe64baffbcd.js deleted file mode 100644 index c41794c99..000000000 --- a/pr/617/docs/search.index/5fe64baffbcd.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9FoUAAABAAEAAQABBQD0ShMAAAEAlAABAPMAAmN19WYQAAABAMYAAQABAAEJAPOPFAAAAQA7AAENAaCgAAAU2XfyyRQAAAEAAYECsIAUnwABoGAAABTHZHM3AQCEhaBAAAAUcrBgFEIAUAKQCiOAAqBwAAARb2RyAQsBoEAAABR2ZfKYFAAAAQATgAKwMBCCAAFkcvMCAWX06wcAAAEAAQABACOAArAgDBgAAWV5AQoA89ISAAABAAEAAQsAOzAAAAEAAAcAAQC1EwcAAQIA9BQUAAABAFAAAQC3AACAhbBwEwgAAbDQFLwAAWAgBgEGAPPfEgAAAQABAAEDAbBAFI0AAXTy6hMAAAEAI4UCoEAAABQaYnMjAgOwQBETAAGgUAAAEYBhYmb3FwkAAAEASAB4BwEAYgABAPOAAmlwAQQA838QAAABAAEA8wABcDswAAABAAAMAAEA6gYMACOAA6AwAAAMKLBwE8kAAWFlafOAA2RtbgEAADowAAABAAAAAAAHABAAAAAiCXcMyBLJEh8TIBMhEyIT8wIBZDswAAABAAAJAAMA8xEDAMMSAwD3EgEAI4ACoJAAABRpaW8BgAOwMBN8AAGAE78AEAGwUBEwAAFjc3jzAAFlAc0EAADnFQCAiaAAAAAMiKBAAAAM4bBADOIAAZBIXQ==") \ No newline at end of file diff --git a/pr/617/docs/search.index/6065d8a3f7a7.js b/pr/617/docs/search.index/6065d8a3f7a7.js deleted file mode 100644 index 708525d52..000000000 --- a/pr/617/docs/search.index/6065d8a3f7a7.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABoLAAABPdYvNnEwAALgEBAAGAAqAgAAAP46AwAAAT6WFy8wABdAGJBwAAAQQBoEAAABR2ZfKYFAAAAQABBQA7MAAAAQAACwAFAGkQBQDiEgEAEBQBAGkUAACIFAAA4wGEoDAAAA/hhQACOzAAAAEAAAsABQCqBQIAxwYEAGgMAQCbEQAA9BIAAGOAA7AAEAQDYmRucg==") \ No newline at end of file diff --git a/pr/617/docs/search.index/60d75a7999e9.js b/pr/617/docs/search.index/60d75a7999e9.js new file mode 100644 index 000000000..fd2676662 --- /dev/null +++ b/pr/617/docs/search.index/60d75a7999e9.js @@ -0,0 +1 @@ +rn_("AQQBsEAUdgABZfU6FAAAAQABAAEAAQABAgA7MAAAAQAACwACAOITBQCgFAUA8wEBbTswAAABAAANAAQA4AwAAH0RAAD6EgUAXhQFAPMBAWXyEBIAAAEAM4CEsBAQnAABoCAAAAwpFEAE") \ No newline at end of file diff --git a/pr/617/docs/search.index/61c4bbfdf792.js b/pr/617/docs/search.index/61c4bbfdf792.js new file mode 100644 index 000000000..9d5dbeeb9 --- /dev/null +++ b/pr/617/docs/search.index/61c4bbfdf792.js @@ -0,0 +1 @@ +rn_("AQEBoGAAABSnc/LrEQAAAQABCAD1UhQAAAEAAQABAAEAowCEsCAJgAABoIAAABMAMEAIOzAAAAEAAAUAAgDyAQAAqhAEAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/61dd5d3444c7.js b/pr/617/docs/search.index/61dd5d3444c7.js deleted file mode 100644 index 31ca450ff..000000000 --- a/pr/617/docs/search.index/61dd5d3444c7.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9N0SAAABAKYAAQABCAD0mRMAAAEAiAABADMBA7BAE2gAAWFiZTowAAABAAAAAAAMABAAAAA7BzwHPQcFDQYN2w9JEJgQmRCaEJsQtRKHEwEAADswAAABAAAiAA4AxQQBAIwFCgA2CQEAPgwBAPYMAQCrDgAAzA8EAOMPAAACEAEA9xABAAoRAACdEQAA+xEBAAASAAABgQKwABGOAAGwABGQAAE0NiOAArCAFJ0AAW9wAQABsEATZAABZfOZEQAAXAEBALcBAICFoGAAABE2EAEL") \ No newline at end of file diff --git a/pr/617/docs/search.index/62a8eb8e83c2.js b/pr/617/docs/search.index/62a8eb8e83c2.js new file mode 100644 index 000000000..571635168 --- /dev/null +++ b/pr/617/docs/search.index/62a8eb8e83c2.js @@ -0,0 +1 @@ +rn_("AQUBsEAS1AABZTswAAABAAAFAAIA+BIBAFkUAwABggKgkAAAFEagkAAAFEcyMwEAA6AgAAASsKAgAAAJCLCAFL8AAWltcDswAAABAAAOAAYAnwQAANcEBQB4CQAAxQ8DABkUAAArFAEA8wABcwFCEQAAAQUA9KITAAABAAEAAQABAAD1iwwAAAEAAQABAAEA84ACZ2tzgISwsBP9AAERSAABBQGgQAAAEgll8xYNAAATBAEAE4ACoDAAAAxEZXIBgAOwUBQ/AAGgEAAADAygcAAAE5FnbnJHAQAAhaBgAAAUGqBQAAAUcqCgAAAUDBUBBDswAAABAAARAAQAAQIBAMkQCADGEQMA0RICAAGFArBwFCYAAbAwE3kAAWVzEwADsEAP6AABoGAAABGcYmVzOzAAAAEAABIABACvBQEA0QYKAGEQAQC5EAMAAQMA9H0QAAABAAQAAQABCgGgQAAAFHVl8pcUAAABABOAArBgEssAAWNp84ACZHLzAAJlbzswAAABAAAJAAIApgcFAOsHAwABAgGwMBFoAAFuOzAAAAEAAAkAAQACDAkAAQAA9L4QAAABAAEAAQABAQA7MAAAAQAA7wACAM4FAAAPCO4AYwADsBATNwABZWlzOjAAAAEAAAAAAA4AEAAAABsJVglXCVgJWQkrDCwMvQ++D+cP+g/qEBARnhOfE+OAhLAgB2IAARFABAGBhLAwDBgAAaBAAAAM4aCAAAARb7BADOIAAQAAHQEFAPXuEwAAAQABAHoAAQATgAKgAAAABwVhZwEDAPSpEgAAAQBrAQEA84ACaW8BAQA7MAAAAQAA7wACAIAHAAATC+4AAQgBsEAUdgABZfU6FAAAAQABAAEAAQDzgQJndAEBAaBwAAASD2k7MAAAAQAAAQECAH8HAAASCgABAQUA89oQAAABAAEA84ACYW8TgAKgQAAADENydQGAAqAAAAAHZKAQAAAJGmR4EwACsCANDQABZXLyKxAAAAEAE4ACsLAUwQABcngBCgD0qRMAAAEAAQABABOAArAQESQCOmFlAQQA9LkQAAABAAEAAQABBAA6MAAAAQAAAAAABwAQAAAASRNKE1gTWRPeE98T8RPyEwEDAPPlEgAAAQCqANOAhLBAERYAAQFIAce/AwCPoIAAABLnsGAM/AABsEAMbQABsTAUlAABP6kfOzAAAAEAAEsAIgB7AAAAgAAAAMAEAQDMBAAAhgUBAEoHAQBaBwAAXgcBAE8JAABjCQcAegkBABcMAAAuDAEANQwBAEkMBwBtDAEAiQwBANgMAQD2DwEAZhABAOYQAQDzEAEACxEAAC0RAgA9EQEATxEEAHMRAADAEQMABBIBAMESAAAiEwEAJhMBAFQTAAC7FAEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/63058cf605b0.js b/pr/617/docs/search.index/63058cf605b0.js deleted file mode 100644 index 2577b0017..000000000 --- a/pr/617/docs/search.index/63058cf605b0.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsEATgAABoMAAABRAZW8BAgD06BEAAAEAAQABABMAA7AgEewAAbCAFFEAAWFzdAHuDAAAAQQA8w8QAAABAAEAs4CEsEATkwABQQAG") \ No newline at end of file diff --git a/pr/617/docs/search.index/636e1a4d8b0b.js b/pr/617/docs/search.index/636e1a4d8b0b.js deleted file mode 100644 index 2fda1df11..000000000 --- a/pr/617/docs/search.index/636e1a4d8b0b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQkA848UAAABADsAAYACoEAAABNssCAUKgABaGkBDAGgoAAAFNl38skUAAABAAEBAPMKEAAAAQABAPOAAnR1AYECsIAUnwABoGAAABTHZHM3AwCDhqBAAAAUcrBgFEIAUAKQDgESAbAAFJMAAXM7MAAAAQAABQABAK0UBQABAMagUAAAEvSgAAAAEKmwIBFcAAGgUAAAEgmhAAAAFKigQAAAFEwAAwILOzAAAAEAABUABwCyBQAAQwwAAPgMAAAaEA0AgBEAAAoTAQBHFAEAAQEBsLAUmgABbDswAAABAAAMAAMALwcIABcNAQBYEAEAAQEBoDAAABE4aPMRCQAALgCyCAECAPToEQAAAQABAAEAE4ECsCAR7AABYXPzggJub/cBAICHoCAAAAWxoEAAAAwqFQEH") \ No newline at end of file diff --git a/pr/617/docs/search.index/646de36a32e1.js b/pr/617/docs/search.index/646de36a32e1.js deleted file mode 100644 index 461c360ac..000000000 --- a/pr/617/docs/search.index/646de36a32e1.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9MARAAABAAEAAQABCgD00RMAAAEAAQABAPOBAm10AQYA84IRAAABAAEAAYICsGATVwABsNAUeQABaXITgAKwEAz5AAFlaQEFAPQUFAAAAQBQAAEA8wQBY/IYEwAAAQABBQDz6RIAAAEAAQDzggJmdAEAAbAAFHAAAWT0DQwAAAEAAQABAPOBAmVpNwYAAImxMBSYAAGgYAAAEYGgUAAAERWwkBMpAAGwQBERAAG1EAcBTBQAAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/64b6efff3a2a.js b/pr/617/docs/search.index/64b6efff3a2a.js new file mode 100644 index 000000000..e386b2f31 --- /dev/null +++ b/pr/617/docs/search.index/64b6efff3a2a.js @@ -0,0 +1 @@ +rn_("AYQCsAARjgABsAARkAABNDbzAAFh8oYMAAABACOAArCQEZQBI2x0AYACsHATpwABsIAT4AABY3MBAAA6MAAAAQAAAAAACQAQAAAA7hHvEQsTDBP5E/oTUBRRFMsUzBTzgAJkZScBAICFoNAAABQgsBAEwgABoDAAABCIAggL") \ No newline at end of file diff --git a/pr/617/docs/search.index/64df930283be.js b/pr/617/docs/search.index/64df930283be.js new file mode 100644 index 000000000..b8bf6c6ce --- /dev/null +++ b/pr/617/docs/search.index/64df930283be.js @@ -0,0 +1 @@ +rn_("AQMAOzAAAAEAAAUAAQDtEgUAAQQBoFAAABQ3aPLLEgAAAQABAAKwQBPIAAGgoAAAFFdlcjowAAABAAAAAAAHABAAAADNBs4GawdsB/4P/w/1EPYQ84ADZ2lz") \ No newline at end of file diff --git a/pr/617/docs/search.index/64ef428f9034.js b/pr/617/docs/search.index/64ef428f9034.js new file mode 100644 index 000000000..90bbc1740 --- /dev/null +++ b/pr/617/docs/search.index/64ef428f9034.js @@ -0,0 +1 @@ +rn_("AQIAOzAAAAEAABYACQBpEAUAexEBAAwSAgDhEgEAEhMEAA8UAQA5FAAAaBQAAIcUAAATgAKw8BTLAAFmZyOAArBQE8oAAW1uAQUA8w8UAAABAHcA8wQBYwGbEQAAAQEBsHAUbAABcDswAAABAAAJAAIA4wcHAOEQAQABAgA7MAAAAQAACwACAOITBQCgFAUA8wEBbTswAAABAAANAAQA4AwAAH0RAAD6EgUAXhQFABMAAqAwAAAM5GVz8hASAAABAAGCArBwFEgAAaBAAAARKGlz9wEAgIawkBQpAAGQSQg=") \ No newline at end of file diff --git a/pr/617/docs/search.index/64fd4fee47f7.js b/pr/617/docs/search.index/64fd4fee47f7.js deleted file mode 100644 index 0a8481615..000000000 --- a/pr/617/docs/search.index/64fd4fee47f7.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQYA9PoTAAABAFYAAQAjgAKhEAAAFLdjchOAAqAgAAAJG2h5AQEBoGAAABSoc/LsEQAAAQAjgAKwIAmAAAFldQGAArAgEDIAAaCQAAAUWGFlAQcBsEAUdwABZfU7FAAAAQABAAEAAQABAAD0OhMAAAEAWAEBAPMEAXM7MAAAAQAADQACALUTBwCtFAUARwAABIaggAAAE5WxABS8AAGwQBL1AAGwgBTMAAGwUBM8AAEVABb0pw4AAAEAAQABAIMAhLBAFMIAAbAwEysAAbAwE8kAAcAAAzowAAABAAAAAAAJABAAAAC7BCERIhHEEcUR0hHTERgUGRSLFAEDAbBAFI0AAXTy6hMAAAEAI4QCoEAAABQaYnMTgAKwwBTAAAF1eQGAAqAAAAAJO7AgERMAAWdwAQEA9OgRAAABAAEAAQDzgAJkbLfoAACMoNAAABOhoFAAABGAoJAAABO9sFATGgABoDAAAAx8t5gHOzAAAAEAAGUAEwADAgEACQIBAGkEAwDNBQAAzQYBAGsHAQB+BwAAFwkBACQJAAAuCQAAYAkAAJUMQgDuDAAA/g8BANgQAQD1EAEAOxEBALsRBADOEgEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/6777b8890d6d.js b/pr/617/docs/search.index/6777b8890d6d.js new file mode 100644 index 000000000..745bd30ff --- /dev/null +++ b/pr/617/docs/search.index/6777b8890d6d.js @@ -0,0 +1 @@ +rn_("AQQA9o4UAAABAA8AAQAnAAQAAQMA89ESAAABAAEAI4ECsSAUywABY2sTgAKgMAAAFEpuclMAA6BAAAAMMGVvcvQ4EQAAgQG/AQEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/6778b40d0be8.js b/pr/617/docs/search.index/6778b40d0be8.js new file mode 100644 index 000000000..91bb075ef --- /dev/null +++ b/pr/617/docs/search.index/6778b40d0be8.js @@ -0,0 +1 @@ +rn_("AQEA9QIIAAABAAEAAQABAAEAAqAwAAARLKCQAAAToTBo9RISAACXAQEAAQABABOAAqAwAAAP4GRyIwKEsBAJBgNCsCAMFQELoDAAABAFEgAY9aAFAAAxAAEAAQABADOAhLCgEuEAAbBAD+oAARBJAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/679932c21ba7.js b/pr/617/docs/search.index/679932c21ba7.js deleted file mode 100644 index ae01b0e63..000000000 --- a/pr/617/docs/search.index/679932c21ba7.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABoDAAAAzeaQGIEwAAAQABsDARWgABbvJiBwAAAQABAQDz4gcAAN8HAQATAAKgUAAAELhlc/VcBAAAAQABAAEAAQABhAKwoBQqAAGwoBQsAAFjcvMBAXT0/REAAOwAAQABAAEDAbBgFHsAAWY7MAAAAQAABwADABoMAwCZEwEAIhQBAPOAAmFl84CEACgF") \ No newline at end of file diff --git a/pr/617/docs/search.index/67ebc6510c69.js b/pr/617/docs/search.index/67ebc6510c69.js deleted file mode 100644 index 34634b53a..000000000 --- a/pr/617/docs/search.index/67ebc6510c69.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBoAAAABNcc/TiDwAA8wMBAAQAAQECsGAUTwABsEAUjQABZHQ7MAAAAQAADQAGAA8TAwB6EwEA6hMBAAEUAQCCFAEAqRQBAAEBA7BwEhEAAbCAEuwAAYATjwAQ+m1uczswAAABAAALAAIAAAkFAG8TBQCjgISwsBPYAAGxcBTMAAEIQIIBAIWgcAAAErGwMBTAAAGgcAAAER+xABRPAAGwABNQAWcGIQHyfAUAAAEAZwQAAIdCQkRmQ46gEAAABbGwMBNDAUmgQAAAEUIRwQw7MAAAAQAABgACAJ0TAAAFFAUA") \ No newline at end of file diff --git a/pr/617/docs/search.index/6831cbf8bafb.js b/pr/617/docs/search.index/6831cbf8bafb.js deleted file mode 100644 index 136dc589b..000000000 --- a/pr/617/docs/search.index/6831cbf8bafb.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQcA9BQSAAABAAEAAQAjAQKgcAAAEhNhcgGEBwAAAQMAOzAAAAEAAAgAAgC2DwYAbRMBAPMAAmN0OzAAAAEAAFcAJwCpBAsAcwUAAB4JAQA4DAEAQAwCAAANAAAMDQIAHg0BAOAPAADlDwEA6A8BAO4PAAB2EAIAfRABAIIQAQCnEAAAHxEAACURAABxEQEAfhEBAJIRAQCWEQIA9xEBAAoSAgAQEgAAxxIAANASAQDVEgEAHRMBAC0TAQAyEwEAUhMBAGITAwBoEwEAyRMBAN0TAABLFAAAdBQEAH0UAgA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/689383818a1f.js b/pr/617/docs/search.index/689383818a1f.js deleted file mode 100644 index 241b112b1..000000000 --- a/pr/617/docs/search.index/689383818a1f.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQAOjAAAAEAAAAAAAcAEAAAAOwP7Q+2ELcQNBM1E8UTxhMBAAD0DwkAAAEA3ArtACcAAACIoRAAABSLoEAAABElsGAUewABoBAAAAyUsFAUEgABsDARhwABsSAUnQABOcECOjAAAAEAAAAAABEAEAAAACUJJgk5CToJGgwbDBwMHQwjDCgM7QyzEpkTmhOvE7ATIhQjFDOAA6CAAAAUG2VveQ==") \ No newline at end of file diff --git a/pr/617/docs/search.index/68d782416b4d.js b/pr/617/docs/search.index/68d782416b4d.js deleted file mode 100644 index ef36debde..000000000 --- a/pr/617/docs/search.index/68d782416b4d.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIBoDAAABCHYQFnDAAAAYECoCAAABEKsFASzgABYW8BAwGgAAAAE/RzAQkRAAAjgAKgAAAACABydmMAA7BQEXsAAWNlczswAAABAAAIAAQAEgkCAEAJAQB/EAIAtBIAAAGAArDQFMAAAaCQAAATvWJoJwEAgIWwUBE7AAGw8BPVAAGwIA+/AAFEIQTzgAJudA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/690e4297f1b1.js b/pr/617/docs/search.index/690e4297f1b1.js new file mode 100644 index 000000000..4c8cb78de --- /dev/null +++ b/pr/617/docs/search.index/690e4297f1b1.js @@ -0,0 +1 @@ +rn_("AYACsHAS6wABoLAAABQoZW8jAQOwcBIQAAGAE44AEPptbnM7MAAAAQAAGgAGAHQHAQAACQUAGQ0BAF4RCQCcEQAAbhMFAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/6967dd890c86.js b/pr/617/docs/search.index/6967dd890c86.js deleted file mode 100644 index 0c7e49775..000000000 --- a/pr/617/docs/search.index/6967dd890c86.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQYA9M0TAAABAAEAAQABDwD0uBQAAAEAAQABAAEAAbDwFMIAAWs7MAAAAQAACAADACkUAAAuFAYAWRQAAAECAPSFEAAAAQCzAAEA9wAAAIWxABSrAAGJMADy7BIAAAEAE4ACsBAMZAABZW8BBQA7MAAAAQAADQABAKwRDQABAgDzFxAAAAEAAQABAgD0ExAAAAEAAQABAAEDADswAAABAAAIAAEAyRAIAAGAAqAAAAAMN7AwFHcAAXFyAYACoEAAABEgsMAUbQABZXMBAQA7MAAAAQAAEwABADQQEwAjgAKwkBPVAAFpbXc2AACKoFAAABQ4sHAUFgABoCAAAA0BnFUROzAAAAEAAAsABABgBwAA+RADAMwSAQA7FAQAAQIBsDARhwABcAGzEgAAs4CEoCAAABHyAigE") \ No newline at end of file diff --git a/pr/617/docs/search.index/6a14e49e8487.js b/pr/617/docs/search.index/6a14e49e8487.js new file mode 100644 index 000000000..617f21538 --- /dev/null +++ b/pr/617/docs/search.index/6a14e49e8487.js @@ -0,0 +1 @@ +rn_("AQACoDAAABEsoJAAABOhMGj1EhIAAJcBAQABAAEAE4ACoDAAAA/gZHIjAYSwEAkGA0KwIAwVAQugMAAAEAUSABj1oAUAADEAAQABAAEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/6a4963efb35c.js b/pr/617/docs/search.index/6a4963efb35c.js deleted file mode 100644 index 41406948d..000000000 --- a/pr/617/docs/search.index/6a4963efb35c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQBoFAAABRvcwGyEgAAAYICsEAS2QABsEAS2wABNDYBCwDz1xMAAGwAAQABBgD0xRQAAAEACgABAPMCAXIBIRQAAPMDAWU7MAAAAQAABQACAI8QAwBAEQEA84QDYWVpY4ADoFAAABSoY2V0") \ No newline at end of file diff --git a/pr/617/docs/search.index/6a6edeb452a8.js b/pr/617/docs/search.index/6a6edeb452a8.js deleted file mode 100644 index 75b98d8f4..000000000 --- a/pr/617/docs/search.index/6a6edeb452a8.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEBsIATfAABdfJUCQAAAQAjgAKwEAlSAAEzNgECAPNbBwAA9QEBAAECAPRNCQAAAQAICAEAAQIA82EMAAABAAEAE4ACsBAJSwABaXUBAgA7MAAAAQAACwACAOMTBQChFAUA8wkBbTswAAABAAAMAAMAfREAAPsSBQBfFAUAdzAAAIqgMAAAE/mgcAAAEhCgIAAACU+wMBHUAAGgUAAAEBIzhQs7MAAAAQAANgIEAP4BAAAkAjMBfwcAABIKAAE=") \ No newline at end of file diff --git a/pr/617/docs/search.index/6b2c05d48848.js b/pr/617/docs/search.index/6b2c05d48848.js deleted file mode 100644 index bc7ff5637..000000000 --- a/pr/617/docs/search.index/6b2c05d48848.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYkCsCAUggABoDAAABSMaXATAAOgIAAAEz6ggAAAE1FlaXX1IgwAADkAAQABAAEAAYYCsJAUqQABoAAAABLLYmQBgAKwMBMyAAGgMAAAEQhydtcAAACFoDAAABB8oDAAABOIEAETOjAAAAEAAAAAAAkAEAAAAAsJDAkNCT0JPgmXEJ4QWBFZEQ4T") \ No newline at end of file diff --git a/pr/617/docs/search.index/6b510b6efde3.js b/pr/617/docs/search.index/6b510b6efde3.js deleted file mode 100644 index fe25028ba..000000000 --- a/pr/617/docs/search.index/6b510b6efde3.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9IkTAAABAAEAAQATAgKwgBNdAAFzdvR8CQAAAQCRCgEA8wABYwF2DAAAAQICoLAAABRpoLAAABRqY3Pz3xAAAAEAXwABgQKwUBIDAAGgwAAAFDhoafOCAmVp84ACZW8=") \ No newline at end of file diff --git a/pr/617/docs/search.index/6b7abdba88fe.js b/pr/617/docs/search.index/6b7abdba88fe.js deleted file mode 100644 index b67ce8b62..000000000 --- a/pr/617/docs/search.index/6b7abdba88fe.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYECoCAAABSosEAQDQABYnABBQD0WRMAAAEAmAABAPMDAWMBwhIAAAEMAbAAFJMAAXM7MAAAAQAABQABAK0UBQDzgAJudgGAArAwDGgAAaAwAAAMamlvAQMBoAAAABRzcwEJEgAAAQQA9CQMAAABAAEAAQDXAwCAhrAABXgAAZFIAQ==") \ No newline at end of file diff --git a/pr/617/docs/search.index/6bb4fe213d80.js b/pr/617/docs/search.index/6bb4fe213d80.js new file mode 100644 index 000000000..d3e0182d0 --- /dev/null +++ b/pr/617/docs/search.index/6bb4fe213d80.js @@ -0,0 +1 @@ +rn_("AQAA9IgTAAABAAEAAQATAAKwgBNcAAFzdvR8CQAAAQCQCgEAE4ACsDARHQABbG4BgAKgAAAAEKWgAAAAEKY0NvMDAXYBEw0AABMAAqBAAAARmWFz90YMAADCA2cBSwEBAAEAsgEBAgD0NRMAAAEAkAABAAEDAPRpEwAAAQCYAAEA8wACYnU6MAAAAQAAAAAAEwAQAAAAVAdVB1YH7A/tD5UQlhC2ELcQTRFOEQASARIzEzQTQBNBE1ATxBPFE/OAAmR0E4ACoCAAAA0dZXIBBwGgQAAAFEpl8ngUAAABAAEAAPVuCQAAAQABAAEAAQAjgAKgoAAAFKZmcEcAAACFsDAPwwABoFAAABCIsHARFgABsUAU0QABgSkA8noFAAABAPOAAmx0lwEAgIaxMBRMAAGxEBR6AAGgsAAAE2sRRQg=") \ No newline at end of file diff --git a/pr/617/docs/search.index/6bbd9d9cc477.js b/pr/617/docs/search.index/6bbd9d9cc477.js deleted file mode 100644 index 5ea342445..000000000 --- a/pr/617/docs/search.index/6bbd9d9cc477.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEA9U8RAAABAAEAAQABACOBArCQFBYAAWF0Q4ADsRAUmAABsIAT2AABY290EwACsAAUbQABbnL2DQIAAAEAfAIBALUCYQw=") \ No newline at end of file diff --git a/pr/617/docs/search.index/6bc61df8f07f.js b/pr/617/docs/search.index/6bc61df8f07f.js new file mode 100644 index 000000000..71c6caf0e --- /dev/null +++ b/pr/617/docs/search.index/6bc61df8f07f.js @@ -0,0 +1 @@ +rn_("AQQA9IQUAAAMACIAEQABAgGwQBLPAAFlAd8PAAABgAKgkAAAFEagkAAAFEcyM5cCAADGoCAAAAyFoGAAABFUsGANGQABAWQAAzswAAABAAAKAAMAIwcFAFEMAACIEwMA") \ No newline at end of file diff --git a/pr/617/docs/search.index/6bf86b6dc333.js b/pr/617/docs/search.index/6bf86b6dc333.js deleted file mode 100644 index b93ab7684..000000000 --- a/pr/617/docs/search.index/6bf86b6dc333.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA8y8TAAABAAEAAQAAOzAAAAEAAAUAAgARDAMABxIBAPMAAWT0DhEAAAEAmgABAFMAA7AwFGcAAWVwcvT0DwAAAQDuAAEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/6c95a9f746d9.js b/pr/617/docs/search.index/6c95a9f746d9.js deleted file mode 100644 index 1af5a7de0..000000000 --- a/pr/617/docs/search.index/6c95a9f746d9.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsCATVABaoFAAABFCbXNDgAOw4BQWAAEnKmMiDCpkZ3IBAwDz6RIAAAEAAQAjgAKwMBQcAAFjaTOAA7AADRQDaGFpbw==") \ No newline at end of file diff --git a/pr/617/docs/search.index/6cacde78e373.js b/pr/617/docs/search.index/6cacde78e373.js new file mode 100644 index 000000000..6999b40a8 --- /dev/null +++ b/pr/617/docs/search.index/6cacde78e373.js @@ -0,0 +1 @@ +rn_("AQQBoFAAABRucwGxEgAAAYICsEAS2AABsEAS2gABNDYBCwDz1hMAAGwAAQABBgD0xBQAAAEACgABAPMCAXIBIBQAAPMDAWU7MAAAAQAABQACAI8QAwBAEQEA84QDYWVpY4ADoFAAABSnY2V0") \ No newline at end of file diff --git a/pr/617/docs/search.index/6d1a04586937.js b/pr/617/docs/search.index/6d1a04586937.js new file mode 100644 index 000000000..5c3af2875 --- /dev/null +++ b/pr/617/docs/search.index/6d1a04586937.js @@ -0,0 +1 @@ +rn_("AQABoDAAAAzeaQGHEwAAAQABsDARWgABbvJiBwAAAQABAQDz4gcAAN8HAQATAAKgUAAAELhlc/VcBAAAAQABAAEAAQABhAKwoBQpAAGwoBQrAAFjcvMBAXT0/BEAAOwAAQABAAEDAbBgFHoAAWY7MAAAAQAABwADABoMAwCYEwEAIRQBAPOAAmFl84CEACgF") \ No newline at end of file diff --git a/pr/617/docs/search.index/1c685467eb29.js b/pr/617/docs/search.index/6df6929b7791.js similarity index 62% rename from pr/617/docs/search.index/1c685467eb29.js rename to pr/617/docs/search.index/6df6929b7791.js index 149e18d1c..b508a9601 100644 --- a/pr/617/docs/search.index/1c685467eb29.js +++ b/pr/617/docs/search.index/6df6929b7791.js @@ -1 +1 @@ -rn_("AQAA9IUQAAABALMAAQABhAKwABGOAAGwABGQAAE0NvMCAWHyhgwAAAEAAQEA9tcSAAABAHAAAQBRAQEA84ACZW/zgAJoaw==") \ No newline at end of file +rn_("AQAA9IUQAAABALMAAQABhAKwABGOAAGwABGQAAE0NvMCAWHyhgwAAAEAAQEA9tYSAAABAHAAAQBRAQEA84ACZW/zgAJoaw==") \ No newline at end of file diff --git a/pr/617/docs/search.index/6df9e397525a.js b/pr/617/docs/search.index/6df9e397525a.js new file mode 100644 index 000000000..eaf3a002a --- /dev/null +++ b/pr/617/docs/search.index/6df9e397525a.js @@ -0,0 +1 @@ +rn_("AQIAOzAAAAEAAAYAAgDABQUAlREAAAEAAPQ5EwAAAQBYAQEA8wQBczswAAABAAANAAIAtBMHAKwUBQBHAAABhqCAAAATlLEAFLsAAbBAEvQAAbCAFMsAAbBQEzsAARUAFvSnDgAAAQABAAEAAQUA8w8QAAABAAEAY4ADsEAQnAABbW5vAQEA9JgQAAABAAEAAQAjgQKwAAz2AAFzdfMAAmFpOjAAAAEAAAAAAAcAEAAAAHEH8Q/SENMQohOjE6QTpRMBgAOwABP0AAGwABP2AAGwcBRvAAE0NmEBBAD0xBQAAAEACgABACOBAqBAAAARbm1wAQIAOzAAAAEAAA4ABAB0BwEAGQ0BAF4RCQCcEQAAAQEAOzAAAAEAAAgAAwDnEgAAWhMAANEUBgABAQA7MAAAAQAACgADAIIRAgDMEwMAtxQDACOAAqAwAAAQaGRn1wMAAMaggAAAEsFARoAIOzAAAAEAABgACQAGEAEAJhEBAFoRAQBqEQMA4REFANATAwAgFAAAfxQBALMUAQABAwD2KAkAAFcAFAcBALcAAQAjgAOwcBS/AAGwcBRMAAFlb3IBBAGwsBSZAAFs9BcNAAABAEADAQABAgA7MAAAAQAADAADAAgJAADiEwUAoBQFAAEBAPNOEAAAAQBhAiOAAqCwAAAUS2d6AQIAOjAAAAEAAAAAAAkAEAAAAO4R7xELEwwT+RP6E1AUURTLFMwU8wABb/KCCQAAAQABBQD0+A8AAAEA7wABAIMAhKBAAAANDLBwE38AAbAgDIkAARAFAjswAAABAAAJAAMAvAQDAIMFAgDeEQIAAYACoCAAABE1sCAUTAABZHQBhAKwsBScAAGwABPsAAFhc9OAhKCwAAAUg0AoBAGAAqAwAAASCLBgEA0AAWhyR/8BAM2gIAAABaOgIAAABaSwcBPgAAEDZzQHOzAAAAEAADoAGQCfBAAA1wQFAHgJAADgDAAAxQ8DAL4QAwB9EQAA5xIAAPoSBQBaEwEAfRMAAIUTAACVEwAAoRMAALITAQC8EwAAvhMCAMITAQAZFAAAHxQAACsUAQBIFAEAXhQFAHIUAADRFAYA") \ No newline at end of file diff --git a/pr/617/docs/search.index/6e094e6c5da3.js b/pr/617/docs/search.index/6e094e6c5da3.js new file mode 100644 index 000000000..859489616 --- /dev/null +++ b/pr/617/docs/search.index/6e094e6c5da3.js @@ -0,0 +1 @@ +rn_("AYkCsCAUgQABoDAAABSLaXATAAOgIAAAEz2ggAAAE1BlaXX1IgwAADkAAQABAAEAAYYCsJAUqAABoAAAABLKYmQBgAKwMBMxAAGgMAAAEQhydtcAAAKFoDAAABB8oDAAABOHEAETOjAAAAEAAAAAAAkAEAAAAAsJDAkNCT0JPgmXEJ4QWBFZEQ0TAQMBsDATSwABc/PaEAAAAQABAAEAADswAAABAAAKAAMAghECAMwTAwC3FAMAlwEAAIWgIAAABbWwIAgNAAFQGAH1iwwAAAEAAQABAAEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/6e27b20dd6b6.js b/pr/617/docs/search.index/6e27b20dd6b6.js deleted file mode 100644 index 0d056a843..000000000 --- a/pr/617/docs/search.index/6e27b20dd6b6.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUAOzAAAAEAAAcAAwDbEQIAjRMBACQUAgABBADz3hEAAAEAAQAjAAKgYAAAEw5hcvP/EAAAAQDLAQGAAqEQAAAUs7CAFKkAAWl1NwQAgoexUBTQAAGgAAAAEN6wcBPtAAGhMAAAFMQKQBsTgAKwEBMaAAFpcg==") \ No newline at end of file diff --git a/pr/617/docs/search.index/6e48771c696f.js b/pr/617/docs/search.index/6e48771c696f.js deleted file mode 100644 index 2443894df..000000000 --- a/pr/617/docs/search.index/6e48771c696f.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBsDATbQABY/OWEQAAAQABAAEDAYATjwAQ+nM7MAAAAQAABQABAG8TBQAjggKgkAAAFHNpcwcBAIDFsFATOAABoKAAABPasHATnwABoNAAABR/gAEYCPOAAmRy") \ No newline at end of file diff --git a/pr/617/docs/search.index/6e4cb0afff2c.js b/pr/617/docs/search.index/6e4cb0afff2c.js deleted file mode 100644 index 6e5edfd75..000000000 --- a/pr/617/docs/search.index/6e4cb0afff2c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9N0SAAABAKYAAQABCAD0mRMAAAEAiAABADMAA7BAE2gAAWFiZTowAAABAAAAAAAMABAAAAA7BzwHPQcFDQYN2w9JEJgQmRCaEJsQtRKHEw==") \ No newline at end of file diff --git a/pr/617/docs/search.index/6e8315600389.js b/pr/617/docs/search.index/6e8315600389.js deleted file mode 100644 index 13ed5f69d..000000000 --- a/pr/617/docs/search.index/6e8315600389.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMAOzAAAAEAAI4AAgAHEQAAGRKNAAEAAbBwE9sAAWXy8g8AAAEAIwACoEAAABQ1ZXP0XRAAAAEATQQBADOAA6BgAAARumRpbw==") \ No newline at end of file diff --git a/pr/617/docs/search.index/6e8cdd1201da.js b/pr/617/docs/search.index/6e8cdd1201da.js new file mode 100644 index 000000000..d49fce043 --- /dev/null +++ b/pr/617/docs/search.index/6e8cdd1201da.js @@ -0,0 +1 @@ +rn_("AQEBsIATewABdfJUCQAAAQAjgAKwEAlSAAEzNgECAPNbBwAA9QEBAAECAPRNCQAAAQAICAEAAQIA82EMAAABAAEAE4ACsBAJSwABaXUBAgA7MAAAAQAACwACAOITBQCgFAUA8wkBbTswAAABAAAMAAMAfREAAPoSBQBeFAUAdzAAAIqgMAAAE/igcAAAEg+gIAAACU+wMBHUAAGgUAAAEBIzhQs7MAAAAQAANgIEAP4BAAAkAjMBfwcAABIKAAE=") \ No newline at end of file diff --git a/pr/617/docs/search.index/6eba048936de.js b/pr/617/docs/search.index/6eba048936de.js deleted file mode 100644 index 8fd2b22dd..000000000 --- a/pr/617/docs/search.index/6eba048936de.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMAOzAAAAEAAEMAAgAuCQAAlQxCAAEFAPTUEAAAAQABAAEAE4ACsLATswABYWwBBAD0xQ8AAAEAAQABAHMABKAQAAAQiGJwcnY7MAAAAQAACgACAHQRBgDKEQMAAYACsIARIwIksOAUHAABaXIBAwGwQBFxAAFlAaYOAAABBQA7MAAAAQAABgABAG8QBgABAwA7MAAAAQAABQABAOcMBQDzAAJwczowAAABAAAAAAANABAAAACtBa4FzAYiCWoMdwwWERcRyBLJEh8TIBMhEyITVwAAAIWwUBDGAjuwUA/kAM+gQAAAEZxIgAM7MAAAAQAADwAGAKMEAABsBQMA0Q8AAPMRAwDDEgMA9xIBANMAhKBgAAAM+xAIBvOwAQAAAQApCw==") \ No newline at end of file diff --git a/pr/617/docs/search.index/6ec3fb3fd992.js b/pr/617/docs/search.index/6ec3fb3fd992.js new file mode 100644 index 000000000..f6b383111 --- /dev/null +++ b/pr/617/docs/search.index/6ec3fb3fd992.js @@ -0,0 +1 @@ +rn_("AQkA844UAAABADsAAYACoEAAABNrsCAUKQABaGkBAQDzChAAAAEAAQATgAKwwBTIAAF0dQGBArCAFJ4AAaBgAAAUxmRzNwMAg4agQAAAFHGwYBRBAFACkA4BEgGwABSSAAFzOzAAAAEAAAUAAQCsFAUAAQDGoFAAABLzoAAAABCpsCARXAABoFAAABIIoQAAABSnoEAAABRLAAMCCzswAAABAAAVAAcAsgUAAEMMAAD4DAAAGhANAIARAAAJEwEARhQBAAEBAbCwFJkAAWw7MAAAAQAADAADAC8HCAAXDQEAWBABAAEBAaAwAAAROGjzEQkAAC4AsQgBAgD05xEAAAEAAQABABOBArAgEesAAWFz84ICbm/3AQCAh6AgAAAFsaBAAAAMKhUBBw==") \ No newline at end of file diff --git a/pr/617/docs/search.index/6ee512238957.js b/pr/617/docs/search.index/6ee512238957.js new file mode 100644 index 000000000..31cb3a70f --- /dev/null +++ b/pr/617/docs/search.index/6ee512238957.js @@ -0,0 +1 @@ +rn_("AQIA9BoRAAABAJMBAQABAAD0AhAAAAEA9AAIASOCArBQE2MAAWVzAQIA9DUTAAABAJAAAQABAwD0aRMAAAEAmAABAPOFAmJ1AQIA9IURAAABAH0BAQDzgAJpbPMAA2J0dTowAAABAAAAAAAPABAAAAB8BwkICggLCAwIKhDwEL0TQRSOFI8UkRSeFJ8UxhTKFAEMAPMjFAAAAQABAAGAArAACSkAAaAgAAANFGRuAQYA9DMTAAABAJAAAQDzAwFi9BgRAAABAJMBAQCXBgAAh7BAEAAAAaBwAAASuLCQE2EAATgJAzowAAABAAAAAAAkABAAAADJBcoFywXMBT4HPwdEDAkNCg0LDQ8NEA0RDfgP+Q9MEE0QURBSEG8QcBBxEHIQcxB0EHUQuBC9EOgQ6RCdE8ET6BOEFJAUshTDFA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/6f53f353729b.js b/pr/617/docs/search.index/6f53f353729b.js new file mode 100644 index 000000000..1958ca342 --- /dev/null +++ b/pr/617/docs/search.index/6f53f353729b.js @@ -0,0 +1 @@ +rn_("AQUAOzAAAAEAAAcAAwDbEQIAjBMBACMUAgABBADz3hEAAAEAAQAjAAKgYAAAEw1hcvP/EAAAAQDKAQGAAqEQAAAUsrCAFKgAAWl1NwQAgYexUBTPAAGgAAAAEN6wcBPsAAGhMAAAFMMKQBsjgAKgMAAAEqtudg==") \ No newline at end of file diff --git a/pr/617/docs/search.index/6f6c17668170.js b/pr/617/docs/search.index/6f6c17668170.js new file mode 100644 index 000000000..979d144ea --- /dev/null +++ b/pr/617/docs/search.index/6f6c17668170.js @@ -0,0 +1 @@ +rn_("AQYA9J4RAAABAAEAAQABAgA7MAAAAQAACgAEAJ8QBQDjEgEAQxMBAGkUAADzgAJmbgEBADswAAABAAAIAAIAtg8GAGwTAQDzgAJkbgEBAPM4BwAAAQABAAGGAqAAAAATZaAAAAATZmRy8wEBdgGREwAAQwADsIAS+AABsLAUmQABa2xyOzAAAAEAAAwAAwAvBwgAFw0BAFgQAQABAgD1AggAAAEAAQABAAEA8wCEFEEAAYEAAAA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/6f86e944f992.js b/pr/617/docs/search.index/6f86e944f992.js new file mode 100644 index 000000000..352759ec8 --- /dev/null +++ b/pr/617/docs/search.index/6f86e944f992.js @@ -0,0 +1 @@ +rn_("AQABoEAAABC9dPcEDQAARAPxAgEAuQCfAAEA8wABczswAAABAAAcAAgAtQQCADoMAwAAEAEAxxABAAgRAQD4EQEAtBMHAKwUBQAjgAKxEBSzAAFhdA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/6fb11194cbac.js b/pr/617/docs/search.index/6fb11194cbac.js deleted file mode 100644 index 07b684cf9..000000000 --- a/pr/617/docs/search.index/6fb11194cbac.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9N0SAAABAKYAAQABCAD0mRMAAAEAiAABADMBA7BAE2gAAWFiZfUFDQAAAQBDA2wC0gABAAA7MAAAAQAABgACAD4MAQDMDwQA84ACc3UjgQKwgBOTAAFydAEGAPSVEAAAAQC3AAEAUwCEsIATGgABoKAAABTZAgEU9X4FAAABAPMOVwABAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/6fdce15328df.js b/pr/617/docs/search.index/6fdce15328df.js new file mode 100644 index 000000000..66e44b101 --- /dev/null +++ b/pr/617/docs/search.index/6fdce15328df.js @@ -0,0 +1 @@ +rn_("AYACsHASqQABsCAUwQABaW8BAAGw8BTBAAFrOzAAAAEAAAgAAwAoFAAALRQGAFgUAADzBQFk8usSAAABAAECAPPrEAAAAQABAPMAAWk6MAAAAQAAAAAABwAQAAAAxhAAE+wT7RMdFB4UPxRAFCODArCwFJwAAWFzAYECoKAAABNVoEAAABCEb3bzgIQBUAE=") \ No newline at end of file diff --git a/pr/617/docs/search.index/6ff044a53b85.js b/pr/617/docs/search.index/6ff044a53b85.js deleted file mode 100644 index 8f35d1862..000000000 --- a/pr/617/docs/search.index/6ff044a53b85.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQBoEAAABQ1ZfKrFAAAAQABAQA7MAAAAQAABgABANIUBgDzBgFl9IAUAAABADMAAQBTAISgIAAACWuwYBOWAIoBAQnzixAAAHsCAQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/7021fc091b39.js b/pr/617/docs/search.index/7021fc091b39.js new file mode 100644 index 000000000..27a49613c --- /dev/null +++ b/pr/617/docs/search.index/7021fc091b39.js @@ -0,0 +1 @@ +rn_("AQABoCAAAAz7bQGIBQAAAQAA9B4TAAABAAEAAQDzBAFk9MISAAABAAEAAQAjhISwoBRmAAGwQBMcAAGwcBQNAAEVgAAzgAOw0BRQAAFhZWk=") \ No newline at end of file diff --git a/pr/617/docs/search.index/703f5cade60e.js b/pr/617/docs/search.index/703f5cade60e.js deleted file mode 100644 index d4a472968..000000000 --- a/pr/617/docs/search.index/703f5cade60e.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9IkTAAABAAEAAQABBADz1A8AAAEAAQABgAKgUAAAEOWgYAAAEVVpcgGAAqAgAAAMhaBgAAARVGVoAQEA84EMAAABAAEAE4ECoBAAAAyEYWMBAwA7MAAAAQAABQACAH0MAwBoEQEAAQYA9CMTAAABAJgBAQATAAKwYBMlAAFmbfLyDAAAAQAjgAKgwAAAE/RucqcPAACIoEAAAA/RsEAP0gABNCAXOzAAAAEAABkAAwALAgAAbQQOAAIMCQABgAOwcBOoAAGwMBNSAAGwgBPhAAFjcnMBAAGwIBOoAAFvOjAAAAEAAAAAAAkAEAAAAO8R8BEMEw0T+hP7E1EUUhTMFM0UAYACsCAJMQABsDATXQABcnT3BAAAyKAgAAAHfqBgAAAP/bCAE10AAUAGkgn2FwIAAHoC6wQBAJEKAQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/70830eb0fe35.js b/pr/617/docs/search.index/70830eb0fe35.js deleted file mode 100644 index e88eedffc..000000000 --- a/pr/617/docs/search.index/70830eb0fe35.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQACoDAAABEsoJAAABOiMGj1ExIAAJcBAQABAAEAE4ACoDAAAA/gZHIjAYSwEAkGA0KwIAwVAQugMAAAEAUSABj1oAUAADEAAQABAAEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/70937cc739a3.js b/pr/617/docs/search.index/70937cc739a3.js deleted file mode 100644 index 8b22ed895..000000000 --- a/pr/617/docs/search.index/70937cc739a3.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQBoEAAABGTZQFLEAAAAQgA9NYRAAABAAEAAQABAwGgQAAAEZJlAdoPAAABAgDzQAwAAAEAAQAjAAKwIAwuAAFrcvb7BgAAAQAZCu4AAQA0AvOAAnJ4AQMBsIAUcAABdjswAAABAAAMAAMAJhEBAOERBgDREwMAI4ACsGAQlwDBYXIBAwGgAAAAEEpz8tgPAAABACOAAqCwAAAT+WVoAQMBoEAAABElZfIlCQAAAQDXCwAAiqAgAAAHcLCwE0gAAaCAAAAR2qAAAAATVZ8ITDowAAABAAAAAAAKABAAAAAMAn0EfgR/BBgMGQyBDIIMgwxWE2wT") \ No newline at end of file diff --git a/pr/617/docs/search.index/70a2231ef1be.js b/pr/617/docs/search.index/70a2231ef1be.js deleted file mode 100644 index b4c76465c..000000000 --- a/pr/617/docs/search.index/70a2231ef1be.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQA9hsNAAABAD4DAQAuAQEAAQoA8+YSAAABAKoAAQEA9y8JAAABAF0HAQBzAAEAFgEjgAKwYBP+AAFpbwECAPTYDAAAAQBOBgEAAQUA9BQUAAABAFAAAQD3CAAAiLCQE8sAAaAgAAAM2rCQEgsAAQTcAzowAAABAAAAAAAIABAAAACZBSAJIQl0DHUMERESERgTGRM=") \ No newline at end of file diff --git a/pr/617/docs/search.index/70a6f2d585ca.js b/pr/617/docs/search.index/70a6f2d585ca.js deleted file mode 100644 index c079bc3d6..000000000 --- a/pr/617/docs/search.index/70a6f2d585ca.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIBsIAT+gABdAEkCQAAAYECoBAAABNWoHAAABOeYWPzAQFy8roRAAAgAPOAAmVpAQAA9CQMAAABAAEAAQDzgAJkcvMAAWX0kAAAAHEMTgcBAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/70a97ca4cdef.js b/pr/617/docs/search.index/70a97ca4cdef.js new file mode 100644 index 000000000..8f2876bda --- /dev/null +++ b/pr/617/docs/search.index/70a97ca4cdef.js @@ -0,0 +1 @@ +rn_("AQIBoKAAABN9ZPL9BgAAAQABgQKgMAAAFIOgUAAAFAxjcwEEA7AgEVwAAaBQAAASCKBAAAAUS2xzdvJGFAAAAQDzAANhZW87MAAAAQAACAADAIwEAQAWDAAAIQ0FAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/70f6f7a9ec4a.js b/pr/617/docs/search.index/70f6f7a9ec4a.js deleted file mode 100644 index a9651919c..000000000 --- a/pr/617/docs/search.index/70f6f7a9ec4a.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABsPAUwgABazswAAABAAAIAAMAKRQAAC4UBgBZFAAAEwACsQAUqwABZG7y7BIAAAEAE4ACsBAMZAABZW8BBQA7MAAAAQAADQABAKwRDQABAgDzFxAAAAEAAQABAgD0ExAAAAEAAQABAAEDADswAAABAAAIAAEAyRAIAAGAAqBAAAARILDAFG0AAWVzAQEAOzAAAAEAABMAAQA0EBMAI4ACsJAT1QABaW03NgAAiqAQAAAMN6BQAAAUOLBwFBYAAaAgAAANAZxVETswAAABAAAGAAMAYAcAAPkQAwDMEgEA84ACbHQ=") \ No newline at end of file diff --git a/pr/617/docs/search.index/7157aa96e738.js b/pr/617/docs/search.index/7157aa96e738.js new file mode 100644 index 000000000..728a2d500 --- /dev/null +++ b/pr/617/docs/search.index/7157aa96e738.js @@ -0,0 +1 @@ +rn_("AQDKoRAAABSKgAkPABrboEAAABElsGAUegABoBAAAAyUsFAUEQABsDARhwABoAAAAAxSsSAUnAABsEARIwIjQE6wCTowAAABAAAAAAAUABAAAAAlCSYJOQk6CRoMGwwcDB0MIwwoDO0MshLeEt8S4BKYE5kTrhOvEyEUIhQ=") \ No newline at end of file diff --git a/pr/617/docs/search.index/7193e1d068da.js b/pr/617/docs/search.index/7193e1d068da.js deleted file mode 100644 index 07b3af48c..000000000 --- a/pr/617/docs/search.index/7193e1d068da.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMA83YQAAABAAEAAYACoLAAABQ4sDATggABbnRDAAOgIAAAESuwUBIDAAFlaGk7MAAAAQAABgADACAMAAAUEgMA4RMBAPOAAmRy") \ No newline at end of file diff --git a/pr/617/docs/search.index/71d44b922c39.js b/pr/617/docs/search.index/71d44b922c39.js deleted file mode 100644 index 127476196..000000000 --- a/pr/617/docs/search.index/71d44b922c39.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQoBoIAAABTIZgGnEwAAAQgA9VMUAAABAAEAAQABAPMDAWb1qhAAAAEAAQABAAEA84MCYXMBAgKgIAAAEe6gMAAAErpiaAHeDwAAI4ACoIAAABNbYXDzAAJobQGnBAAA") \ No newline at end of file diff --git a/pr/617/docs/search.index/72676d5cbaf8.js b/pr/617/docs/search.index/72676d5cbaf8.js new file mode 100644 index 000000000..230d2d967 --- /dev/null +++ b/pr/617/docs/search.index/72676d5cbaf8.js @@ -0,0 +1 @@ +rn_("AQEAOjAAAAEAAAAAAAgAEAAAABkJhxAyETMRNBEKFAsUlhSbFAECAPTCEAAAAQABAAEAE4ACoNAAABSDY3NTgAOgcAAAFH5laW+DgISwIBOuAAGgIAAAD/uwAAdcAAEKCAQ=") \ No newline at end of file diff --git a/pr/617/docs/search.index/72de5c3ebdcb.js b/pr/617/docs/search.index/72de5c3ebdcb.js deleted file mode 100644 index eff818d04..000000000 --- a/pr/617/docs/search.index/72de5c3ebdcb.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQYA9M0TAAABAAEAAQABDwD0uBQAAAEAAQABAPOHAmhtAQAA9B8TAAABAAEAAQDzBAFk9MMSAAABAAEAAQAjh4SwoBRnAAGwQBMdAAGwcBQOAAEVgAABAAGgQAAAEKdlOzAAAAEAAAcAAgB2BwUABwgBABOAArBQEF0AAWRyAQkA9e8TAAABAAEAegABALcCAICGsCASzgABftpKlumdEUkB") \ No newline at end of file diff --git a/pr/617/docs/search.index/72f13898206e.js b/pr/617/docs/search.index/72f13898206e.js deleted file mode 100644 index 1a82e4679..000000000 --- a/pr/617/docs/search.index/72f13898206e.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUAOzAAAAEAAAcAAwDbEQIAjRMBACQUAgABBADz3hEAAAEAAQAjAAKgYAAAEw5hcvP/EAAAAQDLAQGAAqEQAAAUs7CAFKkAAWl1NwQAgYexUBTQAAGgAAAAEN6wcBPtAAGhMAAAFMQKQBsBAAA7MAAAAQAAjQABABgOjQABAgD2sRMAAAEArABYABgAAQAjgQKgIAAAER9ydvOAAmVvI4ACoCAAABKsZHQBAAA7MAAAAQAA8QAEAM4FAAAPCO4AlAwAAIERAADzgANkbnY=") \ No newline at end of file diff --git a/pr/617/docs/search.index/731453b1decc.js b/pr/617/docs/search.index/731453b1decc.js new file mode 100644 index 000000000..1aabc8758 --- /dev/null +++ b/pr/617/docs/search.index/731453b1decc.js @@ -0,0 +1 @@ +rn_("AYECoCAAABSnsEAQDQABYnABBQD0WBMAAAEAmAABAPMDAWMBwRIAAAEMAbAAFJIAAXM7MAAAAQAABQABAKwUBQDzgAJudgGAArAwDGgAAaAwAAAMamlvAQMBoAAAABRycwEIEgAAAQQA9CQMAAABAAEAAQDXAwCAhrAABXgAAZFIAQ==") \ No newline at end of file diff --git a/pr/617/docs/search.index/7375756c2f33.js b/pr/617/docs/search.index/7375756c2f33.js new file mode 100644 index 000000000..d6a4d7b64 --- /dev/null +++ b/pr/617/docs/search.index/7375756c2f33.js @@ -0,0 +1 @@ +rn_("AQIBsIAT+QABdAEkCQAAAYECoBAAABNVoHAAABOdYWPzAQFy8roRAAAgAPOAAmVpAQAA9CQMAAABAAEAAQDzgAJkcvMAAWX0kAAAAHEMTQcBAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/740867f5f36d.js b/pr/617/docs/search.index/740867f5f36d.js new file mode 100644 index 000000000..fc6a98cc1 --- /dev/null +++ b/pr/617/docs/search.index/740867f5f36d.js @@ -0,0 +1 @@ +rn_("AYYCoAAAABNloAAAABNmZHIBgAKgcAAAE/igMAAAEShmdAELAaBAAAAUSmXyeBQAAAEAI4ECsFAQ/wABY3YBAAGwgBRMAAFw8kgUAAABAEMGA6AgAAARcKBAAAASxmRlc/MJEAAATQMBAPMABGlyc3Y7MAAAAQAADwAFAP8GBQDtEgUAGRMBAJETAACDFAAA") \ No newline at end of file diff --git a/pr/617/docs/search.index/747e275b3069.js b/pr/617/docs/search.index/747e275b3069.js new file mode 100644 index 000000000..5c7c74bd7 --- /dev/null +++ b/pr/617/docs/search.index/747e275b3069.js @@ -0,0 +1 @@ +rn_("AQIAOzAAAAEAAO8AAgAeDAAAKQ3uAPMAAW07MAAAAQAA7wACAKcFAADVBe4AAQMAOzAAAAEAAAgAAgC2DwYAbBMBAPMAAWM7MAAAAQAAVwAnAKkECwBzBQAAHgkBADgMAQBADAIAAA0AAAwNAgAeDQEA4A8AAOUPAQDoDwEA7g8AAHYQAgB9EAEAghABAKcQAAAfEQAAJREAAHERAQB+EQEAkhEBAJYRAgD2EQEACRICAA8SAADGEgAAzxIBANQSAQAcEwEALBMBADETAQBREwEAYRMDAGcTAQDIEwEA3BMAAEoUAABzFAQAfBQCAPOAAnJ3AQkA844UAAABADsAAYACoEAAABNrsCAUKQABaGkBAQDzChAAAAEAAQATgAKwwBTIAAF0dQGBArCAFJ4AAaBgAAAUxmRzNwMAgYagQAAAFHGwYBRBAFACkA4BiAKwABP0AAGwABP2AAE0NjOAA6AwAAARlWRuc2MAA6AQAAAEzGFlbwGHAAAA") \ No newline at end of file diff --git a/pr/617/docs/search.index/752f6f507e62.js b/pr/617/docs/search.index/752f6f507e62.js deleted file mode 100644 index ad800d7fe..000000000 --- a/pr/617/docs/search.index/752f6f507e62.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQcA9KMTAAABAAEAAQDzBQF0OzAAAAEAAAgAAgAXDAAASQwHAAEFAPO/EwAAAQABADcAAICFsDAQXQABoKAAABPdoDAAABQaEEFI") \ No newline at end of file diff --git a/pr/617/docs/search.index/755bb5c9b7cf.js b/pr/617/docs/search.index/755bb5c9b7cf.js new file mode 100644 index 000000000..f103f4274 --- /dev/null +++ b/pr/617/docs/search.index/755bb5c9b7cf.js @@ -0,0 +1 @@ +rn_("AQUA8y4TAAABAAEAAQAAOzAAAAEAAAUAAgARDAMABhIBAPMAAWT0DhEAAAEAmgABAFMAA7AwFGYAAWVwcvT0DwAAAQDuAAEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/756205ce70fb.js b/pr/617/docs/search.index/756205ce70fb.js deleted file mode 100644 index ac86b5c76..000000000 --- a/pr/617/docs/search.index/756205ce70fb.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYMCsQAUzgABsNAUvgABa20BCgDzEBQAAAEAdwATgAKiEAAAFNlsbzMChKBgAAASp6EAAAAUigSAAzswAAABAAA2AAIAWQcAAKwHNQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/75c296e3af6c.js b/pr/617/docs/search.index/75c296e3af6c.js new file mode 100644 index 000000000..a61365f19 --- /dev/null +++ b/pr/617/docs/search.index/75c296e3af6c.js @@ -0,0 +1 @@ +rn_("AQcA9rATAAABAKwAWAAYAAEA8wEBaQFrCQAAAQAA9W4JAAABAAEAAQABAEcBAIDGMXkwU1BMoKAAABSmsGAUFQABoBAAAAlCAAogig==") \ No newline at end of file diff --git a/pr/617/docs/search.index/7653e5adb45d.js b/pr/617/docs/search.index/7653e5adb45d.js new file mode 100644 index 000000000..8f742cd5d --- /dev/null +++ b/pr/617/docs/search.index/7653e5adb45d.js @@ -0,0 +1 @@ +rn_("AQkA844UAAABADsAAYADoEAAABNrsCAUKQABoCAAAA/naGl5AQkBoIAAABTHZgGmEwAAAQMBsAAUQQBQc/K9EAAAKwMjgAKgIAAAD+NhcgECAPXrEAAAAQABAF4CAQDzAANpdHU6MAAAAQAAAAAAEAAQAAAAiQc8CQQNSBDGEAATORM6E+wT7RPzEx0UHhQ/FEAUkhSTFAGAAqAwAAAUcbCwE8IAAWFsAQEA8woQAAABAAEAE4ACsMAUyAABdHUBAwDz0RIAAAEAAQABAQD1TxEAAAEAAQABAAEAI4ACsJAUFQABYXQBBAGgQAAAFHVl8pcUAAABAAECADswAAABAAALAAUAaRAFAOESAQAPFAEAaBQAAIcUAAATggKw8BTLAAFmZwGBArCAFJ4AAaBgAAAUxmRzAQgA9VIUAAABAAEAAQABAPMCAWb1qhAAAAEAAQABAAEAI4ADoCAAAA/hsKAUnAABZHJ0d+8BAI2wEBRMAAGw4BROAAGX1Q47MAAAAQAAPQETAIgEAQC1BAIAqgUCAMcGBAD8BwEAOgwDAGgMAQD0DAEArg4HAQAQAQDHEAEA2hACAAgRAQCbEQAAzhEDAPgRAQDzEgAAtBMHAKwUBQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/768dee78c1da.js b/pr/617/docs/search.index/768dee78c1da.js deleted file mode 100644 index cfbdd95f5..000000000 --- a/pr/617/docs/search.index/768dee78c1da.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAAOjAAAAEAAAAAAAoAEAAAAPYP9w/mEOcQcxEFEgYSIxMkE7wUvRTzAQF58hwUAAABACMAA6BAAAARkqBQAAAUb2Vvc/LaDwAA2AI=") \ No newline at end of file diff --git a/pr/617/docs/search.index/76e5a08e87b8.js b/pr/617/docs/search.index/76e5a08e87b8.js deleted file mode 100644 index c385d95c4..000000000 --- a/pr/617/docs/search.index/76e5a08e87b8.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQA9IUUAAAMACIAEQABAgGwQBLQAAFlAd8PAAABgAKgkAAAFEegkAAAFEgyM5cCAADGoCAAAAyFoGAAABFUsGANGQABAWQAAzswAAABAAAKAAMAIwcFAFEMAACJEwMA") \ No newline at end of file diff --git a/pr/617/docs/search.index/7713a6d48c1d.js b/pr/617/docs/search.index/7713a6d48c1d.js deleted file mode 100644 index 44b3a274a..000000000 --- a/pr/617/docs/search.index/7713a6d48c1d.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEBsIATfAABdfJUCQAAAQAjgAKwEAlSAAEzNgEGAPMBEQAAAQAWAROAArAAD8oAAW91AQIA81sHAAD1AQEAAQMA898SAAABAAEAI4ACsAAR1AABaGkBAAA7MAAAAQAAEwABAJAHEwAjgAKgMAAADO1hZfOAAnJ0E4ACsDATywABYWwBAAGggAAAFMhm8goJAACdCgEHAPV1EwAAAQABAAEAAQAjgAKgUAAAEXNhb/MCAmZzOzAAAAEAAAgABABXBwAAigcFABUNAACrEQAAE4ACoEAAAAwfYW8BAwGwQBSNAAF08uoTAAABACOFAqBAAAAUGmJzIwADsEAREwABoFAAABGAYWJm9xcJAAABAEgAeAcBAGIAAQATgQKgUAAAEhBndAGAAqBAAAAR/aAgAAAT+WFpAQIA9E0JAAABAAgIAQABAgDzYQwAAAEAAQATgAOwEAlLAAGwMBQsAAFpdXkBAgA7MAAAAQAACwACAOMTBQChFAUA8wgBbTswAAABAAAMAAMAfREAAPsSBQBfFAUAAYECoIAAABLooHAAABRAYXPzgAJucrfbAQCNoUAAABScoCAAAAlPoFAAABASN5UPOzAAAAEAADcCBQD+AQAAJAIzARMHAAB/BwAAEgoAAQ==") \ No newline at end of file diff --git a/pr/617/docs/search.index/7735482e90b3.js b/pr/617/docs/search.index/7735482e90b3.js deleted file mode 100644 index 5ea335abd..000000000 --- a/pr/617/docs/search.index/7735482e90b3.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEAOjAAAAEAAAAAAAgAEAAAABkJhxAyETMRNBELFAwUlxScFAEBAqBAAAAUf6AwAAAROGVo9REJAAAuADADAQCBBQECAPToEQAAAQABAAEAE4ECsCAR7AABYXPzgAJubwECAPTCEAAAAQABAAEAEwACoNAAABSEY3PyMhAAAAEA84ADZWlv") \ No newline at end of file diff --git a/pr/617/docs/search.index/7778f4540e60.js b/pr/617/docs/search.index/7778f4540e60.js new file mode 100644 index 000000000..13fa5bac7 --- /dev/null +++ b/pr/617/docs/search.index/7778f4540e60.js @@ -0,0 +1 @@ +rn_("AQQBoEAAABQ0ZfKqFAAAAQABAQA7MAAAAQAABgABANEUBgDzBgFl9H8UAAABADMAAQAzBQOwYBOVAIphcnXzixAAAHoCAQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/77ae1f56a954.js b/pr/617/docs/search.index/77ae1f56a954.js deleted file mode 100644 index 59834fc78..000000000 --- a/pr/617/docs/search.index/77ae1f56a954.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsHASqgABsCAUwgABaW8BAAGw8BTCAAFrOzAAAAEAAAgAAwApFAAALhQGAFkUAADzBQFk8uwSAAABAAECAPPrEAAAAQABAPMAAWk6MAAAAQAAAAAABwAQAAAAxhABE+0T7hMeFB8UQBRBFCODArCwFJ0AAWFzAYECoKAAABNWoEAAABCEb3bzgIQBUAE=") \ No newline at end of file diff --git a/pr/617/docs/search.index/77e67fb66bc4.js b/pr/617/docs/search.index/77e67fb66bc4.js deleted file mode 100644 index 6da799301..000000000 --- a/pr/617/docs/search.index/77e67fb66bc4.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMA9CAJAAABAFMDAQABAwDzcQwAAAEAAQDzgAJpbwEBAbBQER0AAWP0ZwcAAAEAAQANAiOBAqBQAAAQ3mNuAQQAOzAAAAEAAAcAAwDbEQIAjRMBACQUAgABAQA6MAAAAQAAAAAABwAQAAAA/hH/ET8TQBOXE5gTRRRGFPMJAWkBHBEAANcCAACHsMATggABoFAAAA/JoRAAABRvFAEXOzAAAAEAACIAAQC6ASIA") \ No newline at end of file diff --git a/pr/617/docs/search.index/7811875cd00f.js b/pr/617/docs/search.index/7811875cd00f.js new file mode 100644 index 000000000..7376aa414 --- /dev/null +++ b/pr/617/docs/search.index/7811875cd00f.js @@ -0,0 +1 @@ +rn_("AQAA9MUPAAABAAEAAQDzAgFzAUIRAAABAAKwgBSeAAGgYAAAFMZkc/IGCQAAQgOTgISwwBPaAAGgEAAAEe0AwUA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/783c8a1a67c6.js b/pr/617/docs/search.index/783c8a1a67c6.js deleted file mode 100644 index 6088a6740..000000000 --- a/pr/617/docs/search.index/783c8a1a67c6.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIBsEAUjQABdDswAAABAAANAAYADxMDAHoTAQDqEwEAARQBAIIUAQCpFAEAE4ACsDATQwFJaXABBQDzvxMAAAEAAQATgAKgMAAAFBpleVMBA7BwFCcAAWJlcwFQEAAAE4ADsDAUnQABsCAHBgABZWxz") \ No newline at end of file diff --git a/pr/617/docs/search.index/78db9c8703f7.js b/pr/617/docs/search.index/78db9c8703f7.js deleted file mode 100644 index e5750492f..000000000 --- a/pr/617/docs/search.index/78db9c8703f7.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQgA9VMUAAABAAEAAQABAPMAAWY7MAAAAQAABQACAPIBAACqEAQAAQIAOzAAAAEAAAsAAgDjEwUAoRQFAPMGAW07MAAAAQAADQAEAOAMAAB9EQAA+xIFAF8UBQBjgAOwAAkxAAFlbXM=") \ No newline at end of file diff --git a/pr/617/docs/search.index/79f5b506de04.js b/pr/617/docs/search.index/79f5b506de04.js new file mode 100644 index 000000000..221a6dbf3 --- /dev/null +++ b/pr/617/docs/search.index/79f5b506de04.js @@ -0,0 +1 @@ +rn_("AQABsGAUHQABZQFXFAAAAQYA9EcTAAABAFEBAQABBAD0YRMAAAEAEQEBAPMAA2VyczowAAABAAAAAAAJABAAAAAzCTQJNQlVEZQRqRKqErcSFRQWFA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/7a17df9d410e.js b/pr/617/docs/search.index/7a17df9d410e.js new file mode 100644 index 000000000..b54197ab6 --- /dev/null +++ b/pr/617/docs/search.index/7a17df9d410e.js @@ -0,0 +1 @@ +rn_("AQMBoAAAABPzcwEJEQAAAQMA9JgQAAABAAEAAQATgAKwUBCcAAFhaQGAArBwFL8AAbBwFEwAAWVyAYACoLAAABSDoDAAABE1bG6XAQAAhaAgAAASsKAgAAAJCBCRBDowAAABAAAAAAATABAAAACfBNcE2ATZBNoE2wTcBHgJxQ/GD8cPyA9bE5UTGRQfFCsULBRIFEkUAQIAOjAAAAEAAAAAAAoAEAAAAGEJYgkxEH8TgBOwE7ETXRS1FM0UzhQBAAA7MAAAAQAABgACAJwTAAAEFAUAAQIBsDATbAABYzowAAABAAAAAAATABAAAAAMDeAPdhB3EHgQfRB+EIIQgxAfEZIRlhGXEZgRDxJhE2ITShRzFHQUAQMA89YTAABsAAEAE4ECsEARfgABYWUTgAKgsAAAFIphZQEBAbBAFHYAAWX1OhQAAAEAAQABAAEAE4ACoBAAAAwhYW+HDwAAyLAwCRkHbrBAFHwAAaCAAAASq0BMiAnzAAgAACkEPgABgAKggAAAE4agkAAAE15hawEDAPPREgAAAQABACOAhLEgFMsAAaAAAAAJPKAwAAAP5wQEBgEIAPXTFAAAAQABAAEAAQATigKwQBSqAAFhcpMAhKDAAAATWqEAAAAUKAFABvKCBwAAAQABAwGgQAAAEgll8xYNAAATBAEAAQUA9MwTAAABAAEAAQBTgAOwABOBAAFlc3QBBgD0txQAAAEAAQABAPMIAW30vRIAAAEAAQABAAECADswAAABAAAFAAEAoBQFAPMNAW07MAAAAQAABQABAF4UBQABBAD3Hg0AAAEApwUOAAEAjgABAAEDAYATjgAQ+nM7MAAAAQAABQABAG4TBQAjggKgkAAAFHJpcwEFAaBQAAAUN2jyyxIAAAEAE4ACsDATgQABbnQBgAKggAAAE6GwQBICAAFhZQGAAqAQAAARK6AAAAARcG5yAQkBoEAAABR1ZfKXFAAAAQAjgAKgkAAAE9lpbMcjAAAKoDAAABEssFATNwABsPAUwQABsHATngABoNAAABR+MGJjZWhpa25vdjswAAABAAAeAAsAIAwAAAMRAwASEgQAmhMBAKkTAwDgEwEAKBQAAC0UBgBMFAEAWBQAAG8UAQABAgKgsAAAFGigsAAAFGljc/PfEAAAAQBfAAEBArBQEgIAAaDAAAAUN2hp8uATAAABAPOAAmVpAQEBsJATmgABc/TCEAAAAQABAAEAAQMBsAAQ/QABcwFlEAAA84ADaGl0AQEBoFAAABMbYwHfDAAAAQUBsIAUbwABdjswAAABAAAHAAIA4REDANATAwABAQD0hRAAAAEAswABAOOAhLBwEgYAAQ4IAPdlDwDRsJASqQABoFAAABEkoQAAABSmoKAAABO9oBAAAAdYQD+/KTswAAABAAA8ABkAxwQAAMkEAACdBQIAIAcCAA0MAwDiDAEA1A8CAOIPAAAJEAAAEhAAADIQAQBCEQgAmREAAOsSAQD0EgEA+BIBAFYTAQB0EwQAyhMBANQTAQDZEwAAUhQEAFkUAwCmFAAAxxQAAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/7acb48b1a833.js b/pr/617/docs/search.index/7acb48b1a833.js deleted file mode 100644 index 86c7334fb..000000000 --- a/pr/617/docs/search.index/7acb48b1a833.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQA9hsNAAABAD4DAQAuAQEAAQoA8+YSAAABAKoAAQEA9y8JAAABAF0HAQBzAAEAFgEjgAKwYBP+AAFpbwECAPTYDAAAAQBOBgEAAQUA9BQUAAABAFAAAQD3BAAAh6AgAAAM2rCQEgsAAQScAzowAAABAAAAAAAIABAAAACZBSAJIQl0DHUMERESERgTGRM=") \ No newline at end of file diff --git a/pr/617/docs/search.index/7ad1f024ed61.js b/pr/617/docs/search.index/7ad1f024ed61.js new file mode 100644 index 000000000..ac3e9e8b9 --- /dev/null +++ b/pr/617/docs/search.index/7ad1f024ed61.js @@ -0,0 +1 @@ +rn_("AQEBsFAP6gABY/SkBAAAAQABAMoAAQABoIAAABTHZgGmEwAA8wYBc/IVDQAAlgQzAAOgYAAAFKdjaXPy6xEAAAEA") \ No newline at end of file diff --git a/pr/617/docs/search.index/7b8c3c69a10a.js b/pr/617/docs/search.index/7b8c3c69a10a.js new file mode 100644 index 000000000..8b33d3b61 --- /dev/null +++ b/pr/617/docs/search.index/7b8c3c69a10a.js @@ -0,0 +1 @@ +rn_("AQIA9M4RAAABAAEAAQATAAKgMAAAEZlldPTUEAAAAQABAAEAAQMA9MoRAAABAAEAAQABgAKgAAAAEKWgAAAAEKY0NvMDAXYBEw0AAJcCAACIsKATLAABsCAMNQABsGATJgABsIATCwABoCAAABEQCUxGOjAAAAEAAAAAAAkAEAAAABAHEQcSB0YMCBBvEboSuxK8Em4U") \ No newline at end of file diff --git a/pr/617/docs/search.index/7b927b2e19c3.js b/pr/617/docs/search.index/7b927b2e19c3.js new file mode 100644 index 000000000..40e6c1807 --- /dev/null +++ b/pr/617/docs/search.index/7b927b2e19c3.js @@ -0,0 +1 @@ +rn_("AQIA9bsRAAABAAEAAQABAAGAArBgEyYAAaAgAAAREG95AQABoMAAABRLafR9EwAACAAcABsAAQYA9LcUAAABAAEAAQABAgD0DhEAAAEAmgABAAECAaBAAAATVHI7MAAAAQAABwACAAwRAQCjEQUA84ACYW9nAwAAhrCAE1MAWrCgE8oAAQwwBjswAAABAAAIAAMALQwAANYRAwC9EgMA84ICZWkjAAKggAAAFMdmaPIKCQAAnAoBBQDzLhMAAAEAAQABAQA7MAAAAQAABQACABEMAwAGEgEAUwADsDAUZgABZXBy9PQPAAABAO4AAQABAAOwQBQrAAGhQAAAFJugUAAAEfxiY2YBEwcAAPOAAm1uAQGEoLAAABPosEAQxwABsMAUFwABoGAAABGiFCAE9voGAABlCQEAOgEvARQBAQUAOzAAAAEAAAUAAQDtEgUAAQcA9XQTAAABAAEAAQABACOAAqBQAAARc2Fv8wCFIEkCOzAAAAEAAAgABABXBwAAigcFABUNAACrEQAA") \ No newline at end of file diff --git a/pr/617/docs/search.index/7bb93f30bb12.js b/pr/617/docs/search.index/7bb93f30bb12.js deleted file mode 100644 index ce2f414f0..000000000 --- a/pr/617/docs/search.index/7bb93f30bb12.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9EUHAAABAAEAAQABhQKwcBQnAAGwMBN6AAFlcxMAA7BAD+gAAaBgAAARnGJlczswAAABAAASAAQArwUBANEGCgBhEAEAuRADAAECAPO9BQAAUAoBAAEDAPXgDwAAnQABAAQAAQABCgGgQAAAFHZl8pgUAAABACMABKAwAAARLKCQAAATorBgEswAATBjaGn1ExIAAJcBAQABAAEA84ACZHLnBAAAh7AQCQYDQrAgDBUBC6AwAAAQBRJBODswAAABAAASAAYAjQACAKAFAADRBQMApgcFAOsHAwAfDAAA") \ No newline at end of file diff --git a/pr/617/docs/search.index/7beb472ea224.js b/pr/617/docs/search.index/7beb472ea224.js new file mode 100644 index 000000000..48925ff87 --- /dev/null +++ b/pr/617/docs/search.index/7beb472ea224.js @@ -0,0 +1 @@ +rn_("AQgBoIAAABTHZgGmEwAAE4ACoOAAABSJYnABAwGwABRBAFBz9vgPAAABAMQAKwABAP8CAYACsEASzQABoBAAABEocHIBgQOgAAAAEZmwAA/jASegEAAAD9tlc3XzgANhb3IBAgD16xAAAAEAAQBeAgEAAQQAOzAAAAEAAAkAAQBeEQkAAQAA9M4RAAABAAEAAQABgAKgkAAAFAGwUBK1AAFid/OAAmR0o4CEsFAUHQABsJATQwABQCADVwMAAIagcAAAE/igQAAAFG4wQQw7MAAAAQAAQgAhAMUEAQCMBQoAiQcAADYJAQA8CQAAPgwBAGEMAgD2DAEABA0AAKsOAADMDwQA4w8AAAIQAQBIEAAAxhAAANQQAwD3EAEAChEAADARAQCdEQAA8REAAPoRAQD/EQAAABMAADkTAQDsEwEA8xMAAB0UAQA/FAEAVxQAAJIUAQDEFAEAzxQBAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/7c776e35ecc3.js b/pr/617/docs/search.index/7c776e35ecc3.js new file mode 100644 index 000000000..051e5a324 --- /dev/null +++ b/pr/617/docs/search.index/7c776e35ecc3.js @@ -0,0 +1 @@ +rn_("AQQA9L4QAAABAAEAAQABAAA6MAAAAQAAAAAACgAQAAAA9g/3D+YQ5xBzEQQSBRIiEyMTuxS8FPMBAXnyGxQAAAEAAQADoKAAABRpoCAAABOgsDAR+gABZWl08gcTAAABAAEDADswAAABAAAGAAEAdBEGAAEIAPQKFAAAAQCLAAUAEwEDsJAUcwABsFATTwFnZ2xwOzAAAAEAABIACQB5EAIA/REBAD4TAQBfEwEAlhMBAPsTAQA1FAEARBQBAIUUAQABgAKwMBH2AAGwMBH4AAFydgECAbAAEscAAWQ7MAAAAQAABQACAPIRAwD2EgEAI4ECoJAAABRoaW8BBwD0hBQAAAwAIgARAHcWAAGJsFAUGwABsFASyQEUsGATTQABtQkDOjAAAAEAAAAAAA4AEAAAAO8M8AzxDKwOrQ7fEOAQHBE/EZ4RnxGgEaERRRN+E0OAA6AgAAAM4aAQAAAM7m1ubwEDAPR2EAAAAQABABoBAYACoLAAABQ3sDATgQABbnRDAAOgIAAAESuwUBICAAFlaGk7MAAAAQAABgADACAMAAATEgMA4BMBAPOAAmRyhwsAAAqAAfgAEAGAAfsAEAGgUAAAEZugUAAAFG6gMAAAEKmgMAAAELQwMWNlaW9zdHV39aQHAAABADUI1wJbAQ==") \ No newline at end of file diff --git a/pr/617/docs/search.index/7cfe15e65b11.js b/pr/617/docs/search.index/7cfe15e65b11.js new file mode 100644 index 000000000..25ba853b5 --- /dev/null +++ b/pr/617/docs/search.index/7cfe15e65b11.js @@ -0,0 +1 @@ +rn_("AYACsEATfwABoMAAABQ/ZW8BAgD05xEAAAEAAQABABMAA7AgEesAAbCAFFAAAWFzdAHuDAAAAQQA8w8QAAABAAEAs4CEsEATkgABQQAG") \ No newline at end of file diff --git a/pr/617/docs/search.index/7d878a92407a.js b/pr/617/docs/search.index/7d878a92407a.js new file mode 100644 index 000000000..165842cd3 --- /dev/null +++ b/pr/617/docs/search.index/7d878a92407a.js @@ -0,0 +1 @@ +rn_("AQEA9EUHAAABAAEAAQABAQGgQAAAEZlz9UYMAAApBUsBAQABABOCAqAgAAANHWVyI4ADoEAAAAxFsDAJNgABZW90AQAA9EEHAAABAAEAAQABAgD0shMAAAEADwABABOAArBAFGwAAWJ284ACb3gBCgD17hMAAAEAAQB6AAEAAYECoCAAABEKsFASzQABYW8BBAGgAAAAE/NzAQkRAABjAwOwUBF7AAFjZXP2EgkAAAEAAQAsAAEAcgkBAQA7MAAAAQAABQABAOcMBQAjgAKwMBNPAWdydPMAhgHAB/LxAQAACgY=") \ No newline at end of file diff --git a/pr/617/docs/search.index/7deb33a6ac15.js b/pr/617/docs/search.index/7deb33a6ac15.js new file mode 100644 index 000000000..d45049bfb --- /dev/null +++ b/pr/617/docs/search.index/7deb33a6ac15.js @@ -0,0 +1 @@ +rn_("AQYA9CYRAAABAL4AAQATgQOgAAAAEW6xIBSzAAFhZG4BBQD0mBAAAAEAAQABAAEUAPXTFAAAAQABAAEAAQABBQGwgBRvAAF2OzAAAAEAAAcAAgDhEQMA0BMDACOAArBwEgYAAWJkVwIAAYawwBOaAAGgoAAAE72gkAAAE4YF4AL1IAcAAAEAAQDwCJQEAQYA8+USAAABAKoAAQABoGAAAA/9cgGRBAAA1wEAgMWgMAAADB8AAIoK") \ No newline at end of file diff --git a/pr/617/docs/search.index/7e12e28816c8.js b/pr/617/docs/search.index/7e12e28816c8.js new file mode 100644 index 000000000..6af06540d --- /dev/null +++ b/pr/617/docs/search.index/7e12e28816c8.js @@ -0,0 +1 @@ +rn_("AQIAOzAAAAEAAAUAAQCgFAUA8w0BbTswAAABAAAFAAEAXhQFAEcAAACFsAAUbwABsEAS1AABoQAAABSmsAATlQCKOAgCOzAAAAEAABwABwANDAMAQxEHAPgSAQB0EwQAUhQEAFkUAwDHFAAA") \ No newline at end of file diff --git a/pr/617/docs/search.index/7ed09b774fa7.js b/pr/617/docs/search.index/7ed09b774fa7.js deleted file mode 100644 index 5cf21a6fd..000000000 --- a/pr/617/docs/search.index/7ed09b774fa7.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQACoDAAABEsoJAAABOiMGj1ExIAAJcBAQABAAEAE4ACoDAAAA/gZHIjAISwEAkGA0KwIAwVAQugMAAAEAUSABj1oAUAADEAAQABAAEAAQIA9MUUAAABAAoAAQABAgDzfxAAAAEAAQDzgAJpcgEBArBAEXEAAaBQAAATh2Vz9KYOAADhAgEAkwIBCwD2sRMAAAEArABYABgAAQDzgAJsdNOAhKCQAAATXwFEBA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/7eda4a96e99d.js b/pr/617/docs/search.index/7eda4a96e99d.js deleted file mode 100644 index 0ead7d06d..000000000 --- a/pr/617/docs/search.index/7eda4a96e99d.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBgBOPABD6czswAAABAAAFAAEAbxMFACOCAqCQAAAUc2lzBwEAhMWwUBM4AAGgoAAAE9qwcBOfAAGg0AAAFH+AARgI") \ No newline at end of file diff --git a/pr/617/docs/search.index/7ee2eb9126d0.js b/pr/617/docs/search.index/7ee2eb9126d0.js deleted file mode 100644 index e815bb5f5..000000000 --- a/pr/617/docs/search.index/7ee2eb9126d0.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsHAR7wABsCAP+gDwaHkBBQD0Fw0AAAEAQAMBAAEAAPRxDAAAAQABAG4AAQIAOjAAAAEAAAAAAAkAEAAAAO8P8A/lEKIRAhMDEwAUiRS+FL8UAQMA8ysJAAABAAEA1wMAAIagQAAAEgoYQAc6MAAAAQAAAAAAFwAQAAAAcgdzBwEIOAlsCW0JFg3cD90P/Q+oEK8QsBCxELIQ8RDyECkRKhGnEv4T/xMqFCsU") \ No newline at end of file diff --git a/pr/617/docs/search.index/7f22e53fd5b4.js b/pr/617/docs/search.index/7f22e53fd5b4.js new file mode 100644 index 000000000..541a2859a --- /dev/null +++ b/pr/617/docs/search.index/7f22e53fd5b4.js @@ -0,0 +1 @@ +rn_("AQUA9BMUAAABAFAAAQDzAwFj8hcTAAABACOAArBwFMsAAWhvAYICsAAS9AABsAAR+gABZXNDAAOgoAAAFGmgIAAAE6BlaXTyBxMAAAEAAQIAOzAAAAEAAAYAAQB0EQYAAQIBoEAAABRKZfJ4FAAAAQDzgAJpbwEIAPQKFAAAAQCLAAUAEwEDsJAUcwABsFATTwFnZ2xwOzAAAAEAABIACQB5EAIA/REBAD4TAQBfEwEAlhMBAPsTAQA1FAEARBQBAIUUAQABgAKwQBLJARSgIAAAEThhaQEDAPPoEgAAAQABACOBArAwFBsAAWNpAQAA9DkTAAABAFgBAQDzAwFzOzAAAAEAAA8AAwD4EQEAtBMHAKwUBQABAwD19hEAAAEAJQEBAGEB84ACcnYBAAA7MAAAAQAABQACAMcSAQAeEwMA8wIBZDswAAABAAAJAAMA8hEDAMISAwD2EgEAI4ACoJAAABRoaW8TgAKw8BS7AAFsbwEFAPSEFAAADAAiABEAI4ACoGAAABOUZ3MjgAKwkBRmAAFibPfHAACNsGAUnAABsHAUDQABsGATTQABsFATOwABtckXOjAAAAEAAAAAACAAEAAAAL0FEQk/CW8McAzvDPAM8QynDqgOqQ6qDqwOrQ4NEA4QDxAQEBEQ3xDgEBwRPxGeEZ8RoBGhEfARRRNcE10TfhM4FA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/7f43d011c020.js b/pr/617/docs/search.index/7f43d011c020.js deleted file mode 100644 index b7a3157e9..000000000 --- a/pr/617/docs/search.index/7f43d011c020.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYMCsQAUzgABsNAUvgABa20BCgDzEBQAAAEAdwATgAKiEAAAFNlsbzMBhKBgAAASp6EAAAAUigSAAzswAAABAAA2AAIAWQcAAKwHNQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/7f5b04ac747f.js b/pr/617/docs/search.index/7f5b04ac747f.js deleted file mode 100644 index 9b3ddb101..000000000 --- a/pr/617/docs/search.index/7f5b04ac747f.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQgA848UAAABADsAI4ACsAAQ7gABbW4BgAOgQAAAE2ywIBQqAAGgIAAAD+doaXkBCQGggAAAFMhmAacTAAABAwGwABRCAFBz8r0QAAAsA0OAA7AgD+MBJ7BQEs4AAWFvcgECAPXrEAAAAQABAF8CAQDzAANpdHU6MAAAAQAAAAAAEQAQAAAAiQc8CQQNSBDGEPIRARM6EzsT7RPuE/QTHhQfFEAUQRSTFJQUAQYA9CMTAAABAJgBAQATAgKwYBMlAAFmbfLyDAAAAQAjggKwQBNSAAFlbAGAAqAwAAAUcrCwE8MAAWFsAQYA8/EPAADhAAEAAQUA9BQUAAABAFAAAQDzAgFj8hgTAAABAAECAaAwAAAQh2EBZwwAAAGAArBQETsAAbAgD78AAWNu84ACbnQBBQA7MAAAAQAADgACAPgMAAAaEA0AAQIAOzAAAAEAAAcAAQBjCQcA5wUAAAmgMAAADGagsAAAFECggAAAEbqw0BRPAAEwYWZpa21ydXY7MAAAAQAAbQIFAAACAABtA+4AgAcAABML7gAYDo0AAQwBoKAAABTZd/LJFAAAAQABAQDzChAAAAEAAQDzgAJ0dQEDAPPSEgAAAQABAAEBAPVPEQAAAQABAAEAAQABAQGwMA/6APB09nIHAAABAI4A/AerAP8BY4ADsJAUFgABYW10AQMBoEAAABR2ZfKYFAAAAQATgAKwYBTMAAFlcgEDAaAAAAAT9HMBCREAAAECADowAAABAAAAAAAKABAAAABhCWIJMRCAE4ETsROyE14UthTOFM8UgwAEoIAAABKssHARfgABsCARHwDxZm5ydgEACAAAAYECoAAAAAk8oDAAAA/nc3THAQCAxrAAFE0AAbCQEqoAAaAQAAAHWAASiCgBAgA7MAAAAQAADQAGAGkQBQB7EQEA4hIBABAUAQBpFAAAiBQAABOCArDwFMwAAWZnAYACsIAUnwABoGAAABTHZHMTgAKwwBPbAAFpbwEIAPVTFAAAAQABAAEAAQDzAgFm9aoQAAABAAEAAQABACOAA6AgAAAP4bCgFJ0AAWRydPf7BwCPoDAAAA/Xl9cPOzAAAAEAAFUBHgCIBAEAtQQCAIAFAgCqBQIAzwUBAMcGBABJBwAAYQcAAPwHAQASCQIAQAkBADoMAwBoDAEAkAwDAPQMAQCuDgcBABABAH8QAgDHEAEA2hACAAgRAQCbEQAAzhEDAPkRAQC0EgAA9BIAACsTAQCTEwEAtRMHAK0UBQA=") \ No newline at end of file diff --git a/pr/617/docs/search.index/7f660a8647b9.js b/pr/617/docs/search.index/7f660a8647b9.js new file mode 100644 index 000000000..e848caaa7 --- /dev/null +++ b/pr/617/docs/search.index/7f660a8647b9.js @@ -0,0 +1 @@ +rn_("AQEBoEAAABR1ZfKXFAAAAQABgAOgMAAAErKw4BRQAAGwkBSZAAFhY22HAQCAhrAQET0AAbAQEVwAAaCgAAAUg6DwAAAUpjFJAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/7f7a7539772c.js b/pr/617/docs/search.index/7f7a7539772c.js new file mode 100644 index 000000000..4f1f19025 --- /dev/null +++ b/pr/617/docs/search.index/7f7a7539772c.js @@ -0,0 +1 @@ +rn_("AQMAOzAAAAEAAI4AAgAHEQAAGBKNAAEAAbBwE9oAAWXy8g8AAAEAIwACoEAAABQ0ZXP0XRAAAAEATAQBADOAA6BgAAARumRpbw==") \ No newline at end of file diff --git a/pr/617/docs/search.index/7fc5a0d8a50f.js b/pr/617/docs/search.index/7fc5a0d8a50f.js deleted file mode 100644 index b058c1e89..000000000 --- a/pr/617/docs/search.index/7fc5a0d8a50f.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQsA9IUUAAAMACIAEQABCgA6MAAAAQAAAAAABwAQAAAAYBNhE/wT/RM2FDcUhhSHFPOBAm90EwCEsCATVABasDARWgABoFAAABFCEDACOzAAAAEAAAgABACFAAEArwADAGIHAQB+CQAA") \ No newline at end of file diff --git a/pr/617/docs/search.index/desc/ac16b357e09e.js b/pr/617/docs/search.index/desc/ac16b357e09e.js deleted file mode 100644 index a63ca8832..000000000 --- a/pr/617/docs/search.index/desc/ac16b357e09e.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\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.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000BaCalls U::from(self).0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dNextCoMethod 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\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.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_biAjA new path has been opened0ChOne 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 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\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.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\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.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\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 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\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 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.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\xa60AePacing rate (bytes/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\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\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\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 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 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\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 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\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 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\xa60BoThe 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/617/docs/search.index/desc/c4e11ee91647.js b/pr/617/docs/search.index/desc/c4e11ee91647.js new file mode 100644 index 000000000..ee80670c6 --- /dev/null +++ b/pr/617/docs/search.index/desc/c4e11ee91647.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 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\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.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000BaCalls U::from(self).0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dNextCoMethod 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\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.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_biAjA new path has been opened0ChOne 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 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\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.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\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 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\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 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.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\xa60AePacing rate (bytes/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\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\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\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\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 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\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 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\xa60BoThe 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\xa60CkSets the maximum number of nat traversal addresses this \xe2\x80\xa60AcNat traversal draft") \ No newline at end of file diff --git a/pr/617/docs/search.index/entry/55a8046a740f.js b/pr/617/docs/search.index/entry/55a8046a740f.js deleted file mode 100644 index e397fc0e4..000000000 --- a/pr/617/docs/search.index/entry/55a8046a740f.js +++ /dev/null @@ -1 +0,0 @@ -rd_("Af[477,15,478,0,134,0,0]Ak[3190,15,3191,3191,135,0,0]Ag[477,15,478,0,4224,0,0]Al[3190,15,3191,1839,4225,0,0]Al[2043,15,2044,2044,4226,0,0]Al[3190,15,1742,1742,1478,0,0]Ao[1229,13,1230,1230,4067,3128,0]Am[477,13,478,3757,4915,3128,0]Am[477,13,478,4286,1462,3128,0]Al[477,13,478,1127,520,3128,0]Am[477,13,478,1127,4507,3128,0]Am[477,13,478,4173,4500,3128,0]Am[477,13,478,4173,3138,3128,0]Am[477,13,478,4173,3341,3128,0]Am[477,13,478,4173,4499,3128,0]Am[477,13,478,4178,3359,3128,0]Am[477,13,478,4178,4264,3128,0]Aj[477,13,478,0,1488,3128,0]Aj[477,13,478,0,5084,3128,0]Aj[477,13,478,0,1185,3128,0]Aj[477,13,478,0,1471,3128,0]Aj[477,13,478,0,3334,3128,0]Aj[477,13,478,0,3133,3128,0]Aj[477,13,478,0,4969,3128,0]Aj[477,13,478,0,3306,3128,0]Aj[477,13,478,0,2359,3128,0]Aj[477,13,478,0,3319,3128,0]Aj[477,13,478,0,3135,3128,0]Aj[477,13,478,0,1923,3128,0]Aj[477,13,478,0,4210,3128,0]Aj[477,13,478,0,4600,3128,0]Aj[477,13,478,0,3129,3128,0]Aj[477,13,478,0,4466,3128,0]Aj[477,13,478,0,4080,3128,0]Aj[477,13,478,0,4070,3128,0]Aj[477,13,478,0,3116,3128,0]Aj[477,13,478,0,4597,3128,0]Aj[477,13,478,0,4805,3128,0]Aj[477,13,478,0,4216,3128,0]Aj[477,13,478,0,4219,3128,0]Aj[477,13,478,0,4224,3128,0]Aj[477,13,478,0,4974,3128,0]Ai[477,13,478,0,526,3128,0]Ai[477,13,478,0,134,3128,0]Aj[477,13,478,0,2346,3128,0]Ak[477,13,1746,0,5240,3128,0]Ak[477,13,1828,0,1202,3128,0]Ao[3190,13,3191,1866,1489,3128,0]Ao[3190,13,3191,1866,5085,3128,0]Ao[3190,13,3191,3155,1186,3128,0]Ao[3190,13,3191,3164,4916,3128,0]Ao[3190,13,3191,1407,1472,3128,0]Ao[3190,13,3191,1407,3335,3128,0]Ao[3190,13,3191,1407,3134,3128,0]Ao[3190,13,3191,1407,4914,3128,0]Ao[3190,13,3191,1407,4970,3128,0]Ao[3190,13,3191,1407,3309,3128,0]Ao[3190,13,3191,1422,2360,3128,0]Ao[3190,13,3191,1422,3320,3128,0]Ao[3190,13,3191,1422,3136,3128,0]Ao[3190,13,3191,1153,3139,3128,0]Ao[3190,13,3191,1153,4390,3128,0]Ao[3190,13,3191,1158,1924,3128,0]Ao[3190,13,3191,1158,3360,3128,0]Ao[3190,13,3191,1158,4079,3128,0]Ao[3190,13,3191,2058,4098,3128,0]Ao[3190,13,3191,2058,4501,3128,0]Ao[3190,13,3191,2058,4214,3128,0]Ao[3190,13,3191,3758,4601,3128,0]Ao[3190,13,3191,3758,3130,3128,0]Ao[3190,13,3191,3758,4467,3128,0]Ao[3190,13,3191,3292,4081,3128,0]Ao[3190,13,3191,1744,4071,3128,0]Ao[3190,13,3191,1397,3117,3128,0]Ao[3190,13,3191,1397,4482,3128,0]Ao[3190,13,3191,1397,3326,3128,0]Ao[3190,13,3191,1397,3349,3128,0]Ao[3190,13,3191,1397,4221,3128,0]Ao[3190,13,3191,1397,4599,3128,0]Ao[3190,13,3191,1397,4807,3128,0]Ao[3190,13,3191,2370,4811,3128,0]Ao[3190,13,3191,2370,4217,3128,0]Ao[3190,13,3191,1809,2332,3128,0]Ao[3190,13,3191,1809,4911,3128,0]Ao[3190,13,3191,1839,4220,3128,0]Ao[3190,13,3191,1839,4225,3128,0]Al[3190,13,3191,0,4505,3128,0]Al[3190,13,3191,0,4975,3128,0]An[3190,13,3191,3191,527,3128,0]An[3190,13,3191,3191,135,3128,0]Ao[3190,13,3191,3191,2347,3128,0]Ao[3190,13,3191,3191,3115,3128,0]Ao[3190,13,1742,1742,4396,3128,0]Ao[3190,13,5126,5126,5022,3128,0]Ao[3190,13,5126,5126,1204,3128,0]Ao[3190,13,1747,1747,5241,3128,0]Al[3190,13,1829,0,1203,3128,0]Ao[2043,13,2044,2044,4226,3128,0]Ai[477,13,478,1127,520,0,0]Aj[477,13,478,1127,4507,0,0]Aj[477,13,478,4173,3339,0,0]Aj[477,13,478,4173,5023,0,0]Aj[477,13,478,4178,3345,0,0]Ah[477,14,3131,0,1464,0,0]Ah[477,14,3131,0,3090,0,0]Ah[477,14,3131,0,3109,0,0]Ah[477,14,3131,0,4249,0,0]Ah[477,14,3131,0,4240,0,0]Ai[3190,14,3132,0,1465,0,0]Ai[3190,14,3132,0,3091,0,0]Ai[3190,14,3132,0,3110,0,0]Ai[3190,14,3132,0,4250,0,0]Ai[3190,14,3132,0,4241,0,0]Ai[3190,14,4097,0,2342,0,0]Ai[3190,14,4097,0,2362,0,0]Ai[3190,14,4097,0,2326,0,0]Ai[3190,14,4097,0,1911,0,0]Ae[1229,7,1230,0,0,0,0]Ah[3190,14,4612,0,516,0,0]Ag[477,15,478,0,3116,0,0]Al[3190,15,3191,1397,3117,0,0]Ae[2423,5,2424,0,0,0,0]Af[477,6,478,3191,0,0,0]Ae[3190,6,3191,0,0,0,0]Al[3190,15,1742,1742,1478,0,0]Ag[477,19,478,0,1488,0,0]Ag[477,19,478,0,1471,0,0]Al[3190,19,3191,1866,1489,0,0]Al[3190,19,3191,1407,1472,0,0]Ae[1229,5,1230,0,0,0,0]Ae[1128,5,1736,0,0,0,0]Ae[1128,5,1834,0,0,0,0]Af[477,15,478,0,134,0,0]Ak[3190,15,3191,3191,135,0,0]Ai[477,13,478,0,2359,128,0]Ai[477,13,478,0,3319,128,0]Ai[477,13,478,0,4604,128,0]An[3190,13,3191,1422,2360,128,0]An[3190,13,3191,1422,3320,128,0]An[3190,13,3191,1422,4605,128,0]Ai[477,13,478,0,1488,145,0]Ai[477,13,478,0,1471,145,0]Ai[477,13,478,0,4080,145,0]Ai[477,13,478,0,4219,145,0]Ah[477,13,478,0,526,145,0]Ah[477,13,478,0,134,145,0]Ai[477,13,478,0,2346,145,0]An[3190,13,3191,1866,1489,145,0]An[3190,13,3191,3164,4916,145,0]An[3190,13,3191,1407,1472,145,0]An[3190,13,3191,1153,3139,145,0]An[3190,13,3191,1153,4390,145,0]An[3190,13,3191,1158,3360,145,0]An[3190,13,3191,3292,4081,145,0]An[3190,13,3191,2370,4811,145,0]An[3190,13,3191,1809,4911,145,0]An[3190,13,3191,1839,4220,145,0]Am[3190,13,3191,3191,527,145,0]Am[3190,13,3191,3191,135,145,0]An[3190,13,3191,3191,2347,145,0]Ag[477,13,478,0,2346,0,0]Al[3190,13,3191,3191,2347,0,0]Ai[3190,14,4097,0,1466,0,0]Ai[3190,14,4097,0,3099,0,0]Ag[477,14,478,0,2356,0,0]Al[3190,14,3191,3191,2357,0,0]Al[2043,14,2044,2044,2344,0,0]Al[2043,14,2044,2044,2358,0,0]An[1229,13,1230,1230,142,1192,0]Ao[1229,13,1230,1230,4091,1192,0]Ao[1229,13,1230,1230,4067,1192,0]Ao[1229,13,1421,1421,4502,1192,0]Ao[4105,13,4106,4106,5178,1192,0]Am[477,13,478,4286,5080,1192,0]Am[477,13,478,3757,3311,1192,0]Am[477,13,478,3757,3313,1192,0]Am[477,13,478,3757,1454,1192,0]Am[477,13,478,3757,5087,1192,0]D`[477,13,478,3757,4915,1192,0,\"impl-Debug-for-SendDatagramError\"]Db[477,13,478,3757,4915,1883,0,\"impl-Display-for-SendDatagramError\"]Am[477,13,478,2369,2324,1192,0]Am[477,13,478,2369,4363,1192,0]Cg[477,13,478,4286,1462,1883,0,\"impl-Display-for-Lagged\"]Ce[477,13,478,4286,1462,1192,0,\"impl-Debug-for-Lagged\"]Am[477,13,478,4286,3333,1192,0]Am[477,13,478,4286,5015,1192,0]Am[477,13,478,2401,2328,1192,0]Ci[477,13,478,2401,3342,1192,0,\"impl-Debug-for-RetryError\"]Ck[477,13,478,2401,3342,1883,0,\"impl-Display-for-RetryError\"]Am[477,13,478,2401,4481,1192,0]Al[477,13,478,1127,520,1192,0]Am[477,13,478,1127,4507,1192,0]Am[477,13,478,4173,3339,1192,0]Am[477,13,478,4173,5023,1192,0]Co[477,13,478,4173,4500,1883,0,\"impl-Display-for-ReadToEndError\"]Cm[477,13,478,4173,4500,1192,0,\"impl-Debug-for-ReadToEndError\"]Cj[477,13,478,4173,3138,1883,0,\"impl-Display-for-ReadError\"]Ch[477,13,478,4173,3138,1192,0,\"impl-Debug-for-ReadError\"]Ck[477,13,478,4173,3341,1883,0,\"impl-Display-for-ResetError\"]Ci[477,13,478,4173,3341,1192,0,\"impl-Debug-for-ResetError\"]Cm[477,13,478,4173,4499,1192,0,\"impl-Debug-for-ReadExactError\"]Co[477,13,478,4173,4499,1883,0,\"impl-Display-for-ReadExactError\"]Am[477,13,478,1435,4265,1192,0]Am[477,13,478,1170,4094,1192,0]Am[477,13,478,4178,3345,1192,0]Ck[477,13,478,4178,3359,1883,0,\"impl-Display-for-WriteError\"]Ci[477,13,478,4178,3359,1192,0,\"impl-Debug-for-WriteError\"]Cm[477,13,478,4178,4264,1883,0,\"impl-Display-for-StoppedError\"]Ck[477,13,478,4178,4264,1192,0,\"impl-Debug-for-StoppedError\"]Am[477,13,478,4178,1913,1192,0]Cb[477,13,478,0,1488,1192,0,\"impl-Debug-for-VarInt\"]Cd[477,13,478,0,1488,1883,0,\"impl-Display-for-VarInt\"]Db[477,13,478,0,5084,1883,0,\"impl-Display-for-VarIntBoundsExceeded\"]D`[477,13,478,0,5084,1192,0,\"impl-Debug-for-VarIntBoundsExceeded\"]Aj[477,13,478,0,1185,1192,0]Cd[477,13,478,0,1471,1883,0,\"impl-Display-for-PathId\"]Cb[477,13,478,0,1471,1192,0,\"impl-Debug-for-PathId\"]Aj[477,13,478,0,3334,1192,0]Aj[477,13,478,0,3133,1192,0]D`[477,13,478,0,4969,1883,0,\"impl-Display-for-SetPathStatusError\"]Cn[477,13,478,0,4969,1192,0,\"impl-Debug-for-SetPathStatusError\"]Cf[477,13,478,0,3306,1192,0,\"impl-Debug-for-ClosedPath\"]Ch[477,13,478,0,3306,1883,0,\"impl-Display-for-ClosedPath\"]Aj[477,13,478,0,2359,1192,0]Aj[477,13,478,0,3319,1192,0]Aj[477,13,478,0,3135,1192,0]Aj[477,13,478,0,4604,1192,0]Aj[477,13,478,0,1923,1192,0]Cj[477,13,478,0,4210,1883,0,\"impl-Display-for-ClosedStream\"]Ch[477,13,478,0,4210,1192,0,\"impl-Debug-for-ClosedStream\"]Cm[477,13,478,0,4600,1883,0,\"impl-Display-for-ConnectionError\"]Ck[477,13,478,0,4600,1192,0,\"impl-Debug-for-ConnectionError\"]Ce[477,13,478,0,3129,1192,0,\"impl-Debug-for-PathError\"]Cg[477,13,478,0,3129,1883,0,\"impl-Display-for-PathError\"]Cl[477,13,478,0,4466,1883,0,\"impl-Display-for-ClosePathError\"]Cj[477,13,478,0,4466,1192,0,\"impl-Debug-for-ClosePathError\"]Aj[477,13,478,0,4614,1192,0]Aj[477,13,478,0,4623,1192,0]Aj[477,13,478,0,4939,1192,0]Aj[477,13,478,0,4954,1192,0]Aj[477,13,478,0,4080,1192,0]Aj[477,13,478,0,4476,1192,0]Aj[477,13,478,0,4258,1192,0]Aj[477,13,478,0,5141,1192,0]Aj[477,13,478,0,4204,1192,0]Cg[477,13,478,0,4070,1192,0,\"impl-Debug-for-ConfigError\"]Ci[477,13,478,0,4070,1883,0,\"impl-Display-for-ConfigError\"]Ce[477,13,478,0,3116,1192,0,\"impl-Debug-for-FrameType\"]Cg[477,13,478,0,3116,1883,0,\"impl-Display-for-FrameType\"]Ck[477,13,478,0,4597,1192,0,\"impl-Debug-for-ConnectionClose\"]Cm[477,13,478,0,4597,1883,0,\"impl-Display-for-ConnectionClose\"]Cn[477,13,478,0,4805,1883,0,\"impl-Display-for-ApplicationClose\"]Cl[477,13,478,0,4805,1192,0,\"impl-Debug-for-ApplicationClose\"]Cj[477,13,478,0,4216,1883,0,\"impl-Display-for-ConnectError\"]Ch[477,13,478,0,4216,1192,0,\"impl-Debug-for-ConnectError\"]Cj[477,13,478,0,4219,1883,0,\"impl-Display-for-ConnectionId\"]Ch[477,13,478,0,4219,1192,0,\"impl-Debug-for-ConnectionId\"]Aj[477,13,478,0,4224,1192,0]D`[477,13,478,0,4974,1883,0,\"impl-Display-for-TransportErrorCode\"]Cn[477,13,478,0,4974,1192,0,\"impl-Debug-for-TransportErrorCode\"]Aj[477,13,478,0,3322,1192,0]Aj[477,13,478,0,4839,1192,0]Ai[477,13,478,0,526,1192,0]C`[477,13,478,0,134,1883,0,\"impl-Display-for-Dir\"]Bn[477,13,478,0,134,1192,0,\"impl-Debug-for-Dir\"]Cf[477,13,478,0,2346,1883,0,\"impl-Display-for-StreamId\"]Cd[477,13,478,0,2346,1192,0,\"impl-Debug-for-StreamId\"]Aj[477,13,478,0,2356,1192,0]Aj[477,13,3752,0,501,1192,0]Ak[477,13,3752,0,3302,1192,0]Ak[477,13,3752,0,1189,1192,0]Ak[477,13,3752,0,4075,1192,0]Ak[477,13,3752,0,1896,1192,0]Ak[477,13,3752,0,4382,1192,0]Ak[477,13,1746,0,5240,1192,0]Ak[477,13,1746,0,4073,1192,0]Ak[477,13,1746,0,4978,1192,0]Cb[477,13,1828,0,1202,1192,0,\"impl-Debug-for-Error\"]Cd[477,13,1828,0,1202,1883,0,\"impl-Display-for-Error\"]Da[477,13,1828,0,5078,1192,0,\"impl-Debug-for-NoInitialCipherSuite\"]Dc[477,13,1828,0,5078,1883,0,\"impl-Display-for-NoInitialCipherSuite\"]Ao[3190,13,3191,3758,3314,1192,0]Ao[3190,13,3191,2370,2325,1192,0]Cg[3190,13,3191,1866,1489,1192,0,\"impl-Debug-for-VarInt\"]Ci[3190,13,3191,1866,1489,1883,0,\"impl-Display-for-VarInt\"]De[3190,13,3191,1866,5085,1192,0,\"impl-Debug-for-VarIntBoundsExceeded\"]Dg[3190,13,3191,1866,5085,1883,0,\"impl-Display-for-VarIntBoundsExceeded\"]Ao[3190,13,3191,3155,1186,1192,0]Dd[3190,13,3191,3164,4916,1883,0,\"impl-Display-for-SendDatagramError\"]Db[3190,13,3191,3164,4916,1192,0,\"impl-Debug-for-SendDatagramError\"]Ci[3190,13,3191,1407,1472,1883,0,\"impl-Display-for-PathId\"]Cg[3190,13,3191,1407,1472,1192,0,\"impl-Debug-for-PathId\"]Ao[3190,13,3191,1407,4254,1192,0]Ao[3190,13,3191,1407,3335,1192,0]Ao[3190,13,3191,1407,3134,1192,0]Ao[3190,13,3191,1407,4914,1192,0]De[3190,13,3191,1407,4970,1883,0,\"impl-Display-for-SetPathStatusError\"]Dc[3190,13,3191,1407,4970,1192,0,\"impl-Debug-for-SetPathStatusError\"]Ck[3190,13,3191,1407,3309,1192,0,\"impl-Debug-for-ClosedPath\"]Cm[3190,13,3191,1407,3309,1883,0,\"impl-Display-for-ClosedPath\"]Ao[3190,13,3191,1422,2360,1192,0]Ao[3190,13,3191,1422,3320,1192,0]Ao[3190,13,3191,1422,3136,1192,0]Ao[3190,13,3191,1422,4605,1192,0]Cj[3190,13,3191,1153,3139,1192,0,\"impl-Debug-for-ReadError\"]Cl[3190,13,3191,1153,3139,1883,0,\"impl-Display-for-ReadError\"]D`[3190,13,3191,1153,4390,1883,0,\"impl-Display-for-ReadableError\"]Cn[3190,13,3191,1153,4390,1192,0,\"impl-Debug-for-ReadableError\"]Ao[3190,13,3191,1158,1924,1192,0]Cm[3190,13,3191,1158,3360,1883,0,\"impl-Display-for-WriteError\"]Ck[3190,13,3191,1158,3360,1192,0,\"impl-Debug-for-WriteError\"]Cl[3190,13,3191,1158,4079,1192,0,\"impl-Debug-for-FinishError\"]Cn[3190,13,3191,1158,4079,1883,0,\"impl-Display-for-FinishError\"]Ao[3190,13,3191,2058,4098,1192,0]Ao[3190,13,3191,2058,4501,1192,0]Co[3190,13,3191,2058,4214,1883,0,\"impl-Display-for-ClosedStream\"]Cm[3190,13,3191,2058,4214,1192,0,\"impl-Debug-for-ClosedStream\"]Db[3190,13,3191,3758,4601,1883,0,\"impl-Display-for-ConnectionError\"]D`[3190,13,3191,3758,4601,1192,0,\"impl-Debug-for-ConnectionError\"]Cj[3190,13,3191,3758,3130,1192,0,\"impl-Debug-for-PathError\"]Cl[3190,13,3191,3758,3130,1883,0,\"impl-Display-for-PathError\"]Da[3190,13,3191,3758,4467,1883,0,\"impl-Display-for-ClosePathError\"]Co[3190,13,3191,3758,4467,1192,0,\"impl-Debug-for-ClosePathError\"]Di[3190,13,3191,3758,5173,1883,0,\"impl-Display-for-MultipathNotNegotiated\"]Dg[3190,13,3191,3758,5173,1192,0,\"impl-Debug-for-MultipathNotNegotiated\"]Ao[3190,13,3191,3758,1207,1192,0]Ao[3190,13,3191,1149,4615,1192,0]Ao[3190,13,3191,3292,4624,1192,0]Ao[3190,13,3191,3292,4940,1192,0]Ao[3190,13,3191,3292,4955,1192,0]Ao[3190,13,3191,3292,4081,1192,0]Ao[3190,13,3191,1744,4477,1192,0]Ao[3190,13,3191,1744,4260,1192,0]Ao[3190,13,3191,1744,5142,1192,0]Ao[3190,13,3191,1744,4206,1192,0]Cn[3190,13,3191,1744,4071,1883,0,\"impl-Display-for-ConfigError\"]Cl[3190,13,3191,1744,4071,1192,0,\"impl-Debug-for-ConfigError\"]Cl[3190,13,3191,1397,3117,1883,0,\"impl-Display-for-FrameType\"]Cj[3190,13,3191,1397,3117,1192,0,\"impl-Debug-for-FrameType\"]Co[3190,13,3191,1397,4482,1192,0,\"impl-Debug-for-InvalidFrameId\"]Da[3190,13,3191,1397,4482,1883,0,\"impl-Display-for-InvalidFrameId\"]Ck[3190,13,3191,1397,3326,1192,0,\"impl-Debug-for-MaybeFrame\"]Cm[3190,13,3191,1397,3326,1883,0,\"impl-Display-for-MaybeFrame\"]Ck[3190,13,3191,1397,3349,1192,0,\"impl-Debug-for-StreamInfo\"]Cm[3190,13,3191,1397,3349,1883,0,\"impl-Display-for-StreamInfo\"]Cm[3190,13,3191,1397,4221,1192,0,\"impl-Debug-for-DatagramInfo\"]Co[3190,13,3191,1397,4221,1883,0,\"impl-Display-for-DatagramInfo\"]Db[3190,13,3191,1397,4599,1883,0,\"impl-Display-for-ConnectionClose\"]D`[3190,13,3191,1397,4599,1192,0,\"impl-Debug-for-ConnectionClose\"]Dc[3190,13,3191,1397,4807,1883,0,\"impl-Display-for-ApplicationClose\"]Da[3190,13,3191,1397,4807,1192,0,\"impl-Debug-for-ApplicationClose\"]Ci[3190,13,3191,1397,2318,1192,0,\"impl-Debug-for-Datagram\"]Ck[3190,13,3191,1397,2318,1883,0,\"impl-Display-for-Datagram\"]Ao[3190,13,3191,2370,4811,1192,0]Ao[3190,13,3191,2370,2329,1192,0]Cm[3190,13,3191,2370,4217,1192,0,\"impl-Debug-for-ConnectError\"]Co[3190,13,3191,2370,4217,1883,0,\"impl-Display-for-ConnectError\"]Ao[3190,13,3191,2370,4065,1192,0]Ck[3190,13,3191,2370,3343,1192,0,\"impl-Debug-for-RetryError\"]Cm[3190,13,3191,2370,3343,1883,0,\"impl-Display-for-RetryError\"]Ao[3190,13,3191,1809,4385,1192,0]Ao[3190,13,3191,1809,4613,1192,0]Ao[3190,13,3191,1809,5177,1192,0]Ao[3190,13,3191,1809,2332,1192,0]Db[3190,13,3191,1809,4911,1192,0,\"impl-Debug-for-PacketDecodeError\"]Dd[3190,13,3191,1809,4911,1883,0,\"impl-Display-for-PacketDecodeError\"]Ao[3190,13,3191,1839,4603,1192,0]Ao[3190,13,3191,1839,4362,1192,0]Cm[3190,13,3191,1839,4220,1192,0,\"impl-Debug-for-ConnectionId\"]Co[3190,13,3191,1839,4220,1883,0,\"impl-Display-for-ConnectionId\"]Ao[3190,13,3191,1839,4225,1192,0]Ce[3190,13,3191,0,4505,1883,0,\"impl-Display-for-Error\"]Cc[3190,13,3191,0,4505,1192,0,\"impl-Debug-for-Error\"]Cb[3190,13,3191,0,4975,1192,0,\"impl-Debug-for-Code\"]Cd[3190,13,3191,0,4975,1883,0,\"impl-Display-for-Code\"]Ao[3190,13,3191,4403,3323,1192,0]Ao[3190,13,3191,4403,5277,1192,0]Ao[3190,13,3191,5010,4840,1192,0]An[3190,13,3191,3191,527,1192,0]Ce[3190,13,3191,3191,135,1883,0,\"impl-Display-for-Dir\"]Cc[3190,13,3191,3191,135,1192,0,\"impl-Debug-for-Dir\"]Ci[3190,13,3191,3191,2347,1192,0,\"impl-Debug-for-StreamId\"]Ck[3190,13,3191,3191,2347,1883,0,\"impl-Display-for-StreamId\"]Ao[3190,13,3191,3191,2357,1192,0]Cl[3190,13,3191,3191,3115,1883,0,\"impl-Display-for-FourTuple\"]Cj[3190,13,3191,3191,3115,1192,0,\"impl-Debug-for-FourTuple\"]Cn[3190,13,1742,1742,4396,1192,0,\"impl-Debug-for-UnexpectedEnd\"]D`[3190,13,1742,1742,4396,1883,0,\"impl-Display-for-UnexpectedEnd\"]Ao[3190,13,5126,5126,5022,1192,0]Ch[3190,13,5126,5126,1204,1883,0,\"impl-Display-for-Error\"]Cf[3190,13,5126,5126,1204,1192,0,\"impl-Debug-for-Error\"]Ao[3190,13,1747,1747,5241,1192,0]Ao[3190,13,1747,1747,4074,1192,0]Ao[3190,13,1747,1747,4981,1192,0]Ce[3190,13,1829,0,1203,1883,0,\"impl-Display-for-Error\"]Cc[3190,13,1829,0,1203,1192,0,\"impl-Debug-for-Error\"]Dg[3190,13,1829,1829,5079,1883,0,\"impl-Display-for-NoInitialCipherSuite\"]De[3190,13,1829,1829,5079,1192,0,\"impl-Debug-for-NoInitialCipherSuite\"]Am[3190,13,3753,535,502,1192,0]An[3190,13,3753,535,3303,1192,0]Ao[3190,13,3753,1393,1190,1192,0]Ao[3190,13,3753,1393,4076,1192,0]Ao[3190,13,3753,2423,1897,1192,0]Ao[3190,13,3753,2423,4383,1192,0]Ch[3190,13,4871,4871,1205,1883,0,\"impl-Display-for-Error\"]Cf[3190,13,4871,4871,1205,1192,0,\"impl-Debug-for-Error\"]Ao[3190,13,4871,4871,1208,1192,0]An[2043,13,2044,432,4506,1192,0]Ao[2043,13,2044,2044,2344,1192,0]Ao[2043,13,2044,2044,2358,1192,0]Ao[2043,13,2044,2044,4226,1192,0]Aj[477,13,478,4286,5080,0,0]Al[3190,13,3191,1407,4254,0,0]Ag[477,14,478,0,2359,0,0]Al[3190,14,3191,1422,2360,0,0]Al[3190,13,3191,1809,4385,0,0]Al[3190,14,3191,1809,5177,0,0]Ah[3190,14,4612,0,516,0,0]Al[2043,14,2044,2044,2344,0,0]Ag[477,13,478,0,5141,0,0]Al[3190,13,3191,1744,5142,0,0]8Al[1229,13,1421,1421,4502,0,0]Al[4105,13,4106,4106,5178,0,0]Ag[477,13,478,0,4354,0,0]Aj[477,13,478,2369,2324,0,0]Ag[477,13,478,0,3336,0,0]Ag[477,13,478,0,4614,0,0]Ag[477,13,478,0,4476,0,0]Ag[477,13,478,0,4258,0,0]Ag[477,13,478,0,4204,0,0]Ag[477,13,478,0,4219,0,0]Ag[477,13,478,0,4839,0,0]Ag[477,13,478,0,2346,0,0]Ah[477,13,3752,0,1189,0,0]Ah[477,13,3752,0,1896,0,0]Al[3190,13,3191,4633,4355,0,0]Al[3190,13,3191,1149,3337,0,0]Al[3190,13,3191,2370,2325,0,0]Al[3190,13,3191,1809,5304,0,0]Al[3190,13,3191,4403,5272,0,0]Al[3190,13,3191,1149,4615,0,0]Al[3190,13,3191,1744,4477,0,0]Al[3190,13,3191,1744,4260,0,0]Al[3190,13,3191,1744,4206,0,0]Al[3190,13,3191,1809,4385,0,0]Al[3190,13,3191,1839,4220,0,0]Ai[3190,13,3191,0,4505,0,0]Al[3190,13,3191,4403,5277,0,0]Al[3190,13,3191,5010,4840,0,0]Al[3190,13,3191,3191,2347,0,0]Al[3190,13,3191,3191,3115,0,0]Al[3190,13,3753,1393,1190,0,0]Al[3190,13,3753,2423,1897,0,0]Ak[2043,13,2044,432,4506,0,0]Al[1128,13,4320,4320,5226,0,0]Al[1128,13,4320,4320,5227,0,0]Aa[477,3,0,0,0,0,0]Ah[477,13,478,0,526,141,0]Am[3190,13,3191,3191,527,141,0]Ag[477,12,478,0,3352,0,0]Aj[477,13,478,2050,1908,0,0]0Am[477,13,478,1435,4265,1908,0]Aj[477,13,478,0,4394,3352,0]Al[3190,12,3191,1744,3353,0,0]Ao[3190,13,3191,1744,4395,3353,0]Aj[477,13,478,3757,3313,0,0]Ag[477,14,478,0,3135,0,0]Al[3190,13,3191,3758,3314,0,0]Al[3190,14,3191,1422,3136,0,0]Ae[1128,7,1736,0,0,0,0]Ae[1128,7,1834,0,0,0,0]Ac[477,4,478,0,0,0,0]Af[477,5,3752,535,0,0,0]Ag[3190,5,3753,535,0,0,0]Al[1128,15,1129,1129,5014,0,0]Ag[477,15,478,0,4224,0,0]Al[3190,15,3191,1839,4225,0,0]Al[2043,15,2044,2044,4226,0,0]210Ag[477,5,1746,1747,0,0,0]Ae[3190,5,1747,0,0,0,0]Al[3190,15,3191,1809,4613,0,0]Al[3190,15,3191,1397,3326,0,0]Af[477,5,478,1127,0,0,0]Al[3190,15,3191,3758,1207,0,0]Ag[477,15,478,0,3116,0,0]Al[3190,15,3191,1397,3117,0,0]Aj[477,15,478,4173,4500,0,0]Af[477,6,478,3191,0,0,0]Ae[3190,6,3191,0,0,0,0]Ag[477,19,478,0,1471,0,0]Al[3190,19,3191,1407,1472,0,0]Ag[477,14,478,0,3319,0,0]Al[3190,14,3191,1422,3320,0,0]Ah[477,14,3131,0,4240,0,0]Ai[3190,14,3132,0,4241,0,0]Al[2043,14,2044,2044,2344,0,0]Aa[536,3,0,0,0,0,0]Ai[3190,14,3191,0,4505,0,0]Ag[477,14,478,0,3135,0,0]Al[3190,14,3191,1422,3136,0,0]Al[3190,14,3191,1397,2318,0,0]Al[477,13,478,3757,2339,504,0]Al[477,13,478,2401,2328,504,0]Ak[477,13,478,1127,520,504,0]Al[477,13,478,1127,4507,504,0]Al[477,13,478,4173,3339,504,0]Al[477,13,478,4178,3345,504,0]An[3190,13,3191,1153,1450,504,0]Am[1229,13,1230,1230,142,511,0]An[1229,13,1230,1230,4091,511,0]An[1229,13,1230,1230,4067,511,0]An[1229,13,1421,1421,4099,511,0]An[1229,13,1421,1421,1222,511,0]An[1229,13,1421,1421,4502,511,0]Ak[536,13,537,537,4068,511,0]An[4105,13,4106,4106,5178,511,0]Al[477,13,478,3757,4628,511,0]Al[477,13,478,3757,2339,511,0]Ai[477,13,478,0,4354,511,0]Al[477,13,478,4286,5080,511,0]Al[477,13,478,1127,2340,511,0]Al[477,13,478,1127,4803,511,0]Al[477,13,478,3757,3311,511,0]Al[477,13,478,3757,3313,511,0]Al[477,13,478,3757,1899,511,0]Al[477,13,478,3757,1463,511,0]Al[477,13,478,3757,3094,511,0]Al[477,13,478,3757,2311,511,0]Al[477,13,478,3757,4248,511,0]Al[477,13,478,3757,4255,511,0]Al[477,13,478,3757,1454,511,0]Al[477,13,478,3757,5087,511,0]Al[477,13,478,3757,4915,511,0]0Al[477,13,478,2369,2324,511,0]Al[477,13,478,2369,4363,511,0]Al[477,13,478,2369,1441,511,0]Al[477,13,478,4286,1462,511,0]Al[477,13,478,4286,3333,511,0]Al[477,13,478,4286,5015,511,0]Al[477,13,478,2401,2328,511,0]Al[477,13,478,2401,3342,511,0]Al[477,13,478,2401,4481,511,0]Ak[477,13,478,1127,520,511,0]Al[477,13,478,1127,4507,511,0]Al[477,13,478,4173,3339,511,0]Al[477,13,478,4173,5023,511,0]Al[477,13,478,4173,4500,511,0]0Df[477,13,478,4173,3138,511,0,\"impl-From%3CResetError%3E-for-ReadError\"]Dk[477,13,478,4173,3138,511,0,\"impl-From%3CConnectionError%3E-for-ReadError\"]Al[477,13,478,4173,3138,511,0]Al[477,13,478,4173,3341,511,0]0Al[477,13,478,4173,4499,511,0]0Al[477,13,478,1435,4265,511,0]Al[477,13,478,1170,4094,511,0]Al[477,13,478,4178,3345,511,0]Di[477,13,478,4178,3359,511,0,\"impl-From%3CClosedStream%3E-for-WriteError\"]Di[477,13,478,4178,3359,511,0,\"impl-From%3CStoppedError%3E-for-WriteError\"]Dl[477,13,478,4178,3359,511,0,\"impl-From%3CConnectionError%3E-for-WriteError\"]Al[477,13,478,4178,3359,511,0]Al[477,13,478,4178,4264,511,0]0Al[477,13,478,4178,1913,511,0]Ai[477,13,478,0,3336,511,0]Ai[477,13,478,0,4394,511,0]Ai[477,13,478,0,4813,511,0]Ai[477,13,478,0,4620,511,0]Ai[477,13,478,0,4238,511,0]Ai[477,13,478,0,4490,511,0]Cn[477,13,478,0,1488,511,0,\"impl-From%3CStreamId%3E-for-VarInt\"]Ch[477,13,478,0,1488,511,0,\"impl-From%3Cu8%3E-for-VarInt\"]Ai[477,13,478,0,1488,511,0]Ci[477,13,478,0,1488,511,0,\"impl-From%3Cu32%3E-for-VarInt\"]Ci[477,13,478,0,1488,511,0,\"impl-From%3Cu16%3E-for-VarInt\"]Dh[477,13,478,0,1488,511,0,\"impl-From%3CTransportErrorCode%3E-for-VarInt\"]Ai[477,13,478,0,5084,511,0]Ai[477,13,478,0,1185,511,0]Ai[477,13,478,0,1471,511,0]0Ai[477,13,478,0,3334,511,0]Ai[477,13,478,0,3133,511,0]Ai[477,13,478,0,4969,511,0]Ai[477,13,478,0,3306,511,0]Ai[477,13,478,0,2359,511,0]Ai[477,13,478,0,3319,511,0]Ai[477,13,478,0,3135,511,0]Ai[477,13,478,0,4604,511,0]Ai[477,13,478,0,1923,511,0]Ai[477,13,478,0,4210,511,0]Ai[477,13,478,0,4600,511,0]Dm[477,13,478,0,4600,511,0,\"impl-From%3CTransportError%3E-for-ConnectionError\"]Dd[477,13,478,0,4600,511,0,\"impl-From%3CClose%3E-for-ConnectionError\"]Dj[477,13,478,0,4600,511,0,\"impl-From%3CCloseReason%3E-for-ConnectionError\"]Ai[477,13,478,0,3129,511,0]Ai[477,13,478,0,4466,511,0]Ai[477,13,478,0,4614,511,0]Ai[477,13,478,0,4623,511,0]Ai[477,13,478,0,4939,511,0]Ai[477,13,478,0,4954,511,0]Ai[477,13,478,0,4080,511,0]0Ai[477,13,478,0,4476,511,0]Ai[477,13,478,0,4258,511,0]Ai[477,13,478,0,5141,511,0]Ai[477,13,478,0,4204,511,0]Ai[477,13,478,0,4070,511,0]Dj[477,13,478,0,4070,511,0,\"impl-From%3CTryFromIntError%3E-for-ConfigError\"]Do[477,13,478,0,4070,511,0,\"impl-From%3CVarIntBoundsExceeded%3E-for-ConfigError\"]Ai[477,13,478,0,3116,511,0]Ai[477,13,478,0,4597,511,0]0Ai[477,13,478,0,4805,511,0]Ai[477,13,478,0,4216,511,0]0Ai[477,13,478,0,4219,511,0]Ai[477,13,478,0,4224,511,0]Ai[477,13,478,0,4974,511,0]0Ai[477,13,478,0,3322,511,0]Ai[477,13,478,0,4839,511,0]Ah[477,13,478,0,526,511,0]Ah[477,13,478,0,134,511,0]Ai[477,13,478,0,2346,511,0]0Ai[477,13,478,0,2356,511,0]Ai[477,13,3752,0,501,511,0]Aj[477,13,3752,0,3302,511,0]Aj[477,13,3752,0,1189,511,0]Aj[477,13,3752,0,4075,511,0]Aj[477,13,3752,0,1896,511,0]Aj[477,13,3752,0,4382,511,0]Aj[477,13,3752,0,4902,511,0]Aj[477,13,1746,0,1891,511,0]Ai[477,13,1746,0,514,511,0]Aj[477,13,1746,0,5240,511,0]Aj[477,13,1746,0,4073,511,0]0Aj[477,13,1746,0,4978,511,0]Aj[477,13,1828,0,3354,511,0]Aj[477,13,1828,0,4365,511,0]Dm[477,13,1828,0,1202,511,0,\"impl-From%3CCertRevocationListError%3E-for-Error\"]D`[477,13,1828,0,1202,511,0,\"impl-From%3COtherError%3E-for-Error\"]De[477,13,1828,0,1202,511,0,\"impl-From%3CGetRandomFailed%3E-for-Error\"]Aj[477,13,1828,0,1202,511,0]De[477,13,1828,0,1202,511,0,\"impl-From%3CSystemTimeError%3E-for-Error\"]Do[477,13,1828,0,1202,511,0,\"impl-From%3CEncryptedClientHelloError%3E-for-Error\"]Df[477,13,1828,0,1202,511,0,\"impl-From%3CCertificateError%3E-for-Error\"]Df[477,13,1828,0,1202,511,0,\"impl-From%3CPeerIncompatible%3E-for-Error\"]Dd[477,13,1828,0,1202,511,0,\"impl-From%3CPeerMisbehaved%3E-for-Error\"]Do[477,13,1828,0,1202,511,0,\"impl-From%3CUnsupportedOperationError%3E-for-Error\"]Df[477,13,1828,0,1202,511,0,\"impl-From%3CInconsistentKeys%3E-for-Error\"]Dd[477,13,1828,0,1202,511,0,\"impl-From%3CInvalidMessage%3E-for-Error\"]Aj[477,13,1828,0,4835,511,0]Aj[477,13,1828,0,5078,511,0]Aj[477,13,1828,0,4837,511,0]Am[2423,13,2424,2424,133,511,0]An[2423,13,2424,2424,2315,511,0]An[3190,13,3191,4633,4355,511,0]An[3190,13,3191,3164,3107,511,0]An[3190,13,3191,1153,1450,511,0]An[3190,13,3191,2058,1917,511,0]An[3190,13,3191,2058,3340,511,0]An[3190,13,3191,2058,3346,511,0]An[3190,13,3191,3758,3314,511,0]An[3190,13,3191,1149,3337,511,0]An[3190,13,3191,1744,4395,511,0]An[3190,13,3191,2370,2325,511,0]An[3190,13,3191,2370,4361,511,0]An[3190,13,3191,2370,4814,511,0]An[3190,13,3191,2370,4373,511,0]An[3190,13,3191,1809,5304,511,0]An[3190,13,3191,4403,5272,511,0]An[3190,13,3191,1434,4621,511,0]An[3190,13,3191,1434,4239,511,0]An[3190,13,3191,1434,4491,511,0]An[3190,13,3191,1866,1489,511,0]Cn[3190,13,3191,1866,1489,511,0,\"impl-From%3Cu32%3E-for-VarInt\"]Cm[3190,13,3191,1866,1489,511,0,\"impl-From%3Cu8%3E-for-VarInt\"]Cn[3190,13,3191,1866,1489,511,0,\"impl-From%3Cu16%3E-for-VarInt\"]Co[3190,13,3191,1866,1489,511,0,\"impl-From%3CCode%3E-for-VarInt\"]Dc[3190,13,3191,1866,1489,511,0,\"impl-From%3CStreamId%3E-for-VarInt\"]An[3190,13,3191,1866,5085,511,0]An[3190,13,3191,3155,1186,511,0]An[3190,13,3191,3164,4916,511,0]An[3190,13,3191,1407,1472,511,0]0An[3190,13,3191,1407,4254,511,0]An[3190,13,3191,1407,3335,511,0]An[3190,13,3191,1407,3134,511,0]An[3190,13,3191,1407,4914,511,0]An[3190,13,3191,1407,4970,511,0]An[3190,13,3191,1407,3309,511,0]An[3190,13,3191,1422,2360,511,0]An[3190,13,3191,1422,3320,511,0]An[3190,13,3191,1422,3136,511,0]An[3190,13,3191,1422,4605,511,0]An[3190,13,3191,1153,3139,511,0]An[3190,13,3191,1153,4390,511,0]An[3190,13,3191,1158,1924,511,0]An[3190,13,3191,1158,3360,511,0]An[3190,13,3191,1158,4079,511,0]An[3190,13,3191,2058,4098,511,0]An[3190,13,3191,2058,4501,511,0]An[3190,13,3191,2058,4214,511,0]An[3190,13,3191,3758,4601,511,0]0An[3190,13,3191,3758,3130,511,0]An[3190,13,3191,3758,4467,511,0]An[3190,13,3191,3758,5173,511,0]An[3190,13,3191,3758,1207,511,0]0An[3190,13,3191,1149,4615,511,0]An[3190,13,3191,3292,4624,511,0]An[3190,13,3191,3292,4940,511,0]An[3190,13,3191,3292,4955,511,0]An[3190,13,3191,3292,4081,511,0]0An[3190,13,3191,1744,4477,511,0]An[3190,13,3191,1744,4260,511,0]An[3190,13,3191,1744,5142,511,0]An[3190,13,3191,1744,4206,511,0]Do[3190,13,3191,1744,4071,511,0,\"impl-From%3CTryFromIntError%3E-for-ConfigError\"]Ed[3190,13,3191,1744,4071,511,0,\"impl-From%3CVarIntBoundsExceeded%3E-for-ConfigError\"]An[3190,13,3191,1744,4071,511,0]An[3190,13,3191,1397,3117,511,0]An[3190,13,3191,1397,4482,511,0]An[3190,13,3191,1397,3326,511,0]An[3190,13,3191,1397,3349,511,0]An[3190,13,3191,1397,4221,511,0]An[3190,13,3191,1397,4599,511,0]0An[3190,13,3191,1397,4807,511,0]An[3190,13,3191,1397,2318,511,0]An[3190,13,3191,2370,4811,511,0]An[3190,13,3191,2370,2329,511,0]An[3190,13,3191,2370,4217,511,0]0An[3190,13,3191,2370,4065,511,0]An[3190,13,3191,2370,3343,511,0]An[3190,13,3191,1809,4385,511,0]An[3190,13,3191,1809,4613,511,0]An[3190,13,3191,1809,5177,511,0]An[3190,13,3191,1809,2332,511,0]An[3190,13,3191,1809,4911,511,0]0An[3190,13,3191,1839,4603,511,0]An[3190,13,3191,1839,4362,511,0]An[3190,13,3191,1839,4220,511,0]An[3190,13,3191,1839,4225,511,0]Ak[3190,13,3191,0,4505,511,0]0Ak[3190,13,3191,0,4975,511,0]0An[3190,13,3191,4403,3323,511,0]An[3190,13,3191,4403,5277,511,0]An[3190,13,3191,5010,4840,511,0]Am[3190,13,3191,3191,527,511,0]Am[3190,13,3191,3191,135,511,0]An[3190,13,3191,3191,2347,511,0]0An[3190,13,3191,3191,2357,511,0]An[3190,13,3191,3191,3115,511,0]An[3190,13,1742,1742,4396,511,0]An[3190,13,5126,5126,5022,511,0]An[3190,13,5126,5126,1204,511,0]0An[3190,13,1747,1747,1892,511,0]Am[3190,13,1747,1747,515,511,0]An[3190,13,1747,1747,5241,511,0]An[3190,13,1747,1747,4074,511,0]0An[3190,13,1747,1747,4981,511,0]An[3190,13,1829,1829,3355,511,0]An[3190,13,1829,1829,4366,511,0]E`[3190,13,1829,0,1203,511,0,\"impl-From%3CUnsupportedOperationError%3E-for-Error\"]Dg[3190,13,1829,0,1203,511,0,\"impl-From%3CPeerIncompatible%3E-for-Error\"]Da[3190,13,1829,0,1203,511,0,\"impl-From%3COtherError%3E-for-Error\"]Df[3190,13,1829,0,1203,511,0,\"impl-From%3CGetRandomFailed%3E-for-Error\"]Ak[3190,13,1829,0,1203,511,0]Df[3190,13,1829,0,1203,511,0,\"impl-From%3CSystemTimeError%3E-for-Error\"]E`[3190,13,1829,0,1203,511,0,\"impl-From%3CEncryptedClientHelloError%3E-for-Error\"]Dn[3190,13,1829,0,1203,511,0,\"impl-From%3CCertRevocationListError%3E-for-Error\"]Dg[3190,13,1829,0,1203,511,0,\"impl-From%3CInconsistentKeys%3E-for-Error\"]De[3190,13,1829,0,1203,511,0,\"impl-From%3CInvalidMessage%3E-for-Error\"]De[3190,13,1829,0,1203,511,0,\"impl-From%3CPeerMisbehaved%3E-for-Error\"]Dg[3190,13,1829,0,1203,511,0,\"impl-From%3CCertificateError%3E-for-Error\"]An[3190,13,1829,1829,4836,511,0]An[3190,13,1829,1829,5079,511,0]An[3190,13,1829,1829,4838,511,0]Al[3190,13,3753,535,502,511,0]Am[3190,13,3753,535,3303,511,0]An[3190,13,3753,1393,1190,511,0]An[3190,13,3753,1393,4076,511,0]An[3190,13,3753,2423,1897,511,0]An[3190,13,3753,2423,4383,511,0]An[3190,13,3753,3753,4903,511,0]An[3190,13,4871,4871,1205,511,0]An[3190,13,4871,4871,1208,511,0]An[2043,13,2044,2044,3358,511,0]0Am[2043,13,2044,432,4506,511,0]An[2043,13,2044,2044,2344,511,0]An[2043,13,2044,2044,2358,511,0]An[2043,13,2044,2044,4226,511,0]An[1128,13,1129,1129,3104,511,0]An[1128,13,1129,1129,5014,511,0]Am[1128,13,1736,1736,143,511,0]An[1128,13,1423,1423,1223,511,0]An[1128,13,1423,1423,4100,511,0]An[1128,13,1423,1423,4609,511,0]An[1128,13,4320,4320,5226,511,0]An[1128,13,4320,4320,5227,511,0]Am[1128,13,1834,1834,144,511,0]Ai[477,13,478,0,1488,512,0]Ai[477,13,478,0,1471,512,0]Ai[477,13,478,0,4080,512,0]Ai[477,13,478,0,4219,512,0]Ah[477,13,478,0,526,512,0]Ah[477,13,478,0,134,512,0]Ai[477,13,478,0,2346,512,0]An[3190,13,3191,1866,1489,512,0]An[3190,13,3191,3164,4916,512,0]An[3190,13,3191,1407,1472,512,0]An[3190,13,3191,1153,3139,512,0]An[3190,13,3191,1153,4390,512,0]An[3190,13,3191,1158,3360,512,0]An[3190,13,3191,3292,4081,512,0]An[3190,13,3191,2370,4811,512,0]An[3190,13,3191,1809,4911,512,0]An[3190,13,3191,1839,4220,512,0]Am[3190,13,3191,3191,527,512,0]Am[3190,13,3191,3191,135,512,0]An[3190,13,3191,3191,2347,512,0]An[3190,13,3191,3191,3115,512,0]Am[1229,13,1230,1230,142,513,0]An[1229,13,1230,1230,4091,513,0]An[1229,13,1230,1230,4067,513,0]An[1229,13,1421,1421,4099,513,0]An[1229,13,1421,1421,1222,513,0]An[1229,13,1421,1421,4502,513,0]Ak[536,13,537,537,4068,513,0]An[4105,13,4106,4106,5178,513,0]Al[477,13,478,3757,4628,513,0]Al[477,13,478,3757,2339,513,0]Ai[477,13,478,0,4354,513,0]Al[477,13,478,4286,5080,513,0]Al[477,13,478,1127,2340,513,0]Al[477,13,478,1127,4803,513,0]Al[477,13,478,3757,3311,513,0]Al[477,13,478,3757,3313,513,0]Al[477,13,478,3757,1899,513,0]Al[477,13,478,3757,1463,513,0]Al[477,13,478,3757,3094,513,0]Al[477,13,478,3757,2311,513,0]Al[477,13,478,3757,4248,513,0]Al[477,13,478,3757,4255,513,0]Al[477,13,478,3757,1454,513,0]Al[477,13,478,3757,5087,513,0]Al[477,13,478,3757,4915,513,0]Al[477,13,478,2369,2324,513,0]Al[477,13,478,2369,4363,513,0]Al[477,13,478,2369,1441,513,0]Al[477,13,478,4286,1462,513,0]Al[477,13,478,4286,3333,513,0]Al[477,13,478,4286,5015,513,0]Al[477,13,478,2401,2328,513,0]Al[477,13,478,2401,3342,513,0]Al[477,13,478,2401,4481,513,0]Ak[477,13,478,1127,520,513,0]Al[477,13,478,1127,4507,513,0]Al[477,13,478,4173,3339,513,0]Al[477,13,478,4173,5023,513,0]Al[477,13,478,4173,4500,513,0]Al[477,13,478,4173,3138,513,0]Al[477,13,478,4173,3341,513,0]Al[477,13,478,4173,4499,513,0]Al[477,13,478,1435,4265,513,0]Al[477,13,478,1170,4094,513,0]Al[477,13,478,4178,3345,513,0]Al[477,13,478,4178,3359,513,0]Al[477,13,478,4178,4264,513,0]Al[477,13,478,4178,1913,513,0]Ai[477,13,478,0,3336,513,0]Ai[477,13,478,0,4394,513,0]Ai[477,13,478,0,4813,513,0]Ai[477,13,478,0,4620,513,0]Ai[477,13,478,0,4238,513,0]Ai[477,13,478,0,4490,513,0]Ai[477,13,478,0,1488,513,0]Ai[477,13,478,0,5084,513,0]Ai[477,13,478,0,1185,513,0]Ai[477,13,478,0,1471,513,0]Ai[477,13,478,0,3334,513,0]Ai[477,13,478,0,3133,513,0]Ai[477,13,478,0,4969,513,0]Ai[477,13,478,0,3306,513,0]Ai[477,13,478,0,2359,513,0]Ai[477,13,478,0,3319,513,0]Ai[477,13,478,0,3135,513,0]Ai[477,13,478,0,4604,513,0]Ai[477,13,478,0,1923,513,0]Ai[477,13,478,0,4210,513,0]Ai[477,13,478,0,4600,513,0]Ai[477,13,478,0,3129,513,0]Ai[477,13,478,0,4466,513,0]Ai[477,13,478,0,4614,513,0]Ai[477,13,478,0,4623,513,0]Ai[477,13,478,0,4939,513,0]Ai[477,13,478,0,4954,513,0]Ai[477,13,478,0,4080,513,0]Ai[477,13,478,0,4476,513,0]Ai[477,13,478,0,4258,513,0]Ai[477,13,478,0,5141,513,0]Ai[477,13,478,0,4204,513,0]Ai[477,13,478,0,4070,513,0]Ai[477,13,478,0,3116,513,0]Ai[477,13,478,0,4597,513,0]Ai[477,13,478,0,4805,513,0]Ai[477,13,478,0,4216,513,0]Ai[477,13,478,0,4219,513,0]Ai[477,13,478,0,4224,513,0]Ai[477,13,478,0,4974,513,0]Ai[477,13,478,0,3322,513,0]Ai[477,13,478,0,4839,513,0]Ah[477,13,478,0,526,513,0]Ah[477,13,478,0,134,513,0]Ai[477,13,478,0,2346,513,0]Ai[477,13,478,0,2356,513,0]Ai[477,13,3752,0,501,513,0]Aj[477,13,3752,0,3302,513,0]Aj[477,13,3752,0,1189,513,0]Aj[477,13,3752,0,4075,513,0]Aj[477,13,3752,0,1896,513,0]Aj[477,13,3752,0,4382,513,0]Aj[477,13,3752,0,4902,513,0]Aj[477,13,1746,0,1891,513,0]Ai[477,13,1746,0,514,513,0]Aj[477,13,1746,0,5240,513,0]Aj[477,13,1746,0,4073,513,0]Aj[477,13,1746,0,4978,513,0]Aj[477,13,1828,0,3354,513,0]Aj[477,13,1828,0,4365,513,0]Aj[477,13,1828,0,1202,513,0]Aj[477,13,1828,0,4835,513,0]Aj[477,13,1828,0,5078,513,0]Aj[477,13,1828,0,4837,513,0]Am[2423,13,2424,2424,133,513,0]An[2423,13,2424,2424,2315,513,0]An[3190,13,3191,4633,4355,513,0]An[3190,13,3191,3164,3107,513,0]An[3190,13,3191,1153,1450,513,0]An[3190,13,3191,2058,1917,513,0]An[3190,13,3191,2058,3340,513,0]An[3190,13,3191,2058,3346,513,0]An[3190,13,3191,3758,3314,513,0]An[3190,13,3191,1149,3337,513,0]An[3190,13,3191,1744,4395,513,0]An[3190,13,3191,2370,2325,513,0]An[3190,13,3191,2370,4361,513,0]An[3190,13,3191,2370,4814,513,0]An[3190,13,3191,2370,4373,513,0]An[3190,13,3191,1809,5304,513,0]An[3190,13,3191,4403,5272,513,0]An[3190,13,3191,1434,4621,513,0]An[3190,13,3191,1434,4239,513,0]An[3190,13,3191,1434,4491,513,0]An[3190,13,3191,1866,1489,513,0]An[3190,13,3191,1866,5085,513,0]An[3190,13,3191,3155,1186,513,0]An[3190,13,3191,3164,4916,513,0]An[3190,13,3191,1407,1472,513,0]An[3190,13,3191,1407,4254,513,0]An[3190,13,3191,1407,3335,513,0]An[3190,13,3191,1407,3134,513,0]An[3190,13,3191,1407,4914,513,0]An[3190,13,3191,1407,4970,513,0]An[3190,13,3191,1407,3309,513,0]An[3190,13,3191,1422,2360,513,0]An[3190,13,3191,1422,3320,513,0]An[3190,13,3191,1422,3136,513,0]An[3190,13,3191,1422,4605,513,0]An[3190,13,3191,1153,3139,513,0]An[3190,13,3191,1153,4390,513,0]An[3190,13,3191,1158,1924,513,0]An[3190,13,3191,1158,3360,513,0]An[3190,13,3191,1158,4079,513,0]An[3190,13,3191,2058,4098,513,0]An[3190,13,3191,2058,4501,513,0]An[3190,13,3191,2058,4214,513,0]An[3190,13,3191,3758,4601,513,0]An[3190,13,3191,3758,3130,513,0]An[3190,13,3191,3758,4467,513,0]An[3190,13,3191,3758,5173,513,0]An[3190,13,3191,3758,1207,513,0]An[3190,13,3191,1149,4615,513,0]An[3190,13,3191,3292,4624,513,0]An[3190,13,3191,3292,4940,513,0]An[3190,13,3191,3292,4955,513,0]An[3190,13,3191,3292,4081,513,0]An[3190,13,3191,1744,4477,513,0]An[3190,13,3191,1744,4260,513,0]An[3190,13,3191,1744,5142,513,0]An[3190,13,3191,1744,4206,513,0]An[3190,13,3191,1744,4071,513,0]An[3190,13,3191,1397,3117,513,0]An[3190,13,3191,1397,4482,513,0]An[3190,13,3191,1397,3326,513,0]An[3190,13,3191,1397,3349,513,0]An[3190,13,3191,1397,4221,513,0]An[3190,13,3191,1397,4599,513,0]An[3190,13,3191,1397,4807,513,0]An[3190,13,3191,1397,2318,513,0]An[3190,13,3191,2370,4811,513,0]An[3190,13,3191,2370,2329,513,0]An[3190,13,3191,2370,4217,513,0]An[3190,13,3191,2370,4065,513,0]An[3190,13,3191,2370,3343,513,0]An[3190,13,3191,1809,4385,513,0]An[3190,13,3191,1809,4613,513,0]An[3190,13,3191,1809,5177,513,0]An[3190,13,3191,1809,2332,513,0]An[3190,13,3191,1809,4911,513,0]An[3190,13,3191,1839,4603,513,0]An[3190,13,3191,1839,4362,513,0]An[3190,13,3191,1839,4220,513,0]An[3190,13,3191,1839,4225,513,0]Ak[3190,13,3191,0,4505,513,0]Ak[3190,13,3191,0,4975,513,0]An[3190,13,3191,4403,3323,513,0]An[3190,13,3191,4403,5277,513,0]An[3190,13,3191,5010,4840,513,0]Am[3190,13,3191,3191,527,513,0]Am[3190,13,3191,3191,135,513,0]An[3190,13,3191,3191,2347,513,0]An[3190,13,3191,3191,2357,513,0]An[3190,13,3191,3191,3115,513,0]An[3190,13,1742,1742,4396,513,0]An[3190,13,5126,5126,5022,513,0]An[3190,13,5126,5126,1204,513,0]An[3190,13,1747,1747,1892,513,0]Am[3190,13,1747,1747,515,513,0]An[3190,13,1747,1747,5241,513,0]An[3190,13,1747,1747,4074,513,0]An[3190,13,1747,1747,4981,513,0]An[3190,13,1829,1829,3355,513,0]An[3190,13,1829,1829,4366,513,0]Ak[3190,13,1829,0,1203,513,0]An[3190,13,1829,1829,4836,513,0]An[3190,13,1829,1829,5079,513,0]An[3190,13,1829,1829,4838,513,0]Al[3190,13,3753,535,502,513,0]Am[3190,13,3753,535,3303,513,0]An[3190,13,3753,1393,1190,513,0]An[3190,13,3753,1393,4076,513,0]An[3190,13,3753,2423,1897,513,0]An[3190,13,3753,2423,4383,513,0]An[3190,13,3753,3753,4903,513,0]An[3190,13,4871,4871,1205,513,0]An[3190,13,4871,4871,1208,513,0]An[2043,13,2044,2044,3358,513,0]Am[2043,13,2044,432,4506,513,0]An[2043,13,2044,2044,2344,513,0]An[2043,13,2044,2044,2358,513,0]An[2043,13,2044,2044,4226,513,0]An[1128,13,1129,1129,3104,513,0]An[1128,13,1129,1129,5014,513,0]Am[1128,13,1736,1736,143,513,0]An[1128,13,1423,1423,1223,513,0]An[1128,13,1423,1423,4100,513,0]An[1128,13,1423,1423,4609,513,0]An[1128,13,4320,4320,5226,513,0]An[1128,13,4320,4320,5227,513,0]Am[1128,13,1834,1834,144,513,0]Ac[536,7,537,0,0,0,0]Ae[4105,7,4106,0,0,0,0]Ae[4406,7,4407,0,0,0,0]Ae[2423,7,2424,0,0,0,0]Ae[4934,7,4935,0,0,0,0]Al[3190,13,3191,1153,1450,0,0]Ao[3190,13,3191,2370,4373,2330,0]Ah[477,12,1746,0,4904,0,0]Al[3190,13,3191,2058,1917,0,0]Al[3190,12,1747,1747,4905,0,0]Aj[477,13,478,3757,3313,0,0]Ab[1128,3,0,0,0,0,0]Ai[477,13,478,1127,520,0,0]Ag[477,14,478,0,3319,0,0]Al[3190,13,3191,3758,3314,0,0]Al[3190,14,3191,1422,3320,0,0]Aj[477,12,478,2050,3298,0,0]Am[477,13,478,3757,4628,1459,0]Am[477,13,478,3757,2339,1459,0]Am[477,13,478,1127,2340,1459,0]Am[477,13,478,3757,3311,1459,0]Am[477,13,478,3757,1899,1459,0]Am[477,13,478,3757,1463,1459,0]Am[477,13,478,3757,3094,1459,0]Am[477,13,478,3757,2311,1459,0]Am[477,13,478,3757,4248,1459,0]Am[477,13,478,3757,4255,1459,0]Am[477,13,478,2369,1441,1459,0]Am[477,13,478,2401,4481,1459,0]Am[477,13,478,4178,1913,1459,0]?Aj[477,13,478,4173,3339,0,0]Al[3190,13,3191,2058,3340,0,0]Al[3190,13,5126,5126,5022,0,0]Al[3190,13,3191,3164,3107,0,0]Ak[2043,13,2044,432,4506,0,0]Ah[477,12,1746,0,4904,0,0]Al[3190,12,1747,1747,4905,0,0]32Ag[477,13,478,0,5141,0,0]Al[3190,13,3191,1744,5142,0,0]Aj[477,13,478,3757,3313,0,0]Al[3190,13,3191,3758,3314,0,0]Ah[477,12,1746,0,1887,0,0]Al[3190,12,1747,1747,1888,0,0]Al[1229,14,1421,1421,4502,0,0]Ag[477,14,478,0,2356,0,0]Al[3190,14,3191,3191,2357,0,0]Ai[3190,14,4612,0,1219,0,0]Aj[477,13,478,4173,3339,0,0]Aj[477,13,478,4173,5023,0,0]Al[3190,13,3191,2058,3340,0,0]Ag[477,12,478,0,3356,0,0]Aj[477,13,478,0,4490,3356,0]Aj[477,13,478,0,4839,3356,0]Al[3190,12,3191,1434,3357,0,0]Ao[3190,13,3191,1434,4491,3357,0]Ao[3190,13,3191,5010,4840,3357,0]Af[477,5,478,3155,0,0,0]Ah[3190,5,3191,3155,0,0,0]Ag[477,5,3752,1393,0,0,0]Ah[3190,5,3753,1393,0,0,0]Al[1128,15,1129,1129,5014,0,0]Ag[477,6,1828,1396,0,0,0]Ah[3190,6,1829,1396,0,0,0]Ae[3190,6,5126,0,0,0,0]Ae[3190,6,4871,0,0,0,0]Ah[3190,6,3191,3758,0,0,0]1Al[3190,15,3191,1397,3326,0,0]Ah[477,15,1828,0,1202,0,0]Ai[3190,15,1829,0,1203,0,0]Aj[477,15,478,4173,3138,0,0]Ag[477,15,478,0,4600,0,0]Al[3190,15,3191,1153,3139,0,0]Al[3190,15,3191,3758,4601,0,0]Al[3190,15,3191,1809,4613,0,0]0Ae[1229,5,1421,0,0,0,0]Ae[1128,5,1423,0,0,0,0]Al[1229,15,1230,1230,4091,0,0]Ag[477,13,478,0,4813,0,0]Al[3190,13,3191,2370,4814,0,0]Ab[1229,3,0,0,0,0,0]Ah[477,12,3752,0,4900,0,0]Ak[477,13,3752,0,3302,4900,0]Ak[477,13,3752,0,4075,4900,0]Ak[477,13,3752,0,4382,4900,0]Al[3190,12,3753,3753,4901,0,0]An[3190,13,3753,535,3303,4901,0]Ao[3190,13,3753,1393,4076,4901,0]Ao[3190,13,3753,2423,4383,4901,0]Al[1128,13,1129,1129,5014,0,0]Ag[477,14,478,0,1185,0,0]Ag[477,14,478,0,2359,0,0]Ag[477,14,478,0,1923,0,0]Al[3190,14,3191,3155,1186,0,0]Al[3190,14,3191,1422,2360,0,0]Al[3190,14,3191,1158,1924,0,0]Al[3190,14,3191,2370,4065,0,0]An[1229,13,1230,1230,142,1187,0]Ao[1229,13,1230,1230,4091,1187,0]Ao[1229,13,1230,1230,4067,1187,0]Am[477,13,478,3757,3313,1187,0]Am[477,13,478,3757,1454,1187,0]Am[477,13,478,3757,5087,1187,0]Am[477,13,478,3757,4915,1187,0]Am[477,13,478,2369,2324,1187,0]Am[477,13,478,2369,4363,1187,0]Am[477,13,478,4286,1462,1187,0]Al[477,13,478,1127,520,1187,0]Am[477,13,478,1127,4507,1187,0]Am[477,13,478,4173,4500,1187,0]Am[477,13,478,4173,3138,1187,0]Am[477,13,478,4173,3341,1187,0]Am[477,13,478,4173,4499,1187,0]Am[477,13,478,4178,3359,1187,0]Am[477,13,478,4178,4264,1187,0]Aj[477,13,478,0,1488,1187,0]Aj[477,13,478,0,5084,1187,0]Aj[477,13,478,0,1471,1187,0]Aj[477,13,478,0,3334,1187,0]Aj[477,13,478,0,3133,1187,0]Aj[477,13,478,0,4969,1187,0]Aj[477,13,478,0,3306,1187,0]Aj[477,13,478,0,2359,1187,0]Aj[477,13,478,0,3319,1187,0]Aj[477,13,478,0,3135,1187,0]Aj[477,13,478,0,4604,1187,0]Aj[477,13,478,0,1923,1187,0]Aj[477,13,478,0,4210,1187,0]Aj[477,13,478,0,4600,1187,0]Aj[477,13,478,0,3129,1187,0]Aj[477,13,478,0,4466,1187,0]Aj[477,13,478,0,4623,1187,0]Aj[477,13,478,0,4939,1187,0]Aj[477,13,478,0,4954,1187,0]Aj[477,13,478,0,4080,1187,0]Aj[477,13,478,0,4476,1187,0]Aj[477,13,478,0,4258,1187,0]Aj[477,13,478,0,5141,1187,0]Aj[477,13,478,0,4204,1187,0]Aj[477,13,478,0,4070,1187,0]Aj[477,13,478,0,3116,1187,0]Aj[477,13,478,0,4597,1187,0]Aj[477,13,478,0,4805,1187,0]Aj[477,13,478,0,4216,1187,0]Aj[477,13,478,0,4219,1187,0]Aj[477,13,478,0,4224,1187,0]Aj[477,13,478,0,4974,1187,0]Aj[477,13,478,0,3322,1187,0]Ai[477,13,478,0,526,1187,0]Ai[477,13,478,0,134,1187,0]Aj[477,13,478,0,2346,1187,0]Aj[477,13,3752,0,501,1187,0]Ak[477,13,3752,0,3302,1187,0]Ak[477,13,3752,0,1189,1187,0]Ak[477,13,3752,0,4075,1187,0]Ak[477,13,3752,0,1896,1187,0]Ak[477,13,3752,0,4382,1187,0]Ak[477,13,1828,0,1202,1187,0]Ak[477,13,1828,0,4835,1187,0]Ak[477,13,1828,0,5078,1187,0]Ak[477,13,1828,0,4837,1187,0]Ao[3190,13,3191,1866,1489,1187,0]Ao[3190,13,3191,1866,5085,1187,0]Ao[3190,13,3191,3164,4916,1187,0]Ao[3190,13,3191,1407,1472,1187,0]Ao[3190,13,3191,1407,4254,1187,0]Ao[3190,13,3191,1407,3335,1187,0]Ao[3190,13,3191,1407,3134,1187,0]Ao[3190,13,3191,1407,4914,1187,0]Ao[3190,13,3191,1407,4970,1187,0]Ao[3190,13,3191,1407,3309,1187,0]Ao[3190,13,3191,1422,2360,1187,0]Ao[3190,13,3191,1422,3320,1187,0]Ao[3190,13,3191,1422,3136,1187,0]Ao[3190,13,3191,1422,4605,1187,0]Ao[3190,13,3191,1153,3139,1187,0]Ao[3190,13,3191,1153,4390,1187,0]Ao[3190,13,3191,1158,1924,1187,0]Ao[3190,13,3191,1158,3360,1187,0]Ao[3190,13,3191,1158,4079,1187,0]Ao[3190,13,3191,2058,4501,1187,0]Ao[3190,13,3191,2058,4214,1187,0]Ao[3190,13,3191,3758,4601,1187,0]Ao[3190,13,3191,3758,3130,1187,0]Ao[3190,13,3191,3758,4467,1187,0]Ao[3190,13,3191,3758,5173,1187,0]Ao[3190,13,3191,3292,4624,1187,0]Ao[3190,13,3191,3292,4940,1187,0]Ao[3190,13,3191,3292,4955,1187,0]Ao[3190,13,3191,3292,4081,1187,0]Ao[3190,13,3191,1744,4477,1187,0]Ao[3190,13,3191,1744,4260,1187,0]Ao[3190,13,3191,1744,5142,1187,0]Ao[3190,13,3191,1744,4206,1187,0]Ao[3190,13,3191,1744,4071,1187,0]Ao[3190,13,3191,1397,3117,1187,0]Ao[3190,13,3191,1397,3326,1187,0]Ao[3190,13,3191,1397,3349,1187,0]Ao[3190,13,3191,1397,4221,1187,0]Ao[3190,13,3191,1397,4599,1187,0]Ao[3190,13,3191,1397,4807,1187,0]Ao[3190,13,3191,1397,2318,1187,0]Ao[3190,13,3191,2370,4811,1187,0]Ao[3190,13,3191,2370,4217,1187,0]Ao[3190,13,3191,1809,4613,1187,0]Ao[3190,13,3191,1809,5177,1187,0]Ao[3190,13,3191,1809,2332,1187,0]Ao[3190,13,3191,1809,4911,1187,0]Ao[3190,13,3191,1839,4220,1187,0]Ao[3190,13,3191,1839,4225,1187,0]Al[3190,13,3191,0,4505,1187,0]Al[3190,13,3191,0,4975,1187,0]Ao[3190,13,3191,4403,3323,1187,0]Ao[3190,13,3191,4403,5277,1187,0]An[3190,13,3191,3191,527,1187,0]An[3190,13,3191,3191,135,1187,0]Ao[3190,13,3191,3191,2347,1187,0]Ao[3190,13,3191,3191,3115,1187,0]Ao[3190,13,1742,1742,4396,1187,0]Ao[3190,13,5126,5126,5022,1187,0]Ao[3190,13,5126,5126,1204,1187,0]Al[3190,13,1829,0,1203,1187,0]Ao[3190,13,1829,1829,4836,1187,0]Ao[3190,13,1829,1829,5079,1187,0]Ao[3190,13,1829,1829,4838,1187,0]Am[3190,13,3753,535,502,1187,0]An[3190,13,3753,535,3303,1187,0]Ao[3190,13,3753,1393,1190,1187,0]Ao[3190,13,3753,1393,4076,1187,0]Ao[3190,13,3753,2423,1897,1187,0]Ao[3190,13,3753,2423,4383,1187,0]Ao[3190,13,4871,4871,1208,1187,0]Ao[2043,13,2044,2044,2344,1187,0]Ao[2043,13,2044,2044,2358,1187,0]Ao[2043,13,2044,2044,4226,1187,0]Ao[1128,13,1129,1129,5014,1187,0]Ao[1128,13,1423,1423,4609,1187,0]Ao[1128,13,4320,4320,5226,1187,0]Ao[1128,13,4320,4320,5227,1187,0]Aj[477,13,478,3757,3313,0,0]Aj[477,13,478,2369,2324,0,0]Ai[477,13,478,1127,520,0,0]Al[3190,13,3191,3758,3314,0,0]Aj[477,13,478,0,4219,1193,0]Ao[3190,13,3191,1839,4220,1193,0]Ai[3190,14,3191,0,4505,0,0]Ag[477,13,478,0,2346,0,0]Al[3190,13,3191,3191,2347,0,0]Ag[477,13,478,0,4224,0,0]Al[3190,13,3191,1839,4225,0,0]Ah[477,14,1746,0,1891,0,0]Al[3190,14,1747,1747,1892,0,0]Al[3190,12,3191,1809,4945,0,0]Ao[3190,13,3191,1809,5304,4945,0];Al[1229,13,1421,1421,1222,0,0]Ai[536,13,537,537,4068,0,0]Al[1128,13,1423,1423,1223,0,0]Aj[477,12,478,2050,3298,0,0]Aj[477,13,478,4178,3345,0,0]Al[3190,13,3191,2058,3346,0,0]Aj[477,13,478,2401,2328,0,0]Al[3190,13,3191,2370,2325,0,0]Aj[477,12,478,2050,1908,0,0]Am[477,13,478,1435,4265,1908,0]Am[477,13,478,1170,4094,1908,0]Ae[1229,2,1230,0,0,0,0]Ak[1229,14,1230,1230,142,0,0]Aj[477,13,478,3757,3313,0,0]Aj[477,13,478,2369,2324,0,0]Ai[477,13,478,1127,520,0,0]Aj[477,14,478,3757,1454,0,0]Al[3190,13,3191,3758,3314,0,0]Ae[1128,2,1129,0,0,0,0]Ag[477,13,478,0,3336,0,0]Al[3190,13,3191,1149,3337,0,0]Aj[477,13,478,4178,3345,0,0]Al[3190,13,3191,2058,3346,0,0]Al[3190,13,5126,5126,5022,0,0]Af[477,5,478,2369,0,0,0]Ag[477,15,478,0,3116,0,0]Al[3190,15,3191,1397,3117,0,0]Al[1229,15,1230,1230,4067,0,0]0Ag[477,15,478,0,3334,0,0]Al[3190,15,3191,1407,3335,0,0]Ah[3190,5,3191,1153,0,0,0]Af[477,15,478,0,526,0,0]Al[2423,15,2424,2424,2315,0,0]Ak[3190,15,3191,3191,527,0,0]Af[477,5,478,3757,0,0,0]Al[3190,15,4871,4871,1205,0,0]:9Af[477,5,478,4286,0,0,0]2Ag[477,15,478,0,3133,0,0]Al[3190,15,3191,1407,3134,0,0]Al[3190,15,3191,2058,4098,0,0]Af[477,5,478,1407,0,0,0]Ah[3190,5,3191,1407,0,0,0]Ae[3190,8,1742,0,0,0,0];:9Ag[477,15,478,0,3116,0,0]Al[3190,15,3191,3758,1207,0,0]Al[3190,15,3191,1397,3117,0,0]Af[477,5,478,1866,0,0,0]Ah[3190,5,3191,1866,0,0,0]Aj[477,13,478,2369,2324,0,0]Aj[477,13,478,2401,2328,0,0]Al[3190,13,3191,2058,1917,0,0]Al[3190,13,3191,2370,2325,0,0]An[1229,13,1230,1230,142,1448,0]Ao[1229,13,1230,1230,4091,1448,0]Ao[1229,13,1230,1230,4067,1448,0]Ao[1229,13,1421,1421,4099,1448,0]Ao[1229,13,1421,1421,1222,1448,0]Ao[1229,13,1421,1421,4502,1448,0]Al[536,13,537,537,4068,1448,0]Ao[4105,13,4106,4106,5178,1448,0]Am[477,13,478,3757,4628,1448,0]Am[477,13,478,3757,2339,1448,0]Aj[477,13,478,0,4354,1448,0]Am[477,13,478,4286,5080,1448,0]Am[477,13,478,1127,2340,1448,0]Am[477,13,478,1127,4803,1448,0]Am[477,13,478,3757,3311,1448,0]Am[477,13,478,3757,3313,1448,0]Am[477,13,478,3757,1899,1448,0]Am[477,13,478,3757,1463,1448,0]Am[477,13,478,3757,3094,1448,0]Am[477,13,478,3757,2311,1448,0]Am[477,13,478,3757,4248,1448,0]Am[477,13,478,3757,4255,1448,0]Am[477,13,478,3757,1454,1448,0]Am[477,13,478,3757,5087,1448,0]Am[477,13,478,3757,4915,1448,0]Am[477,13,478,2369,2324,1448,0]Am[477,13,478,2369,4363,1448,0]Am[477,13,478,2369,1441,1448,0]Am[477,13,478,4286,1462,1448,0]Am[477,13,478,4286,3333,1448,0]Am[477,13,478,4286,5015,1448,0]Am[477,13,478,2401,2328,1448,0]Am[477,13,478,2401,3342,1448,0]Am[477,13,478,2401,4481,1448,0]Al[477,13,478,1127,520,1448,0]Am[477,13,478,1127,4507,1448,0]Am[477,13,478,4173,3339,1448,0]Am[477,13,478,4173,5023,1448,0]Am[477,13,478,4173,4500,1448,0]Am[477,13,478,4173,3138,1448,0]Am[477,13,478,4173,3341,1448,0]Am[477,13,478,4173,4499,1448,0]Am[477,13,478,1435,4265,1448,0]Am[477,13,478,1170,4094,1448,0]Am[477,13,478,4178,3345,1448,0]Am[477,13,478,4178,3359,1448,0]Am[477,13,478,4178,4264,1448,0]Am[477,13,478,4178,1913,1448,0]Aj[477,13,478,0,3336,1448,0]Aj[477,13,478,0,4394,1448,0]Aj[477,13,478,0,4813,1448,0]Aj[477,13,478,0,4620,1448,0]Aj[477,13,478,0,4238,1448,0]Aj[477,13,478,0,4490,1448,0]Aj[477,13,478,0,1488,1448,0]Aj[477,13,478,0,5084,1448,0]Aj[477,13,478,0,1185,1448,0]Aj[477,13,478,0,1471,1448,0]Aj[477,13,478,0,3334,1448,0]Aj[477,13,478,0,3133,1448,0]Aj[477,13,478,0,4969,1448,0]Aj[477,13,478,0,3306,1448,0]Aj[477,13,478,0,2359,1448,0]Aj[477,13,478,0,3319,1448,0]Aj[477,13,478,0,3135,1448,0]Aj[477,13,478,0,4604,1448,0]Aj[477,13,478,0,1923,1448,0]Aj[477,13,478,0,4210,1448,0]Aj[477,13,478,0,4600,1448,0]Aj[477,13,478,0,3129,1448,0]Aj[477,13,478,0,4466,1448,0]Aj[477,13,478,0,4614,1448,0]Aj[477,13,478,0,4623,1448,0]Aj[477,13,478,0,4939,1448,0]Aj[477,13,478,0,4954,1448,0]Aj[477,13,478,0,4080,1448,0]Aj[477,13,478,0,4476,1448,0]Aj[477,13,478,0,4258,1448,0]Aj[477,13,478,0,5141,1448,0]Aj[477,13,478,0,4204,1448,0]Aj[477,13,478,0,4070,1448,0]Aj[477,13,478,0,3116,1448,0]Aj[477,13,478,0,4597,1448,0]Aj[477,13,478,0,4805,1448,0]Aj[477,13,478,0,4216,1448,0]Aj[477,13,478,0,4219,1448,0]Aj[477,13,478,0,4224,1448,0]Aj[477,13,478,0,4974,1448,0]Aj[477,13,478,0,3322,1448,0]Aj[477,13,478,0,4839,1448,0]Ai[477,13,478,0,526,1448,0]Ai[477,13,478,0,134,1448,0]Aj[477,13,478,0,2346,1448,0]Aj[477,13,478,0,2356,1448,0]Aj[477,13,3752,0,501,1448,0]Ak[477,13,3752,0,3302,1448,0]Ak[477,13,3752,0,1189,1448,0]Ak[477,13,3752,0,4075,1448,0]Ak[477,13,3752,0,1896,1448,0]Ak[477,13,3752,0,4382,1448,0]Ak[477,13,3752,0,4902,1448,0]Ak[477,13,1746,0,1891,1448,0]Aj[477,13,1746,0,514,1448,0]Ak[477,13,1746,0,5240,1448,0]Ak[477,13,1746,0,4073,1448,0]Ak[477,13,1746,0,4978,1448,0]Ak[477,13,1828,0,3354,1448,0]Ak[477,13,1828,0,4365,1448,0]Ak[477,13,1828,0,1202,1448,0]Ak[477,13,1828,0,4835,1448,0]Ak[477,13,1828,0,5078,1448,0]Ak[477,13,1828,0,4837,1448,0]An[2423,13,2424,2424,133,1448,0]Ao[2423,13,2424,2424,2315,1448,0]Ao[3190,13,3191,4633,4355,1448,0]Ao[3190,13,3191,3164,3107,1448,0]Ao[3190,13,3191,1153,1450,1448,0]Ao[3190,13,3191,2058,1917,1448,0]Ao[3190,13,3191,2058,3340,1448,0]Ao[3190,13,3191,2058,3346,1448,0]Ao[3190,13,3191,3758,3314,1448,0]Ao[3190,13,3191,1149,3337,1448,0]Ao[3190,13,3191,1744,4395,1448,0]Ao[3190,13,3191,2370,2325,1448,0]Ao[3190,13,3191,2370,4361,1448,0]Ao[3190,13,3191,2370,4814,1448,0]Ao[3190,13,3191,2370,4373,1448,0]Ao[3190,13,3191,1809,5304,1448,0]Ao[3190,13,3191,4403,5272,1448,0]Ao[3190,13,3191,1434,4621,1448,0]Ao[3190,13,3191,1434,4239,1448,0]Ao[3190,13,3191,1434,4491,1448,0]Ao[3190,13,3191,1866,1489,1448,0]Ao[3190,13,3191,1866,5085,1448,0]Ao[3190,13,3191,3155,1186,1448,0]Ao[3190,13,3191,3164,4916,1448,0]Ao[3190,13,3191,1407,1472,1448,0]Ao[3190,13,3191,1407,4254,1448,0]Ao[3190,13,3191,1407,3335,1448,0]Ao[3190,13,3191,1407,3134,1448,0]Ao[3190,13,3191,1407,4914,1448,0]Ao[3190,13,3191,1407,4970,1448,0]Ao[3190,13,3191,1407,3309,1448,0]Ao[3190,13,3191,1422,2360,1448,0]Ao[3190,13,3191,1422,3320,1448,0]Ao[3190,13,3191,1422,3136,1448,0]Ao[3190,13,3191,1422,4605,1448,0]Ao[3190,13,3191,1153,3139,1448,0]Ao[3190,13,3191,1153,4390,1448,0]Ao[3190,13,3191,1158,1924,1448,0]Ao[3190,13,3191,1158,3360,1448,0]Ao[3190,13,3191,1158,4079,1448,0]Ao[3190,13,3191,2058,4098,1448,0]Ao[3190,13,3191,2058,4501,1448,0]Ao[3190,13,3191,2058,4214,1448,0]Ao[3190,13,3191,3758,4601,1448,0]Ao[3190,13,3191,3758,3130,1448,0]Ao[3190,13,3191,3758,4467,1448,0]Ao[3190,13,3191,3758,5173,1448,0]Ao[3190,13,3191,3758,1207,1448,0]Ao[3190,13,3191,1149,4615,1448,0]Ao[3190,13,3191,3292,4624,1448,0]Ao[3190,13,3191,3292,4940,1448,0]Ao[3190,13,3191,3292,4955,1448,0]Ao[3190,13,3191,3292,4081,1448,0]Ao[3190,13,3191,1744,4477,1448,0]Ao[3190,13,3191,1744,4260,1448,0]Ao[3190,13,3191,1744,5142,1448,0]Ao[3190,13,3191,1744,4206,1448,0]Ao[3190,13,3191,1744,4071,1448,0]Ao[3190,13,3191,1397,3117,1448,0]Ao[3190,13,3191,1397,4482,1448,0]Ao[3190,13,3191,1397,3326,1448,0]Ao[3190,13,3191,1397,3349,1448,0]Ao[3190,13,3191,1397,4221,1448,0]Ao[3190,13,3191,1397,4599,1448,0]Ao[3190,13,3191,1397,4807,1448,0]Ao[3190,13,3191,1397,2318,1448,0]Ao[3190,13,3191,2370,4811,1448,0]Ao[3190,13,3191,2370,2329,1448,0]Ao[3190,13,3191,2370,4217,1448,0]Ao[3190,13,3191,2370,4065,1448,0]Ao[3190,13,3191,2370,3343,1448,0]Ao[3190,13,3191,1809,4385,1448,0]Ao[3190,13,3191,1809,4613,1448,0]Ao[3190,13,3191,1809,5177,1448,0]Ao[3190,13,3191,1809,2332,1448,0]Ao[3190,13,3191,1809,4911,1448,0]Ao[3190,13,3191,1839,4603,1448,0]Ao[3190,13,3191,1839,4362,1448,0]Ao[3190,13,3191,1839,4220,1448,0]Ao[3190,13,3191,1839,4225,1448,0]Al[3190,13,3191,0,4505,1448,0]Al[3190,13,3191,0,4975,1448,0]Ao[3190,13,3191,4403,3323,1448,0]Ao[3190,13,3191,4403,5277,1448,0]Ao[3190,13,3191,5010,4840,1448,0]An[3190,13,3191,3191,527,1448,0]An[3190,13,3191,3191,135,1448,0]Ao[3190,13,3191,3191,2347,1448,0]Ao[3190,13,3191,3191,2357,1448,0]Ao[3190,13,3191,3191,3115,1448,0]Ao[3190,13,1742,1742,4396,1448,0]Ao[3190,13,5126,5126,5022,1448,0]Ao[3190,13,5126,5126,1204,1448,0]Ao[3190,13,1747,1747,1892,1448,0]An[3190,13,1747,1747,515,1448,0]Ao[3190,13,1747,1747,5241,1448,0]Ao[3190,13,1747,1747,4074,1448,0]Ao[3190,13,1747,1747,4981,1448,0]Ao[3190,13,1829,1829,3355,1448,0]Ao[3190,13,1829,1829,4366,1448,0]Al[3190,13,1829,0,1203,1448,0]Ao[3190,13,1829,1829,4836,1448,0]Ao[3190,13,1829,1829,5079,1448,0]Ao[3190,13,1829,1829,4838,1448,0]Am[3190,13,3753,535,502,1448,0]An[3190,13,3753,535,3303,1448,0]Ao[3190,13,3753,1393,1190,1448,0]Ao[3190,13,3753,1393,4076,1448,0]Ao[3190,13,3753,2423,1897,1448,0]Ao[3190,13,3753,2423,4383,1448,0]Ao[3190,13,3753,3753,4903,1448,0]Ao[3190,13,4871,4871,1205,1448,0]Ao[3190,13,4871,4871,1208,1448,0]Ao[2043,13,2044,2044,3358,1448,0]An[2043,13,2044,432,4506,1448,0]Ao[2043,13,2044,2044,2344,1448,0]Ao[2043,13,2044,2044,2358,1448,0]Ao[2043,13,2044,2044,4226,1448,0]Ao[1128,13,1129,1129,3104,1448,0]Ao[1128,13,1129,1129,5014,1448,0]An[1128,13,1736,1736,143,1448,0]Ao[1128,13,1423,1423,1223,1448,0]Ao[1128,13,1423,1423,4100,1448,0]Ao[1128,13,1423,1423,4609,1448,0]Ao[1128,13,4320,4320,5226,1448,0]Ao[1128,13,4320,4320,5227,1448,0]An[1128,13,1834,1834,144,1448,0]Ag[477,14,478,0,1923,0,0]Al[3190,14,3191,1158,1924,0,0]Ak[1229,14,1230,1230,142,0,0]Ac[536,7,537,0,0,0,0]Aj[477,13,478,2369,2324,0,0]Ae[1128,2,1129,0,0,0,0]Ae[4934,7,4935,0,0,0,0]Aj[477,13,478,3757,3313,0,0]Ae[3190,2,3191,0,0,0,0]Al[3190,13,3191,2370,2325,0,0]Af[477,2,478,3191,0,0,0]Ag[477,13,478,0,4974,0,0]Ag[477,14,478,0,3319,0,0]Ag[477,14,478,0,4258,0,0]5Ai[3190,13,3191,0,4975,0,0]Al[3190,14,3191,1422,3320,0,0]Al[3190,14,3191,1744,4260,0,0]Ai[3190,14,3191,0,4505,0,0]Aj[477,13,478,0,1488,3108,0]Aj[477,13,478,0,1471,3108,0]Aj[477,13,478,0,3116,3108,0]Aj[477,13,478,0,4974,3108,0]Aj[477,13,478,0,2346,3108,0]Ao[3190,13,3191,1866,1489,3108,0]Ao[3190,13,3191,1407,1472,3108,0]Ao[3190,13,3191,1397,3117,3108,0]Ao[3190,13,3191,1397,3326,3108,0]Al[3190,13,3191,1809,4613,0,0]Al[3190,13,3191,0,4975,3108,0]Ao[3190,13,3191,3191,2347,3108,0]Al[3190,12,1742,1742,3108,0,0]Al[2043,14,2044,2044,2344,0,0]Aj[477,13,478,0,1488,3113,0]Aj[477,13,478,0,1471,3113,0]Aj[477,13,478,0,3116,3113,0]Aj[477,13,478,0,4974,3113,0]Aj[477,13,478,0,2346,3113,0]Ao[3190,13,3191,1866,1489,3113,0]Ao[3190,13,3191,1407,1472,3113,0]Ao[3190,13,3191,1397,3117,3113,0]Ao[3190,13,3191,1397,3326,3113,0]Ao[3190,13,3191,1397,2318,3113,0]Al[3190,13,3191,0,4975,3113,0]Ao[3190,13,3191,3191,2347,3113,0]Al[3190,12,1742,1742,3113,0,0]Aj[477,13,478,4178,3345,0,0]Al[3190,13,3191,2058,3346,0,0]Al[1128,13,1423,1423,4100,0,0]Al[3190,13,3191,2370,2325,0,0]Ag[477,14,1746,0,514,0,0]Ak[3190,14,1747,1747,515,0,0]Aj[477,13,478,2401,2328,0,0]3Ag[477,12,478,0,3356,0,0]Aj[477,13,478,0,4490,3356,0]Aj[477,13,478,0,4839,3356,0]Al[3190,12,3191,1434,3357,0,0]Ao[3190,13,3191,1434,4491,3357,0]Ao[3190,13,3191,5010,4840,3357,0]Al[1128,14,1129,1129,3104,0,0]Ag[477,14,478,0,1185,0,0]Al[3190,14,3191,3155,1186,0,0]Ah[477,13,3752,0,3315,0,0]Aj[477,13,3752,0,501,3315,0]Ak[477,13,3752,0,1189,3315,0]Ak[477,13,3752,0,1896,3315,0]Al[3190,13,3753,3753,3316,0,0]Am[3190,13,3753,535,502,3316,0]Ao[3190,13,3753,1393,1190,3316,0]Ao[3190,13,3753,2423,1897,3316,0]Ag[477,14,1746,0,514,0,0]Ak[3190,14,1747,1747,515,0,0]Ai[3190,14,4612,0,4845,0,0]Aj[477,14,478,3757,1454,0,0]Ag[477,14,478,0,4597,0,0]Ag[477,14,478,0,4805,0,0]Ah[477,14,3131,0,3090,0,0]Al[3190,14,3191,1397,4599,0,0]Al[3190,14,3191,1397,4807,0,0]Ai[3190,14,3191,0,4505,0,0]Ai[3190,14,1206,0,4469,0,0]Ai[3190,14,3132,0,3091,0,0]Aj[477,13,478,2369,2324,0,0]Aj[477,13,478,2401,2328,0,0]Al[3190,13,3191,2370,2325,0,0]Ah[477,14,1746,0,1891,0,0]Al[3190,13,3191,3191,3115,0,0]Al[3190,14,1747,1747,1892,0,0]Ac[477,4,478,0,0,0,0]Ag[477,2,1746,1747,0,0,0]Ae[3190,4,3191,0,0,0,0]Ae[3190,2,1747,0,0,0,0]Ac[536,7,537,0,0,0,0]:Ae[1128,2,1129,0,0,0,0]Ae[4934,7,4935,0,0,0,0]Am[477,13,478,3757,4915,1195,0]Am[477,13,478,4173,4500,1195,0]Am[477,13,478,4173,3138,1195,0]Am[477,13,478,4173,3341,1195,0]Am[477,13,478,4173,4499,1195,0]Am[477,13,478,4178,3359,1195,0]Am[477,13,478,4178,4264,1195,0]Aj[477,13,478,0,4600,1195,0]Ao[3190,13,3191,3758,4601,1195,0]Ag[477,14,478,0,2356,0,0]Al[3190,14,3191,3191,2357,0,0]Al[2043,14,2044,2044,2358,0,0]Ai[477,13,478,1127,520,0,0]Ah[477,14,3131,0,4249,0,0]Ai[3190,14,3132,0,4250,0,0]Ag[477,14,478,0,3319,0,0]Al[3190,14,3191,1422,3320,0,0]Al[2043,14,2044,2044,2344,0,0]Ag[477,14,478,0,3135,0,0]Ag[477,14,478,0,4604,0,0]Al[3190,14,3191,1422,3136,0,0]Al[3190,14,3191,1422,4605,0,0]3210Ah[477,12,1746,0,1887,0,0]Al[3190,12,1747,1747,1888,0,0]Ah[477,12,3752,0,3315,0,0]Aj[477,13,3752,0,501,3315,0]Ak[477,13,3752,0,1189,3315,0]Ak[477,13,3752,0,1896,3315,0]Al[3190,12,3753,3753,3316,0,0]Am[3190,13,3753,535,502,3316,0]Ao[3190,13,3753,1393,1190,3316,0]Ao[3190,13,3753,2423,1897,3316,0]Al[3190,15,3191,3164,4916,0,0]Al[3190,15,3191,1153,3139,0,0]Al[3190,15,3191,1158,3360,0,0]Ah[477,15,1828,0,1202,0,0]Ai[3190,15,1829,0,1203,0,0]Ah[477,10,1746,1747,0,0,0]Af[3190,10,1747,0,0,0,0]Al[3190,15,3191,1809,4613,0,0]Ag[477,5,1746,1747,0,0,0]Ae[3190,5,1747,0,0,0,0]Ag[477,15,478,0,3116,0,0]Al[3190,15,3191,1397,3117,0,0]Ag[477,5,3752,2423,0,0,0]Ah[3190,5,3753,2423,0,0,0]Al[1128,15,1129,1129,5014,0,0]Af[477,5,478,3757,0,0,0]5454Ag[477,10,478,2050,0,0,0];:Aj[477,15,478,4178,3359,0,0]Af[477,5,478,4178,0,0,0]Al[3190,15,3191,1158,3360,0,0]Al[3190,15,3191,1158,4079,0,0]Al[3190,15,3191,2058,4098,0,0]Ah[3190,5,3191,2058,0,0,0]Aj[477,15,478,4173,4500,0,0]Al[3190,15,3191,1397,3326,0,0]Af[477,5,478,1158,0,0,0]Ah[3190,5,3191,1158,0,0,0]Al[3190,15,3191,1809,2332,0,0]Ag[477,12,478,0,5132,0,0]Al[3190,12,3191,4403,5133,0,0]Ao[3190,13,3191,4403,5272,5133,0]Ao[3190,13,3191,4403,5277,5133,0]Ak[1229,14,1230,1230,142,0,0]An[1229,13,1230,1230,142,4468,0]An[2423,13,2424,2424,133,4468,0]Ak[2423,14,2424,2424,133,0,0]Ao[1128,13,1129,1129,3104,4468,0]An[1128,13,1736,1736,143,4468,0]An[1128,13,1834,1834,144,4468,0]Aj[477,13,478,0,1488,3310,0]Aj[477,13,478,0,1471,3310,0]Aj[477,13,478,0,4080,3310,0]Aj[477,13,478,0,4219,3310,0]Ai[477,13,478,0,526,3310,0]Ai[477,13,478,0,134,3310,0]Aj[477,13,478,0,2346,3310,0]Ao[3190,13,3191,1866,1489,3310,0]Ao[3190,13,3191,3164,4916,3310,0]Ao[3190,13,3191,1407,1472,3310,0]Ao[3190,13,3191,1153,3139,3310,0]Ao[3190,13,3191,1153,4390,3310,0]Ao[3190,13,3191,1158,3360,3310,0]Ao[3190,13,3191,3292,4081,3310,0]Ao[3190,13,3191,2370,4811,3310,0]Ao[3190,13,3191,1809,4911,3310,0]Ao[3190,13,3191,1839,4220,3310,0]An[3190,13,3191,3191,527,3310,0]An[3190,13,3191,3191,135,3310,0]Ao[3190,13,3191,3191,2347,3310,0]Aj[477,13,478,2369,2324,0,0]Al[3190,13,3191,2370,2325,0,0]Aj[477,13,478,2401,2328,0,0]Ah[477,12,1746,0,3126,0,0]Ah[477,12,1746,0,3119,0,0]Al[3190,13,3191,2370,2329,0,0]Al[3190,12,1747,1747,3127,0,0]Al[3190,12,1747,1747,3120,0,0]Ao[1229,13,1421,1421,4099,1882,0]Ao[1229,13,1421,1421,1222,1882,0]Al[536,13,537,537,4068,1882,0]Aj[477,13,478,0,4354,1882,0]Am[477,13,478,2369,4363,1882,0]Aj[477,13,478,0,1488,1882,0]Aj[477,13,478,0,1471,1882,0]Aj[477,13,478,0,3334,1882,0]Aj[477,13,478,0,3306,1882,0]Aj[477,13,478,0,2359,1882,0]Aj[477,13,478,0,3319,1882,0]Aj[477,13,478,0,3135,1882,0]Aj[477,13,478,0,4604,1882,0]Aj[477,13,478,0,1923,1882,0]Aj[477,13,478,0,4210,1882,0]Aj[477,13,478,0,4623,1882,0]Aj[477,13,478,0,4939,1882,0]Aj[477,13,478,0,4954,1882,0]Aj[477,13,478,0,4080,1882,0]Aj[477,13,478,0,4476,1882,0]Aj[477,13,478,0,5141,1882,0]Aj[477,13,478,0,4839,1882,0]Ak[477,13,3752,0,3302,1882,0]Ak[477,13,3752,0,4075,1882,0]Ak[477,13,3752,0,4382,1882,0]Ak[477,13,3752,0,4902,1882,0]Ao[3190,13,3191,4633,4355,1882,0]Ao[3190,13,3191,4403,5272,1882,0]Ao[3190,13,3191,1866,1489,1882,0]Ao[3190,13,3191,1407,1472,1882,0]Ao[3190,13,3191,1407,3335,1882,0]Ao[3190,13,3191,1407,3309,1882,0]Ao[3190,13,3191,1422,2360,1882,0]Ao[3190,13,3191,1422,3320,1882,0]Ao[3190,13,3191,1422,3136,1882,0]Ao[3190,13,3191,1422,4605,1882,0]Ao[3190,13,3191,1158,1924,1882,0]Ao[3190,13,3191,2058,4501,1882,0]Ao[3190,13,3191,2058,4214,1882,0]Ao[3190,13,3191,3292,4624,1882,0]Ao[3190,13,3191,3292,4940,1882,0]Ao[3190,13,3191,3292,4955,1882,0]Ao[3190,13,3191,3292,4081,1882,0]Ao[3190,13,3191,1744,4477,1882,0]Ao[3190,13,3191,1744,5142,1882,0]Ao[3190,13,3191,4403,5277,1882,0]Ao[3190,13,3191,5010,4840,1882,0]An[3190,13,3753,535,3303,1882,0]Ao[3190,13,3753,1393,4076,1882,0]Ao[3190,13,3753,2423,4383,1882,0]Ao[3190,13,3753,3753,4903,1882,0]Ao[2043,13,2044,2044,2344,1882,0]Ao[1128,13,1423,1423,1223,1882,0]Ao[1128,13,1423,1423,4609,1882,0]Al[3190,13,3191,1839,4362,0,0]Al[3190,13,3191,1809,4385,0,0]Al[3190,13,3191,1809,4613,0,0]Al[3190,14,3191,1809,5177,0,0]Ai[3190,14,4910,0,4976,0,0]Ah[3190,14,4612,0,516,0,0]Ai[3190,14,4612,0,1217,0,0]Ai[3190,14,4612,0,1219,0,0]Ai[3190,14,4612,0,4845,0,0]Ah[477,12,1746,0,3126,0,0]Ah[477,12,1746,0,3119,0,0]Al[3190,12,1747,1747,3127,0,0]Al[3190,12,1747,1747,3120,0,0]Aj[477,13,478,4173,3339,0,0]Aj[477,13,478,4173,5023,0,0]Ah[477,13,3752,0,3315,0,0]Aj[477,13,3752,0,501,3315,0]Ak[477,13,3752,0,1189,3315,0]Ak[477,13,3752,0,1896,3315,0]Al[3190,13,3753,3753,3316,0,0]Am[3190,13,3753,535,502,3316,0]Ao[3190,13,3753,1393,1190,3316,0]Ao[3190,13,3753,2423,1897,3316,0]Ag[477,13,478,0,4623,0,0]Al[3190,13,3191,3292,4624,0,0]Ab[2043,3,0,0,0,0,0]:6Aj[477,13,478,3757,3313,0,0]Aj[477,13,478,1127,2340,0,0]Al[3190,14,3191,1809,5177,0,0]Ai[3190,14,4910,0,4976,0,0]Ah[3190,14,4612,0,516,0,0]Ai[3190,14,4612,0,1217,0,0]Ai[3190,14,4612,0,4845,0,0]Aj[477,13,478,4178,3345,0,0]Al[3190,13,3191,2058,3346,0,0]Ak[1229,14,1230,1230,142,0,0]Al[1229,14,1421,1421,1222,0,0]Al[3190,13,3191,3758,3314,0,0]Ah[477,12,1746,0,3126,0,0]Al[3190,12,1747,1747,3127,0,0]Am[1229,13,1230,1230,142,129,0]An[1229,13,1230,1230,4091,129,0]An[1229,13,1230,1230,4067,129,0]An[1229,13,1421,1421,4099,129,0]An[1229,13,1421,1421,1222,129,0]An[1229,13,1421,1421,4502,129,0]Ak[536,13,537,537,4068,129,0]An[4105,13,4106,4106,5178,129,0]Al[477,13,478,3757,4628,129,0]Al[477,13,478,3757,2339,129,0]Ai[477,13,478,0,4354,129,0]Al[477,13,478,4286,5080,129,0]Al[477,13,478,1127,2340,129,0]Al[477,13,478,1127,4803,129,0]Al[477,13,478,3757,3311,129,0]Al[477,13,478,3757,3313,129,0]Al[477,13,478,3757,1899,129,0]Al[477,13,478,3757,1463,129,0]Al[477,13,478,3757,3094,129,0]Al[477,13,478,3757,2311,129,0]Al[477,13,478,3757,4248,129,0]Al[477,13,478,3757,4255,129,0]Al[477,13,478,3757,1454,129,0]Al[477,13,478,3757,5087,129,0]Al[477,13,478,3757,4915,129,0]Al[477,13,478,2369,2324,129,0]Al[477,13,478,2369,4363,129,0]Al[477,13,478,2369,1441,129,0]Al[477,13,478,4286,1462,129,0]Al[477,13,478,4286,3333,129,0]Al[477,13,478,4286,5015,129,0]Al[477,13,478,2401,2328,129,0]Al[477,13,478,2401,3342,129,0]Al[477,13,478,2401,4481,129,0]Ak[477,13,478,1127,520,129,0]Al[477,13,478,1127,4507,129,0]Al[477,13,478,4173,3339,129,0]Al[477,13,478,4173,5023,129,0]Al[477,13,478,4173,4500,129,0]Al[477,13,478,4173,3138,129,0]Al[477,13,478,4173,3341,129,0]Al[477,13,478,4173,4499,129,0]Al[477,13,478,1435,4265,129,0]Al[477,13,478,1170,4094,129,0]Al[477,13,478,4178,3345,129,0]Al[477,13,478,4178,3359,129,0]Al[477,13,478,4178,4264,129,0]Al[477,13,478,4178,1913,129,0]Ai[477,13,478,0,3336,129,0]Ai[477,13,478,0,4394,129,0]Ai[477,13,478,0,4813,129,0]Ai[477,13,478,0,4620,129,0]Ai[477,13,478,0,4238,129,0]Ai[477,13,478,0,4490,129,0]Ai[477,13,478,0,1488,129,0]Ai[477,13,478,0,5084,129,0]Ai[477,13,478,0,1185,129,0]Ai[477,13,478,0,1471,129,0]Ai[477,13,478,0,3334,129,0]Ai[477,13,478,0,3133,129,0]Ai[477,13,478,0,4969,129,0]Ai[477,13,478,0,3306,129,0]Ai[477,13,478,0,2359,129,0]Ai[477,13,478,0,3319,129,0]Ai[477,13,478,0,3135,129,0]Ai[477,13,478,0,4604,129,0]Ai[477,13,478,0,1923,129,0]Ai[477,13,478,0,4210,129,0]Ai[477,13,478,0,4600,129,0]Ai[477,13,478,0,3129,129,0]Ai[477,13,478,0,4466,129,0]Ai[477,13,478,0,4614,129,0]Ai[477,13,478,0,4623,129,0]Ai[477,13,478,0,4939,129,0]Ai[477,13,478,0,4954,129,0]Ai[477,13,478,0,4080,129,0]Ai[477,13,478,0,4476,129,0]Ai[477,13,478,0,4258,129,0]Ai[477,13,478,0,5141,129,0]Ai[477,13,478,0,4204,129,0]Ai[477,13,478,0,4070,129,0]Ai[477,13,478,0,3116,129,0]Ai[477,13,478,0,4597,129,0]Ai[477,13,478,0,4805,129,0]Ai[477,13,478,0,4216,129,0]Ai[477,13,478,0,4219,129,0]Ai[477,13,478,0,4224,129,0]Ai[477,13,478,0,4974,129,0]Ai[477,13,478,0,3322,129,0]Ai[477,13,478,0,4839,129,0]Ah[477,13,478,0,526,129,0]Ah[477,13,478,0,134,129,0]Ai[477,13,478,0,2346,129,0]Ai[477,13,478,0,2356,129,0]Ai[477,13,3752,0,501,129,0]Aj[477,13,3752,0,3302,129,0]Aj[477,13,3752,0,1189,129,0]Aj[477,13,3752,0,4075,129,0]Aj[477,13,3752,0,1896,129,0]Aj[477,13,3752,0,4382,129,0]Aj[477,13,3752,0,4902,129,0]Aj[477,13,1746,0,1891,129,0]Ai[477,13,1746,0,514,129,0]Aj[477,13,1746,0,5240,129,0]Aj[477,13,1746,0,4073,129,0]Aj[477,13,1746,0,4978,129,0]Aj[477,13,1828,0,3354,129,0]Aj[477,13,1828,0,4365,129,0]Aj[477,13,1828,0,1202,129,0]Aj[477,13,1828,0,4835,129,0]Aj[477,13,1828,0,5078,129,0]Aj[477,13,1828,0,4837,129,0]Am[2423,13,2424,2424,133,129,0]An[2423,13,2424,2424,2315,129,0]An[3190,13,3191,4633,4355,129,0]An[3190,13,3191,3164,3107,129,0]An[3190,13,3191,1153,1450,129,0]An[3190,13,3191,2058,1917,129,0]An[3190,13,3191,2058,3340,129,0]An[3190,13,3191,2058,3346,129,0]An[3190,13,3191,3758,3314,129,0]An[3190,13,3191,1149,3337,129,0]An[3190,13,3191,1744,4395,129,0]An[3190,13,3191,2370,2325,129,0]An[3190,13,3191,2370,4361,129,0]An[3190,13,3191,2370,4814,129,0]An[3190,13,3191,2370,4373,129,0]An[3190,13,3191,1809,5304,129,0]An[3190,13,3191,4403,5272,129,0]An[3190,13,3191,1434,4621,129,0]An[3190,13,3191,1434,4239,129,0]An[3190,13,3191,1434,4491,129,0]An[3190,13,3191,1866,1489,129,0]An[3190,13,3191,1866,5085,129,0]An[3190,13,3191,3155,1186,129,0]An[3190,13,3191,3164,4916,129,0]An[3190,13,3191,1407,1472,129,0]An[3190,13,3191,1407,4254,129,0]An[3190,13,3191,1407,3335,129,0]An[3190,13,3191,1407,3134,129,0]An[3190,13,3191,1407,4914,129,0]An[3190,13,3191,1407,4970,129,0]An[3190,13,3191,1407,3309,129,0]An[3190,13,3191,1422,2360,129,0]An[3190,13,3191,1422,3320,129,0]An[3190,13,3191,1422,3136,129,0]An[3190,13,3191,1422,4605,129,0]An[3190,13,3191,1153,3139,129,0]An[3190,13,3191,1153,4390,129,0]An[3190,13,3191,1158,1924,129,0]An[3190,13,3191,1158,3360,129,0]An[3190,13,3191,1158,4079,129,0]An[3190,13,3191,2058,4098,129,0]An[3190,13,3191,2058,4501,129,0]An[3190,13,3191,2058,4214,129,0]An[3190,13,3191,3758,4601,129,0]An[3190,13,3191,3758,3130,129,0]An[3190,13,3191,3758,4467,129,0]An[3190,13,3191,3758,5173,129,0]An[3190,13,3191,3758,1207,129,0]An[3190,13,3191,1149,4615,129,0]An[3190,13,3191,3292,4624,129,0]An[3190,13,3191,3292,4940,129,0]An[3190,13,3191,3292,4955,129,0]An[3190,13,3191,3292,4081,129,0]An[3190,13,3191,1744,4477,129,0]An[3190,13,3191,1744,4260,129,0]An[3190,13,3191,1744,5142,129,0]An[3190,13,3191,1744,4206,129,0]An[3190,13,3191,1744,4071,129,0]An[3190,13,3191,1397,3117,129,0]An[3190,13,3191,1397,4482,129,0]An[3190,13,3191,1397,3326,129,0]An[3190,13,3191,1397,3349,129,0]An[3190,13,3191,1397,4221,129,0]An[3190,13,3191,1397,4599,129,0]An[3190,13,3191,1397,4807,129,0]An[3190,13,3191,1397,2318,129,0]An[3190,13,3191,2370,4811,129,0]An[3190,13,3191,2370,2329,129,0]An[3190,13,3191,2370,4217,129,0]An[3190,13,3191,2370,4065,129,0]An[3190,13,3191,2370,3343,129,0]An[3190,13,3191,1809,4385,129,0]An[3190,13,3191,1809,4613,129,0]An[3190,13,3191,1809,5177,129,0]An[3190,13,3191,1809,2332,129,0]An[3190,13,3191,1809,4911,129,0]An[3190,13,3191,1839,4603,129,0]An[3190,13,3191,1839,4362,129,0]An[3190,13,3191,1839,4220,129,0]An[3190,13,3191,1839,4225,129,0]Ak[3190,13,3191,0,4505,129,0]Ak[3190,13,3191,0,4975,129,0]An[3190,13,3191,4403,3323,129,0]An[3190,13,3191,4403,5277,129,0]An[3190,13,3191,5010,4840,129,0]Am[3190,13,3191,3191,527,129,0]Am[3190,13,3191,3191,135,129,0]An[3190,13,3191,3191,2347,129,0]An[3190,13,3191,3191,2357,129,0]An[3190,13,3191,3191,3115,129,0]An[3190,13,1742,1742,4396,129,0]An[3190,13,5126,5126,5022,129,0]An[3190,13,5126,5126,1204,129,0]An[3190,13,1747,1747,1892,129,0]Am[3190,13,1747,1747,515,129,0]An[3190,13,1747,1747,5241,129,0]An[3190,13,1747,1747,4074,129,0]An[3190,13,1747,1747,4981,129,0]An[3190,13,1829,1829,3355,129,0]An[3190,13,1829,1829,4366,129,0]Ak[3190,13,1829,0,1203,129,0]An[3190,13,1829,1829,4836,129,0]An[3190,13,1829,1829,5079,129,0]An[3190,13,1829,1829,4838,129,0]Al[3190,13,3753,535,502,129,0]Am[3190,13,3753,535,3303,129,0]An[3190,13,3753,1393,1190,129,0]An[3190,13,3753,1393,4076,129,0]An[3190,13,3753,2423,1897,129,0]An[3190,13,3753,2423,4383,129,0]An[3190,13,3753,3753,4903,129,0]An[3190,13,4871,4871,1205,129,0]An[3190,13,4871,4871,1208,129,0]An[2043,13,2044,2044,3358,129,0]Am[2043,13,2044,432,4506,129,0]An[2043,13,2044,2044,2344,129,0]An[2043,13,2044,2044,2358,129,0]An[2043,13,2044,2044,4226,129,0]An[1128,13,1129,1129,3104,129,0]An[1128,13,1129,1129,5014,129,0]Am[1128,13,1736,1736,143,129,0]An[1128,13,1423,1423,1223,129,0]An[1128,13,1423,1423,4100,129,0]An[1128,13,1423,1423,4609,129,0]An[1128,13,4320,4320,5226,129,0]An[1128,13,4320,4320,5227,129,0]Am[1128,13,1834,1834,144,129,0]Aj[477,13,478,3757,5087,0,0]Aj[477,13,478,1127,4507,0,0]Ag[477,13,478,0,4204,0,0]Al[3190,13,3191,1744,4206,0,0]Al[3190,14,3191,1809,5177,0,0]Ai[3190,14,4910,0,4976,0,0]Ah[3190,14,4612,0,516,0,0]Ai[3190,14,4612,0,1217,0,0]Af[477,5,478,3757,0,0,0]Al[1229,15,1230,1230,4067,0,0]Ae[2423,6,2424,0,0,0,0]Ag[477,15,478,0,3116,0,0]Al[3190,15,3191,1397,3117,0,0]Ah[3190,5,3191,1397,0,0,0]Aj[477,15,478,3757,4915,0,0]Al[3190,15,3191,3164,4916,0,0]Af[477,5,478,2369,0,0,0]Ah[3190,5,3191,2370,0,0,0]Al[3190,15,3191,2058,4098,0,0]Af[477,5,478,2401,0,0,0]2Ah[3190,6,3191,1809,0,0,0]Ag[477,19,478,0,1488,0,0]Al[3190,19,3191,1866,1489,0,0]Ag[477,19,478,0,4974,0,0]Ai[3190,19,3191,0,4975,0,0]=Ah[477,12,1746,0,4259,0,0]>Ak[477,13,1828,0,4837,4259,0]>Al[3190,12,1747,1747,4261,0,0]>Ao[3190,13,1829,1829,4838,4261,0]Ao[1128,13,4320,4320,5227,4261,0]An[2423,13,2424,2424,133,4082,0]Ao[2423,13,2424,2424,2315,4082,0]Ag[477,14,478,0,3135,0,0]Ag[477,14,478,0,4604,0,0]Al[3190,14,3191,1422,3136,0,0]Al[3190,14,3191,1422,4605,0,0]Ag[477,13,478,0,4258,0,0]Al[3190,13,3191,1744,4260,0,0]Aj[477,13,478,2050,4464,0,0]0Ak[2043,13,2044,432,4506,0,0]Al[3190,13,3191,3758,3314,0,0]Al[1128,13,1423,1423,4609,0,0]Ae[1128,2,1129,0,0,0,0]Aj[477,13,478,2401,2328,0,0]Al[3190,13,3191,2370,2329,0,0]Ag[477,14,478,0,3319,0,0]Al[3190,14,3191,1422,3320,0,0]6Ag[477,13,478,0,4623,0,0]Al[3190,13,3191,3292,4624,0,0]32Ak[1229,14,1230,1230,142,0,0]Ag[477,14,478,0,2356,0,0]Al[3190,14,3191,3191,2357,0,0]Al[2043,14,2044,2044,2358,0,0]Ah[477,14,3752,0,4902,0,0]Al[3190,14,3753,3753,4903,0,0]Al[3190,13,3191,2058,1917,0,0]Al[3190,13,3191,4403,5272,0,0]Al[3190,13,3191,4403,5277,0,0]Aj[477,13,478,4178,3345,0,0]Al[3190,13,3191,2058,3346,0,0]>=Al[1229,14,1421,1421,1222,0,0]Ai[536,14,537,537,4068,0,0]Ah[477,13,1828,0,4835,0,0]Ah[477,13,1828,0,4837,0,0]Al[3190,13,1829,1829,4836,0,0]Al[3190,13,1829,1829,4838,0,0]76Ah[477,15,1828,0,1202,0,0]Ai[3190,15,1829,0,1203,0,0]Af[477,5,478,4633,0,0,0]Ah[3190,5,3191,4633,0,0,0]Ag[477,15,478,0,4600,0,0]Ag[477,15,478,0,4216,0,0]Al[3190,15,3191,3758,4601,0,0]Al[3190,15,3191,2370,4217,0,0]Ah[3190,6,3191,2370,0,0,0]Ah[3190,5,3191,1839,0,0,0]Af[477,5,478,2369,0,0,0]Aj[477,15,478,4173,4499,0,0]Ag[477,5,1828,1829,0,0,0]Ae[3190,5,1829,0,0,0,0]Ag[477,15,478,0,3116,0,0]Al[3190,15,3191,1397,3117,0,0]Ag[477,19,478,0,4974,0,0]Ai[3190,19,3191,0,4975,0,0]Ah[3190,5,3191,2370,0,0,0]Al[3190,15,3191,1809,4911,0,0]?=5454Al[3190,15,3191,2370,4361,0,0]Ag[477,5,3752,2423,0,0,0]Ah[3190,5,3753,2423,0,0,0]Ah[3190,5,3191,1809,0,0,0]98Ah[3190,6,3191,1153,0,0,0]:9Af[477,5,478,1744,0,0,0]Ah[3190,5,3191,1744,0,0,0]Ae[3190,5,1742,0,0,0,0]Al[3190,13,3191,3758,3314,0,0]Ag[477,14,478,0,3319,0,0]Al[3190,14,3191,1422,3320,0,0]Al[1128,14,1129,1129,3104,0,0]Ae[3190,2,3191,0,0,0,0]Ag[477,13,478,0,4476,0,0]Al[3190,13,3191,1744,4477,0,0]Aj[477,12,478,2050,4464,0,0]Ab[4406,3,0,0,0,0,0]Ak[1229,14,1230,1230,142,0,0]Al[1229,14,1421,1421,4099,0,0]98Aj[477,13,478,2401,3342,0,0]Al[3190,13,3191,2370,3343,0,0]Ag[477,13,478,0,4939,0,0]Al[3190,13,3191,3292,4940,0,0];==;:Ak[1229,14,1230,1230,142,0,0]21Al[1128,14,1129,1129,3104,0,0]Aj[477,13,478,4173,3339,0,0]Aj[477,13,478,4173,5023,0,0]Al[3190,13,3191,2058,3340,0,0]Aj[477,13,478,3757,3311,0,0]Aj[477,13,478,3757,3313,0,0]Aj[477,13,478,2401,2328,0,0]Ai[477,13,478,1127,520,0,0]Al[3190,13,3191,2370,2329,0,0]=43Ae[4406,7,4407,0,0,0,0]6Al[1128,14,1129,1129,3104,0,0]70Ae[1229,7,1230,0,0,0,0]Ag[477,13,478,0,4258,0,0]Ag[477,13,478,0,4204,0,0]Al[3190,13,3191,1744,4260,0,0]Al[3190,13,3191,1744,4206,0,0]Ag[477,14,478,0,4258,0,0]Al[3190,14,3191,1744,4260,0,0]53Aj[477,13,478,4178,3345,0,0]Ag[477,19,478,0,4974,0,0]Ai[3190,19,3191,0,4975,0,0]Ag[477,15,478,0,4600,0,0]Al[3190,15,3191,1407,4914,0,0]Al[3190,15,3191,3758,4601,0,0]Ah[477,10,3752,3753,0,0,0]Af[3190,10,3753,0,0,0,0]Ag[477,5,3752,3753,0,0,0]Ae[3190,5,3753,0,0,0,0]Ah[477,10,1746,1747,0,0,0]Af[3190,10,1747,0,0,0,0]Ag[477,15,478,0,4216,0,0]Al[3190,15,3191,2370,4217,0,0]Ag[477,10,478,3758,0,0,0]Ai[3190,10,3191,3758,0,0,0]Ah[3190,6,3191,1809,0,0,0]Ah[3190,6,3191,1407,0,0,0]Af[477,6,478,3757,0,0,0]Ah[3190,6,3191,3164,0,0,0]Ag[477,15,478,0,3116,0,0]Al[3190,15,3191,1397,3117,0,0]10Aj[477,15,478,3757,4915,0,0]Al[3190,15,3191,3164,4916,0,0]Ag[477,14,478,0,3135,0,0]Al[3190,14,3191,1422,3136,0,0]Ah[477,14,3752,0,4902,0,0]Al[3190,14,3753,3753,4903,0,0]Aj[477,13,478,3757,3313,0,0]Ag[477,14,478,0,3319,0,0]Al[3190,14,3191,1422,3320,0,0]10Al[3190,13,3191,3164,3107,0,0]Aj[477,13,478,2369,2324,0,0]Al[3190,13,3191,2370,2325,0,0]Ab[4934,3,0,0,0,0,0]Ao[1128,13,1129,1129,5014,3144,0]Ag[477,19,478,0,4974,0,0]Ai[3190,19,3191,0,4975,0,0]Af[477,5,478,3292,0,0,0]Ah[3190,5,3191,3292,0,0,0]Ah[477,15,1828,0,1202,0,0]Ai[3190,15,1829,0,1203,0,0]54Ai[3190,10,3191,1809,0,0,0]Al[3190,15,3191,3758,1207,0,0]7600Al[3190,15,3191,1153,4390,0,0]4365Al[3190,15,4871,4871,1205,0,0]Ae[1128,9,1129,0,0,0,0]:9Ag[477,15,478,0,3116,0,0]Al[3190,15,3191,1397,3117,0,0]<;<;Af[477,6,478,1407,0,0,0]Ah[3190,6,3191,1407,0,0,0]32Al[1229,15,1230,1230,4091,0,0]Ac[477,5,478,0,0,0,0]Ae[3190,5,3191,0,0,0,0]Ag[477,15,478,0,4216,0,0]Ag[477,5,1746,1747,0,0,0]Al[3190,15,3191,2370,4217,0,0]Al[3190,15,3191,1809,4911,0,0]Ae[3190,5,1747,0,0,0,0]An[1229,13,1230,1230,142,4468,0]An[2423,13,2424,2424,133,4468,0]Ao[1128,13,1129,1129,3104,4468,0]An[1128,13,1736,1736,143,4468,0]An[1128,13,1834,1834,144,4468,0]Ag[477,13,478,0,4623,0,0]Al[3190,13,3191,3292,4624,0,0]Ag[477,13,478,0,1488,0,0]Al[3190,13,3191,1866,1489,0,0]Aj[477,14,478,2369,4363,0,0]Aj[477,13,478,3757,5087,0,0]Ag[477,13,478,0,4476,0,0]Al[3190,13,3191,1744,4477,0,0]Ag[477,13,478,0,4354,0,0]Al[3190,13,3191,4633,4355,0,0]Ag[477,14,478,0,3319,0,0]Al[3190,14,3191,1422,3320,0,0]7Al[3190,13,3191,3758,3314,0,0]Aj[477,13,478,3757,3313,0,0]Ah[477,13,1828,0,4835,0,0]Ah[477,13,1828,0,4837,0,0]Al[3190,13,1829,1829,4836,0,0]Al[3190,13,1829,1829,4838,0,0]45;:Ao[4105,13,4106,4106,5178,4968,0]Ag[477,13,478,0,4614,0,0]Al[3190,13,3191,1149,4615,0,0]Ae[1128,6,1129,0,0,0,0]Af[477,5,478,4286,0,0,0]Ag[477,15,478,0,3116,0,0]Al[3190,15,3191,1397,3117,0,0]10Ag[477,15,478,0,3129,0,0]Al[3190,15,3191,3758,3130,0,0]Ae[3190,5,5126,0,0,0,0]Af[477,5,478,4173,0,0,0]Ah[477,15,1828,0,1202,0,0]Ai[3190,15,1829,0,1203,0,0]Al[3190,15,4871,4871,1205,0,0]Aj[477,14,478,2369,4363,0,0]Ai[477,13,478,0,526,3096,0]Ai[477,13,478,0,134,3096,0]An[3190,13,3191,3191,527,3096,0]An[3190,13,3191,3191,135,3096,0]Ao[2423,13,2424,2424,2315,3350,0]Ag[477,13,478,0,4954,0,0]Al[3190,13,3191,3292,4955,0,0]Ah[477,12,1746,0,1909,0,0]Ak[477,13,1828,0,3354,1909,0]Al[3190,12,1747,1747,1910,0,0]Ao[3190,13,1829,1829,3355,1910,0]Aj[477,13,478,3757,3313,0,0]Ag[477,12,478,0,4908,0,0]Al[3190,12,3191,3758,4909,0,0]Ag[477,13,478,0,4623,0,0]Al[3190,13,3191,3292,4624,0,0]Ag[477,14,478,0,3135,0,0]Al[3190,14,3191,1422,3136,0,0]Ah[477,12,3752,0,3315,0,0]Aj[477,13,3752,0,501,3315,0]Ak[477,13,3752,0,1189,3315,0]Ak[477,13,3752,0,1896,3315,0]Al[3190,12,3753,3753,3316,0,0]Am[3190,13,3753,535,502,3316,0]Ao[3190,13,3753,1393,1190,3316,0]Ao[3190,13,3753,2423,1897,3316,0]Aj[477,14,478,2369,4363,0,0]Al[3190,13,3191,2058,1917,0,0]Ah[3190,2,4401,4177,0,0,0]Ae[1229,7,1230,0,0,0,0]==Ai[477,13,478,1127,520,0,0]Ak[2043,13,2044,432,4506,0,0]032Ah[477,12,1746,0,1909,0,0]Ak[477,13,1828,0,3354,1909,0]Ae[3190,2,3191,0,0,0,0]Al[3190,12,1747,1747,1910,0,0]Ao[3190,13,1829,1829,3355,1910,0]Ag[477,10,478,4403,0,0,0]Ai[3190,10,3191,4403,0,0,0]Ah[477,15,1828,0,1202,0,0]Ai[3190,15,1829,0,1203,0,0]Ag[477,15,478,0,4216,0,0]Al[3190,15,3191,2370,4217,0,0]Ag[477,19,478,0,4974,0,0]Ai[3190,19,3191,0,4975,0,0]Af[477,5,478,1744,0,0,0]Ah[3190,5,3191,1744,0,0,0]Ah[477,12,1746,0,3126,0,0]Al[3190,12,1747,1747,3127,0,0]Aj[477,13,478,2369,2324,0,0]Al[3190,13,3191,3758,3314,0,0]Al[3190,13,3191,2370,2325,0,0]Al[3190,13,3191,3191,3115,0,0]Ah[477,13,3752,0,4382,0,0]Al[3190,13,3753,2423,4383,0,0]Aj[477,13,478,2050,3142,0,0]0Aj[477,13,478,3757,3313,0,0]6Ag[477,14,478,0,3319,0,0]Al[3190,14,3191,1422,3320,0,0]Ag[477,13,478,0,4623,0,0]Al[3190,13,3191,3292,4624,0,0]Al[1128,14,1129,1129,3104,0,0]Ag[477,19,478,0,4974,0,0]Ai[3190,19,3191,0,4975,0,0]Al[3190,15,4871,4871,1205,0,0]Ah[477,15,1828,0,1202,0,0]Ai[3190,15,1829,0,1203,0,0]10Ag[477,15,478,0,4969,0,0]Ag[477,15,478,0,3129,0,0]Ag[477,15,478,0,4466,0,0]Al[3190,15,3191,1407,4970,0,0]Al[3190,15,3191,3758,3130,0,0]Al[3190,15,3191,3758,4467,0,0]Ah[3190,5,3191,3758,0,0,0]Al[3190,15,3191,1407,4914,0,0]Ag[477,15,478,0,3116,0,0]Al[3190,15,3191,1397,3117,0,0]Ah[3190,5,3191,1809,0,0,0]Ae[4105,5,4106,0,0,0,0]Al[1128,13,1129,1129,3104,0,0]Aj[477,13,478,3757,3313,0,0]Ah[477,12,1746,0,1909,0,0]Ak[477,13,1828,0,3354,1909,0]Al[3190,12,1747,1747,1910,0,0]Ao[3190,13,1829,1829,3355,1910,0]Ah[3190,7,5057,5056,0,0,0]0Al[3190,13,3191,3758,3314,0,0]6Ai[477,13,478,1127,520,0,0]Ag[477,14,478,0,3319,0,0]Al[3190,14,3191,1422,3320,0,0]Ao[4105,13,4106,4106,5178,4968,0]0Ag[477,13,478,0,4204,0,0]Al[3190,13,3191,1744,4206,0,0]Ah[477,15,1828,0,1202,0,0]Ai[3190,15,1829,0,1203,0,0]10Ag[477,13,478,0,4939,0,0]Al[3190,13,3191,3292,4940,0,0]Am[1229,13,1230,1230,142,499,0]Am[2423,13,2424,2424,133,499,0]An[1128,13,1129,1129,3104,499,0]Am[1128,13,1736,1736,143,499,0]Am[1128,13,1834,1834,144,499,0]??Ah[477,13,3752,0,3315,0,0]Aj[477,13,3752,0,501,3315,0]Al[3190,13,3753,3753,3316,0,0]Am[3190,13,3753,535,502,3316,0]Ai[477,13,478,1127,520,0,0]An[1229,13,1230,1230,142,4478,0]An[2423,13,2424,2424,133,4478,0]Ao[2423,13,2424,2424,2315,4478,0]Ao[1128,13,1129,1129,3104,4478,0]An[1128,13,1736,1736,143,4478,0]An[1128,13,1834,1834,144,4478,0]Ag[477,13,478,0,4258,0,0]Al[3190,13,3191,1744,4260,0,0]Ag[477,19,478,0,4974,0,0]Ai[3190,19,3191,0,4975,0,0]Ae[1128,5,4320,0,0,0,0]0Ag[477,13,478,0,4476,0,0]Al[3190,13,3191,1744,4477,0,0]Ag[477,13,478,0,4204,0,0]Al[3190,13,3191,1744,4206,0,0]Aj[477,13,478,2369,2324,0,0]Aj[477,13,478,2401,2328,0,0]Al[3190,13,3191,2370,2329,0,0]Al[3190,13,3191,3758,3314,0,0]Ao[4105,13,4106,4106,5178,4968,0];:Ag[477,5,1746,1747,0,0,0]Ae[3190,5,1747,0,0,0,0]Ah[477,15,1828,0,1202,0,0]Ai[3190,15,1829,0,1203,0,0]?>?>Aj[477,13,478,3757,3313,0,0]6Ag[477,13,478,0,4623,0,0]Al[3190,13,3191,3292,4624,0,0]Ae[4105,7,4106,0,0,0,0]Ae[4406,7,4407,0,0,0,0]Ag[477,14,478,0,3319,0,0]Al[3190,14,3191,1422,3320,0,0]?Ah[477,12,1746,0,4259,0,0]>Ak[477,13,1828,0,4836,4259,0]>Al[3190,12,1747,1747,4261,0,0]>Ao[3190,13,1829,1829,4837,4261,0]Ao[1128,13,4320,4320,5226,4261,0]An[2423,13,2424,2424,133,4082,0]Ao[2423,13,2424,2424,2315,4082,0]Ag[477,14,478,0,3135,0,0]Ag[477,14,478,0,4603,0,0]Al[3190,14,3191,1422,3136,0,0]Al[3190,14,3191,1422,4604,0,0]Ag[477,13,478,0,4258,0,0]Al[3190,13,3191,1744,4260,0,0]Aj[477,13,478,2050,4464,0,0]0Ak[2043,13,2044,432,4506,0,0]Al[3190,13,3191,3758,3314,0,0]Al[1128,13,1423,1423,4608,0,0]Ae[1128,2,1129,0,0,0,0]Aj[477,13,478,2401,2328,0,0]Al[3190,13,3191,2370,2329,0,0]Ag[477,14,478,0,3319,0,0]Al[3190,14,3191,1422,3320,0,0]6Ag[477,13,478,0,4622,0,0]Al[3190,13,3191,3292,4623,0,0]32Ak[1229,14,1230,1230,142,0,0]Ag[477,14,478,0,2356,0,0]Al[3190,14,3191,3191,2357,0,0]Al[2043,14,2044,2044,2358,0,0]Ah[477,14,3752,0,4901,0,0]Al[3190,14,3753,3753,4902,0,0]Al[3190,13,3191,2058,1917,0,0]Al[3190,13,3191,4403,5271,0,0]Al[3190,13,3191,4403,5276,0,0]Aj[477,13,478,4178,3345,0,0]Al[3190,13,3191,2058,3346,0,0]>=Al[1229,14,1421,1421,1222,0,0]Ai[536,14,537,537,4068,0,0]Ah[477,13,1828,0,4834,0,0]Ah[477,13,1828,0,4836,0,0]Al[3190,13,1829,1829,4835,0,0]Al[3190,13,1829,1829,4837,0,0]76Ah[477,15,1828,0,1202,0,0]Ai[3190,15,1829,0,1203,0,0]Af[477,5,478,4632,0,0,0]Ah[3190,5,3191,4632,0,0,0]Ag[477,15,478,0,4599,0,0]Ag[477,15,478,0,4216,0,0]Al[3190,15,3191,3758,4600,0,0]Al[3190,15,3191,2370,4217,0,0]Ah[3190,6,3191,2370,0,0,0]Ah[3190,5,3191,1839,0,0,0]Af[477,5,478,2369,0,0,0]Aj[477,15,478,4173,4499,0,0]Ag[477,5,1828,1829,0,0,0]Ae[3190,5,1829,0,0,0,0]Ag[477,15,478,0,3116,0,0]Al[3190,15,3191,1397,3117,0,0]Ag[477,19,478,0,4973,0,0]Ai[3190,19,3191,0,4974,0,0]Ah[3190,5,3191,2370,0,0,0]Al[3190,15,3191,1809,4910,0,0]?=5454Al[3190,15,3191,2370,4361,0,0]Ag[477,5,3752,2423,0,0,0]Ah[3190,5,3753,2423,0,0,0]Ah[3190,5,3191,1809,0,0,0]98Ah[3190,6,3191,1153,0,0,0]:9Af[477,5,478,1744,0,0,0]Ah[3190,5,3191,1744,0,0,0]Ae[3190,5,1742,0,0,0,0]Al[3190,13,3191,3758,3314,0,0]Ag[477,14,478,0,3319,0,0]Al[3190,14,3191,1422,3320,0,0]Al[1128,14,1129,1129,3104,0,0]Ae[3190,2,3191,0,0,0,0]Ag[477,13,478,0,4476,0,0]Al[3190,13,3191,1744,4477,0,0]Aj[477,12,478,2050,4464,0,0]Ab[4406,3,0,0,0,0,0]Ak[1229,14,1230,1230,142,0,0]Al[1229,14,1421,1421,4099,0,0]98Aj[477,13,478,2401,3342,0,0]Al[3190,13,3191,2370,3343,0,0]Ag[477,13,478,0,4938,0,0]Al[3190,13,3191,3292,4939,0,0];==;:Ak[1229,14,1230,1230,142,0,0]21Al[1128,14,1129,1129,3104,0,0]Aj[477,13,478,4173,3339,0,0]Aj[477,13,478,4173,5022,0,0]Al[3190,13,3191,2058,3340,0,0]Aj[477,13,478,3757,3311,0,0]Aj[477,13,478,2401,2328,0,0]Ai[477,13,478,1127,520,0,0]Al[3190,13,3191,2370,2329,0,0]<;Ag[477,13,478,0,1488,0,0]Ag[477,13,478,0,1471,0,0]Al[3190,13,3191,1866,1489,0,0]Al[3190,13,3191,1407,1472,0,0]20Ae[1229,7,1421,0,0,0,0]?>Al[1229,14,1421,1421,1222,0,0]Ao[1128,13,1129,1129,5013,3144,0]Ag[477,15,478,0,3116,0,0]Af[477,5,478,1397,0,0,0]Al[3190,15,3191,1397,3117,0,0]Ah[3190,5,3191,1397,0,0,0]Af[477,6,478,3758,0,0,0]Ah[3190,6,3191,3758,0,0,0]Al[3190,15,3191,2370,4361,0,0]Ah[3190,5,3191,1839,0,0,0]Af[477,5,478,1422,0,0,0]Ah[3190,5,3191,1422,0,0,0]97Ae[1128,5,1423,0,0,0,0]:8Ah[3190,6,3191,1809,0,0,0]Af[477,5,478,1149,0,0,0]Ah[3190,5,3191,1149,0,0,0]Al[3190,15,3191,1407,4913,0,0]Af[477,5,478,1434,0,0,0]Ah[3190,5,3191,1434,0,0,0]Af[477,5,478,3292,0,0,0]Ah[3190,5,3191,3292,0,0,0]Ag[477,15,478,0,4599,0,0]Al[3190,15,3191,3758,4600,0,0]Af[477,5,478,3757,0,0,0]Aj[477,15,478,4173,3138,0,0]Aj[477,15,478,4173,3341,0,0]Aj[477,15,478,4178,3359,0,0]Aj[477,15,478,4178,4264,0,0]An[1229,13,1230,1230,142,4360,0]Ao[1229,13,1230,1230,4091,4360,0]Ao[1229,13,1230,1230,4067,4360,0]Am[477,13,478,3757,3313,4360,0]Am[477,13,478,3757,1454,4360,0]Am[477,13,478,3757,5086,4360,0]Am[477,13,478,3757,4914,4360,0]Am[477,13,478,2369,2324,4360,0]Am[477,13,478,2369,4363,4360,0]Am[477,13,478,4286,1462,4360,0]Al[477,13,478,1127,520,4360,0]Am[477,13,478,1127,4507,4360,0]Am[477,13,478,4173,4500,4360,0]Am[477,13,478,4173,3138,4360,0]Am[477,13,478,4173,3341,4360,0]Am[477,13,478,4173,4499,4360,0]Am[477,13,478,4178,3359,4360,0]Am[477,13,478,4178,4264,4360,0]Aj[477,13,478,0,1488,4360,0]Aj[477,13,478,0,5083,4360,0]Aj[477,13,478,0,1471,4360,0]Aj[477,13,478,0,3334,4360,0]Aj[477,13,478,0,3133,4360,0]Aj[477,13,478,0,4968,4360,0]Aj[477,13,478,0,3306,4360,0]Aj[477,13,478,0,2359,4360,0]Aj[477,13,478,0,3319,4360,0]Aj[477,13,478,0,3135,4360,0]Aj[477,13,478,0,4603,4360,0]Aj[477,13,478,0,1923,4360,0]Aj[477,13,478,0,4210,4360,0]Aj[477,13,478,0,4599,4360,0]Aj[477,13,478,0,3129,4360,0]Aj[477,13,478,0,4466,4360,0]Aj[477,13,478,0,4622,4360,0]Aj[477,13,478,0,4938,4360,0]Aj[477,13,478,0,4953,4360,0]Aj[477,13,478,0,4080,4360,0]Aj[477,13,478,0,4476,4360,0]Aj[477,13,478,0,4258,4360,0]Aj[477,13,478,0,5140,4360,0]Aj[477,13,478,0,4204,4360,0]Aj[477,13,478,0,4070,4360,0]Aj[477,13,478,0,3116,4360,0]Aj[477,13,478,0,4596,4360,0]Aj[477,13,478,0,4804,4360,0]Aj[477,13,478,0,4216,4360,0]Aj[477,13,478,0,4219,4360,0]Aj[477,13,478,0,4224,4360,0]Aj[477,13,478,0,4973,4360,0]Aj[477,13,478,0,3322,4360,0]Ai[477,13,478,0,526,4360,0]Ai[477,13,478,0,134,4360,0]Aj[477,13,478,0,2346,4360,0]Aj[477,13,3752,0,501,4360,0]Ak[477,13,3752,0,3302,4360,0]Ak[477,13,3752,0,1189,4360,0]Ak[477,13,3752,0,4075,4360,0]Ak[477,13,3752,0,1896,4360,0]Ak[477,13,3752,0,4382,4360,0]Ak[477,13,1828,0,1202,4360,0]Ak[477,13,1828,0,4834,4360,0]Ak[477,13,1828,0,5077,4360,0]Ak[477,13,1828,0,4836,4360,0]Ao[3190,13,3191,1866,1489,4360,0]Ao[3190,13,3191,1866,5084,4360,0]Ao[3190,13,3191,3164,4915,4360,0]Ao[3190,13,3191,1407,1472,4360,0]Ao[3190,13,3191,1407,4254,4360,0]Ao[3190,13,3191,1407,3335,4360,0]Ao[3190,13,3191,1407,3134,4360,0]Ao[3190,13,3191,1407,4913,4360,0]Ao[3190,13,3191,1407,4969,4360,0]Ao[3190,13,3191,1407,3309,4360,0]Ao[3190,13,3191,1422,2360,4360,0]Ao[3190,13,3191,1422,3320,4360,0]Ao[3190,13,3191,1422,3136,4360,0]Ao[3190,13,3191,1422,4604,4360,0]Ao[3190,13,3191,1153,3139,4360,0]Ao[3190,13,3191,1153,4390,4360,0]Ao[3190,13,3191,1158,1924,4360,0]Ao[3190,13,3191,1158,3360,4360,0]Ao[3190,13,3191,1158,4079,4360,0]Ao[3190,13,3191,2058,4501,4360,0]Ao[3190,13,3191,2058,4214,4360,0]Ao[3190,13,3191,3758,4600,4360,0]Ao[3190,13,3191,3758,3130,4360,0]Ao[3190,13,3191,3758,4467,4360,0]Ao[3190,13,3191,3758,5172,4360,0]Ao[3190,13,3191,3292,4623,4360,0]Ao[3190,13,3191,3292,4939,4360,0]Ao[3190,13,3191,3292,4954,4360,0]Ao[3190,13,3191,3292,4081,4360,0]Ao[3190,13,3191,1744,4477,4360,0]Ao[3190,13,3191,1744,4260,4360,0]Ao[3190,13,3191,1744,5141,4360,0]Ao[3190,13,3191,1744,4206,4360,0]Ao[3190,13,3191,1744,4071,4360,0]Ao[3190,13,3191,1397,3117,4360,0]Ao[3190,13,3191,1397,3326,4360,0]Ao[3190,13,3191,1397,3349,4360,0]Ao[3190,13,3191,1397,4221,4360,0]Ao[3190,13,3191,1397,4598,4360,0]Ao[3190,13,3191,1397,4806,4360,0]Ao[3190,13,3191,1397,2318,4360,0]Ao[3190,13,3191,2370,4810,4360,0]Ao[3190,13,3191,2370,4217,4360,0]Ao[3190,13,3191,1809,4612,4360,0]Ao[3190,13,3191,1809,5176,4360,0]Ao[3190,13,3191,1809,2332,4360,0]Ao[3190,13,3191,1809,4910,4360,0]Ao[3190,13,3191,1839,4220,4360,0]Ao[3190,13,3191,1839,4225,4360,0]Al[3190,13,3191,0,4505,4360,0]Al[3190,13,3191,0,4974,4360,0]Ao[3190,13,3191,4403,3323,4360,0]Ao[3190,13,3191,4403,5276,4360,0]An[3190,13,3191,3191,527,4360,0]An[3190,13,3191,3191,135,4360,0]Ao[3190,13,3191,3191,2347,4360,0]Ao[3190,13,3191,3191,3115,4360,0]Ao[3190,13,1742,1742,4396,4360,0]Ao[3190,13,5125,5125,5021,4360,0]Ao[3190,13,5125,5125,1204,4360,0]Al[3190,13,1829,0,1203,4360,0]Ao[3190,13,1829,1829,4835,4360,0]Ao[3190,13,1829,1829,5078,4360,0]Ao[3190,13,1829,1829,4837,4360,0]Am[3190,13,3753,535,502,4360,0]An[3190,13,3753,535,3303,4360,0]Ao[3190,13,3753,1393,1190,4360,0]Ao[3190,13,3753,1393,4076,4360,0]Ao[3190,13,3753,2423,1897,4360,0]Ao[3190,13,3753,2423,4383,4360,0]Ao[3190,13,4870,4870,1208,4360,0]Ao[2043,13,2044,2044,2344,4360,0]Ao[2043,13,2044,2044,2358,4360,0]Ao[2043,13,2044,2044,4226,4360,0]Ao[1128,13,1129,1129,5013,4360,0]Ao[1128,13,1423,1423,4608,4360,0]Ao[1128,13,4320,4320,5225,4360,0]Ao[1128,13,4320,4320,5226,4360,0]Af[477,7,478,2050,0,0,0]Ag[477,13,478,0,4476,0,0]Al[3190,13,3191,1744,4477,0,0]Ah[477,12,1746,0,3126,0,0]Al[3190,12,1747,1747,3127,0,0]Al[2043,14,2044,2044,2344,0,0]Ag[477,14,478,0,3319,0,0]Al[3190,14,3191,1422,3320,0,0]10Ae[1128,7,1129,0,0,0,0]Aj[477,13,478,2369,2324,0,0]Ae[4406,7,4407,0,0,0,0]Ae[1229,7,1230,0,0,0,0]Al[3190,13,3191,3758,3314,0,0]Aj[477,13,478,3757,3313,0,0]1Al[3190,13,3191,2058,4501,0,0]Al[1229,13,1421,1421,1222,0,0]Al[1229,14,1421,1421,4099,0,0]Aj[477,12,478,2050,1908,0,0]Am[477,13,478,1435,4265,1908,0]Am[477,13,478,1170,4094,1908,0]Ah[477,12,1746,0,1909,0,0]Ak[477,13,1828,0,3354,1909,0]Al[3190,12,1747,1747,1910,0,0]Ao[3190,13,1829,1829,3355,1910,0]Af[477,5,478,1127,0,0,0]Ag[477,15,478,0,3116,0,0]Af[477,5,478,1397,0,0,0]Al[3190,15,3191,1397,3117,0,0]Ah[3190,5,3191,1397,0,0,0]Ag[477,15,478,0,4599,0,0]Al[3190,15,3191,3758,4600,0,0]Ah[3190,5,3191,2370,0,0,0]Al[3190,15,3191,3758,1207,0,0]Af[477,5,478,2370,0,0,0]2Ag[477,15,478,0,4216,0,0]Al[3190,15,3191,2370,4217,0,0]Ag[477,19,478,0,4973,0,0]Ai[3190,19,3191,0,4974,0,0]Ah[477,15,1828,0,1202,0,0]Ai[3190,15,1829,0,1203,0,0]32Ag[477,15,478,0,3129,0,0]Al[3190,15,3191,3758,3130,0,0]Ag[477,15,478,0,3116,0,0]?0?0?43Ag[477,5,1828,1829,0,0,0]Ae[3190,5,1829,0,0,0,0]10Af[477,5,478,5009,0,0,0]Ah[3190,5,3191,5009,0,0,0]Al[3190,15,4870,4870,1205,0,0]7Al[3190,15,3191,1407,4913,0,0]7Al[3190,15,3191,1809,4612,0,0]Ag[477,12,478,0,2352,0,0]Aj[477,13,478,0,4354,2352,0]Aj[477,13,478,0,4238,2352,0]Al[3190,12,3191,1434,2353,0,0]Ao[3190,13,3191,4632,4355,2353,0]Ao[3190,13,3191,1434,4239,2353,0]Ae[4105,7,4106,0,0,0,0]Aj[477,13,478,3757,3313,0,0]Al[3190,13,3191,3758,3314,0,0]Ag[477,14,478,0,3319,0,0]Al[3190,14,3191,1422,3320,0,0]32An[1229,13,1230,1230,142,4478,0]An[2423,13,2424,2424,133,4478,0]Ao[2423,13,2424,2424,2315,4478,0]Ao[1128,13,1129,1129,3104,4478,0]An[1128,13,1736,1736,143,4478,0]An[1128,13,1834,1834,144,4478,0]Ak[2043,13,2044,432,4506,0,0]Ag[477,13,478,0,4622,0,0]Al[3190,13,3191,3292,4623,0,0]:9Ae[3190,2,3191,0,0,0,0]Aj[477,13,478,2369,2324,0,0]Al[3190,13,3191,2370,2325,0,0]?>43Ae[4406,7,4407,0,0,0,0]6Al[1128,14,1129,1129,3104,0,0]70Ae[1229,7,1230,0,0,0,0]Ag[477,13,478,0,4258,0,0]Ag[477,13,478,0,4204,0,0]Al[3190,13,3191,1744,4260,0,0]Al[3190,13,3191,1744,4206,0,0]Ag[477,14,478,0,4258,0,0]Al[3190,14,3191,1744,4260,0,0]53Aj[477,13,478,4178,3345,0,0]Ag[477,19,478,0,4973,0,0]Ai[3190,19,3191,0,4974,0,0]Ag[477,15,478,0,4599,0,0]Al[3190,15,3191,1407,4913,0,0]Al[3190,15,3191,3758,4600,0,0]Ah[477,10,3752,3753,0,0,0]Af[3190,10,3753,0,0,0,0]Ag[477,5,3752,3753,0,0,0]Ae[3190,5,3753,0,0,0,0]Ah[477,10,1746,1747,0,0,0]Af[3190,10,1747,0,0,0,0]Ag[477,15,478,0,4216,0,0]Al[3190,15,3191,2370,4217,0,0]Ag[477,10,478,3758,0,0,0]Ai[3190,10,3191,3758,0,0,0]Ah[3190,6,3191,1809,0,0,0]Ah[3190,6,3191,1407,0,0,0]Af[477,6,478,3757,0,0,0]Ah[3190,6,3191,3164,0,0,0]Ag[477,15,478,0,3116,0,0]Al[3190,15,3191,1397,3117,0,0]10Aj[477,15,478,3757,4914,0,0]Al[3190,15,3191,3164,4915,0,0]Ag[477,14,478,0,3135,0,0]Al[3190,14,3191,1422,3136,0,0]Ah[477,14,3752,0,4901,0,0]Al[3190,14,3753,3753,4902,0,0]Aj[477,13,478,3757,3313,0,0]Ag[477,14,478,0,3319,0,0]Al[3190,14,3191,1422,3320,0,0]10Al[3190,13,3191,3164,3107,0,0]Aj[477,13,478,2369,2324,0,0]Al[3190,13,3191,2370,2325,0,0]Ab[4933,3,0,0,0,0,0]Ao[1128,13,1129,1129,5013,3144,0]Ag[477,19,478,0,4973,0,0]Ai[3190,19,3191,0,4974,0,0]Af[477,5,478,3292,0,0,0]Ah[3190,5,3191,3292,0,0,0]Ah[477,15,1828,0,1202,0,0]Ai[3190,15,1829,0,1203,0,0]54Ai[3190,10,3191,1809,0,0,0]Al[3190,15,3191,3758,1207,0,0]7600Al[3190,15,3191,1153,4390,0,0]4365Al[3190,15,4870,4870,1205,0,0]Ae[1128,9,1129,0,0,0,0]:9Ag[477,15,478,0,3116,0,0]Al[3190,15,3191,1397,3117,0,0]<;<;Af[477,6,478,1407,0,0,0]Ah[3190,6,3191,1407,0,0,0]32Al[1229,15,1230,1230,4091,0,0]Ac[477,5,478,0,0,0,0]Ae[3190,5,3191,0,0,0,0]Ag[477,15,478,0,4216,0,0]Ag[477,5,1746,1747,0,0,0]Al[3190,15,3191,2370,4217,0,0]Al[3190,15,3191,1809,4910,0,0]Ae[3190,5,1747,0,0,0,0]An[1229,13,1230,1230,142,4468,0]An[2423,13,2424,2424,133,4468,0]Ao[1128,13,1129,1129,3104,4468,0]An[1128,13,1736,1736,143,4468,0]An[1128,13,1834,1834,144,4468,0]Ag[477,13,478,0,4622,0,0]Al[3190,13,3191,3292,4623,0,0]Ag[477,13,478,0,1488,0,0]Al[3190,13,3191,1866,1489,0,0]Aj[477,14,478,2369,4363,0,0]Aj[477,13,478,3757,5086,0,0]Ag[477,13,478,0,4476,0,0]Al[3190,13,3191,1744,4477,0,0]Ag[477,13,478,0,4354,0,0]Al[3190,13,3191,4632,4355,0,0]Ag[477,14,478,0,3319,0,0]Al[3190,14,3191,1422,3320,0,0]7Al[3190,13,3191,3758,3314,0,0]Aj[477,13,478,3757,3313,0,0]Ah[477,13,1828,0,4834,0,0]Ah[477,13,1828,0,4836,0,0]Al[3190,13,1829,1829,4835,0,0]Al[3190,13,1829,1829,4837,0,0]45;:Ao[4105,13,4106,4106,5177,4967,0]Ag[477,13,478,0,4613,0,0]Al[3190,13,3191,1149,4614,0,0]Ae[1128,6,1129,0,0,0,0]Af[477,5,478,4286,0,0,0]Ag[477,15,478,0,3116,0,0]Al[3190,15,3191,1397,3117,0,0]10Ag[477,15,478,0,3129,0,0]Al[3190,15,3191,3758,3130,0,0]Ae[3190,5,5125,0,0,0,0]Af[477,5,478,4173,0,0,0]Ah[477,15,1828,0,1202,0,0]Ai[3190,15,1829,0,1203,0,0]Al[3190,15,4870,4870,1205,0,0]Aj[477,14,478,2369,4363,0,0]Ai[477,13,478,0,526,3096,0]Ai[477,13,478,0,134,3096,0]An[3190,13,3191,3191,527,3096,0]An[3190,13,3191,3191,135,3096,0]Ao[2423,13,2424,2424,2315,3350,0]Ag[477,13,478,0,4953,0,0]Al[3190,13,3191,3292,4954,0,0]Ah[477,12,1746,0,1909,0,0]Ak[477,13,1828,0,3354,1909,0]Al[3190,12,1747,1747,1910,0,0]Ao[3190,13,1829,1829,3355,1910,0]Aj[477,13,478,3757,3313,0,0]Ag[477,12,478,0,4907,0,0]Al[3190,12,3191,3758,4908,0,0]Ag[477,13,478,0,4622,0,0]Al[3190,13,3191,3292,4623,0,0]Ag[477,14,478,0,3135,0,0]Al[3190,14,3191,1422,3136,0,0]Ah[477,12,3752,0,3315,0,0]Aj[477,13,3752,0,501,3315,0]Ak[477,13,3752,0,1189,3315,0]Ak[477,13,3752,0,1896,3315,0]Al[3190,12,3753,3753,3316,0,0]Am[3190,13,3753,535,502,3316,0]Ao[3190,13,3753,1393,1190,3316,0]Ao[3190,13,3753,2423,1897,3316,0]Aj[477,14,478,2369,4363,0,0]Al[3190,13,3191,2058,1917,0,0]Ah[3190,2,4401,4177,0,0,0]Ae[1229,7,1230,0,0,0,0]==Ai[477,13,478,1127,520,0,0]Ak[2043,13,2044,432,4506,0,0]032Ah[477,12,1746,0,1909,0,0]Ak[477,13,1828,0,3354,1909,0]Ae[3190,2,3191,0,0,0,0]Al[3190,12,1747,1747,1910,0,0]Ao[3190,13,1829,1829,3355,1910,0]Ag[477,10,478,4403,0,0,0]Ai[3190,10,3191,4403,0,0,0]Ah[477,15,1828,0,1202,0,0]Ai[3190,15,1829,0,1203,0,0]Ag[477,15,478,0,4216,0,0]Al[3190,15,3191,2370,4217,0,0]Ag[477,19,478,0,4973,0,0]Ai[3190,19,3191,0,4974,0,0]Af[477,5,478,1744,0,0,0]Ah[3190,5,3191,1744,0,0,0]Ah[477,12,1746,0,3126,0,0]Al[3190,12,1747,1747,3127,0,0]Aj[477,13,478,2369,2324,0,0]Al[3190,13,3191,3758,3314,0,0]Al[3190,13,3191,2370,2325,0,0]Al[3190,13,3191,3191,3115,0,0]Ah[477,13,3752,0,4382,0,0]Al[3190,13,3753,2423,4383,0,0]Aj[477,13,478,2050,3142,0,0]0Aj[477,13,478,3757,3313,0,0]6Ag[477,14,478,0,3319,0,0]Al[3190,14,3191,1422,3320,0,0]Ag[477,13,478,0,4622,0,0]Al[3190,13,3191,3292,4623,0,0]Al[1128,14,1129,1129,3104,0,0]Ag[477,19,478,0,4973,0,0]Ai[3190,19,3191,0,4974,0,0]Al[3190,15,4870,4870,1205,0,0]Ah[477,15,1828,0,1202,0,0]Ai[3190,15,1829,0,1203,0,0]10Ag[477,15,478,0,4968,0,0]Ag[477,15,478,0,3129,0,0]Ag[477,15,478,0,4466,0,0]Al[3190,15,3191,1407,4969,0,0]Al[3190,15,3191,3758,3130,0,0]Al[3190,15,3191,3758,4467,0,0]Ah[3190,5,3191,3758,0,0,0]Al[3190,15,3191,1407,4913,0,0]Ag[477,15,478,0,3116,0,0]Al[3190,15,3191,1397,3117,0,0]Ah[3190,5,3191,1809,0,0,0]Ae[4105,5,4106,0,0,0,0]Al[1128,13,1129,1129,3104,0,0]Aj[477,13,478,3757,3313,0,0]Ah[477,12,1746,0,1909,0,0]Ak[477,13,1828,0,3354,1909,0]Al[3190,12,1747,1747,1910,0,0]Ao[3190,13,1829,1829,3355,1910,0]Ah[3190,7,5056,5055,0,0,0]0Al[3190,13,3191,3758,3314,0,0]6Ai[477,13,478,1127,520,0,0]Ag[477,14,478,0,3319,0,0]Al[3190,14,3191,1422,3320,0,0]Ao[4105,13,4106,4106,5177,4967,0]0Ag[477,13,478,0,4204,0,0]Al[3190,13,3191,1744,4206,0,0]Ah[477,15,1828,0,1202,0,0]Ai[3190,15,1829,0,1203,0,0]10Ag[477,13,478,0,4938,0,0]Al[3190,13,3191,3292,4939,0,0]Am[1229,13,1230,1230,142,499,0]Am[2423,13,2424,2424,133,499,0]An[1128,13,1129,1129,3104,499,0]Am[1128,13,1736,1736,143,499,0]Am[1128,13,1834,1834,144,499,0]??Ah[477,13,3752,0,3315,0,0]Aj[477,13,3752,0,501,3315,0]Al[3190,13,3753,3753,3316,0,0]Am[3190,13,3753,535,502,3316,0]Ai[477,13,478,1127,520,0,0]An[1229,13,1230,1230,142,4478,0]An[2423,13,2424,2424,133,4478,0]Ao[2423,13,2424,2424,2315,4478,0]Ao[1128,13,1129,1129,3104,4478,0]An[1128,13,1736,1736,143,4478,0]An[1128,13,1834,1834,144,4478,0]Ag[477,13,478,0,4258,0,0]Al[3190,13,3191,1744,4260,0,0]Ag[477,19,478,0,4973,0,0]Ai[3190,19,3191,0,4974,0,0]Ae[1128,5,4320,0,0,0,0]0Ag[477,13,478,0,4476,0,0]Al[3190,13,3191,1744,4477,0,0]Ag[477,13,478,0,4204,0,0]Al[3190,13,3191,1744,4206,0,0]Aj[477,13,478,2369,2324,0,0]Aj[477,13,478,2401,2328,0,0]Al[3190,13,3191,2370,2329,0,0]Al[3190,13,3191,3758,3314,0,0]Ao[4105,13,4106,4106,5177,4967,0];:Ag[477,5,1746,1747,0,0,0]Ae[3190,5,1747,0,0,0,0]Ah[477,15,1828,0,1202,0,0]Ai[3190,15,1829,0,1203,0,0]?>?>Aj[477,13,478,3757,3313,0,0]6Ag[477,13,478,0,4622,0,0]Al[3190,13,3191,3292,4623,0,0]Ae[4105,7,4106,0,0,0,0]Ae[4406,7,4407,0,0,0,0]Ag[477,14,478,0,3319,0,0]Al[3190,14,3191,1422,3320,0,0]?>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Cf[\"{{{A@d{B@Md}}}{{KHd{BELfc}}}{}}\",[\"TryFrom::Error\"]]?Bm[\"{B@Md{{KHd{BELfc}}}{}}\",[\"TryFrom::Error\"]]Ao[\"{c{{KHd{e}}}{}{}}\",[\"U\",\"T\"]]Bm[\"{BAD`{{KHd{BELjc}}}{}}\",[\"TryFrom::Error\"]]Cf[\"{{{A@d{BAD`}}}{{KHd{BELjc}}}{}}\",[\"TryFrom::Error\"]]222222222222222222222222Aj[\"{{}{{KHd{c}}}{}}\",[\"U\"]]0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Cd[\"{{{ABd{{AIAb{CGdc}}}}{AIAb{CGdNKb}}}DAh{}}\",[\"F\"]]000000000Cc[\"{{{AIAb{BCCd}}AJCl{AIAb{ABFl}}}{{KHf{ICl}}}}\",[]]Bk[\"{{{AIAb{BHAf}}B@Of}{{KHd{IClAIOd}}}}\",[]]Bk[\"{{{AIAb{BHAh}}B@Oh}{{KHd{IClAIOf}}}}\",[]]0Bk[\"{{{AIAb{BIBn}}B@Oh}{{KHd{IClAIOf}}}}\",[]]Ak[\"{{{AIAb{AINb}}}AB@n}\",[]]Bi[\"{{{AIAb{CGdBAEd}}}{{KHd{KJ`IFb}}}}\",[]]Bi[\"{{{AIAb{CGdBAEd}}}{{KHd{DAlIFb}}}}\",[]]Bi[\"{{{AIAb{CGdBAEd}}}{{KHd{A@lIFb}}}}\",[]]Bj[\"{{{AIAb{CGdBAEd}}}{{KHd{ABEdIFb}}}}\",[]]Bf[\"{{{AIAb{CGdBAEd}}}{{KHj{KJb}}}}\",[]]Bf[\"{{{AIAb{CGdBAEd}}}{{KHj{DAn}}}}\",[]]Bf[\"{{{AIAb{CGdBAEd}}}{{KHj{A@n}}}}\",[]]Bg[\"{{{AIAb{CGdBAEd}}}{{KHj{ABEf}}}}\",[]]Aa[\"{AOLfBGKd}\",[]]Bd[\"{{{AIAb{AINf}}}{{A@f{AINf}}}}\",[]]Bc[\"{{{AIAb{CNj}}}{{A@f{AINf}}}}\",[]]Bc[\"{{{AIAb{IDj}}}{{A@f{AINf}}}}\",[]]Bc[\"{{{AIAb{NM`}}}{{A@f{AINf}}}}\",[]]Bd[\"{{{AIAb{AINh}}}{{A@f{AINh}}}}\",[]]Bc[\"{{{AIAb{CNl}}}{{A@f{AINh}}}}\",[]]Bc[\"{{{AIAb{IDl}}}{{A@f{AINh}}}}\",[]]Bc[\"{{{AIAb{NMb}}}{{A@f{AINh}}}}\",[]]A`[\"{ABFnCNb}\",[]]An[\"{{{AIAb{CGdAINd}}}AHDf}\",[]]A`[\"{ABG`CNb}\",[]]C`[\"{{{AIAb{CGdB@Of}}}{{AIAb{CGd{KAb{Oj}}}}}}\",[]]C`[\"{{{AIAb{CGdB@Oh}}}{{AIAb{CGd{KAb{Oj}}}}}}\",[]]Ah[\"{Oj{{KGl{BA@`}}}}\",[]]Ah[\"{Oj{{KGl{BA@b}}}}\",[]]Ah[\"{Oj{{KGl{BA@d}}}}\",[]]A`[\"{ABEdDAl}\",[]]A`[\"{ABEfDAn}\",[]]Bi[\"{AIMn{{KHd{{KCh{AINbBDBf}}AIMn}}}}\",[]]Aa[\"{{}c{}}\",[\"I\"]]o[\"{DAlDC`}\",[]]o[\"{DAnDC`}\",[]]Aj[\"{{{AIAb{AINd}}}DC`}\",[]]21Aj[\"{{{AIAb{ABC`}}}DC`}\",[]]Aj[\"{{{AIAb{ABCb}}}DC`}\",[]]Bn[\"{{{AIAb{CGdBADd}}DC`}{{AIAb{CGdBADd}}}}\",[]]Bn[\"{{{AIAb{CGdBADh}}DC`}{{AIAb{CGdBADh}}}}\",[]]Bm[\"{{{AIAb{NNh}}NLd}{{ABd{{A@f{AILd}}}}}}\",[]]Bn[\"{{{AIAb{BAEb}}NLd}{{ABd{{A@f{AILd}}}}}}\",[]]Bn[\"{{{AIAb{AOOl}}NLd}{{ABd{{A@f{AILd}}}}}}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]Ak[\"{{{AIAb{AINb}}}ABDf}\",[]]Bc[\"{{{AIAb{AINb}}AJBhAJ@l}ABDh}\",[]]Ce[\"{{{AIAb{CGdAINd}}AHEfAJ@nNLd}{{KHd{KH`AHGd}}}}\",[]]43Bm[\"{{{AIAb{CGdAINd}}KH`}{{KHd{IClAIMj}}}}\",[]]E`[\"{{{ABd{{AIAb{CGdBGJn}}}}{AIAb{CGdNKb}}}{{DAh{{KGl{c}}}}}{}}\",[\"Stream::Item\"]]E`[\"{{{ABd{{AIAb{CGdBEHd}}}}{AIAb{CGdNKb}}}{{DAh{{KGl{c}}}}}{}}\",[\"Stream::Item\"]]E`[\"{{{ABd{{AIAb{CGdAJ@j}}}}{AIAb{CGdNKb}}}{{DAh{{KGl{c}}}}}{}}\",[\"Stream::Item\"]]E`[\"{{{ABd{{AIAb{CGdBGBl}}}}{AIAb{CGdNKb}}}{{DAh{{KGl{c}}}}}{}}\",[\"Stream::Item\"]]E`[\"{{{ABd{{AIAb{CGdAJAf}}}}{AIAb{CGdNKb}}{AIAb{CGdNNb}}}{{DAh{{KHf{ICl}}}}}}\",[]]Ec[\"{{{AIAb{CGdAJAf}}{AIAb{CGdNKb}}{AIAb{CGd{KAb{Oj}}}}}{{DAh{{KHd{KCjAHHd}}}}}}\",[]]Ef[\"{{{ABd{{AIAb{CGdAJAf}}}}{AIAb{CGdNKb}}{AIAb{CGd{KAb{Oj}}}}}{{DAh{{KHf{KCj}}}}}}\",[]]Fg[\"{{{AIAb{CGdBBN`}}{AIAb{CGdNKb}}{AIAb{CGd{KAb{AIOh}}}}{AIAb{CGd{KAb{ABE`}}}}}{{DAh{{KHf{KCj}}}}}}\",[]]Dn[\"{{{ABd{{AIAb{CGdAHHl}}}}{AIAb{ABFl}}{AIAb{CGdNKb}}}{{DAh{{KHf{ICl}}}}}}\",[]]>=Ce[\"{{{AIAb{CGdBBOh}}{A@d{NKn}}}{{AIAb{CGdBBOh}}}}\",[]]Ce[\"{{{AIAb{CGdBBOj}}{A@d{NL`}}}{{AIAb{CGdBBOj}}}}\",[]]Cj[\"{{{AIAb{BADf}}CN`B@Of{AIAb{{KAb{Oj}}}}}{{IIj{Oj}}}}\",[]]Cj[\"{{{AIAb{BELh}}CN`B@Of{AIAb{{KAb{Oj}}}}}{{IIj{Oj}}}}\",[]]Cj[\"{{{AIAb{BADj}}CN`B@Oh{AIAb{{KAb{Oj}}}}}{{IIj{Oj}}}}\",[]]Cj[\"{{{AIAb{BELj}}CN`B@Oh{AIAb{{KAb{Oj}}}}}{{IIj{Oj}}}}\",[]]Cj[\"{{{AIAb{BHMd}}CN`B@Od{AIAb{{KAb{Oj}}}}}{{IIj{Oj}}}}\",[]]Bb[\"{KCj{{KCh{KCj{KGl{KCj}}}}}}\",[]]000Aj[\"{{{AIAb{AINb}}}KCj}\",[]]A`[\"{AIAbKIl}\",[]]000000000000000000000000000000000000000000000000000000000000000000Cf[\"{{{AIAb{CGdBADd}}{A@d{BFDn}}}{{AIAb{CGdBADd}}}}\",[]]Cf[\"{{{AIAb{CGdBADh}}{A@d{BFE`}}}{{AIAb{CGdBADh}}}}\",[]]A`[\"{BHG`IGh}\",[]]A`[\"{BADdA@d}\",[]]A`[\"{BADhA@d}\",[]]Aj[\"{{{AIAb{ABBh}}}ICl}\",[]]Ck[\"{{{AIAb{CGdAJBb}}{AIAb{{KAb{Oj}}}}}{{KHd{IClAJCn}}}}\",[]]n[\"{{}KCj}\",[]]Ak[\"{{{AIAb{AINb}}}AHBl}\",[]]Ba[\"{{{AIAb{CGdABFn}}ABFn}ICl}\",[]]Ba[\"{{{AIAb{CGdAINn}}AINn}ICl}\",[]]Ba[\"{{{AIAb{CGdBCOf}}AHGn}ICl}\",[]]Ba[\"{{{AIAb{CGdABG`}}ABG`}ICl}\",[]]Ba[\"{{{AIAb{CGdAIO`}}AIO`}ICl}\",[]]Ba[\"{{{AIAb{CGdBCOh}}AHH`}ICl}\",[]]Bn[\"{{{AIAb{CGdBDAl}}DC`}{{AIAb{CGdBDAl}}}}\",[]]Bn[\"{{{AIAb{CGdBDAn}}DC`}{{AIAb{CGdBDAn}}}}\",[]]Bi[\"{{{AIAb{CGd}}}{{AIAb{CGdc}}}{}}\",[\"T\"]]0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Bc[\"{{AIAb{AIAb{CGdc}}}ICl{}}\",[\"T\"]]000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Cc[\"{{{AIAb{CGdAINd}}NLdKH`KJb}{{KHd{IClBBNf}}}}\",[]]A`[\"{AHD`DC`}\",[]]B`[\"{{AIAb{AIAb{c}}}DC`{}}\",[\"K\"]]00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Aa[\"{BCNhBFMj}\",[]]A`[\"{BEHhKJ`}\",[]]Aa[\"{BCNlBFMl}\",[]]A`[\"{BEHlKJb}\",[]]A`[\"{BFCnKJb}\",[]]A`[\"{BD@nKJb}\",[]]o[\"{NNnKJb}\",[]]Aa[\"{BCNhAIOj}\",[]]Aa[\"{BCNlAIOl}\",[]]o[\"{KJ`CNb}\",[]]o[\"{KJbCNb}\",[]]Aj[\"{{{AIAb{AINd}}}DC`}\",[]]Aj[\"{{{AIAb{BBAd}}}DC`}\",[]]Bd[\"{{{AIAb{BBN`}}}{{KHf{AJBh}}}}\",[]]Bd[\"{{{AIAb{ABBh}}}{{KHf{AJBh}}}}\",[]]A`[\"{AHGnCNb}\",[]]A`[\"{BCOfCNb}\",[]]A`[\"{AHH`CNb}\",[]]A`[\"{BCOhCNb}\",[]]Cc[\"{{{AIAb{BD@`}}{AIAb{AJB`}}CNb}{{A@d{B@@h}}}}\",[]]Bn[\"{{{AIAb{CGdBDAl}}DC`}{{AIAb{CGdBDAl}}}}\",[]]Bn[\"{{{AIAb{CGdBDAn}}DC`}{{AIAb{CGdBDAn}}}}\",[]]Bg[\"{{{AIAb{AINb}}KGn}{{KGl{AHGn}}}}\",[]]A`[\"{KElABA`}\",[]]A`[\"{AHDjA@f}\",[]]Bj[\"{{{AIAb{CGdAINd}}KH`}{{KGl{AHH`}}}}\",[]]A`[\"{AHDlA@f}\",[]]Db[\"{{{ABd{{AIAb{CGdAJBb}}}}{AIAb{CGdNKb}}}{{DAh{{KHf{ICl}}}}}}\",[]]00Ec[\"{{{ABd{{AIAb{CGdAJBb}}}}{AIAb{CGdNKb}}{AIAb{{KAb{Oj}}}}}{{DAh{{KHf{KCj}}}}}}\",[]]0Eg[\"{{{ABd{{AIAb{CGdAJBb}}}}{AIAb{CGdNKb}}{AIAb{{KAb{Oj}}}}}{{DAh{{KHd{KCjAJCn}}}}}}\",[]]Bm[\"{{{AIAb{IHn}}{AIAb{D@n}}}{{KHf{ICl}}}}\",[]]Cd[\"{{{AIAb{CGdAJAf}}KCj}{{KHd{{KGl{IDb}}AHHd}}}}\",[]]Cd[\"{{{AIAb{CGdBGCl}}KCj}{{KHd{{KGl{IDb}}AHHd}}}}\",[]]Cn[\"{{{AIAb{CGdAJAf}}{AIAb{CGd{KAb{Oj}}}}}{{KHd{IClBCBf}}}}\",[]]Bj[\"{{{AIAb{DA`}}AJ@l}{{KHd{IClBFM`}}}}\",[]]Bn[\"{{{AIAb{CGdAJA`}}NLd}{{AIAb{CGdAJA`}}}}\",[]]Bn[\"{{{AIAb{CGdAJAb}}NLd}{{AIAb{CGdAJAb}}}}\",[]]A`[\"{BCBlAGb}\",[]]o[\"{IHlCNb}\",[]]o[\"{{}AJBh}\",[]]0o[\"{{}AIAb}\",[]]Bk[\"{{ABC`{A@d{BADd}}}{{KHd{AIMnBCNn}}}}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]Bh[\"{{{AIAb{AHD`}}AJBh}{{KHh{AHHn}}}}\",[]]A`[\"{AHGnCMn}\",[]]Aj[\"{{{AIAb{AINd}}}CMn}\",[]]A`[\"{AHH`CMn}\",[]]Ce[\"{{{AIAb{CGdAJA`}}{KGl{KIl}}}{{AIAb{CGdAJA`}}}}\",[]]Ce[\"{{{AIAb{CGdAJAb}}{KGl{KIl}}}{{AIAb{CGdAJAb}}}}\",[]]Aa[\"{ABFhAJBh}\",[]]Aa[\"{ABFjAJBh}\",[]]Aa[\"{ABFlAJBh}\",[]]Aa[\"{AJBhAHEf}\",[]]o[\"{AAlCMn}\",[]]Bn[\"{{{AIAb{CGdBDAl}}CMn}{{AIAb{CGdBDAl}}}}\",[]]Bn[\"{{{AIAb{CGdBDAn}}CMn}{{AIAb{CGdBDAn}}}}\",[]]A`[\"{AHD`CMn}\",[]]Bo[\"{{{AIAb{CGdBDAl}}ABBd}{{AIAb{CGdBDAl}}}}\",[]]Bo[\"{{{AIAb{CGdBDAn}}ABBd}{{AIAb{CGdBDAn}}}}\",[]]A`[\"{AHD`KGl}\",[]]k[\"{{}}\",[]]0000000000Bh[\"{ABC`c{}}\",[\"IntoFuture::IntoFuture\"]]11A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]o[\"{AAlKCj}\",[]]Ao[\"{{AIAbc}{{NLj{c}}}{}}\",[\"S\"]]0C`[\"{{{AIAb{CGdAINf}}NLdCNbDC`{KGl{CNb}}}ICl}\",[]]Bo[\"{{{AIAb{CGdCNj}}NLdCNbDC`{KGl{CNb}}}ICl}\",[]]C`[\"{{{AIAb{CGdAINh}}NLdCNbDC`{KGl{CNb}}}ICl}\",[]]Bo[\"{{{AIAb{CGdCNl}}NLdCNbDC`{KGl{CNb}}}ICl}\",[]]Bk[\"{{{AIAb{CGdIHn}}NLd{AIAb{BD@`}}}ICl}\",[]]A`[\"{BFDjKGl}\",[]]A`[\"{BFDlKGl}\",[]]Bn[\"{{{AIAb{KJ`}}{AIAb{KJ`}}}{{KGl{ABDj}}}}\",[]]Bn[\"{{{AIAb{KGn}}{AIAb{KGn}}}{{KGl{ABDj}}}}\",[]]C`[\"{{{AIAb{AON`}}{AIAb{AON`}}}{{KGl{ABDj}}}}\",[]]C`[\"{{{AIAb{B@Of}}{AIAb{B@Of}}}{{KGl{ABDj}}}}\",[]]Bn[\"{{{AIAb{DAl}}{AIAb{DAl}}}{{KGl{ABDj}}}}\",[]]Bn[\"{{{AIAb{A@l}}{AIAb{A@l}}}{{KGl{ABDj}}}}\",[]]C`[\"{{{AIAb{ABEd}}{AIAb{ABEd}}}{{KGl{ABDj}}}}\",[]]Bn[\"{{{AIAb{KJb}}{AIAb{KJb}}}{{KGl{ABDj}}}}\",[]]C`[\"{{{AIAb{BFFf}}{AIAb{BFFf}}}{{KGl{ABDj}}}}\",[]]Bn[\"{{{AIAb{KH`}}{AIAb{KH`}}}{{KGl{ABDj}}}}\",[]]C`[\"{{{AIAb{AHHf}}{AIAb{AHHf}}}{{KGl{ABDj}}}}\",[]]C`[\"{{{AIAb{BBDl}}{AIAb{BBDl}}}{{KGl{ABDj}}}}\",[]]C`[\"{{{AIAb{AJD`}}{AIAb{AJD`}}}{{KGl{ABDj}}}}\",[]]C`[\"{{{AIAb{AONb}}{AIAb{AONb}}}{{KGl{ABDj}}}}\",[]]C`[\"{{{AIAb{BEId}}{AIAb{BEId}}}{{KGl{ABDj}}}}\",[]]C`[\"{{{AIAb{BFEl}}{AIAb{BFEl}}}{{KGl{ABDj}}}}\",[]]C`[\"{{{AIAb{B@Oh}}{AIAb{B@Oh}}}{{KGl{ABDj}}}}\",[]]Bn[\"{{{AIAb{DAn}}{AIAb{DAn}}}{{KGl{ABDj}}}}\",[]]Bn[\"{{{AIAb{A@n}}{AIAb{A@n}}}{{KGl{ABDj}}}}\",[]]C`[\"{{{AIAb{ABEf}}{AIAb{ABEf}}}{{KGl{ABDj}}}}\",[]]Ak[\"{{{AIAb{AINb}}}AJ@j}\",[]]Bk[\"{{{AIAb{AINd}}KH`}{{KHd{AJ@nAIMj}}}}\",[]]Df[\"{{{AIAb{CGdAJAf}}{AIAb{CGd{KAb{ID`}}}}}{{KHd{{KGl{KCj}}AHHd}}}}\",[]]Cc[\"{{{AIAb{CGdAJAf}}KCj}{{KHd{{ABj{Oj}}BCBh}}}}\",[]]Bb[\"{{{AIAb{CGdAINd}}ABEf}AJAh}\",[]]Aj[\"{{{AIAb{AHEn}}}KCj}\",[]]Aj[\"{{{AIAb{AHF`}}}KCj}\",[]]Bb[\"{{{AIAb{CGdAINd}}ABEf}AJBd}\",[]]Bn[\"{{{AIAb{CGdBDAl}}CNb}{{AIAb{CGdBDAl}}}}\",[]]Bn[\"{{{AIAb{CGdBDAn}}CNb}{{AIAb{CGdBDAn}}}}\",[]]A`[\"{AHD`KGl}\",[]]A`[\"{BBAjKGl}\",[]]A`[\"{BBAlKGl}\",[]]Cf[\"{{{AIAb{CGdBADd}}{A@d{AJC`}}}{{AIAb{CGdBADd}}}}\",[]]Cf[\"{{{AIAb{CGdBADh}}{A@d{AJCb}}}{{AIAb{CGdBADh}}}}\",[]]Cf[\"{{{AIAb{CGdB@Mh}}{A@d{AJCh}}}{{AIAb{CGdB@Mh}}}}\",[]]Cf[\"{{{AIAb{CGdB@Ml}}{A@d{AJCj}}}{{AIAb{CGdB@Ml}}}}\",[]]o[\"{AAlCNb}\",[]]Bn[\"{{{AIAb{CGdBFKb}}CMn}{{AIAb{CGdBFKb}}}}\",[]]Bn[\"{{{AIAb{CGdBFKd}}CMn}{{AIAb{CGdBFKd}}}}\",[]]Ak[\"{{{AIAb{AINb}}}BGKl}\",[]]Aj[\"{{{AIAb{DA`}}}BCCf}\",[]]Aj[\"{{{A@d{BADf}}}BADd}\",[]]Aj[\"{{{A@d{BADj}}}BADh}\",[]]Aj[\"{{BD@jc}BD@jABEn}\",[\"\"]]Aj[\"{{BD@lc}BD@lABEn}\",[\"\"]]Bm[\"{{{AIAb{CGdAJBb}}ID`}{{KHd{IClAJCn}}}}\",[]]n[\"{{}KFj}\",[]]o[\"{NK`NK`}\",[]]0000Bd[\"{{{AIAb{BHAf}}}{{KGl{ABBd}}}}\",[]]Bd[\"{{{AIAb{BHAh}}}{{KGl{ABBd}}}}\",[]]Bd[\"{{{AIAb{BIBn}}}{{KGl{ABBd}}}}\",[]]Bd[\"{{{AIAb{BICh}}}{{KGl{ABBd}}}}\",[]]Bd[\"{{{AIAb{AINb}}}{{KGl{BCNn}}}}\",[]]Ck[\"{{{AIAb{ABBh}}B@MhAJBh{AIAb{CMl}}}{{KHd{AIMnB@O`}}}}\",[]]Ak[\"{{{AIAb{BACl}}}ABBd}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]An[\"{{AJAdDC`}{{KHh{KCj}}}}\",[]]Cl[\"{{{AIAb{NNj}}}{{KGl{{KCh{{A@f{AHEn}}{A@f{AHFl}}}}}}}}\",[]]Cm[\"{{{AIAb{AJCd}}}{{KGl{{KCh{{A@f{AHEn}}{A@f{AHFl}}}}}}}}\",[]]Cl[\"{{{AIAb{NNl}}}{{KGl{{KCh{{A@f{AHF`}}{A@f{AHFn}}}}}}}}\",[]]Cm[\"{{{AIAb{AJCf}}}{{KGl{{KCh{{A@f{AHF`}}{A@f{AHFn}}}}}}}}\",[]]A`[\"{BGIjABj}\",[]]A`[\"{BIG`ABj}\",[]]A`[\"{BGIlABj}\",[]]A`[\"{BIGbABj}\",[]]An[\"{{{AIAb{CGdBHAf}}}B@Of}\",[]]An[\"{{{AIAb{CGdBHAh}}}B@Oh}\",[]]An[\"{{{AIAb{CGdBIBn}}}B@Oh}\",[]]An[\"{{{AIAb{CGdBICh}}}B@Oh}\",[]]Ak[\"{{{AIAb{BCCd}}}BAAj}\",[]]Ba[\"{{{AIAb{CGdAINd}}BCOd}ICl}\",[]]Bo[\"{{{AIAb{CGdABBj}}BEIdBBAd}{{KGl{BCOd}}}}\",[]]B`[\"{{{AIAb{NNj}}B@OfDAl}D@d}\",[]]Bn[\"{{{AIAb{BADf}}CN`B@Of}{{KHd{D@dBFNb}}}}\",[]]Ba[\"{{{AIAb{AJCd}}B@OfDAl}D@d}\",[]]Bn[\"{{{AIAb{BELh}}CN`B@Of}{{KHd{D@dBFNb}}}}\",[]]B`[\"{{{AIAb{NNl}}B@OhDAn}D@f}\",[]]Bn[\"{{{AIAb{BADj}}CN`B@Oh}{{KHd{D@fBFNh}}}}\",[]]Ba[\"{{{AIAb{AJCf}}B@OhDAn}D@f}\",[]]Bn[\"{{{AIAb{BELj}}CN`B@Oh}{{KHd{D@fBFNh}}}}\",[]]Bn[\"{{{AIAb{BHMd}}CN`B@Od}{{KHd{D@fBFNh}}}}\",[]]Aj[\"{{}{{NNd{c}}}{}}\",[\"T\"]]0A`[\"{AHGnCNb}\",[]]A`[\"{BCOfCNb}\",[]]A`[\"{AHH`CNb}\",[]]A`[\"{BCOhCNb}\",[]]Bn[\"{{{AIAb{CGdBADd}}KCj}{{AIAb{CGdBADd}}}}\",[]]Bn[\"{{{AIAb{CGdBADh}}KCj}{{AIAb{CGdBADh}}}}\",[]]Aj[\"{{{AIAb{BBN`}}}DC`}\",[]]0Aj[\"{{{AIAb{BCCd}}}DC`}\",[]]Bk[\"{{{AIAb{AINd}}KH`}{{KHd{AHEfAIMj}}}}\",[]]Cc[\"{{{AIAb{BD@`}}{AIAb{AJAd}}CNb}{{A@d{B@@h}}}}\",[]]Ak[\"{{{AIAb{ABC`}}}B@Of}\",[]]Ak[\"{{{AIAb{ABCb}}}B@Oh}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]Bf[\"{{{AIAb{CGdAINd}}}{{KGl{NLd}}}}\",[]]Cf[\"{{{AIAb{CGdBDAl}}{A@d{AONn}}}{{AIAb{CGdBDAl}}}}\",[]]Cf[\"{{{AIAb{CGdBDAn}}{A@d{AOO`}}}{{AIAb{CGdBDAn}}}}\",[]]43A`[\"{AAlAOOf}\",[]]A`[\"{ABFhKGl}\",[]]A`[\"{ABFjKGl}\",[]]A`[\"{ABFlKGl}\",[]]A`[\"{BFDjKGl}\",[]]A`[\"{BFDlKGl}\",[]]Ai[\"{{{AIAb{NOj}}}KCj}\",[]]Bo[\"{{{AIAb{CGdBIBn}}ABBd}{{AIAb{CGdBIBn}}}}\",[]]Bo[\"{{{AIAb{CGdBICh}}ABBd}{{AIAb{CGdBICh}}}}\",[]]Bj[\"{{{AIAb{AJBb}}CEn}{{KHd{IClB@Nd}}}}\",[]]Bm[\"{{{AIAb{CGdAJBd}}CEn}{{KHd{IClB@Nl}}}}\",[]]?>A`[\"{IHlB@@f}\",[]]A`[\"{AOLhIHl}\",[]]Bj[\"{{{A@d{B@Md}}II`}{{KHd{BELdBGJj}}}}\",[]]Bj[\"{{{A@d{BAD`}}II`}{{KHd{BELhBGJj}}}}\",[]]Bj[\"{{{A@d{B@Md}}II`}{{KHd{BELfBGJl}}}}\",[]]Bj[\"{{{A@d{BAD`}}II`}{{KHd{BELjBGJl}}}}\",[]]Co[\"{{{AIAb{CGdAJBb}}{AIAb{CGd{KAb{ID`}}}}}{{KHd{O@fAJCn}}}}\",[]]Co[\"{{{AIAb{CGdAJBd}}{AIAb{CGd{KAb{ID`}}}}}{{KHd{O@hAJD`}}}}\",[]]Aj[\"{{{AIAb{AINd}}}DC`}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]A`[\"{AHD`DC`}\",[]]Cd[\"{{{AIAb{CGdBBOh}}{A@d{A`}}}{{AIAb{CGdBBOh}}}}\",[]]Cd[\"{{{AIAb{CGdBBOj}}{A@d{A`}}}{{AIAb{CGdBBOj}}}}\",[]]Bk[\"{{{AIAb{BBN`}}}{{ABd{{A@f{AHHl}}}}}}\",[]]o[\"{AAlCNb}\",[]]Aa[\"{B@@fAHFb}\",[]]76Aa[\"{AJAlABC`}\",[]]Aa[\"{AJAnABCb}\",[]]Cf[\"{{{AIAb{CGdBFId}}{KGl{ABBd}}}{{AIAb{CGdBFId}}}}\",[]]Cf[\"{{{AIAb{CGdBFIf}}{KGl{ABBd}}}{{AIAb{CGdBFIf}}}}\",[]]9;:An[\"{{{AIAb{B@@h}}ABBd}ICl}\",[]]B`[\"{{{AIAb{CGdAINf}}CMn}ICl}\",[]]Ao[\"{{{AIAb{CGdCNj}}CMn}ICl}\",[]]Ao[\"{{{AIAb{CGdIDj}}CMn}ICl}\",[]]Ao[\"{{{AIAb{CGdNM`}}CMn}ICl}\",[]]B`[\"{{{AIAb{CGdAINh}}CMn}ICl}\",[]]Ao[\"{{{AIAb{CGdCNl}}CMn}ICl}\",[]]Ao[\"{{{AIAb{CGdIDl}}CMn}ICl}\",[]]Ao[\"{{{AIAb{CGdNMb}}CMn}ICl}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]10Bj[\"{{{AIAb{AINb}}}{{KGl{{A@f{A@b}}}}}}\",[]]Bi[\"{{{AIAb{NNj}}}{{KGl{{A@f{A@b}}}}}}\",[]]Bj[\"{{{AIAb{AJCd}}}{{KGl{{A@f{A@b}}}}}}\",[]]Bi[\"{{{AIAb{NNl}}}{{KGl{{A@f{A@b}}}}}}\",[]]Bj[\"{{{AIAb{AJCf}}}{{KGl{{A@f{A@b}}}}}}\",[]]Db[\"{{{ABd{{AIAb{CGdAJBb}}}}{AIAb{CGdNKb}}}{{DAh{{KHf{ICl}}}}}}\",[]]Db[\"{{{AIAb{CGdAINd}}NLdBAAj{AIAb{CGd{ABj{Oj}}}}}{{KGl{ABFj}}}}\",[]]Cf[\"{{{AIAb{CGdBDAl}}{AIAb{CMl}}}{{AIAb{CGdBDAl}}}}\",[]]Cf[\"{{{AIAb{CGdBDAn}}{AIAb{CMl}}}{{AIAb{CGdBDAn}}}}\",[]]Ak[\"{{{AIAb{AINb}}}BAC`}\",[]]Bj[\"{{{AIAb{AINb}}ID`}{{KHd{IClBFFd}}}}\",[]]Bn[\"{{{AIAb{CGdBDAl}}DC`}{{AIAb{CGdBDAl}}}}\",[]]Bn[\"{{{AIAb{CGdBDAn}}DC`}{{AIAb{CGdBDAn}}}}\",[]]Ai[\"{{{AIAb{NKn}}}KCj}\",[]]Ai[\"{{{AIAb{NL`}}}KCj}\",[]]Dh[\"{{{AIAb{B@Mj}}CN`{AIAb{CMl}}{AIAb{BGCj}}}{{KHd{{A@f{NNj}}B@O`}}}}\",[]]Cb[\"{{{AIAb{BADf}}CN`{AIAb{BGCj}}}{{A@f{NNj}}}}\",[]]Dh[\"{{{AIAb{BELd}}CN`{AIAb{CMl}}{AIAb{BGCj}}}{{KHd{{A@f{NNj}}B@O`}}}}\",[]]Cb[\"{{{AIAb{BELh}}CN`{AIAb{BGCj}}}{{A@f{NNj}}}}\",[]]Dh[\"{{{AIAb{B@Mn}}CN`{AIAb{CMl}}{AIAb{BGCj}}}{{KHd{{A@f{NNl}}B@Ob}}}}\",[]]Cb[\"{{{AIAb{BADj}}CN`{AIAb{BGCj}}}{{A@f{NNl}}}}\",[]]Dh[\"{{{AIAb{BELf}}CN`{AIAb{CMl}}{AIAb{BGCj}}}{{KHd{{A@f{NNl}}B@Ob}}}}\",[]]Cb[\"{{{AIAb{BELj}}CN`{AIAb{BGCj}}}{{A@f{NNl}}}}\",[]]Dh[\"{{{AIAb{BHMb}}CN`{AIAb{CMl}}{AIAb{BGCj}}}{{KHd{{A@f{NNl}}B@Nn}}}}\",[]]Cb[\"{{{AIAb{BHMd}}CN`{AIAb{BGCj}}}{{A@f{NNl}}}}\",[]]Dd[\"{{{ABd{{AIAb{CGdc}}}}{AIAb{CGdNKb}}}{{DAh{{KGl{KHd}}}}}{}}\",[\"S\"]]0Bn[\"{{AJBhBBNbAAl}{{KHh{{KCh{ABBfAIN`}}}}}}\",[]]Bd[\"{{{AIAb{AINd}}}{{AIAb{NNl}}}}\",[]]A`[\"{AOLhIHl}\",[]]Cb[\"{{{AIAb{AONn}}DAlAJBhB@OfNLd}{{KGl{AJA`}}}}\",[]]Cb[\"{{{AIAb{BD@j}}DAlAJBhB@OfNLd}{{KGl{AJA`}}}}\",[]]Cb[\"{{{AIAb{AOO`}}DAnAJBhB@OhNLd}{{KGl{AJAb}}}}\",[]]Cb[\"{{{AIAb{BD@l}}DAnAJBhB@OhNLd}{{KGl{AJAb}}}}\",[]]B`[\"{{{AIAb{CGdAINd}}NLd}ICl}\",[]]Ca[\"{{{AIAb{CGdAIMn}}}{{KHd{{A@f{A@b}}BCNn}}}}\",[]]Bj[\"{{{AIAb{AINb}}}{{KGl{{A@f{A@b}}}}}}\",[]]Bi[\"{{{AIAb{NNj}}}{{KGl{{A@f{A@b}}}}}}\",[]]Bj[\"{{{AIAb{AJCd}}}{{KGl{{A@f{A@b}}}}}}\",[]]Bi[\"{{{AIAb{NNl}}}{{KGl{{A@f{A@b}}}}}}\",[]]Bj[\"{{{AIAb{AJCf}}}{{KGl{{A@f{A@b}}}}}}\",[]]o[\"{AINnOj}\",[]]o[\"{AIO`Oj}\",[]]Ai[\"{{{AIAb{CMl}}}DC`}\",[]]Aj[\"{{{AIAb{AINf}}}CNb}\",[]]Ai[\"{{{AIAb{CNj}}}CNb}\",[]]Bn[\"{{{AIAb{CGdAILl}}CNb}{{AIAb{CGdAILl}}}}\",[]]Ai[\"{{{AIAb{IDj}}}CNb}\",[]]Bn[\"{{{AIAb{CGdAOMf}}CNb}{{AIAb{CGdAOMf}}}}\",[]]Ai[\"{{{AIAb{NM`}}}CNb}\",[]]Bn[\"{{{AIAb{CGdBBCl}}CNb}{{AIAb{CGdBBCl}}}}\",[]]Aj[\"{{{AIAb{AINh}}}CNb}\",[]]Ai[\"{{{AIAb{CNl}}}CNb}\",[]]Bn[\"{{{AIAb{CGdAILn}}CNb}{{AIAb{CGdAILn}}}}\",[]]Ai[\"{{{AIAb{IDl}}}CNb}\",[]]Bn[\"{{{AIAb{CGdAOMh}}CNb}{{AIAb{CGdAOMh}}}}\",[]]Ai[\"{{{AIAb{NMb}}}CNb}\",[]]Bn[\"{{{AIAb{CGdBBCn}}CNb}{{AIAb{CGdBBCn}}}}\",[]]Aa[\"{AJAfBGCl}\",[]]Ai[\"{{{AIAb{NNj}}}DC`}\",[]]Aj[\"{{{AIAb{AJCd}}}DC`}\",[]]Aj[\"{{{AIAb{AINd}}}DC`}\",[]]Ai[\"{{{AIAb{NNl}}}DC`}\",[]]Aj[\"{{{AIAb{AJCf}}}DC`}\",[]]Co[\"{{{AIAb{NNj}}B@Of{AIAb{{KAb{Oj}}}}{AIAb{{KAb{Oj}}}}}DC`}\",[]]D`[\"{{{AIAb{AJCd}}B@Of{AIAb{{KAb{Oj}}}}{AIAb{{KAb{Oj}}}}}DC`}\",[]]Co[\"{{{AIAb{NNl}}B@Oh{AIAb{{KAb{Oj}}}}{AIAb{{KAb{Oj}}}}}DC`}\",[]]D`[\"{{{AIAb{AJCf}}B@Oh{AIAb{{KAb{Oj}}}}{AIAb{{KAb{Oj}}}}}DC`}\",[]]Bn[\"{{{AIAb{CGdBFKb}}CMn}{{AIAb{CGdBFKb}}}}\",[]]Bn[\"{{{AIAb{CGdBFKd}}CMn}{{AIAb{CGdBFKd}}}}\",[]]Cd[\"{{{AIAb{CGdNNj}}}{{KGl{{NLf{{A@f{AHFl}}}}}}}}\",[]]Ce[\"{{{AIAb{CGdAJCd}}}{{KGl{{NLf{{A@f{AHFl}}}}}}}}\",[]]Cd[\"{{{AIAb{CGdNNl}}}{{KGl{{NLh{{A@f{AHFn}}}}}}}}\",[]]Ce[\"{{{AIAb{CGdAJCf}}}{{KGl{{NLh{{A@f{AHFn}}}}}}}}\",[]]Bf[\"{{{AIAb{CGdAINf}}CMnCNbNLd}ICl}\",[]]Be[\"{{{AIAb{CGdCNj}}CMnCNbNLd}ICl}\",[]]Bf[\"{{{AIAb{CGdAINh}}CMnCNbNLd}ICl}\",[]]Be[\"{{{AIAb{CGdCNl}}CMnCNbNLd}ICl}\",[]]Bf[\"{{{AIAb{CGdAINf}}NLdCMnCNb}ICl}\",[]]Be[\"{{{AIAb{CGdCNj}}NLdCMnCNb}ICl}\",[]]Bf[\"{{{AIAb{CGdAINh}}NLdCMnCNb}ICl}\",[]]Be[\"{{{AIAb{CGdCNl}}NLdCMnCNb}ICl}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]De[\"{{{AIAb{CGdBDAl}}c{AIAb{CMl}}}{{AIAb{CGdBDAl}}}{{ICn{D@n}}}}\",[\"\"]]De[\"{{{AIAb{CGdBDAn}}c{AIAb{CMl}}}{{AIAb{CGdBDAn}}}{{ICn{D@n}}}}\",[\"\"]]Ci[\"{{{AIAb{CGdNNj}}{AIAb{{KAb{Oj}}}}}{{KHd{DC`IEn}}}}\",[]]Cj[\"{{{AIAb{CGdAJCd}}{AIAb{{KAb{Oj}}}}}{{KHd{DC`IEn}}}}\",[]]Cj[\"{{{AIAb{CGdNNl}}{AIAb{{KAb{Oj}}}}}{{KHd{DC`BCCb}}}}\",[]]Ck[\"{{{AIAb{CGdAJCf}}{AIAb{{KAb{Oj}}}}}{{KHd{DC`BCCb}}}}\",[]]o[\"{AAlDC`}\",[]]Bn[\"{{{AIAb{CGdBDAl}}KJ`}{{AIAb{CGdBDAl}}}}\",[]]Bn[\"{{{AIAb{CGdBDAn}}KJb}{{AIAb{CGdBDAn}}}}\",[]]A`[\"{AHD`KGl}\",[]]Ca[\"{{{AIAb{CGdAJAf}}}{{KHd{{KGl{KJ`}}AJAj}}}}\",[]]Ca[\"{{{AIAb{CGdBGCl}}}{{KHd{{KGl{KJ`}}AJAj}}}}\",[]]Ca[\"{{{AIAb{CGdAJAh}}}{{KHd{{KGl{KJb}}B@Nl}}}}\",[]]Ak[\"{{{AIAb{AIMn}}}AJBh}\",[]]Ak[\"{{{AIAb{ABC`}}}AJBh}\",[]]Bg[\"{{{AIAb{DA`}}}{{KHd{AJBhAIMd}}}}\",[]]Ak[\"{{{AIAb{ABCb}}}AJBh}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]B`[\"{{KJ`c}KJ`{{D@b{KJ`}}}}\",[\"\"]]B`[\"{{KGnc}KGn{{D@b{KGn}}}}\",[\"\"]]B`[\"{{KJbc}KJb{{D@b{KJb}}}}\",[\"\"]]B`[\"{{KH`c}KH`{{D@b{KH`}}}}\",[\"\"]]20Ae[\"{{ABBdCNb}AGb}\",[]]Bn[\"{{{AIAb{CGdBDAl}}AG`}{{AIAb{CGdBDAl}}}}\",[]]Bn[\"{{{AIAb{CGdBDAn}}AG`}{{AIAb{CGdBDAn}}}}\",[]]A`[\"{IHlABBd}\",[]]B`[\"{{}{{AIAb{{KAb{BGBj}}}}}}\",[]]Ao[\"{{AIAb{A@@b{CGdOj}}}ICl}\",[]]000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000An[\"{{}{{KGl{{A@d{NNh}}}}}}\",[]]Bn[\"{{{AIAb{CGdBBOh}}DC`}{{AIAb{CGdBBOh}}}}\",[]]Bn[\"{{{AIAb{CGdBBOj}}DC`}{{AIAb{CGdBBOj}}}}\",[]]Aj[\"{{{AIAb{AHFl}}}CNb}\",[]]Aj[\"{{{AIAb{AHFn}}}CNb}\",[]]A`[\"{ABE`KGl}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]10Bf[\"{{{AIAb{CMl}}}{{KHh{CNbBBD`}}}}\",[]]Bn[\"{{{AIAb{ABBh}}{A@f{BBN`}}}{{KHf{ICl}}}}\",[]]Ai[\"{AIN`{{KHh{ICl}}}}\",[]]Cj[\"{{{AIAb{NNf}}BBNbBBDj{AIAb{AAl}}}{{KCh{AJBhABBf}}}}\",[]]Cb[\"{{{AIAb{CGdAINd}}KH`AJ@n}{{KHd{AJ@nBFMb}}}}\",[]]Am[\"{{{AIAb{AINb}}CNb}ICl}\",[]]B`[\"{{{AIAb{CGdAINd}}CNb}ICl}\",[]]A`[\"{BCBjDC`}\",[]]B`[\"{{{AIAb{CGdIHl}}BCBl}ICl}\",[]]Aa[\"{B@@fAHFb}\",[]]Bn[\"{{{AIAb{NNh}}AHHn}{{KHf{{A@f{BBN`}}}}}}\",[]]Bo[\"{{{AIAb{BAEb}}AHHn}{{KHf{{A@f{BBN`}}}}}}\",[]]Bo[\"{{{AIAb{AOOl}}AHHn}{{KHf{{A@f{BBN`}}}}}}\",[]]Ci[\"{{{AIAb{CGdNNj}}{AIAb{CGd{ABj{Oj}}}}}{{KGl{D@d}}}}\",[]]Cj[\"{{{AIAb{CGdAJCd}}{AIAb{CGd{ABj{Oj}}}}}{{KGl{D@d}}}}\",[]]Ci[\"{{{AIAb{CGdNNl}}{AIAb{CGd{ABj{Oj}}}}}{{KGl{D@f}}}}\",[]]Cj[\"{{{AIAb{CGdAJCf}}{AIAb{CGd{ABj{Oj}}}}}{{KGl{D@f}}}}\",[]]Cb[\"{{{AIAb{ABF`}}ICjAJBnABBd}{{KHd{IClBDAf}}}}\",[]]Cb[\"{{{AIAb{BB@d}}ICjAJBnABBd}{{KHd{IClBDAf}}}}\",[]]Cb[\"{{{AIAb{BAAl}}ICjAJBnABBd}{{KHd{IClBDAf}}}}\",[]]Cb[\"{{{AIAb{ABFb}}ICjAJBnABBd}{{KHd{IClBDAh}}}}\",[]]Cb[\"{{{AIAb{BB@f}}ICjAJBnABBd}{{KHd{IClBDAh}}}}\",[]]Cb[\"{{{AIAb{BAAn}}ICjAJBnABBd}{{KHd{IClBDAh}}}}\",[]]Al[\"{{}{{KHd{B@MfBGJl}}}}\",[]]Bn[\"{{{AIAb{AINb}}KGn}{{KGl{{A@f{AINf}}}}}}\",[]]Bo[\"{{{AIAb{AINd}}KH`}{{KGl{{AIAb{AINh}}}}}}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]Aj[\"{{{AIAb{AINb}}}ICl}\",[]]Am[\"{{{AIAb{CGdAINd}}}ICl}\",[]]Bf[\"{{{AIAb{AILb}}}{{KHd{AAlIF`}}}}\",[]]Bf[\"{{{AIAb{AILb}}}{{KHd{A@jIF`}}}}\",[]]Bg[\"{{{AIAb{AILb}}}{{KHd{ABAfIF`}}}}\",[]]Bg[\"{{{AIAb{AILb}}}{{KHd{AHD`IF`}}}}\",[]]Bf[\"{{{AIAb{AILb}}}{{KHd{AAnIF`}}}}\",[]]Bf[\"{{{AIAb{AILb}}}{{KHd{AB`IF`}}}}\",[]]Ak[\"{{{AIAb{BCCd}}}BAAj}\",[]]Cf[\"{{{AIAb{CGdBDAl}}{KGl{AON`}}}{{AIAb{CGdBDAl}}}}\",[]]Cf[\"{{{AIAb{CGdBDAn}}{KGl{AONb}}}{{AIAb{CGdBDAn}}}}\",[]]<;Aj[\"{{{AIAb{ABBh}}}KCj}\",[]]Aj[\"{{{AIAb{ABBj}}}KCj}\",[]]Bc[\"{{{AIAb{AINb}}AJBhAJ@l}ABDh}\",[]]Co[\"{{{AIAb{CGdAINd}}AHEfAJ@nNLd}{{KHd{{KCh{KH`DC`}}AHGd}}}}\",[]]Bn[\"{{{AIAb{CGdBDAl}}CN`}{{AIAb{CGdBDAl}}}}\",[]]Bn[\"{{{AIAb{CGdBDAn}}CN`}{{AIAb{CGdBDAn}}}}\",[]]Ai[\"{AIN`{{KHh{ICl}}}}\",[]]Bg[\"{{{AIAb{BCCd}}AJCl}{{KHf{KCj}}}}\",[]]A`[\"{AHD`CNb}\",[]]10Aj[\"{{{AIAb{AAl}}}BDAj}\",[]]Cf[\"{{{AIAb{CGdBADd}}{A@d{BDAl}}}{{AIAb{CGdBADd}}}}\",[]]Cf[\"{{{AIAb{CGdB@Mh}}{A@d{BDAl}}}{{AIAb{CGdB@Mh}}}}\",[]]Cf[\"{{{AIAb{CGdBADh}}{A@d{BDAn}}}{{AIAb{CGdBADh}}}}\",[]]Cf[\"{{{AIAb{CGdB@Ml}}{A@d{BDAn}}}{{AIAb{CGdB@Ml}}}}\",[]]Aa[\"{BADdBHBh}\",[]]Aa[\"{BADhBHBj}\",[]]Bj[\"{{{ABj{BBNb}}BBDj}{{KHd{BADdIFd}}}}\",[]]Bj[\"{{{ABj{BBNb}}BBDj}{{KHd{BADhIFf}}}}\",[]]Co[\"{{{AIAb{CGdAJBb}}{AIAb{CGd{KAb{ID`}}}}}{{KHd{IClAJCn}}}}\",[]]A`[\"{AHGnCNb}\",[]]A`[\"{AHH`CNb}\",[]]A`[\"{BFDjCNb}\",[]]A`[\"{BFDlCNb}\",[]]Bc[\"{{{AIAb{AINb}}}{{KGl{KCj}}}}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]10Aj[\"{{{AIAb{AHDf}}}KCj}\",[]]Be[\"{{{AIAb{ABBh}}{KGl{BADd}}}ICl}\",[]]Bo[\"{{{AIAb{CGdABBj}}{KGl{{A@d{BADh}}}}}ICl}\",[]]Bd[\"{{{AIAb{BGBj}}}{{KGl{BBDh}}}}\",[]]o[\"{{}AIAb}\",[]]n[\"{{}NK`}\",[]]0000Bn[\"{{{AIAb{CGdBDAl}}KCj}{{AIAb{CGdBDAl}}}}\",[]]Bn[\"{{{AIAb{CGdBDAn}}KCj}{{AIAb{CGdBDAn}}}}\",[]]o[\"{CNbKJ`}\",[]]o[\"{CNbKJb}\",[]]A`[\"{BBAfCNb}\",[]]Bf[\"{{{AIAb{BGKl}}{AIAb{BGKl}}}DC`}\",[]]Bo[\"{{{AIAb{CGdBBOh}}ABBd}{{AIAb{CGdBBOh}}}}\",[]]Bo[\"{{{AIAb{CGdBBOj}}ABBd}{{AIAb{CGdBBOj}}}}\",[]]Ae[\"{{KCjCNb}BB@d}\",[]]Ae[\"{{KCjCNb}BB@f}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]7Bg[\"{{{AIAb{AINd}}KH`}{{KGl{AJ@n}}}}\",[]]An[\"{{{AIAb{AINb}}ID`}BACn}\",[]]Bm[\"{{{AIAb{CGdBELd}}{ABj{{ABj{Oj}}}}}ICl}\",[]]Bm[\"{{{AIAb{CGdBELh}}{ABj{{ABj{Oj}}}}}ICl}\",[]]Bm[\"{{{AIAb{CGdBELf}}{ABj{{ABj{Oj}}}}}ICl}\",[]]Bm[\"{{{AIAb{CGdBELj}}{ABj{{ABj{Oj}}}}}ICl}\",[]]Am[\"{{{AIAb{AINb}}KJ`}ICl}\",[]]B`[\"{{{AIAb{CGdAINd}}KJb}ICl}\",[]]Ce[\"{{{AIAb{CGdBBOh}}{ABj{CN`}}}{{AIAb{CGdBBOh}}}}\",[]]Ce[\"{{{AIAb{CGdBBOj}}{ABj{CN`}}}{{AIAb{CGdBBOj}}}}\",[]]Fg[\"{{{AIAb{BHGb}}{AIAb{BBNb}}{AIAb{{KAb{BBNb}}}}{AIAb{AJBf}}{AIAb{{KAb{Oj}}}}ABGb}{{KHd{BFLlIEj}}}}\",[]]Af[\"{{BD@jNLd}BD@j}\",[]]Af[\"{{BD@lNLd}BD@l}\",[]]A`[\"{BBAfCNb}\",[]]Al[\"{BAEd{{KHd{DAlIFb}}}}\",[]]Al[\"{BAEd{{KHd{A@lIFb}}}}\",[]]Ai[\"{BAEd{{KHj{DAn}}}}\",[]]Ai[\"{BAEd{{KHj{A@n}}}}\",[]]o[\"{NK`NK`}\",[]]Bo[\"{{{AIAb{CGdBFKb}}ABBd}{{AIAb{CGdBFKb}}}}\",[]]Bo[\"{{{AIAb{CGdBFKd}}ABBd}{{AIAb{CGdBFKd}}}}\",[]]Bb[\"{{{AIAb{NNj}}}{{KGl{DC`}}}}\",[]]Bc[\"{{{AIAb{AJCd}}}{{KGl{DC`}}}}\",[]]Bb[\"{{{AIAb{NNl}}}{{KGl{DC`}}}}\",[]]Bc[\"{{{AIAb{AJCf}}}{{KGl{DC`}}}}\",[]]Bg[\"{{{AIAb{AINb}}}{{KHd{IClBCNn}}}}\",[]]Ba[\"{{{AIAb{BFEf}}KGnAHEf}DC`}\",[]]Ba[\"{{{AIAb{BFEh}}KH`AHEf}DC`}\",[]]Cf[\"{{{AIAb{CGdBDAl}}{KGl{ABBd}}}{{AIAb{CGdBDAl}}}}\",[]]Cf[\"{{{AIAb{CGdBDAn}}{KGl{ABBd}}}{{AIAb{CGdBDAn}}}}\",[]]A`[\"{AHGnCNb}\",[]]A`[\"{AHH`CNb}\",[]]Bo[\"{{{AIAb{CGdAINf}}NLdNLdDC`DC`CNbCNb}ICl}\",[]]Bn[\"{{{AIAb{CGdCNj}}NLdNLdDC`DC`CNbCNb}ICl}\",[]]Bn[\"{{{AIAb{CGdIDj}}NLdNLdDC`DC`CNbCNb}ICl}\",[]]Bn[\"{{{AIAb{CGdNM`}}NLdNLdDC`DC`CNbCNb}ICl}\",[]]Bo[\"{{{AIAb{CGdAINh}}NLdNLdDC`DC`CNbCNb}ICl}\",[]]Bn[\"{{{AIAb{CGdCNl}}NLdNLdDC`DC`CNbCNb}ICl}\",[]]Bn[\"{{{AIAb{CGdIDl}}NLdNLdDC`DC`CNbCNb}ICl}\",[]]Bn[\"{{{AIAb{CGdNMb}}NLdNLdDC`DC`CNbCNb}ICl}\",[]]A`[\"{BBAfCNb}\",[]]Al[\"{{{AIAb{NOj}}A@n}CNb}\",[]]Bi[\"{{{AIAb{CGdAJB`}}CNb}{{KHh{ICl}}}}\",[]]<;A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]10Cf[\"{{{AIAb{CGdBDAl}}{KGl{BFId}}}{{AIAb{CGdBDAl}}}}\",[]]Cf[\"{{{AIAb{CGdBDAn}}{KGl{BFIf}}}{{AIAb{CGdBDAn}}}}\",[]]A`[\"{AHGnCNb}\",[]]A`[\"{AHH`CNb}\",[]]Bi[\"{{{AIAb{CGdAILb}}}{{KHd{AAlIF`}}}}\",[]]Bi[\"{{{AIAb{CGdAILb}}}{{KHd{A@jIF`}}}}\",[]]Bj[\"{{{AIAb{CGdAILb}}}{{KHd{ABAfIF`}}}}\",[]]Bj[\"{{{AIAb{CGdAILb}}}{{KHd{AHD`IF`}}}}\",[]]Bi[\"{{{AIAb{CGdAILb}}}{{KHd{AAnIF`}}}}\",[]]Bi[\"{{{AIAb{CGdAILb}}}{{KHd{AB`IF`}}}}\",[]]Cd[\"{{{A@d{AIN`}}CNbDC`}{{KHh{{KCh{BCBlBCBl}}}}}}\",[]]Bn[\"{{{AIAb{CGdBADd}}CNb}{{AIAb{CGdBADd}}}}\",[]]Bn[\"{{{AIAb{CGdBADh}}CNb}{{AIAb{CGdBADh}}}}\",[]]Aj[\"{{{AIAb{AINb}}}DC`}\",[]]Ak[\"{{{AIAb{BBN`}}}BAAj}\",[]]0Ci[\"{{{AIAb{CGdBBOh}}CMn}{{KHd{{AIAb{CGdBBOh}}AOLl}}}}\",[]]Ci[\"{{{AIAb{CGdBBOj}}CMn}{{KHd{{AIAb{CGdBBOj}}AOLn}}}}\",[]]A`[\"{AHD`CMn}\",[]]Cf[\"{{{AIAb{CGdBDAl}}{KGl{BFKb}}}{{AIAb{CGdBDAl}}}}\",[]]Cf[\"{{{AIAb{CGdBDAn}}{KGl{BFKd}}}{{AIAb{CGdBDAn}}}}\",[]]Bg[\"{{{AIAb{CGdAINd}}}{{KGl{BBAd}}}}\",[]]Cf[\"{{{AIAb{CGdBADd}}{KGl{BADl}}}{{AIAb{CGdBADd}}}}\",[]]Cf[\"{{{AIAb{CGdBADh}}{KGl{BADl}}}{{AIAb{CGdBADh}}}}\",[]]Cf[\"{{{AIAb{CGdBADd}}{KGl{BADn}}}{{AIAb{CGdBADd}}}}\",[]]Cf[\"{{{AIAb{CGdBADh}}{KGl{BADn}}}{{AIAb{CGdBADh}}}}\",[]]Cd[\"{{}{{KHd{{KCh{{ABj{BBNb}}BBDj}}{A@f{IEf}}}}}}\",[]]Bn[\"{{{AIAb{CGdBFId}}KJ`}{{AIAb{CGdBFId}}}}\",[]]Bn[\"{{{AIAb{CGdBFIf}}KJb}{{AIAb{CGdBFIf}}}}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]Bo[\"{{{AIAb{CGdBADd}}ABBd}{{AIAb{CGdBADd}}}}\",[]]Bo[\"{{{AIAb{CGdBADh}}ABBd}{{AIAb{CGdBADh}}}}\",[]]Ci[\"{{{AIAb{DA`}}{KGl{ABBd}}}{{KHd{{KGl{ABBd}}AIMd}}}}\",[]]Bj[\"{{{AIAb{BCCd}}AJClKCj}{{KHf{ICl}}}}\",[]]054Bm[\"{{{AIAb{NNj}}}{{KHd{{KGl{BGCj}}IEn}}}}\",[]]Bn[\"{{{AIAb{AJCd}}}{{KHd{{KGl{BGCj}}IEn}}}}\",[]]Bn[\"{{{AIAb{NNl}}}{{KHd{{KGl{BGCj}}BCCb}}}}\",[]]Bo[\"{{{AIAb{AJCf}}}{{KHd{{KGl{BGCj}}BCCb}}}}\",[]]Aj[\"{{{AIAb{AHFl}}}CNb}\",[]]Aj[\"{{{AIAb{AHFn}}}CNb}\",[]]Bl[\"{{{AIAb{ABBh}}{KGl{{A@d{BFEf}}}}}ICl}\",[]]Cc[\"{{{AIAb{CGdAINd}}{KGl{{AIAb{BFEh}}}}NLd}ICl}\",[]]Aj[\"{{{AIAb{ABBj}}}CNb}\",[]]Bf[\"{{{AIAb{AHEf}}{AIAb{AHEf}}}DC`}\",[]]Bn[\"{{{AIAb{CGdBBCl}}AG`}{{AIAb{CGdBBCl}}}}\",[]]Bn[\"{{{AIAb{CGdBBCn}}AG`}{{AIAb{CGdBBCn}}}}\",[]]Ak[\"{{{AIAb{AHHl}}}BAAj}\",[]]0Ak[\"{{{AIAb{AINb}}}BGBl}\",[]]Cb[\"{{{AIAb{AINd}}KH`}{{KHd{{KGl{AJBh}}AIMj}}}}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]Bn[\"{{{AIAb{CGdBDAl}}KJ`}{{AIAb{CGdBDAl}}}}\",[]]Bn[\"{{{AIAb{CGdBDAn}}KJb}{{AIAb{CGdBDAn}}}}\",[]]A`[\"{AHD`KGl}\",[]]Bo[\"{{{AIAb{AHD`}}{AIAb{CMl}}}{{KHf{BDAj}}}}\",[]]Em[\"{{{AIAb{AINb}}{AIAb{CGd{KAb{Oj}}}}{AIAb{{KAb{Oj}}}}{AIAb{{KAb{Oj}}}}}{{KHd{IClBHNn}}}}\",[]]El[\"{{{AIAb{NNj}}{AIAb{CGd{KAb{Oj}}}}{AIAb{{KAb{Oj}}}}{AIAb{{KAb{Oj}}}}}{{KHd{IClBHNn}}}}\",[]]Em[\"{{{AIAb{AJCd}}{AIAb{CGd{KAb{Oj}}}}{AIAb{{KAb{Oj}}}}{AIAb{{KAb{Oj}}}}}{{KHd{IClBHNn}}}}\",[]]El[\"{{{AIAb{NNl}}{AIAb{CGd{KAb{Oj}}}}{AIAb{{KAb{Oj}}}}{AIAb{{KAb{Oj}}}}}{{KHd{IClBHO`}}}}\",[]]Em[\"{{{AIAb{AJCf}}{AIAb{CGd{KAb{Oj}}}}{AIAb{{KAb{Oj}}}}{AIAb{{KAb{Oj}}}}}{{KHd{IClBHO`}}}}\",[]]Am[\"{{{AIAb{CGdAHDd}}}ICl}\",[]]0Am[\"{{{AIAb{AINd}}A@n}CNb}\",[]]Ak[\"{{{AIAb{AINb}}}BGJn}\",[]]Bg[\"{{{AIAb{DA`}}}{{KHd{BEHdAIMd}}}}\",[]]>=E`[\"{{{AIAb{BHGb}}{AIAb{{KAb{Oj}}}}{AIAb{BBNb}}{AIAb{BHAj}}}{{KHd{BHIhIEj}}}}\",[]]0Bg[\"{{{A@d{BBEb}}}{{KHd{B@MhBGKj}}}}\",[]]Bg[\"{{{A@d{BBEb}}}{{KHd{B@MlBGKj}}}}\",[]]Bn[\"{{{AIAb{CGdBFId}}KJ`}{{AIAb{CGdBFId}}}}\",[]]Bn[\"{{{AIAb{CGdBFIf}}KJb}{{AIAb{CGdBFIf}}}}\",[]]o[\"{NK`NK`}\",[]]0000Aj[\"{{{AIAb{AINd}}}DC`}\",[]]0Bd[\"{{{AIAb{CGdAINf}}CNbABBd}ICl}\",[]]Bc[\"{{{AIAb{CGdCNj}}CNbABBd}ICl}\",[]]Bd[\"{{{AIAb{CGdAINh}}CNbABBd}ICl}\",[]]Bc[\"{{{AIAb{CGdCNl}}CNbABBd}ICl}\",[]]Ci[\"{{{AIAb{DA`}}{KGl{ABBd}}}{{KHd{{KGl{ABBd}}AIMd}}}}\",[]]Cd[\"{{{AIAb{CGdAAl}}{AIAb{AILb}}}{{KHd{IClIF`}}}}\",[]]Cd[\"{{{AIAb{CGdA@j}}{AIAb{AILb}}}{{KHd{IClIF`}}}}\",[]]Ce[\"{{{AIAb{CGdABAf}}{AIAb{AILb}}}{{KHd{IClIF`}}}}\",[]]Ce[\"{{{AIAb{CGdAHD`}}{AIAb{AILb}}}{{KHd{IClIF`}}}}\",[]]Cd[\"{{{AIAb{CGdAAn}}{AIAb{AILb}}}{{KHd{IClIF`}}}}\",[]]Cd[\"{{{AIAb{CGdAB`}}{AIAb{AILb}}}{{KHd{IClIF`}}}}\",[]]Bo[\"{{{AIAb{CGdBADd}}BHBh}{{AIAb{CGdBADd}}}}\",[]]Bo[\"{{{AIAb{CGdBADh}}BHBj}{{AIAb{CGdBADh}}}}\",[]]Aj[\"{{{AIAb{BBOh}}}CNb}\",[]]Aj[\"{{{AIAb{BBOj}}}CNb}\",[]]Cd[\"{{{AIAb{CGdB@Mh}}{A@d{A`}}}{{AIAb{CGdB@Mh}}}}\",[]]Cd[\"{{{AIAb{CGdB@Ml}}{A@d{A`}}}{{AIAb{CGdB@Ml}}}}\",[]]Cl[\"{{BBOh{KGl{BADd}}{A@f{BBN`}}{A@d{NNh}}}{{KHf{ABBh}}}}\",[]]Aj[\"{{{AIAb{ABC`}}}DC`}\",[]]Aj[\"{{{AIAb{ABCb}}}DC`}\",[]]Ca[\"{{{AIAb{CGdAINd}}NLdAJ@`}{{KHd{IClBHFh}}}}\",[]]Bd[\"{{{AIAb{BHGb}}}{{ABj{BDAb}}}}\",[]]Bj[\"{{{AIAb{AINb}}AJBh}{{KHd{IClIFj}}}}\",[]]Bm[\"{{{AIAb{CGdAINd}}AJBh}{{KHd{IClIFj}}}}\",[]]Bn[\"{{{AIAb{CGdBDAl}}KCj}{{AIAb{CGdBDAl}}}}\",[]]Bn[\"{{{AIAb{CGdBDAn}}KCj}{{AIAb{CGdBDAn}}}}\",[]]Bm[\"{{}{{KHd{{KCh{BBNbBFKn}}{A@f{IEf}}}}}}\",[]]Ai[\"{AIN`{{KHh{ICl}}}}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]An[\"{{{AIAb{ABBh}}B@Mh}ICl}\",[]]Dc[\"{{{AIAb{CGdAINd}}NLdKH`{KGl{ABBd}}}{{KHd{{KGl{ABBd}}AIMj}}}}\",[]]o[\"{{}AIAb}\",[]]Aj[\"{{{AIAb{AINb}}}KCj}\",[]]Bn[\"{{{AIAb{CGdBADd}}CNb}{{AIAb{CGdBADd}}}}\",[]]Bn[\"{{{AIAb{CGdBADh}}CNb}{{AIAb{CGdBADh}}}}\",[]]Bn[\"{{{AIAb{CGdBDAl}}KJ`}{{AIAb{CGdBDAl}}}}\",[]]Bn[\"{{{AIAb{CGdBDAn}}KJb}{{AIAb{CGdBDAn}}}}\",[]]:Bc[\"{{{AIAb{CGdAINd}}A@nKJb}ICl}\",[]]A`[\"{AHGnCNb}\",[]]A`[\"{AHH`CNb}\",[]]Ak[\"{{}{{KHd{B@MhIFd}}}}\",[]]Ak[\"{{}{{KHd{B@MlIFf}}}}\",[]]Bn[\"{{{AIAb{CGdBDAl}}DC`}{{AIAb{CGdBDAl}}}}\",[]]Bn[\"{{{AIAb{CGdBDAn}}DC`}{{AIAb{CGdBDAn}}}}\",[]]87Cg[\"{{{AIAb{CGdAAl}}{AIAb{CGdAILb}}}{{KHd{IClIF`}}}}\",[]]Cg[\"{{{AIAb{CGdA@j}}{AIAb{CGdAILb}}}{{KHd{IClIF`}}}}\",[]]Ch[\"{{{AIAb{CGdABAf}}{AIAb{CGdAILb}}}{{KHd{IClIF`}}}}\",[]]Ch[\"{{{AIAb{CGdAHD`}}{AIAb{CGdAILb}}}{{KHd{IClIF`}}}}\",[]]Cg[\"{{{AIAb{CGdAAn}}{AIAb{CGdAILb}}}{{KHd{IClIF`}}}}\",[]]Cg[\"{{{AIAb{CGdAB`}}{AIAb{CGdAILb}}}{{KHd{IClIF`}}}}\",[]]Bi[\"{{{AIAb{CGdAHEf}}{AIAb{AHEf}}}DC`}\",[]]n[\"{{}ICl}\",[]]Ce[\"{{{AIAb{CGdBDAl}}{KGl{KCj}}}{{AIAb{CGdBDAl}}}}\",[]]Ce[\"{{{AIAb{CGdBDAn}}{KGl{KCj}}}{{AIAb{CGdBDAn}}}}\",[]]Bn[\"{{{AIAb{AINb}}}{{KHd{{ABj{AJBh}}IFj}}}}\",[]]Cd[\"{{{AIAb{CGdAINd}}NLd}{{KHd{{ABj{AJBh}}IFj}}}}\",[]]Am[\"{{{AIAb{CGdAINf}}}ICl}\",[]]Al[\"{{{AIAb{CGdCNj}}}ICl}\",[]]Al[\"{{{AIAb{CGdIDj}}}ICl}\",[]]Am[\"{{{AIAb{CGdAINh}}}ICl}\",[]]Al[\"{{{AIAb{CGdCNl}}}ICl}\",[]]Al[\"{{{AIAb{CGdIDl}}}ICl}\",[]]Ai[\"{AIN`{{KHh{ICl}}}}\",[]]Bj[\"{{{AIAb{AINb}}AJBh}{{KHd{IClIFj}}}}\",[]]Bm[\"{{{AIAb{CGdAINd}}AJBh}{{KHd{IClIFj}}}}\",[]]D`[\"{{{AIAb{CGdAINd}}KH`{KGl{ABBd}}}{{KHd{{KGl{ABBd}}AIMj}}}}\",[]]Cf[\"{{{AIAb{CGdBDAl}}{A@d{BFDf}}}{{AIAb{CGdBDAl}}}}\",[]]Cf[\"{{{AIAb{CGdBDAn}}{A@d{BFDh}}}{{AIAb{CGdBDAn}}}}\",[]]Cf[\"{{{AIAb{CGdBDAl}}{KGl{ABBd}}}{{AIAb{CGdBDAl}}}}\",[]]Cf[\"{{{AIAb{CGdBDAn}}{KGl{ABBd}}}{{AIAb{CGdBDAn}}}}\",[]]Ce[\"{{{AIAb{CGdBDAl}}{KGl{CNb}}}{{AIAb{CGdBDAl}}}}\",[]]Ce[\"{{{AIAb{CGdBDAn}}{KGl{CNb}}}{{AIAb{CGdBDAn}}}}\",[]]Aa[\"{BBAjAIOn}\",[]]Aa[\"{BBAlAIOn}\",[]];Bn[\"{{{AIAb{CGdBDAl}}DC`}{{AIAb{CGdBDAl}}}}\",[]]Bn[\"{{{AIAb{CGdBDAn}}DC`}{{AIAb{CGdBDAn}}}}\",[]]Am[\"{{{AIAb{AINb}}KJ`}ICl}\",[]]o[\"{NK`NK`}\",[]]Bn[\"{{{AIAb{CGdBDAl}}CN`}{{AIAb{CGdBDAl}}}}\",[]]Bn[\"{{{AIAb{CGdBDAn}}CN`}{{AIAb{CGdBDAn}}}}\",[]]310;:54Bn[\"{{{AIAb{AINb}}}{{KHd{{ABj{AJBh}}IFj}}}}\",[]]Bn[\"{{{AIAb{AINd}}}{{KHd{{ABj{AJBh}}IFj}}}}\",[]]10Bm[\"{{{AIAb{CGdBDAl}}Oj}{{AIAb{CGdBDAl}}}}\",[]]Bm[\"{{{AIAb{CGdBDAn}}Oj}{{AIAb{CGdBDAn}}}}\",[]]A`[\"{BGCjKGl}\",[]]") \ No newline at end of file diff --git a/pr/617/docs/search.index/function/c06b3b2c2e2b.js b/pr/617/docs/search.index/function/c06b3b2c2e2b.js deleted file mode 100644 index 270c33047..000000000 --- a/pr/617/docs/search.index/function/c06b3b2c2e2b.js +++ /dev/null @@ -1 +0,0 @@ -rd_("Bf[\"{{{AIAb{AOLf}}{AIAb{AOLf}}}DC`}\",[]]Bf[\"{{{AIAb{BFFf}}{AIAb{BFFf}}}DC`}\",[]]Bd[\"{{{AIAb{KFl}}{AIAb{KFl}}}DC`}\",[]]Bd[\"{{{AIAb{DA`}}{AIAb{DA`}}}DC`}\",[]]Bf[\"{{{AIAb{BCCf}}{AIAb{BCCf}}}DC`}\",[]]Bf[\"{{{AIAb{BCBh}}{AIAb{BCBh}}}DC`}\",[]]Bf[\"{{{AIAb{AHHd}}{AIAb{AHHd}}}DC`}\",[]]Bf[\"{{{AIAb{AJAj}}{AIAb{AJAj}}}DC`}\",[]]Bf[\"{{{AIAb{BCBf}}{AIAb{BCBf}}}DC`}\",[]]Bf[\"{{{AIAb{AJCn}}{AIAb{AJCn}}}DC`}\",[]]Bf[\"{{{AIAb{BAE`}}{AIAb{BAE`}}}DC`}\",[]]Bd[\"{{{AIAb{KJ`}}{AIAb{KJ`}}}DC`}\",[]]Bf[\"{{{AIAb{BGKh}}{AIAb{BGKh}}}DC`}\",[]]Bd[\"{{{AIAb{IDb}}{AIAb{IDb}}}DC`}\",[]]Bd[\"{{{AIAb{KGn}}{AIAb{KGn}}}DC`}\",[]]Bf[\"{{{AIAb{AJ@l}}{AIAb{AJ@l}}}DC`}\",[]]Bf[\"{{{AIAb{AHGj}}{AIAb{AHGj}}}DC`}\",[]]Bf[\"{{{AIAb{BFMb}}{AIAb{BFMb}}}DC`}\",[]]Bf[\"{{{AIAb{AIMd}}{AIAb{AIMd}}}DC`}\",[]]Bf[\"{{{AIAb{ABFn}}{AIAb{ABFn}}}DC`}\",[]]Bf[\"{{{AIAb{AINn}}{AIAb{AINn}}}DC`}\",[]]Bf[\"{{{AIAb{AHGn}}{AIAb{AHGn}}}DC`}\",[]]Bd[\"{{{AIAb{O@f}}{AIAb{O@f}}}DC`}\",[]]Bf[\"{{{AIAb{B@Nd}}{AIAb{B@Nd}}}DC`}\",[]]Bf[\"{{{AIAb{BCO`}}{AIAb{BCO`}}}DC`}\",[]]Bf[\"{{{AIAb{AHGb}}{AIAb{AHGb}}}DC`}\",[]]Bf[\"{{{AIAb{BBNd}}{AIAb{BBNd}}}DC`}\",[]]Bf[\"{{{AIAb{AON`}}{AIAb{AON`}}}DC`}\",[]]Bf[\"{{{AIAb{AOLl}}{AIAb{AOLl}}}DC`}\",[]]Bf[\"{{{AIAb{AHEh}}{AIAb{AHEh}}}DC`}\",[]]Bf[\"{{{AIAb{BCNj}}{AIAb{BCNj}}}DC`}\",[]]Bf[\"{{{AIAb{BEHj}}{AIAb{BEHj}}}DC`}\",[]]Bf[\"{{{AIAb{B@O`}}{AIAb{B@O`}}}DC`}\",[]]Bf[\"{{{AIAb{B@Of}}{AIAb{B@Of}}}DC`}\",[]]Bf[\"{{{AIAb{BA@`}}{AIAb{BA@`}}}DC`}\",[]]Bf[\"{{{AIAb{BFMl}}{AIAb{BFMl}}}DC`}\",[]]Bd[\"{{{AIAb{DAl}}{AIAb{DAl}}}DC`}\",[]]Bd[\"{{{AIAb{A@l}}{AIAb{A@l}}}DC`}\",[]]Bf[\"{{{AIAb{ABEd}}{AIAb{ABEd}}}DC`}\",[]]Bf[\"{{{AIAb{BHO`}}{AIAb{BHO`}}}DC`}\",[]]Bd[\"{{{AIAb{IFd}}{AIAb{IFd}}}DC`}\",[]]Bd[\"{{{AIAb{KJb}}{AIAb{KJb}}}DC`}\",[]]Bf[\"{{{AIAb{BGKj}}{AIAb{BGKj}}}DC`}\",[]]Bd[\"{{{AIAb{IDd}}{AIAb{IDd}}}DC`}\",[]]Bf[\"{{{AIAb{BFFh}}{AIAb{BFFh}}}DC`}\",[]]Bd[\"{{{AIAb{KH`}}{AIAb{KH`}}}DC`}\",[]]Bf[\"{{{AIAb{AJ@n}}{AIAb{AJ@n}}}DC`}\",[]]Bf[\"{{{AIAb{AHGl}}{AIAb{AHGl}}}DC`}\",[]]Bf[\"{{{AIAb{BFFd}}{AIAb{BFFd}}}DC`}\",[]]Bf[\"{{{AIAb{BFMd}}{AIAb{BFMd}}}DC`}\",[]]Bf[\"{{{AIAb{AIMj}}{AIAb{AIMj}}}DC`}\",[]]Bf[\"{{{AIAb{ABG`}}{AIAb{ABG`}}}DC`}\",[]]Bf[\"{{{AIAb{AIO`}}{AIAb{AIO`}}}DC`}\",[]]Bf[\"{{{AIAb{AHH`}}{AIAb{AHH`}}}DC`}\",[]]Bf[\"{{{AIAb{AHHf}}{AIAb{AHHf}}}DC`}\",[]]Bf[\"{{{AIAb{BBDl}}{AIAb{BBDl}}}DC`}\",[]]Bd[\"{{{AIAb{O@h}}{AIAb{O@h}}}DC`}\",[]]Bf[\"{{{AIAb{AJD`}}{AIAb{AJD`}}}DC`}\",[]]Bf[\"{{{AIAb{AOMn}}{AIAb{AOMn}}}DC`}\",[]]Bf[\"{{{AIAb{B@@d}}{AIAb{B@@d}}}DC`}\",[]]Bf[\"{{{AIAb{BCBj}}{AIAb{BCBj}}}DC`}\",[]]Bf[\"{{{AIAb{B@Nl}}{AIAb{B@Nl}}}DC`}\",[]]Bf[\"{{{AIAb{BCOb}}{AIAb{BCOb}}}DC`}\",[]]Bf[\"{{{AIAb{AHGd}}{AIAb{AHGd}}}DC`}\",[]]Bf[\"{{{AIAb{BBNf}}{AIAb{BBNf}}}DC`}\",[]]Bf[\"{{{AIAb{AONb}}{AIAb{AONb}}}DC`}\",[]]Bf[\"{{{AIAb{AOLn}}{AIAb{AOLn}}}DC`}\",[]]Bf[\"{{{AIAb{AHEj}}{AIAb{AHEj}}}DC`}\",[]]Bf[\"{{{AIAb{BC@d}}{AIAb{BC@d}}}DC`}\",[]]Bf[\"{{{AIAb{AIOl}}{AIAb{AIOl}}}DC`}\",[]]Bf[\"{{{AIAb{AJBj}}{AIAb{AJBj}}}DC`}\",[]]Bf[\"{{{AIAb{B@Oj}}{AIAb{B@Oj}}}DC`}\",[]]Bf[\"{{{AIAb{BCNn}}{AIAb{BCNn}}}DC`}\",[]]Bf[\"{{{AIAb{BEHn}}{AIAb{BEHn}}}DC`}\",[]]Bf[\"{{{AIAb{BEIf}}{AIAb{BEIf}}}DC`}\",[]]Bf[\"{{{AIAb{B@Ob}}{AIAb{B@Ob}}}DC`}\",[]]Bf[\"{{{AIAb{ABCh}}{AIAb{ABCh}}}DC`}\",[]]Bf[\"{{{AIAb{BFEn}}{AIAb{BFEn}}}DC`}\",[]]Bf[\"{{{AIAb{B@Oh}}{AIAb{B@Oh}}}DC`}\",[]]Bf[\"{{{AIAb{BA@b}}{AIAb{BA@b}}}DC`}\",[]]Bf[\"{{{AIAb{BCCb}}{AIAb{BCCb}}}DC`}\",[]]Bf[\"{{{AIAb{BFMn}}{AIAb{BFMn}}}DC`}\",[]]Bd[\"{{{AIAb{DAn}}{AIAb{DAn}}}DC`}\",[]]Bd[\"{{{AIAb{A@n}}{AIAb{A@n}}}DC`}\",[]]Bf[\"{{{AIAb{ABEf}}{AIAb{ABEf}}}DC`}\",[]]Bf[\"{{{AIAb{AHEf}}{AIAb{AHEf}}}DC`}\",[]]Bf[\"{{{AIAb{BBEh}}{AIAb{BBEh}}}DC`}\",[]]Bf[\"{{{AIAb{BGCl}}{AIAb{BGCl}}}DC`}\",[]]Bd[\"{{{AIAb{IFh}}{AIAb{IFh}}}DC`}\",[]]Bf[\"{{{AIAb{BHOb}}{AIAb{BHOb}}}DC`}\",[]]Bd[\"{{{AIAb{IFf}}{AIAb{IFf}}}DC`}\",[]]Bf[\"{{{AIAb{BA@d}}{AIAb{BA@d}}}DC`}\",[]]Ai[\"{{{AIAb{DA`}}}KGn}\",[]]Aj[\"{{{AIAb{BCCf}}}KGn}\",[]]Ak[\"{{{AIAb{AJAf}}}ABEd}\",[]]Ak[\"{{{AIAb{BGCn}}}ABEd}\",[]]Ak[\"{{{AIAb{AJBb}}}ABEd}\",[]]o[\"{KG`KGn}\",[]]A`[\"{AHBdKGn}\",[]]A`[\"{AHDjKGn}\",[]]A`[\"{BACbKGn}\",[]]A`[\"{BAB`KGn}\",[]]o[\"{KGbKH`}\",[]]A`[\"{AHBfKH`}\",[]]A`[\"{AHDlKH`}\",[]]A`[\"{BACdKH`}\",[]]A`[\"{BABbKH`}\",[]]Aa[\"{ABDlABEf}\",[]]Aa[\"{ABGdABEf}\",[]]Aa[\"{ABBlABEf}\",[]]A`[\"{NNnABEf}\",[]]A`[\"{AOOfNNf}\",[]]A`[\"{D@hABCh}\",[]]Ac[\"{{ABFnABFn}}\",[]]Ac[\"{{AINnAINn}}\",[]]Ac[\"{{BCOhAHGn}}\",[]]Bc[\"{{ABG`ABG`}c{}}\",[\"Add::Output\"]]Bc[\"{{AIO`AIO`}c{}}\",[\"Add::Output\"]]Bc[\"{{BCOjAHH`}c{}}\",[\"Add::Output\"]]Be[\"{{{AIAb{KJ`}}{AIAb{KJ`}}}ABDj}\",[]]Be[\"{{{AIAb{KGn}}{AIAb{KGn}}}ABDj}\",[]]Bg[\"{{{AIAb{AON`}}{AIAb{AON`}}}ABDj}\",[]]Bg[\"{{{AIAb{B@Of}}{AIAb{B@Of}}}ABDj}\",[]]Be[\"{{{AIAb{DAl}}{AIAb{DAl}}}ABDj}\",[]]Be[\"{{{AIAb{A@l}}{AIAb{A@l}}}ABDj}\",[]]Bg[\"{{{AIAb{ABEd}}{AIAb{ABEd}}}ABDj}\",[]]Be[\"{{{AIAb{KJb}}{AIAb{KJb}}}ABDj}\",[]]Bg[\"{{{AIAb{BFFh}}{AIAb{BFFh}}}ABDj}\",[]]Be[\"{{{AIAb{KH`}}{AIAb{KH`}}}ABDj}\",[]]Bg[\"{{{AIAb{AHHf}}{AIAb{AHHf}}}ABDj}\",[]]Bg[\"{{{AIAb{BBDl}}{AIAb{BBDl}}}ABDj}\",[]]Bg[\"{{{AIAb{AJD`}}{AIAb{AJD`}}}ABDj}\",[]]Bg[\"{{{AIAb{AONb}}{AIAb{AONb}}}ABDj}\",[]]Bg[\"{{{AIAb{BEIf}}{AIAb{BEIf}}}ABDj}\",[]]Bg[\"{{{AIAb{BFEn}}{AIAb{BFEn}}}ABDj}\",[]]Bg[\"{{{AIAb{B@Oh}}{AIAb{B@Oh}}}ABDj}\",[]]Be[\"{{{AIAb{DAn}}{AIAb{DAn}}}ABDj}\",[]]Be[\"{{{AIAb{A@n}}{AIAb{A@n}}}ABDj}\",[]]Bg[\"{{{AIAb{ABEf}}{AIAb{ABEf}}}ABDj}\",[]]A`[\"{ABEdA@l}\",[]]A`[\"{ABEfA@n}\",[]]o[\"{KGdA@n}\",[]]A`[\"{AHCfA@n}\",[]]A`[\"{ABFhKGl}\",[]]A`[\"{ABFjKGl}\",[]]A`[\"{ABE`KGl}\",[]]A`[\"{ABFlKGl}\",[]]Bh[\"{{{AIAb{AAl}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{AOOf}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{AOLf}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BCBl}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BHGd}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BGK`}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{AIMn}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{AINb}}{AIAb{CGdAHEd}}}KHb}\",[]]Bh[\"{{{AIAb{KEl}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BGKn}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BFFf}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{ABBh}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BBAf}}{AIAb{CGdAHEd}}}KHb}\",[]]Bh[\"{{{AIAb{KFl}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{AJ@j}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BGBn}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{ABC`}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{AJAl}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{BC@b}}{AIAb{CGdAHEd}}}KHb}\",[]]Bh[\"{{{AIAb{DA`}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BCCf}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{AJAf}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BGCn}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BCBh}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{AHHd}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{AJAj}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{BCBf}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{BAEb}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{AOOl}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{AJBb}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{AJCn}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{BAE`}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bh[\"{{{AIAb{NOb}}{AIAb{CGdAHEd}}}KHb}\",[]]Cd[\"{{{AIAb{KJ`}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]0Ce[\"{{{AIAb{BGKh}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]0Cd[\"{{{AIAb{IDb}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Cd[\"{{{AIAb{KGn}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]0Ce[\"{{{AIAb{AJ@l}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Ce[\"{{{AIAb{AHGj}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Ce[\"{{{AIAb{BFMb}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]0Ce[\"{{{AIAb{AIMd}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]0Ce[\"{{{AIAb{ABFn}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Ce[\"{{{AIAb{AINn}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Ce[\"{{{AIAb{AHGn}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Ce[\"{{{AIAb{BCOh}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Cd[\"{{{AIAb{O@f}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Ce[\"{{{AIAb{B@Nd}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]0Ce[\"{{{AIAb{BCO`}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]0Ce[\"{{{AIAb{AHGb}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]0Ce[\"{{{AIAb{BBNd}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]0Ce[\"{{{AIAb{BD@l}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Ce[\"{{{AIAb{BDAn}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Ce[\"{{{AIAb{BFIf}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Ce[\"{{{AIAb{BFKd}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Ce[\"{{{AIAb{AON`}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Ce[\"{{{AIAb{BBOh}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Ce[\"{{{AIAb{BADd}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Ce[\"{{{AIAb{BHBj}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Ce[\"{{{AIAb{B@Mh}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Ce[\"{{{AIAb{AOLl}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]0Ce[\"{{{AIAb{AHEh}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]0Ce[\"{{{AIAb{BCNj}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]0Ce[\"{{{AIAb{BEHj}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]0Ce[\"{{{AIAb{B@O`}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]0Ce[\"{{{AIAb{B@Of}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]0Ce[\"{{{AIAb{BA@`}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Ce[\"{{{AIAb{BFMl}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]0Ce[\"{{{AIAb{AIOd}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Ce[\"{{{AIAb{BELn}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Cd[\"{{{AIAb{DAl}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Cd[\"{{{AIAb{A@l}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]0Ce[\"{{{AIAb{ABEd}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]0Ce[\"{{{AIAb{ABFh}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Cd[\"{{{AIAb{CNj}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Ce[\"{{{AIAb{AILl}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Cd[\"{{{AIAb{IDj}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Ce[\"{{{AIAb{AOMf}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Cd[\"{{{AIAb{NM`}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Ce[\"{{{AIAb{BBCl}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Ce[\"{{{AIAb{BHO`}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Ce[\"{{{AIAb{AOMb}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Ce[\"{{{AIAb{BFNd}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]Cd[\"{{{AIAb{IFd}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]0Ce[\"{{{AIAb{BGJl}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]0Bi[\"{{{AIAb{AINd}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{ABBj}}{AIAb{CGdAHEd}}}KHb}\",[]]Bh[\"{{{AIAb{KJb}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{BGKj}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bh[\"{{{AIAb{IDd}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BFFh}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bh[\"{{{AIAb{KH`}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{BACl}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{AJ@n}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{AHGl}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BFFd}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BFMd}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{AIMj}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{ABG`}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{AIO`}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{AHH`}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BCOj}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{AHHf}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{BBDl}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bh[\"{{{AIAb{O@h}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{AJD`}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{AOMn}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{B@@d}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BCBj}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{B@Nl}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{BCOb}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{AHGd}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{BBNf}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{BHFj}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bh[\"{{{AIAb{IFn}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BD@n}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BDB`}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BFIh}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BFKf}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{AONb}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BBOj}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BADh}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BHBl}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{B@Ml}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{AOLn}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{AHEj}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{BC@d}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{AIOl}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{AJBj}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{B@Oj}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{BCNn}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{BEHn}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{ABAl}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{BEIf}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{ABCb}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{B@Ob}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{AOLb}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{AJAn}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{BBDb}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BD@j}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BHGb}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{ABCh}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BFEn}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{BCOf}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BBAd}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{B@Oh}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{BA@b}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BCCb}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{BFMn}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{AIOf}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BICj}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BEM`}}{AIAb{CGdAHEd}}}KHb}\",[]]Bh[\"{{{AIAb{DAn}}{AIAb{CGdAHEd}}}KHb}\",[]]Bh[\"{{{AIAb{A@n}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{ABEf}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{ABFj}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{AHEf}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{BBEh}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{BGCl}}{AIAb{CGdAHEd}}}KHb}\",[]]Bh[\"{{{AIAb{IFh}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bi[\"{{{AIAb{BHOb}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{AOMd}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BFNj}}{AIAb{CGdAHEd}}}KHb}\",[]]Cd[\"{{{AIAb{IFf}}{AIAb{CGdAHEd}}}{{KHd{IClIEd}}}}\",[]]0Bi[\"{{{AIAb{BGJn}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bh[\"{{{AIAb{CNl}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{AILn}}{AIAb{CGdAHEd}}}KHb}\",[]]Bh[\"{{{AIAb{IDl}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{AOMh}}{AIAb{CGdAHEd}}}KHb}\",[]]Bh[\"{{{AIAb{NMb}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BBCn}}{AIAb{CGdAHEd}}}KHb}\",[]]Bh[\"{{{AIAb{IFj}}{AIAb{CGdAHEd}}}KHb}\",[]]0Bh[\"{{{AIAb{IG`}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BCCd}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{ABE`}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{ABFl}}{AIAb{CGdAHEd}}}KHb}\",[]]Bi[\"{{{AIAb{BA@d}}{AIAb{CGdAHEd}}}KHb}\",[]]Bd[\"{{{AIAb{BGK`}}}{{KGl{AJBh}}}}\",[]]Ak[\"{{{AIAb{BACl}}}ABBd}\",[]]A`[\"{ABFnCNb}\",[]]A`[\"{ABG`CNb}\",[]]Aj[\"{{{AIAb{BBDb}}}KCj}\",[]]A`[\"{BHGbCNb}\",[]]o[\"{D@hCNb}\",[]]A`[\"{ABE`KCj}\",[]]Cf[\"{{{AIAb{CGdBHBj}}{A@d{ABF`}}}{{AIAb{CGdBHBj}}}}\",[]]Cf[\"{{{AIAb{CGdBHBl}}{A@d{ABFb}}}{{AIAb{CGdBHBl}}}}\",[]]8Af[\"{{ABBdCNb}BCBl}\",[]]Ah[\"{{}{{A@d{BHGd}}}}\",[]]Ae[\"{{KCjCN`}BB@d}\",[]]Ce[\"{{BBOh{KGl{BADd}}AHHn{A@d{NNh}}}{{KHf{ABBh}}}}\",[]]Ai[\"{{{A@f{IId}}}AJA`}\",[]]A`[\"{NN`BD@l}\",[]]Ai[\"{{{A@d{NKn}}}BBOh}\",[]]Be[\"{{{A@d{BADf}}{A@d{BFE`}}}BADd}\",[]]Aj[\"{{{A@d{B@Mj}}}B@Mh}\",[]]B`[\"{{{AIAb{{KAb{Oj}}}}}B@Of}\",[]]Ae[\"{{CN`KCj}BELn}\",[]]Ah[\"{{DAlA@lCNb}ABEd}\",[]]Ao[\"{{{A@d{AOMf}}NLdCMn}IDj}\",[]]Ao[\"{{{A@d{BBCl}}NLdCMn}NM`}\",[]]Ae[\"{{KCjCN`}BB@f}\",[]]Ai[\"{{{A@f{IId}}}AJAb}\",[]]Bo[\"{{{A@d{BBOj}}{KGl{{A@d{BADh}}}}DC`}ABBj}\",[]]A`[\"{KCjBIG`}\",[]]o[\"{{}BIC`}\",[]]A`[\"{NN`BD@n}\",[]]Ai[\"{{{A@d{NL`}}}BBOj}\",[]]Be[\"{{{A@d{BADj}}{A@d{BFEb}}}BADh}\",[]]Aj[\"{{{A@d{B@Mn}}}B@Ml}\",[]]Ek[\"{{ABAb{AIAb{c}}{AIAb{{KAb{CN`}}}}DC`}{{KHd{{KCh{BBDb{KGl{ABAb}}}}BFEn}}}{BFJbIHj}}\",[\"\"]]B`[\"{{{AIAb{{KAb{Oj}}}}}B@Oh}\",[]]Af[\"{{BFMnKIl}BCCb}\",[]]A`[\"{KCjBICj}\",[]]Ae[\"{{CN`KCj}BEM`}\",[]]Ah[\"{{DAnA@nCNb}ABEf}\",[]]Am[\"{{AJBh{KGl{KFj}}}AHEf}\",[]]Ao[\"{{{A@d{AOMh}}NLdCMn}IDl}\",[]]Ao[\"{{{A@d{BBCn}}NLdCMn}NMb}\",[]]Aj[\"{AJCl{{KHf{BCCd}}}}\",[]]Aa[\"{BELhBHMd}\",[]]Aa[\"{BELlBHMf}\",[]]o[\"{DAlDAl}\",[]]o[\"{DAnDAn}\",[]]Ak[\"{{{AIAb{AJC`}}}AJBn}\",[]]Ai[\"{{{AIAb{NNh}}}NLd}\",[]]0Aj[\"{{{AIAb{BAEb}}}NLd}\",[]]Ak[\"{{{AIAb{BBEd}}}AJBn}\",[]]Ak[\"{{{AIAb{AJCb}}}AJBn}\",[]]Ak[\"{{{AIAb{BBEf}}}AJBn}\",[]]Bg[\"{{{AIAb{AINb}}KGn}{{KGl{ABBd}}}}\",[]]Aa[\"{AHGnABBd}\",[]]Bg[\"{{{AIAb{AINd}}KH`}{{KGl{ABBd}}}}\",[]]Aa[\"{AHH`ABBd}\",[]]Ah[\"{AAn{{KHh{ICl}}}}\",[]]Ah[\"{AB`{{KHh{ICl}}}}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]Aa[\"{BAB`AJBh}\",[]]Aa[\"{BABbAJBh}\",[]]Aa[\"{ABE`AJBh}\",[]]Aa[\"{BCCbBFMn}\",[]]A`[\"{AHGnCNb}\",[]]A`[\"{AHH`CNb}\",[]]A`[\"{ABAlID`}\",[]]Am[\"{{{AIAb{CGdABDf}}}ICl}\",[]]Am[\"{{{AIAb{CGdABC`}}}ICl}\",[]]Al[\"{{{AIAb{CGdDA`}}}ICl}\",[]]Am[\"{{{AIAb{CGdBCCf}}}ICl}\",[]]Am[\"{{{AIAb{CGdAJAf}}}ICl}\",[]]Am[\"{{{AIAb{CGdAJBb}}}ICl}\",[]]Al[\"{{{AIAb{CGdKEd}}}ICl}\",[]]A`[\"{cc{}}\",[\"T\"]]00000000000000000000000Aa[\"{BCO`BFFf}\",[]]11111111111111Aa[\"{AHHdBCBh}\",[]]2Aa[\"{AJAjAHHd}\",[]]Aa[\"{BCO`AHHd}\",[]]4Aa[\"{BCO`AJAj}\",[]]55Aa[\"{AHHdBCBf}\",[]]666Aa[\"{B@NdAJCn}\",[]]Aa[\"{BAE`AJCn}\",[]]Aa[\"{BCO`AJCn}\",[]]99Aa[\"{BCO`BAE`}\",[]]:::::::A`[\"{ABEdKJ`}\",[]]n[\"{OjKJ`}\",[]]A`[\"{BDAfIFd}\",[]]A`[\"{BHNnIFd}\",[]]A`[\"{BEI`IFd}\",[]]A`[\"{BEL`IFd}\",[]]A`[\"{BCAhIFd}\",[]]A`[\"{BI@`IFd}\",[]]A`[\"{BEJfIFd}\",[]]A`[\"{BC@fIFd}\",[]]A`[\"{cc{}}\",[\"T\"]]00000000000000000000000o[\"{CN`KJb}\",[]]n[\"{OjKJb}\",[]]o[\"{CMnKJb}\",[]]A`[\"{BFMnKJb}\",[]]A`[\"{ABEfKJb}\",[]]5555Al[\"{cKH`{{D@b{CN`}}}}\",[\"T\"]]6666666666666666666Aa[\"{BCCbBCOb}\",[]]7777A`[\"{AHGlIFn}\",[]]8888A`[\"{KJbAONb}\",[]]99999Aa[\"{BDBbAOLn}\",[]]Aa[\"{BGKjAOLn}\",[]];;;;;;Aa[\"{BCCbBCNn}\",[]]<<<<>>>>A`[\"{IFhBCCb}\",[]]?A`[\"{KJbBFMn}\",[]]A`[\"{cc{}}\",[\"T\"]]000000A`[\"{KJbABEf}\",[]]11111A`[\"{BBEhIFh}\",[]]2222Aa[\"{B@@jAOMd}\",[]]333A`[\"{BI@`IFf}\",[]]A`[\"{BEL`IFf}\",[]]A`[\"{AJ@bIFf}\",[]]A`[\"{BCOlIFf}\",[]]7A`[\"{BDAfIFf}\",[]]A`[\"{BHNnIFf}\",[]]A`[\"{BHIhIFf}\",[]]A`[\"{BEJfIFf}\",[]]A`[\"{BC@fIFf}\",[]]A`[\"{BCAhIFf}\",[]]A`[\"{BEI`IFf}\",[]]>>>>>>>>>>>>An[\"{{{AIAb{c}}}AJClCNh}\",[\"S\"]]??????????????Bm[\"{{{AIAb{KJ`}}{AIAb{CGdc}}}IClKFh}\",[\"__H\"]]Bk[\"{{{AIAb{KGn}}{AIAb{CGdc}}}IClKFh}\",[\"H\"]]Bn[\"{{{AIAb{AON`}}{AIAb{CGdc}}}IClKFh}\",[\"__H\"]]Bn[\"{{{AIAb{B@Of}}{AIAb{CGdc}}}IClKFh}\",[\"__H\"]]Bm[\"{{{AIAb{DAl}}{AIAb{CGdc}}}IClKFh}\",[\"__H\"]]Bm[\"{{{AIAb{A@l}}{AIAb{CGdc}}}IClKFh}\",[\"__H\"]]Bn[\"{{{AIAb{ABEd}}{AIAb{CGdc}}}IClKFh}\",[\"__H\"]]Bm[\"{{{AIAb{KJb}}{AIAb{CGdc}}}IClKFh}\",[\"__H\"]]Bn[\"{{{AIAb{BFFh}}{AIAb{CGdc}}}IClKFh}\",[\"__H\"]]Bk[\"{{{AIAb{KH`}}{AIAb{CGdc}}}IClKFh}\",[\"H\"]]Bn[\"{{{AIAb{AHHf}}{AIAb{CGdc}}}IClKFh}\",[\"__H\"]]Bn[\"{{{AIAb{BBDl}}{AIAb{CGdc}}}IClKFh}\",[\"__H\"]]Bn[\"{{{AIAb{AJD`}}{AIAb{CGdc}}}IClKFh}\",[\"__H\"]]Bn[\"{{{AIAb{AONb}}{AIAb{CGdc}}}IClKFh}\",[\"__H\"]]Bn[\"{{{AIAb{BEIf}}{AIAb{CGdc}}}IClKFh}\",[\"__H\"]]Bn[\"{{{AIAb{BFEn}}{AIAb{CGdc}}}IClKFh}\",[\"__H\"]]Bn[\"{{{AIAb{B@Oh}}{AIAb{CGdc}}}IClKFh}\",[\"__H\"]]Bm[\"{{{AIAb{DAn}}{AIAb{CGdc}}}IClKFh}\",[\"__H\"]]Bm[\"{{{AIAb{A@n}}{AIAb{CGdc}}}IClKFh}\",[\"__H\"]]Bn[\"{{{AIAb{ABEf}}{AIAb{CGdc}}}IClKFh}\",[\"__H\"]]Bn[\"{{{AIAb{AHEf}}{AIAb{CGdc}}}IClKFh}\",[\"__H\"]]Aa[\"{{}c{}}\",[\"U\"]]0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000n[\"{{}ICl}\",[]]0000Cc[\"{{{AIAb{CGdKEd}}KCj}{{KHd{{KGl{IDd}}AHHf}}}}\",[]]Cf[\"{{{AIAb{CGdBBBj}}}{{KGl{c}}}{}}\",[\"Iterator::Item\"]]Dl[\"{{{AIAb{BFE`}}ICj{AIAb{CGd{KAb{Oj}}}}}{{KHd{{AIAb{{KAb{Oj}}}}AOMb}}}}\",[]]Bi[\"{{{AIAb{CGdNOj}}A@n}{{KGl{ABEf}}}}\",[]]Dl[\"{{{AIAb{BFEb}}ICj{AIAb{CGd{KAb{Oj}}}}}{{KHd{{AIAb{{KAb{Oj}}}}AOMd}}}}\",[]]Bf[\"{{{AIAb{AINb}}KGn}{{KGl{DA`}}}}\",[]]Bf[\"{{{AIAb{DA`}}}{{KHd{IClAIMd}}}}\",[]]A`[\"{AINnCNb}\",[]]Am[\"{{{AIAb{CGdAINd}}}ICl}\",[]]A`[\"{AIO`CNb}\",[]]Ck[\"{{{ABd{{AIAb{CGdAILd}}}}{AIAb{CGdNKb}}}{{DAh{ICl}}}}\",[]]Dk[\"{{{ABd{{AIAb{CGdBDBh}}}}{AIAb{CGdNKb}}}{{DAh{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{ABd{{AIAb{CGdABDf}}}}{AIAb{CGdNKb}}}{{DAh{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{ABd{{AIAb{CGdABDh}}}}{AIAb{CGdNKb}}}{{DAh{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{ABd{{AIAb{CGdAIMn}}}}{AIAb{CGdNKb}}}{{DAh{c}}}{}}\",[\"Future::Output\"]]Dj[\"{{{ABd{{AIAb{CGdNMf}}}}{AIAb{CGdNKb}}}{{DAh{c}}}{}}\",[\"Future::Output\"]]Dj[\"{{{ABd{{AIAb{CGdKFn}}}}{AIAb{CGdNKb}}}{{DAh{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{ABd{{AIAb{CGdAHBl}}}}{AIAb{CGdNKb}}}{{DAh{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{ABd{{AIAb{CGdAB@n}}}}{AIAb{CGdNKb}}}{{DAh{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{ABd{{AIAb{CGdBAC`}}}}{AIAb{CGdNKb}}}{{DAh{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{ABd{{AIAb{CGdBACn}}}}{AIAb{CGdNKb}}}{{DAh{c}}}{}}\",[\"Future::Output\"]]Dj[\"{{{ABd{{AIAb{CGdKDb}}}}{AIAb{CGdNKb}}}{{DAh{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{ABd{{AIAb{CGdBC@b}}}}{AIAb{CGdNKb}}}{{DAh{c}}}{}}\",[\"Future::Output\"]]Dj[\"{{{ABd{{AIAb{CGdNOb}}}}{AIAb{CGdNKb}}}{{DAh{c}}}{}}\",[\"Future::Output\"]]Bf[\"{{{AIAb{CGdAINd}}}{{KGl{IFn}}}}\",[]]De[\"{{{AIAb{CGdAJAf}}{AIAb{CGd{KAb{Oj}}}}}{{KHd{{KGl{KCj}}AHHd}}}}\",[]]Bm[\"{{{AIAb{CGdAJAh}}DC`}{{KHd{KEdBBDl}}}}\",[]]C`[\"{{DAn{AIAb{CGdc}}}{{KHd{BGClIFh}}}A@h}\",[\"R\"]]Bf[\"{{{AIAb{CGdAHDf}}}{{KGl{ID`}}}}\",[]]Ec[\"{{{AIAb{BCCd}}AJCl{AIAb{CGd{KAb{AIOh}}}}{AIAb{CGd{KAb{ABE`}}}}}{{KHf{KCj}}}}\",[]]Cn[\"{{{AIAb{BFE`}}ICj{AIAb{CGd{ABj{Oj}}}}}{{KHd{IClAOMb}}}}\",[]]Cn[\"{{{AIAb{BFEb}}ICj{AIAb{CGd{ABj{Oj}}}}}{{KHd{IClAOMd}}}}\",[]]C`[\"{{{AIAb{CGdAHDf}}ID`DC`}{{KHd{IClBFFh}}}}\",[]]Cc[\"{{{AIAb{BCCd}}AJCl{AIAb{ABFl}}}{{KHf{ICl}}}}\",[]]Bn[\"{{{AIAb{CGdBHBj}}CN`}{{AIAb{CGdBHBj}}}}\",[]]Bn[\"{{{AIAb{CGdBHBl}}CN`}{{AIAb{CGdBHBl}}}}\",[]]Aj[\"{{{AIAb{AINb}}}DAl}\",[]]Aj[\"{{{AIAb{AINd}}}DAn}\",[]]Cn[\"{{{AIAb{NKn}}{AIAb{{KAb{Oj}}}}{AIAb{CGd{KAb{Oj}}}}}ICl}\",[]]Cn[\"{{{AIAb{NL`}}{AIAb{{KAb{Oj}}}}{AIAb{CGd{KAb{Oj}}}}}ICl}\",[]]A`[\"{BCBlCNb}\",[]]A`[\"{ABFhKCj}\",[]]A`[\"{ABFjKCj}\",[]]o[\"{IHfDC`}\",[]]Bm[\"{{{AIAb{CGdAJAf}}KJ`}{{KHd{IClB@Nd}}}}\",[]]Bm[\"{{{AIAb{CGdBGCn}}KJ`}{{KHd{IClB@Nd}}}}\",[]]Bm[\"{{{AIAb{CGdAJAh}}KJb}{{KHd{IClB@Nl}}}}\",[]]Bn[\"{{{AIAb{AJCh}}{AIAb{CMl}}}{{KGl{ID`}}}}\",[]]Bn[\"{{{AIAb{BCAd}}{AIAb{CMl}}}{{KGl{ID`}}}}\",[]]Bn[\"{{{AIAb{BELn}}{AIAb{CMl}}}{{KGl{ID`}}}}\",[]]Bn[\"{{{AIAb{AJCj}}{AIAb{CMl}}}{{KGl{ID`}}}}\",[]]Bn[\"{{{AIAb{BCAf}}{AIAb{CMl}}}{{KGl{ID`}}}}\",[]]Bn[\"{{{AIAb{BEM`}}{AIAb{CMl}}}{{KGl{ID`}}}}\",[]]Bd[\"{{{AIAb{BEIj}}}{{KGl{BBBh}}}}\",[]]Bd[\"{{{AIAb{BEIl}}}{{KGl{BBBj}}}}\",[]]Bi[\"{{{A@d{BFDh}}NLdCMn}{{A@f{AINf}}}}\",[]]Bi[\"{{{A@d{AILl}}NLdCMn}{{A@f{AINf}}}}\",[]]Bi[\"{{{A@d{AOMf}}NLdCMn}{{A@f{AINf}}}}\",[]]Bi[\"{{{A@d{BBCl}}NLdCMn}{{A@f{AINf}}}}\",[]]Bi[\"{{{A@d{BFDj}}NLdCMn}{{A@f{AINh}}}}\",[]]Bi[\"{{{A@d{AILn}}NLdCMn}{{A@f{AINh}}}}\",[]]Bi[\"{{{A@d{AOMh}}NLdCMn}{{A@f{AINh}}}}\",[]]Bi[\"{{{A@d{BBCn}}NLdCMn}{{A@f{AINh}}}}\",[]]Aj[\"{BGBl{{A@d{BFDj}}}}\",[]]o[\"{IDbID`}\",[]]A`[\"{ABFnCNb}\",[]]o[\"{O@fKCj}\",[]]o[\"{IDdID`}\",[]]A`[\"{ABG`CNb}\",[]]o[\"{O@hKCj}\",[]]Aa[\"{AOLbBCOb}\",[]]Ai[\"{{{AIAb{AAl}}}AAl}\",[]]Ak[\"{{{AIAb{AOOf}}}AOOf}\",[]]Ak[\"{{{AIAb{AOLf}}}AOLf}\",[]]Ak[\"{{{AIAb{AINb}}}AINb}\",[]]Ai[\"{{{AIAb{KEl}}}KEl}\",[]]Ak[\"{{{AIAb{BGKn}}}BGKn}\",[]]Ak[\"{{{AIAb{BFFf}}}BFFf}\",[]]Ak[\"{{{AIAb{ABBh}}}ABBh}\",[]]Ak[\"{{{AIAb{BBAf}}}BBAf}\",[]]Ai[\"{{{AIAb{KFl}}}KFl}\",[]]Ai[\"{{{AIAb{DA`}}}DA`}\",[]]Ak[\"{{{AIAb{BCCf}}}BCCf}\",[]]Ak[\"{{{AIAb{BCBh}}}BCBh}\",[]]Ak[\"{{{AIAb{AHHd}}}AHHd}\",[]]Ak[\"{{{AIAb{AJAj}}}AJAj}\",[]]Ak[\"{{{AIAb{BCBf}}}BCBf}\",[]]Ak[\"{{{AIAb{AJCn}}}AJCn}\",[]]Ak[\"{{{AIAb{BAE`}}}BAE`}\",[]]Ai[\"{{{AIAb{KJ`}}}KJ`}\",[]]Ak[\"{{{AIAb{BGKh}}}BGKh}\",[]]Ai[\"{{{AIAb{KGn}}}KGn}\",[]]Ak[\"{{{AIAb{AJ@l}}}AJ@l}\",[]]Ak[\"{{{AIAb{AHGj}}}AHGj}\",[]]Ak[\"{{{AIAb{BFMb}}}BFMb}\",[]]Ak[\"{{{AIAb{AIMd}}}AIMd}\",[]]Ak[\"{{{AIAb{ABFn}}}ABFn}\",[]]Ak[\"{{{AIAb{AINn}}}AINn}\",[]]Ak[\"{{{AIAb{AHGn}}}AHGn}\",[]]Ak[\"{{{AIAb{BCOh}}}BCOh}\",[]]Ai[\"{{{AIAb{O@f}}}O@f}\",[]]Ak[\"{{{AIAb{B@Nd}}}B@Nd}\",[]]Ak[\"{{{AIAb{BCO`}}}BCO`}\",[]]Ak[\"{{{AIAb{AHGb}}}AHGb}\",[]]Ak[\"{{{AIAb{BBNd}}}BBNd}\",[]]Ak[\"{{{AIAb{BDAn}}}BDAn}\",[]]Ak[\"{{{AIAb{BFIf}}}BFIf}\",[]]Ak[\"{{{AIAb{BFKd}}}BFKd}\",[]]Ak[\"{{{AIAb{AON`}}}AON`}\",[]]Ak[\"{{{AIAb{BBOh}}}BBOh}\",[]]Ak[\"{{{AIAb{BADd}}}BADd}\",[]]Ak[\"{{{AIAb{BHBj}}}BHBj}\",[]]Ak[\"{{{AIAb{B@Mh}}}B@Mh}\",[]]Ak[\"{{{AIAb{AOLl}}}AOLl}\",[]]Ak[\"{{{AIAb{AHEh}}}AHEh}\",[]]Ak[\"{{{AIAb{BCNj}}}BCNj}\",[]]Ak[\"{{{AIAb{BEHj}}}BEHj}\",[]]Ak[\"{{{AIAb{B@O`}}}B@O`}\",[]]Ak[\"{{{AIAb{B@Of}}}B@Of}\",[]]Ak[\"{{{AIAb{BA@`}}}BA@`}\",[]]Ak[\"{{{AIAb{BFMl}}}BFMl}\",[]]Ak[\"{{{AIAb{AIOd}}}AIOd}\",[]]Ai[\"{{{AIAb{DAl}}}DAl}\",[]]Ai[\"{{{AIAb{A@l}}}A@l}\",[]]Ak[\"{{{AIAb{ABEd}}}ABEd}\",[]]Ai[\"{{{AIAb{CNj}}}CNj}\",[]]Ak[\"{{{AIAb{AILl}}}AILl}\",[]]Ai[\"{{{AIAb{IDj}}}IDj}\",[]]Ak[\"{{{AIAb{AOMf}}}AOMf}\",[]]Ai[\"{{{AIAb{NM`}}}NM`}\",[]]Ak[\"{{{AIAb{BBCl}}}BBCl}\",[]]Ai[\"{{{AIAb{IFd}}}IFd}\",[]]Ak[\"{{{AIAb{BELf}}}BELf}\",[]]Ak[\"{{{AIAb{BGJl}}}BGJl}\",[]]Ak[\"{{{AIAb{BELj}}}BELj}\",[]]Ai[\"{{{AIAb{KJb}}}KJb}\",[]]Ak[\"{{{AIAb{BGKj}}}BGKj}\",[]]Ak[\"{{{AIAb{BFFh}}}BFFh}\",[]]Ai[\"{{{AIAb{KH`}}}KH`}\",[]]Ak[\"{{{AIAb{BACl}}}BACl}\",[]]Ak[\"{{{AIAb{AJ@n}}}AJ@n}\",[]]Ak[\"{{{AIAb{AHGl}}}AHGl}\",[]]Ak[\"{{{AIAb{BFFd}}}BFFd}\",[]]Ak[\"{{{AIAb{BFMd}}}BFMd}\",[]]Ak[\"{{{AIAb{AIMj}}}AIMj}\",[]]Ak[\"{{{AIAb{ABG`}}}ABG`}\",[]]Ak[\"{{{AIAb{AIO`}}}AIO`}\",[]]Ak[\"{{{AIAb{AHH`}}}AHH`}\",[]]Ak[\"{{{AIAb{BCOj}}}BCOj}\",[]]Ak[\"{{{AIAb{AHHf}}}AHHf}\",[]]Ak[\"{{{AIAb{BBDl}}}BBDl}\",[]]Ai[\"{{{AIAb{O@h}}}O@h}\",[]]Ak[\"{{{AIAb{AJD`}}}AJD`}\",[]]Ak[\"{{{AIAb{AOMn}}}AOMn}\",[]]Ak[\"{{{AIAb{BCBj}}}BCBj}\",[]]Ak[\"{{{AIAb{B@Nl}}}B@Nl}\",[]]Ak[\"{{{AIAb{BCOb}}}BCOb}\",[]]Ak[\"{{{AIAb{AHGd}}}AHGd}\",[]]Ak[\"{{{AIAb{BBNf}}}BBNf}\",[]]Ak[\"{{{AIAb{BHFj}}}BHFj}\",[]]Ak[\"{{{AIAb{BDB`}}}BDB`}\",[]]Ak[\"{{{AIAb{BFIh}}}BFIh}\",[]]Ak[\"{{{AIAb{BFKf}}}BFKf}\",[]]Ak[\"{{{AIAb{AONb}}}AONb}\",[]]Ak[\"{{{AIAb{BBOj}}}BBOj}\",[]]Ak[\"{{{AIAb{BADh}}}BADh}\",[]]Ak[\"{{{AIAb{BHBl}}}BHBl}\",[]]Ak[\"{{{AIAb{B@Ml}}}B@Ml}\",[]]Ak[\"{{{AIAb{AOLn}}}AOLn}\",[]]Ak[\"{{{AIAb{AHEj}}}AHEj}\",[]]Ak[\"{{{AIAb{AIOl}}}AIOl}\",[]]Ak[\"{{{AIAb{AJBj}}}AJBj}\",[]]Ak[\"{{{AIAb{B@Oj}}}B@Oj}\",[]]Ak[\"{{{AIAb{BCNn}}}BCNn}\",[]]Ak[\"{{{AIAb{BEHn}}}BEHn}\",[]]Ak[\"{{{AIAb{ABAl}}}ABAl}\",[]]Ak[\"{{{AIAb{BEIf}}}BEIf}\",[]]Ak[\"{{{AIAb{B@Ob}}}B@Ob}\",[]]Ak[\"{{{AIAb{BD@j}}}BD@j}\",[]]Ak[\"{{{AIAb{BHGb}}}BHGb}\",[]]Ak[\"{{{AIAb{ABCh}}}ABCh}\",[]]Ak[\"{{{AIAb{BFEn}}}BFEn}\",[]]Ak[\"{{{AIAb{B@Oh}}}B@Oh}\",[]]Ak[\"{{{AIAb{BA@b}}}BA@b}\",[]]Ak[\"{{{AIAb{BCCb}}}BCCb}\",[]]Ak[\"{{{AIAb{BFMn}}}BFMn}\",[]]Ak[\"{{{AIAb{AIOf}}}AIOf}\",[]]Ak[\"{{{AIAb{BICj}}}BICj}\",[]]Ai[\"{{{AIAb{DAn}}}DAn}\",[]]Ai[\"{{{AIAb{A@n}}}A@n}\",[]]Ak[\"{{{AIAb{ABEf}}}ABEf}\",[]]Ak[\"{{{AIAb{AHEf}}}AHEf}\",[]]Ak[\"{{{AIAb{BBEh}}}BBEh}\",[]]Ak[\"{{{AIAb{BGCl}}}BGCl}\",[]]Ai[\"{{{AIAb{IFh}}}IFh}\",[]]Ai[\"{{{AIAb{IFf}}}IFf}\",[]]Ak[\"{{{AIAb{BELh}}}BELh}\",[]]Ak[\"{{{AIAb{BGJn}}}BGJn}\",[]]Ak[\"{{{AIAb{BELl}}}BELl}\",[]]Ai[\"{{{AIAb{CNl}}}CNl}\",[]]Ak[\"{{{AIAb{AILn}}}AILn}\",[]]Ai[\"{{{AIAb{IDl}}}IDl}\",[]]Ak[\"{{{AIAb{AOMh}}}AOMh}\",[]]Ai[\"{{{AIAb{NMb}}}NMb}\",[]]Ak[\"{{{AIAb{BBCn}}}BBCn}\",[]]Ai[\"{{{AIAb{IG`}}}IG`}\",[]]Ak[\"{{{AIAb{ABE`}}}ABE`}\",[]]Ak[\"{{{AIAb{ABFl}}}ABFl}\",[]]Ak[\"{{{AIAb{BA@d}}}BA@d}\",[]]Ak[\"{{{AIAb{BGBl}}}BGBl}\",[]]Ak[\"{{{AIAb{BD@b}}}BD@b}\",[]]Ak[\"{{{AIAb{BHMd}}}BHMd}\",[]]Ak[\"{{{AIAb{BHMf}}}BHMf}\",[]]Bn[\"{{{AIAb{AINb}}KJ`{AIAb{{KAb{Oj}}}}}ICl}\",[]]Bn[\"{{{AIAb{ABBh}}KJ`{AIAb{{KAb{Oj}}}}}ICl}\",[]]Bf[\"{{{AIAb{DA`}}}{{KHd{IClBBNd}}}}\",[]]Bf[\"{{{AIAb{CGdAINd}}NLdKJbID`}ICl}\",[]]Bj[\"{{{AIAb{B@Of}}}{{AIAb{{KAb{Oj}}}}}}\",[]]Bj[\"{{{AIAb{B@Oh}}}{{AIAb{{KAb{Oj}}}}}}\",[]]Aa[\"{BCCbAIOl}\",[]]A`[\"{ABEdCNb}\",[]]A`[\"{ABEfCNb}\",[]]A`[\"{BA@`DC`}\",[]]A`[\"{BA@bDC`}\",[]]l[\"{NLf}\",[]]l[\"{NLh}\",[]]Cf[\"{{{AIAb{BFJb}}{AIAb{CGdA@h}}}{{KHd{B@OhBFEn}}}}\",[]]Cf[\"{{{AIAb{BIG`}}{AIAb{CGdA@h}}}{{KHd{B@OhBFEn}}}}\",[]]Bc[\"{{{AIAb{AINd}}}{{ABj{KH`}}}}\",[]]Bd[\"{{{AIAb{IHl}}{AIAb{CMl}}}ICl}\",[]]Am[\"{{{AIAb{AOLh}}KCj}ICl}\",[]]Ai[\"{{{AIAb{IHn}}}ICl}\",[]]Bg[\"{{{ABd{{AIAb{CGdAILd}}}}NLd}ICl}\",[]]Bm[\"{{{AIAb{CGdAJBb}}KJ`}{{KHd{IClB@Nd}}}}\",[]]Bm[\"{{{AIAb{CGdAJBd}}KJb}{{KHd{IClB@Nl}}}}\",[]]Am[\"{ABC`{{KHd{IClAJAl}}}}\",[]]Dc[\"{{{AIAb{CGdABBj}}ABCb{AIAb{CGd{ABj{Oj}}}}}{{KHd{ABFjAJAn}}}}\",[]]Bj[\"{{{AIAb{NNh}}{ABd{{A@f{KFf}}}}}ICl}\",[]]Bk[\"{{{AIAb{BAEb}}{ABd{{A@f{KFf}}}}}ICl}\",[]]Bk[\"{{{AIAb{AOOl}}{ABd{{A@f{KFf}}}}}ICl}\",[]]o[\"{AAlDC`}\",[]]Ak[\"{{{AIAb{AINb}}}BCOh}\",[]]Ak[\"{{{AIAb{ABBh}}}BBAf}\",[]]Aj[\"{{{AIAb{DA`}}}AHGn}\",[]]A`[\"{KElBCOh}\",[]]An[\"{{{AIAb{CGdAINd}}}BCOj}\",[]]Ce[\"{{{AIAb{CGdAJA`}}{KGl{KIl}}}{{AIAb{CGdAJA`}}}}\",[]]Ce[\"{{{AIAb{CGdAJAb}}{KGl{KIl}}}{{AIAb{CGdAJAb}}}}\",[]]Ck[\"{{{AIAb{CGdAJBb}}{AIAb{{KAb{Oj}}}}}{{KHd{KCjAJCn}}}}\",[]]Ck[\"{{{AIAb{CGdAJBd}}{AIAb{{KAb{Oj}}}}}{{KHd{KCjAJD`}}}}\",[]]Bl[\"{{{AIAb{BGCl}}{AIAb{CGdc}}}IClKEb}\",[\"W\"]]Aj[\"{{{AIAb{ABBh}}}KDb}\",[]]An[\"{ABC`{{KHd{AIMnBCO`}}}}\",[]]Bi[\"{{{AIAb{CGdNOj}}A@n}{{KGl{ABEf}}}}\",[]]Fj[\"{{{AIAb{CGdABBj}}ABCbNLd{AIAb{CGd{ABj{Oj}}}}{KGl{{A@d{BADh}}}}}{{KHd{{KCh{BEIfAINd}}{A@f{AOLb}}}}}}\",[]]Am[\"{AIAb{{AIAb{c}}}{}}\",[\"T\"]]0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000o[\"{O@fKCj}\",[]]o[\"{O@hKCj}\",[]]A`[\"{AAlAOLf}\",[]]Bc[\"{{AJBhBBNbAAl}{{KHh{AOLh}}}}\",[]]Aj[\"{AJBh{{KHf{ABBh}}}}\",[]]Ba[\"{{}{{KHd{ICl{A@f{IEf}}}}}}\",[]]Ak[\"{{{AIAb{AINb}}}BCO`}\",[]]Be[\"{{{AIAb{ABBj}}}{{AIAb{BBOj}}}}\",[]]o[\"{OjBFMl}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{BADdA@d}\",[]]o[\"{OjBFMn}\",[]]A`[\"{AIO`CNb}\",[]]A`[\"{BADhA@d}\",[]]A`[\"{BCCbKGl}\",[]]Bm[\"{{{AIAb{CGdc}}}{{KHd{KJ`BBEh}}}A@h}\",[\"B\"]]Bm[\"{{{AIAb{CGdc}}}{{KHd{KGnBBEh}}}A@h}\",[\"B\"]]Bn[\"{{{AIAb{CGdc}}}{{KHd{AHEhBBEh}}}A@h}\",[\"B\"]]Bn[\"{{{AIAb{CGdc}}}{{KHd{BFMlBBEh}}}A@h}\",[\"B\"]]Bn[\"{{{AIAb{CGdc}}}{{KHd{ABEdBBEh}}}A@h}\",[\"B\"]]Bi[\"{{{AIAb{CGdc}}}{{KHl{KJb}}}A@h}\",[\"B\"]]Bi[\"{{{AIAb{CGdc}}}{{KHl{KH`}}}A@h}\",[\"B\"]]Bj[\"{{{AIAb{CGdc}}}{{KHl{AHEj}}}A@h}\",[\"B\"]]Bj[\"{{{AIAb{CGdc}}}{{KHl{AIOl}}}A@h}\",[\"B\"]]Ek[\"{{{AIAb{CGd{KFd{ABAb}}}}{AIAb{c}}{AIAb{{KAb{CN`}}}}DC`}{{KHd{BD@jBFEn}}}{BFJbIHj}}\",[\"\"]]Bj[\"{{{AIAb{CGdc}}}{{KHl{BFMn}}}A@h}\",[\"B\"]]Bj[\"{{{AIAb{CGdc}}}{{KHl{ABEf}}}A@h}\",[\"B\"]]Bj[\"{{{AIAb{CGdc}}}{{KHl{AHDh}}}A@h}\",[\"B\"]]A`[\"{ABE`KGl}\",[]]Bk[\"{{{AIAb{KJ`}}{AIAb{CGdc}}}IClKEb}\",[\"B\"]]Bk[\"{{{AIAb{KGn}}{AIAb{CGdc}}}IClKEb}\",[\"B\"]]Bl[\"{{{AIAb{AHEh}}{AIAb{CGdc}}}IClKEb}\",[\"B\"]]Bl[\"{{{AIAb{BFMl}}{AIAb{CGdc}}}IClKEb}\",[\"B\"]]Bl[\"{{{AIAb{ABEd}}{AIAb{CGdc}}}IClKEb}\",[\"B\"]]Bk[\"{{{AIAb{KJb}}{AIAb{CGdc}}}IClKEb}\",[\"B\"]]Bk[\"{{{AIAb{KH`}}{AIAb{CGdc}}}IClKEb}\",[\"B\"]]Bl[\"{{{AIAb{AHEj}}{AIAb{CGdc}}}IClKEb}\",[\"B\"]]Bl[\"{{{AIAb{AIOl}}{AIAb{CGdc}}}IClKEb}\",[\"B\"]]Bl[\"{{{AIAb{ABAl}}{AIAb{CGdc}}}IClKEb}\",[\"B\"]]Bl[\"{{{AIAb{BFMn}}{AIAb{CGdc}}}IClKEb}\",[\"B\"]]Bl[\"{{{AIAb{ABEf}}{AIAb{CGdc}}}IClKEb}\",[\"B\"]]Bl[\"{{{AIAb{AHEb}}{AIAb{CGdc}}}IClKEb}\",[\"B\"]]Bj[\"{{{AIAb{CGdAJBb}}}{{KHd{IClB@Nd}}}}\",[]]Bj[\"{{{AIAb{CGdAJBd}}}{{KHd{IClAOMn}}}}\",[]]An[\"{{{AIAb{B@@h}}ABBd}ICl}\",[]]Ea[\"{{{AIAb{CGdABBj}}NLdAHEf{KGl{BA@b}}ABAb{AIAb{CGd{ABj{Oj}}}}}{{KGl{BBAb}}}}\",[]]o[\"{D@dNLf}\",[]]o[\"{D@fNLh}\",[]]A`[\"{ABC`ICl}\",[]]Ba[\"{{{AIAb{CGdABBj}}ABCb}ICl}\",[]]Bh[\"{{{AIAb{AJCh}}{AIAb{CMl}}ID`}ICl}\",[]]Bh[\"{{{AIAb{BCAd}}{AIAb{CMl}}ID`}ICl}\",[]]Bh[\"{{{AIAb{BELn}}{AIAb{CMl}}ID`}ICl}\",[]]Bh[\"{{{AIAb{AJCj}}{AIAb{CMl}}ID`}ICl}\",[]]Bh[\"{{{AIAb{BCAf}}{AIAb{CMl}}ID`}ICl}\",[]]Bh[\"{{{AIAb{BEM`}}{AIAb{CMl}}ID`}ICl}\",[]]A`[\"{AHD`DC`}\",[]]o[\"{IDbCNb}\",[]]o[\"{IDdCNb}\",[]]Ch[\"{{{AIAb{CGdAINf}}NLdNLdCNbCNbDC`{AIAb{BACj}}}ICl}\",[]]Cg[\"{{{AIAb{CGdCNj}}NLdNLdCNbCNbDC`{AIAb{BACj}}}ICl}\",[]]Cg[\"{{{AIAb{CGdIDj}}NLdNLdCNbCNbDC`{AIAb{BACj}}}ICl}\",[]]Cg[\"{{{AIAb{CGdNM`}}NLdNLdCNbCNbDC`{AIAb{BACj}}}ICl}\",[]]Ch[\"{{{AIAb{CGdAINh}}NLdNLdCNbCNbDC`{AIAb{BACl}}}ICl}\",[]]Cg[\"{{{AIAb{CGdCNl}}NLdNLdCNbCNbDC`{AIAb{BACl}}}ICl}\",[]]Cg[\"{{{AIAb{CGdIDl}}NLdNLdCNbCNbDC`{AIAb{BACl}}}ICl}\",[]]Cg[\"{{{AIAb{CGdNMb}}NLdNLdCNbCNbDC`{AIAb{BACl}}}ICl}\",[]]o[\"{D@dNLf}\",[]]o[\"{D@fNLh}\",[]]o[\"{BEMjOj}\",[]]A`[\"{KElBCO`}\",[]]A`[\"{BCNjID`}\",[]]A`[\"{BEHjID`}\",[]]Aa[\"{AHBdBFFb}\",[]]A`[\"{BCNnID`}\",[]]A`[\"{BEHnID`}\",[]]A`[\"{BCCbKIl}\",[]]Aa[\"{BBNjBCOb}\",[]]Aa[\"{AHBfBFFd}\",[]]Bg[\"{{{AIAb{ABBh}}AHHn}{{KHf{ICl}}}}\",[]]A`[\"{ABC`ICl}\",[]]Cf[\"{{{AIAb{CGdABBj}}ABCb{AIAb{CGd{ABj{Oj}}}}}ABFj}\",[]]l[\"{NLf}\",[]]Ak[\"{{{AIAb{AHEf}}}AJBh}\",[]]l[\"{NLh}\",[]]An[\"{{ABBfAAl}{{KHh{ICl}}}}\",[]]B`[\"{{BADdAJBh}{{KHf{ABBh}}}}\",[]]Bc[\"{BADb{{KHd{ICl{A@f{IEf}}}}}}\",[]]Bk[\"{{{AIAb{BFFf}}}{{KGl{{AIAb{IEf}}}}}}\",[]]Bk[\"{{{AIAb{BCBh}}}{{KGl{{AIAb{IEf}}}}}}\",[]]Bk[\"{{{AIAb{AHHd}}}{{KGl{{AIAb{IEf}}}}}}\",[]]Bk[\"{{{AIAb{AJAj}}}{{KGl{{AIAb{IEf}}}}}}\",[]]Bk[\"{{{AIAb{BCBf}}}{{KGl{{AIAb{IEf}}}}}}\",[]]Bk[\"{{{AIAb{AJCn}}}{{KGl{{AIAb{IEf}}}}}}\",[]]Bk[\"{{{AIAb{BAE`}}}{{KGl{{AIAb{IEf}}}}}}\",[]]Bk[\"{{{AIAb{BCO`}}}{{KGl{{AIAb{IEf}}}}}}\",[]]Bk[\"{{{AIAb{BCOb}}}{{KGl{{AIAb{IEf}}}}}}\",[]]A`[\"{ABFhKGl}\",[]]A`[\"{ABFjKGl}\",[]]A`[\"{ABFlKGl}\",[]]Bg[\"{{{AIAb{DA`}}}{{KHd{AJ@lAIMd}}}}\",[]]Aa[\"{BACbAJ@l}\",[]]Aa[\"{BACdAJ@n}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]A`[\"{ABE`KCj}\",[]]Aa[\"{AHGnABFn}\",[]]Aa[\"{BCOhABFn}\",[]]Aa[\"{AHH`ABG`}\",[]]Aa[\"{BCOjABG`}\",[]]3210Dh[\"{{{AIAb{NKn}}{AIAb{{KAb{Oj}}}}{AIAb{{KAb{Oj}}}}}{{KHd{IClAOMb}}}}\",[]]Dh[\"{{{AIAb{NL`}}{AIAb{{KAb{Oj}}}}{AIAb{{KAb{Oj}}}}}{{KHd{IClAOMd}}}}\",[]]Aj[\"{{{AIAb{AINf}}}CNb}\",[]]Ai[\"{{{AIAb{CNj}}}CNb}\",[]]Ai[\"{{{AIAb{IDj}}}CNb}\",[]]Ai[\"{{{AIAb{NM`}}}CNb}\",[]]Aj[\"{{{AIAb{AINh}}}CNb}\",[]]Ai[\"{{{AIAb{CNl}}}CNb}\",[]]Ai[\"{{{AIAb{IDl}}}CNb}\",[]]Ai[\"{{{AIAb{NMb}}}CNb}\",[]]Aj[\"{{{AIAb{BHAh}}}KCj}\",[]]Aj[\"{{{AIAb{BHAj}}}KCj}\",[]]Aj[\"{{{AIAb{BIC`}}}KCj}\",[]]Aj[\"{{{AIAb{BICj}}}KCj}\",[]]o[\"{AAlKCj}\",[]]n[\"{{}NK`}\",[]]0A`[\"{A@jABAf}\",[]]111Ba[\"{{AIAb{AIAb{c}}}ABDj{}}\",[\"K\"]]0000000000000000000Cg[\"{{{AIAb{ABBh}}AJBh{AIAb{CMl}}}{{KHd{AIMnB@O`}}}}\",[]]Dl[\"{{{AIAb{CGdABBj}}NLdB@MlAJBh{AIAb{CMl}}}{{KHd{{KCh{BEIfAINd}}B@Ob}}}}\",[]]Bd[\"{{{AIAb{ABC`}}}{{KGl{BEIj}}}}\",[]]Dm[\"{{{AIAb{AHFl}}KGnCNb{AIAb{{KAb{Oj}}}}{AIAb{CGdABAb}}}{{KHd{IClAOMb}}}}\",[]]Ca[\"{{{AIAb{AHEn}}KCj{AIAb{CGd{KAb{Oj}}}}}ICl}\",[]]Bd[\"{{{AIAb{ABCb}}}{{KGl{BEIl}}}}\",[]]Dm[\"{{{AIAb{AHFn}}KH`CNb{AIAb{{KAb{Oj}}}}{AIAb{CGdABAb}}}{{KHd{IClAOMd}}}}\",[]]Ca[\"{{{AIAb{AHF`}}KCj{AIAb{CGd{KAb{Oj}}}}}ICl}\",[]]o[\"{{}B@@f}\",[]]n[\"{{}IHl}\",[]]o[\"{{}AOLh}\",[]]o[\"{{}BB@d}\",[]]o[\"{{}BBAf}\",[]]n[\"{{}KJ`}\",[]]n[\"{{}KGn}\",[]]o[\"{{}AJ@l}\",[]]o[\"{{}AIMd}\",[]]o[\"{{}ABFn}\",[]]o[\"{{}AINn}\",[]]o[\"{{}AHGn}\",[]]o[\"{{}BCOh}\",[]]n[\"{{}O@f}\",[]]o[\"{{}B@Nd}\",[]]o[\"{{}BDAn}\",[]]o[\"{{}BFIf}\",[]]o[\"{{}BFKd}\",[]]o[\"{{}AON`}\",[]]o[\"{{}BBOh}\",[]]o[\"{{}BHBj}\",[]]o[\"{{}BELn}\",[]]o[\"{{}AILl}\",[]]o[\"{{}AOMf}\",[]]o[\"{{}BBCl}\",[]]o[\"{{}BFDl}\",[]]o[\"{{}BB@f}\",[]]o[\"{{}BIC`}\",[]]n[\"{{}KJb}\",[]]n[\"{{}KH`}\",[]]o[\"{{}AJ@n}\",[]]o[\"{{}AIMj}\",[]]o[\"{{}ABG`}\",[]]o[\"{{}AIO`}\",[]]o[\"{{}AHH`}\",[]]o[\"{{}BCOj}\",[]]n[\"{{}O@h}\",[]]o[\"{{}BCBj}\",[]]o[\"{{}B@Nl}\",[]]o[\"{{}BDB`}\",[]]o[\"{{}BFIh}\",[]]o[\"{{}BFKf}\",[]]o[\"{{}AONb}\",[]]o[\"{{}BBOj}\",[]]o[\"{{}BHBl}\",[]]o[\"{{}BICj}\",[]]o[\"{{}BEM`}\",[]]o[\"{{}AILn}\",[]]o[\"{{}AOMh}\",[]]o[\"{{}BBCn}\",[]]o[\"{{}BFDn}\",[]]o[\"{{}ABE`}\",[]]n[\"{{}IHn}\",[]]o[\"{{}BD@b}\",[]]o[\"{{}BBAd}\",[]]Ak[\"{{{AIAb{BBDb}}}B@Oh}\",[]]Ak[\"{{{AIAb{BD@j}}}B@Oh}\",[]]Aa[\"{BHGbB@Oh}\",[]]Aa[\"{BFN`B@Oh}\",[]]A`[\"{D@hB@Oh}\",[]]A`[\"{IHbB@Oh}\",[]]A`[\"{IHfB@Oh}\",[]]Aa[\"{BEMjB@Oh}\",[]]Cg[\"{{{AIAb{AHFl}}KGnCNb{AIAb{CGd{KAb{Oj}}}}KCj}ICl}\",[]]Ca[\"{{{AIAb{AHEn}}KCj{AIAb{CGd{KAb{Oj}}}}}ICl}\",[]]Cg[\"{{{AIAb{AHFn}}KH`CNb{AIAb{CGd{KAb{Oj}}}}KCj}ICl}\",[]]Ca[\"{{{AIAb{AHF`}}KCj{AIAb{CGd{KAb{Oj}}}}}ICl}\",[]]Aj[\"{{{AIAb{AJAf}}}DC`}\",[]]Aj[\"{{{AIAb{BGCn}}}DC`}\",[]]Ak[\"{{{AIAb{AINf}}}BFDl}\",[]]Aj[\"{{{AIAb{CNj}}}BFDl}\",[]]Aj[\"{{{AIAb{IDj}}}BFDl}\",[]]Aj[\"{{{AIAb{NM`}}}BFDl}\",[]]Ak[\"{{{AIAb{AINh}}}BFDn}\",[]]Aj[\"{{{AIAb{CNl}}}BFDn}\",[]]Aj[\"{{{AIAb{IDl}}}BFDn}\",[]]Aj[\"{{{AIAb{NMb}}}BFDn}\",[]]Bn[\"{{{AIAb{CGdBDAn}}CMn}{{AIAb{CGdBDAn}}}}\",[]]Bn[\"{{{AIAb{CGdBDB`}}CMn}{{AIAb{CGdBDB`}}}}\",[]]Bf[\"{{{AIAb{CGdAINf}}NLdCNbCNb}ICl}\",[]]Bf[\"{{{AIAb{CGdAINh}}NLdCNbCNb}ICl}\",[]]Aj[\"{{{AIAb{AINb}}}KFn}\",[]]Bc[\"{{{AIAb{ABDh}}}{{KGl{KGn}}}}\",[]]Aa[\"{BHGbB@Oh}\",[]]Aa[\"{BFN`B@Oh}\",[]]A`[\"{D@hB@Oh}\",[]]A`[\"{IHbB@Oh}\",[]]Aa[\"{BEMjB@Oh}\",[]]Aj[\"{{{AIAb{AJBb}}}NOb}\",[]]Bn[\"{{{AIAb{AJBd}}}{{KHd{{KGl{KJb}}B@Nl}}}}\",[]]o[\"{AAlKCj}\",[]]o[\"{IHlKCj}\",[]]Am[\"{{{AIAb{CGdAINd}}}NOj}\",[]]Aj[\"{{{AIAb{AHFl}}}KCj}\",[]]Aj[\"{{{AIAb{AHFn}}}KCj}\",[]]A`[\"{AIAbKIn}\",[]]0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Bd[\"{{{AIAb{BGKn}}}{{KGl{AINb}}}}\",[]]Bc[\"{{{AIAb{BCCf}}}{{KGl{DA`}}}}\",[]]Bn[\"{{{AIAb{CGdB@Mh}}CN`}{{AIAb{CGdB@Mh}}}}\",[]]Bn[\"{{{AIAb{CGdB@Ml}}CN`}{{AIAb{CGdB@Ml}}}}\",[]]A`[\"{BHGbCN`}\",[]]A`[\"{BFN`CN`}\",[]]o[\"{D@hCN`}\",[]]o[\"{IHbCN`}\",[]]A`[\"{AHD`KGl}\",[]]Aa[\"{ABFlAIAb}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]Aa[\"{BCBlABBd}\",[]]A`[\"{KEdBCBj}\",[]]Aa[\"{AHGnAINn}\",[]]Aa[\"{BCOhAINn}\",[]]Aa[\"{AHH`AIO`}\",[]]Aa[\"{BCOjAIO`}\",[]]3210Bh[\"{{{AIAb{CGdc}}KCj}B@Of{A@hIHj}}\",[\"\"]]Bh[\"{{{AIAb{CGdc}}KCj}B@Oh{A@hIHj}}\",[\"\"]]o[\"{{}BD@l}\",[]]o[\"{{}BD@n}\",[]]A`[\"{CNbBIC`}\",[]]Cd[\"{{{AIAb{CMl}}}{{KHh{AOOfc}}}{}}\",[\"FromStr::Err\"]]Cd[\"{{{AIAb{CMl}}}{{KHh{AOLfc}}}{}}\",[\"FromStr::Err\"]]o[\"{CN`KJ`}\",[]]o[\"{CN`KJb}\",[]]Al[\"{CNb{{KHd{KJ`BGKh}}}}\",[]]Al[\"{CNb{{KHd{KJbBGKj}}}}\",[]]Aa[\"{BGIlAOM`}\",[]]Aa[\"{BIGbBBBb}\",[]]Aa[\"{BGInAOM`}\",[]]Aa[\"{BIGdBBBb}\",[]]Af[\"{{}{{KGl{Ab}}}}\",[]]0000Aj[\"{{{AIAb{AINd}}}DC`}\",[]]Bo[\"{{{AIAb{CGdBFKd}}ABBd}{{AIAb{CGdBFKd}}}}\",[]]Bo[\"{{{AIAb{CGdBFKf}}ABBd}{{AIAb{CGdBFKf}}}}\",[]]Bb[\"{{{A@f{AINf}}}{{A@f{A@b}}}}\",[]]Ba[\"{{{A@f{CNj}}}{{A@f{A@b}}}}\",[]]Ba[\"{{{A@f{IDj}}}{{A@f{A@b}}}}\",[]]Ba[\"{{{A@f{NM`}}}{{A@f{A@b}}}}\",[]]Bb[\"{{{A@f{AINh}}}{{A@f{A@b}}}}\",[]]Ba[\"{{{A@f{CNl}}}{{A@f{A@b}}}}\",[]]Ba[\"{{{A@f{IDl}}}{{A@f{A@b}}}}\",[]]Ba[\"{{{A@f{NMb}}}{{A@f{A@b}}}}\",[]]Aj[\"{{{AIAb{BGKn}}}DC`}\",[]]Bo[\"{{{AIAb{CGdBHBj}}ABBd}{{AIAb{CGdBHBj}}}}\",[]]Bo[\"{{{AIAb{CGdBHBl}}ABBd}{{AIAb{CGdBHBl}}}}\",[]]Bc[\"{{{AIAb{AIMn}}}{{KGl{KFj}}}}\",[]]Bc[\"{{{AIAb{AINb}}}{{KGl{KFj}}}}\",[]]Bc[\"{{{AIAb{ABC`}}}{{KGl{KFj}}}}\",[]]Bc[\"{{{AIAb{ABCb}}}{{KGl{KFj}}}}\",[]]Bc[\"{{{AIAb{AHEf}}}{{KGl{KFj}}}}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]Bc[\"{{{AIAb{AHDf}}}{{KGl{KCj}}}}\",[]]Am[\"{{{AIAb{B@@h}}KCj}ICl}\",[]]Aj[\"{{{AIAb{AINb}}}NMf}\",[]]Bg[\"{{{AIAb{AJBb}}}{{KHd{CEnB@Nd}}}}\",[]]Bg[\"{{{AIAb{AJBd}}}{{KHd{CEnB@Nl}}}}\",[]]A`[\"{BBAjKGl}\",[]]A`[\"{BBAlKGl}\",[]]A`[\"{AHD`KGl}\",[]]A`[\"{AOLbKGl}\",[]]Ck[\"{{{AIAb{CGdBBOh}}{KGl{{IIj{Oj}}}}}{{AIAb{CGdBBOh}}}}\",[]]Ck[\"{{{AIAb{CGdBBOj}}{KGl{{IIj{Oj}}}}}{{AIAb{CGdBBOj}}}}\",[]]A`[\"{BFDlKGl}\",[]]A`[\"{BFDnKGl}\",[]]Ac[\"{AIAbc{}}\",[\"T\"]]000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Ao[\"{c{{KHd{e}}}{}{}}\",[\"U\",\"T\"]]00000000000000000000000000000000000000000000000000000Al[\"{ICj{{KHd{KJ`BGKh}}}}\",[]]1Al[\"{CNb{{KHd{KJ`BGKh}}}}\",[]]Al[\"{KCj{{KHd{KJ`BGKh}}}}\",[]]333333333333333333333Aj[\"{ABBd{{KHd{AON`}}}}\",[]]444444Ai[\"{CNb{{KHd{AHEh}}}}\",[]]5555555555555555555555555555Bc[\"{{{A@d{B@Md}}}{{KHd{BELf}}}}\",[]]Aj[\"{B@Md{{KHd{BELf}}}}\",[]]77Aj[\"{BAD`{{KHd{BELj}}}}\",[]]Bc[\"{{{A@d{BAD`}}}{{KHd{BELj}}}}\",[]]99999999999999999999Al[\"{ICj{{KHd{KJbBGKj}}}}\",[]]:Al[\"{CNb{{KHd{KJbBGKj}}}}\",[]]Al[\"{KCj{{KHd{KJbBGKj}}}}\",[]]<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Bm[\"{B@Md{{KHd{BELhc}}}{}}\",[\"TryFrom::Error\"]]?Cf[\"{{{A@d{B@Md}}}{{KHd{BELhc}}}{}}\",[\"TryFrom::Error\"]]Ao[\"{c{{KHd{e}}}{}{}}\",[\"U\",\"T\"]]Cf[\"{{{A@d{BAD`}}}{{KHd{BELlc}}}{}}\",[\"TryFrom::Error\"]]1Bm[\"{BAD`{{KHd{BELlc}}}{}}\",[\"TryFrom::Error\"]]22222222222222222222222Aj[\"{{}{{KHd{c}}}{}}\",[\"U\"]]0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Cd[\"{{{ABd{{AIAb{CGdc}}}}{AIAb{CGdNKb}}}DAh{}}\",[\"F\"]]000000000Cc[\"{{{AIAb{BCCd}}AJCl{AIAb{ABFl}}}{{KHf{ICl}}}}\",[]]Bk[\"{{{AIAb{BHAh}}B@Of}{{KHd{IClAIOd}}}}\",[]]Bk[\"{{{AIAb{BHAj}}B@Oh}{{KHd{IClAIOf}}}}\",[]]0Bk[\"{{{AIAb{BIC`}}B@Oh}{{KHd{IClAIOf}}}}\",[]]Ak[\"{{{AIAb{AINb}}}AB@n}\",[]]Bi[\"{{{AIAb{CGdBAEd}}}{{KHd{KJ`IFb}}}}\",[]]Bi[\"{{{AIAb{CGdBAEd}}}{{KHd{DAlIFb}}}}\",[]]Bi[\"{{{AIAb{CGdBAEd}}}{{KHd{A@lIFb}}}}\",[]]Bj[\"{{{AIAb{CGdBAEd}}}{{KHd{ABEdIFb}}}}\",[]]Bf[\"{{{AIAb{CGdBAEd}}}{{KHj{KJb}}}}\",[]]Bf[\"{{{AIAb{CGdBAEd}}}{{KHj{DAn}}}}\",[]]Bf[\"{{{AIAb{CGdBAEd}}}{{KHj{A@n}}}}\",[]]Bg[\"{{{AIAb{CGdBAEd}}}{{KHj{ABEf}}}}\",[]]Aa[\"{AOLfBGKf}\",[]]Bd[\"{{{AIAb{AINf}}}{{A@f{AINf}}}}\",[]]Bc[\"{{{AIAb{CNj}}}{{A@f{AINf}}}}\",[]]Bc[\"{{{AIAb{IDj}}}{{A@f{AINf}}}}\",[]]Bc[\"{{{AIAb{NM`}}}{{A@f{AINf}}}}\",[]]Bd[\"{{{AIAb{AINh}}}{{A@f{AINh}}}}\",[]]Bc[\"{{{AIAb{CNl}}}{{A@f{AINh}}}}\",[]]Bc[\"{{{AIAb{IDl}}}{{A@f{AINh}}}}\",[]]Bc[\"{{{AIAb{NMb}}}{{A@f{AINh}}}}\",[]]A`[\"{ABFnCNb}\",[]]An[\"{{{AIAb{CGdAINd}}}AHDf}\",[]]A`[\"{ABG`CNb}\",[]]C`[\"{{{AIAb{CGdB@Of}}}{{AIAb{CGd{KAb{Oj}}}}}}\",[]]C`[\"{{{AIAb{CGdB@Oh}}}{{AIAb{CGd{KAb{Oj}}}}}}\",[]]Ah[\"{Oj{{KGl{BA@`}}}}\",[]]Ah[\"{Oj{{KGl{BA@b}}}}\",[]]Ah[\"{Oj{{KGl{BA@d}}}}\",[]]A`[\"{ABEdDAl}\",[]]A`[\"{ABEfDAn}\",[]]Bi[\"{AIMn{{KHd{{KCh{AINbBDBh}}AIMn}}}}\",[]]Aa[\"{{}c{}}\",[\"I\"]]o[\"{DAlDC`}\",[]]o[\"{DAnDC`}\",[]]Aj[\"{{{AIAb{AINd}}}DC`}\",[]]21Aj[\"{{{AIAb{ABC`}}}DC`}\",[]]Aj[\"{{{AIAb{ABCb}}}DC`}\",[]]Bn[\"{{{AIAb{CGdBADd}}DC`}{{AIAb{CGdBADd}}}}\",[]]Bn[\"{{{AIAb{CGdBADh}}DC`}{{AIAb{CGdBADh}}}}\",[]]Bm[\"{{{AIAb{NNh}}NLd}{{ABd{{A@f{AILd}}}}}}\",[]]Bn[\"{{{AIAb{BAEb}}NLd}{{ABd{{A@f{AILd}}}}}}\",[]]Bn[\"{{{AIAb{AOOl}}NLd}{{ABd{{A@f{AILd}}}}}}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]Ak[\"{{{AIAb{AINb}}}ABDf}\",[]]Bc[\"{{{AIAb{AINb}}AJBhAJ@l}ABDh}\",[]]Ce[\"{{{AIAb{CGdAINd}}AHEfAJ@nNLd}{{KHd{KH`AHGd}}}}\",[]]43Bm[\"{{{AIAb{CGdAINd}}KH`}{{KHd{IClAIMj}}}}\",[]]E`[\"{{{ABd{{AIAb{CGdBGK`}}}}{AIAb{CGdNKb}}}{{DAh{{KGl{c}}}}}{}}\",[\"Stream::Item\"]]E`[\"{{{ABd{{AIAb{CGdBEHf}}}}{AIAb{CGdNKb}}}{{DAh{{KGl{c}}}}}{}}\",[\"Stream::Item\"]]E`[\"{{{ABd{{AIAb{CGdAJ@j}}}}{AIAb{CGdNKb}}}{{DAh{{KGl{c}}}}}{}}\",[\"Stream::Item\"]]E`[\"{{{ABd{{AIAb{CGdBGBn}}}}{AIAb{CGdNKb}}}{{DAh{{KGl{c}}}}}{}}\",[\"Stream::Item\"]]E`[\"{{{ABd{{AIAb{CGdAJAf}}}}{AIAb{CGdNKb}}{AIAb{CGdNNb}}}{{DAh{{KHf{ICl}}}}}}\",[]]Ef[\"{{{ABd{{AIAb{CGdAJAf}}}}{AIAb{CGdNKb}}{AIAb{CGd{KAb{Oj}}}}}{{DAh{{KHf{KCj}}}}}}\",[]]Ec[\"{{{AIAb{CGdAJAf}}{AIAb{CGdNKb}}{AIAb{CGd{KAb{Oj}}}}}{{DAh{{KHd{KCjAHHd}}}}}}\",[]]Fg[\"{{{AIAb{CGdBBN`}}{AIAb{CGdNKb}}{AIAb{CGd{KAb{AIOh}}}}{AIAb{CGd{KAb{ABE`}}}}}{{DAh{{KHf{KCj}}}}}}\",[]]Dn[\"{{{ABd{{AIAb{CGdAHHl}}}}{AIAb{ABFl}}{AIAb{CGdNKb}}}{{DAh{{KHf{ICl}}}}}}\",[]]>=Ce[\"{{{AIAb{CGdBBOh}}{A@d{NKn}}}{{AIAb{CGdBBOh}}}}\",[]]Ce[\"{{{AIAb{CGdBBOj}}{A@d{NL`}}}{{AIAb{CGdBBOj}}}}\",[]]Cj[\"{{{AIAb{BADf}}CN`B@Of{AIAb{{KAb{Oj}}}}}{{IIj{Oj}}}}\",[]]Cj[\"{{{AIAb{BELj}}CN`B@Of{AIAb{{KAb{Oj}}}}}{{IIj{Oj}}}}\",[]]Cj[\"{{{AIAb{BADj}}CN`B@Oh{AIAb{{KAb{Oj}}}}}{{IIj{Oj}}}}\",[]]Cj[\"{{{AIAb{BELl}}CN`B@Oh{AIAb{{KAb{Oj}}}}}{{IIj{Oj}}}}\",[]]Cj[\"{{{AIAb{BHMf}}CN`B@Od{AIAb{{KAb{Oj}}}}}{{IIj{Oj}}}}\",[]]Bb[\"{KCj{{KCh{KCj{KGl{KCj}}}}}}\",[]]000Aj[\"{{{AIAb{AINb}}}KCj}\",[]]A`[\"{AIAbKIl}\",[]]000000000000000000000000000000000000000000000000000000000000000000Cf[\"{{{AIAb{CGdBADd}}{A@d{BFE`}}}{{AIAb{CGdBADd}}}}\",[]]Cf[\"{{{AIAb{CGdBADh}}{A@d{BFEb}}}{{AIAb{CGdBADh}}}}\",[]]A`[\"{BHGbIGh}\",[]]A`[\"{BADdA@d}\",[]]A`[\"{BADhA@d}\",[]]Aj[\"{{{AIAb{ABBh}}}ICl}\",[]]Ck[\"{{{AIAb{CGdAJBb}}{AIAb{{KAb{Oj}}}}}{{KHd{IClAJCn}}}}\",[]]n[\"{{}KCj}\",[]]Ak[\"{{{AIAb{AINb}}}AHBl}\",[]]Ba[\"{{{AIAb{CGdABFn}}ABFn}ICl}\",[]]Ba[\"{{{AIAb{CGdAINn}}AINn}ICl}\",[]]Ba[\"{{{AIAb{CGdBCOh}}AHGn}ICl}\",[]]Ba[\"{{{AIAb{CGdABG`}}ABG`}ICl}\",[]]Ba[\"{{{AIAb{CGdAIO`}}AIO`}ICl}\",[]]Ba[\"{{{AIAb{CGdBCOj}}AHH`}ICl}\",[]]Bn[\"{{{AIAb{CGdBDAn}}DC`}{{AIAb{CGdBDAn}}}}\",[]]Bn[\"{{{AIAb{CGdBDB`}}DC`}{{AIAb{CGdBDB`}}}}\",[]]Bi[\"{{{AIAb{CGd}}}{{AIAb{CGdc}}}{}}\",[\"T\"]]0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Bc[\"{{AIAb{AIAb{CGdc}}}ICl{}}\",[\"T\"]]000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Cc[\"{{{AIAb{CGdAINd}}NLdKH`KJb}{{KHd{IClBBNf}}}}\",[]]A`[\"{AHD`DC`}\",[]]B`[\"{{AIAb{AIAb{c}}}DC`{}}\",[\"K\"]]00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Aa[\"{BCNjBFMl}\",[]]A`[\"{BEHjKJ`}\",[]]Aa[\"{BCNnBFMn}\",[]]A`[\"{BEHnKJb}\",[]]A`[\"{BFD`KJb}\",[]]A`[\"{BDA`KJb}\",[]]o[\"{NNnKJb}\",[]]Aa[\"{BCNjAIOj}\",[]]Aa[\"{BCNnAIOl}\",[]]o[\"{KJ`CNb}\",[]]o[\"{KJbCNb}\",[]]Aj[\"{{{AIAb{AINd}}}DC`}\",[]]Aj[\"{{{AIAb{BBAd}}}DC`}\",[]]Bd[\"{{{AIAb{BBN`}}}{{KHf{AJBh}}}}\",[]]Bd[\"{{{AIAb{ABBh}}}{{KHf{AJBh}}}}\",[]]A`[\"{AHGnCNb}\",[]]A`[\"{BCOhCNb}\",[]]A`[\"{AHH`CNb}\",[]]A`[\"{BCOjCNb}\",[]]Cc[\"{{{AIAb{BD@b}}{AIAb{AJB`}}CNb}{{A@d{B@@h}}}}\",[]]Bn[\"{{{AIAb{CGdBDAn}}DC`}{{AIAb{CGdBDAn}}}}\",[]]Bn[\"{{{AIAb{CGdBDB`}}DC`}{{AIAb{CGdBDB`}}}}\",[]]Bg[\"{{{AIAb{AINb}}KGn}{{KGl{AHGn}}}}\",[]]A`[\"{KElABA`}\",[]]A`[\"{AHDjA@f}\",[]]Bj[\"{{{AIAb{CGdAINd}}KH`}{{KGl{AHH`}}}}\",[]]A`[\"{AHDlA@f}\",[]]Db[\"{{{ABd{{AIAb{CGdAJBb}}}}{AIAb{CGdNKb}}}{{DAh{{KHf{ICl}}}}}}\",[]]00Ec[\"{{{ABd{{AIAb{CGdAJBb}}}}{AIAb{CGdNKb}}{AIAb{{KAb{Oj}}}}}{{DAh{{KHf{KCj}}}}}}\",[]]0Eg[\"{{{ABd{{AIAb{CGdAJBb}}}}{AIAb{CGdNKb}}{AIAb{{KAb{Oj}}}}}{{DAh{{KHd{KCjAJCn}}}}}}\",[]]Bm[\"{{{AIAb{IHn}}{AIAb{D@n}}}{{KHf{ICl}}}}\",[]]Cd[\"{{{AIAb{CGdAJAf}}KCj}{{KHd{{KGl{IDb}}AHHd}}}}\",[]]Cd[\"{{{AIAb{CGdBGCn}}KCj}{{KHd{{KGl{IDb}}AHHd}}}}\",[]]Cn[\"{{{AIAb{CGdAJAf}}{AIAb{CGd{KAb{Oj}}}}}{{KHd{IClBCBf}}}}\",[]]Bj[\"{{{AIAb{DA`}}AJ@l}{{KHd{IClBFMb}}}}\",[]]Bn[\"{{{AIAb{CGdAJA`}}NLd}{{AIAb{CGdAJA`}}}}\",[]]Bn[\"{{{AIAb{CGdAJAb}}NLd}{{AIAb{CGdAJAb}}}}\",[]]A`[\"{BCBlAGb}\",[]]o[\"{IHlCNb}\",[]]o[\"{{}AJBh}\",[]]0o[\"{{}AIAb}\",[]]Bk[\"{{ABC`{A@d{BADd}}}{{KHd{AIMnBCO`}}}}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]Bh[\"{{{AIAb{AHD`}}AJBh}{{KHh{AHHn}}}}\",[]]A`[\"{AHGnCMn}\",[]]Aj[\"{{{AIAb{AINd}}}CMn}\",[]]A`[\"{AHH`CMn}\",[]]Ce[\"{{{AIAb{CGdAJA`}}{KGl{KIl}}}{{AIAb{CGdAJA`}}}}\",[]]Ce[\"{{{AIAb{CGdAJAb}}{KGl{KIl}}}{{AIAb{CGdAJAb}}}}\",[]]Aa[\"{ABFhAJBh}\",[]]Aa[\"{ABFjAJBh}\",[]]Aa[\"{ABFlAJBh}\",[]]Aa[\"{AJBhAHEf}\",[]]o[\"{AAlCMn}\",[]]Bn[\"{{{AIAb{CGdBDAn}}CMn}{{AIAb{CGdBDAn}}}}\",[]]Bn[\"{{{AIAb{CGdBDB`}}CMn}{{AIAb{CGdBDB`}}}}\",[]]A`[\"{AHD`CMn}\",[]]Bo[\"{{{AIAb{CGdBDAn}}ABBd}{{AIAb{CGdBDAn}}}}\",[]]Bo[\"{{{AIAb{CGdBDB`}}ABBd}{{AIAb{CGdBDB`}}}}\",[]]A`[\"{AHD`KGl}\",[]]k[\"{{}}\",[]]0000000000Bh[\"{ABC`c{}}\",[\"IntoFuture::IntoFuture\"]]11A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]o[\"{AAlKCj}\",[]]Ao[\"{{AIAbc}{{NLj{c}}}{}}\",[\"S\"]]0C`[\"{{{AIAb{CGdAINf}}NLdCNbDC`{KGl{CNb}}}ICl}\",[]]Bo[\"{{{AIAb{CGdCNj}}NLdCNbDC`{KGl{CNb}}}ICl}\",[]]C`[\"{{{AIAb{CGdAINh}}NLdCNbDC`{KGl{CNb}}}ICl}\",[]]Bo[\"{{{AIAb{CGdCNl}}NLdCNbDC`{KGl{CNb}}}ICl}\",[]]Bk[\"{{{AIAb{CGdIHn}}NLd{AIAb{BD@b}}}ICl}\",[]]A`[\"{BFDlKGl}\",[]]A`[\"{BFDnKGl}\",[]]Bn[\"{{{AIAb{KJ`}}{AIAb{KJ`}}}{{KGl{ABDj}}}}\",[]]Bn[\"{{{AIAb{KGn}}{AIAb{KGn}}}{{KGl{ABDj}}}}\",[]]C`[\"{{{AIAb{AON`}}{AIAb{AON`}}}{{KGl{ABDj}}}}\",[]]C`[\"{{{AIAb{B@Of}}{AIAb{B@Of}}}{{KGl{ABDj}}}}\",[]]Bn[\"{{{AIAb{DAl}}{AIAb{DAl}}}{{KGl{ABDj}}}}\",[]]Bn[\"{{{AIAb{A@l}}{AIAb{A@l}}}{{KGl{ABDj}}}}\",[]]C`[\"{{{AIAb{ABEd}}{AIAb{ABEd}}}{{KGl{ABDj}}}}\",[]]Bn[\"{{{AIAb{KJb}}{AIAb{KJb}}}{{KGl{ABDj}}}}\",[]]C`[\"{{{AIAb{BFFh}}{AIAb{BFFh}}}{{KGl{ABDj}}}}\",[]]Bn[\"{{{AIAb{KH`}}{AIAb{KH`}}}{{KGl{ABDj}}}}\",[]]C`[\"{{{AIAb{AHHf}}{AIAb{AHHf}}}{{KGl{ABDj}}}}\",[]]C`[\"{{{AIAb{BBDl}}{AIAb{BBDl}}}{{KGl{ABDj}}}}\",[]]C`[\"{{{AIAb{AJD`}}{AIAb{AJD`}}}{{KGl{ABDj}}}}\",[]]C`[\"{{{AIAb{AONb}}{AIAb{AONb}}}{{KGl{ABDj}}}}\",[]]C`[\"{{{AIAb{BEIf}}{AIAb{BEIf}}}{{KGl{ABDj}}}}\",[]]C`[\"{{{AIAb{BFEn}}{AIAb{BFEn}}}{{KGl{ABDj}}}}\",[]]C`[\"{{{AIAb{B@Oh}}{AIAb{B@Oh}}}{{KGl{ABDj}}}}\",[]]Bn[\"{{{AIAb{DAn}}{AIAb{DAn}}}{{KGl{ABDj}}}}\",[]]Bn[\"{{{AIAb{A@n}}{AIAb{A@n}}}{{KGl{ABDj}}}}\",[]]C`[\"{{{AIAb{ABEf}}{AIAb{ABEf}}}{{KGl{ABDj}}}}\",[]]Ak[\"{{{AIAb{AINb}}}AJ@j}\",[]]Bk[\"{{{AIAb{AINd}}KH`}{{KHd{AJ@nAIMj}}}}\",[]]Df[\"{{{AIAb{CGdAJAf}}{AIAb{CGd{KAb{ID`}}}}}{{KHd{{KGl{KCj}}AHHd}}}}\",[]]Cc[\"{{{AIAb{CGdAJAf}}KCj}{{KHd{{ABj{Oj}}BCBh}}}}\",[]]Bb[\"{{{AIAb{CGdAINd}}ABEf}AJAh}\",[]]Aj[\"{{{AIAb{AHEn}}}KCj}\",[]]Aj[\"{{{AIAb{AHF`}}}KCj}\",[]]Bb[\"{{{AIAb{CGdAINd}}ABEf}AJBd}\",[]]Bn[\"{{{AIAb{CGdBDAn}}CNb}{{AIAb{CGdBDAn}}}}\",[]]Bn[\"{{{AIAb{CGdBDB`}}CNb}{{AIAb{CGdBDB`}}}}\",[]]A`[\"{AHD`KGl}\",[]]A`[\"{BBAjKGl}\",[]]A`[\"{BBAlKGl}\",[]]Cf[\"{{{AIAb{CGdBADd}}{A@d{AJC`}}}{{AIAb{CGdBADd}}}}\",[]]Cf[\"{{{AIAb{CGdBADh}}{A@d{AJCb}}}{{AIAb{CGdBADh}}}}\",[]]Cf[\"{{{AIAb{CGdB@Mh}}{A@d{AJCh}}}{{AIAb{CGdB@Mh}}}}\",[]]Cf[\"{{{AIAb{CGdB@Ml}}{A@d{AJCj}}}{{AIAb{CGdB@Ml}}}}\",[]]o[\"{AAlCNb}\",[]]Bn[\"{{{AIAb{CGdBFKd}}CMn}{{AIAb{CGdBFKd}}}}\",[]]Bn[\"{{{AIAb{CGdBFKf}}CMn}{{AIAb{CGdBFKf}}}}\",[]]Ak[\"{{{AIAb{AINb}}}BGKn}\",[]]Aj[\"{{{AIAb{DA`}}}BCCf}\",[]]Aj[\"{{{A@d{BADf}}}BADd}\",[]]Aj[\"{{{A@d{BADj}}}BADh}\",[]]Aj[\"{{BD@lc}BD@lABEn}\",[\"\"]]Aj[\"{{BD@nc}BD@nABEn}\",[\"\"]]Bm[\"{{{AIAb{CGdAJBb}}ID`}{{KHd{IClAJCn}}}}\",[]]n[\"{{}KFj}\",[]]o[\"{NK`NK`}\",[]]0000Bd[\"{{{AIAb{BHAh}}}{{KGl{ABBd}}}}\",[]]Bd[\"{{{AIAb{BHAj}}}{{KGl{ABBd}}}}\",[]]Bd[\"{{{AIAb{BIC`}}}{{KGl{ABBd}}}}\",[]]Bd[\"{{{AIAb{BICj}}}{{KGl{ABBd}}}}\",[]]Bd[\"{{{AIAb{AINb}}}{{KGl{BCO`}}}}\",[]]Ck[\"{{{AIAb{ABBh}}B@MhAJBh{AIAb{CMl}}}{{KHd{AIMnB@O`}}}}\",[]]Ak[\"{{{AIAb{BACl}}}ABBd}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]An[\"{{AJAdDC`}{{KHh{KCj}}}}\",[]]Cl[\"{{{AIAb{NNj}}}{{KGl{{KCh{{A@f{AHEn}}{A@f{AHFl}}}}}}}}\",[]]Cm[\"{{{AIAb{AJCd}}}{{KGl{{KCh{{A@f{AHEn}}{A@f{AHFl}}}}}}}}\",[]]Cl[\"{{{AIAb{NNl}}}{{KGl{{KCh{{A@f{AHF`}}{A@f{AHFn}}}}}}}}\",[]]Cm[\"{{{AIAb{AJCf}}}{{KGl{{KCh{{A@f{AHF`}}{A@f{AHFn}}}}}}}}\",[]]A`[\"{BGIlABj}\",[]]A`[\"{BIGbABj}\",[]]A`[\"{BGInABj}\",[]]A`[\"{BIGdABj}\",[]]An[\"{{{AIAb{CGdBHAh}}}B@Of}\",[]]An[\"{{{AIAb{CGdBHAj}}}B@Oh}\",[]]An[\"{{{AIAb{CGdBIC`}}}B@Oh}\",[]]An[\"{{{AIAb{CGdBICj}}}B@Oh}\",[]]Ak[\"{{{AIAb{BCCd}}}BAAj}\",[]]Ba[\"{{{AIAb{CGdAINd}}BCOf}ICl}\",[]]Bo[\"{{{AIAb{CGdABBj}}BEIfBBAd}{{KGl{BCOf}}}}\",[]]B`[\"{{{AIAb{NNj}}B@OfDAl}D@d}\",[]]Bn[\"{{{AIAb{BADf}}CN`B@Of}{{KHd{D@dBFNd}}}}\",[]]Ba[\"{{{AIAb{AJCd}}B@OfDAl}D@d}\",[]]Bn[\"{{{AIAb{BELj}}CN`B@Of}{{KHd{D@dBFNd}}}}\",[]]B`[\"{{{AIAb{NNl}}B@OhDAn}D@f}\",[]]Bn[\"{{{AIAb{BADj}}CN`B@Oh}{{KHd{D@fBFNj}}}}\",[]]Ba[\"{{{AIAb{AJCf}}B@OhDAn}D@f}\",[]]Bn[\"{{{AIAb{BELl}}CN`B@Oh}{{KHd{D@fBFNj}}}}\",[]]Bn[\"{{{AIAb{BHMf}}CN`B@Od}{{KHd{D@fBFNj}}}}\",[]]Aj[\"{{}{{NNd{c}}}{}}\",[\"T\"]]0A`[\"{AHGnCNb}\",[]]A`[\"{BCOhCNb}\",[]]A`[\"{AHH`CNb}\",[]]A`[\"{BCOjCNb}\",[]]Bn[\"{{{AIAb{CGdBADd}}KCj}{{AIAb{CGdBADd}}}}\",[]]Bn[\"{{{AIAb{CGdBADh}}KCj}{{AIAb{CGdBADh}}}}\",[]]Aj[\"{{{AIAb{BBN`}}}DC`}\",[]]0Aj[\"{{{AIAb{BCCd}}}DC`}\",[]]Bk[\"{{{AIAb{AINd}}KH`}{{KHd{AHEfAIMj}}}}\",[]]Cc[\"{{{AIAb{BD@b}}{AIAb{AJAd}}CNb}{{A@d{B@@h}}}}\",[]]Ak[\"{{{AIAb{ABC`}}}B@Of}\",[]]Ak[\"{{{AIAb{ABCb}}}B@Oh}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]Bf[\"{{{AIAb{CGdAINd}}}{{KGl{NLd}}}}\",[]]Cf[\"{{{AIAb{CGdBDAn}}{A@d{AONn}}}{{AIAb{CGdBDAn}}}}\",[]]Cf[\"{{{AIAb{CGdBDB`}}{A@d{AOO`}}}{{AIAb{CGdBDB`}}}}\",[]]43A`[\"{AAlAOOf}\",[]]A`[\"{ABFhKGl}\",[]]A`[\"{ABFjKGl}\",[]]A`[\"{ABFlKGl}\",[]]A`[\"{BFDlKGl}\",[]]A`[\"{BFDnKGl}\",[]]Ai[\"{{{AIAb{NOj}}}KCj}\",[]]Bo[\"{{{AIAb{CGdBIC`}}ABBd}{{AIAb{CGdBIC`}}}}\",[]]Bo[\"{{{AIAb{CGdBICj}}ABBd}{{AIAb{CGdBICj}}}}\",[]]Bj[\"{{{AIAb{AJBb}}CEn}{{KHd{IClB@Nd}}}}\",[]]Bm[\"{{{AIAb{CGdAJBd}}CEn}{{KHd{IClB@Nl}}}}\",[]]?>A`[\"{IHlB@@f}\",[]]A`[\"{AOLhIHl}\",[]]Bj[\"{{{A@d{B@Md}}II`}{{KHd{BELfBGJl}}}}\",[]]Bj[\"{{{A@d{BAD`}}II`}{{KHd{BELjBGJl}}}}\",[]]Bj[\"{{{A@d{B@Md}}II`}{{KHd{BELhBGJn}}}}\",[]]Bj[\"{{{A@d{BAD`}}II`}{{KHd{BELlBGJn}}}}\",[]]Co[\"{{{AIAb{CGdAJBb}}{AIAb{CGd{KAb{ID`}}}}}{{KHd{O@fAJCn}}}}\",[]]Co[\"{{{AIAb{CGdAJBd}}{AIAb{CGd{KAb{ID`}}}}}{{KHd{O@hAJD`}}}}\",[]]Aj[\"{{{AIAb{AINd}}}DC`}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]A`[\"{AHD`DC`}\",[]]Cd[\"{{{AIAb{CGdBBOh}}{A@d{A`}}}{{AIAb{CGdBBOh}}}}\",[]]Cd[\"{{{AIAb{CGdBBOj}}{A@d{A`}}}{{AIAb{CGdBBOj}}}}\",[]]Bk[\"{{{AIAb{BBN`}}}{{ABd{{A@f{AHHl}}}}}}\",[]]o[\"{AAlCNb}\",[]]Aa[\"{B@@fAHFb}\",[]]76Aa[\"{AJAlABC`}\",[]]Aa[\"{AJAnABCb}\",[]]Cf[\"{{{AIAb{CGdBFIf}}{KGl{ABBd}}}{{AIAb{CGdBFIf}}}}\",[]]Cf[\"{{{AIAb{CGdBFIh}}{KGl{ABBd}}}{{AIAb{CGdBFIh}}}}\",[]]9;:An[\"{{{AIAb{B@@h}}ABBd}ICl}\",[]]B`[\"{{{AIAb{CGdAINf}}CMn}ICl}\",[]]Ao[\"{{{AIAb{CGdCNj}}CMn}ICl}\",[]]Ao[\"{{{AIAb{CGdIDj}}CMn}ICl}\",[]]Ao[\"{{{AIAb{CGdNM`}}CMn}ICl}\",[]]B`[\"{{{AIAb{CGdAINh}}CMn}ICl}\",[]]Ao[\"{{{AIAb{CGdCNl}}CMn}ICl}\",[]]Ao[\"{{{AIAb{CGdIDl}}CMn}ICl}\",[]]Ao[\"{{{AIAb{CGdNMb}}CMn}ICl}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]10Bj[\"{{{AIAb{AINb}}}{{KGl{{A@f{A@b}}}}}}\",[]]Bi[\"{{{AIAb{NNj}}}{{KGl{{A@f{A@b}}}}}}\",[]]Bj[\"{{{AIAb{AJCd}}}{{KGl{{A@f{A@b}}}}}}\",[]]Bi[\"{{{AIAb{NNl}}}{{KGl{{A@f{A@b}}}}}}\",[]]Bj[\"{{{AIAb{AJCf}}}{{KGl{{A@f{A@b}}}}}}\",[]]Db[\"{{{ABd{{AIAb{CGdAJBb}}}}{AIAb{CGdNKb}}}{{DAh{{KHf{ICl}}}}}}\",[]]Db[\"{{{AIAb{CGdAINd}}NLdBAAj{AIAb{CGd{ABj{Oj}}}}}{{KGl{ABFj}}}}\",[]]Cf[\"{{{AIAb{CGdBDAn}}{AIAb{CMl}}}{{AIAb{CGdBDAn}}}}\",[]]Cf[\"{{{AIAb{CGdBDB`}}{AIAb{CMl}}}{{AIAb{CGdBDB`}}}}\",[]]Ak[\"{{{AIAb{AINb}}}BAC`}\",[]]Bj[\"{{{AIAb{AINb}}ID`}{{KHd{IClBFFf}}}}\",[]]Bn[\"{{{AIAb{CGdBDAn}}DC`}{{AIAb{CGdBDAn}}}}\",[]]Bn[\"{{{AIAb{CGdBDB`}}DC`}{{AIAb{CGdBDB`}}}}\",[]]Ai[\"{{{AIAb{NKn}}}KCj}\",[]]Ai[\"{{{AIAb{NL`}}}KCj}\",[]]Dh[\"{{{AIAb{B@Mj}}CN`{AIAb{CMl}}{AIAb{BGCl}}}{{KHd{{A@f{NNj}}B@O`}}}}\",[]]Cb[\"{{{AIAb{BADf}}CN`{AIAb{BGCl}}}{{A@f{NNj}}}}\",[]]Dh[\"{{{AIAb{BELf}}CN`{AIAb{CMl}}{AIAb{BGCl}}}{{KHd{{A@f{NNj}}B@O`}}}}\",[]]Cb[\"{{{AIAb{BELj}}CN`{AIAb{BGCl}}}{{A@f{NNj}}}}\",[]]Dh[\"{{{AIAb{B@Mn}}CN`{AIAb{CMl}}{AIAb{BGCl}}}{{KHd{{A@f{NNl}}B@Ob}}}}\",[]]Cb[\"{{{AIAb{BADj}}CN`{AIAb{BGCl}}}{{A@f{NNl}}}}\",[]]Dh[\"{{{AIAb{BELh}}CN`{AIAb{CMl}}{AIAb{BGCl}}}{{KHd{{A@f{NNl}}B@Ob}}}}\",[]]Cb[\"{{{AIAb{BELl}}CN`{AIAb{BGCl}}}{{A@f{NNl}}}}\",[]]Dh[\"{{{AIAb{BHMd}}CN`{AIAb{CMl}}{AIAb{BGCl}}}{{KHd{{A@f{NNl}}B@Nn}}}}\",[]]Cb[\"{{{AIAb{BHMf}}CN`{AIAb{BGCl}}}{{A@f{NNl}}}}\",[]]Dd[\"{{{ABd{{AIAb{CGdc}}}}{AIAb{CGdNKb}}}{{DAh{{KGl{KHd}}}}}{}}\",[\"S\"]]0Bn[\"{{AJBhBBNbAAl}{{KHh{{KCh{ABBfAIN`}}}}}}\",[]]Bd[\"{{{AIAb{AINd}}}{{AIAb{NNl}}}}\",[]]A`[\"{AOLhIHl}\",[]]Cb[\"{{{AIAb{AONn}}DAlAJBhB@OfNLd}{{KGl{AJA`}}}}\",[]]Cb[\"{{{AIAb{BD@l}}DAlAJBhB@OfNLd}{{KGl{AJA`}}}}\",[]]Cb[\"{{{AIAb{AOO`}}DAnAJBhB@OhNLd}{{KGl{AJAb}}}}\",[]]Cb[\"{{{AIAb{BD@n}}DAnAJBhB@OhNLd}{{KGl{AJAb}}}}\",[]]B`[\"{{{AIAb{CGdAINd}}NLd}ICl}\",[]]Ca[\"{{{AIAb{CGdAIMn}}}{{KHd{{A@f{A@b}}BCO`}}}}\",[]]Bj[\"{{{AIAb{AINb}}}{{KGl{{A@f{A@b}}}}}}\",[]]Bi[\"{{{AIAb{NNj}}}{{KGl{{A@f{A@b}}}}}}\",[]]Bj[\"{{{AIAb{AJCd}}}{{KGl{{A@f{A@b}}}}}}\",[]]Bi[\"{{{AIAb{NNl}}}{{KGl{{A@f{A@b}}}}}}\",[]]Bj[\"{{{AIAb{AJCf}}}{{KGl{{A@f{A@b}}}}}}\",[]]o[\"{AINnOj}\",[]]o[\"{AIO`Oj}\",[]]Ai[\"{{{AIAb{CMl}}}DC`}\",[]]Aj[\"{{{AIAb{AINf}}}CNb}\",[]]Ai[\"{{{AIAb{CNj}}}CNb}\",[]]Bn[\"{{{AIAb{CGdAILl}}CNb}{{AIAb{CGdAILl}}}}\",[]]Ai[\"{{{AIAb{IDj}}}CNb}\",[]]Bn[\"{{{AIAb{CGdAOMf}}CNb}{{AIAb{CGdAOMf}}}}\",[]]Ai[\"{{{AIAb{NM`}}}CNb}\",[]]Bn[\"{{{AIAb{CGdBBCl}}CNb}{{AIAb{CGdBBCl}}}}\",[]]Aj[\"{{{AIAb{AINh}}}CNb}\",[]]Ai[\"{{{AIAb{CNl}}}CNb}\",[]]Bn[\"{{{AIAb{CGdAILn}}CNb}{{AIAb{CGdAILn}}}}\",[]]Ai[\"{{{AIAb{IDl}}}CNb}\",[]]Bn[\"{{{AIAb{CGdAOMh}}CNb}{{AIAb{CGdAOMh}}}}\",[]]Ai[\"{{{AIAb{NMb}}}CNb}\",[]]Bn[\"{{{AIAb{CGdBBCn}}CNb}{{AIAb{CGdBBCn}}}}\",[]]Aa[\"{AJAfBGCn}\",[]]Ai[\"{{{AIAb{NNj}}}DC`}\",[]]Aj[\"{{{AIAb{AJCd}}}DC`}\",[]]Aj[\"{{{AIAb{AINd}}}DC`}\",[]]Ai[\"{{{AIAb{NNl}}}DC`}\",[]]Aj[\"{{{AIAb{AJCf}}}DC`}\",[]]Co[\"{{{AIAb{NNj}}B@Of{AIAb{{KAb{Oj}}}}{AIAb{{KAb{Oj}}}}}DC`}\",[]]D`[\"{{{AIAb{AJCd}}B@Of{AIAb{{KAb{Oj}}}}{AIAb{{KAb{Oj}}}}}DC`}\",[]]Co[\"{{{AIAb{NNl}}B@Oh{AIAb{{KAb{Oj}}}}{AIAb{{KAb{Oj}}}}}DC`}\",[]]D`[\"{{{AIAb{AJCf}}B@Oh{AIAb{{KAb{Oj}}}}{AIAb{{KAb{Oj}}}}}DC`}\",[]]Bn[\"{{{AIAb{CGdBFKd}}CMn}{{AIAb{CGdBFKd}}}}\",[]]Bn[\"{{{AIAb{CGdBFKf}}CMn}{{AIAb{CGdBFKf}}}}\",[]]Cd[\"{{{AIAb{CGdNNj}}}{{KGl{{NLf{{A@f{AHFl}}}}}}}}\",[]]Ce[\"{{{AIAb{CGdAJCd}}}{{KGl{{NLf{{A@f{AHFl}}}}}}}}\",[]]Cd[\"{{{AIAb{CGdNNl}}}{{KGl{{NLh{{A@f{AHFn}}}}}}}}\",[]]Ce[\"{{{AIAb{CGdAJCf}}}{{KGl{{NLh{{A@f{AHFn}}}}}}}}\",[]]Bf[\"{{{AIAb{CGdAINf}}CMnCNbNLd}ICl}\",[]]Be[\"{{{AIAb{CGdCNj}}CMnCNbNLd}ICl}\",[]]Bf[\"{{{AIAb{CGdAINh}}CMnCNbNLd}ICl}\",[]]Be[\"{{{AIAb{CGdCNl}}CMnCNbNLd}ICl}\",[]]Bf[\"{{{AIAb{CGdAINf}}NLdCMnCNb}ICl}\",[]]Be[\"{{{AIAb{CGdCNj}}NLdCMnCNb}ICl}\",[]]Bf[\"{{{AIAb{CGdAINh}}NLdCMnCNb}ICl}\",[]]Be[\"{{{AIAb{CGdCNl}}NLdCMnCNb}ICl}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]De[\"{{{AIAb{CGdBDAn}}c{AIAb{CMl}}}{{AIAb{CGdBDAn}}}{{ICn{D@n}}}}\",[\"\"]]De[\"{{{AIAb{CGdBDB`}}c{AIAb{CMl}}}{{AIAb{CGdBDB`}}}{{ICn{D@n}}}}\",[\"\"]]Ci[\"{{{AIAb{CGdNNj}}{AIAb{{KAb{Oj}}}}}{{KHd{DC`IEn}}}}\",[]]Cj[\"{{{AIAb{CGdAJCd}}{AIAb{{KAb{Oj}}}}}{{KHd{DC`IEn}}}}\",[]]Cj[\"{{{AIAb{CGdNNl}}{AIAb{{KAb{Oj}}}}}{{KHd{DC`BCCb}}}}\",[]]Ck[\"{{{AIAb{CGdAJCf}}{AIAb{{KAb{Oj}}}}}{{KHd{DC`BCCb}}}}\",[]]o[\"{AAlDC`}\",[]]Bn[\"{{{AIAb{CGdBDAn}}KJ`}{{AIAb{CGdBDAn}}}}\",[]]Bn[\"{{{AIAb{CGdBDB`}}KJb}{{AIAb{CGdBDB`}}}}\",[]]A`[\"{AHD`KGl}\",[]]Ca[\"{{{AIAb{CGdAJAf}}}{{KHd{{KGl{KJ`}}AJAj}}}}\",[]]Ca[\"{{{AIAb{CGdBGCn}}}{{KHd{{KGl{KJ`}}AJAj}}}}\",[]]Ca[\"{{{AIAb{CGdAJAh}}}{{KHd{{KGl{KJb}}B@Nl}}}}\",[]]Ak[\"{{{AIAb{AIMn}}}AJBh}\",[]]Ak[\"{{{AIAb{AINb}}}AJBh}\",[]]Ak[\"{{{AIAb{ABC`}}}AJBh}\",[]]Bg[\"{{{AIAb{DA`}}}{{KHd{AJBhAIMd}}}}\",[]]Ak[\"{{{AIAb{ABCb}}}AJBh}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]B`[\"{{KJ`c}KJ`{{D@b{KJ`}}}}\",[\"\"]]B`[\"{{KGnc}KGn{{D@b{KGn}}}}\",[\"\"]]B`[\"{{KJbc}KJb{{D@b{KJb}}}}\",[\"\"]]B`[\"{{KH`c}KH`{{D@b{KH`}}}}\",[\"\"]]20Ae[\"{{ABBdCNb}AGb}\",[]]Bn[\"{{{AIAb{CGdBDAn}}AG`}{{AIAb{CGdBDAn}}}}\",[]]Bn[\"{{{AIAb{CGdBDB`}}AG`}{{AIAb{CGdBDB`}}}}\",[]]A`[\"{IHlABBd}\",[]]B`[\"{{}{{AIAb{{KAb{BGBl}}}}}}\",[]]Ao[\"{{AIAb{A@@b{CGdOj}}}ICl}\",[]]000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000An[\"{{}{{KGl{{A@d{NNh}}}}}}\",[]]Bn[\"{{{AIAb{CGdBBOh}}DC`}{{AIAb{CGdBBOh}}}}\",[]]Bn[\"{{{AIAb{CGdBBOj}}DC`}{{AIAb{CGdBBOj}}}}\",[]]Aj[\"{{{AIAb{AHFl}}}CNb}\",[]]Aj[\"{{{AIAb{AHFn}}}CNb}\",[]]A`[\"{ABE`KGl}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]10Bf[\"{{{AIAb{CMl}}}{{KHh{CNbBBD`}}}}\",[]]Bn[\"{{{AIAb{ABBh}}{A@f{BBN`}}}{{KHf{ICl}}}}\",[]]Ai[\"{AIN`{{KHh{ICl}}}}\",[]]Cj[\"{{{AIAb{NNf}}BBNbBBDj{AIAb{AAl}}}{{KCh{AJBhABBf}}}}\",[]]Cb[\"{{{AIAb{CGdAINd}}KH`AJ@n}{{KHd{AJ@nBFMd}}}}\",[]]Am[\"{{{AIAb{AINb}}CNb}ICl}\",[]]B`[\"{{{AIAb{CGdAINd}}CNb}ICl}\",[]]A`[\"{BCBjDC`}\",[]]B`[\"{{{AIAb{CGdIHl}}BCBl}ICl}\",[]]Aa[\"{B@@fAHFb}\",[]]Bn[\"{{{AIAb{NNh}}AHHn}{{KHf{{A@f{BBN`}}}}}}\",[]]Bo[\"{{{AIAb{BAEb}}AHHn}{{KHf{{A@f{BBN`}}}}}}\",[]]Bo[\"{{{AIAb{AOOl}}AHHn}{{KHf{{A@f{BBN`}}}}}}\",[]]Ci[\"{{{AIAb{CGdNNj}}{AIAb{CGd{ABj{Oj}}}}}{{KGl{D@d}}}}\",[]]Cj[\"{{{AIAb{CGdAJCd}}{AIAb{CGd{ABj{Oj}}}}}{{KGl{D@d}}}}\",[]]Ci[\"{{{AIAb{CGdNNl}}{AIAb{CGd{ABj{Oj}}}}}{{KGl{D@f}}}}\",[]]Cj[\"{{{AIAb{CGdAJCf}}{AIAb{CGd{ABj{Oj}}}}}{{KGl{D@f}}}}\",[]]Cb[\"{{{AIAb{ABF`}}ICjAJBnABBd}{{KHd{IClBDAh}}}}\",[]]Cb[\"{{{AIAb{BB@d}}ICjAJBnABBd}{{KHd{IClBDAh}}}}\",[]]Cb[\"{{{AIAb{BAAl}}ICjAJBnABBd}{{KHd{IClBDAh}}}}\",[]]Cb[\"{{{AIAb{ABFb}}ICjAJBnABBd}{{KHd{IClBDAj}}}}\",[]]Cb[\"{{{AIAb{BB@f}}ICjAJBnABBd}{{KHd{IClBDAj}}}}\",[]]Cb[\"{{{AIAb{BAAn}}ICjAJBnABBd}{{KHd{IClBDAj}}}}\",[]]Al[\"{{}{{KHd{B@MfBGJn}}}}\",[]]Bn[\"{{{AIAb{AINb}}KGn}{{KGl{{A@f{AINf}}}}}}\",[]]Bo[\"{{{AIAb{AINd}}KH`}{{KGl{{AIAb{AINh}}}}}}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]Aj[\"{{{AIAb{AINb}}}ICl}\",[]]Am[\"{{{AIAb{CGdAINd}}}ICl}\",[]]Bf[\"{{{AIAb{AILb}}}{{KHd{AAlIF`}}}}\",[]]Bf[\"{{{AIAb{AILb}}}{{KHd{A@jIF`}}}}\",[]]Bg[\"{{{AIAb{AILb}}}{{KHd{ABAfIF`}}}}\",[]]Bg[\"{{{AIAb{AILb}}}{{KHd{AHD`IF`}}}}\",[]]Bf[\"{{{AIAb{AILb}}}{{KHd{AAnIF`}}}}\",[]]Bf[\"{{{AIAb{AILb}}}{{KHd{AB`IF`}}}}\",[]]Ak[\"{{{AIAb{BCCd}}}BAAj}\",[]]Cf[\"{{{AIAb{CGdBDAn}}{KGl{AON`}}}{{AIAb{CGdBDAn}}}}\",[]]Cf[\"{{{AIAb{CGdBDB`}}{KGl{AONb}}}{{AIAb{CGdBDB`}}}}\",[]]<;Aj[\"{{{AIAb{ABBh}}}KCj}\",[]]Aj[\"{{{AIAb{ABBj}}}KCj}\",[]]Bc[\"{{{AIAb{AINb}}AJBhAJ@l}ABDh}\",[]]Co[\"{{{AIAb{CGdAINd}}AHEfAJ@nNLd}{{KHd{{KCh{KH`DC`}}AHGd}}}}\",[]]Bn[\"{{{AIAb{CGdBDAn}}CN`}{{AIAb{CGdBDAn}}}}\",[]]Bn[\"{{{AIAb{CGdBDB`}}CN`}{{AIAb{CGdBDB`}}}}\",[]]Ai[\"{AIN`{{KHh{ICl}}}}\",[]]Bg[\"{{{AIAb{BCCd}}AJCl}{{KHf{KCj}}}}\",[]]A`[\"{AHD`CNb}\",[]]10Aj[\"{{{AIAb{AAl}}}BDAl}\",[]]Cf[\"{{{AIAb{CGdBADd}}{A@d{BDAn}}}{{AIAb{CGdBADd}}}}\",[]]Cf[\"{{{AIAb{CGdB@Mh}}{A@d{BDAn}}}{{AIAb{CGdB@Mh}}}}\",[]]Cf[\"{{{AIAb{CGdBADh}}{A@d{BDB`}}}{{AIAb{CGdBADh}}}}\",[]]Cf[\"{{{AIAb{CGdB@Ml}}{A@d{BDB`}}}{{AIAb{CGdB@Ml}}}}\",[]]Aa[\"{BADdBHBj}\",[]]Aa[\"{BADhBHBl}\",[]]Bj[\"{{{ABj{BBNb}}BBDj}{{KHd{BADdIFd}}}}\",[]]Bj[\"{{{ABj{BBNb}}BBDj}{{KHd{BADhIFf}}}}\",[]]Co[\"{{{AIAb{CGdAJBb}}{AIAb{CGd{KAb{ID`}}}}}{{KHd{IClAJCn}}}}\",[]]A`[\"{AHGnCNb}\",[]]A`[\"{AHH`CNb}\",[]]A`[\"{BFDlCNb}\",[]]A`[\"{BFDnCNb}\",[]]Bc[\"{{{AIAb{AINb}}}{{KGl{KCj}}}}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]10Aj[\"{{{AIAb{AHDf}}}KCj}\",[]]Be[\"{{{AIAb{ABBh}}{KGl{BADd}}}ICl}\",[]]Bo[\"{{{AIAb{CGdABBj}}{KGl{{A@d{BADh}}}}}ICl}\",[]]Bd[\"{{{AIAb{BGBl}}}{{KGl{BBDh}}}}\",[]]o[\"{{}AIAb}\",[]]n[\"{{}NK`}\",[]]0000Bn[\"{{{AIAb{CGdBDAn}}KCj}{{AIAb{CGdBDAn}}}}\",[]]Bn[\"{{{AIAb{CGdBDB`}}KCj}{{AIAb{CGdBDB`}}}}\",[]]o[\"{CNbKJ`}\",[]]o[\"{CNbKJb}\",[]]A`[\"{BBAfCNb}\",[]]Bf[\"{{{AIAb{BGKn}}{AIAb{BGKn}}}DC`}\",[]]Bo[\"{{{AIAb{CGdBBOh}}ABBd}{{AIAb{CGdBBOh}}}}\",[]]Bo[\"{{{AIAb{CGdBBOj}}ABBd}{{AIAb{CGdBBOj}}}}\",[]]Ae[\"{{KCjCNb}BB@d}\",[]]Ae[\"{{KCjCNb}BB@f}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]7Bg[\"{{{AIAb{AINd}}KH`}{{KGl{AJ@n}}}}\",[]]An[\"{{{AIAb{AINb}}ID`}BACn}\",[]]Bm[\"{{{AIAb{CGdBELf}}{ABj{{ABj{Oj}}}}}ICl}\",[]]Bm[\"{{{AIAb{CGdBELj}}{ABj{{ABj{Oj}}}}}ICl}\",[]]Bm[\"{{{AIAb{CGdBELh}}{ABj{{ABj{Oj}}}}}ICl}\",[]]Bm[\"{{{AIAb{CGdBELl}}{ABj{{ABj{Oj}}}}}ICl}\",[]]Am[\"{{{AIAb{AINb}}KJ`}ICl}\",[]]B`[\"{{{AIAb{CGdAINd}}KJb}ICl}\",[]]Ce[\"{{{AIAb{CGdBBOh}}{ABj{CN`}}}{{AIAb{CGdBBOh}}}}\",[]]Ce[\"{{{AIAb{CGdBBOj}}{ABj{CN`}}}{{AIAb{CGdBBOj}}}}\",[]]Fg[\"{{{AIAb{BHGd}}{AIAb{BBNb}}{AIAb{{KAb{BBNb}}}}{AIAb{AJBf}}{AIAb{{KAb{Oj}}}}ABGb}{{KHd{BFLnIEj}}}}\",[]]Af[\"{{BD@lNLd}BD@l}\",[]]Af[\"{{BD@nNLd}BD@n}\",[]]A`[\"{BBAfCNb}\",[]]Al[\"{BAEd{{KHd{DAlIFb}}}}\",[]]Al[\"{BAEd{{KHd{A@lIFb}}}}\",[]]Ai[\"{BAEd{{KHj{DAn}}}}\",[]]Ai[\"{BAEd{{KHj{A@n}}}}\",[]]o[\"{NK`NK`}\",[]]Bo[\"{{{AIAb{CGdBFKd}}ABBd}{{AIAb{CGdBFKd}}}}\",[]]Bo[\"{{{AIAb{CGdBFKf}}ABBd}{{AIAb{CGdBFKf}}}}\",[]]Bb[\"{{{AIAb{NNj}}}{{KGl{DC`}}}}\",[]]Bc[\"{{{AIAb{AJCd}}}{{KGl{DC`}}}}\",[]]Bb[\"{{{AIAb{NNl}}}{{KGl{DC`}}}}\",[]]Bc[\"{{{AIAb{AJCf}}}{{KGl{DC`}}}}\",[]]Bg[\"{{{AIAb{AINb}}}{{KHd{IClBCO`}}}}\",[]]Ba[\"{{{AIAb{BFEh}}KGnAHEf}DC`}\",[]]Ba[\"{{{AIAb{BFEj}}KH`AHEf}DC`}\",[]]Cf[\"{{{AIAb{CGdBDAn}}{KGl{ABBd}}}{{AIAb{CGdBDAn}}}}\",[]]Cf[\"{{{AIAb{CGdBDB`}}{KGl{ABBd}}}{{AIAb{CGdBDB`}}}}\",[]]A`[\"{AHGnCNb}\",[]]A`[\"{AHH`CNb}\",[]]Bo[\"{{{AIAb{CGdAINf}}NLdNLdDC`DC`CNbCNb}ICl}\",[]]Bn[\"{{{AIAb{CGdCNj}}NLdNLdDC`DC`CNbCNb}ICl}\",[]]Bn[\"{{{AIAb{CGdIDj}}NLdNLdDC`DC`CNbCNb}ICl}\",[]]Bn[\"{{{AIAb{CGdNM`}}NLdNLdDC`DC`CNbCNb}ICl}\",[]]Bo[\"{{{AIAb{CGdAINh}}NLdNLdDC`DC`CNbCNb}ICl}\",[]]Bn[\"{{{AIAb{CGdCNl}}NLdNLdDC`DC`CNbCNb}ICl}\",[]]Bn[\"{{{AIAb{CGdIDl}}NLdNLdDC`DC`CNbCNb}ICl}\",[]]Bn[\"{{{AIAb{CGdNMb}}NLdNLdDC`DC`CNbCNb}ICl}\",[]]A`[\"{BBAfCNb}\",[]]Al[\"{{{AIAb{NOj}}A@n}CNb}\",[]]Bi[\"{{{AIAb{CGdAJB`}}CNb}{{KHh{ICl}}}}\",[]]<;A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]10Cf[\"{{{AIAb{CGdBDAn}}{KGl{BFIf}}}{{AIAb{CGdBDAn}}}}\",[]]Cf[\"{{{AIAb{CGdBDB`}}{KGl{BFIh}}}{{AIAb{CGdBDB`}}}}\",[]]A`[\"{AHGnCNb}\",[]]A`[\"{AHH`CNb}\",[]]Bi[\"{{{AIAb{CGdAILb}}}{{KHd{AAlIF`}}}}\",[]]Bi[\"{{{AIAb{CGdAILb}}}{{KHd{A@jIF`}}}}\",[]]Bj[\"{{{AIAb{CGdAILb}}}{{KHd{ABAfIF`}}}}\",[]]Bj[\"{{{AIAb{CGdAILb}}}{{KHd{AHD`IF`}}}}\",[]]Bi[\"{{{AIAb{CGdAILb}}}{{KHd{AAnIF`}}}}\",[]]Bi[\"{{{AIAb{CGdAILb}}}{{KHd{AB`IF`}}}}\",[]]Cd[\"{{{A@d{AIN`}}CNbDC`}{{KHh{{KCh{BCBlBCBl}}}}}}\",[]]Bn[\"{{{AIAb{CGdBADd}}CNb}{{AIAb{CGdBADd}}}}\",[]]Bn[\"{{{AIAb{CGdBADh}}CNb}{{AIAb{CGdBADh}}}}\",[]]Aj[\"{{{AIAb{AINb}}}DC`}\",[]]Ak[\"{{{AIAb{BBN`}}}BAAj}\",[]]0Ci[\"{{{AIAb{CGdBBOh}}CMn}{{KHd{{AIAb{CGdBBOh}}AOLl}}}}\",[]]Ci[\"{{{AIAb{CGdBBOj}}CMn}{{KHd{{AIAb{CGdBBOj}}AOLn}}}}\",[]]A`[\"{AHD`CMn}\",[]]Cf[\"{{{AIAb{CGdBDAn}}{KGl{BFKd}}}{{AIAb{CGdBDAn}}}}\",[]]Cf[\"{{{AIAb{CGdBDB`}}{KGl{BFKf}}}{{AIAb{CGdBDB`}}}}\",[]]Bg[\"{{{AIAb{CGdAINd}}}{{KGl{BBAd}}}}\",[]]Cf[\"{{{AIAb{CGdBADd}}{KGl{BADl}}}{{AIAb{CGdBADd}}}}\",[]]Cf[\"{{{AIAb{CGdBADh}}{KGl{BADl}}}{{AIAb{CGdBADh}}}}\",[]]Cf[\"{{{AIAb{CGdBADd}}{KGl{BADn}}}{{AIAb{CGdBADd}}}}\",[]]Cf[\"{{{AIAb{CGdBADh}}{KGl{BADn}}}{{AIAb{CGdBADh}}}}\",[]]Cd[\"{{}{{KHd{{KCh{{ABj{BBNb}}BBDj}}{A@f{IEf}}}}}}\",[]]Bn[\"{{{AIAb{CGdBFIf}}KJ`}{{AIAb{CGdBFIf}}}}\",[]]Bn[\"{{{AIAb{CGdBFIh}}KJb}{{AIAb{CGdBFIh}}}}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]Bo[\"{{{AIAb{CGdBADd}}ABBd}{{AIAb{CGdBADd}}}}\",[]]Bo[\"{{{AIAb{CGdBADh}}ABBd}{{AIAb{CGdBADh}}}}\",[]]Ci[\"{{{AIAb{DA`}}{KGl{ABBd}}}{{KHd{{KGl{ABBd}}AIMd}}}}\",[]]Bj[\"{{{AIAb{BCCd}}AJClKCj}{{KHf{ICl}}}}\",[]]054Bm[\"{{{AIAb{NNj}}}{{KHd{{KGl{BGCl}}IEn}}}}\",[]]Bn[\"{{{AIAb{AJCd}}}{{KHd{{KGl{BGCl}}IEn}}}}\",[]]Bn[\"{{{AIAb{NNl}}}{{KHd{{KGl{BGCl}}BCCb}}}}\",[]]Bo[\"{{{AIAb{AJCf}}}{{KHd{{KGl{BGCl}}BCCb}}}}\",[]]Aj[\"{{{AIAb{AHFl}}}CNb}\",[]]Aj[\"{{{AIAb{AHFn}}}CNb}\",[]]Bl[\"{{{AIAb{ABBh}}{KGl{{A@d{BFEh}}}}}ICl}\",[]]Cc[\"{{{AIAb{CGdAINd}}{KGl{{AIAb{BFEj}}}}NLd}ICl}\",[]]Aj[\"{{{AIAb{ABBj}}}CNb}\",[]]Bf[\"{{{AIAb{AHEf}}{AIAb{AHEf}}}DC`}\",[]]Bn[\"{{{AIAb{CGdBBCl}}AG`}{{AIAb{CGdBBCl}}}}\",[]]Bn[\"{{{AIAb{CGdBBCn}}AG`}{{AIAb{CGdBBCn}}}}\",[]]Ak[\"{{{AIAb{AHHl}}}BAAj}\",[]]0Ak[\"{{{AIAb{AINb}}}BGBn}\",[]]Cb[\"{{{AIAb{AINd}}KH`}{{KHd{{KGl{AJBh}}AIMj}}}}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]Bn[\"{{{AIAb{CGdBDAn}}KJ`}{{AIAb{CGdBDAn}}}}\",[]]Bn[\"{{{AIAb{CGdBDB`}}KJb}{{AIAb{CGdBDB`}}}}\",[]]A`[\"{AHD`KGl}\",[]]Bo[\"{{{AIAb{AHD`}}{AIAb{CMl}}}{{KHf{BDAl}}}}\",[]]Em[\"{{{AIAb{AINb}}{AIAb{CGd{KAb{Oj}}}}{AIAb{{KAb{Oj}}}}{AIAb{{KAb{Oj}}}}}{{KHd{IClBHO`}}}}\",[]]El[\"{{{AIAb{NNj}}{AIAb{CGd{KAb{Oj}}}}{AIAb{{KAb{Oj}}}}{AIAb{{KAb{Oj}}}}}{{KHd{IClBHO`}}}}\",[]]Em[\"{{{AIAb{AJCd}}{AIAb{CGd{KAb{Oj}}}}{AIAb{{KAb{Oj}}}}{AIAb{{KAb{Oj}}}}}{{KHd{IClBHO`}}}}\",[]]El[\"{{{AIAb{NNl}}{AIAb{CGd{KAb{Oj}}}}{AIAb{{KAb{Oj}}}}{AIAb{{KAb{Oj}}}}}{{KHd{IClBHOb}}}}\",[]]Em[\"{{{AIAb{AJCf}}{AIAb{CGd{KAb{Oj}}}}{AIAb{{KAb{Oj}}}}{AIAb{{KAb{Oj}}}}}{{KHd{IClBHOb}}}}\",[]]Am[\"{{{AIAb{CGdAHDd}}}ICl}\",[]]0Am[\"{{{AIAb{AINd}}A@n}CNb}\",[]]Ak[\"{{{AIAb{AINb}}}BGK`}\",[]]Bg[\"{{{AIAb{DA`}}}{{KHd{BEHfAIMd}}}}\",[]]>=E`[\"{{{AIAb{BHGd}}{AIAb{{KAb{Oj}}}}{AIAb{BBNb}}{AIAb{BHAl}}}{{KHd{BHIjIEj}}}}\",[]]0Bg[\"{{{A@d{BBEb}}}{{KHd{B@MhBGKl}}}}\",[]]Bg[\"{{{A@d{BBEb}}}{{KHd{B@MlBGKl}}}}\",[]]Bn[\"{{{AIAb{CGdBFIf}}KJ`}{{AIAb{CGdBFIf}}}}\",[]]Bn[\"{{{AIAb{CGdBFIh}}KJb}{{AIAb{CGdBFIh}}}}\",[]]o[\"{NK`NK`}\",[]]0000Aj[\"{{{AIAb{AINd}}}DC`}\",[]]0Bd[\"{{{AIAb{CGdAINf}}CNbABBd}ICl}\",[]]Bc[\"{{{AIAb{CGdCNj}}CNbABBd}ICl}\",[]]Bd[\"{{{AIAb{CGdAINh}}CNbABBd}ICl}\",[]]Bc[\"{{{AIAb{CGdCNl}}CNbABBd}ICl}\",[]]Ci[\"{{{AIAb{DA`}}{KGl{ABBd}}}{{KHd{{KGl{ABBd}}AIMd}}}}\",[]]Cd[\"{{{AIAb{CGdAAl}}{AIAb{AILb}}}{{KHd{IClIF`}}}}\",[]]Cd[\"{{{AIAb{CGdA@j}}{AIAb{AILb}}}{{KHd{IClIF`}}}}\",[]]Ce[\"{{{AIAb{CGdABAf}}{AIAb{AILb}}}{{KHd{IClIF`}}}}\",[]]Ce[\"{{{AIAb{CGdAHD`}}{AIAb{AILb}}}{{KHd{IClIF`}}}}\",[]]Cd[\"{{{AIAb{CGdAAn}}{AIAb{AILb}}}{{KHd{IClIF`}}}}\",[]]Cd[\"{{{AIAb{CGdAB`}}{AIAb{AILb}}}{{KHd{IClIF`}}}}\",[]]Bo[\"{{{AIAb{CGdBADd}}BHBj}{{AIAb{CGdBADd}}}}\",[]]Bo[\"{{{AIAb{CGdBADh}}BHBl}{{AIAb{CGdBADh}}}}\",[]]Aj[\"{{{AIAb{BBOh}}}CNb}\",[]]Aj[\"{{{AIAb{BBOj}}}CNb}\",[]]Cd[\"{{{AIAb{CGdB@Mh}}{A@d{A`}}}{{AIAb{CGdB@Mh}}}}\",[]]Cd[\"{{{AIAb{CGdB@Ml}}{A@d{A`}}}{{AIAb{CGdB@Ml}}}}\",[]]Cl[\"{{BBOh{KGl{BADd}}{A@f{BBN`}}{A@d{NNh}}}{{KHf{ABBh}}}}\",[]]Aj[\"{{{AIAb{ABC`}}}DC`}\",[]]Aj[\"{{{AIAb{ABCb}}}DC`}\",[]]Ca[\"{{{AIAb{CGdAINd}}NLdAJ@`}{{KHd{IClBHFj}}}}\",[]]Bd[\"{{{AIAb{BHGd}}}{{ABj{BDAd}}}}\",[]]Bj[\"{{{AIAb{AINb}}AJBh}{{KHd{IClIFj}}}}\",[]]Bm[\"{{{AIAb{CGdAINd}}AJBh}{{KHd{IClIFj}}}}\",[]]Bn[\"{{{AIAb{CGdBDAn}}KCj}{{AIAb{CGdBDAn}}}}\",[]]Bn[\"{{{AIAb{CGdBDB`}}KCj}{{AIAb{CGdBDB`}}}}\",[]]Bm[\"{{}{{KHd{{KCh{BBNbBFL`}}{A@f{IEf}}}}}}\",[]]Ai[\"{AIN`{{KHh{ICl}}}}\",[]]A`[\"{AINnCNb}\",[]]A`[\"{AIO`CNb}\",[]]An[\"{{{AIAb{ABBh}}B@Mh}ICl}\",[]]Dc[\"{{{AIAb{CGdAINd}}NLdKH`{KGl{ABBd}}}{{KHd{{KGl{ABBd}}AIMj}}}}\",[]]o[\"{{}AIAb}\",[]]Aj[\"{{{AIAb{AINb}}}KCj}\",[]]Bn[\"{{{AIAb{CGdBADd}}CNb}{{AIAb{CGdBADd}}}}\",[]]Bn[\"{{{AIAb{CGdBADh}}CNb}{{AIAb{CGdBADh}}}}\",[]]Bn[\"{{{AIAb{CGdBDAn}}KJ`}{{AIAb{CGdBDAn}}}}\",[]]Bn[\"{{{AIAb{CGdBDB`}}KJb}{{AIAb{CGdBDB`}}}}\",[]]:Bc[\"{{{AIAb{CGdAINd}}A@nKJb}ICl}\",[]]A`[\"{AHGnCNb}\",[]]A`[\"{AHH`CNb}\",[]]Ak[\"{{}{{KHd{B@MhIFd}}}}\",[]]Ak[\"{{}{{KHd{B@MlIFf}}}}\",[]]Bn[\"{{{AIAb{CGdBDAn}}DC`}{{AIAb{CGdBDAn}}}}\",[]]Bn[\"{{{AIAb{CGdBDB`}}DC`}{{AIAb{CGdBDB`}}}}\",[]]87Cg[\"{{{AIAb{CGdAAl}}{AIAb{CGdAILb}}}{{KHd{IClIF`}}}}\",[]]Cg[\"{{{AIAb{CGdA@j}}{AIAb{CGdAILb}}}{{KHd{IClIF`}}}}\",[]]Ch[\"{{{AIAb{CGdABAf}}{AIAb{CGdAILb}}}{{KHd{IClIF`}}}}\",[]]Ch[\"{{{AIAb{CGdAHD`}}{AIAb{CGdAILb}}}{{KHd{IClIF`}}}}\",[]]Cg[\"{{{AIAb{CGdAAn}}{AIAb{CGdAILb}}}{{KHd{IClIF`}}}}\",[]]Cg[\"{{{AIAb{CGdAB`}}{AIAb{CGdAILb}}}{{KHd{IClIF`}}}}\",[]]Bi[\"{{{AIAb{CGdAHEf}}{AIAb{AHEf}}}DC`}\",[]]n[\"{{}ICl}\",[]]Ce[\"{{{AIAb{CGdBDAn}}{KGl{KCj}}}{{AIAb{CGdBDAn}}}}\",[]]Ce[\"{{{AIAb{CGdBDB`}}{KGl{KCj}}}{{AIAb{CGdBDB`}}}}\",[]]Bn[\"{{{AIAb{AINb}}}{{KHd{{ABj{AJBh}}IFj}}}}\",[]]Cd[\"{{{AIAb{CGdAINd}}NLd}{{KHd{{ABj{AJBh}}IFj}}}}\",[]]Am[\"{{{AIAb{CGdAINf}}}ICl}\",[]]Al[\"{{{AIAb{CGdCNj}}}ICl}\",[]]Al[\"{{{AIAb{CGdIDj}}}ICl}\",[]]Am[\"{{{AIAb{CGdAINh}}}ICl}\",[]]Al[\"{{{AIAb{CGdCNl}}}ICl}\",[]]Al[\"{{{AIAb{CGdIDl}}}ICl}\",[]]Ai[\"{AIN`{{KHh{ICl}}}}\",[]]Bj[\"{{{AIAb{AINb}}AJBh}{{KHd{IClIFj}}}}\",[]]Bm[\"{{{AIAb{CGdAINd}}AJBh}{{KHd{IClIFj}}}}\",[]]D`[\"{{{AIAb{CGdAINd}}KH`{KGl{ABBd}}}{{KHd{{KGl{ABBd}}AIMj}}}}\",[]]Cf[\"{{{AIAb{CGdBDAn}}{A@d{BFDh}}}{{AIAb{CGdBDAn}}}}\",[]]Cf[\"{{{AIAb{CGdBDB`}}{A@d{BFDj}}}{{AIAb{CGdBDB`}}}}\",[]]Cf[\"{{{AIAb{CGdBDAn}}{KGl{ABBd}}}{{AIAb{CGdBDAn}}}}\",[]]Cf[\"{{{AIAb{CGdBDB`}}{KGl{ABBd}}}{{AIAb{CGdBDB`}}}}\",[]]Ce[\"{{{AIAb{CGdBDAn}}{KGl{CNb}}}{{AIAb{CGdBDAn}}}}\",[]]Ce[\"{{{AIAb{CGdBDB`}}{KGl{CNb}}}{{AIAb{CGdBDB`}}}}\",[]]Aa[\"{BBAjAIOn}\",[]]Aa[\"{BBAlAIOn}\",[]];Bn[\"{{{AIAb{CGdBDAn}}DC`}{{AIAb{CGdBDAn}}}}\",[]]Bn[\"{{{AIAb{CGdBDB`}}DC`}{{AIAb{CGdBDB`}}}}\",[]]Am[\"{{{AIAb{AINb}}KJ`}ICl}\",[]]o[\"{NK`NK`}\",[]]Bn[\"{{{AIAb{CGdBDAn}}CN`}{{AIAb{CGdBDAn}}}}\",[]]Bn[\"{{{AIAb{CGdBDB`}}CN`}{{AIAb{CGdBDB`}}}}\",[]]310;:54Bn[\"{{{AIAb{AINb}}}{{KHd{{ABj{AJBh}}IFj}}}}\",[]]Bn[\"{{{AIAb{AINd}}}{{KHd{{ABj{AJBh}}IFj}}}}\",[]]10A`[\"{BGClKGl}\",[]]Bm[\"{{{AIAb{CGdBDAn}}Oj}{{AIAb{CGdBDAn}}}}\",[]]Bm[\"{{{AIAb{CGdBDB`}}Oj}{{AIAb{CGdBDB`}}}}\",[]]o[\"{{}AJ@`}\",[]]") \ No newline at end of file diff --git a/pr/617/docs/search.index/generic_inverted_index/c475b6aeacd1.js b/pr/617/docs/search.index/generic_inverted_index/c475b6aeacd1.js new file mode 100644 index 000000000..9b5a8de33 --- /dev/null +++ b/pr/617/docs/search.index/generic_inverted_index/c475b6aeacd1.js @@ -0,0 +1 @@ +rb_("QU5gADswAAABAADvAAIAbQPuAGcMAAA7MAAAAQAAbgIoACQCFwA9Ag0ATAIAAE8CAABRAgEAVAICAFoCAQBdAgYAZgIAAGoCAgBuAgoAfAIFAIMCBQCLAgEAjgIBAJECAgCVAgQAmwIKAKcCAwCyAgAAtwIWANMCBADZAhEA7AIDAPECBQD4AgMA/gIGAAYDAwALAwcAFAMEABsDBQAiAwQAKAMEAC4DAgA8AwwASgMNAIQJjQATC+4AJRAAANIQAQA7MAAAAQAA4AEQAJgAAgDVBe4AEgo1AEkKAABMChQAYgoEAGgKHACHCgAAiQoAAIsKFACjCh8AxAoEAMoKKgD2CgAA+AoAAPsKFwA7MAAAAQAAJgELAG0CAADYAgAASQMAAJAHEwDDCgAAyQoAAPcKAAD5CgAArg4HASsQAQBjEAEAOzAAAAEAAIcBBgBiBAAAUAkBAPUKAAD6CgAAKQ18AecRBQA7MAAAAQAABgACAOEGAwDmBgIAOzAAAAEAACkABQBYAxQAnwUAANwGBADqBgwASwkBADswAAABAAAKAAIAfwQAAAIMCQA7MAAAAQAADAABAG4EDAA7MAAAAQAABQACAH0MAwBoEQEAAtQRAADVEQAAAAAAAtEBAADlBgAARGYAAAA7MAAAAQAA7gAOABIKNQBJCgAATAoUAGIKBABoChwAhwoAAIkKAACLChQAowofAMQKBADKCioA9goAAPgKAAD7ChcA") \ No newline at end of file diff --git a/pr/617/docs/search.index/generic_inverted_index/f9c98603db6c.js b/pr/617/docs/search.index/generic_inverted_index/f9c98603db6c.js deleted file mode 100644 index db2d7d2b3..000000000 --- a/pr/617/docs/search.index/generic_inverted_index/f9c98603db6c.js +++ /dev/null @@ -1 +0,0 @@ -rb_("QU5sADswAAABAADvAAIAbQPuAGcMAAA7MAAAAQAAbgIqACQCFwA9Ag0ATAIAAE8CAABRAgEAVAICAFoCAQBdAgYAZgIAAGoCAgBuAgoAfAIFAIMCBQCLAgEAjgIBAJECAgCVAgQAmwILAKgCAgCuAgAAtwIXANQCAwDZAhIA7QIDAPICAwD3AgQA/gIFAAUDBAALAwYAEwMEABkDAAAbAwYAIwMEACkDAwAuAwIANQMAAD0DCwBKAw0AhAmNABML7gAlEAAA0hABADswAAABAADgAREAmAACANUF7gASCjUASQoAAEwKFABiCgUAaQobAIcKAQCLChMAoAoAAKMKHwDECgQAygoqAPYKAAD4CgAA+goAAPwKFgA7MAAAAQAAJgELAG0CAADYAgAASQMAAJAHEwDDCgAAyQoAAPUKAAD7CgAArg4HASsQAQBjEAEAOzAAAAEAAIcBBgBiBAAAUAkBAPcKAAD5CgAAKQ18AegRBQA7MAAAAQAABgACAOEGAwDmBgIAOzAAAAEAACkABQBYAxQAnwUAANwGBADqBgwASwkBADswAAABAAAKAAIAfwQAAAIMCQA7MAAAAQAADAABAG4EDAA7MAAAAQAABQACAH0MAwBoEQEAAtQRAADVEQAAAAAAAtEBAADlBgAARGoAAAA7MAAAAQAA7gAPABIKNQBJCgAATAoUAGIKBQBpChsAhwoBAIsKEwCgCgAAowofAMQKBADKCioA9goAAPgKAAD6CgAA/AoWAA==") \ No newline at end of file diff --git a/pr/617/docs/search.index/name/56eac45ecfe0.js b/pr/617/docs/search.index/name/d0f424d3d1a6.js similarity index 59% rename from pr/617/docs/search.index/name/56eac45ecfe0.js rename to pr/617/docs/search.index/name/d0f424d3d1a6.js index 72f808314..6db033bf7 100644 --- a/pr/617/docs/search.index/name/56eac45ecfe0.js +++ b/pr/617/docs/search.index/name/d0f424d3d1a6.js @@ -1 +1 @@ -rd_("b()b->bBi0bCe00bFnbIdbOkb[]beq0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bid000000000000000000brtbtybu8cAck0cAddcAnycArccBoxcBufcClicDir0cErrcMAX000cNotcOpt00cOrdcPincUni0cVeccadd00000ccmp0000000000000000000cdir000cecn000cf32cf64cfmt00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cget0ci32cimpcios0clen000clog0cmincmutcnew0000000000000000000000000000000000cnoqcnot0cnow000000crtt000crun0cstrcu16cu32cu64cudpdArgsdAsFddBbr300dDropdEct000dEct100dFromdHashdIntodKeys0dLong0dNonedPath00dPing0dPolldReaddSide0dZERO0dacks0daddr00dbbr3dbooldbulkdcodedcwnd0ddataddrop000000dfrom0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dhash00000000000000000000dinto0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dmain0000dnext0dopen00dpath0dperfdping000dpoll00000000000000dqlogdread00drecv00dseal0dsend00dsent0dside0dsign0dsize00dsmoldspindstop00dtake00000du128duniteAsRefeByteseChunk0eCloneeCloseeCubic00eDebugeDerefeError00000000000eEvent00eKnowneOther0eRangeeReset000eRetry0eShort0eSizedeStats0eSuiteeTokioeWriteealpns0earrayebenchebuild00000000ebytes00000ecauseeclone000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000eclose000ecubicederef0eerroreframe0eindex0eis_ce0elocal0eparse0epaths0eprint00ereset00eretry0esliceespawn00estats0000000000etitle0etokenetokioetupleeusizeewrite00fAcceptfAckEcn0fAes128fAes256fBackup0fBorrowfBufMutfChunksfClient00fClosed0fCrypto0fCursorfFuturefHasherfIpAddrfLaggedfOpenBifOpened00000fOptionfPathId0fResult00000fServer00fStream000fStringfTypeIdfVarInt0faccept000fborrow0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fchunks0fcipherfclient0000fclosedfcoding0fconfig0fcrypto0000000000fdecode000000000000fdst_ipfencode000000000000ffinish00fhandlefheader0fignore0finsert00000fkeylogfoffset0fon_ack0000000fpacket00frandomfreason00000000frebindfrefuse0fremote00frustls00000fserver0000fsharedfsource00000000fsrc_ip00fstatus00fstream0fstridefudp_rx000fudp_tx000fvarintfverify0fwindow0000000gBlocked00gCommandgContextgDefaultgDisplaygFromStrgGeneral0gHmacKey0gInitialgInstantgKeyPair0gLayeredgMaxData0gNewReno00gOpenUnigPadding0gPathAck0gPathBufgReadBufgRequestgRuntime0gSession0gStopped00000gStreamsgToOwnedgTooLonggTryFromgTryIntogUnknowngWritten0gZeroRttgcid_len000gclientsgcommand00000gcompare0000000000000000000gconnect0gdecrypt00000gdefault00000000000000000000000000000000000000000000000000000gdrainedgdst_cid0000000gencrypt000gis_0rtt0gmetrics0000000gmin_mtu0gnoq_udpgon_sent0gopen_bigpath_idgpointergruntimegsrc_cid0000gstopped0gstreams000gtag_len0gtype_id0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000gupgrade0gversion00000hAcceptBihBTreeMaphBytesMuthChacha20hCommandshDatagram00hDerefMuthDisabled0hDurationhEndpoint00hFinished0hIncoming0hIteratorhLayerExthLongTypehMAX_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_owned000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000htry_from0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000htry_into0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000htry_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_SIZEjBbr3Config0jClosedPath00000jComparablejConnectingjConnection00jController0jEquivalent0jFrameStats0jIntoFuturejInvalidCid0jIoSliceMutjMaybeFrame0jNamedGroupjNonZeroU32jOtherErrorjPartialOrdjPathAckEcn0jPathEventsjPathStatus0jQlogConfig0jRecvStream00jResetErrorjRetryError0jSendStream00jServerNamejSocketAddrjStreamInfojSubcommandjSystemTimejTimeSource0jTlsSession0jTokenStore0jUdpSockRefjWriteError0jaccept_unijadd_assign00000jallow_spin0jborrow_mut0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000jclone_into000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000jclose_pathjcongestion000jconn_statsjconnection0jequivalent00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000jerror_code000000jframe_type0jinto_inner0jis_drained0jlocal_addr0jlost_bytes000jnew_senderjpad_to_mtu0jpath_stats0000jpoll_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_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_chunks0mAlertReceived0mBloomTokenLog0mCidsExhausted000mCloneToUninitmDatagramEventmEndpointEventmEndpointStatsmFinishedEarlymHandshakeData0mHandshakeDone0mHandshakeTypemINVALID_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_address0000nremove_address0nsaturating_add000nsaturating_sub0nthroughput_bpsntime_threshold0ntotal_durationnvalue_variantsoConnectionClose000oConnectionError0oConnectionEvent0oConnectionStats0oGetRandomFailedoNewConnectionId0oOpenStreamStatsoPathCidsBlocked0oProtectedHeader0oQlogFileFactory0oRemoteAbandoned0oSignatureSchemeoSystemTimeErroroTokenReuseError0oTransportConfig00oTryFromIntErroroVersionMismatch0oZeroRttAcceptedoZeroRttRejected000obloom_token_logoclone_to_uninit000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000odefault_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`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_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_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_addressesBfset_max_remote_nat_traversal_addresses0BoDEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED") \ No newline at end of file +rd_("b()b->bBi0bCe00bFnbIdbOkb[]beq0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bid000000000000000000brtbtybu8cAck0cAddcAnycArccBoxcBufcClicDir0cErrcMAX000cNotcOpt00cOrdcPincUni0cVeccadd00000ccmp0000000000000000000cdir000cecn000cf32cf64cfmt00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cget0ci32cimpcios0clen000clog0cmincmutcnew0000000000000000000000000000000000cnoqcnot0cnow000000crtt000crun0cstrcu16cu32cu64cudpdArgsdAsFddBbr300dDropdEct000dEct100dFromdHashdIntodKeys0dLong0dNonedPath00dPing0dPolldReaddSide0dZERO0dacks0daddr00dbbr3dbooldbulkdcodedcwnd0ddataddrop000000dfrom0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dhash00000000000000000000dinto0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dmain0000dnext0dopen00dpath0dperfdping000dpoll00000000000000dqlogdread00drecv00dseal0dsend00dsent0dside0dsign0dsize00dsmoldspindstop00dtake00000du128duniteAsRefeByteseChunk0eCloneeCloseeCubic00eDebugeDerefeError00000000000eEvent00eKnowneOther0eRangeeReset000eRetry0eShort0eSizedeStats0eSuiteeTokioeWriteealpns0earrayebenchebuild00000000ebytes00000ecauseeclone000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000eclose000ecubicederef0eerroreframe0eindex0eis_ce0elocal0eparse0epaths0eprint00ereset00eretry0esliceespawn00estats0000000000etitle0etokenetokioetupleeusizeewrite00fAcceptfAckEcn0fAes128fAes256fBackup0fBorrowfBufMutfChunksfClient00fClosed0fCrypto0fCursorfFuturefHasherfIpAddrfLaggedfOpenBifOpened00000fOptionfPathId0fResult00000fServer00fStream000fStringfTypeIdfVarInt0faccept000fborrow0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fchunks0fcipherfclient0000fclosedfcoding0fconfig0fcrypto0000000000fdecode000000000000fdst_ipfencode000000000000ffinish00fhandlefheader0fignore0finsert00000fkeylogfoffset0fon_ack0000000fpacket00frandomfreason00000000frebindfrefuse0fremote00frustls00000fserver0000fsharedfsource00000000fsrc_ip00fstatus00fstream0fstridefudp_rx000fudp_tx000fvarintfverify0fwindow0000000gBlocked00gCommandgContextgDefaultgDisplaygFromStrgGeneral0gHmacKey0gInitialgInstantgKeyPair0gLayeredgMaxData0gNewReno00gOpenUnigPadding0gPathAck0gPathBufgReadBufgRequestgRuntime0gSession0gStopped00000gStreamsgToOwnedgTooLonggTryFromgTryIntogUnknowngWritten0gZeroRttgcid_len000gclientsgcommand00000gcompare0000000000000000000gconnect0gdecrypt00000gdefault00000000000000000000000000000000000000000000000000000gdrainedgdst_cid0000000gencrypt000gis_0rtt0gmetrics0000000gmin_mtu0gnoq_udpgon_sent0gopen_bigpath_idgpointergruntimegsrc_cid0000gstopped0gstreams000gtag_len0gtype_id0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000gupgrade0gversion00000hAcceptBihBTreeMaphBytesMuthChacha20hCommandshDatagram00hDerefMuthDisabled0hDurationhEndpoint00hFinished0hIncoming0hIteratorhLayerExthLongTypehMAX_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_owned000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000htry_from0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000htry_into0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000htry_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_SIZEjBbr3Config0jClosedPath00000jComparablejConnectingjConnection00jController0jEquivalent0jFrameStats0jIntoFuturejInvalidCid0jIoSliceMutjMaybeFrame0jNamedGroupjNonZeroU32jOtherErrorjPartialOrdjPathAckEcn0jPathEventsjPathStatus0jQlogConfig0jRecvStream00jResetErrorjRetryError0jSendStream00jServerNamejSocketAddrjStreamInfojSubcommandjSystemTimejTimeSource0jTlsSession0jTokenStore0jUdpSockRefjWriteError0jaccept_unijadd_assign00000jallow_spin0jborrow_mut0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000jclone_into000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000jclose_pathjcongestion000jconn_statsjconnection0jequivalent00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000jerror_code000000jframe_type0jinto_inner0jis_drained0jlocal_addr0jlost_bytes000jnew_senderjpad_to_mtu0jpath_stats0000jpoll_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_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_chunks0mAlertReceived0mBloomTokenLog0mCidsExhausted000mCloneToUninitmDatagramEventmEndpointEventmEndpointStatsmFinishedEarlymHandshakeData0mHandshakeDone0mHandshakeTypemINVALID_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_uninit000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000odefault_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`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_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_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/617/docs/search.index/normalizedName/6387bfcfcd90.js b/pr/617/docs/search.index/normalizedName/aae5a537ba3b.js similarity index 60% rename from pr/617/docs/search.index/normalizedName/6387bfcfcd90.js rename to pr/617/docs/search.index/normalizedName/aae5a537ba3b.js index 747f1d70a..f559742ad 100644 --- a/pr/617/docs/search.index/normalizedName/6387bfcfcd90.js +++ b/pr/617/docs/search.index/normalizedName/aae5a537ba3b.js @@ -1 +1 @@ -rd_("b()b->bbi0bce00bfnbidbokb[]beq00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003333333333333333333brtbtybu8cack0caddcanycarccboxcbufcclicdir0cerrcmax000cnotcopt00cordcpincuni0cvec>>>>>>ccmp00000000000000000009999cecn000cf32cf64cfmt00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cget0ci32cimpcios0clen000clog0cmincmutcnew0000000000000000000000000000000000cnoqcnot0cnow000000crtt000crun0cstrcu16cu32cu64cudpdargsdasfddbbr300ddropdect000dect100dfromdhashdintodkeys0dlong0dnonedpath00dping0dpolldreaddside0dzero0dacks0daddr00dbbr3dbooldbulkdcodedcwnd0ddataddrop000000dfrom0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dhash00000000000000000000dinto0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dmain0000dnext0dopen00dpath0dperfdping000dpoll00000000000000dqlogdread00drecv00dseal0dsend00dsent0dside0dsign0dsize00dsmoldspindstop00dtake00000du128duniteasrefebytesechunk0ecloneecloseecubic00edebugederefeerror00000000000eevent00eknowneother0erangeereset000eretry0eshort0esizedestats0esuiteetokioewriteealpns0earrayebenchebuild00000000ebytes00000ecauseeclone000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000eclose000ecubicederef0eerroreframe0eindex0disce0elocal0eparse0epaths0eprint00ereset00eretry0esliceespawn00estats0000000000etitle0etokenetokioetupleeusizeewrite00facceptfackecn0faes128faes256fbackup0fborrowfbufmutfchunksfclient00fclosed0fcrypto0fcursorffuturefhasherfipaddrflaggedfopenbifopened00000foptionfpathid0fresult00000fserver00fstream000fstringftypeidfvarint0faccept000fborrow0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fchunks0fcipherfclient0000fclosedfcoding0fconfig0fcrypto0000000000fdecode000000000000edstipfencode000000000000ffinish00fhandlefheader0fignore0finsert00000fkeylogfoffset0eonack0000000fpacket00frandomfreason00000000frebindfrefuse0fremote00frustls00000fserver0000fsharedfsource00000000esrcip00fstatus00fstream0fstrideeudprx000eudptx000fvarintfverify0fwindow0000000gblocked00gcommandgcontextgdefaultgdisplaygfromstrggeneral0ghmackey0ginitialginstantgkeypair0glayeredgmaxdata0gnewreno00gopenunigpadding0gpathack0gpathbufgreadbufgrequestgruntime0gsession0gstopped00000gstreamsgtoownedgtoolonggtryfromgtryintogunknowngwritten0gzerorttfcidlen000gclientsgcommand00000gcompare0000000000000000000gconnect0gdecrypt00000gdefault00000000000000000000000000000000000000000000000000000gdrainedfdstcid0000000gencrypt000fis0rtt0gmetrics0000000fminmtu0fnoqudpfonsent0fopenbifpathidgpointergruntimefsrccid0000gstopped0gstreams000ftaglen0ftypeid0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000gupgrade0gversion00000hacceptbihbtreemaphbytesmuthchacha20hcommandshdatagram00hderefmuthdisabled0hdurationhendpoint00hfinished0hincoming0hiteratorhlayerexthlongtypegmaxsize0gnoerror0hnewtoken0honclosedhopenpathhorderinghreadable0hrecvmetahresponsehstreamid0htimedout00htostringhtokenlog0htoolarge0htransmit00hudpstats0hunixtimehwritable0gcongalghcontentshdatagram0hdurationhendpoint0hfinalizegframerx000gframetx000gfrombuf0gfromenv0gfromkeygfromstr0gfromu320gfromu640ggottype000ggroupid0000ghas0rtthincominghinterval0gintoany0000000gisalivehlifetime0glocalip0000gmaxdata0gmaxsizegnewrenognoqperfgonbytesgopenunihpriority0hprotocol0gqlogdirhresponsegrngseed0hssthresh0gtoowned000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000gtryfrom0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000gtryinto0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000gtrypoll000000000gtrysendhvalidate000iabandoned000iacceptuniiaddassigniarbitraryiasyncread0iavailable000iborrowmuticommonopticonnectedicriterionidatagramsidecodableidiscarded000iencodableiformatterifourtupleiframetype0ihandshakeiheaderkey0ihistogramimalformedimaxpathid0imultipathipacketkey0ipartialeqipatherror0ipathevent000ipathstats0ireaderror00itryfutureitrystreamiudpsenderiudpsocketivalueenumhacceptbiiarbitrary0000000hasrustlsiassemblerhclonebox0000000idatagrams000hderefmut0hfrombits00iinitiator0hinto0rtthintoiterhisclient0hisclosedhisserver0hmayretry0imigration0hnewtimer00hnewtoken0hnoqprotohonclosedhopenpath0hpathacks0hpingpathhpollnext000hpollread00hpollrecvhpollsendhreachout0ireferencehresetkey0hretrytag0000hsizehint000hstableidhtostring000000000000000000000000000000000000000000000000000000000000000000htokenkey0htokenpositransport00hwaitidlehwritealljargmatchesjasynctimerjasyncwrite0ibatchsizejbbr3config0jclosedpath00000jcomparablejconnectingjconnection00jcontroller0jequivalent0jframestats0jintofuturejinvalidcid0jioslicemutjmaybeframe0jnamedgroupjnonzerou32jothererrorjpartialordjpathackecn0jpatheventsjpathstatus0jqlogconfig0jrecvstream00jreseterrorjretryerror0jsendstream00jservernamejsocketaddrjstreaminfojsubcommandjsystemtimejtimesource0jtlssession0jtokenstore0judpsockrefjwriteerror0iacceptuniiaddassign00000iallowspin0iborrowmut0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000icloneinto000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000iclosepathjcongestion000iconnstatsjconnection0jequivalent00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ierrorcode000000iframetype0iintoinner0iisdrained0ilocaladdr0ilostbytes000inewsenderhpadtomtu0ipathstats0000ipollcloseipollflush0ipollwrite00iprintjsonireadchunk0ireadexactisetstatusistarttime0jthroughputitotalsizekaccepterrorjclientaddrkciphersuitekclientstatskclosereasonkconfigerror0kcontenttypekcryptoerror0kcubicconfig0kdatablocked0kfinisherrorkidletimeout0kintorequestkoutofbounds0kpathabandon0kqlogfactory0kresetstream0kruntimetypejserveraddrjservernameksmolruntimekstopsending0kstreamevent0kstreamstats0kunspecifiedjacceptwithjaddaddress0jbindsocketkcertificatejcurrentmtu00kdescription0kdestination00jfromremotejinitialmtu000jinitialrtt00jintofuture0000000000000imaxpathid0jmaxstreamsjnamedlayer0ionendacks000jonintervaljpacingrate0jpartialcmp0000000000000000000jpatheventsjpathstatusjreadchunksireadtoendjrecvstream0jsamplesize0jsendbufferjsendstream0jsendwindow00jservername0jtimesource0jtokenstore0juploadsizejupperbound0jweakhandle0jwithcrypto0jwithprefix0jwritechunklackfrequency0laddressaddedlclientconfig00000lclosedstream000000lconnecterror00lconnectionid00ldatagraminfoldecrypterror0lecncodepoint00lencrypterror0lillegalvaluelimmediateack0lintoiteratorklocalhostv4llastopenpath0lnattraversallnonzerousizelnonetokenlog0lobservedaddr000lpathresponse0lpathsblocked0lreaddatagramlremotestatus000lrttestimator0lsenddatagramlserverconfig00000lsocketaddrv4lsocketaddrv6lstoppederrorltokioruntimelunstructuredkaugmentargs0000kcidlifetime000kclosereasonkconnectwithlconservativekdatablocked0kdrainstreamkearlycrypto000keventstreamkexpecttypes000kgeneratecid000kgrosegmentskhandleevent0kinitialkeys00000000kintorequest0klostpackets000kmaxincoming0kmayfragment00knetworkpathknewreceiverlnoprotection0jorigdstcid0kpathabandon0kpolltimeoutkqlogfactory0kresetstream0kruntimetypeksegmentsize00ksendquantum0ksendstreamsksetlifetime0ksetpriority0kstopsending0kstreamstatskuploadstatskwithinitial000kwritechunks0malertreceived0mbloomtokenlog0mcidsexhausted000mclonetouninitmdatagrameventmendpointeventmendpointstatsmfinishedearlymhandshakedata0mhandshakedone0mhandshaketypelinvalidtoken0mincomingalpns0minvalidheadermlocallyclosed0mmaxstreamdata0mmaxstreamsuni0mnewconnectionmnewrenoconfig0mparseinterrormpartialdecodempathchallenge0mpossiblevaluemprivatekeydermreadableerrormremoveaddress0mrootcertstoremstdsystemtime0munexpectedendlaccepted0rttlackfrequency00lbenchexports0lcidgenerator00lcreatesenderldatatransferldownloadsizeldurationhistlimmediateack0lintoincoming0kmaxackdelay0lnoprotectionlobservedaddr0konfirstbytekonmtuupdate0000000lpathresponse0lpathsblocked0lpeeridentity0000lpollshutdownlpolltransmitkqlogfromenv0lreaddatagramlsenddatagramlsendfairness0lsignaturelen0lstartsession000000000ktrypollnext0naddipv4address0naddipv6address0naddressremovednasyncudpsocketncertificatedernclosepatherror0ncommandfactorynconnectionlost000000nendpointconfig0nfromargmatchesminternalerror0nincomingfutureninvalidframeidninvalidmessage00nmaxstreamsbidi0lnoviablepath0nnonetokenstore0npeermisbehaved00nreachoutatipv40nreachoutatipv60nreadexacterrornreadtoenderrornshouldtransmitntransferresultntransporterror00nudpsocketstatenweakpathhandlemconnectclientmcryptosessionmdownloadstatsmforconnection000mhandletimeoutmhandshakedata00000mhandshakedone0mhassubcommandminitialwindow0000000000000mintounorderedmishandshaking0000lisvalidretry000mminimumchange0lnext1rttkeys000lonpacketlost000lonpacketsent000mpathchallenge0lqlogfrompath0mreadhandshake000mreadunorderedmreceivewindow00mreceivedreset00mremoteaddress0000mremoveaddress0msaturatingadd000msaturatingsub0mthroughputbpsmtimethreshold0mtotaldurationmvaluevariantsoconnectionclose000oconnectionerror0oconnectionevent0oconnectionstats0ogetrandomfailedonewconnectionid0oopenstreamstatsopathcidsblocked0oprotectedheader0oqlogfilefactory0oremoteabandoned0osignatureschemeosystemtimeerrorotokenreuseerror0otransportconfig00otryfrominterroroversionmismatch0ozerorttacceptedozerorttrejected000mbloomtokenlogmclonetouninit000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ndefaultruntimemgreasequicbit0nintegritylimit0ninterfaceindexmmaxstreamdata0mmaxstreamsuni0mparsebytesizenrebindabstractnsendunreliablenserverendpointmsetpathstatusmsetsendwindow0nshouldtransmitnstreamfinishednthroughputhistmwrapudpsocket00nwritehandshake000A`addressdiscoveryA`applicationclose000A`certificateerrorA`connectionclosed0A`connectionhandleA`datagramreceivedA`decryptedinitial0A`endpointstopping0nfinalsizeerror0A`inconsistentkeys00nkeyupdateerror0A`maxpathidreached0A`observedipv4addr0A`observedipv6addr0A`pathstatusbackup0A`peerincompatible00A`quicclientconfig0A`quicserverconfig0A`tokenmemorycache0A`toomanyaddressesA`validationfailed00A`versionnegotiate0ncheckandinsert00000oconfigureclientocongestionstate0oconnectionclose0nforcekeyupdate0nfromargmatches00000nmaxgsosegmentsnmaxidletimeout0nmaxstreamsbidi0nn0nattraversal0oopenconnections0nopenpathensure0opacketthreshold0oreceivedatagramnrecvbuffersize0nsendbuffersize0otransportconfig0000ovalidationtoken0nwithsinglecert0nwriteallchunksA`applicationerror0Aaapplicationclosed000Aacontrollerfactory0Aacontrollermetrics0Aahandshaketokenkey0Aainvalidservername0Aanetworkchangehint0Aapacketdecodeerror0Aapathabandonreason00Aasenddatagramerror0Aastreamdatablocked0Aastreamsblockeduni0Aaunsupportedbypeer0A`congestionevents0A`congestionwindow0omaxdatagramsizeonewconnectionid0opathcidsblocked0osendbufferspaceosetserverconfig0osetupconnectionotopossiblevalueA`aeadlimitreached0Abackfrequencyconfig0Abbadmaxfragmentsize0Aaconnectionrefused0AbconnectionidparserAbdatagramsunblockedA`flowcontrolerror0AbhandshakeconfirmedAbhandshakedatareadyAbillegalorderedreadAbinvalidcertificate0Abmtudiscoveryconfig0AbnotenoughaddressesA`perfciphersuitesAaprotocolviolation0Abprivatepkcs8keyderAbretireconnectionid0A`streamlimiterror0A`streamstateerror0AbservercertverifiedAbservercertverifierAbsetpathstatuserror0Abstreamsblockedbidi0AbtokiocurrentthreadAbtransporterrorcode0Abunsupportedversion00000A`commandforupdate0000A`cryptobuffersize0A`fromu64unchecked0AaignoredhandshakesA`issameconnectionA`minresetinterval0A`newexpecteditems0A`pathstatusbackup0AarefusedhandshakesA`remotepathstatusA`senddatagramwaitA`setalpnprotocols000A`setreceivewindow0Aasupportedversions0A`tokenmemorycacheA`verifyservercertA`withstartinstant0AccongestionalgorithmAcnattraversalupdatesAcpathnewconnectionid0Acpathstatusavailable0Acremotecidsexhausted0ActransportparametersAcunorderedrecvstreamAcunsupportednametype0AcwrongconnectionsideAbacceptedhandshakesAaarbitrarytakerest000AbaugmentsubcommandsAablackholecooldown0Aaearlydataaccepted000AbhandshakeconfirmedAaispathrecoverable0Aakeepaliveinterval0Aalostplpmtudprobes0Aaoncongestionevent0000000AboutgoinghandshakesAaremoteopenstreamsAasendbufferbenches00A`senddataonstreamAasentplpmtudprobes0Aastreamdatablocked0Aastreamsblockeduni0Abframeencodingerror0Adhandshakenotcomplete0Adinappropriatemessage000Adinvalidremoteaddress000Adnoinitialciphersuite0AdobservedexternaladdrAdserversidenotallowed0AdsupportedciphersuiteAdvarintboundsexceeded0AdverifierbuildererrorAdweakconnectionhandleAbackfrequencyconfig0Abblackholesdetected0Aafromargmatchesmut00000AbhandleclientstreamAbincomingbuffersize0AbismultipathenabledAbmaxreceivesegments0Aamaxudppayloadsize00Abmtudiscoveryconfig0AbpollendpointeventsAbpreferredaddressv40Abpreferredaddressv60AareadcertsfromfileAcreorderingthreshold0Abretireconnectionid0Abretrytokenlifetime0AasetmaxidletimeoutAasetrecvbuffersizeAasetsendbuffersizeAbstreamsblockedbidi0Actransportparameters00000Aeconnectionidgenerator0AedigitallysignedstructAenoapplicationprotocol0Aenodefaultclientconfig0Abpathunstableorpoor0Aevalidationtokenconfig0Adconfidentialitylimit0Achandlenetworkchange0AcincomingbufferbytesAbisprobablysamepathAclossreductionfactor0Acmaxtransmitsegments0AcnattraversalupdatesAcpathobservedaddressAcpathstatusavailable0Acstreamreceivewindow00Adcryptobufferexceeded0AfextensionnotnegotiatedAffailedtogetcurrenttime0Affailedtogetrandombytes0Afmultipathnotnegotiated000000AfnattraversalroundendedAfpathretireconnectionid0AfprotectedinitialheaderAfskipserververificationAdbuildtransportconfigAdexportkeyingmaterial0000AcgetintomanysegmentsAcgetloopmanysegmentsAdmaxconcurrentstreamsAdobservedexternaladdr0Acpathnewconnectionid0Adverifytls12signatureAdverifytls13signatureAdwithrootcertificates0AgcertrevocationlisterrorAghandshakesignaturevalidAgnocertificatespresented0Agpeersentoversizedrecord0Aeackelicitingthreshold0Adaugmentargsforupdate0000AehaspendingretransmitsAeismultipathnegotiatedAdonackfrequencyupdate000AdsetkeepaliveintervalAdupdatefromargmatches00000Aevalidationtokenconfig0Afapplicationabandonpath0AhnoprotectionclientconfigAhnoprotectionserverconfigAdgetmaxudppayloadsize0Aeinitialdstcidprovider0AenewwithabstractsocketAfremoteaddressvalidated0AesetmaxconcurrentpathsAfsupportedverifyschemesAfconnectionidlimiterror0AiencryptedclienthelloerrorAiexportkeyingmaterialerror0Aiinvalidcertrevocationlist0Aenocidavailableforpath0Agtransportparametererror0AiunsupportedoperationerrorAfaddnattraversaladdress0Afdatagramsendbuffersize0AfgenerateselfsignedcertAgopenbidirectionalstreamAfpathretireconnectionid0AfsetdefaultclientconfigAesetpathmaxidletimeoutAhdefaultsupportedversionsAjunusableafternetworkchangeAgdatagramsendbufferspaceAgincomingbuffersizetotal0Agmaxconcurrentunistreams0AhopenunidirectionalstreamAgsetmaxconcurrentstreamsAhspuriouscongestionevents0Agtrywithplatformverifier0AkhashedconnectionidgeneratorAkinvalidencryptedclienthello0Ahpathresourcelimitreached0AkrandomconnectionidgeneratorAienablesegmentationoffload0Ahmaxconcurrentbidistreams0Agupdatefromargmatchesmut00000AgupdatelocalifsameremoteAjconfiguretracingsubscriberAidatagramreceivebuffersize0Aiinitiatenattraversalround0Aionspuriouscongestionevent00000AjreceivebidirectionalstreamAiremovenattraversaladdress0AhsetpathkeepaliveintervalAmfixedlengthconnectionidparserAminappropriatehandshakemessage000Akcongestioncontrollerfactory0Aidefaultpathmaxidletimeout0Aimaxoutgoingbytespersecond0Ajnegotiatedkeyexchangegroup0AkreceiveunidirectionalstreamAjsendobservedaddressreports0AisetmaxconcurrentbistreamsAkaugmentsubcommandsforupdateAkmaxconcurrentmultipathpaths0AjsetmaxconcurrentunistreamsAmpersistentcongestionthreshold0Aldefaultpathkeepaliveinterval0Amreceiveobservedaddressreports0Amgetlocalnattraversaladdresses0Angetremotenattraversaladdresses0AnmaxremotenattraversaladdressesBasetmaxremotenattraversaladdresses0Bjdefaultconcurrentmultipathpathswhenenabled") \ No newline at end of file +rd_("b()b->bbi0bce00bfnbidbokb[]beq00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003333333333333333333brtbtybu8cack0caddcanycarccboxcbufcclicdir0cerrcmax000cnotcopt00cordcpincuni0cvec>>>>>>ccmp00000000000000000009999cecn000cf32cf64cfmt00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cget0ci32cimpcios0clen000clog0cmincmutcnew0000000000000000000000000000000000cnoqcnot0cnow000000crtt000crun0cstrcu16cu32cu64cudpdargsdasfddbbr300ddropdect000dect100dfromdhashdintodkeys0dlong0dnonedpath00dping0dpolldreaddside0dzero0dacks0daddr00dbbr3dbooldbulkdcodedcwnd0ddataddrop000000dfrom0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dhash00000000000000000000dinto0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dmain0000dnext0dopen00dpath0dperfdping000dpoll00000000000000dqlogdread00drecv00dseal0dsend00dsent0dside0dsign0dsize00dsmoldspindstop00dtake00000du128duniteasrefebytesechunk0ecloneecloseecubic00edebugederefeerror00000000000eevent00eknowneother0erangeereset000eretry0eshort0esizedestats0esuiteetokioewriteealpns0earrayebenchebuild00000000ebytes00000ecauseeclone000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000eclose000ecubicederef0eerroreframe0eindex0disce0elocal0eparse0epaths0eprint00ereset00eretry0esliceespawn00estats0000000000etitle0etokenetokioetupleeusizeewrite00facceptfackecn0faes128faes256fbackup0fborrowfbufmutfchunksfclient00fclosed0fcrypto0fcursorffuturefhasherfipaddrflaggedfopenbifopened00000foptionfpathid0fresult00000fserver00fstream000fstringftypeidfvarint0faccept000fborrow0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fchunks0fcipherfclient0000fclosedfcoding0fconfig0fcrypto0000000000fdecode000000000000edstipfencode000000000000ffinish00fhandlefheader0fignore0finsert00000fkeylogfoffset0eonack0000000fpacket00frandomfreason00000000frebindfrefuse0fremote00frustls00000fserver0000fsharedfsource00000000esrcip00fstatus00fstream0fstrideeudprx000eudptx000fvarintfverify0fwindow0000000gblocked00gcommandgcontextgdefaultgdisplaygfromstrggeneral0ghmackey0ginitialginstantgkeypair0glayeredgmaxdata0gnewreno00gopenunigpadding0gpathack0gpathbufgreadbufgrequestgruntime0gsession0gstopped00000gstreamsgtoownedgtoolonggtryfromgtryintogunknowngwritten0gzerorttfcidlen000gclientsgcommand00000gcompare0000000000000000000gconnect0gdecrypt00000gdefault00000000000000000000000000000000000000000000000000000gdrainedfdstcid0000000gencrypt000fis0rtt0gmetrics0000000fminmtu0fnoqudpfonsent0fopenbifpathidgpointergruntimefsrccid0000gstopped0gstreams000ftaglen0ftypeid0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000gupgrade0gversion00000hacceptbihbtreemaphbytesmuthchacha20hcommandshdatagram00hderefmuthdisabled0hdurationhendpoint00hfinished0hincoming0hiteratorhlayerexthlongtypegmaxsize0gnoerror0hnewtoken0honclosedhopenpathhorderinghreadable0hrecvmetahresponsehstreamid0htimedout00htostringhtokenlog0htoolarge0htransmit00hudpstats0hunixtimehwritable0gcongalghcontentshdatagram0hdurationhendpoint0hfinalizegframerx000gframetx000gfrombuf0gfromenv0gfromkeygfromstr0gfromu320gfromu640ggottype000ggroupid0000ghas0rtthincominghinterval0gintoany0000000gisalivehlifetime0glocalip0000gmaxdata0gmaxsizegnewrenognoqperfgonbytesgopenunihpriority0hprotocol0gqlogdirhresponsegrngseed0hssthresh0gtoowned000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000gtryfrom0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000gtryinto0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000gtrypoll000000000gtrysendhvalidate000iabandoned000iacceptuniiaddassigniarbitraryiasyncread0iavailable000iborrowmuticommonopticonnectedicriterionidatagramsidecodableidiscarded000iencodableiformatterifourtupleiframetype0ihandshakeiheaderkey0ihistogramimalformedimaxpathid0imultipathipacketkey0ipartialeqipatherror0ipathevent000ipathstats0ireaderror00itryfutureitrystreamiudpsenderiudpsocketivalueenumhacceptbiiarbitrary0000000hasrustlsiassemblerhclonebox0000000idatagrams000hderefmut0hfrombits00iinitiator0hinto0rtthintoiterhisclient0hisclosedhisserver0hmayretry0imigration0hnewtimer00hnewtoken0hnoqprotohonclosedhopenpath0hpathacks0hpingpathhpollnext000hpollread00hpollrecvhpollsendhreachout0ireferencehresetkey0hretrytag0000hsizehint000hstableidhtostring000000000000000000000000000000000000000000000000000000000000000000htokenkey0htokenpositransport00hwaitidlehwritealljargmatchesjasynctimerjasyncwrite0ibatchsizejbbr3config0jclosedpath00000jcomparablejconnectingjconnection00jcontroller0jequivalent0jframestats0jintofuturejinvalidcid0jioslicemutjmaybeframe0jnamedgroupjnonzerou32jothererrorjpartialordjpathackecn0jpatheventsjpathstatus0jqlogconfig0jrecvstream00jreseterrorjretryerror0jsendstream00jservernamejsocketaddrjstreaminfojsubcommandjsystemtimejtimesource0jtlssession0jtokenstore0judpsockrefjwriteerror0iacceptuniiaddassign00000iallowspin0iborrowmut0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000icloneinto000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000iclosepathjcongestion000iconnstatsjconnection0jequivalent00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ierrorcode000000iframetype0iintoinner0iisdrained0ilocaladdr0ilostbytes000inewsenderhpadtomtu0ipathstats0000ipollcloseipollflush0ipollwrite00iprintjsonireadchunk0ireadexactisetstatusistarttime0jthroughputitotalsizekaccepterrorjclientaddrkciphersuitekclientstatskclosereasonkconfigerror0kcontenttypekcryptoerror0kcubicconfig0kdatablocked0kfinisherrorkidletimeout0kintorequestkoutofbounds0kpathabandon0kqlogfactory0kresetstream0kruntimetypejserveraddrjservernameksmolruntimekstopsending0kstreamevent0kstreamstats0kunspecifiedjacceptwithjaddaddress0jbindsocketkcertificatejcurrentmtu00kdescription0kdestination00jfromremotejinitialmtu000jinitialrtt00jintofuture0000000000000imaxpathid0jmaxstreamsjnamedlayer0ionendacks000jonintervaljpacingrate0jpartialcmp0000000000000000000jpatheventsjpathstatusjreadchunksireadtoendjrecvstream0jsamplesize0jsendbufferjsendstream0jsendwindow00jservername0jtimesource0jtokenstore0juploadsizejupperbound0jweakhandle0jwithcrypto0jwithprefix0jwritechunklackfrequency0laddressaddedlclientconfig00000lclosedstream000000lconnecterror00lconnectionid00ldatagraminfoldecrypterror0lecncodepoint00lencrypterror0lillegalvaluelimmediateack0lintoiteratorklocalhostv4llastopenpath0lnattraversallnonzerousizelnonetokenlog0lobservedaddr000lpathresponse0lpathsblocked0lreaddatagramlremotestatus000lrttestimator0lsenddatagramlserverconfig00000lsocketaddrv4lsocketaddrv6lstoppederrorltokioruntimelunstructuredkaugmentargs0000kcidlifetime000kclosereasonkconnectwithlconservativekdatablocked0kdrainstreamkearlycrypto000keventstreamkexpecttypes000kgeneratecid000kgrosegmentskhandleevent0kinitialkeys00000000kintorequest0klostpackets000kmaxincoming0kmayfragment00knetworkpathknewreceiverlnoprotection0jorigdstcid0kpathabandon0kpolltimeoutkqlogfactory0kresetstream0kruntimetypeksegmentsize00ksendquantum0ksendstreamsksetlifetime0ksetpriority0kstopsending0kstreamstatskuploadstatskwithinitial000kwritechunks0malertreceived0mbloomtokenlog0mcidsexhausted000mclonetouninitmdatagrameventmendpointeventmendpointstatsmfinishedearlymhandshakedata0mhandshakedone0mhandshaketypelinvalidtoken0mincomingalpns0minvalidheadermlocallyclosed0mmaxstreamdata0mmaxstreamsuni0mnewconnectionmnewrenoconfig0mparseinterrormpartialdecodempathchallenge0mpossiblevaluemprivatekeydermreadableerrormremoveaddress0mrootcertstoremstdsystemtime0munexpectedendlaccepted0rttlackfrequency00lbenchexports0lcidgenerator00lcreatesenderldatatransferldownloadsizeldurationhistlimmediateack0lintoincoming0kmaxackdelay0lnoprotectionlobservedaddr0konfirstbytekonmtuupdate0000000lpathresponse0lpathsblocked0lpeeridentity0000lpollshutdownlpolltransmitkqlogfromenv0lreaddatagramlsenddatagramlsendfairness0lsignaturelen0lstartsession000000000ktrypollnext0naddipv4address0naddipv6address0naddressremovednasyncudpsocketncertificatedernclosepatherror0ncommandfactorynconnectionlost000000nendpointconfig0nfromargmatchesminternalerror0nincomingfutureninvalidframeidninvalidmessage00nmaxstreamsbidi0lnoviablepath0nnonetokenstore0npeermisbehaved00nreachoutatipv40nreachoutatipv60nreadexacterrornreadtoenderrornshouldtransmitntransferresultntransporterror00nudpsocketstatenweakpathhandlemconnectclientmcryptosessionmdownloadstatsmforconnection000mhandletimeoutmhandshakedata00000mhandshakedone0mhassubcommandminitialwindow0000000000000mintounorderedmishandshaking0000lisvalidretry000mminimumchange0lnext1rttkeys000lonpacketlost000lonpacketsent000mpathchallenge0lqlogfrompath0mreadhandshake000mreadunorderedmreceivewindow00mreceivedreset00mremoteaddress000mremoveaddress0msaturatingadd000msaturatingsub0mthroughputbpsmtimethreshold0mtotaldurationmvaluevariantsoconnectionclose000oconnectionerror0oconnectionevent0oconnectionstats0ogetrandomfailedonewconnectionid0oopenstreamstatsopathcidsblocked0oprotectedheader0oqlogfilefactory0oremoteabandoned0osignatureschemeosystemtimeerrorotokenreuseerror0otransportconfig00otryfrominterroroversionmismatch0ozerorttacceptedozerorttrejected000mbloomtokenlogmclonetouninit000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ndefaultruntimemgreasequicbit0nintegritylimit0ninterfaceindexmmaxstreamdata0mmaxstreamsuni0mparsebytesizenrebindabstractnsendunreliablenserverendpointmsetpathstatusmsetsendwindow0nshouldtransmitnstreamfinishednthroughputhistmwrapudpsocket00nwritehandshake000A`addressdiscoveryA`applicationclose000A`certificateerrorA`connectionclosed0A`connectionhandleA`datagramreceivedA`decryptedinitial0A`endpointstopping0nfinalsizeerror0A`inconsistentkeys00nkeyupdateerror0A`maxpathidreached0A`observedipv4addr0A`observedipv6addr0A`pathstatusbackup0A`peerincompatible00A`quicclientconfig0A`quicserverconfig0A`tokenmemorycache0A`toomanyaddressesA`validationfailed00A`versionnegotiate0ncheckandinsert00000oconfigureclientocongestionstate0oconnectionclose0nforcekeyupdate0nfromargmatches00000nmaxgsosegmentsnmaxidletimeout0nmaxstreamsbidi0nn0nattraversal0oopenconnections0nopenpathensure0opacketthreshold0oreceivedatagramnrecvbuffersize0nsendbuffersize0otransportconfig0000ovalidationtoken0nwithsinglecert0nwriteallchunksA`applicationerror0Aaapplicationclosed000Aacontrollerfactory0Aacontrollermetrics0Aahandshaketokenkey0Aainvalidservername0Aanetworkchangehint0Aapacketdecodeerror0Aapathabandonreason00Aasenddatagramerror0Aastreamdatablocked0Aastreamsblockeduni0Aaunsupportedbypeer0A`congestionevents0A`congestionwindow0omaxdatagramsizeonewconnectionid0opathcidsblocked0osendbufferspaceosetserverconfig0osetupconnectionotopossiblevalueA`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/617/docs/search.index/root.js b/pr/617/docs/search.index/root.js index 6bca28231..b4e21dea7 100644 --- a/pr/617/docs/search.index/root.js +++ b/pr/617/docs/search.index/root.js @@ -1 +1 @@ -rr_('{"normalizedName":{"I":"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","N":"ADMj","E":"OjAAAAAAAAA=","H":"Y4e/z82Q"},"crateNames":{"N":"j","E":"OjAAAAAAAAA=","H":"JFDJy0NX"},"name":{"N":"ADMj","E":"OjAAAAAAAAA=","H":"VurEXs/g"},"path":{"N":"BBh","E":"OzAAAAEAALESuwACAAQACQAAAAsAcAB9AAEAhwAEAJIAAQCVACEAuQD0ALABCAC6ASIA3gEOAPEBAAD2AQAA+AEFAAQCAQAIAgIADAIAAA8CBgAZAkwCZwQAAGkEEgB9BAIAgQQDAIYECgCSBAkApgQAALgEAgC8BAMAwQQAAMMEAADIBAAAygQBAM4EoQBxBQEAdQUIAH8FCACJBQIAjwUJAJ0FAgChBQUAqgUCAK4FAgC6BQIAxgUCAMoFAgDRBfUAyAYEAM4GAADQBgAA0wY8ABEHEQAlBwMAKgcDAC8HGQBKBwwAXAcBAGAHAABlBwEAaQcAAGsHAwB3BwAAeQcCAH4HAACBBwAAhAd2APwHAwACCAYACgj7AAkJAAALCQEADwkBABYJAAAcCQUAJgkAACgJAAArCQIAMQkBADoJBQBCCR0AYQkUAHgJmAITDAEAGwwCACAMAAAmDAEALQwAADEMAwBADAAASAwJAFMMBwBcDBkAdwwQAIkMUQDcDAMA5AwAAOcMAQDqDAEAAg0BACANhgGpDgIArg4xAeEPAADsDwEA8g8DAPgPAQD7DwEA/g8BAAUQAwAKEEEATRACAFIQFgBvEAEAchACAH0QAQCCEAQAiBADAJEQBQCaEAEAqhASAL4QIADgECAAAxEDAAsRAAAOEQEAEREBABURBwAhEQEAJhEBACwRAwAxEQAAMxECADcRNwB1EQUAfhEBAIMRBQCMEQUAlhEBAJsRWAD1EQAA+REAAP4RAQABEgEACBIAABESAQAUEgMAGRKoAMMSAADFEgAAyBIBAMsSAADOEgMA0xILAOASAQDoEgMA7RIYAAcTFwAgEwIAKRMBADQTEQBHEwIATBMDAFETBwBbEwMAYBMFAGoTAgBwEwAAchMBAHUTGwCSEwIAlxMFAJ8THwDBEwsAzxMFANgTAQDfEyUABhQEAA4UBQAWFB0ANRQCADoUEABNFBsAaxQKAHkUBQCAFBYAmBQDAJ0UGQC6FB8A","H":"veepvBbR"},"entry":{"N":"ACMa","E":"OjAAAAEAAAAAAAgBEAAAAAAAAQAHAAgACgB8AH8AgACBAIIAgwCMAJAAkQCUALcAuACuAa8BuQHtAe4B7wHwAfIB8wH3Af4B/wEAAgMCBgILAhYCFwJmBHwEgASFBJEEnASdBJ4EnwSiBKMEpwSoBKkEqgSrBKwErQSuBK8EsAS1BLsEwATCBMQExwTJBMwEcAVzBXQFfgWIBYwFjQWOBZkFmgWbBZwFpwWoBbEFsgWzBbQFtwW4BbkFvQXABcEFwgXDBcQFyQXNBc4FxwbNBs8G0QbSBhAHIwckBykHLgdJB1cHWAdZB1oHWwdhB2QHbwdwB3EHcgd2B30HfweABwAIAQgJCAcJCAkOCREJEgkVCRkJGgkkCSUJLgk4CTkJQAlBCWAJdgkRDBIMFgwXDBgMGQwaDB4MIQwkDCUMKQwwDDcMOgw7DEMMRAxGDEcMUgxbDIgM2wzgDOIM4wztDO8M9Az1DPgM+wz8DP4M/wwADQENCQ0PDRINEw0VDRYNpw6oDqwOrQ7kD+cP8Q8AEAQQTBBQEFEQaRBqEHYQeRCHEIwQjxCQEJgQmRCcEJ8QoBClEKYQqRC9EN8QBxEQERMRHxEjESQRKBEwETIRcBFzEXQRfRGCEYsR/REDEgcSCRIKEg0SEBIYEscS0hLfEuwSBhMfEy0TLxMwE18TZhNnE28TkRO/E8ATzRPOE9oT3RMFFA0USxRMFHYUfxS4FLkU","H":"VagEanQP"},"desc":{"N":"GBo","E":"OzAAAAEAAKoNhgEAAAEABwABAAoAXAB6AAAAfAAIAIwAAQCPAAIAlAAaALcA9ACuAQEAuQECANoBAgDeAQEA4wEBAOYBAADrAQgA9gEBAP4BAgADAgAABgIAAAkCAgARAgEAFgICAB0CBgA8AgAASwIAAE0CAQBQAgAAUwIAAFcCAgBcAgAAZAIBAGcCAgBtAgAAeQICAIICAACJAgEAjQIAAJACAACUAgAAmgIAAKcCAACrAgIArwIHAM8CBADYAgAA7AIAAPECAAD2AgAA/AIBAAQDAAAKAwAAEgMAABgDAAAaAwAAIgMAACgDAAAtAwAAMQMDADYDBgBJAwAAWAMUAFwEBABiBAAAZgQAAGgEAABqBAAAbAQAAG4EDAB8BAAAgAQAAIIEAACFBAAAjgQAAJEEAACXBAEAmgQFAKIEAQCmBAoAtQQAALsEAADABAAAwgQAAMQEBQDMBAEAzwQCANMEAwDeBI0AcAUEAH0FAQCABQIAiAUAAIoFBQCWBQAAmQUDAKEFAwCnBQEArwUFALcFAgC9BQAAwAUEAMkFAQDMBQIA1QXuAMcGAQDKBgEAzQYAAM8GAADRBgEA1QYAANkGAADcBggA5gYBAOoGCwD5BgAAAAcBAAMHAQAJBwIADQcDACMHAgAnBwAAKQcBACwHCwA+BwEASQcAAE0HAgBRBwIAVwcEAGEHAABkBwIAaQcAAGsHBwB2BwAAfQcAAH8HAQCHBwAAigcZAKwHMgDgBwEA8gcCAPYHAgAACAEACQgAAAsIAAAPCO4ABwkFAA4JAAARCQEAFQkAABkJAQAgCQEAJAkBAC4JAAA4CQEAPQkEAFAJAQBaCQQAYAkAAGQJAgBoCQIAcwkBAHYJAgCECcMASQoAAEwKUgCgCgAAowpoARAMAgAWDAQAHgwBACEMAAAkDAEAKQwAACsMAQAwDAAAMgwBADcMAAA6DAEAQwwBAEYMAQBJDAkAVAwCAFgMAwBfDAEAZwwAAHIMAwB6DAEAfQwFAIYMAgCMDAAAjgwFAJUMQgDbDAAA4AwAAOIMAQDtDAAA7wwAAPQMAQD4DAAA+wwBAP4MBQAJDQAADw0AABINBAAhDQUAKQ18AacOAQCsDgkByQ8AANEPBADXDwAA3g8BAOEPAwDnDwAA7A8BAPEPAAD0DwEA+A8EAP4PAgACEAIABhADABoQDwArEAEALhAAADAQAQA0EBMATBAAAFAQAQBmEAEAaRABAHYQAAB5EAAAfBAAAIUQAwCMEAAAjxABAJMQAwCYEAEAnBAAAJ8QAQClEAEAqRAFALEQAQC2EAIAuhAAALwQAQDEEAEAyxABAM8QBADeEAIA4xABAOgQAQD1EAMABxEAAA4RAgATEQAAGBEDAB8RAAAhEQMAJhECAC0RAQAwEQAAMhEAADYRAAA4EQIAQBECAEQRAgBIEQYAVBEAAGARAQBkEQkAcBEAAHMRAQB9EQAAghEAAIURAQCLEQAAjhEDAJURAACdEQAAnxEAAKERAACmEQAAqBEDAK0RAACvEQAAsREAALQRAAC2EQAAuBEAALwRAAC/EQAAwREAAMMRAADHEQAAyREAAMsRAADNEQAAzxEAANERAgDXEQAA2REAAOYRAQDuEQAA8RECAPURAAD9EQIAARICAAcSAAAJEgEADRIAABASAAAYEo4ArRIEALMSAAC5EgEAvBIBAL8SAADBEgAAwxIAAMUSAADHEgAA0hIAANkSBgDsEgAA7xIBAPISAgD3EgEA+xIFAAQTAgAOEwAAExMAAB8TAAAtEwAALxMBADQTAwA/EwMARhMBAFwTAABfEwIAZhMBAGoTAgBvEwAAdRMEAIQTAQCREwEAlRMAAJcTAwCjEwQAqxMAAK0TAAC2EwIAuhMCAL8TAQDCEwAAxRMDAM0TAQDaEwAA3RMAAOMTBgD5EwAA/BMBAAMUAgAHFAAAChQAAA0UAAAiFAEANhQBADkUAQA9FAAAPxQAAEUUAwBLFAEAUxQEAFsUAABdFAAAXxQFAGkUAQBzFAAAdhQAAH8UAACEFAMAkRQAAKEUBQCoFAAArxQAALIUAQC4FAEAxBQAAMgUAAA=","H":"rBazV+Ce"},"function":{"N":"AACk","E":"OzAAAAEAAJ4DVwAAAAoAfAAYALcAAQCuAQEAuQEAAN0BAADtASMAFgICAGYEAABoBAAAfAQAAIAEAACFBAAAkQQAAJwELQDMBAEAcAUAAHMFAQB+BQAAiAUAAIwFAwCWBQAAmQUDAKAFMADHBgAAygYAAM0GAgDRBgIA1wYAABAHAAAjBwYALAcAAC4HAABJBwAAVAcwAPsHAAAACAEACQgAAAYJNABACQEAYAkAAHYJAQARDDYAUgwAAFsMAAB2DAAAiAwAANsMAADgDAMA5Qw6AKcOAwCsDgEA4A8AAOIPGAD9DwcACRAAAEwQAABQEAEAZhAhAIkQIAC9EAAA3xABAP8QLAAwEQIANhEAAGoRMADzESUAwhIrAAYTAQAdExwARhMAAEgTEwBdExcAkRMAAJUTDAC/EwIAyRMVAAUUAAAIFAAACxQKACcUEgBLFAUAZxQDAHQUCwCLFAAAlxQFALcUBAA=","H":"wGs7LC4r"},"type":{"N":"AKd","E":"OzAAAAEAACUTnAACAAQACQAAAAsAcAB9AAIAhwAFAJAAAACSAAEAlQAhALkA9ACvAQkAugEyAPEBAQD2AQkABAIBAAgCAgAMAgAADwIHABgCgwKiBAAApgQCAKsEAACtBAAAtQQAALgEAgC8BAMAwQQAAMMEAADIBAAAygQBAM0EugCJBREAnQUCAKEFBgCqBQIArgUCALoFAgDGBQYA0QWFAVkHBABgBwAAZQcBAGkHAABrBwMAdwcAAHkHAgB9BwQAhAd7AAEIBAEJCQAACwkBAA4JAgAWCQAAGQkBABwJBQAmCQAAKAkAACsJAgAxCQEAOgnWAhMMAQAWDAMAGwwDACAMAAAmDAEALQwAADEMAwA3DAAAOgwBAEAMAABDDAEARwxAAIkMVgDiDAIA5wwBAOoMAQDtDAAA9AwBAPgMAAABDQIAFQ0AACANvwLhDwAA7A8BAPEPBAD4DwEA+w8BAP4PAgAFEGMAbxABAHIQAgB9EAEAghAJAJEQBQCaEAEAqhBWAAMRBAALEQAADhEBABERAQAVEQcAIREBACYRAQAsEUIAcxEAAHURBQB9EQIAgxEFAIwRBQCWEQEAmxFYAPURAAD5EQAA/hEBAAESAgAIEgAAERIBABQSrQDDEgAAxRIAAMgSAQDLEgAAzhIDANMSCwDgEgEA6BIDAO0SMQAgEwIAKRMBAC0TAAAvEwAANBMVAEwTAwBREwcAWxMDAGATBQBnEwAAahMCAHATAAByEwEAdRMfAJcTBQCfEy0AzxMFANgTAQDfEysADhQFABYUHQA1FAIAOhQQAE0UGwBrFAoAeRQFAIAUFgCYFAMAnRQZALoUHwA=","H":"b5G7Z7EN"},"alias":{"N":"`","E":"OzAAAAEAANkUAQAAANkU","H":"p2IVDFMs"},"generic_inverted_index":{"N":"b","E":"OjAAAAAAAAA=","H":"+cmGA9ts"}}') \ No newline at end of file +rr_('{"normalizedName":{"I":"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","N":"ADMh","E":"OjAAAAAAAAA=","H":"quWlN7o7"},"crateNames":{"N":"j","E":"OjAAAAAAAAA=","H":"JFDJy0NX"},"name":{"N":"ADMh","E":"OjAAAAAAAAA=","H":"0PQk09Gm"},"path":{"N":"BBh","E":"OzAAAAEAAK8SuwACAAQACQAAAAsAcAB9AAEAhwAEAJIAAQCVACEAuQD0ALABCAC6ASIA3gEOAPEBAAD2AQAA+AEFAAQCAQAIAgIADAIAAA8CBgAZAkwCZwQAAGkEEgB9BAIAgQQDAIYECgCSBAkApgQAALgEAgC8BAMAwQQAAMMEAADIBAAAygQBAM4EoQBxBQEAdQUIAH8FCACJBQIAjwUJAJ0FAgChBQUAqgUCAK4FAgC6BQIAxgUCAMoFAgDRBfUAyAYEAM4GAADQBgAA0wY8ABEHEQAlBwMAKgcDAC8HGQBKBwwAXAcBAGAHAABlBwEAaQcAAGsHAwB3BwAAeQcCAH4HAACBBwAAhAd2APwHAwACCAYACgj7AAkJAAALCQEADwkBABYJAAAcCQUAJgkAACgJAAArCQIAMQkBADoJBQBCCR0AYQkUAHgJmAITDAEAGwwCACAMAAAmDAEALQwAADEMAwBADAAASAwJAFMMBwBcDBkAdwwQAIkMUQDcDAMA5AwAAOcMAQDqDAEAAg0BACANhgGpDgIArg4xAeEPAADsDwEA8g8DAPgPAQD7DwEA/g8BAAUQAwAKEEEATRACAFIQFgBvEAEAchACAH0QAQCCEAQAiBADAJEQBQCaEAEAqhASAL4QIADgECAAAxEDAAsRAAAOEQEAEREBABURBwAhEQEAJhEBACwRAwAxEQAAMxECADcRNwB1EQUAfhEBAIMRBQCMEQUAlhEBAJsRVwD0EQAA+BEAAP0RAQAAEgEABxIAABASAQATEgMAGBKoAMISAADEEgAAxxIBAMoSAADNEgMA0hILAN8SAQDnEgMA7BIYAAYTFwAfEwIAKBMBADMTEQBGEwIASxMDAFATBwBaEwMAXxMFAGkTAgBvEwAAcRMBAHQTGwCREwIAlhMFAJ4THwDAEwsAzhMFANcTAQDeEyUABRQEAA0UBQAVFB0ANBQCADkUEABMFBsAahQKAHgUBQB/FBYAlxQDAJwUGQC5FB4A","H":"veepvBbR"},"entry":{"N":"ACLo","E":"OjAAAAEAAAAAAAgBEAAAAAAAAQAHAAgACgB8AH8AgACBAIIAgwCMAJAAkQCUALcAuACuAa8BuQHtAe4B7wHwAfIB8wH3Af4B/wEAAgMCBgILAhYCFwJmBHwEgASFBJEEnASdBJ4EnwSiBKMEpwSoBKkEqgSrBKwErQSuBK8EsAS1BLsEwATCBMQExwTJBMwEcAVzBXQFfgWIBYwFjQWOBZkFmgWbBZwFpwWoBbEFsgWzBbQFtwW4BbkFvQXABcEFwgXDBcQFyQXNBc4FxwbNBs8G0QbSBhAHIwckBykHLgdJB1cHWAdZB1oHWwdhB2QHbwdwB3EHcgd2B30HfweABwAIAQgJCAcJCAkOCREJEgkVCRkJGgkkCSUJLgk4CTkJQAlBCWAJdgkRDBIMFgwXDBgMGQwaDB4MIQwkDCUMKQwwDDcMOgw7DEMMRAxGDEcMUgxbDIgM2wzgDOIM4wztDO8M9Az1DPgM+wz8DP4M/wwADQENCQ0PDRINEw0VDRYNpw6oDqwOrQ7kD+cP8Q8AEAQQTBBQEFEQaRBqEHYQeRCHEIwQjxCQEJgQmRCcEJ8QoBClEKYQqRC9EN8QBxEQERMRHxEjESQRKBEwETIRcBFzEXQRfRGCEYsR/BECEgYSCBIJEgwSDxIXEsYS0RLeEusSBRMeEywTLhMvE14TZRNmE24TkBO+E78TzBPNE9kT3BMEFAwUShRLFHUUfhS3FLgU","H":"1gf0vp4p"},"desc":{"N":"GBm","E":"OzAAAAEAAKoNhAEAAAEABwABAAoAXAB6AAAAfAAIAIwAAQCPAAIAlAAaALcA9ACuAQEAuQECANoBAgDeAQEA4wEBAOYBAADrAQgA9gEBAP4BAgADAgAABgIAAAkCAgARAgEAFgICAB0CBgA8AgAASwIAAE0CAQBQAgAAUwIAAFcCAgBcAgAAZAIBAGcCAgBtAgAAeQICAIICAACJAgEAjQIAAJACAACUAgAAmgIAAKYCAACrAgYAswIDAM4CBADYAgAA6wIAAPACAAD3AgAA/AIBAAUDAAAKAwAAEwMAABkDAQAhAwAAJwMAAC0DAAAxAwoASQMAAFgDFABcBAQAYgQAAGYEAABoBAAAagQAAGwEAABuBAwAfAQAAIAEAACCBAAAhQQAAI4EAACRBAAAlwQBAJoEBQCiBAEApgQKALUEAAC7BAAAwAQAAMIEAADEBAUAzAQBAM8EAgDTBAMA3gSNAHAFBAB9BQEAgAUCAIgFAACKBQUAlgUAAJkFAwChBQMApwUBAK8FBQC3BQIAvQUAAMAFBADJBQEAzAUCANUF7gDHBgEAygYBAM0GAADPBgAA0QYBANUGAADZBgAA3AYIAOYGAQDqBgsA+QYAAAAHAQADBwEACQcCAA0HAwAjBwIAJwcAACkHAQAsBwsAPgcBAEkHAABNBwIAUQcCAFcHBABhBwAAZAcCAGkHAABrBwcAdgcAAH0HAAB/BwEAhwcAAIoHGQCsBzIA4AcBAPIHAgD2BwIAAAgBAAkIAAALCAAADwjuAAcJBQAOCQAAEQkBABUJAAAZCQEAIAkBACQJAQAuCQAAOAkBAD0JBABQCQEAWgkEAGAJAABkCQIAaAkCAHMJAQB2CQIAhAnDAEkKAABMClMAowpoARAMAgAWDAQAHgwBACEMAAAkDAEAKQwAACsMAQAwDAAAMgwBADcMAAA6DAEAQwwBAEYMAQBJDAkAVAwCAFgMAwBfDAEAZwwAAHIMAwB6DAEAfQwEAIMMAACGDAIAjAwAAI4MBQCVDEIA2wwAAOAMAADiDAEA7QwAAO8MAAD0DAEA+AwAAPsMAQD+DAUACQ0AAA8NAAASDQQAIQ0FACkNfAGnDgEArA4JAckPAADRDwQA1w8AAN4PAQDhDwMA5w8AAOwPAQDxDwAA9A8BAPgPBAD+DwIAAhACAAYQAwAaEA8AKxABAC4QAAAwEAEANBATAEwQAABQEAEAZhABAGkQAQB2EAAAeRAAAHwQAACFEAMAjBAAAI8QAQCTEAMAmBABAJwQAACfEAEApRABAKkQBQCxEAEAthACALoQAAC8EAEAxBABAMsQAQDPEAQA3hACAOMQAQDoEAEA9RADAAcRAAAOEQIAExEAABgRAwAfEQAAIREDACYRAgAtEQEAMBEAADIRAAA2EQAAOBECAEARAgBEEQIASBEGAFQRAABgEQEAZBEJAHARAABzEQEAfREAAIIRAACFEQEAixEAAI4RAwCVEQAAnREAAJ8RAAChEQAAphEAAKgRAwCtEQAArxEAALERAAC0EQAAthEAALgRAAC8EQAAvxEAAMERAADDEQAAxxEAAMkRAADLEQAAzREAAM8RAADREQIA1xEAANkRAADlEQEA7REAAPARAgD0EQAA/BECAAASAgAGEgAACBIBAAwSAAAPEgAAFxKOAKwSBACyEgAAuBIBALsSAQC+EgAAwBIAAMISAADEEgAAxhIAANESAADYEgYA6xIAAO4SAQDxEgIA9hIBAPoSBQADEwIADRMAABITAAAeEwAALBMAAC4TAQAzEwMAPhMDAEUTAQBbEwAAXhMCAGUTAQBpEwIAbhMAAHQTBACDEwEAkBMBAJQTAACWEwMAohMEAKoTAACsEwAAtRMCALkTAgC+EwEAwRMAAMQTAwDMEwEA2RMAANwTAADiEwYA+BMAAPsTAQACFAIABhQAAAkUAAAMFAAAIRQBADUUAQA4FAEAPBQAAD4UAABEFAMAShQBAFIUBABaFAAAXBQAAF4UBQBoFAEAchQAAHUUAAB+FAAAgxQDAJAUAACgFAUApxQAAK4UAACxFAEAtxQBAMMUAADHFAAA","H":"xOEe6RZH"},"function":{"N":"AACi","E":"OzAAAAEAAJ4DVwAAAAoAfAAYALcAAQCuAQEAuQEAAN0BAADtASMAFgICAGYEAABoBAAAfAQAAIAEAACFBAAAkQQAAJwELQDMBAEAcAUAAHMFAQB+BQAAiAUAAIwFAwCWBQAAmQUDAKAFMADHBgAAygYAAM0GAgDRBgIA1wYAABAHAAAjBwYALAcAAC4HAABJBwAAVAcwAPsHAAAACAEACQgAAAYJNABACQEAYAkAAHYJAQARDDYAUgwAAFsMAAB2DAAAiAwAANsMAADgDAMA5Qw6AKcOAwCsDgEA4A8AAOIPGAD9DwcACRAAAEwQAABQEAEAZhAhAIkQIAC9EAAA3xABAP8QLAAwEQIANhEAAGoRMADyESUAwRIrAAUTAQAcExwARRMAAEcTEwBcExcAkBMAAJQTDAC+EwIAyBMVAAQUAAAHFAAAChQKACYUEgBKFAUAZhQDAHMUCwCKFAAAlhQFALYUBAA=","H":"WlGelQy4"},"type":{"N":"AKd","E":"OzAAAAEAACMTnAACAAQACQAAAAsAcAB9AAIAhwAFAJAAAACSAAEAlQAhALkA9ACvAQkAugEyAPEBAQD2AQkABAIBAAgCAgAMAgAADwIHABgCgwKiBAAApgQCAKsEAACtBAAAtQQAALgEAgC8BAMAwQQAAMMEAADIBAAAygQBAM0EugCJBREAnQUCAKEFBgCqBQIArgUCALoFAgDGBQYA0QWFAVkHBABgBwAAZQcBAGkHAABrBwMAdwcAAHkHAgB9BwQAhAd7AAEIBAEJCQAACwkBAA4JAgAWCQAAGQkBABwJBQAmCQAAKAkAACsJAgAxCQEAOgnWAhMMAQAWDAMAGwwDACAMAAAmDAEALQwAADEMAwA3DAAAOgwBAEAMAABDDAEARwxAAIkMVgDiDAIA5wwBAOoMAQDtDAAA9AwBAPgMAAABDQIAFQ0AACANvwLhDwAA7A8BAPEPBAD4DwEA+w8BAP4PAgAFEGMAbxABAHIQAgB9EAEAghAJAJEQBQCaEAEAqhBWAAMRBAALEQAADhEBABERAQAVEQcAIREBACYRAQAsEUIAcxEAAHURBQB9EQIAgxEFAIwRBQCWEQEAmxFXAPQRAAD4EQAA/REBAAASAgAHEgAAEBIBABMSrQDCEgAAxBIAAMcSAQDKEgAAzRIDANISCwDfEgEA5xIDAOwSMQAfEwIAKBMBACwTAAAuEwAAMxMVAEsTAwBQEwcAWhMDAF8TBQBmEwAAaRMCAG8TAABxEwEAdBMfAJYTBQCeEy0AzhMFANcTAQDeEysADRQFABUUHQA0FAIAORQQAEwUGwBqFAoAeBQFAH8UFgCXFAMAnBQZALkUHgA=","H":"wvtxQLG9"},"alias":{"N":"`","E":"OzAAAAEAANcUAQAAANcU","H":"p2IVDFMs"},"generic_inverted_index":{"N":"b","E":"OjAAAAAAAAA=","H":"xHW2rqzR"}}') \ No newline at end of file diff --git a/pr/617/docs/search.index/type/6f91bb67b10d.js b/pr/617/docs/search.index/type/6f91bb67b10d.js deleted file mode 100644 index da14f3844..000000000 --- a/pr/617/docs/search.index/type/6f91bb67b10d.js +++ /dev/null @@ -1 +0,0 @@ -rd_("CDc[\"\",\"ADswAAABAAAFAAIAXAQEAKgUAAAC/QYAAB4HAAA6MAAAAQAAAAAACgAQAAAA6wHsAYIF3gyzEvkSDhOFFJEUsxTEFDowAAABAAAAAAAcABAAAAAdAh4CHwIgAiECIgIjAmsEgQWGBcsG+QYqB3gJQhG2EvoSjRNAFEEUiBStFK4UrxSwFLEUshTHFMsUOzAAAAEAADoBEgBpBAAAbgUAAIAFAAD+BgAAHQcAAC0HAACQDAMAIQ0FABgOjQDHEAAAQxEHAKIRAAAZEo0AtxIAALkSAABEEwAAjhMAAK4TAAA6MAAAAQAAAAAAIQAQAAAAgwWJBYoFiwX3BvgG/wYABwEHAgcDBwQHDQwODA8MEAxmDNcP2w/zEFkRshLCEwEUAhQYFFoUWxRcFF0UgBSEFJIUtBQ7MAAAAQAAQwAVAFgDFACHBAAAkwQCAGwFAQBvBQAAhAUBAJ8FAADqBgwA/AcBAAwMAAB8DAAAMRAAAGUQAAD0EAAAmxEAAMoRBwBFEwAAiRMDAPkTAACBFAAAtRQAADswAAABAABgAA0A4wBGAJsBAQCGBAAAqAcAAKsHAADsBwAA7gcAALkQAwDuEgUA6RMAABkUAABfFAUAchQAADswAAABAAANAAYAbQQAAN8MAACmDgAALRADALQSAAChFAUAOzAAAAEAABMACQCDBAEAjAQBAOsHAADtBwAA0Q8CANoPAABUEQAAHBMAALUTBwA7MAAAAQAACgADAAgHBwBKBwEACxMAAAGFDAAABKUHAACnBwAAqgcAAIEMAAAAOzAAAAEAAAUAAgDRAQAAOxQEAAAAAdQFAAA=\"]Bk[\"AAAAAAAAAAAEMxEAADQRAABtFAAAbhQAAA==\",\"\"]0Bg[\"\",\"AAAFWgkAAFsJAABcCQAAXQkAAF4JAAA=\"]AGg[\"AAAAAALDAQAA0gEAAAAAAmwFAABtBQAABKgHAACrBwAA7AcAAO4HAAA7MAAAAQAADQAFAJ0FAQCACQEAiwwEAN8MAADWEQMAOjAAAAEAAAAAAAsAEAAAAIwEjQTrB+0H2g/9EP4QwBHBEcIRwxEcEwR9BAAASgcAAEsHAABKEAAABGMEAABlBAAARxQAAEgUAAAFggQAAKcHAACqBwAA1A8AANUPAAADggwAAIMMAADWDwAAOzAAAAEAAAYAAwDRAQAA5QYAADsUBAABkhMAAAGEDAAA\",\"AAAAAfIRAAAAAnEFAAByBQAAAAJfDAAAYAwAAAAFiwwAAIwMAACNDAAAjgwAAI8MAAAAAAJjBAAAZQQAAA==\"]ANg[\"AAAEZQIAANACAADUBgAA2AYAAANhDAAAYgwAAGMMAAACwwEAANIBAAA7MAAAAQAAjQABABkSjQAAOzAAAAEAAAcAAwBsBQEAiRMDANcUAQAEqAcAAKsHAADsBwAA7gcAADswAAABAAASAAcAnQUBAB8HAACACQEAiwwEAN8MAADWEQMAvhIDADowAAABAAAAAAAKABAAAACDBIQEjASNBOsH7QfaD8ARwRHCEcMRBX0EAACHBQAASgcAAEsHAABVEQAABGMEAABlBAAARxQAAEgUAAAEpwcAAKoHAADUDwAA1Q8AAAT6BgAAggwAAIMMAADWDwAABTsUAAA8FAAAPRQAAD4UAAA/FAAAAZITAAAAAdQFAAA=\",\"AAAAAAACcQUAAHIFAAAAAl8MAABgDAAAAUsQAAAFiwwAAIwMAACNDAAAjgwAAI8MAAAAAAJjBAAAZQQAAAAAAAAAAxMHAACpEQAAqhEAAA==\"]Dc[\"\",\"AAAAADswAAABAAAHAAEAYwkHADswAAABAAAJAAIATxEEAKQRBAAAAaMRAAA=\"]ADi[\"AAAABsABAADCAQAAzgEAANABAABhEAAAYhAAAAKFCgAAigoAADowAAABAAAAAAAJABAAAADBAcYBxwHPAdgB2QH3CvkKSRRKFDswAAABAAANAAQAzgQHAAUQAAD5EAMAGBQAAALKAQAARRMAADswAAABAAAXAAsAtgEBAL0BAACJDAEA2AwBAFgQAwDmEAEAMxEBABQTAwDpEwAAbRQBALwUAQABbxQAAAAAAAAAAAAAAdQFAAA=\",\"AAABuwEAAALWBAAApxIAAAAAAALJDwAA3hAAAAAAAAAAAAAAAAAE1gYAANoGAADcDAAA3QwAAA==\",1]ADe[\"AAAAAr4BAADJAQAAOzAAAAEAAAcAAQBjCQcAAASJBQAAigUAAIsFAACyEgAAAAABbxQAAA==\",\"AAAAADswAAABAAAQAAMAywYAAGMJBwBTDAcAOzAAAAEAAAsABAAtBwAANREAAE8RBACkEQQAOzAAAAEAAA8ABQDOBAcAcQwCALsSAgD1EgAAhBQAADowAAABAAAAAAAKABAAAABfEWERYxFlEWcRoxHGEccRyBHJEfkTBLkQAAC6EAAAuxAAALwQAAAAAAVeEQAAYBEAAGIRAABkEQAAZhEAAAAAAAAAAAPUBQAAzg8AANAPAAA=\",1]Ec[\"AAAAAAAAOzAAAAEAAAYAAgDhBgMA5gYCADswAAABAAAGAAIA3AYEAEsJAQADfwQAAHwFAAB9BQAA\",\"\"]Dg[\"AAAAAAAAAAABYBQAAAGiFAAAAAAAAAAAAAABjAcAAA==\",\"AAAAAAAB/BIAAAHkEwAA\"]Eo[\"AAAAARIFAAABxQEAAAIwAAAAoAAAAAE5EAAAAV0DAAACGAEAABkBAAA=\",\"AAABrwAAAAESBQAAAaQTAAAAAUsMAAA=\"]Ic[\"AAAAAVQFAAAD1gEAAL4TAABCFAAAAl4AAACtAAAABowBAACNAQAAZAQAANMFAABGEAAAkhQAAAFqAwAA\",\"AAABsAAAAAJUBQAAphMAAAABTwwAAAAAAAAAAAAAAAAAAAKxAAAAsgAAAA==\"]Ik[\"AAAAAt4EAAATEwAAASoHAAAByAYAAAG5AAAAAZsRAAABXxQAAAK0EgAAoRQAAAAAAAAAAAAAOjAAAAEAAAAAAAkAEAAAAJAFxgaJBwoIExAqEFwQ6hA3EdoR\",\"AAAAAd4EAAAAAfsSAAAB4xMAAA==\"]Ck[\"AAAAAesBAAAAAAAAAWMUAAABpRQAAA==\",\"AAAAAAAB/xIAAAHnEwAA\"]Ck[\"AAAAAewBAAAAAAAAAWQUAAABphQAAA==\",\"AAAAAAABABMAAAHoEwAA\"]Kk[\"AAAAAAAABIMFAACJBQAAigUAAIsFAAAAOzAAAAEAAAkAAQACDAkAOzAAAAEAAA0AAQBtBA0AOzAAAAEAAAkABAB9DAMA0Q8CAFQRAABoEQEAAAGFDAAAA4EMAADUDwAA1Q8AAAKCDAAA1g8AAA==\",\"AAAAAAABNREAAANxDAAAcgwAAHMMAAA=\"]Nk[\"AAAAAAAAAhoTAAAbEwAABIkTAACKEwAAixMAAIwTAAACjxMAAJATAAAFHwcAAL4SAAC/EgAAwBIAAMESAAACgwQAAIQEAAAChwUAAFURAAAAAAH6BgAAAAAAAdQFAAA=\",\"AAAAAAJ/BQAAcxQAAAAFqxQAANIUAADTFAAA1BQAANUUAAAB+RMAAAJLEAAArBQAAAAAAAAAAAAAAAS+EAAAvxAAAMAQAADBEAAA\"]Bg[\"AAAAAAAAAATvEQAA8BEAABwUAAAdFAAA\",\"\"]Bk[\"\",\"AAAAAe4RAAAAAAAAAAAAAAAAAAAAAAHeDwAA\"]Cc[\"AAAAAAAAAfMQAAAB9BAAAA==\",\"AAAAAAACegkAAHsJAAA=\"]FHk[\"AAAAADswAAABAAAXAAkAHQIGAGsEAACVBQAADAgAAF0MAADCEAMA+hIAAEAUAQCtFAUAOzAAAAEAACkCEgBiBAAAewQAAIEEAAD+BgAATQwDACENBQApDXwBTRAAAFIQAADHEAAA5RAAAEMRBwCiEQAAGRKNALcSAAC5EgAAjhMAAPQTAAA7MAAAAQAAxwAQALkAKQAqAXAAnQEOAGQEAACDBQAA0wUAAOEGAwDmBgIA9wYBAEkMAwDPDwAAwhMAAOMTBQBaFAMAkhQAAKcUAAA7MAAAAQAAqABCAFgDFAB+BAAAiAQBAJMEAgBvBQAAhAUBAJ8FAADcBgQA6gYMAPkHAQD8BwEAAAkBAEsJAQBhCQEAbAkBAF8MAQBvDAEAfAwAACcNAQDKDwEA3A8BABQQAQAXEAEAMRAAAFMQAQBdEAEAZRAAAMgQAADYEAEA8RABAPQQAABaEQEAoxEAAK4RAACwEQAAshEAALURAAC3EQAAuREAAMQRDQDbEQEA3hECAO8RAQCoEgEADBMBAEUTAAB6EwEAgBMBAIkTAwCoEwEA6hMBAPoTAQD+EwEAHBQBACQUAQBRFAEAZRQBAIEUAgCNFAMAnRQDALUUAADFFAEAyRQBAMwUAQDQFAEA1xQBADswAAABAACcACcA4wBGAJsBAQC2AQEAYQQAAH8EAACGBAAAfAUBAJcFAQCoBwAAqwcAAOwHAADuBwAAAgwJAIkMAQDYDAEA2A8BAA0QAQBLEAAAWBADAOYQAQAzEQEAPREBAFYRAQC1EgAAAhMBABQTAwCPEwEAsRMBAN8TAQDyEwEA9RMDABkUAABfFAUAbRQBAHIUAACpFAEArBQAALwUBQDOFAEAOzAAAAEAACkADABtBA0AnQUBAB8HAACACQEAeQwAAN8MAACmDgAALRADANYRAwC+EgMA7xMBAKEUBQA7MAAAAQAAHAANAIMEAQCMBAEA6wcAAO0HAAB9DAMA0Q8CANoPAAD9EAEAVBEAAGgRAQAcEwAAtRMHALYUAAA6MAAAAQAAAAAADwAQAAAAfQSHBQgHCQcKBwsHDAcNBw4HDwdKEFUR1BHVEQsTiRQDYwQAAGUEAACFDAAAB4IEAAClBwAApwcAAKoHAACBDAAA1A8AANUPAAAE+gYAAIIMAACDDAAA1g8AADswAAABAAAFAAIA5QYAADsUBAAAAYQMAAAB1AUAAA==\",\"AAAAAAA7MAAAAQAA7gABACkN7gAAOjAAAAEAAAAAAFkAEAAAAIgEiQT5B/oHAAkBCWEJYglsCW0JXwxgDG8McAwnDSgNyg/LD9wP3Q8UEBUQFxAYEFMQVBBdEF4Q2BDZEPEQ8hBaEVsRrhGwEbIRtRG3EbkRxBHFEdsR3BHvEfARqBKpEgwTDRN6E3sTgBOBE6gTqRPqE+sT+hP7E/4T/xMcFB0UJBQlFFEUUhRlFGYUghSDFI0UjhSPFJAUnRSeFJ8UoBTFFMYUyRTKFMwUzRTQFNEU1xTYFDswAAABAAAzABUAtgEBAJcFAQCJDAEA2AwBAA0QAQBYEAMA5hABADMRAQA9EQEAVhEBAAITAQAUEwMAjxMBALETAQDfEwEA8hMBAPUTAwBtFAEAqRQBALwUBQDOFAEABIAJAACBCQAA7xMAAPATAAAAAtQRAADVEQAA\"]Ic[\"AAAAAasRAAAABIAFAABQCQAAUQkAALESAAA7MAAAAQAADAADAJYEBQD/BgUAOhQAAAADpAcAAFYRAABXEQAAAbQQAAAAB14RAABgEQAAYhEAAGQRAABmEQAA1BEAANURAAAAAaUHAAA=\",\"\"]Ko[\"AAACaAIAANECAAAAADswAAABAAALAAMAxgEBANgBAQBDEQcAOzAAAAEAAAcAAQDOBAcAOzAAAAEAAA8ABQD5BwEAFBABAF0QAQDEEQEAyhEHAAAC7xMAAPATAAA=\",\"AAAAAQsQAAAAAAAAAAAAAAAAAAAAAAUKEAAADBAAABMQAAAWEAAA8RMAAA==\"]AAg[\"AAAEZwIAAM8CAABSCQAAUwkAAAS8AQAAxAEAAMgBAADVAQAAAm0CAADYAgAAAAA6MAAAAQAAAAAAEwAQAAAAiASJBAAJAQnKEMwQzhDQENEQXxFhEWMRZRFnEQwTDRPJFMoUzBTNFAKPEwAAkBMAAAWLDAAAjAwAAI0MAACODAAAjwwAAAAFXhEAAGARAABiEQAAZBEAAGYRAAAAAAAC0QEAAOUGAAA=\",\"AAAAAAAAAAAAAAAAAAAAAAAABAIJAAADCQAABAkAAAUJAAA=\"]C@o[\"AAADTwkAAHwTAAB9EwAABboBAABoCgAA7hEAAIITAACDEwAAOjAAAAEAAAAAAAcAEAAAAMUB1gFUCVUJSgqhCskKthIBtxIAAAXCEwAAWhQAAFsUAABcFAAAXRQAADowAAABAAAAAAAXABAAAAD8B/0HyQ9TEFQQ3hCuEbARshG1EbcRuRHKEcsRzBHNEc4RzxHQEdER6hPrE40UjhQD6RMAAMAUAADBFAAABC0QAAAuEAAALxAAADAQAAA7MAAAAQAACQADAOsHAADtBwAAtRMHADswAAABAAAHAAEACAcHAAACpwcAAKoHAAA=\",\"AAAEdgUAAHcFAAC/DwAAwA8AADswAAABAAAWAAsATAcHAKwRAQCvEQAAsREAALMRAQC2EQAAuBEAAKoSAQAWFAEAGhQAAGsUAQACvhMAAEIUAAABsRIAAAAAAAAAAAAAAAAAADowAAABAAAAAABxABAAAACwAbEBswG0ARECEgIaAhsCagRsBI4E2ATbBNUG2QYGBwcHPgc/Bz0JPglzCXQJXAxeDHQMdQx6DHsMhgyHDMUPxg/HD8gP3w8GEAcQKBApEFwQthC3ENQQ1RDWENcQ4xDkEOgQ6RD1EPYQLREuETcRORE6EUARQRFLEUwRTRFOEdIR0xHmEecRrRKuEq8SsBL3EvgSBBMFExATEhM6EzsTPBM9Ez8TQBNBE0ITfhOEE4UThhOiE7MTtBO9E8MTxBPFE8YTxxPIE+ET4hP8E/0TAxQEFCIUIxRFFEYUhhSHFJMUlBQ=\"]Ac[\"AAAAAAFJAwAA\",\"\"]Gc[\"AAAABBQFAABNBwAA8gcAAK0RAAADZAkAAFQMAACuFAAAAUQRAAABWxQAAALLEQAAzxEAAAEdAQAAAS4QAAABthMAAAEJBwAA\",\"AAAAARQFAAA=\"]Gc[\"AAAABF4FAABRBwAA9gcAALQRAAADaAkAAFgMAACxFAAAAUgRAAACnwEAAF0UAAACzREAANERAAAAATAQAAABuhMAAAENBwAA\",\"AAAAAV4FAAA=\"]Ca[\"AAAAAAJtAgAA2AIAADswAAABAAAFAAEA6BEFAA==\",\"\",1]Ek[\"AAAAAAAAAAAAAAAAAAAAAAAAAvsGAAARBwAA\",\"AAAAAAACyRAAAMsQAAAAAsoQAADMEAAAAAK+EgAAvxIAAA==\"]Eo[\"AAAAAAAAAAAAAAAAAAAAAAAAAvwGAAASBwAA\",\"AAAAAAACzRAAAM8QAAAAA84QAADQEAAA0RAAAAACwBIAAMESAAA=\"]Ck[\"AAAAAAAAAAAAAAAAAAAAAAAABXsAAAC0AQAA5wcAAAQIAAAECQAA\",\"\"]Bg[\"AAAAAAAAAdcPAAAAAAAAAtQRAADVEQAA\",\"\"]Gk[\"AAAABGcAAADoBAAAkwUAAGAQAAABHwIAAAYOAAAAaQQAAG4FAAA7BwAA5BEAAEQUAAACzwAAANsPAAAAAgAUAABeFAAA\",\"AAAAAegEAAAB/wgAAAFnBAAA\"]Ic[\"\",\"AAAAAAAAAAA7MAAAAQAACQABAAIMCQA7MAAAAQAADQABAG0EDQA7MAAAAQAACQAEAH0MAwDRDwIAVBEAAGgRAQAAAYUMAAADgQwAANQPAADVDwAAA4IMAACDDAAA1g8AAAAAAYQMAAA=\"]Hk[\"AAAD3gEAAGgMAABrDAAAAREFAAABxQEAAAQvAAAAnwAAAMkQAADLEAAAATgQAAABXAMAAAMXAQAAnhEAAJ8RAAA=\",\"AAAC3gEAAGQMAAACigQAABEFAAABoxMAAAABSgwAAA==\"]Hk[\"AAAD3wEAAGkMAABsDAAAAVMFAAAB1gEAAARdAAAArAAAAM0QAADPEAAAAosBAABFEAAAAWkDAAADfwQAAKARAAChEQAA\",\"AAAC3wEAAGUMAAADiwQAAFMFAAClEwAAAAFODAAA\"]AMo[\"AAAAAAG4EAAAAAA6MAAAAQAAAAAAEQAQAAAAygF+BG8McAwnDSgNyg/LD1oRWxGoEqkSnRSeFMUUxhTQFNEUAoYEAADpEwAABC0QAAAuEAAALxAAADAQAAA7MAAAAQAABwABALUTBwA7MAAAAQAABwABAAgHBwAAAAAC0QEAAOUGAAA=\",\"AAAHeAUAAHkFAABoDAAAaQwAAGsMAABsDAAAuBIAADowAAABAAAAAAAXABAAAADvB/AHXwlrCWoMbQxuDMEPwg/aENsQ3BAsEasRuxG8Eb0RvhG/EewTWBRZFHAUcRQ7MAAAAQAACwECAK4OBwGqEwMAOzAAAAEAAF8ABAALAFsAfxMAAK8TAQAbFAAAAacUAAAAAATWEQAA1xEAANgRAADZEQAABMARAADBEQAAwhEAAMMRAAABCxMAAAAAAAAAAAeSBAAAkAUAAAUHAACrDgAALxEAAD8RAADaEQAA\"]Dk[\"AAAAAAJICgAAnwoAAAAAADswAAABAAAFAAEA7hIFAAACgwQAAIQEAAAAAmMEAABlBAAA\",\"\"]C@k[\"\",\"ADswAAABAAAFAAIAXAQEAKgUAAAC/QYAAB4HAAA6MAAAAQAAAAAACgAQAAAA6wHsAYIF3gyzEvkSDhOFFJEUsxTEFDowAAABAAAAAAAcABAAAAAdAh4CHwIgAiECIgIjAmsEgQWGBcsG+QYqB3gJQhG2EvoSjRNAFEEUiBStFK4UrxSwFLEUshTHFMsUOzAAAAEAADYBEQBpBAAAbgUAAIAFAAD+BgAAHQcAAC0HAAAhDQUAGA6NAMcQAABDEQcAohEAABkSjQC3EgAAuRIAAEQTAACOEwAArhMAADowAAABAAAAAAAfABAAAACDBYkFigWLBfcG+Ab/BgAHAQcCBwMHBAcNDA4MDwwQDNcP2w/zEFkRshLCEwEUAhQYFFoUWxRcFF0UgBSSFLQUOzAAAAEAAEEAEwBYAxQAhwQAAJMEAgBsBQEAbwUAAIQFAQCfBQAA6gYMAPwHAQAMDAAAfAwAADEQAABlEAAA9BAAAMoRBwBFEwAAiRMDAIEUAAC1FAAAOzAAAAEAAFsACwDjAEYAmwEBAIYEAACoBwAAqwcAAOwHAADuBwAA7hIFABkUAABfFAUAchQAADswAAABAAAMAAUAbQQAAN8MAACmDgAALRADAKEUBQA7MAAAAQAAEwAJAIMEAQCMBAEA6wcAAO0HAADRDwIA2g8AAFQRAAAcEwAAtRMHADswAAABAAAJAAIACAcHAEoHAQABhQwAAAOnBwAAqgcAAIEMAAAABTsUAAA8FAAAPRQAAD4UAAA/FAAA\"]Bc[\"AAAAAAAAAAAAAAAC1BEAANURAAA=\",\"\"]Kc[\"AAAAAAGIEwAAADswAAABAAAGAAIA/wYFAFkRAAACbwUAAGUQAAABhgQAAAAD/RAAAP4QAAAcEwAAAUoQAAA=\",\"AAAAAAABgQQAADswAAABAAAFAAEAlgQFAAAAAAAAAAAAAAAABxwCAADXBAAA2gQAABUHAAAWBwAAGAcAABkHAAA=\"]Do[\"AAAAAAABGAAAAAAAAecAAAAAAAAAAAAAAAAC1wQAAAYHAAA=\",\"AAAAAAAAAAAC2A8AANkPAAA=\"]Dk[\"AAAAAAABNgAAAAEwAQAAAAAAAAAAAAAAAAAC2gQAAAcHAAA=\",\"AAAAAAAAAAABYQQAAA==\"]Ac[\"AAABegIAAA==\",\"\"]Fg[\"AAAABBYFAABOBwAA8wcAAK8RAAADZQkAAFUMAACvFAAAAUURAAAAAAEfAQAAAAG3EwAAAQoHAAA=\",\"AAAAARYFAAAAAcYBAAA=\"]Fg[\"AAAABGAFAABSBwAA9wcAALYRAAADaQkAAFkMAACyFAAAAUkRAAABoQEAAAAAAAG7EwAAAQ4HAAA=\",\"AAAAAWAFAAAAAdgBAAA=\"]Bk[\"\",\"AAAAAAAAAAA7MAAAAQAASAACAOMARgCbAQEA\"]Ck[\"\",\"AAAAAAHLBgAAOzAAAAEAAAkAAgAtBwAALwcIAAGEFAAAAfkTAAA=\"]Bo[\"\",\"AAAAAAAAAAAAAAAAAkcUAABIFAAAAAAAAZITAAA=\"]Ck[\"AAACeQIAAI0CAAA=\",\"AAAAAAAAAgYUAAAHFAAAAAAC1hEAANcRAAA=\"]Eo[\"\",\"AAAAAAA7MAAAAQAABQABAPsSBQA7MAAAAQAABQABAOMTBQAAOzAAAAEAAAUAAQBfFAUAOzAAAAEAAAUAAQChFAUA\"]Cc[\"\",\"AAAAAAKjEwAApBMAAAAESQwAAEoMAABLDAAATAwAAA==\"]Eo[\"AAAAARoFAAAAATMAAAAAAAImAQAAJwEAAA==\",\"AAA7MAAAAQAACgACAKsCAgCvAgcAAhoFAACVFAAAAAABGhMAAA==\"]Eo[\"AAAAAVoFAAAAAWUAAAAAAAKbAQAAnAEAAA==\",\"AAA7MAAAAQAACgACADEDAwA2AwYAAloFAACWFAAAAAABGxMAAA==\"]Dg[\"AAABGAMAAAFZBQAAAAFjAAAAApYBAACXAQAA\",\"AAABKAMAAAFZBQAAAAAAAAF/BAAA\"]Eo[\"AAAAAAAAAqUBAACmAQAA\",\"AAAAAAAAB4AUAACrFAAAtBQAANIUAADTFAAA1BQAANUUAAACgRQAALUUAAABrBQAAA==\"]Bk[\"AAAAAAAAAVYBAAA=\",\"AAAB8QIAAAAAAXsEAAA=\"]Bg[\"AAAAAWQFAAAAAAGnAQAA\",\"AAAAAWQFAAA=\"]Bg[\"\",\"AAAAAAAAAAAAAAAAAAAAAAAAAdoMAAA=\"]Cc[\"AAAAAAAAAAAAAAAAAAAAAAAAA+gHAAAFCAAABQkAAA==\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAApIEAADpBwAA\",\"\"]Cc[\"AAAAAAAAAAJLCQAATAkAAAAAAAAAAAAC0QEAAOUGAAA=\",\"\"]Fo[\"AAAAAAACgAUAALkSAAAAAAAAAAAAAAAAAAAECwgAAN8PAAD3EAAA8REAAA==\",\"AAGtBwAAAAAAAAAAAAAAAAAAAAAAAAL4EAAAnREAAA==\"]Bk[\"AAAAAYIFAAAAAAHXDwAAATEQAAA=\",\"AAHgBwAA\"]Bg[\"AAAAAAAABPkQAAD6EAAA+xAAAPwQAAA=\",\"\"]Ag[\"AAAAAr4BAADJAQAA\",\"\"]Dg[\"AAAAAAAAAAAAAoAJAACBCQAA\",\"AAAAAAAAAAAABYsMAACMDAAAjQwAAI4MAACPDAAA\"]AEg[\"AAAAAALDAQAA0gEAAAAAAmwFAABtBQAABKgHAACrBwAA7AcAAO4HAAA7MAAAAQAACwAEAJ0FAQCLDAQA3wwAANYRAwA6MAAAAQAAAAAACwAQAAAAjASNBOsH7QfaD/0Q/hDAEcERwhHDERwTBH0EAABKBwAASwcAAEoQAAAEYwQAAGUEAABHFAAASBQAAAWCBAAApwcAAKoHAADUDwAA1Q8AAAOCDAAAgwwAANYPAAA7MAAAAQAABgADANEBAADlBgAAOxQEAAGSEwAAAYQMAAA=\",\"AAAAAfIRAAAAAnEFAAByBQAAAAJfDAAAYAwAAAAAAAACYwQAAGUEAAA=\"]He[\"\",\"AAAAAAAEkAwAAJEMAACSDAAAkwwAAAJmDAAAhBQAAAKbEQAA+RMAAAW5EAAAuhAAALsQAAC8EAAA6RMAAAG0EgAAAAELEwAAAAGlBwAAAAHRAQAAAAAB1AUAAA==\",1]AMk[\"AAACywEAANQBAAAGvAEAAMQBAADIAQAA1QEAAIITAACDEwAABIEFAAB4CQAASwoAAKIKAAAEkAwAAJEMAACSDAAAkwwAAAIBFAAAAhQAADowAAABAAAAAAAHABAAAABLCUwJ2BDZEHoTexOCFIMUOjAAAAEAAAAAAAkAEAAAAGEEqAerB+wH7gfYD9kPSxCpFKoUAALrBwAA7QcAAA==\",\"AAAAOjAAAAEAAAAAABAAEAAAALIBhQeGB4cHiAcNCA4IlAxOEE8Q8BBcEV0RCBMJE0MTjBQDdQkAALgQAAA+EwAABpAMAACRDAAAkgwAAJMMAAAPEwAAERMAAAAAAAKdBQAAngUAAAACfQQAAEoQAAAAA4IEAADUDwAA1Q8AAAOCDAAAgwwAANYPAAAAAAKEDAAA5AwAADowAAABAAAAAAALABAAAAC1AY8EkATZBNwExAbFBkAHiQcKCAsIKhA=\"]Bg[\"AAAAAAAAAAAAAXgEAAA=\",\"AAAAAdEFAAA=\"]Bk[\"AAAAAAAAADswAAABAAANAAIAnwUAAOoGDAA=\",\"\"]Cc[\"AAABQgkAAAABIwIAAAAAAAFhBAAA\",\"AAAAAAAAAAF+BAAA\"]Dg[\"AAAAAeIEAAAAAAHBAAAAAAAAAAAAAAAAAAADlAUAABQHAADNDwAA\",\"AAAAAeIEAAA=\"]Bc[\"AAAAAAAAAAAAAAAAAAAAAeUGAAA=\",\"\"]Be[\"AAAAAAAAA4kFAACKBQAAiwUAAA==\",\"\",1]Bg[\"AAAAAAAAADswAAABAAAUAAEAWAMUAA==\",\"\"]Dk[\"AAAAAAHXAQAA\",\"AAAAAAVuCQAAbwkAAHAJAABxCQAAcgkAAAAAAAAAAAAAAAAAAAGIEAAA\"]Cc[\"AAAAAecEAAAAAQ0AAAACxwAAAMgAAAA=\",\"AAAAAecEAAA=\"]Bg[\"AAAAAAAAAAAAAXMEAAA=\",\"AAAAAf4HAAA=\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAWwAAAA=\",\"\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAXEAAAA=\",\"\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAbEAAAA=\",\"\"]C@a[\"AAAAAAHXAQAAAUQTAAABGBQAAALKAQAARRMAADowAAABAAAAAAAdABAAAAC9AZcFmAUNEA4QPRE+EQITAxOxE7IT3xPgE/IT8xP1E/YT9xP4EwAUGRReFKkUqhS+FL8UwBTBFM4UzxQ7MAAAAQAABgADAIAJAQAtEAMAbxQAAAG2FAAAAYkUAAAAAAH6BgAAAAAAAdQFAAA=\",\"AAAFWgkAAFsJAABcCQAAXQkAAF4JAAAEYQwAAGIMAABjDAAApxIAADswAAABAAAYAAwArAEAAMoEAQCmBwAAqQcAAP8HAAD+CAEAbgkEAHUJAACvEAQAPhMAAEcTAACqEwMAOzAAAAEAACAADgDnAQAA6QEAAGIEAABnBAAAewQAAIEEAAAvBwgAkAwDAMwPAADlEAAATxEEAKQRBACHEwAA9BMAADswAAABAAAjAAkAZAQAAJYEBQDTBQAACAgAAM8PAAA0EBMA9RIBAAYUAQAJFAEAOzAAAAEAAAgABADIEAAAxhEDAN4RAgAhFAAAOzAAAAEAAAwABgBhBAAA2A8BALkQAwCeEQMAABQAAF4UAAAEvhIAAL8SAADAEgAAwRIAADswAAABAAAGAAMAfQwDAGgRAQC2FAAABH0EAABKEAAAVREAAIkUAAAAAAH6BgAAAdEBAAAAADswAAABAAAeAA8AswADANsGAADpBgAAOAcCADsJAAB8CQMAggkBABkQAAAyEAEAVRACAOsQBADdEQAArBIAACYUAADWFAAA\",1]Mg[\"AAAAAfIEAAAAOjAAAAEAAAAAAAcAEAAAABkAnADnAWcEzA/pEewRrxMCNRAAAPUSAAACWQMAAOsGAAAC6AAAAOkAAAAAAesHAAAAAAGnBwAA\",\"AAGyBwAAAANnAAAAaAAAAPIEAAACbQIAAP8HAAAC6REAAOwRAAAAAd0GAAAAAAAAAAAAAAAABWwAAABtAAAAbgAAAG8AAABwAAAA\"]A@c[\"AAAAASEFAAAABzgAAACkAAAA6QEAAOsRAADtEQAAhxMAALATAAAHMwEAADQBAADPDwAAPRAAAEkQAADdEAAA9hIAAARhAwAA8AYAAHwMAAAhFAAAAbUSAAABpg4AAALtBwAAthQAAAGJFAAAAAGqBwAA\",\"AAHJBwAAAAEhBQAAAtgCAAB/BQAAAusRAADtEQAAAeIGAAAAAAF5DAAAAAELEwAAAAAAAAAABXEAAAByAAAAcwAAAHQAAAB1AAAA\"]Bo[\"\",\"AAAAAAAAOzAAAAEAAKkAAwC5ACkAKgFwAJ0BDgA=\"]CDm[\"\",\"AAA7MAAAAQAA7gABABML7gA7MAAAAQAA9QAPABIKNQBJCgAATAo4AIYKAwCLChMAoAoAAKMKHwDECgQAygoqAPYKAAD4CgAA+goAAPwKFgD0EgAAlRQBADowAAABAAAAAAASABAAAACGBdIFywZUCVUJSApKCksKhQqKCp8KoQqiCsMKyQr1CvsKoxOkEzowAAABAAAAAAASABAAAABpBG4FLQc7B3oJewn3CvkK5BH7EvwS/RL+Ev8SABOuE0QUSRRKFDowAAABAAAAAAApABAAAAD3BvgGCAgNDA4MDwwQDEkMSgxLDEwMZgzbDwUQSRDdEPMQ+RD6EPsQ/BBZERoTGxPjE+QT5RPmE+cT6BMGFAcUCRQKFIAUhBSrFLQU0hTTFNQU1RQ6MAAAAQAAAAAAGgAQAAAAfgSTBJQElQSEBYUF3AbdBt4G3wbgBnwMZRDKEMwQzhDQENEQ9BCjEd4R3xHgEfkTIRSBFLUUOzAAAAEAAGIADwDjAEYAmwEBAGEEAAB/BAAAhgQAAHwFAQCkBwAA2A8BAEsQAAC1EgAA7hIFAAAUAABeFAYAchQAAKwUAAA7MAAAAQAAEQAIAJ0FAQB5DAAA3wwAAKYOAAC0EAAA1hEDAO8TAQChFAUAOjAAAAEAAAAAAAgAEAAAAIMEhATaD/0Q/hBoEWkRHBO2FDowAAABAAAAAAALABAAAAB9BIcFSgdLB0oQXhFgEWIRZBFmEQsTiRQEYwQAAGUEAABHFAAASBQAAAOlBwAApwcAAKoHAAACgwwAANYPAAA7MAAAAQAABgADANEBAADlBgAAOxQEAAGSEwAAAAHUBQAA\",1]Le[\"\",\"AAAAAtoBAADJBgAAAysHAADDDwAAxA8AAAMdBwAADxMAABETAAA6MAAAAQAAAAAABwAQAAAA1w+yErsSvBK9EgEUAhQ6FAKHBAAADAwAAAG9AQAAAW8UAAAE0Q8AANIPAADTDwAAVBEAAAABhQwAAASCBAAAgQwAANQPAADVDwAAAYIMAAAAAAGEDAAA\",1]Gc[\"\",\"AAAAOjAAAAEAAAAAAAcAEAAAAOsB7AGzEg4ThRSRFLMUxBQCKgcAALgQAAAFyAYAAFAJAABRCQAACBAAALESAAABwhMAAAGbEQAAAekTAAA=\"]Bo[\"\",\"AAAAAqUTAACmEwAAAARNDAAATgwAAE8MAABQDAAA\"]Bk[\"\",\"AAAAAAAAOzAAAAEAAAYAAgDhBgMA5gYCAA==\"]Fc[\"AAAAAdMBAAAAAAAABJcFAACYBQAADRAAAA4QAAA=\",\"AAA7MAAAAQAAQgABAJUMQgAAAAAAAAAAAAAAAAAAAAABGgcAAA==\"]Ao[\"\",\"AAA7MAAAAQAA7gABAA8I7gA=\"]AAg[\"AAAEggIAAJQCAACaAgAAvw8AAAHwBAAAA40TAADHFAAAyxQAAAMWAAAAmwAAAOgRAAABNBAAADowAAABAAAAAAAMABAAAABYA5MElARsBW0FhAXqBtsR+hMkFFEUjxSfFALjAAAA5AAAAA==\",\"AAGxBwAAB2QCAABlAgAAZwIAAGgCAABpAgAAUgkAAHwTAAAB8AQAAARUCQAASAoAAEoKAABLCgAAAegRAAABSQwAAAPcBgAA3hEAAN8RAAAAAAAAAAAAAAAAAbcPAAA=\"]AAk[\"AAAE9gIAABoDAAAiAwAAwA8AAAEeBQAAAAQ0AAAAogAAAOoRAACOEwAABCwBAAAtAQAAOxAAAJIUAAA6MAAAAQAAAAAACgAQAAAAXwOVBG8FhQXvBtwR+xMlFFIUkBSgFAABpg4AAA==\",\"AAHIBwAAOzAAAAEAAAYAAwDPAgQAUwkAAH0TAAABHgUAAARVCQAAnwoAAKEKAACiCgAAAk0MAADqEQAAAuEGAAAICAAAAeARAAAAAAAAAAAAAAAABLkPAAC6DwAAuw8AALwPAAA=\"]Gc[\"AAA7MAAAAQAACwAEAKoQBACnEwAAUxQEAMgUAAA=\",\"ADswAAABAAAJAAMAigcBAI0HAgB1EwQAOzAAAAEAAAsABACqEAQApxMAAFMUBADIFAAA\"]Ic[\"AAAAAAAAAAA7MAAAAQAACQABAAIMCQA7MAAAAQAADQABAG0EDQA7MAAAAQAACQAEAH0MAwDRDwIAVBEAAGgRAQAAAYUMAAADgQwAANQPAADVDwAAA4IMAACDDAAA1g8AAAAAAYQMAAA=\",\"\"]Cc[\"AAAAAsABAABcEQAAAAAAAAGJDAAAAAGMBAAAAUoHAAA=\",\"\"]Cc[\"AAAAAs4BAABdEQAAAAAAAAGKDAAAAAGNBAAAAUsHAAA=\",\"\"]ACg[\"AAAAApMTAACUEwAAAAXGAQAAxwEAANgBAADZAQAAohEAADswAAABAAALAAMAzgQHAIMFAABxDAIAOzAAAAEAAA0ABQBvBQAA/AcBANwPAQAxEAAAyhEHAAeeEQAAnxEAAKARAAChEQAAGRQAAHIUAACsFAAABnkMAACmDgAALRAAAC4QAAAvEAAAMBAAADswAAABAAAHAAEAtRMHADswAAABAAAKAAQACAcHAFURAAALEwAAiRQAAAABpQcAAAH6BgAAAAAAAdQFAAA=\",\"AAAAA+EBAADiAQAA4wEAAAAB5RAAAA==\"]Ek[\"AAAAAAAAAAAAAAAAAAAAAAACegUAACAHAAA=\",\"AAAAAAAAAALGEQAAxxEAAAAAAAAAAAAAAAAC+wYAABEHAAA=\"]Ek[\"AAAAAAAAAAAAAAAAAAAAAAACewUAACIHAAA=\",\"AAAAAAAAAALIEQAAyREAAAAAAAAAAAAAAAAC/AYAABIHAAA=\"]Ak[\"\",\"AAAAAAIrEAAALBAAAA==\"]Fc[\"AAAABBgFAABPBwAA9AcAALERAAACZgkAAFYMAAABRhEAAAAAASEBAAAAAbgTAAABCwcAAA==\",\"AAAAARgFAAAAAccBAAA=\"]Fc[\"AAAABGIFAABTBwAA+AcAALgRAAACagkAAFoMAAABShEAAAGjAQAAAAAAAbwTAAABDwcAAA==\",\"AAAAAWIFAAAAAdkBAAA=\"]Bg[\"AAAAAAAAAAAAAXIEAAA=\",\"AAAAAXkJAAA=\"]Ag[\"AAACvwEAAM0BAAA=\",\"\"]Ao[\"AAAAAAAAAAAAAAAAAAGBDAAA\",\"\"]Ag[\"\",\"AAAC0hAAANMQAAA=\"]Bg[\"AAAAAAAAAAAAAbQSAAA=\",\"AAABegAAAA==\"]Dg[\"AAAAAuEBAADiAQAAAAADiQUAAHEMAAC7EgAAAAG9AQAAAW8UAAA=\",\"AAAAAacSAAA=\"]Hg[\"AAAAAbsRAAABqhMAAAPJEAAAUBEAAKURAAABBhQAAAHGEQAAAbkQAAAC1hEAAL4SAAABwBEAAAAAAAABPBQAAA==\",\"AAAAAAAAAAJfEQAAYREAAAAAAAJeEQAAYBEAAA==\"]Ig[\"AAAAAb4RAAABrBMAAAPNEAAAUhEAAKcRAAABCRQAAAHIEQAAAbsQAAAC2BEAAMASAAABwhEAAAAAAAABPhQAAA==\",\"AAAAAAGcEQAAAAADYxEAAGURAABnEQAAAAAAA2IRAABkEQAAZhEAAA==\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAnkAAAC8DwAA\",\"\"]Bk[\"AAAAAAAAAeIAAAAAAAF6BAAA\",\"AAAAAQcIAAA=\"]Cc[\"AAAAAfAQAAABvhMAAAACZAQAANMFAAA=\",\"AAAAAAEMCAAA\"]Ek[\"AAAAAfsEAAAAASEAAAAAAAH0AAAAAAAAAAAAAAAAAtkEAADEBgAA\",\"AAG5BwAAAAH7BAAAAAAAAAAAAf0QAAA=\"]Ek[\"AAAAAS4FAAAAAUMAAAABRQEAAAAAAAAAAAAAAAAAAtwEAADFBgAA\",\"AAHQBwAAAAEuBQAAAAAAAAAAAf4QAAA=\"]Be[\"AAAAAAA7MAAAAQAAjQABABkSjQA=\",\"\",1]Bg[\"AAAAAAAAAAAAAXUEAAA=\",\"AAAAAUgMAAA=\"]Bg[\"\",\"AAAAAAAAAAAAAAAAAAAAAAAAAc0PAAA=\"]Ec[\"AAAAAAAAAAAAAAAAAAKnBwAAqgcAAAH6BgAAAtEBAADlBgAA\",\"AAAAAAAAAAAAAAAAAAAAAdEBAAA=\"]Dk[\"AAAAAAAAAAABYRQAAAGjFAAA\",\"AAAAAAAB/RIAAAHlEwAAAAAAAAAAAAAAAAABjAcAAA==\"]Dg[\"AAAAAUYFAAAAAAJwAQAAcQEAAAHzBgAAAAAAAAAAAAAAAAEcAgAA\",\"AAAAAUYFAAA=\"]ACg[\"AAAAA7oBAABhCgAA7hEAAAP5BgAAwwoAAEIRAAAABFoUAABbFAAAXBQAAF0UAAA6MAAAAQAAAAAADQAQAAAAYQliCWwJbQkXEBgQ8RDyEIATgROoE6kT/hP/EzswAAABAAAPAAcAPREBAO4SBQCxEwEAABQAAF4UAAC+FAEAzhQBAAABthQAAAGJFAAA\",\"AAAAA60BAAC4AQAAtRAAAASvEAAAsBAAALEQAACyEAAAAucBAADpAQAAAAACABQAAF4UAAAAAbYUAAABiRQAAAAAAAAAAAToAQAA6gEAAD8JAADxEQAA\"]Bk[\"AAAAAAEqBwAA\",\"AAAAAAAAAAGbEQAAAAG0EgAA\"]Ho[\"AAAABeUEAACSBQAA0QUAAN4MAAAIEwAAAsQPAACIFAAAAh0HAABEEwAAA8UAAACyEgAAGBQAAAFtBQAAAaQHAAABtBAAAA==\",\"AAAAAuUEAADJBgAAASsHAAAAAAABvQEAAAFvFAAA\"]Gc[\"AAAAAgkTAAAaFAAAAdAGAAAB/gYAAAErAQAAAsgQAABFEwAAAAEfBwAAAAGHBQAAAAGlBwAAAfoGAAAAAAAB1AUAAA==\",\"AAAAAAAAAAHKAQAA\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAXgAAAA=\",\"\"]Fg[\"AAAD/QYAAB4HAAAlEAAABG0MAADhEAAA4xEAAHAUAAAFHgIAAIYFAADSBQAApgcAAHAJAAAAAssAAAAFEAAA\",\"AAABOxEAAA==\"]Fg[\"AAAABG4MAADiEAAA5REAAHEUAAACqQcAAHEJAAAB/gYAAAFzAQAAAAABHwcAAAABhwUAAAAAAAAAAAHUBQAA\",\"AAABPBEAAA==\"]Dg[\"AAAAAUsFAAAAAVcAAAABfAEAAA==\",\"AAAAAUsFAAAAAAAAAAAAAAAAAAAAAAF7AAAA\"]Bk[\"AAAAAAEdAgAAAAAAAAFvBAAA\",\"AAAAAXcMAAA=\"]Cc[\"AAAAAAH/BwAAAAAAAAFwBAAA\",\"AAAAAAACeAwAAAoTAAA=\"]Dk[\"\",\"AAAAADswAAABAAATAAEAkAcTADswAAABAAATAAEAmwATADswAAABAAATAAEANBATAA==\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAXYAAAA=\",\"\"]Fg[\"AAAAAWUFAAAAAAGpAQAAAAAAAAAAAYIEAAAAAAABhAwAAAa1AAAAtQEAABUCAADpBgAAQAcAAKwSAAA=\",\"AAHfBwAAAAFlBQAA\"]Hk[\"AAAErwAAAGQCAAB2BQAAZAwAAAETBQAAAAIxAAAAoQAAAAE6EAAAAl4DAADuBgAAAhoBAAAbAQAA\",\"AAABmgIAAARpAAAAagAAAGsAAAATBQAAAcUBAAAAAUwMAAAB4AYAAA==\"]Kc[\"AAAEsAAAANMCAAB3BQAAZQwAAAFVBQAAAARfAAAArgAAAE0QAABSEAAAA44BAACPAQAARxAAAAJrAwAA9QYAAA==\",\"AAABIgMAAAFVBQAAAdYBAAABUAwAAANkBAAA0wUAAOcGAAAAAAAAAAAAAAAAAAR2AAAAdwAAAHgAAAB5AAAA\"]Ak[\"AAAAAAJjEAAAZBAAAA==\",\"\"]Ao[\"AAAAAAAAAAACtgEAAO4SAAA=\",\"\"]Ao[\"AAAAAAAAAAACtwEAAPESAAA=\",\"\"]Dc[\"AAAAAAAAAAABHAEAAAAAAAAAAAAAAAWzAAAAjwQAADgHAAAPEAAA6xAAAA==\",\"\"]Fc[\"AAAAAAAAAZABAAAAAAAAAAAAAAAAAAW0AAAAkAQAADkHAAAQEAAA7BAAAA==\",\"AAAAAAAAAAAAAR8HAAAAAocFAABVEQAA\"]Fc[\"AAAAAWYFAAAAAAGqAQAAAocEAAAMDAAAAAAAAAGFDAAAAAAAAAAFtgAAADoHAAA8CQAAERAAAO0QAAA=\",\"AAAAAWYFAAA=\"]Hg[\"AAABlQAAAAH3BAAAAAIeAAAAIQ0AAAAAAfAAAAAAAAAAAAAAAAADsAEAANgEAABcDAAA\",\"AAG1BwAAAAH3BAAAAAAAAAAAAAAAAAAAAAAEQQcAAEIHAABFBwAARgcAAA==\"]Hg[\"AAAAApgAAAAoBQAAAAI+AAAAJA0AAAE9AQAAAAAAAAAAAAAAAAADsQEAANsEAABeDAAA\",\"AAHMBwAAAAEoBQAAAAAAAAAAAAAAAAAAAAAEQwcAAEQHAABHBwAASAcAAA==\"]Bc[\"AAAAAAAAAAAAAAAAAAAAAAGSEwAA\",\"\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAXcAAAA=\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAm0AAAAXBwAA\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAnIAAAAcBwAA\",\"\"]Bg[\"AAAAAAAAAAAAAXQEAAA=\",\"AAAAASANAAA=\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAbIAAAA=\",\"\"]Hc[\"AAAAAAABCBAAAAE6FAAAAAFiFAAAAaQUAAAAAAAAAAAAADowAAABAAAAAAANABAAAAAFBzsJfgmrDhYQGRBVEC8RPxHdERATEhPxEyYU\",\"AAAAAAAB/hIAAAHmEwAA\"]Ak[\"AAAAAAJAFAAAQRQAAA==\",\"\"]Cg[\"AAAAAUMTAAABdQkAAAGBBAAAAAABhgQAAA==\",\"AAAAAAFdDAAA\"]Ag[\"\",\"AAAAAAAAAegGAAA=\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAm4AAADODwAA\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAnMAAADQDwAA\",\"\"]Ag[\"AAAAAAAAAAH2BgAA\",\"\"]Dk[\"AAAAAAAAOzAAAAEAAKkAAwC5ACkAKgFwAJ0BDgAAOzAAAAEAAEgAAgDjAEYAmwEBAA==\",\"\"]Hg[\"AAAAAlYFAAAhBwAAAXIJAAAEYAAAAK8TAACwEwAAGxQAAAORAQAAkgEAAKcUAAABbAMAAAABeQwAAAABCxMAAAAAAfoGAAA=\",\"AAABEhAAAAFWBQAAAdcBAAAAAd0QAAA=\"]Dk[\"AAAAAQkFAAAAASgAAAAAAewGAAACCAEAAAkBAAA=\",\"AAAAAgkFAABoCgAAAAAAAd4GAAA=\"]Dk[\"AAAAAUAFAAAAAU4AAAACYgEAAGMBAAAB8QYAAA==\",\"AAAAAUAFAAAByQoAAAAB4wYAAA==\"]Ck[\"AAAAAU4QAAAAAAAAAqgHAADsBwAA\",\"AAAAAAAAAAACuRAAALoQAAA=\"]Ck[\"AAAAAU8QAAAAAAAAAqsHAADuBwAA\",\"AAAAAAAAAAACuxAAALwQAAA=\"]Bk[\"\",\"AAAAAAAAAAAAAAAAAAAAAAAAAjgRAAC6EgAA\"]Ek[\"AAAAAw0IAACqEgAAFhQAAAAAAAAAAAHrBwAAAAABpwcAAA==\",\"AAAAAAAAAALGEQAAxxEAAAK5EAAAuhAAAA==\"]Ek[\"AAAAAw4IAACrEgAAFxQAAAAAAAAAAAHtBwAAAAABqgcAAA==\",\"AAAAAAAAAALIEQAAyREAAAK7EAAAvBAAAA==\"]Cg[\"AAAAAf4EAAAAASQAAAAAAAL5AAAA+gAAAA==\",\"AAAAAf4EAAA=\"]Dg[\"AAAAATQFAAAAAUoAAAACUAEAAFEBAAA=\",\"AAAAATQFAAAAAAAAAAF5DAAAAAELEwAA\"]Bo[\"AAAAAfQEAAAAARsAAAAAAAHrAAAA\",\"AAAAAfQEAAA=\"]Cc[\"AAAB8QIAAAEkBQAAAAE6AAAAATcBAAA=\",\"AAAAASQFAAA=\"]Ig[\"AAABlwAAAAH5BAAAAAIgAAAAIw0AAAAAAfIAAAAAAAAAAAAAAAA6MAAAAQAAAAAADQAQAAAA6AEaAkEHRQdDCUcJxQ8KENQQOhOzE8MT4ROTFA==\",\"AAG3BwAAAAL5BAAAkwUAAAABzA8AAA==\"]Ic[\"AAAAApoAAAAqBQAAAAJAAAAAJg0AAAE/AQAAAAAAAAAAAAAAAAA6MAAAAQAAAAAADQAQAAAA6gEbAkMHRwdFCUkJxw8MENYQOxO0E8QT4hOUFA==\",\"AAHOBwAAAAEqBQAAAAABzw8AAA==\"]Gg[\"AAACSwIAAFMCAAAB6wQAAAACEQAAADEHAAAC1QAAANYAAAA=\",\"AAACTQIAAE4CAAAB6wQAAAAAAAAC2A8AANkPAAAAAAJ9BAAAShAAAAAAAYMMAAA=\"]Do[\"AAAAASwFAAAAAkEAAAClAAAAA0EBAABCAQAAPhAAAAFiAwAA\",\"AAAAASwFAAAAAAAAAWEEAAA=\"]Ck[\"AAAAAh4UAAAfFAAAAAAAAAAAAAABhQwAAA==\",\"AAAAAAABNREAAA==\"]Co[\"AAAAAAABHQcAAAO7EgAAvBIAAL0SAAAAAb0BAAA=\",\"AAAAAAABCBAAAA==\"]KAe[\"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\",1]Eo[\"AAAAAAA7MAAAAQAABQABAPsSBQA7MAAAAQAABQABAOMTBQAAOzAAAAEAAAUAAQBfFAUAOzAAAAEAAAUAAQChFAUA\",\"\"]Ck[\"AAAAAAAAAYMFAAAAAAFtBAAA\",\"AAAAAAAAA3EMAAByDAAAcwwAAA==\"]Dg[\"AAAAARUFAAAAAAHPBAAAAa4RAAABHgEAAA==\",\"AAHCBwAAAAEVBQAAAAAAAa4RAAA=\"]Dc[\"AAAAAV8FAAAAAAKgAQAA0wQAAAG1EQAA\",\"AAHbBwAAAAFfBQAAAAAAAbURAAA=\"]Fc[\"AAAAAfYEAAAAAR0AAAAAAALuAAAA7wAAAA==\",\"AAG0BwAAAAH2BAAAAARpBAAAOwcAAOQRAABEFAAAAAACABQAAF4UAAA=\"]Fc[\"AAAAAScFAAAAAT0AAAACOwEAADwBAAA=\",\"AAHLBwAAAAEnBQAAAAACSRAAAN0QAAACfAwAACEUAAAAAAG2FAAAAYkUAAA=\"]Eo[\"AAAAAeERAAABbgkAAAACvwAAAGYMAAABoxEAAAABcQQAAA==\",\"AAAAAAHSBQAAAAJmDAAABRAAAAABpAcAAAG0EAAA\"]Dk[\"AAAABrMSAAAOEwAAhRQAAJEUAACzFAAAxBQAAAAAAAAB6RMAAA==\",\"AAAAAAAAAAGbEQAA\"]A@g[\"AAAAOjAAAAEAAAAAABIAEAAAAIoE4QSRBcwG/gd5CUgMdwyUDCANSBBfEFgR4hH5EuwTIBRDFIwUOjAAAAEAAAAAAAcAEAAAAG8JsxC2Ej4TiBONE8cUyxQ6MAAAAQAAAAAABwAQAAAA5wFnBHgMzA9PEaQRChOuEzowAAABAAAAAAAHABAAAADAAFkR9RKAFKsUtBTSFNQUAWwFAAAAAAAAAAAAATsUAAA=\",\"AAAAAeEEAAAB/ggAAAABZgwAAA==\"]ACk[\"AAAAOjAAAAEAAAAAAAgAEAAAAIsEXwlqDMEPCxAsEb0RWBRZFDowAAABAAAAAAAHABAAAABrBH8FlQUMCF0MnBH6EkIUOjAAAAEAAAAAAAoAEAAAAOkBewRNEFIQxxDlEKIRtxKHE44T9BM6MAAAAQAAAAAABwAQAAAAKgHPD0kQ3RD2EpIU0xTVFAVvBQAAfAwAACEUAACBFAAAtRQAAAS1EgAAGRQAAHIUAACsFAAAAnkMAACmDgAAAbYUAAADVREAAAsTAACJFAAA\",\"AAAAAAAAAAAAAAAAAAGlBwAAAAAAAAHUBQAA\"]Io[\"AAAAA0wHAADxBwAArBEAAANjCQAAUwwAAK0UAAABQxEAAAFaFAAAA/wHAADKEQAAzhEAAAABLRAAAAG1EwAAAQgHAAA=\",\"AAAAAARTDAAAVAwAAFUMAABWDAAAAAXOBAAAzwQAANAEAADRBAAA9RIAAA==\"]Io[\"AAAAA1AHAAD1BwAAsxEAAANnCQAAVwwAALAUAAABRxEAAAFcFAAAA/0HAADMEQAA0BEAAAABLxAAAAG5EwAAAQwHAAA=\",\"AAAAAARXDAAAWAwAAFkMAABaDAAAAAXSBAAA0wQAANQEAADVBAAA9hIAAA==\"]No[\"AAABlgAAAAH4BAAAAAIfAAAAIg0AAAAAAfEAAAAAAAAAAAAAAAA6MAAAAQAAAAAAJAAQAAAAEQJqBNUGPgc9CXMJdAx6DIYMBhAoELYQ4xDoEPUQLRE5EUARSxFNEakR0hHmEa0SrxL3EgQTPxNBE4QTxRPHE/wTAxQiFEUUhhQ=\",\"AAG2BwAAAAH4BAAAAAAAAAAAAAAAAAAAAAAEQwkAAEQJAABHCQAASAkAAA==\"]No[\"AAAAApkAAAApBQAAAAI/AAAAJQ0AAAE+AQAAAAAAAAAAAAAAAAA6MAAAAQAAAAAAJAAQAAAAEgJsBNkGPwc+CXQJdQx7DIcMBxApELcQ5BDpEPYQLhE6EUERTBFOEaoR0xHnEa4SsBL4EgUTQBNCE4UTxhPIE/0TBBQjFEYUhxQ=\",\"AAHNBwAAAAEpBQAAAAAAAAAAAAAAAAAAAAAERQkAAEYJAABJCQAASgkAAA==\"]Cc[\"AAAAARAFAAAAAAAAARUBAAA=\",\"AAAAARAFAAAAAAENDAAA\"]Ck[\"AAAAAVEFAAAAAAGIAQAA\",\"AAAAAVEFAAAAAAMODAAADwwAABAMAAA=\"]Bk[\"AAAAAAAAAAAAAAAAAAGCBAAAAAAAAYQMAAA=\",\"\"]Bg[\"\",\"AAAAAAAAAAAAAAAAAAAAAAAAAb0PAAA=\"]Eo[\"AAAAAUEFAAAAAVAAAAACZgEAAGcBAAAB8gYAAA==\",\"AAAAAUEFAAAAAAHkBgAAAAAAAAAAAAAAAAACdQUAAL4PAAA=\"]Bk[\"\",\"AAAAAAAAAAAAAAAAAAAAAAAAAsIUAADDFAAA\"]Ck[\"AAAAAAAAAAABchQAAA==\",\"AAAAAAAAAAAAAAAAAAAAAAAB2RQAAA==\"]Ag[\"AAACrAIAADMDAAA=\",\"\"]Bo[\"AAAAAAAAAckAAAAAAAABfwwAAA==\",\"AAAAAUgQAAA=\"]Fc[\"AAAAAfMEAAAAAxoAAAB4DAAAChMAAAHbDwAAAAHqAAAA\",\"AAGzBwAAAAHzBAAAAAE7BwAAAAAAAAAAAAAAAAAAATwHAAA=\"]Gc[\"AAAAASMFAAAAATkAAAABNgEAAAABtRIAAAF5DAAAAAELEwAA\",\"AAHKBwAAAAEjBQAAAAGHEwAAAUkQAAAAAbUSAAAAAAAAAAAAAAABPQcAAA==\"]Do[\"AAAAAAAAAAHcDwAAApcFAAANEAAA\",\"AAAAAb4BAAAAAAAB3A8AAASXBQAADRAAAJ4RAACfEQAA\"]Do[\"AAAAAAAAAAHdDwAAApgFAAAOEAAA\",\"AAAAAckBAAAAAAAB3Q8AAASYBQAADhAAAKARAAChEQAA\"]Ao[\"AAAAAAG4EAAAAAAB3hAAAA==\",\"\"]Fk[\"AAABuhEAAAJpAAAA7wcAAAEhAgAAAAHRAAAAApMEAADeEQAAAtgPAABLEAAAAAHaDwAAAn0EAABKEAAAAAGBDAAAAoIMAACDDAAA\",\"\"]Cc[\"AAAAAAAAAAN+BAAAlQQAAOARAAA=\",\"AAAAAAABTRAAAA==\"]Ec[\"AAABTQIAAAHsBAAAAAISAAAAMgcAAALXAAAA2AAAAA==\",\"AAABUAIAAAHsBAAAAAAAAt4RAADfEQAA\"]Bo[\"AAABOxEAAAAAAALMAAAAzQAAAA==\",\"AAAAAAGGBQAA\"]Ck[\"AAABPBEAAAAAAAJ3AQAAeAEAAA==\",\"AAAAAAAAAAAAAAABhwUAAA==\"]Ao[\"AAAAAAAAAcITAAAByQ8AAA==\",\"\"]Hg[\"AAAAAmsAAAAHCAAAASICAAABegkAAAPdAAAA9wYAAPMQAAAChAUAAGUQAAAAAp0FAADfDAAABtEPAADSDwAA0w8AAP0QAABUEQAAHBMAAAAAAtQPAADVDwAAAdYPAAA=\",\"\"]Dk[\"AAAAAAABewkAAAL4BgAACAgAAAKFBQAA9BAAAAABngUAAAH+EAAA\",\"AAAAAAABUhAAAA==\"]Bc[\"AAAAAAAAAAAAAAAAAAAAAAGSEwAA\",\"\"]AAo[\"AAABEhAAAAHJBgAAAtcBAAArBwAABMgGAAB4DAAACBAAAAoTAAACgBQAALQUAAADmxEAAIEUAAC1FAAABaQHAACeEQAAnxEAAKARAAChEQAAAbQQAAAAAAABpQcAAA==\",\"AAAABSEHAADhEQAA4hEAAOMRAADlEQAAA6wBAADDDwAAxA8AAAHkEQAABasUAADSFAAA0xQAANQUAADVFAAAASEUAAABrBQAAAG0EgAAAAAAAAAAAALhDwAA+w8AADswAAABAAAFAAIAEwICAA8QAgA=\"]Cc[\"AAAAAUIFAAAAAVEAAAACaAEAAGkBAAA=\",\"AAAAAUIFAAA=\"]Dc[\"AAAAAAAAAAA7MAAAAQAABQABAO4SBQA=\",\"AAAABOABAADkAQAA5QEAAOYBAAA=\"]Ao[\"AAAAAeABAAAAAAAAAVgQAAA=\",\"\"]Ao[\"AAAAAeUBAAAAAAAAAVkQAAA=\",\"\"]Eo[\"AAAAAbwRAAABqxMAAAPLEAAAUREAAKYRAAABBxQAAAHHEQAAAboQAAAC1xEAAL8SAAABwREAAAAAAAABPRQAAA==\",\"\"]Eo[\"AAAAAb8RAAABrRMAAAPPEAAAUxEAAKgRAAABChQAAAHJEQAAAbwQAAAC2REAAMESAAABwxEAAAAAAAABPxQAAA==\",\"\"]Bc[\"AAAAAAAAApYEAAD/BgAAAAFaEAAA\",\"\"]Bc[\"AAAAAAAAApkEAAACBwAAAAFbEAAA\",\"\"]Ec[\"AAAAAdoBAAAAAg8TAAAREwAAAgEUAAACFAAAAocEAAAMDAAAAAAAAAABggQAAA==\",\"AAAAAAFJAwAA\"]Gg[\"AAAAAe4EAAAAAhQAAAA0BwAAAt4AAADfAAAA\",\"AAADVwIAAFgCAABZAgAAAe4EAAAAAAABZRAAAAACnQUAAN8MAAAC/RAAABwTAAAAAAAB1g8AAA==\"]Eg[\"AAAAAS8FAAAAAkQAAACnAAAAA0YBAABHAQAAQBAAAAFkAwAA\",\"AAAAAS8FAAAAAAAAAAGeBQAAAf4QAAA=\"]Ec[\"AAAAAAAAAXYBAAAAAAAAAAAAAAAAAALdBAAAfwkAAA==\",\"AAAAAAAAAAAAAAAAAAAAAAAAAdQFAAA=\"]Ec[\"AAABUQwAAAHgBAAAAAELAAAAAbsAAAA=\",\"AAAAAeAEAAAAAVEJAAAAAAAAAAAAAAAAAAABxgYAAA==\"]Dg[\"AAAAAAGBBQAAAAAAAAAAAAAAAAAAAAL4EAAAnREAAA==\",\"AAGuBwAAAAAAAcgGAAA=\"]Ac[\"AAABewIAAA==\",\"\"]Do[\"AAAAAQgFAAAAAScAAAAAAAIGAQAABwEAAA==\",\"AAACiQIAAIoCAAABCAUAAAAAAAAAAe8TAAA=\"]Dk[\"AAAAAT8FAAAAAU0AAAACYAEAAGEBAAA=\",\"AAAC/AIAAP0CAAABPwUAAAAAAAAAAfATAAA=\"]Bk[\"\",\"AAAAAAAAAAAAAAAAAAAAAAAAAlYJAABYCQAA\"]Dk[\"AAAAAAAAAAABJAEAAA==\",\"AAABpwIAAAAAAAAAAAABgwQAAAFKBwAAAWMEAAABpwcAAA==\"]Dg[\"AAAAAAAAAZkBAAA=\",\"AAABLQMAAAAAAAAAAAABhAQAAAFLBwAAAWUEAAABqgcAAA==\"]Dk[\"AAAAARcFAAAAAcYBAAAB0AQAAAGwEQAAASABAAA=\",\"AAHDBwAAAAEXBQAAAAAAAbARAAA=\"]Dk[\"AAAAAWEFAAAAAdgBAAACogEAANQEAAABtxEAAA==\",\"AAHcBwAAAAFhBQAAAAAAAbcRAAA=\"]Ck[\"AAAAATAFAAAAAUUAAAACSAEAAEkBAAA=\",\"AAAAATAFAAAAAAH4BgAA\"]Eg[\"AAAAAQMFAAAAAiYAAACdAAAAATYQAAABWgMAAAIBAQAAAhMAAA==\",\"AAG+BwAAAYICAAACAwUAAGEKAAA=\"]Eg[\"AAAAAToFAAAAAkwAAACoAAAAAlsBAABBEAAAAWUDAAABAxMAAA==\",\"AAHWBwAAAfYCAAABOgUAAAHDCgAA\"]Ao[\"AAAAAAAAAAAC5hAAAJ4RAAA=\",\"\"]Ao[\"AAAAAAAAAAAC5xAAAKARAAA=\",\"\"]Do[\"AAABegAAAAHfBAAAAAABugAAAA==\",\"AAAAAd8EAAAAAVAJAAAAAAAAAAAAAAAAAAAB6hAAAA==\"]Bg[\"AAAAAAAABNwAAACLBQAAcwwAAL0SAAA=\",\"\"]Ak[\"AAAAAAABRgAAAAFKAQAA\",\"\"]Do[\"AAAAAAAAAAAAAAAAAAAAAAAAAjgRAAC6EgAA\",\"AAGsBwAAAAAAAAAAAAAAAAAAAAAAAAH3EAAA\"]Cg[\"AAAAAAP5BgAAeAkAAEIRAAA=\",\"AAAAAAAAAALJDwAA3hAAAA==\"]Ag[\"AAACpwIAAC0DAAA=\",\"\"]Cc[\"AAAAAYYKAAAChQoAAPUKAAAB9woAAAL5EAAA+xAAAA==\",\"\"]Ac[\"\",\"AAAAAfQSAAA=\"]Gg[\"AAAAAQcFAAABiBQAAAAAAQAJAAAEBQEAAFoQAAAVEwAAbRQAAAG0EAAA\",\"AAAAA8IBAAAHBQAAlRQAAAABSRQAAAABAAkAAANaEAAAFRMAAG0UAAA=\"]Bc[\"AAAAAcIBAAAAAAAAAAAAAV4RAAA=\",\"\"]Gg[\"AAAAAT4FAAAAAAFfAQAAAQEJAAADWxAAABcTAABuFAAAAAAAAAGlBwAA\",\"AAAAA9ABAAA+BQAAlhQAAAABShQAAAABAQkAAANbEAAAFxMAAG4UAAA=\"]Bc[\"AAAAAdABAAAAAAAAAAAAAWIRAAA=\",\"\"]Fg[\"AAABVwIAAAH8BAAAAAEiAAAAAAAC9QAAAPYAAAA=\",\"AAG6BwAAAAH8BAAAAAF6CQAAAvcGAADzEAAAA5MEAACUBAAAhAUAAA==\"]Eo[\"AAAAATIFAAAAAUgAAAACTAEAAE0BAAA=\",\"AAHSBwAAAAEyBQAAAAF7CQAAAQgIAAAElQQAAIUFAAD0EAAA4BEAAA==\"]Ao[\"\",\"AAAAAAAAAAAAAAABZhEAAA==\"]Ek[\"AAAAAQwFAAAAASsAAAAAAAIOAQAADwEAAA==\",\"AAABkAIAAAEMBQAAAAAAAAGkBwAAAbQQAAAAAl4RAABgEQAA\"]Eg[\"AAAAAUgFAAAAAVYAAAACdAEAAHUBAAA=\",\"AAABCgMAAAFIBQAAAAAAAAAAAAJiEQAAZBEAAAABpQcAAA==\"]Ao[\"AAAAAAAAAAHREAAAAAGPDAAA\",\"\"]Jk[\"AAAAAQ0FAAAABSwAAACeAAAAcQUAAMkQAADLEAAAAg0MAAA3EAAABFsDAABfDAAAyhAAAMwQAAAEEAEAABEBAACeEQAAnxEAAAKLDAAAjAwAAALAEQAAwREAAA==\",\"AAAAAg0FAADhEAAAAsMBAADCEAAAAAABSwkAAA==\"]Og[\"AAAAAU0FAAAABVkAAACrAAAAcgUAAM0QAADPEAAABoEBAACCAQAADgwAAA8MAAAQDAAARBAAAARoAwAAYAwAAM4QAADQEAAAAqARAAChEQAAAo0MAACODAAAAsIRAADDEQAA\",\"AAAABE0FAADjBwAA5AcAAOIQAAAE0gEAAMMQAADEEAAAxRAAAAAAAUwJAAACfAUAAH0FAAAAAAAAAAAAAAA7MAAAAQAACgACAOUHBQACCAQA\"]Cc[\"AAAAAUMFAAAAAVIAAAACagEAAGsBAAA=\",\"AAAAAUMFAAA=\"]Ck[\"AAABeAUAAAEOBQAAAAEtAAAAAAABEgEAAA==\",\"AAAAAg4FAABhDAAA\"]Dg[\"AAABeQUAAAFOBQAAAAFaAAAAAYMBAAAAAAAAAAAAAfoGAAA=\",\"AAAAAk4FAABiDAAA\"]Cc[\"AAAAAWcFAAAAAWYAAAABqwEAAA==\",\"AAAAAmcFAABjDAAA\"]Dg[\"AAAAAAAAAAAAAAABVREAAA==\",\"AAAABsYQAAABEwAA7RMAAO4TAAAeFAAAHxQAAA==\"]Ak[\"AAAAAAAAAAAB8BIAAA==\",\"\"]Ak[\"AAAAAAAAAAAB8xIAAA==\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAnAAAAATAgAA\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAnUAAAAUAgAA\",\"\"]Bg[\"AAAAAAAAAAAAAXYEAAA=\",\"AAAAAVgRAAA=\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAm8AAAA8BwAA\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAnQAAAA9BwAA\",\"\"]Bo[\"AAAAAAAAAAAAAAAECAcAAAkHAAAKBwAACwcAAA==\",\"\"]Ec[\"AAAABK0BAAC4AQAAIgUAALUQAAAAAAE1AQAAAAAAAAQMBwAADQcAAA4HAAAPBwAA\",\"AAAAASIFAAA=\"]Bg[\"AAAAAAAAAAAAAXcEAAA=\",\"AAAAAAGIEwAA\"]Cc[\"AAAAAYkKAAACigoAAPsKAAAB+QoAAAL6EAAA/BAAAA==\",\"\"]Ag[\"AAAAAAABLQcAAA==\",\"\"]No[\"AAAAAQUFAAABKwcAAAFEEwAAAQUQAAAGbwwAANgQAADqEwAA/hMAAGUUAACNFAAABwMBAAC9AQAA2AwAAFgQAAAUEwAA9RMAAPcTAAABbxQAAAAAAAAAAAAAA9YGAADcDAAAGBMAAA==\",\"AAAAAgUFAABhEAAAAAHBAQAAARoTAAAGbwwAANgQAADqEwAA/hMAAGUUAACNFAAABdgMAABYEAAAFBMAAPUTAAD3EwAA\"]Bo[\"AAAAAWEQAAAAAcEBAAAAAsoQAABfEQAAAAGLDAAA\",\"\"]Nc[\"AAAAATwFAAAAAAFdAQAAOjAAAAEAAAAAAAcAEAAAAMoBcAzZEEUT6xP/E2YUjhQF2QwAAFkQAAAWEwAA9hMAAPgTAAAAAAAAAAAAAAAE1AUAANoGAADdDAAAGRMAAA==\",\"AAAAAjwFAABiEAAAAAHPAQAAARsTAAAGcAwAANkQAADrEwAA/xMAAGYUAACOFAAABdkMAABZEAAAFhMAAPYTAAD4EwAA\"]Bo[\"AAAAAWIQAAAAAc8BAAAAAs4QAABjEQAAAAGNDAAA\",\"\"]Ao[\"AAAAAAAAAAAC9RMAAPYTAAA=\",\"\"]Ao[\"AAAAAAAAAAAC9xMAAPgTAAA=\",\"\"]Dc[\"AAABWAIAAAHvBAAAAAIVAAAANQcAAALgAAAA4QAAAA==\",\"AAABXAIAAAHvBAAA\"]Bk[\"AAAAAeMBAAAAAATbAAAAigUAAHIMAAC8EgAA\",\"\"]Ec[\"AAAAAqUTAACmEwAAAqMTAACkEwAABE0MAABODAAATwwAAFAMAAAESQwAAEoMAABLDAAATAwAAA==\",\"\"]Cc[\"AAAAAAAAAAAB7xIAAA==\",\"AAGvBwAAAAK8AQAAghMAAA==\"]Cc[\"AAAAAAAAAAAB8hIAAA==\",\"AAHGBwAAAALIAQAAgxMAAA==\"]Bc[\"\",\"AAAAAAAAAAAAAAAAAAAB+gYAAA==\"]Cg[\"AAAAAcIPAAAAAAGAAQAAAcgQAAA=\",\"AAHiBwAAAAAAAfQTAAA=\"]Ek[\"AAAAAeYEAAAAAAHGAAAAAAAAAAAAAAAAAAAEfhMAAIYTAACiEwAAvRMAAA==\",\"AAGwBwAAAALmBAAAkgUAAA==\"]Cc[\"AAAAAAAAAAAAAAAAAAAAAAAAA3wJAABWEAAAwhQAAA==\",\"\"]Cc[\"AAAAAAAAAAAAAAAAAAAAAAAAA30JAABXEAAAwxQAAA==\",\"\"]Bk[\"\",\"AAAAAAAAAAAAAAAAAAAAAAAAAlcJAABZCQAA\"]Ac[\"\",\"AAAAAAHKBAAA\"]Bc[\"AAAAAAABYgQAAA==\",\"AAAAAAHLBAAA\"]Ec[\"AAAAARkFAAAAAccBAAAB0QQAAAKyEQAAHBQAAAEiAQAA\",\"AAHEBwAAAAEZBQAAAAAAArIRAAAcFAAA\"]Ec[\"AAAAAWMFAAAAAdkBAAACpAEAANUEAAACuREAAB0UAAA=\",\"AAHdBwAAAAFjBQAAAAAAArkRAAAdFAAA\"]Ag[\"\",\"AAAAAAABsRIAAA==\"]Co[\"AAAAArIBAADjBwAAAAABeQEAAA==\",\"AAAAAAAAAAAAAAAAAAAAAdEBAAA=\"]Ac[\"\",\"AAAAAAFHEwAA\"]Cg[\"AAAAAAAAAhoTAAAbEwAAAAABtBIAAA==\",\"AAAAAAAAAAH5EwAA\"]Do[\"AAAAAS0FAAAAAkIAAACmAAAAA0MBAABEAQAAPxAAAAFjAwAA\",\"AAAAAS0FAAAAAAABfgQAAA==\"]Ak[\"AAAAAAACSRQAAEoUAAA=\",\"\"]Ac[\"AAAAAeQBAAA=\",\"\"]Ac[\"AAAAAeYBAAA=\",\"\"]Eo[\"AAACEgMAACgDAAABVwUAAAABYQAAAAKTAQAAlAEAAA==\",\"AAAAAVcFAAAAAAAF3AYAAN0GAADeBgAA3wYAAOAGAAA=\"]Fk[\"AAAABNoQAADbEAAA7RMAAO4TAAABww8AAAE1EQAAAbISAAAAAAFvFAAAAAAAAAAAAAGEDAAA\",\"AAAAAAAAA7sSAAC8EgAAvRIAAA==\"]Fc[\"AAAAAAAByAYAAAIaEwAAGxMAAAGbEQAAAAG0EgAAAAACRxQAAEgUAAAAAAABkhMAAA==\",\"AAAAAAAAAYQUAAAB+RMAAA==\"]Co[\"AAAAAf8EAAAAASUAAAAAAAL7AAAA/AAAAA==\",\"AAAAAf8EAAAAAW4FAAA=\"]Co[\"AAAAATUFAAAAAUsAAAACUgEAAFMBAAA=\",\"AAAAATUFAAAAAAAAAAGmDgAA\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAARsHAAA=\",\"\"]Ik[\"AAAAAgQFAABrFAAAAAAAAqgSAACAEwAABQIBAAC9AQAAiQwAADMRAACPEwAAA4AJAADvEwAAbxQAAA==\",\"AAG/BwAAAALAAQAABAUAAAAAAAKoEgAAgBMAAAOJDAAAMxEAAI8TAAACgAkAAO8TAAA=\"]Ig[\"AAAAAjsFAABsFAAAAAABXAEAAAPKAQAAqRIAAIETAAADigwAADQRAACQEwAAAoEJAADwEwAA\",\"AAHXBwAAAALOAQAAOwUAAAHQBgAAAAACqRIAAIETAAADigwAADQRAACQEwAAAoEJAADwEwAA\"]Ao[\"AAAAAAAAAc4AAAAAAAF5BAAA\",\"\"]Bc[\"AAAAAAABTwAAAAJkAQAAZQEAAA==\",\"\"]Ag[\"AAACtgIAADoDAAA=\",\"\"]Ak[\"AAAAAAAAApcEAAAABwAA\",\"\"]Ak[\"AAAAAAAAApoEAAADBwAA\",\"\"]Ag[\"AAACswIAADsDAAA=\",\"\"]Dk[\"AAAAAe0EAAAAAhMAAAAzBwAAAtkAAADaAAAA\",\"AAABUwIAAAHtBAAAAAAAAAAAAdoPAAA=\"]Dg[\"AAAAAeoEAAAAAhAAAAAwBwAAAtMAAADUAAAA\",\"AAABSwIAAAHqBAAAAAAAAAFLEAAA\"]Co[\"AAABuBIAAAExBQAAAAFHAAAAAUsBAAA=\",\"AAHRBwAAAUIJAAABMQUAAA==\"]Ec[\"AAAAAAABuRIAAAG8AAAAAAAAAAAAAAAAAAADjgQAAD8JAADeDwAA\",\"AAAAAboBAAAAAAAAAekTAAA=\"]Hk[\"AAAC7AIAAAQDAAABTwUAAAABWwAAAAKEAQAAhQEAAAAAAAAAAAAAAAAABBkCAAB1BQAA2wYAABoHAAA=\",\"AAABGAMAAALTAQAATwUAAAAAAgkUAAAKFAAAAAAC2BEAANkRAAA=\"]Fc[\"AAAAA8YQAADcEAAAARMAAAACDxMAABETAAADqAEAAAEUAAACFAAAAocEAAAMDAAAAAAAAAABggQAAA==\",\"AAAAAdoBAAA=\"]Dc[\"AAAAAmgAAADpBAAAAiACAAD/CAAAAQ8AAAAB0AAAAA==\",\"AAAAAukEAABgEAAA\"]Eg[\"AAAAAQoFAAAAASkAAAAAAAIKAQAACwEAAAAAAAAAAAAAAAMVBwAAtg8AAL0PAAA=\",\"AAABjQIAAAEKBQAA\"]Eg[\"AAAAAUQFAAAAAVMAAAACbAEAAG0BAAAAAAAAAAAAAAAAAAMYBwAAuA8AAL4PAAA=\",\"AAABBAMAAAFEBQAA\"]Io[\"AAAFPAIAAE4CAABQAgAAWQIAAFwCAAAB/QQAAAACIwAAADYHAAAAAAL3AAAA+AAAAA==\",\"AAADeQIAAHoCAAB7AgAAAv0EAADMBgAAAtIFAACzEAAAAa4TAAABBRAAAAGjEQAAAAAAAAAAAAAAAAEUBwAA\"]Ek[\"AAAAATMFAAAAAkkAAAA3BwAAAk4BAABPAQAA\",\"AAAB7AIAAAEzBQAAAAAAAAAAAAAAAAAAAAAC3QQAABsHAAA=\"]Bk[\"AAAAAAABxxAAAAF/AQAA\",\"AAAAAAAAAAHIEAAA\"]Hg[\"AAABlwAAAAH6BAAAAAEjDQAAAAAB8wAAAAAAAAAAAAAAAAZCBwAARgcAAEQJAABICQAAxg8AANUQAAA=\",\"AAG4BwAAAAL6BAAAkQUAAAAAAAAAAAAAAAAAAAAAAZQFAAA=\"]Fo[\"AAAAApoAAAArBQAAAAEmDQAAAUABAAAAAAAAAAAAAAAAAAZEBwAASAcAAEYJAABKCQAAyA8AANcQAAA=\",\"AAHPBwAAAAErBQAAAZUFAAA=\"]Ag[\"AAACrQIAADQDAAA=\",\"\"]Cg[\"AAAAAWkFAAAAAAADyQ8AADEQAADeEAAA\",\"AAHhBwAAAAFpBQAA\"]Dc[\"AAAAAkkFAADkBwAAAAABegEAAA==\",\"AAAAAUkFAAAAAAAAAAAAAAAAAAHlBgAA\"]Dg[\"AAAAAZMTAAABYxAAAAAAAAL9AAAAnxEAAA==\",\"AAFNCQAAAb8BAAABkxMAAAFjEAAA\"]Dg[\"AAAAAZQTAAABZBAAAAABVwEAAAABoREAAA==\",\"AAFOCQAAAc0BAAABlBMAAAFkEAAA\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAbsPAAA=\",\"\"]Ac[\"\",\"AAAAAAFzFAAA\"]Ag[\"AAACrwIAADYDAAA=\",\"\"]Bc[\"\",\"AAAAAAAAAAAD7hIAAO8SAADwEgAA\"]Bc[\"\",\"AAAAAAAAAAAD8RIAAPISAADzEgAA\"]Ak[\"\",\"AAAAARMTAAAAAAE6FAAA\"]ADg[\"AAAAAQAFAAAAAAA6MAAAAQAAAAAAFAAQAAAA+QcnDcoPFBAXEFMQWhHbEe8RDBN6EyQUghSPFJ0UnxTFFMkUzBTQFNcUOjAAAAEAAAAAAA0AEAAAAP4A5hBWEQITFBMVE7ET3xPyE6kUvBS+FMAUzhQAAAHUEQAA\",\"AAG7BwAAAAEABQAAAAAAOjAAAAEAAAAAABQAEAAAAPkHJw3KDxQQFxBTEFoR2xHvEQwTehMkFIIUjxSdFJ8UxRTJFMwU0BTXFDowAAABAAAAAAAKABAAAADmEFYRAhOxE98T8hOpFLwUvhTAFM4UAAAB1BEAAA==\"]ADk[\"AAAAATcFAAAAAAFYAQAAOjAAAAEAAAAAABQAEAAAAPoHKA3LDxUQGBBUEFsR3BHwEQ0TexMlFIMUkBSeFKAUxhTKFM0U0RTYFDowAAABAAAAAAAMABAAAADnEFcRAxMWExcTshPgE/MTqhS9FL8UwRTPFAAAAdURAAA=\",\"AAHTBwAAAAE3BQAAAAAAOjAAAAEAAAAAABQAEAAAAPoHKA3LDxUQGBBUEFsR3BHwEQ0TexMlFIMUkBSeFKAUxhTKFM0U0RTYFDowAAABAAAAAAAKABAAAADnEFcRAxOyE+AT8xOqFL0UvxTBFM8UAAAB1REAAA==\"]Ag[\"AAACiQIAAPwCAAA=\",\"\"]Bk[\"AAAAAAAAAAAAAW4EAAA=\",\"AAAAAAAAAWYMAAA=\"]Bk[\"AAAAAAAAAAAAAAF+DAAA\",\"AAAAAAABRBQAAA==\"]Do[\"AAAAAQsFAAAAASoAAAAAAAIMAQAADQEAAAAAAAAAAAAAAAIWBwAAtw8AAA==\",\"AAAAAQsFAAA=\"]Do[\"AAAAAUUFAAAAAVQAAAACbgEAAG8BAAAAAAAAAAAAAAAAAAIZBwAAuQ8AAA==\",\"AAAAAUUFAAA=\"]Ag[\"AAACsQIAADwDAAA=\",\"\"]Fc[\"AAAAAUcFAAAAAlUAAACpAAAAAnIBAABCEAAAAmYDAADIEAAA\",\"AAAAAUcFAAAAAAAAAAAAAAABpQcAAAAAAAAB1AUAAA==\"]Ao[\"AAAAAAHKBAAA\",\"AAAAAAGmBwAA\"]Ao[\"AAAAAAHLBAAA\",\"AAAAAAGpBwAA\"]Ag[\"AAACtQIAADkDAAA=\",\"\"]Ag[\"AAACsgIAADIDAAA=\",\"\"]Dg[\"AAAAARsFAAAAAAABiRMAAAAAAAFgEQAA\",\"AAAAAhsFAACGCgAAAYUKAAAAAfkQAAA=\"]Do[\"AAAB2wEAAAFbBQAAAAAAAYsTAAAAAAABZBEAAA==\",\"AAAAAVsFAAAB9QoAAAH3CgAAAfsQAAA=\"]Do[\"AAAAAR0FAAAAAAADzBAAAGERAACKEwAAAAGMDAAA\",\"AAAAAh0FAACJCgAAAYoKAAAAAfoQAAA=\"]Eg[\"AAAB3AEAAAFdBQAAAAAAA9AQAABlEQAAjBMAAAABjgwAAA==\",\"AAAAAV0FAAAB+woAAAH5CgAAAfwQAAA=\"]Cc[\"AAAAAAAAApgEAAABBwAAAAEWAQAA\",\"AAHBBwAAAAHEAQAA\"]Cc[\"AAAAAAAAA4oBAACbBAAABAcAAA==\",\"AAHaBwAAAAHVAQAA\"]Cc[\"AAAAAAAAAAAAAAAAAAAAAAAAAxMHAADqBwAABggAAA==\",\"\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAboPAAA=\",\"\"]Ao[\"AAAAAAAAAc4EAAAAAbwUAAA=\",\"\"]Bk[\"AAAAAAAAAdIEAAAAAb0UAAA=\",\"AAAAAdYEAAA=\"]Eg[\"AAAAAAAAAAAAAAAAAAAAAAAABIIJAAAyEAAA7hAAADwTAAA=\",\"AAHFBwAAAATxBwAA8gcAAPMHAAD0BwAA\"]Eg[\"AAAAAAAAAAAAAAAAAAAAAAAABIMJAAAzEAAA7xAAAD0TAAA=\",\"AAHeBwAAAAT1BwAA9gcAAPcHAAD4BwAA\"]Bo[\"AAAAAAABwQEAAAAAAdgMAAAAAYMEAAAAAWMEAAA=\",\"\"]Bo[\"AAAAAAABzwEAAAAAAdkMAAAAAYQEAAAAAWUEAAA=\",\"\"]Ak[\"AAAAAAABrxMAAAEYFAAA\",\"\"]Ao[\"AAAAAAABsBMAAAAAARkUAAA=\",\"\"]Fo[\"AAAAAUwFAAAAAlgAAACqAAAAA30BAAB+AQAAQxAAAAFnAwAA\",\"AAABEgMAAAFMBQAAAAAAAAJ8BQAAfQUAAAAAAAAAAALRAQAA5QYAAA==\"]Bg[\"\",\"AAAAAAAAAAAAAAAAAAAAAAAAARcHAAA=\"]Dg[\"AAAAASUFAAAAATsAAAABOAEAAA==\",\"AAAAASUFAAAAAAAAAAAAAAAAAAAAAAEcBwAA\"]Dg[\"AAAAAeQEAAAAAgwAAAAvBwAAAsMAAADEAAAA\",\"AAABPAIAAAHkBAAAAAABWREAAA==\"]Do[\"AAAAASAFAAAAAjcAAACjAAAAAzEBAAAyAQAAPBAAAAFgAwAA\",\"AAAAASAFAAAAAAAAAYYEAAA=\"]Ek[\"AAAAAQEFAAAAAAAC+hMAAFEUAAAD/wAAAD0RAADfEwAA\",\"AAG8BwAAAAEBBQAAAAAAAvoTAABRFAAAAT0RAAA=\"]Ek[\"AAAAATgFAAAAAAFZAQAAAvsTAABSFAAAAj4RAADgEwAA\",\"AAHUBwAAAAE4BQAAAAAAAvsTAABSFAAAAT4RAAA=\"]Bo[\"AAAAAAAAAAABfAUAAAAAAAAAAALRAQAA5QYAAA==\",\"\"]Fg[\"AAAAAQIFAAAAAAAEYQkAAF0QAADEEQAAqBMAAAIAAQAA8hMAAA==\",\"AAG9BwAAAAECBQAAAAAABGEJAABdEAAAxBEAAKgTAAA=\"]Fg[\"AAAAATkFAAAAAAFaAQAABGIJAABeEAAAxREAAKkTAAAB8xMAAA==\",\"AAHVBwAAAAE5BQAAAAAABGIJAABeEAAAxREAAKkTAAA=\"]Ag[\"\",\"AAAAAAAAAYQUAAA=\"]Bc[\"\",\"AAAAAAAAAAAAAAAAAAAAAAGSEwAA\"]Co[\"AAAAAfUEAAAAARwAAAAAAALsAAAA7QAAAA==\",\"AAAAAfUEAAAAAAHbDwAA\"]Co[\"AAAAASYFAAAAATwAAAACOQEAADoBAAA=\",\"AAAAASYFAAAAAAAAAbUSAAA=\"]Fg[\"AAABaQIAAAEPBQAAAAEuAAAAAAHtBgAAAhMBAAAUAQAA\",\"AAAClAIAANQGAAABDwUAAAAAAAHfBgAAAAAAAAAAAAAAAAG2DwAA\"]Gc[\"AAAB0gIAAALTAQAAUAUAAAABXAAAAAKGAQAAhwEAAAH0BgAA\",\"AAACGgMAANgGAAABUAUAAAAAAeYGAAAAAAAAAAAAAAAAAAIZAgAAuA8AAA==\"]Cc[\"AAAAAAAAAAAAAAAAAAAAAAAAA+YHAAADCAAAAwkAAA==\",\"\"]Cg[\"AAABkAIAAAAAAAAAASUBAAA=\",\"AAAAAAAAAALKEAAAzBAAAA==\"]Cg[\"AAABCgMAAAAAAAGaAQAA\",\"AAAAAAAAAAPOEAAA0BAAANEQAAA=\"]Bo[\"AAAAAtYEAABoBQAAAUcTAAA=\",\"AAAAAmgFAADyEQAA\"]Bo[\"AAAAAAAAAcoAAAAAAAABgAwAAA==\",\"AAAAASAUAAA=\"]Io[\"AAAAAVgFAAAAAWIAAAABlQEAAAafBQAAXxEAAGERAABjEQAAZREAAGcRAAAAAAAFXhEAAGARAABiEQAAZBEAAGYRAAAAAAAAAAAB1hQAAA==\",\"AAAAAVgFAAAAAAQGFAAABxQAAAkUAAAKFAAAAAF/BAAA\"]Co[\"AAAAAmoAAADwBwAAAAAB0gAAAAKUBAAA3xEAAAHZDwAA\",\"AAABuhEAAA==\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAlYJAAC+EAAA\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAlgJAADAEAAA\",\"\"]Co[\"AAAAARwFAAAAAAAAAigBAAApAQAA\",\"AAAAARwFAAAAAAL5EAAA+hAAAA==\"]Dc[\"AAAAAVwFAAAAAAKdAQAAngEAAA==\",\"AAAAAlwFAAD0EgAAAAAC+xAAAPwQAAA=\"]Cc[\"AAAAAAGsAQAAAAG+AAAAAAAAAX0MAAA=\",\"AAAAAUMUAAA=\"]Ac[\"\",\"AAABUQwAAA==\"]Ec[\"AAABigIAAAHxBAAAAAEXAAAAAAAC5QAAAOYAAAA=\",\"AAAAAfEEAAAEVAkAAEgKAABKCgAASwoAAA==\"]Do[\"AAAB/QIAAAEfBQAAAAE1AAAAAi4BAAAvAQAA\",\"AAAAAR8FAAAEVQkAAJ8KAAChCgAAogoAAA==\"]Ak[\"\",\"AAAAAAACSRQAAEoUAAA=\"]Ck[\"AAAAAuMEAABrCQAAAf4IAAABfxMAAAHCAAAA\",\"AAAAAuMEAABfEAAA\"]Bg[\"AAAAAYUHAAACrxAAAMIQAAAAAQ0MAAA=\",\"\"]Bk[\"AAAAAYYHAAACsBAAAMMQAAAAAg4MAAAPDAAA\",\"\"]Bc[\"AAAAAAAAAAAAAAAAAkcUAABIFAAA\",\"\"]Fo[\"AAAAAQYFAAAAAAADiAQAAGwJAABlFAAAAgQBAAC2AQAA\",\"AAHABwAAAAEGBQAAAAAAAogEAABsCQAAAbYBAAAAAAAAAAAAAAABGBMAAA==\"]Fo[\"AAAAAT0FAAAAAAFeAQAAA4kEAABtCQAAZhQAAAG3AQAA\",\"AAHYBwAAAAE9BQAAAAAAAokEAABtCQAAAbcBAAAAAAAAAAAAAAABGRMAAA==\"]Ck[\"AAAAATYFAAAAAAJUAQAAVQEAAA==\",\"AAAAATYFAAAAAAAAAXIUAAA=\"]Ec[\"AAAAAUoFAAAAAAF7AQAAAAAAAAAAAAAAAAAFswEAAOUHAAACCAAAAgkAANoMAAA=\",\"AAAAAUoFAAA=\"]Dg[\"AAAAAAFzFAAAAAG9AAAAAAAAAAACRxQAAEgUAAAAAAABkhMAAA==\",\"AAABuwEAAA==\"]Ag[\"AAACqwIAADgDAAA=\",\"\"]Bc[\"\",\"AAAAAAAAAAAAAAAAAkcUAABIFAAA\"]Cc[\"AAAAAWoFAAAAAAAAAAAAAWYRAAA=\",\"AAAB2wEAAAFqBQAA\"]Ck[\"AAAAAWsFAAAAAAAC0RAAAGcRAAAAAY8MAAA=\",\"AAAB3AEAAAFrBQAA\"]Ag[\"AAACsAIAADcDAAA=\",\"\"]Dg[\"AAAAAAABMgAAAAAAASMBAAA=\",\"AAAAAAAAAAAAAAAAAAAAAzsUAAA8FAAAPRQAAA==\"]Ck[\"AAAAAAABZAAAAAGYAQAA\",\"AAAAAAAAAAAAAAAAAAAAAj4UAAA/FAAA\"]Ag[\"AAACtAIAADEDAAA=\",\"\"]Do[\"AAAAAYcHAAACsRAAAMQQAAAAARAMAAAB8RAAAA==\",\"AALMAQAAxwcAAAFPCQAAAAAAAAHxEAAA\"]Ec[\"AAAAAlIFAACIBwAAArIQAADFEAAAAAGJAQAAAfIQAAA=\",\"AAHZBwAAAdQBAAABUgUAAAAAAAHyEAAA\"]Bg[\"AAAAAAAAAAABfQUAAA==\",\"AAABywEAAA==\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAlcJAAC/EAAA\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAlkJAADBEAAA\",\"\"]") \ No newline at end of file diff --git a/pr/617/docs/search.index/type/c2fb7140b1bd.js b/pr/617/docs/search.index/type/c2fb7140b1bd.js new file mode 100644 index 000000000..eb759be30 --- /dev/null +++ b/pr/617/docs/search.index/type/c2fb7140b1bd.js @@ -0,0 +1 @@ +rd_("CDc[\"\",\"ADswAAABAAAFAAIAXAQEAKcUAAAC/QYAAB4HAAA6MAAAAQAAAAAACgAQAAAA6wHsAYIF3gyyEvgSDROEFJAUshTDFDowAAABAAAAAAAcABAAAAAdAh4CHwIgAiECIgIjAmsEgQWGBcsG+QYqB3gJQhG1EvkSjBM/FEAUhxSsFK0UrhSvFLAUsRTGFMoUOzAAAAEAADoBEgBpBAAAbgUAAIAFAAD+BgAAHQcAAC0HAACQDAMAIQ0FABgOjQDHEAAAQxEHAKIRAAAYEo0AthIAALgSAABDEwAAjRMAAK0TAAA6MAAAAQAAAAAAIQAQAAAAgwWJBYoFiwX3BvgG/wYABwEHAgcDBwQHDQwODA8MEAxmDNcP2w/zEFkRsRLBEwAUARQXFFkUWhRbFFwUfxSDFJEUsxQ7MAAAAQAAQwAVAFgDFACHBAAAkwQCAGwFAQBvBQAAhAUBAJ8FAADqBgwA/AcBAAwMAAB8DAAAMRAAAGUQAAD0EAAAmxEAAMoRBwBEEwAAiBMDAPgTAACAFAAAtBQAADswAAABAABgAA0A4wBGAJsBAQCGBAAAqAcAAKsHAADsBwAA7gcAALkQAwDtEgUA6BMAABgUAABeFAUAcRQAADswAAABAAANAAYAbQQAAN8MAACmDgAALRADALMSAACgFAUAOzAAAAEAABMACQCDBAEAjAQBAOsHAADtBwAA0Q8CANoPAABUEQAAGxMAALQTBwA7MAAAAQAACgADAAgHBwBKBwEAChMAAAGFDAAABKUHAACnBwAAqgcAAIEMAAAAOzAAAAEAAAUAAgDRAQAAOhQEAAAAAdQFAAA=\"]Bk[\"AAAAAAAAAAAEMxEAADQRAABsFAAAbRQAAA==\",\"\"]0Bg[\"\",\"AAAFWgkAAFsJAABcCQAAXQkAAF4JAAA=\"]AGg[\"AAAAAALDAQAA0gEAAAAAAmwFAABtBQAABKgHAACrBwAA7AcAAO4HAAA7MAAAAQAADQAFAJ0FAQCACQEAiwwEAN8MAADWEQMAOjAAAAEAAAAAAAsAEAAAAIwEjQTrB+0H2g/9EP4QwBHBEcIRwxEbEwR9BAAASgcAAEsHAABKEAAABGMEAABlBAAARhQAAEcUAAAFggQAAKcHAACqBwAA1A8AANUPAAADggwAAIMMAADWDwAAOzAAAAEAAAYAAwDRAQAA5QYAADoUBAABkRMAAAGEDAAA\",\"AAAAAfERAAAAAnEFAAByBQAAAAJfDAAAYAwAAAAFiwwAAIwMAACNDAAAjgwAAI8MAAAAAAJjBAAAZQQAAA==\"]ANg[\"AAAEZQIAANECAADUBgAA2AYAAANhDAAAYgwAAGMMAAACwwEAANIBAAA7MAAAAQAAjQABABgSjQAAOzAAAAEAAAcAAwBsBQEAiBMDANUUAQAEqAcAAKsHAADsBwAA7gcAADswAAABAAASAAcAnQUBAB8HAACACQEAiwwEAN8MAADWEQMAvRIDADowAAABAAAAAAAKABAAAACDBIQEjASNBOsH7QfaD8ARwRHCEcMRBX0EAACHBQAASgcAAEsHAABVEQAABGMEAABlBAAARhQAAEcUAAAEpwcAAKoHAADUDwAA1Q8AAAT6BgAAggwAAIMMAADWDwAABToUAAA7FAAAPBQAAD0UAAA+FAAAAZETAAAAAdQFAAA=\",\"AAAAAAACcQUAAHIFAAAAAl8MAABgDAAAAUsQAAAFiwwAAIwMAACNDAAAjgwAAI8MAAAAAAJjBAAAZQQAAAAAAAAAAxMHAACpEQAAqhEAAA==\"]Dc[\"\",\"AAAAADswAAABAAAHAAEAYwkHADswAAABAAAJAAIATxEEAKQRBAAAAaMRAAA=\"]ADi[\"AAAABsABAADCAQAAzgEAANABAABhEAAAYhAAAAKGCgAAiAoAADowAAABAAAAAAAJABAAAADBAcYBxwHPAdgB2QH1CvoKSBRJFDswAAABAAANAAQAzgQHAAUQAAD5EAMAFxQAAALKAQAARBMAADswAAABAAAXAAsAtgEBAL0BAACJDAEA2AwBAFgQAwDmEAEAMxEBABMTAwDoEwAAbBQBALsUAQABbhQAAAAAAAAAAAAAAdQFAAA=\",\"AAABuwEAAALWBAAAphIAAAAAAALJDwAA3hAAAAAAAAAAAAAAAAAE1gYAANoGAADcDAAA3QwAAA==\",1]ADe[\"AAAAAr4BAADJAQAAOzAAAAEAAAcAAQBjCQcAAASJBQAAigUAAIsFAACxEgAAAAABbhQAAA==\",\"AAAAADswAAABAAAQAAMAywYAAGMJBwBTDAcAOzAAAAEAAAsABAAtBwAANREAAE8RBACkEQQAOzAAAAEAAA8ABQDOBAcAcQwCALoSAgD0EgAAgxQAADowAAABAAAAAAAKABAAAABfEWERYxFlEWcRoxHGEccRyBHJEfgTBLkQAAC6EAAAuxAAALwQAAAAAAVeEQAAYBEAAGIRAABkEQAAZhEAAAAAAAAAAAPUBQAAzg8AANAPAAA=\",1]Ec[\"AAAAAAAAOzAAAAEAAAYAAgDhBgMA5gYCADswAAABAAAGAAIA3AYEAEsJAQADfwQAAHwFAAB9BQAA\",\"\"]Dg[\"AAAAAAAAAAABXxQAAAGhFAAAAAAAAAAAAAABjAcAAA==\",\"AAAAAAAB+xIAAAHjEwAA\"]Eo[\"AAAAARIFAAABxQEAAAIwAAAAoAAAAAE5EAAAAV0DAAACGAEAABkBAAA=\",\"AAABrwAAAAESBQAAAaMTAAAAAUsMAAA=\"]Ic[\"AAAAAVQFAAAD1gEAAL0TAABBFAAAAl4AAACtAAAABowBAACNAQAAZAQAANMFAABGEAAAkRQAAAFqAwAA\",\"AAABsAAAAAJUBQAApRMAAAABTwwAAAAAAAAAAAAAAAAAAAKxAAAAsgAAAA==\"]Ik[\"AAAAAt4EAAASEwAAASoHAAAByAYAAAG5AAAAAZsRAAABXhQAAAKzEgAAoBQAAAAAAAAAAAAAOjAAAAEAAAAAAAkAEAAAAJAFxgaJBwoIExAqEFwQ6hA3EdoR\",\"AAAAAd4EAAAAAfoSAAAB4hMAAA==\"]Ck[\"AAAAAesBAAAAAAAAAWIUAAABpBQAAA==\",\"AAAAAAAB/hIAAAHmEwAA\"]Ck[\"AAAAAewBAAAAAAAAAWMUAAABpRQAAA==\",\"AAAAAAAB/xIAAAHnEwAA\"]Kk[\"AAAAAAAABIMFAACJBQAAigUAAIsFAAAAOzAAAAEAAAkAAQACDAkAOzAAAAEAAA0AAQBtBA0AOzAAAAEAAAkABAB9DAMA0Q8CAFQRAABoEQEAAAGFDAAAA4EMAADUDwAA1Q8AAAKDDAAA1g8AAA==\",\"AAAAAAABNREAAANxDAAAcgwAAHMMAAA=\"]Nk[\"AAAAAAAAAhkTAAAaEwAABIgTAACJEwAAihMAAIsTAAACjhMAAI8TAAAFHwcAAL0SAAC+EgAAvxIAAMASAAACgwQAAIQEAAAChwUAAFURAAAAAAH6BgAAAAAAAdQFAAA=\",\"AAAAAAJ/BQAAchQAAAAFqhQAANEUAADSFAAA0xQAANQUAAAB+BMAAAJLEAAAqxQAAAAAAAAAAAAAAAS+EAAAvxAAAMAQAADBEAAA\"]Bg[\"AAAAAAAAAATuEQAA7xEAABsUAAAcFAAA\",\"\"]Bk[\"\",\"AAAAAe0RAAAAAAAAAAAAAAAAAAAAAAHeDwAA\"]Cc[\"AAAAAAAAAfMQAAAB9BAAAA==\",\"AAAAAAACegkAAHsJAAA=\"]FHk[\"AAAAADswAAABAAAXAAkAHQIGAGsEAACVBQAADAgAAF0MAADCEAMA+RIAAD8UAQCsFAUAOzAAAAEAACkCEgBiBAAAewQAAIEEAAD+BgAATQwDACENBQApDXwBTRAAAFIQAADHEAAA5RAAAEMRBwCiEQAAGBKNALYSAAC4EgAAjRMAAPMTAAA7MAAAAQAAxwAQALkAKQAqAXAAnQEOAGQEAACDBQAA0wUAAOEGAwDmBgIA9wYBAEkMAwDPDwAAwRMAAOITBQBZFAMAkRQAAKYUAAA7MAAAAQAAqABCAFgDFAB+BAAAiAQBAJMEAgBvBQAAhAUBAJ8FAADcBgQA6gYMAPkHAQD8BwEAAAkBAEsJAQBhCQEAbAkBAF8MAQBvDAEAfAwAACcNAQDKDwEA3A8BABQQAQAXEAEAMRAAAFMQAQBdEAEAZRAAAMgQAADYEAEA8RABAPQQAABaEQEAoxEAAK4RAACwEQAAshEAALURAAC3EQAAuREAAMQRDQDbEQEA3hECAO4RAQCnEgEACxMBAEQTAAB5EwEAfxMBAIgTAwCnEwEA6RMBAPkTAQD9EwEAGxQBACMUAQBQFAEAZBQBAIAUAgCMFAMAnBQDALQUAADEFAEAyBQBAMsUAQDPFAEA1RQBADswAAABAACcACcA4wBGAJsBAQC2AQEAYQQAAH8EAACGBAAAfAUBAJcFAQCoBwAAqwcAAOwHAADuBwAAAgwJAIkMAQDYDAEA2A8BAA0QAQBLEAAAWBADAOYQAQAzEQEAPREBAFYRAQC0EgAAARMBABMTAwCOEwEAsBMBAN4TAQDxEwEA9BMDABgUAABeFAUAbBQBAHEUAACoFAEAqxQAALsUBQDNFAEAOzAAAAEAACkADABtBA0AnQUBAB8HAACACQEAeQwAAN8MAACmDgAALRADANYRAwC9EgMA7hMBAKAUBQA7MAAAAQAAHAANAIMEAQCMBAEA6wcAAO0HAAB9DAMA0Q8CANoPAAD9EAEAVBEAAGgRAQAbEwAAtBMHALUUAAA6MAAAAQAAAAAADwAQAAAAfQSHBQgHCQcKBwsHDAcNBw4HDwdKEFUR1BHVEQoTiBQDYwQAAGUEAACFDAAAB4IEAAClBwAApwcAAKoHAACBDAAA1A8AANUPAAAE+gYAAIIMAACDDAAA1g8AADswAAABAAAFAAIA5QYAADoUBAAAAYQMAAAB1AUAAA==\",\"AAAAAAA7MAAAAQAA7gABACkN7gAAOjAAAAEAAAAAAFkAEAAAAIgEiQT5B/oHAAkBCWEJYglsCW0JXwxgDG8McAwnDSgNyg/LD9wP3Q8UEBUQFxAYEFMQVBBdEF4Q2BDZEPEQ8hBaEVsRrhGwEbIRtRG3EbkRxBHFEdsR3BHuEe8RpxKoEgsTDBN5E3oTfxOAE6cTqBPpE+oT+RP6E/0T/hMbFBwUIxQkFFAUURRkFGUUgRSCFIwUjRSOFI8UnBSdFJ4UnxTEFMUUyBTJFMsUzBTPFNAU1RTWFDswAAABAAAzABUAtgEBAJcFAQCJDAEA2AwBAA0QAQBYEAMA5hABADMRAQA9EQEAVhEBAAETAQATEwMAjhMBALATAQDeEwEA8RMBAPQTAwBsFAEAqBQBALsUBQDNFAEABIAJAACBCQAA7hMAAO8TAAAAAtQRAADVEQAA\"]Ic[\"AAAAAasRAAAABIAFAABQCQAAUQkAALASAAA7MAAAAQAADAADAJYEBQD/BgUAORQAAAADpAcAAFYRAABXEQAAAbQQAAAAB14RAABgEQAAYhEAAGQRAABmEQAA1BEAANURAAAAAaUHAAA=\",\"\"]Ko[\"AAACaAIAANICAAAAADswAAABAAALAAMAxgEBANgBAQBDEQcAOzAAAAEAAAcAAQDOBAcAOzAAAAEAAA8ABQD5BwEAFBABAF0QAQDEEQEAyhEHAAAC7hMAAO8TAAA=\",\"AAAAAQsQAAAAAAAAAAAAAAAAAAAAAAUKEAAADBAAABMQAAAWEAAA8BMAAA==\"]AAg[\"AAAEZwIAANACAABSCQAAUwkAAAS8AQAAxAEAAMgBAADVAQAAAm0CAADYAgAAAAA6MAAAAQAAAAAAEwAQAAAAiASJBAAJAQnKEMwQzhDQENEQXxFhEWMRZRFnEQsTDBPIFMkUyxTMFAKOEwAAjxMAAAWLDAAAjAwAAI0MAACODAAAjwwAAAAFXhEAAGARAABiEQAAZBEAAGYRAAAAAAAC0QEAAOUGAAA=\",\"AAAAAAAAAAAAAAAAAAAAAAAABAIJAAADCQAABAkAAAUJAAA=\"]C@o[\"AAADTwkAAHsTAAB8EwAABboBAABnCgAA7REAAIETAACCEwAAOjAAAAEAAAAAAAcAEAAAAMUB1gFUCVUJSAqgCskKtRIBthIAAAXBEwAAWRQAAFoUAABbFAAAXBQAADowAAABAAAAAAAXABAAAAD8B/0HyQ9TEFQQ3hCuEbARshG1EbcRuRHKEcsRzBHNEc4RzxHQEdER6RPqE4wUjRQD6BMAAL8UAADAFAAABC0QAAAuEAAALxAAADAQAAA7MAAAAQAACQADAOsHAADtBwAAtBMHADswAAABAAAHAAEACAcHAAACpwcAAKoHAAA=\",\"AAAEdgUAAHcFAAC/DwAAwA8AADswAAABAAAWAAsATAcHAKwRAQCvEQAAsREAALMRAQC2EQAAuBEAAKkSAQAVFAEAGRQAAGoUAQACvRMAAEEUAAABsBIAAAAAAAAAAAAAAAAAADowAAABAAAAAABxABAAAACwAbEBswG0ARECEgIaAhsCagRsBI4E2ATbBNUG2QYGBwcHPgc/Bz0JPglzCXQJXAxeDHQMdQx6DHsMhgyHDMUPxg/HD8gP3w8GEAcQKBApEFwQthC3ENQQ1RDWENcQ4xDkEOgQ6RD1EPYQLREuETcRORE6EUARQRFLEUwRTRFOEdIR0xHlEeYRrBKtEq4SrxL2EvcSAxMEEw8TERM5EzoTOxM8Ez4TPxNAE0ETfRODE4QThROhE7ITsxO8E8ITwxPEE8UTxhPHE+AT4RP7E/wTAhQDFCEUIhREFEUUhRSGFJIUkxQ=\"]Ac[\"AAAAAAFJAwAA\",\"\"]Gc[\"AAAABBQFAABNBwAA8gcAAK0RAAADZAkAAFQMAACtFAAAAUQRAAABWhQAAALLEQAAzxEAAAEdAQAAAS4QAAABtRMAAAEJBwAA\",\"AAAAARQFAAA=\"]Gc[\"AAAABF4FAABRBwAA9gcAALQRAAADaAkAAFgMAACwFAAAAUgRAAACnwEAAFwUAAACzREAANERAAAAATAQAAABuRMAAAENBwAA\",\"AAAAAV4FAAA=\"]Ca[\"AAAAAAJtAgAA2AIAADswAAABAAAFAAEA5xEFAA==\",\"\",1]Ek[\"AAAAAAAAAAAAAAAAAAAAAAAAAvsGAAARBwAA\",\"AAAAAAACyRAAAMsQAAAAAsoQAADMEAAAAAK9EgAAvhIAAA==\"]Eo[\"AAAAAAAAAAAAAAAAAAAAAAAAAvwGAAASBwAA\",\"AAAAAAACzRAAAM8QAAAAA84QAADQEAAA0RAAAAACvxIAAMASAAA=\"]Ck[\"AAAAAAAAAAAAAAAAAAAAAAAABXsAAAC0AQAA5wcAAAQIAAAECQAA\",\"\"]Bg[\"AAAAAAAAAdcPAAAAAAAAAtQRAADVEQAA\",\"\"]Hc[\"AAAABGcAAADoBAAAkwUAAGAQAAABHwIAAAYOAAAAaQQAAG4FAAA7BwAA4xEAAEMUAAADzwAAAHAJAADbDwAAAAL/EwAAXRQAAA==\",\"AAAAAegEAAAB/wgAAAFnBAAA\"]Ic[\"\",\"AAAAAAAAAAA7MAAAAQAACQABAAIMCQA7MAAAAQAADQABAG0EDQA7MAAAAQAACQAEAH0MAwDRDwIAVBEAAGgRAQAAAYUMAAADgQwAANQPAADVDwAAA4IMAACDDAAA1g8AAAAAAYQMAAA=\"]Hk[\"AAAD3gEAAGgMAABrDAAAAREFAAABxQEAAAQvAAAAnwAAAMkQAADLEAAAATgQAAABXAMAAAMXAQAAnhEAAJ8RAAA=\",\"AAAC3gEAAGQMAAACigQAABEFAAABohMAAAABSgwAAA==\"]Hk[\"AAAD3wEAAGkMAABsDAAAAVMFAAAB1gEAAARdAAAArAAAAM0QAADPEAAAAosBAABFEAAAAWkDAAADfwQAAKARAAChEQAA\",\"AAAC3wEAAGUMAAADiwQAAFMFAACkEwAAAAFODAAA\"]AMo[\"AAAAAAG4EAAAAAA6MAAAAQAAAAAAEQAQAAAAygF+BG8McAwnDSgNyg/LD1oRWxGnEqgSnBSdFMQUxRTPFNAUAoYEAADoEwAABC0QAAAuEAAALxAAADAQAAA7MAAAAQAABwABALQTBwA7MAAAAQAABwABAAgHBwAAAAAC0QEAAOUGAAA=\",\"AAAHeAUAAHkFAABoDAAAaQwAAGsMAABsDAAAtxIAADowAAABAAAAAAAXABAAAADvB/AHXwlrCWoMbQxuDMEPwg/aENsQ3BAsEasRuxG8Eb0RvhG/EesTVxRYFG8UcBQ7MAAAAQAACwECAK4OBwGpEwMAOzAAAAEAAF8ABAALAFsAfhMAAK4TAQAaFAAAAaYUAAAAAATWEQAA1xEAANgRAADZEQAABMARAADBEQAAwhEAAMMRAAABChMAAAAAAAAAAAeSBAAAkAUAAAUHAACrDgAALxEAAD8RAADaEQAA\"]Dk[\"AAAAAAJLCgAAoQoAAAAAADswAAABAAAFAAEA7RIFAAACgwQAAIQEAAAAAmMEAABlBAAA\",\"\"]C@k[\"\",\"ADswAAABAAAFAAIAXAQEAKcUAAAC/QYAAB4HAAA6MAAAAQAAAAAACgAQAAAA6wHsAYIF3gyyEvgSDROEFJAUshTDFDowAAABAAAAAAAcABAAAAAdAh4CHwIgAiECIgIjAmsEgQWGBcsG+QYqB3gJQhG1EvkSjBM/FEAUhxSsFK0UrhSvFLAUsRTGFMoUOzAAAAEAADYBEQBpBAAAbgUAAIAFAAD+BgAAHQcAAC0HAAAhDQUAGA6NAMcQAABDEQcAohEAABgSjQC2EgAAuBIAAEMTAACNEwAArRMAADowAAABAAAAAAAfABAAAACDBYkFigWLBfcG+Ab/BgAHAQcCBwMHBAcNDA4MDwwQDNcP2w/zEFkRsRLBEwAUARQXFFkUWhRbFFwUfxSRFLMUOzAAAAEAAEEAEwBYAxQAhwQAAJMEAgBsBQEAbwUAAIQFAQCfBQAA6gYMAPwHAQAMDAAAfAwAADEQAABlEAAA9BAAAMoRBwBEEwAAiBMDAIAUAAC0FAAAOzAAAAEAAFsACwDjAEYAmwEBAIYEAACoBwAAqwcAAOwHAADuBwAA7RIFABgUAABeFAUAcRQAADswAAABAAAMAAUAbQQAAN8MAACmDgAALRADAKAUBQA7MAAAAQAAEwAJAIMEAQCMBAEA6wcAAO0HAADRDwIA2g8AAFQRAAAbEwAAtBMHADswAAABAAAJAAIACAcHAEoHAQABhQwAAAOnBwAAqgcAAIEMAAAABToUAAA7FAAAPBQAAD0UAAA+FAAA\"]Bc[\"AAAAAAAAAAAAAAAC1BEAANURAAA=\",\"\"]Kc[\"AAAAAAGHEwAAADswAAABAAAGAAIA/wYFAFkRAAACbwUAAGUQAAABhgQAAAAD/RAAAP4QAAAbEwAAAUoQAAA=\",\"AAAAAAABgQQAADswAAABAAAFAAEAlgQFAAAAAAAAAAAAAAAABxwCAADXBAAA2gQAABUHAAAWBwAAGAcAABkHAAA=\"]Do[\"AAAAAAABGAAAAAAAAecAAAAAAAAAAAAAAAAC1wQAAAYHAAA=\",\"AAAAAAAAAAAC2A8AANkPAAA=\"]Dk[\"AAAAAAABNgAAAAEwAQAAAAAAAAAAAAAAAAAC2gQAAAcHAAA=\",\"AAAAAAAAAAABYQQAAA==\"]Ac[\"AAABegIAAA==\",\"\"]Fg[\"AAAABBYFAABOBwAA8wcAAK8RAAADZQkAAFUMAACuFAAAAUURAAAAAAEfAQAAAAG2EwAAAQoHAAA=\",\"AAAAARYFAAAAAcYBAAA=\"]Fg[\"AAAABGAFAABSBwAA9wcAALYRAAADaQkAAFkMAACxFAAAAUkRAAABoQEAAAAAAAG6EwAAAQ4HAAA=\",\"AAAAAWAFAAAAAdgBAAA=\"]Bk[\"\",\"AAAAAAAAAAA7MAAAAQAASAACAOMARgCbAQEA\"]Ck[\"\",\"AAAAAAHLBgAAOzAAAAEAAAkAAgAtBwAALwcIAAGDFAAAAfgTAAA=\"]Bo[\"\",\"AAAAAAAAAAAAAAAAAkYUAABHFAAAAAAAAZETAAA=\"]Ck[\"AAACeQIAAI0CAAA=\",\"AAAAAAAAAgUUAAAGFAAAAAAC1hEAANcRAAA=\"]Eo[\"\",\"AAAAAAA7MAAAAQAABQABAPoSBQA7MAAAAQAABQABAOITBQAAOzAAAAEAAAUAAQBeFAUAOzAAAAEAAAUAAQCgFAUA\"]Cc[\"\",\"AAAAAAKiEwAAoxMAAAAESQwAAEoMAABLDAAATAwAAA==\"]Eo[\"AAAAARoFAAAAATMAAAAAAAImAQAAJwEAAA==\",\"AAA7MAAAAQAACgACAKsCBgCzAgMAAhoFAACUFAAAAAABGRMAAA==\"]Eg[\"AAAAAVoFAAAAAWUAAAAAAAKbAQAAnAEAAA==\",\"AAA7MAAAAQAACgABADEDCgACWgUAAJUUAAAAAAEaEwAA\"]Dg[\"AAABGQMAAAFZBQAAAAFjAAAAApYBAACXAQAA\",\"AAABJwMAAAFZBQAAAAAAAAF/BAAA\"]Eo[\"AAAAAAAAAqUBAACmAQAA\",\"AAAAAAAAB38UAACqFAAAsxQAANEUAADSFAAA0xQAANQUAAACgBQAALQUAAABqxQAAA==\"]Bk[\"AAAAAAAAAVYBAAA=\",\"AAAB8AIAAAAAAXsEAAA=\"]Bg[\"AAAAAWQFAAAAAAGnAQAA\",\"AAAAAWQFAAA=\"]Bg[\"\",\"AAAAAAAAAAAAAAAAAAAAAAAAAdoMAAA=\"]Cc[\"AAAAAAAAAAAAAAAAAAAAAAAAA+gHAAAFCAAABQkAAA==\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAApIEAADpBwAA\",\"\"]Cc[\"AAAAAAAAAAJLCQAATAkAAAAAAAAAAAAC0QEAAOUGAAA=\",\"\"]Fo[\"AAAAAAACgAUAALgSAAAAAAAAAAAAAAAAAAAECwgAAN8PAAD3EAAA8BEAAA==\",\"AAGtBwAAAAAAAAAAAAAAAAAAAAAAAAL4EAAAnREAAA==\"]Bk[\"AAAAAYIFAAAAAAHXDwAAATEQAAA=\",\"AAHgBwAA\"]Bg[\"AAAAAAAABPkQAAD6EAAA+xAAAPwQAAA=\",\"\"]Ag[\"AAAAAr4BAADJAQAA\",\"\"]Dg[\"AAAAAAAAAAAAAoAJAACBCQAA\",\"AAAAAAAAAAAABYsMAACMDAAAjQwAAI4MAACPDAAA\"]AEg[\"AAAAAALDAQAA0gEAAAAAAmwFAABtBQAABKgHAACrBwAA7AcAAO4HAAA7MAAAAQAACwAEAJ0FAQCLDAQA3wwAANYRAwA6MAAAAQAAAAAACwAQAAAAjASNBOsH7QfaD/0Q/hDAEcERwhHDERsTBH0EAABKBwAASwcAAEoQAAAEYwQAAGUEAABGFAAARxQAAAWCBAAApwcAAKoHAADUDwAA1Q8AAAOCDAAAgwwAANYPAAA7MAAAAQAABgADANEBAADlBgAAOhQEAAGREwAAAYQMAAA=\",\"AAAAAfERAAAAAnEFAAByBQAAAAJfDAAAYAwAAAAAAAACYwQAAGUEAAA=\"]He[\"\",\"AAAAAAAEkAwAAJEMAACSDAAAkwwAAAJmDAAAgxQAAAKbEQAA+BMAAAW5EAAAuhAAALsQAAC8EAAA6BMAAAGzEgAAAAEKEwAAAAGlBwAAAAHRAQAAAAAB1AUAAA==\",1]AMk[\"AAACywEAANQBAAAGvAEAAMQBAADIAQAA1QEAAIETAACCEwAABIEFAAB4CQAASgoAAKIKAAAEkAwAAJEMAACSDAAAkwwAAAIAFAAAARQAADowAAABAAAAAAAHABAAAABLCUwJ2BDZEHkTehOBFIIUOjAAAAEAAAAAAAkAEAAAAGEEqAerB+wH7gfYD9kPSxCoFKkUAALrBwAA7QcAAA==\",\"AAAAOjAAAAEAAAAAABAAEAAAALIBhQeGB4cHiAcNCA4IlAxOEE8Q8BBcEV0RBxMIE0ITixQDdQkAALgQAAA9EwAABpAMAACRDAAAkgwAAJMMAAAOEwAAEBMAAAAAAAKdBQAAngUAAAACfQQAAEoQAAAAA4IEAADUDwAA1Q8AAAOCDAAAgwwAANYPAAAAAAKEDAAA5AwAADowAAABAAAAAAALABAAAAC1AY8EkATZBNwExAbFBkAHiQcKCAsIKhA=\"]Bg[\"AAAAAAAAAAAAAXgEAAA=\",\"AAAAAdEFAAA=\"]Bk[\"AAAAAAAAADswAAABAAANAAIAnwUAAOoGDAA=\",\"\"]Cc[\"AAABQgkAAAABIwIAAAAAAAFhBAAA\",\"AAAAAAAAAAF+BAAA\"]Dg[\"AAAAAeIEAAAAAAHBAAAAAAAAAAAAAAAAAAADlAUAABQHAADNDwAA\",\"AAAAAeIEAAA=\"]Bc[\"AAAAAAAAAAAAAAAAAAAAAeUGAAA=\",\"\"]Be[\"AAAAAAAAA4kFAACKBQAAiwUAAA==\",\"\",1]Bg[\"AAAAAAAAADswAAABAAAUAAEAWAMUAA==\",\"\"]Dk[\"AAAAAAHXAQAA\",\"AAAAAARuCQAAbwkAAHEJAAByCQAAAAFwCQAAAAAAAAAAAAAAAAGIEAAA\"]Cc[\"AAAAAecEAAAAAQ0AAAACxwAAAMgAAAA=\",\"AAAAAecEAAA=\"]Bg[\"AAAAAAAAAAAAAXMEAAA=\",\"AAAAAf4HAAA=\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAWwAAAA=\",\"\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAXEAAAA=\",\"\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAbEAAAA=\",\"\"]C@m[\"AAAAAAHXAQAAAUMTAAABFxQAAALKAQAARBMAADowAAABAAAAAAAdABAAAAC9AZcFmAUNEA4QPRE+EQETAhOwE7ET3hPfE/ET8hP0E/UT9hP3E/8TGBRdFKgUqRS9FL4UvxTAFM0UzhQ7MAAAAQAABgADAIAJAQAtEAMAbhQAAAG1FAAAAYgUAAAAAAH6BgAAAAAAAdQFAAA=\",\"AAAFWgkAAFsJAABcCQAAXQkAAF4JAAAEYQwAAGIMAABjDAAAphIAADowAAABAAAAAAAXABAAAACsAcoEywSmB6kH/wf+CP8IbglvCXEJcgl1Ca8QsBCxELIQsxA9E0YTqROqE6sTrBM7MAAAAQAAIAAOAOcBAADpAQAAYgQAAGcEAAB7BAAAgQQAAC8HCACQDAMAzA8AAOUQAABPEQQApBEEAIYTAADzEwAAOzAAAAEAACQACgBkBAAAlgQFANMFAAAICAAAcAkAAM8PAAA0EBMA9BIBAAUUAQAIFAEAOzAAAAEAAAgABADIEAAAxhEDAN4RAgAgFAAAOzAAAAEAAAwABgBhBAAA2A8BALkQAwCeEQMA/xMAAF0UAAAEvRIAAL4SAAC/EgAAwBIAADswAAABAAAGAAMAfQwDAGgRAQC1FAAABH0EAABKEAAAVREAAIgUAAAAAAH6BgAAAdEBAAAAADswAAABAAAeAA8AswADANsGAADpBgAAOAcCADsJAAB8CQMAggkBABkQAAAyEAEAVRACAOsQBADdEQAAqxIAACUUAADXFAAA\",1]Mg[\"AAAAAfIEAAAAOjAAAAEAAAAAAAcAEAAAABkAnADnAWcEzA/oEesRrhMCNRAAAPQSAAACWQMAAOsGAAAC6AAAAOkAAAAAAesHAAAAAAGnBwAA\",\"AAGyBwAAAANnAAAAaAAAAPIEAAACbQIAAP8HAAAC6BEAAOsRAAAAAd0GAAAAAAAAAAAAAAAABWwAAABtAAAAbgAAAG8AAABwAAAA\"]A@c[\"AAAAASEFAAAABzgAAACkAAAA6QEAAOoRAADsEQAAhhMAAK8TAAAHMwEAADQBAADPDwAAPRAAAEkQAADdEAAA9RIAAARhAwAA8AYAAHwMAAAgFAAAAbQSAAABpg4AAALtBwAAtRQAAAGIFAAAAAGqBwAA\",\"AAHJBwAAAAEhBQAAAtgCAAB/BQAAAuoRAADsEQAAAeIGAAAAAAF5DAAAAAEKEwAAAAAAAAAABXEAAAByAAAAcwAAAHQAAAB1AAAA\"]Bo[\"\",\"AAAAAAAAOzAAAAEAAKkAAwC5ACkAKgFwAJ0BDgA=\"]CDe[\"\",\"AAA7MAAAAQAA7gABABML7gA7MAAAAQAA9QANABIKNQBJCgAATAo5AIcKAACJChYAowofAMQKBADKCioA9goAAPgKAAD7ChcA8xIAAJQUAQA6MAAAAQAAAAAAEgAQAAAAhgXSBcsGVAlVCUgKSgpLCoYKiAqgCqEKogrDCskK9wr5CqIToxM6MAAAAQAAAAAAEgAQAAAAaQRuBS0HOwd6CXsJ9Qr6CuMR+hL7EvwS/RL+Ev8SrRNDFEgUSRQ6MAAAAQAAAAAAKgAQAAAA9wb4BggIcAkNDA4MDwwQDEkMSgxLDEwMZgzbDwUQSRDdEPMQ+RD6EPsQ/BBZERkTGhPiE+MT5BPlE+YT5xMFFAYUCBQJFH8UgxSqFLMU0RTSFNMU1BQ6MAAAAQAAAAAAGgAQAAAAfgSTBJQElQSEBYUF3AbdBt4G3wbgBnwMZRDKEMwQzhDQENEQ9BCjEd4R3xHgEfgTIBSAFLQUOzAAAAEAAGIADwDjAEYAmwEBAGEEAAB/BAAAhgQAAHwFAQCkBwAA2A8BAEsQAAC0EgAA7RIFAP8TAABdFAYAcRQAAKsUAAA7MAAAAQAAEQAIAJ0FAQB5DAAA3wwAAKYOAAC0EAAA1hEDAO4TAQCgFAUAOjAAAAEAAAAAAAgAEAAAAIMEhATaD/0Q/hBoEWkRGxO1FDowAAABAAAAAAALABAAAAB9BIcFSgdLB0oQXhFgEWIRZBFmEQoTiBQEYwQAAGUEAABGFAAARxQAAAOlBwAApwcAAKoHAAACggwAANYPAAA7MAAAAQAABgADANEBAADlBgAAOhQEAAGREwAAAAHUBQAA\",1]Le[\"\",\"AAAAAtoBAADJBgAAAysHAADDDwAAxA8AAAMdBwAADhMAABATAAA6MAAAAQAAAAAABwAQAAAA1w+xEroSuxK8EgAUARQ5FAKHBAAADAwAAAG9AQAAAW4UAAAE0Q8AANIPAADTDwAAVBEAAAABhQwAAASCBAAAgQwAANQPAADVDwAAAYMMAAAAAAGEDAAA\",1]Gc[\"\",\"AAAAOjAAAAEAAAAAAAcAEAAAAOsB7AGyEg0ThBSQFLIUwxQCKgcAALgQAAAFyAYAAFAJAABRCQAACBAAALASAAABwRMAAAGbEQAAAegTAAA=\"]Bo[\"\",\"AAAAAqQTAAClEwAAAARNDAAATgwAAE8MAABQDAAA\"]Bk[\"\",\"AAAAAAAAOzAAAAEAAAYAAgDhBgMA5gYCAA==\"]Fc[\"AAAAAdMBAAAAAAAABJcFAACYBQAADRAAAA4QAAA=\",\"AAA7MAAAAQAAQgABAJUMQgAAAAAAAAAAAAAAAAAAAAABGgcAAA==\"]Ao[\"\",\"AAA7MAAAAQAA7gABAA8I7gA=\"]AAg[\"AAAEggIAAJQCAACaAgAAvw8AAAHwBAAAA4wTAADGFAAAyhQAAAMWAAAAmwAAAOcRAAABNBAAADowAAABAAAAAAAMABAAAABYA5MElARsBW0FhAXqBtsR+RMjFFAUjhSeFALjAAAA5AAAAA==\",\"AAGxBwAAB2QCAABlAgAAZwIAAGgCAABpAgAAUgkAAHsTAAAB8AQAAARUCQAASAoAAEoKAABLCgAAAecRAAABSQwAAAPcBgAA3hEAAN8RAAAAAAAAAAAAAAAAAbcPAAA=\"]AAk[\"AAAE9wIAABoDAAAhAwAAwA8AAAEeBQAAAAQ0AAAAogAAAOkRAACNEwAABCwBAAAtAQAAOxAAAJEUAAA6MAAAAQAAAAAACgAQAAAAXwOVBG8FhQXvBtwR+hMkFFEUjxSfFAABpg4AAA==\",\"AAHIBwAAOzAAAAEAAAYAAwDOAgQAUwkAAHwTAAABHgUAAARVCQAAoAoAAKEKAACiCgAAAk0MAADpEQAAAuEGAAAICAAAAeARAAAAAAAAAAAAAAAABLkPAAC6DwAAuw8AALwPAAA=\"]Gc[\"AAA7MAAAAQAACwAEAKoQBACmEwAAUhQEAMcUAAA=\",\"ADswAAABAAAJAAMAigcBAI0HAgB0EwQAOzAAAAEAAAsABACqEAQAphMAAFIUBADHFAAA\"]Ic[\"AAAAAAAAAAA7MAAAAQAACQABAAIMCQA7MAAAAQAADQABAG0EDQA7MAAAAQAACQAEAH0MAwDRDwIAVBEAAGgRAQAAAYUMAAADgQwAANQPAADVDwAAA4IMAACDDAAA1g8AAAAAAYQMAAA=\",\"\"]Cc[\"AAAAAsABAABcEQAAAAAAAAGJDAAAAAGMBAAAAUoHAAA=\",\"\"]Cc[\"AAAAAs4BAABdEQAAAAAAAAGKDAAAAAGNBAAAAUsHAAA=\",\"\"]ACg[\"AAAAApITAACTEwAAAAXGAQAAxwEAANgBAADZAQAAohEAADswAAABAAALAAMAzgQHAIMFAABxDAIAOzAAAAEAAA0ABQBvBQAA/AcBANwPAQAxEAAAyhEHAAeeEQAAnxEAAKARAAChEQAAGBQAAHEUAACrFAAABnkMAACmDgAALRAAAC4QAAAvEAAAMBAAADswAAABAAAHAAEAtBMHADswAAABAAAKAAQACAcHAFURAAAKEwAAiBQAAAABpQcAAAH6BgAAAAAAAdQFAAA=\",\"AAAAA+EBAADiAQAA4wEAAAAB5RAAAA==\"]Ek[\"AAAAAAAAAAAAAAAAAAAAAAACegUAACAHAAA=\",\"AAAAAAAAAALGEQAAxxEAAAAAAAAAAAAAAAAC+wYAABEHAAA=\"]Ek[\"AAAAAAAAAAAAAAAAAAAAAAACewUAACIHAAA=\",\"AAAAAAAAAALIEQAAyREAAAAAAAAAAAAAAAAC/AYAABIHAAA=\"]Ak[\"\",\"AAAAAAIrEAAALBAAAA==\"]Fc[\"AAAABBgFAABPBwAA9AcAALERAAACZgkAAFYMAAABRhEAAAAAASEBAAAAAbcTAAABCwcAAA==\",\"AAAAARgFAAAAAccBAAA=\"]Fc[\"AAAABGIFAABTBwAA+AcAALgRAAACagkAAFoMAAABShEAAAGjAQAAAAAAAbsTAAABDwcAAA==\",\"AAAAAWIFAAAAAdkBAAA=\"]Bg[\"AAAAAAAAAAAAAXIEAAA=\",\"AAAAAXkJAAA=\"]Ag[\"AAACvwEAAM0BAAA=\",\"\"]Ao[\"AAAAAAAAAAAAAAAAAAGBDAAA\",\"\"]Ag[\"\",\"AAAC0hAAANMQAAA=\"]Bg[\"AAAAAAAAAAAAAbMSAAA=\",\"AAABegAAAA==\"]Dg[\"AAAAAuEBAADiAQAAAAADiQUAAHEMAAC6EgAAAAG9AQAAAW4UAAA=\",\"AAAAAaYSAAA=\"]Hg[\"AAAAAbsRAAABqRMAAAPJEAAAUBEAAKURAAABBRQAAAHGEQAAAbkQAAAC1hEAAL0SAAABwBEAAAAAAAABOxQAAA==\",\"AAAAAAAAAAJfEQAAYREAAAAAAAJeEQAAYBEAAA==\"]Ig[\"AAAAAb4RAAABqxMAAAPNEAAAUhEAAKcRAAABCBQAAAHIEQAAAbsQAAAC2BEAAL8SAAABwhEAAAAAAAABPRQAAA==\",\"AAAAAAGcEQAAAAADYxEAAGURAABnEQAAAAAAA2IRAABkEQAAZhEAAA==\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAnkAAAC8DwAA\",\"\"]Bk[\"AAAAAAAAAeIAAAAAAAF6BAAA\",\"AAAAAQcIAAA=\"]Cc[\"AAAAAfAQAAABvRMAAAACZAQAANMFAAA=\",\"AAAAAAEMCAAA\"]Ek[\"AAAAAfsEAAAAASEAAAAAAAH0AAAAAAAAAAAAAAAAAtkEAADEBgAA\",\"AAG5BwAAAAH7BAAAAAAAAAAAAf0QAAA=\"]Ek[\"AAAAAS4FAAAAAUMAAAABRQEAAAAAAAAAAAAAAAAAAtwEAADFBgAA\",\"AAHQBwAAAAEuBQAAAAAAAAAAAf4QAAA=\"]Be[\"AAAAAAA7MAAAAQAAjQABABgSjQA=\",\"\",1]Bg[\"AAAAAAAAAAAAAXUEAAA=\",\"AAAAAUgMAAA=\"]Bg[\"\",\"AAAAAAAAAAAAAAAAAAAAAAAAAc0PAAA=\"]Ec[\"AAAAAAAAAAAAAAAAAAKnBwAAqgcAAAH6BgAAAtEBAADlBgAA\",\"AAAAAAAAAAAAAAAAAAAAAdEBAAA=\"]Dk[\"AAAAAAAAAAABYBQAAAGiFAAA\",\"AAAAAAAB/BIAAAHkEwAAAAAAAAAAAAAAAAABjAcAAA==\"]Dg[\"AAAAAUYFAAAAAAJwAQAAcQEAAAHzBgAAAAAAAAAAAAAAAAEcAgAA\",\"AAAAAUYFAAA=\"]ACg[\"AAAAA7oBAABhCgAA7REAAAP5BgAAwwoAAEIRAAAABFkUAABaFAAAWxQAAFwUAAA6MAAAAQAAAAAADQAQAAAAYQliCWwJbQkXEBgQ8RDyEH8TgBOnE6gT/RP+EzswAAABAAAPAAcAPREBAO0SBQCwEwEA/xMAAF0UAAC9FAEAzRQBAAABtRQAAAGIFAAA\",\"AAAAA60BAAC4AQAAtRAAAASvEAAAsBAAALEQAACyEAAAAucBAADpAQAAAAAC/xMAAF0UAAAAAbUUAAABiBQAAAAAAAAAAAToAQAA6gEAAD8JAADwEQAA\"]Bk[\"AAAAAAEqBwAA\",\"AAAAAAAAAAGbEQAAAAGzEgAA\"]Ho[\"AAAABeUEAACSBQAA0QUAAN4MAAAHEwAAAsQPAACHFAAAAh0HAABDEwAAA8UAAACxEgAAFxQAAAFtBQAAAaQHAAABtBAAAA==\",\"AAAAAuUEAADJBgAAASsHAAAAAAABvQEAAAFuFAAA\"]Gc[\"AAAAAggTAAAZFAAAAdAGAAAB/gYAAAErAQAAAsgQAABEEwAAAAEfBwAAAAGHBQAAAAGlBwAAAfoGAAAAAAAB1AUAAA==\",\"AAAAAAAAAAHKAQAA\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAXgAAAA=\",\"\"]Fg[\"AAAD/QYAAB4HAAAlEAAABG0MAADhEAAA4hEAAG8UAAAFHgIAAIYFAADSBQAApgcAAG8JAAAAAssAAAAFEAAA\",\"AAABOxEAAA==\"]Fg[\"AAAABG4MAADiEAAA5BEAAHAUAAACqQcAAHEJAAAB/gYAAAFzAQAAAAABHwcAAAABhwUAAAAAAAAAAAHUBQAA\",\"AAABPBEAAA==\"]Dg[\"AAAAAUsFAAAAAVcAAAABfAEAAA==\",\"AAAAAUsFAAAAAAAAAAAAAAAAAAAAAAF7AAAA\"]Bk[\"AAAAAAEdAgAAAAAAAAFvBAAA\",\"AAAAAXcMAAA=\"]Cc[\"AAAAAAH/BwAAAAAAAAFwBAAA\",\"AAAAAAACeAwAAAkTAAA=\"]Dk[\"\",\"AAAAADswAAABAAATAAEAkAcTADswAAABAAATAAEAmwATADswAAABAAATAAEANBATAA==\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAXYAAAA=\",\"\"]Fg[\"AAAAAWUFAAAAAAGpAQAAAAAAAAAAAYIEAAAAAAABhAwAAAa1AAAAtQEAABUCAADpBgAAQAcAAKsSAAA=\",\"AAHfBwAAAAFlBQAA\"]Hk[\"AAAErwAAAGQCAAB2BQAAZAwAAAETBQAAAAIxAAAAoQAAAAE6EAAAAl4DAADuBgAAAhoBAAAbAQAA\",\"AAABmgIAAARpAAAAagAAAGsAAAATBQAAAcUBAAAAAUwMAAAB4AYAAA==\"]Kc[\"AAAEsAAAAM8CAAB3BQAAZQwAAAFVBQAAAARfAAAArgAAAE0QAABSEAAAA44BAACPAQAARxAAAAJrAwAA9QYAAA==\",\"AAABIQMAAAFVBQAAAdYBAAABUAwAAANkBAAA0wUAAOcGAAAAAAAAAAAAAAAAAAR2AAAAdwAAAHgAAAB5AAAA\"]Ak[\"AAAAAAJjEAAAZBAAAA==\",\"\"]Ao[\"AAAAAAAAAAACtgEAAO0SAAA=\",\"\"]Ao[\"AAAAAAAAAAACtwEAAPASAAA=\",\"\"]Dc[\"AAAAAAAAAAABHAEAAAAAAAAAAAAAAAWzAAAAjwQAADgHAAAPEAAA6xAAAA==\",\"\"]Fc[\"AAAAAAAAAZABAAAAAAAAAAAAAAAAAAW0AAAAkAQAADkHAAAQEAAA7BAAAA==\",\"AAAAAAAAAAAAAR8HAAAAAocFAABVEQAA\"]Fc[\"AAAAAWYFAAAAAAGqAQAAAocEAAAMDAAAAAAAAAGFDAAAAAAAAAAFtgAAADoHAAA8CQAAERAAAO0QAAA=\",\"AAAAAWYFAAA=\"]Hg[\"AAABlQAAAAH3BAAAAAIeAAAAIQ0AAAAAAfAAAAAAAAAAAAAAAAADsAEAANgEAABcDAAA\",\"AAG1BwAAAAH3BAAAAAAAAAAAAAAAAAAAAAAEQQcAAEIHAABFBwAARgcAAA==\"]Hg[\"AAAAApgAAAAoBQAAAAI+AAAAJA0AAAE9AQAAAAAAAAAAAAAAAAADsQEAANsEAABeDAAA\",\"AAHMBwAAAAEoBQAAAAAAAAAAAAAAAAAAAAAEQwcAAEQHAABHBwAASAcAAA==\"]Bc[\"AAAAAAAAAAAAAAAAAAAAAAGREwAA\",\"\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAXcAAAA=\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAm0AAAAXBwAA\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAnIAAAAcBwAA\",\"\"]Bg[\"AAAAAAAAAAAAAXQEAAA=\",\"AAAAASANAAA=\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAbIAAAA=\",\"\"]Hc[\"AAAAAAABCBAAAAE5FAAAAAFhFAAAAaMUAAAAAAAAAAAAADowAAABAAAAAAANABAAAAAFBzsJfgmrDhYQGRBVEC8RPxHdEQ8TERPwEyUU\",\"AAAAAAAB/RIAAAHlEwAA\"]Ak[\"AAAAAAI/FAAAQBQAAA==\",\"\"]Cg[\"AAAAAUITAAABdQkAAAGBBAAAAAABhgQAAA==\",\"AAAAAAFdDAAA\"]Ag[\"\",\"AAAAAAAAAegGAAA=\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAm4AAADODwAA\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAnMAAADQDwAA\",\"\"]Ag[\"AAAAAAAAAAH2BgAA\",\"\"]Dk[\"AAAAAAAAOzAAAAEAAKkAAwC5ACkAKgFwAJ0BDgAAOzAAAAEAAEgAAgDjAEYAmwEBAA==\",\"\"]Hg[\"AAAAAlYFAAAhBwAAAXIJAAAEYAAAAK4TAACvEwAAGhQAAAORAQAAkgEAAKYUAAABbAMAAAABeQwAAAABChMAAAAAAfoGAAA=\",\"AAABEhAAAAFWBQAAAdcBAAAAAd0QAAA=\"]Dk[\"AAAAAQkFAAAAASgAAAAAAewGAAACCAEAAAkBAAA=\",\"AAAAAgkFAABnCgAAAAAAAd4GAAA=\"]Dk[\"AAAAAUAFAAAAAU4AAAACYgEAAGMBAAAB8QYAAA==\",\"AAAAAUAFAAAByQoAAAAB4wYAAA==\"]Ck[\"AAAAAU4QAAAAAAAAAqgHAADsBwAA\",\"AAAAAAAAAAACuRAAALoQAAA=\"]Ck[\"AAAAAU8QAAAAAAAAAqsHAADuBwAA\",\"AAAAAAAAAAACuxAAALwQAAA=\"]Bk[\"\",\"AAAAAAAAAAAAAAAAAAAAAAAAAjgRAAC5EgAA\"]Ek[\"AAAAAw0IAACpEgAAFRQAAAAAAAAAAAHrBwAAAAABpwcAAA==\",\"AAAAAAAAAALGEQAAxxEAAAK5EAAAuhAAAA==\"]Ek[\"AAAAAw4IAACqEgAAFhQAAAAAAAAAAAHtBwAAAAABqgcAAA==\",\"AAAAAAAAAALIEQAAyREAAAK7EAAAvBAAAA==\"]Cg[\"AAAAAf4EAAAAASQAAAAAAAL5AAAA+gAAAA==\",\"AAAAAf4EAAA=\"]Dg[\"AAAAATQFAAAAAUoAAAACUAEAAFEBAAA=\",\"AAAAATQFAAAAAAAAAAF5DAAAAAEKEwAA\"]Bo[\"AAAAAfQEAAAAARsAAAAAAAHrAAAA\",\"AAAAAfQEAAA=\"]Cc[\"AAAB8AIAAAEkBQAAAAE6AAAAATcBAAA=\",\"AAAAASQFAAA=\"]Ig[\"AAABlwAAAAH5BAAAAAIgAAAAIw0AAAAAAfIAAAAAAAAAAAAAAAA6MAAAAQAAAAAADQAQAAAA6AEaAkEHRQdDCUcJxQ8KENQQOROyE8IT4BOSFA==\",\"AAG3BwAAAAL5BAAAkwUAAAABzA8AAA==\"]Ic[\"AAAAApoAAAAqBQAAAAJAAAAAJg0AAAE/AQAAAAAAAAAAAAAAAAA6MAAAAQAAAAAADQAQAAAA6gEbAkMHRwdFCUkJxw8MENYQOhOzE8MT4ROTFA==\",\"AAHOBwAAAAEqBQAAAAABzw8AAA==\"]Gg[\"AAACSwIAAFMCAAAB6wQAAAACEQAAADEHAAAC1QAAANYAAAA=\",\"AAACTQIAAE4CAAAB6wQAAAAAAAAC2A8AANkPAAAAAAJ9BAAAShAAAAAAAYIMAAA=\"]Do[\"AAAAASwFAAAAAkEAAAClAAAAA0EBAABCAQAAPhAAAAFiAwAA\",\"AAAAASwFAAAAAAAAAWEEAAA=\"]Ck[\"AAAAAh0UAAAeFAAAAAAAAAAAAAABhQwAAA==\",\"AAAAAAABNREAAA==\"]Co[\"AAAAAAABHQcAAAO6EgAAuxIAALwSAAAAAb0BAAA=\",\"AAAAAAABCBAAAA==\"]KBa[\"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\",1]Eo[\"AAAAAAA7MAAAAQAABQABAPoSBQA7MAAAAQAABQABAOITBQAAOzAAAAEAAAUAAQBeFAUAOzAAAAEAAAUAAQCgFAUA\",\"\"]Ck[\"AAAAAAAAAYMFAAAAAAFtBAAA\",\"AAAAAAAAA3EMAAByDAAAcwwAAA==\"]Dg[\"AAAAARUFAAAAAAHPBAAAAa4RAAABHgEAAA==\",\"AAHCBwAAAAEVBQAAAAAAAa4RAAA=\"]Dc[\"AAAAAV8FAAAAAAKgAQAA0wQAAAG1EQAA\",\"AAHbBwAAAAFfBQAAAAAAAbURAAA=\"]Fg[\"AAAAAfYEAAAAAR0AAAAAAALuAAAA7wAAAA==\",\"AAG0BwAAAAH2BAAAAARpBAAAOwcAAOMRAABDFAAAAXAJAAAAAv8TAABdFAAA\"]Fc[\"AAAAAScFAAAAAT0AAAACOwEAADwBAAA=\",\"AAHLBwAAAAEnBQAAAAACSRAAAN0QAAACfAwAACAUAAAAAAG1FAAAAYgUAAA=\"]Eo[\"AAAAAeERAAABbgkAAAACvwAAAGYMAAABoxEAAAABcQQAAA==\",\"AAAAAAHSBQAAAAJmDAAABRAAAAABpAcAAAG0EAAA\"]Dk[\"AAAABrISAAANEwAAhBQAAJAUAACyFAAAwxQAAAAAAAAB6BMAAA==\",\"AAAAAAAAAAGbEQAA\"]Oo[\"AAAAOjAAAAEAAAAAABEAEAAAAIoE4QSRBcwG/gd5CUgMdwyUDCANSBBfEFgR+BLrEx8UQhSLFAezEAAAtRIAAD0TAACHEwAAjBMAAMYUAADKFAAAOjAAAAEAAAAAAAcAEAAAAOcBZwR4DMwPTxGkEQkTrRM6MAAAAQAAAAAABwAQAAAAwABZEfQSfxSqFLMU0RTTFAFsBQAAAAAAAAAAAAE6FAAA\",\"AAAAAeEEAAAB/ggAAAABZgwAAA==\"]ACk[\"AAAAOjAAAAEAAAAAAAgAEAAAAIsEXwlqDMEPCxAsEb0RVxRYFDowAAABAAAAAAAHABAAAABrBH8FlQUMCF0MnBH5EkEUOjAAAAEAAAAAAAoAEAAAAOkBewRNEFIQxxDlEKIRthKGE40T8xM6MAAAAQAAAAAABwAQAAAAKgHPD0kQ3RD1EpEU0hTUFAVvBQAAfAwAACAUAACAFAAAtBQAAAS0EgAAGBQAAHEUAACrFAAAAnkMAACmDgAAAbUUAAADVREAAAoTAACIFAAA\",\"AAAAAAAAAAAAAAAAAAGlBwAAAAAAAAHUBQAA\"]Io[\"AAAAA0wHAADxBwAArBEAAANjCQAAUwwAAKwUAAABQxEAAAFZFAAAA/wHAADKEQAAzhEAAAABLRAAAAG0EwAAAQgHAAA=\",\"AAAAAARTDAAAVAwAAFUMAABWDAAAAAXOBAAAzwQAANAEAADRBAAA9BIAAA==\"]Io[\"AAAAA1AHAAD1BwAAsxEAAANnCQAAVwwAAK8UAAABRxEAAAFbFAAAA/0HAADMEQAA0BEAAAABLxAAAAG4EwAAAQwHAAA=\",\"AAAAAARXDAAAWAwAAFkMAABaDAAAAAXSBAAA0wQAANQEAADVBAAA9RIAAA==\"]No[\"AAABlgAAAAH4BAAAAAIfAAAAIg0AAAAAAfEAAAAAAAAAAAAAAAA6MAAAAQAAAAAAJAAQAAAAEQJqBNUGPgc9CXMJdAx6DIYMBhAoELYQ4xDoEPUQLRE5EUARSxFNEakR0hHlEawSrhL2EgMTPhNAE4MTxBPGE/sTAhQhFEQUhRQ=\",\"AAG2BwAAAAH4BAAAAAAAAAAAAAAAAAAAAAAEQwkAAEQJAABHCQAASAkAAA==\"]No[\"AAAAApkAAAApBQAAAAI/AAAAJQ0AAAE+AQAAAAAAAAAAAAAAAAA6MAAAAQAAAAAAJAAQAAAAEgJsBNkGPwc+CXQJdQx7DIcMBxApELcQ5BDpEPYQLhE6EUERTBFOEaoR0xHmEa0SrxL3EgQTPxNBE4QTxRPHE/wTAxQiFEUUhhQ=\",\"AAHNBwAAAAEpBQAAAAAAAAAAAAAAAAAAAAAERQkAAEYJAABJCQAASgkAAA==\"]Cc[\"AAAAARAFAAAAAAAAARUBAAA=\",\"AAAAARAFAAAAAAENDAAA\"]Ck[\"AAAAAVEFAAAAAAGIAQAA\",\"AAAAAVEFAAAAAAMODAAADwwAABAMAAA=\"]Bk[\"AAAAAAAAAAAAAAAAAAGCBAAAAAAAAYQMAAA=\",\"\"]Bg[\"\",\"AAAAAAAAAAAAAAAAAAAAAAAAAb0PAAA=\"]Eo[\"AAAAAUEFAAAAAVAAAAACZgEAAGcBAAAB8gYAAA==\",\"AAAAAUEFAAAAAAHkBgAAAAAAAAAAAAAAAAACdQUAAL4PAAA=\"]Bk[\"\",\"AAAAAAAAAAAAAAAAAAAAAAAAAsEUAADCFAAA\"]Ak[\"AAAAAAAAAAABcRQAAA==\",\"\"]Ag[\"AAACsQIAADMDAAA=\",\"\"]Bo[\"AAAAAAAAAckAAAAAAAABfwwAAA==\",\"AAAAAUgQAAA=\"]Fc[\"AAAAAfMEAAAAAxoAAAB4DAAACRMAAAHbDwAAAAHqAAAA\",\"AAGzBwAAAAHzBAAAAAE7BwAAAAAAAAAAAAAAAAAAATwHAAA=\"]Gc[\"AAAAASMFAAAAATkAAAABNgEAAAABtBIAAAF5DAAAAAEKEwAA\",\"AAHKBwAAAAEjBQAAAAGGEwAAAUkQAAAAAbQSAAAAAAAAAAAAAAABPQcAAA==\"]Do[\"AAAAAAAAAAHcDwAAApcFAAANEAAA\",\"AAAAAb4BAAAAAAAB3A8AAASXBQAADRAAAJ4RAACfEQAA\"]Do[\"AAAAAAAAAAHdDwAAApgFAAAOEAAA\",\"AAAAAckBAAAAAAAB3Q8AAASYBQAADhAAAKARAAChEQAA\"]Ao[\"AAAAAAG4EAAAAAAB3hAAAA==\",\"\"]Fk[\"AAABuhEAAAJpAAAA7wcAAAEhAgAAAAHRAAAAApMEAADeEQAAAtgPAABLEAAAAAHaDwAAAn0EAABKEAAAAAGBDAAAAoIMAACDDAAA\",\"\"]Cc[\"AAAAAAAAAAN+BAAAlQQAAOARAAA=\",\"AAAAAAABTRAAAA==\"]Ec[\"AAABTQIAAAHsBAAAAAISAAAAMgcAAALXAAAA2AAAAA==\",\"AAABUAIAAAHsBAAAAAAAAt4RAADfEQAA\"]Bo[\"AAABOxEAAAAAAALMAAAAzQAAAA==\",\"AAAAAAGGBQAA\"]Ck[\"AAABPBEAAAAAAAJ3AQAAeAEAAA==\",\"AAAAAAAAAAAAAAABhwUAAA==\"]Ao[\"AAAAAAAAAcETAAAByQ8AAA==\",\"\"]Hg[\"AAAAAmsAAAAHCAAAASICAAABegkAAAPdAAAA9wYAAPMQAAAChAUAAGUQAAAAAp0FAADfDAAABtEPAADSDwAA0w8AAP0QAABUEQAAGxMAAAAAAtQPAADVDwAAAdYPAAA=\",\"\"]Dk[\"AAAAAAABewkAAAL4BgAACAgAAAKFBQAA9BAAAAABngUAAAH+EAAA\",\"AAAAAAABUhAAAA==\"]Bc[\"AAAAAAAAAAAAAAAAAAAAAAGREwAA\",\"\"]AAk[\"AAABEhAAAAHJBgAAAtcBAAArBwAABMgGAAB4DAAACBAAAAkTAAACfxQAALMUAAADmxEAAIAUAAC0FAAABaQHAACeEQAAnxEAAKARAAChEQAAAbQQAAAAAAABpQcAAA==\",\"AAAABCEHAADhEQAA4hEAAOQRAAADrAEAAMMPAADEDwAAAeMRAAAFqhQAANEUAADSFAAA0xQAANQUAAABIBQAAAGrFAAAAbMSAAAAAAAAAAAAAuEPAAD7DwAAOzAAAAEAAAUAAgATAgIADxACAA==\"]Cc[\"AAAAAUIFAAAAAVEAAAACaAEAAGkBAAA=\",\"AAAAAUIFAAA=\"]Dc[\"AAAAAAAAAAA7MAAAAQAABQABAO0SBQA=\",\"AAAABOABAADkAQAA5QEAAOYBAAA=\"]Ao[\"AAAAAeABAAAAAAAAAVgQAAA=\",\"\"]Ao[\"AAAAAeUBAAAAAAAAAVkQAAA=\",\"\"]Eo[\"AAAAAbwRAAABqhMAAAPLEAAAUREAAKYRAAABBhQAAAHHEQAAAboQAAAC1xEAAL4SAAABwREAAAAAAAABPBQAAA==\",\"\"]Eo[\"AAAAAb8RAAABrBMAAAPPEAAAUxEAAKgRAAABCRQAAAHJEQAAAbwQAAAC2REAAMASAAABwxEAAAAAAAABPhQAAA==\",\"\"]Bc[\"AAAAAAAAApYEAAD/BgAAAAFaEAAA\",\"\"]Bc[\"AAAAAAAAApkEAAACBwAAAAFbEAAA\",\"\"]Ec[\"AAAAAdoBAAAAAg4TAAAQEwAAAgAUAAABFAAAAocEAAAMDAAAAAAAAAABggQAAA==\",\"AAAAAAFJAwAA\"]Gg[\"AAAAAe4EAAAAAhQAAAA0BwAAAt4AAADfAAAA\",\"AAADVwIAAFgCAABZAgAAAe4EAAAAAAABZRAAAAACnQUAAN8MAAAC/RAAABsTAAAAAAAB1g8AAA==\"]Eg[\"AAAAAS8FAAAAAkQAAACnAAAAA0YBAABHAQAAQBAAAAFkAwAA\",\"AAAAAS8FAAAAAAAAAAGeBQAAAf4QAAA=\"]Ec[\"AAAAAAAAAXYBAAAAAAAAAAAAAAAAAALdBAAAfwkAAA==\",\"AAAAAAAAAAAAAAAAAAAAAAAAAdQFAAA=\"]Ec[\"AAABUQwAAAHgBAAAAAELAAAAAbsAAAA=\",\"AAAAAeAEAAAAAVEJAAAAAAAAAAAAAAAAAAABxgYAAA==\"]Dg[\"AAAAAAGBBQAAAAAAAAAAAAAAAAAAAAL4EAAAnREAAA==\",\"AAGuBwAAAAAAAcgGAAA=\"]Ac[\"AAABewIAAA==\",\"\"]Do[\"AAAAAQgFAAAAAScAAAAAAAIGAQAABwEAAA==\",\"AAACiQIAAIoCAAABCAUAAAAAAAAAAe4TAAA=\"]Dk[\"AAAAAT8FAAAAAU0AAAACYAEAAGEBAAA=\",\"AAAC/AIAAP0CAAABPwUAAAAAAAAAAe8TAAA=\"]Bk[\"\",\"AAAAAAAAAAAAAAAAAAAAAAAAAlYJAABYCQAA\"]Dk[\"AAAAAAAAAAABJAEAAA==\",\"AAABpgIAAAAAAAAAAAABgwQAAAFKBwAAAWMEAAABpwcAAA==\"]Dg[\"AAAAAAAAAZkBAAA=\",\"AAABLQMAAAAAAAAAAAABhAQAAAFLBwAAAWUEAAABqgcAAA==\"]Dk[\"AAAAARcFAAAAAcYBAAAB0AQAAAGwEQAAASABAAA=\",\"AAHDBwAAAAEXBQAAAAAAAbARAAA=\"]Dk[\"AAAAAWEFAAAAAdgBAAACogEAANQEAAABtxEAAA==\",\"AAHcBwAAAAFhBQAAAAAAAbcRAAA=\"]Ck[\"AAAAATAFAAAAAUUAAAACSAEAAEkBAAA=\",\"AAAAATAFAAAAAAH4BgAA\"]Eg[\"AAAAAQMFAAAAAiYAAACdAAAAATYQAAABWgMAAAIBAQAAARMAAA==\",\"AAG+BwAAAYICAAACAwUAAGEKAAA=\"]Eg[\"AAAAAToFAAAAAkwAAACoAAAAAlsBAABBEAAAAWUDAAABAhMAAA==\",\"AAHWBwAAAfcCAAABOgUAAAHDCgAA\"]Ao[\"AAAAAAAAAAAC5hAAAJ4RAAA=\",\"\"]Ao[\"AAAAAAAAAAAC5xAAAKARAAA=\",\"\"]Do[\"AAABegAAAAHfBAAAAAABugAAAA==\",\"AAAAAd8EAAAAAVAJAAAAAAAAAAAAAAAAAAAB6hAAAA==\"]Bg[\"AAAAAAAABNwAAACLBQAAcwwAALwSAAA=\",\"\"]Ak[\"AAAAAAABRgAAAAFKAQAA\",\"\"]Do[\"AAAAAAAAAAAAAAAAAAAAAAAAAjgRAAC5EgAA\",\"AAGsBwAAAAAAAAAAAAAAAAAAAAAAAAH3EAAA\"]Cg[\"AAAAAAP5BgAAeAkAAEIRAAA=\",\"AAAAAAAAAALJDwAA3hAAAA==\"]Ag[\"AAACpgIAAC0DAAA=\",\"\"]Cc[\"AAAAAYUKAAAChgoAAPcKAAAB9QoAAAL5EAAA+xAAAA==\",\"\"]Ac[\"\",\"AAAAAfMSAAA=\"]Gg[\"AAAAAQcFAAABhxQAAAAAAQAJAAAEBQEAAFoQAAAUEwAAbBQAAAG0EAAA\",\"AAAAA8IBAAAHBQAAlBQAAAABSBQAAAABAAkAAANaEAAAFBMAAGwUAAA=\"]Bc[\"AAAAAcIBAAAAAAAAAAAAAV4RAAA=\",\"\"]Gg[\"AAAAAT4FAAAAAAFfAQAAAQEJAAADWxAAABYTAABtFAAAAAAAAAGlBwAA\",\"AAAAA9ABAAA+BQAAlRQAAAABSRQAAAABAQkAAANbEAAAFhMAAG0UAAA=\"]Bc[\"AAAAAdABAAAAAAAAAAAAAWIRAAA=\",\"\"]Fg[\"AAABVwIAAAH8BAAAAAEiAAAAAAAC9QAAAPYAAAA=\",\"AAG6BwAAAAH8BAAAAAF6CQAAAvcGAADzEAAAA5MEAACUBAAAhAUAAA==\"]Eo[\"AAAAATIFAAAAAUgAAAACTAEAAE0BAAA=\",\"AAHSBwAAAAEyBQAAAAF7CQAAAQgIAAAElQQAAIUFAAD0EAAA4BEAAA==\"]Ao[\"\",\"AAAAAAAAAAAAAAABZhEAAA==\"]Ek[\"AAAAAQwFAAAAASsAAAAAAAIOAQAADwEAAA==\",\"AAABkAIAAAEMBQAAAAAAAAGkBwAAAbQQAAAAAl4RAABgEQAA\"]Eg[\"AAAAAUgFAAAAAVYAAAACdAEAAHUBAAA=\",\"AAABCgMAAAFIBQAAAAAAAAAAAAJiEQAAZBEAAAABpQcAAA==\"]Ao[\"AAAAAAAAAAHREAAAAAGPDAAA\",\"\"]Jk[\"AAAAAQ0FAAAABSwAAACeAAAAcQUAAMkQAADLEAAAAg0MAAA3EAAABFsDAABfDAAAyhAAAMwQAAAEEAEAABEBAACeEQAAnxEAAAKLDAAAjAwAAALAEQAAwREAAA==\",\"AAAAAg0FAADhEAAAAsMBAADCEAAAAAABSwkAAA==\"]Og[\"AAAAAU0FAAAABVkAAACrAAAAcgUAAM0QAADPEAAABoEBAACCAQAADgwAAA8MAAAQDAAARBAAAARoAwAAYAwAAM4QAADQEAAAAqARAAChEQAAAo0MAACODAAAAsIRAADDEQAA\",\"AAAABE0FAADjBwAA5AcAAOIQAAAE0gEAAMMQAADEEAAAxRAAAAAAAUwJAAACfAUAAH0FAAAAAAAAAAAAAAA7MAAAAQAACgACAOUHBQACCAQA\"]Cc[\"AAAAAUMFAAAAAVIAAAACagEAAGsBAAA=\",\"AAAAAUMFAAA=\"]Ck[\"AAABeAUAAAEOBQAAAAEtAAAAAAABEgEAAA==\",\"AAAAAg4FAABhDAAA\"]Dg[\"AAABeQUAAAFOBQAAAAFaAAAAAYMBAAAAAAAAAAAAAfoGAAA=\",\"AAAAAk4FAABiDAAA\"]Cc[\"AAAAAWcFAAAAAWYAAAABqwEAAA==\",\"AAAAAmcFAABjDAAA\"]Dg[\"AAAAAAAAAAAAAAABVREAAA==\",\"AAAABsYQAAAAEwAA7BMAAO0TAAAdFAAAHhQAAA==\"]Ak[\"AAAAAAAAAAAB7xIAAA==\",\"\"]Ak[\"AAAAAAAAAAAB8hIAAA==\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAnAAAAATAgAA\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAnUAAAAUAgAA\",\"\"]Bg[\"AAAAAAAAAAAAAXYEAAA=\",\"AAAAAVgRAAA=\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAm8AAAA8BwAA\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAnQAAAA9BwAA\",\"\"]Bo[\"AAAAAAAAAAAAAAAECAcAAAkHAAAKBwAACwcAAA==\",\"\"]Ec[\"AAAABK0BAAC4AQAAIgUAALUQAAAAAAE1AQAAAAAAAAQMBwAADQcAAA4HAAAPBwAA\",\"AAAAASIFAAA=\"]Bg[\"AAAAAAAAAAAAAXcEAAA=\",\"AAAAAAGHEwAA\"]Cc[\"AAAAAYoKAAACiAoAAPkKAAAB+goAAAL6EAAA/BAAAA==\",\"\"]Ag[\"AAAAAAABLQcAAA==\",\"\"]No[\"AAAAAQUFAAABKwcAAAFDEwAAAQUQAAAGbwwAANgQAADpEwAA/RMAAGQUAACMFAAABwMBAAC9AQAA2AwAAFgQAAATEwAA9BMAAPYTAAABbhQAAAAAAAAAAAAAA9YGAADcDAAAFxMAAA==\",\"AAAAAgUFAABhEAAAAAHBAQAAARkTAAAGbwwAANgQAADpEwAA/RMAAGQUAACMFAAABdgMAABYEAAAExMAAPQTAAD2EwAA\"]Bo[\"AAAAAWEQAAAAAcEBAAAAAsoQAABfEQAAAAGLDAAA\",\"\"]Nc[\"AAAAATwFAAAAAAFdAQAAOjAAAAEAAAAAAAcAEAAAAMoBcAzZEEQT6hP+E2UUjRQF2QwAAFkQAAAVEwAA9RMAAPcTAAAAAAAAAAAAAAAE1AUAANoGAADdDAAAGBMAAA==\",\"AAAAAjwFAABiEAAAAAHPAQAAARoTAAAGcAwAANkQAADqEwAA/hMAAGUUAACNFAAABdkMAABZEAAAFRMAAPUTAAD3EwAA\"]Bo[\"AAAAAWIQAAAAAc8BAAAAAs4QAABjEQAAAAGNDAAA\",\"\"]Ao[\"AAAAAAAAAAAC9BMAAPUTAAA=\",\"\"]Ao[\"AAAAAAAAAAAC9hMAAPcTAAA=\",\"\"]Dc[\"AAABWAIAAAHvBAAAAAIVAAAANQcAAALgAAAA4QAAAA==\",\"AAABXAIAAAHvBAAA\"]Bk[\"AAAAAeMBAAAAAATbAAAAigUAAHIMAAC7EgAA\",\"\"]Ec[\"AAAAAqQTAAClEwAAAqITAACjEwAABE0MAABODAAATwwAAFAMAAAESQwAAEoMAABLDAAATAwAAA==\",\"\"]Cc[\"AAAAAAAAAAAB7hIAAA==\",\"AAGvBwAAAAK8AQAAgRMAAA==\"]Cc[\"AAAAAAAAAAAB8RIAAA==\",\"AAHGBwAAAALIAQAAghMAAA==\"]Bc[\"\",\"AAAAAAAAAAAAAAAAAAAB+gYAAA==\"]Cg[\"AAAAAcIPAAAAAAGAAQAAAcgQAAA=\",\"AAHiBwAAAAAAAfMTAAA=\"]Ek[\"AAAAAeYEAAAAAAHGAAAAAAAAAAAAAAAAAAAEfRMAAIUTAAChEwAAvBMAAA==\",\"AAGwBwAAAALmBAAAkgUAAA==\"]Cc[\"AAAAAAAAAAAAAAAAAAAAAAAAA3wJAABWEAAAwRQAAA==\",\"\"]Cc[\"AAAAAAAAAAAAAAAAAAAAAAAAA30JAABXEAAAwhQAAA==\",\"\"]Bk[\"\",\"AAAAAAAAAAAAAAAAAAAAAAAAAlcJAABZCQAA\"]Ac[\"\",\"AAAAAAHKBAAA\"]Bc[\"AAAAAAABYgQAAA==\",\"AAAAAAHLBAAA\"]Ec[\"AAAAARkFAAAAAccBAAAB0QQAAAKyEQAAGxQAAAEiAQAA\",\"AAHEBwAAAAEZBQAAAAAAArIRAAAbFAAA\"]Ec[\"AAAAAWMFAAAAAdkBAAACpAEAANUEAAACuREAABwUAAA=\",\"AAHdBwAAAAFjBQAAAAAAArkRAAAcFAAA\"]Ag[\"\",\"AAAAAAABsBIAAA==\"]Co[\"AAAAArIBAADjBwAAAAABeQEAAA==\",\"AAAAAAAAAAAAAAAAAAAAAdEBAAA=\"]Ac[\"\",\"AAAAAAFGEwAA\"]Cg[\"AAAAAAAAAhkTAAAaEwAAAAABsxIAAA==\",\"AAAAAAAAAAH4EwAA\"]Do[\"AAAAAS0FAAAAAkIAAACmAAAAA0MBAABEAQAAPxAAAAFjAwAA\",\"AAAAAS0FAAAAAAABfgQAAA==\"]Ak[\"AAAAAAACSBQAAEkUAAA=\",\"\"]Ac[\"AAAAAeQBAAA=\",\"\"]Ac[\"AAAAAeYBAAA=\",\"\"]Eo[\"AAACEwMAACcDAAABVwUAAAABYQAAAAKTAQAAlAEAAA==\",\"AAAAAVcFAAAAAAAF3AYAAN0GAADeBgAA3wYAAOAGAAA=\"]Fk[\"AAAABNoQAADbEAAA7BMAAO0TAAABww8AAAE1EQAAAbESAAAAAAFuFAAAAAAAAAAAAAGEDAAA\",\"AAAAAAAAA7oSAAC7EgAAvBIAAA==\"]Fc[\"AAAAAAAByAYAAAIZEwAAGhMAAAGbEQAAAAGzEgAAAAACRhQAAEcUAAAAAAABkRMAAA==\",\"AAAAAAAAAYMUAAAB+BMAAA==\"]Co[\"AAAAAf8EAAAAASUAAAAAAAL7AAAA/AAAAA==\",\"AAAAAf8EAAAAAW4FAAA=\"]Co[\"AAAAATUFAAAAAUsAAAACUgEAAFMBAAA=\",\"AAAAATUFAAAAAAAAAAGmDgAA\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAARsHAAA=\",\"\"]Ik[\"AAAAAgQFAABqFAAAAAAAAqcSAAB/EwAABQIBAAC9AQAAiQwAADMRAACOEwAAA4AJAADuEwAAbhQAAA==\",\"AAG/BwAAAALAAQAABAUAAAAAAAKnEgAAfxMAAAOJDAAAMxEAAI4TAAACgAkAAO4TAAA=\"]Ig[\"AAAAAjsFAABrFAAAAAABXAEAAAPKAQAAqBIAAIATAAADigwAADQRAACPEwAAAoEJAADvEwAA\",\"AAHXBwAAAALOAQAAOwUAAAHQBgAAAAACqBIAAIATAAADigwAADQRAACPEwAAAoEJAADvEwAA\"]Ao[\"AAAAAAAAAc4AAAAAAAF5BAAA\",\"\"]Bc[\"AAAAAAABTwAAAAJkAQAAZQEAAA==\",\"\"]Ag[\"AAACswIAADcDAAA=\",\"\"]Ak[\"AAAAAAAAApcEAAAABwAA\",\"\"]Ak[\"AAAAAAAAApoEAAADBwAA\",\"\"]Ag[\"AAACtQIAADIDAAA=\",\"\"]Dk[\"AAAAAe0EAAAAAhMAAAAzBwAAAtkAAADaAAAA\",\"AAABUwIAAAHtBAAAAAAAAAAAAdoPAAA=\"]Dg[\"AAAAAeoEAAAAAhAAAAAwBwAAAtMAAADUAAAA\",\"AAABSwIAAAHqBAAAAAAAAAFLEAAA\"]Co[\"AAABtxIAAAExBQAAAAFHAAAAAUsBAAA=\",\"AAHRBwAAAUIJAAABMQUAAA==\"]Ec[\"AAAAAAABuBIAAAG8AAAAAAAAAAAAAAAAAAADjgQAAD8JAADeDwAA\",\"AAAAAboBAAAAAAAAAegTAAA=\"]Hk[\"AAAC6wIAAAUDAAABTwUAAAABWwAAAAKEAQAAhQEAAAAAAAAAAAAAAAAABBkCAAB1BQAA2wYAABoHAAA=\",\"AAABGQMAAALTAQAATwUAAAAAAggUAAAJFAAAAAAC2BEAANkRAAA=\"]Fc[\"AAAAA8YQAADcEAAAABMAAAACDhMAABATAAADqAEAAAAUAAABFAAAAocEAAAMDAAAAAAAAAABggQAAA==\",\"AAAAAdoBAAA=\"]Dc[\"AAAAAmgAAADpBAAAAiACAAD/CAAAAQ8AAAAB0AAAAA==\",\"AAAAAukEAABgEAAA\"]Eg[\"AAAAAQoFAAAAASkAAAAAAAIKAQAACwEAAAAAAAAAAAAAAAMVBwAAtg8AAL0PAAA=\",\"AAABjQIAAAEKBQAA\"]Eg[\"AAAAAUQFAAAAAVMAAAACbAEAAG0BAAAAAAAAAAAAAAAAAAMYBwAAuA8AAL4PAAA=\",\"AAABBQMAAAFEBQAA\"]Io[\"AAAFPAIAAE4CAABQAgAAWQIAAFwCAAAB/QQAAAACIwAAADYHAAAAAAL3AAAA+AAAAA==\",\"AAADeQIAAHoCAAB7AgAAAv0EAADMBgAAAtIFAACzEAAAAa0TAAABBRAAAAGjEQAAAAAAAAAAAAAAAAEUBwAA\"]Ek[\"AAAAATMFAAAAAkkAAAA3BwAAAk4BAABPAQAA\",\"AAAB6wIAAAEzBQAAAAAAAAAAAAAAAAAAAAAC3QQAABsHAAA=\"]Bk[\"AAAAAAABxxAAAAF/AQAA\",\"AAAAAAAAAAHIEAAA\"]Hg[\"AAABlwAAAAH6BAAAAAEjDQAAAAAB8wAAAAAAAAAAAAAAAAZCBwAARgcAAEQJAABICQAAxg8AANUQAAA=\",\"AAG4BwAAAAL6BAAAkQUAAAAAAAAAAAAAAAAAAAAAAZQFAAA=\"]Fo[\"AAAAApoAAAArBQAAAAEmDQAAAUABAAAAAAAAAAAAAAAAAAZEBwAASAcAAEYJAABKCQAAyA8AANcQAAA=\",\"AAHPBwAAAAErBQAAAZUFAAA=\"]Ag[\"AAACsAIAADQDAAA=\",\"\"]Cg[\"AAAAAWkFAAAAAAADyQ8AADEQAADeEAAA\",\"AAHhBwAAAAFpBQAA\"]Dc[\"AAAAAkkFAADkBwAAAAABegEAAA==\",\"AAAAAUkFAAAAAAAAAAAAAAAAAAHlBgAA\"]Dg[\"AAAAAZITAAABYxAAAAAAAAL9AAAAnxEAAA==\",\"AAFNCQAAAb8BAAABkhMAAAFjEAAA\"]Dg[\"AAAAAZMTAAABZBAAAAABVwEAAAABoREAAA==\",\"AAFOCQAAAc0BAAABkxMAAAFkEAAA\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAbsPAAA=\",\"\"]Ac[\"\",\"AAAAAAFyFAAA\"]Ag[\"AAACrwIAADUDAAA=\",\"\"]Bc[\"\",\"AAAAAAAAAAAD7RIAAO4SAADvEgAA\"]Bc[\"\",\"AAAAAAAAAAAD8BIAAPESAADyEgAA\"]Ak[\"\",\"AAAAARITAAAAAAE5FAAA\"]ADg[\"AAAAAQAFAAAAAAA6MAAAAQAAAAAAFAAQAAAA+QcnDcoPFBAXEFMQWhHbEe4RCxN5EyMUgRSOFJwUnhTEFMgUyxTPFNUUOjAAAAEAAAAAAA0AEAAAAP4A5hBWEQETExMUE7AT3hPxE6gUuxS9FL8UzRQAAAHUEQAA\",\"AAG7BwAAAAEABQAAAAAAOjAAAAEAAAAAABQAEAAAAPkHJw3KDxQQFxBTEFoR2xHuEQsTeRMjFIEUjhScFJ4UxBTIFMsUzxTVFDowAAABAAAAAAAKABAAAADmEFYRAROwE94T8ROoFLsUvRS/FM0UAAAB1BEAAA==\"]ADk[\"AAAAATcFAAAAAAFYAQAAOjAAAAEAAAAAABQAEAAAAPoHKA3LDxUQGBBUEFsR3BHvEQwTehMkFIIUjxSdFJ8UxRTJFMwU0BTWFDowAAABAAAAAAAMABAAAADnEFcRAhMVExYTsRPfE/ITqRS8FL4UwBTOFAAAAdURAAA=\",\"AAHTBwAAAAE3BQAAAAAAOjAAAAEAAAAAABQAEAAAAPoHKA3LDxUQGBBUEFsR3BHvEQwTehMkFIIUjxSdFJ8UxRTJFMwU0BTWFDowAAABAAAAAAAKABAAAADnEFcRAhOxE98T8hOpFLwUvhTAFM4UAAAB1REAAA==\"]Ag[\"AAACiQIAAP0CAAA=\",\"\"]Bk[\"AAAAAAAAAAAAAW4EAAA=\",\"AAAAAAAAAWYMAAA=\"]Bk[\"AAAAAAAAAAAAAAF+DAAA\",\"AAAAAAABQxQAAA==\"]Do[\"AAAAAQsFAAAAASoAAAAAAAIMAQAADQEAAAAAAAAAAAAAAAIWBwAAtw8AAA==\",\"AAAAAQsFAAA=\"]Do[\"AAAAAUUFAAAAAVQAAAACbgEAAG8BAAAAAAAAAAAAAAAAAAIZBwAAuQ8AAA==\",\"AAAAAUUFAAA=\"]Ag[\"AAACrAIAADkDAAA=\",\"\"]Fc[\"AAAAAUcFAAAAAlUAAACpAAAAAnIBAABCEAAAAmYDAADIEAAA\",\"AAAAAUcFAAAAAAAAAAAAAAABpQcAAAAAAAAB1AUAAA==\"]Ao[\"AAAAAAHKBAAA\",\"AAAAAAGmBwAA\"]Ao[\"AAAAAAHLBAAA\",\"AAAAAAGpBwAA\"]Ag[\"AAACtAIAADYDAAA=\",\"\"]Ag[\"AAACqwIAADsDAAA=\",\"\"]Dg[\"AAAAARsFAAAAAAABiBMAAAAAAAFgEQAA\",\"AAAAAhsFAACFCgAAAYYKAAAAAfkQAAA=\"]Do[\"AAAB2wEAAAFbBQAAAAAAAYoTAAAAAAABZBEAAA==\",\"AAAAAVsFAAAB9woAAAH1CgAAAfsQAAA=\"]Do[\"AAAAAR0FAAAAAAADzBAAAGERAACJEwAAAAGMDAAA\",\"AAAAAh0FAACKCgAAAYgKAAAAAfoQAAA=\"]Eg[\"AAAB3AEAAAFdBQAAAAAAA9AQAABlEQAAixMAAAABjgwAAA==\",\"AAAAAV0FAAAB+QoAAAH6CgAAAfwQAAA=\"]Cc[\"AAAAAAAAApgEAAABBwAAAAEWAQAA\",\"AAHBBwAAAAHEAQAA\"]Cc[\"AAAAAAAAA4oBAACbBAAABAcAAA==\",\"AAHaBwAAAAHVAQAA\"]Cc[\"AAAAAAAAAAAAAAAAAAAAAAAAAxMHAADqBwAABggAAA==\",\"\"]Bg[\"AAAAAAAAAAAAAAAAAAAAAAAAAboPAAA=\",\"\"]Ao[\"AAAAAAAAAc4EAAAAAbsUAAA=\",\"\"]Bk[\"AAAAAAAAAdIEAAAAAbwUAAA=\",\"AAAAAdYEAAA=\"]Eg[\"AAAAAAAAAAAAAAAAAAAAAAAABIIJAAAyEAAA7hAAADsTAAA=\",\"AAHFBwAAAATxBwAA8gcAAPMHAAD0BwAA\"]Eg[\"AAAAAAAAAAAAAAAAAAAAAAAABIMJAAAzEAAA7xAAADwTAAA=\",\"AAHeBwAAAAT1BwAA9gcAAPcHAAD4BwAA\"]Bo[\"AAAAAAABwQEAAAAAAdgMAAAAAYMEAAAAAWMEAAA=\",\"\"]Bo[\"AAAAAAABzwEAAAAAAdkMAAAAAYQEAAAAAWUEAAA=\",\"\"]Ak[\"AAAAAAABrhMAAAEXFAAA\",\"\"]Ao[\"AAAAAAABrxMAAAAAARgUAAA=\",\"\"]Fo[\"AAAAAUwFAAAAAlgAAACqAAAAA30BAAB+AQAAQxAAAAFnAwAA\",\"AAABEwMAAAFMBQAAAAAAAAJ8BQAAfQUAAAAAAAAAAALRAQAA5QYAAA==\"]Bg[\"\",\"AAAAAAAAAAAAAAAAAAAAAAAAARcHAAA=\"]Dg[\"AAAAASUFAAAAATsAAAABOAEAAA==\",\"AAAAASUFAAAAAAAAAAAAAAAAAAAAAAEcBwAA\"]Dg[\"AAAAAeQEAAAAAgwAAAAvBwAAAsMAAADEAAAA\",\"AAABPAIAAAHkBAAAAAABWREAAA==\"]Do[\"AAAAASAFAAAAAjcAAACjAAAAAzEBAAAyAQAAPBAAAAFgAwAA\",\"AAAAASAFAAAAAAAAAYYEAAA=\"]Ek[\"AAAAAQEFAAAAAAAC+RMAAFAUAAAD/wAAAD0RAADeEwAA\",\"AAG8BwAAAAEBBQAAAAAAAvkTAABQFAAAAT0RAAA=\"]Ek[\"AAAAATgFAAAAAAFZAQAAAvoTAABRFAAAAj4RAADfEwAA\",\"AAHUBwAAAAE4BQAAAAAAAvoTAABRFAAAAT4RAAA=\"]Bo[\"AAAAAAAAAAABfAUAAAAAAAAAAALRAQAA5QYAAA==\",\"\"]Fg[\"AAAAAQIFAAAAAAAEYQkAAF0QAADEEQAApxMAAAIAAQAA8RMAAA==\",\"AAG9BwAAAAECBQAAAAAABGEJAABdEAAAxBEAAKcTAAA=\"]Fg[\"AAAAATkFAAAAAAFaAQAABGIJAABeEAAAxREAAKgTAAAB8hMAAA==\",\"AAHVBwAAAAE5BQAAAAAABGIJAABeEAAAxREAAKgTAAA=\"]Ag[\"\",\"AAAAAAAAAYMUAAA=\"]Bc[\"\",\"AAAAAAAAAAAAAAAAAAAAAAGREwAA\"]Co[\"AAAAAfUEAAAAARwAAAAAAALsAAAA7QAAAA==\",\"AAAAAfUEAAAAAAHbDwAA\"]Co[\"AAAAASYFAAAAATwAAAACOQEAADoBAAA=\",\"AAAAASYFAAAAAAAAAbQSAAA=\"]Fg[\"AAABaQIAAAEPBQAAAAEuAAAAAAHtBgAAAhMBAAAUAQAA\",\"AAAClAIAANQGAAABDwUAAAAAAAHfBgAAAAAAAAAAAAAAAAG2DwAA\"]Gc[\"AAABzgIAAALTAQAAUAUAAAABXAAAAAKGAQAAhwEAAAH0BgAA\",\"AAACGgMAANgGAAABUAUAAAAAAeYGAAAAAAAAAAAAAAAAAAIZAgAAuA8AAA==\"]Cc[\"AAAAAAAAAAAAAAAAAAAAAAAAA+YHAAADCAAAAwkAAA==\",\"\"]Cg[\"AAABkAIAAAAAAAAAASUBAAA=\",\"AAAAAAAAAALKEAAAzBAAAA==\"]Cg[\"AAABCgMAAAAAAAGaAQAA\",\"AAAAAAAAAAPOEAAA0BAAANEQAAA=\"]Bo[\"AAAAAtYEAABoBQAAAUYTAAA=\",\"AAAAAmgFAADxEQAA\"]Bo[\"AAAAAAAAAcoAAAAAAAABgAwAAA==\",\"AAAAAR8UAAA=\"]Io[\"AAAAAVgFAAAAAWIAAAABlQEAAAafBQAAXxEAAGERAABjEQAAZREAAGcRAAAAAAAFXhEAAGARAABiEQAAZBEAAGYRAAAAAAAAAAAB1xQAAA==\",\"AAAAAVgFAAAAAAQFFAAABhQAAAgUAAAJFAAAAAF/BAAA\"]Co[\"AAAAAmoAAADwBwAAAAAB0gAAAAKUBAAA3xEAAAHZDwAA\",\"AAABuhEAAA==\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAlYJAAC+EAAA\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAlgJAADAEAAA\",\"\"]Co[\"AAAAARwFAAAAAAAAAigBAAApAQAA\",\"AAAAARwFAAAAAAL5EAAA+hAAAA==\"]Dc[\"AAAAAVwFAAAAAAKdAQAAngEAAA==\",\"AAAAAlwFAADzEgAAAAAC+xAAAPwQAAA=\"]Cc[\"AAAAAAGsAQAAAAG+AAAAAAAAAX0MAAA=\",\"AAAAAUIUAAA=\"]Ac[\"\",\"AAABUQwAAA==\"]Ec[\"AAABigIAAAHxBAAAAAEXAAAAAAAC5QAAAOYAAAA=\",\"AAAAAfEEAAAEVAkAAEgKAABKCgAASwoAAA==\"]Do[\"AAAB/AIAAAEfBQAAAAE1AAAAAi4BAAAvAQAA\",\"AAAAAR8FAAAEVQkAAKAKAAChCgAAogoAAA==\"]Ak[\"\",\"AAAAAAACSBQAAEkUAAA=\"]Ck[\"AAAAAuMEAABrCQAAAf4IAAABfhMAAAHCAAAA\",\"AAAAAuMEAABfEAAA\"]Bg[\"AAAAAYUHAAACrxAAAMIQAAAAAQ0MAAA=\",\"\"]Bk[\"AAAAAYYHAAACsBAAAMMQAAAAAg4MAAAPDAAA\",\"\"]Bc[\"AAAAAAAAAAAAAAAAAkYUAABHFAAA\",\"\"]Fo[\"AAAAAQYFAAAAAAADiAQAAGwJAABkFAAAAgQBAAC2AQAA\",\"AAHABwAAAAEGBQAAAAAAAogEAABsCQAAAbYBAAAAAAAAAAAAAAABFxMAAA==\"]Fo[\"AAAAAT0FAAAAAAFeAQAAA4kEAABtCQAAZRQAAAG3AQAA\",\"AAHYBwAAAAE9BQAAAAAAAokEAABtCQAAAbcBAAAAAAAAAAAAAAABGBMAAA==\"]Ck[\"AAAAATYFAAAAAAJUAQAAVQEAAA==\",\"AAAAATYFAAAAAAAAAXEUAAA=\"]Ec[\"AAAAAUoFAAAAAAF7AQAAAAAAAAAAAAAAAAAFswEAAOUHAAACCAAAAgkAANoMAAA=\",\"AAAAAUoFAAA=\"]Dg[\"AAAAAAFyFAAAAAG9AAAAAAAAAAACRhQAAEcUAAAAAAABkRMAAA==\",\"AAABuwEAAA==\"]Ag[\"AAACrQIAADgDAAA=\",\"\"]Bc[\"\",\"AAAAAAAAAAAAAAAAAkYUAABHFAAA\"]Cc[\"AAAAAWoFAAAAAAAAAAAAAWYRAAA=\",\"AAAB2wEAAAFqBQAA\"]Ck[\"AAAAAWsFAAAAAAAC0RAAAGcRAAAAAY8MAAA=\",\"AAAB3AEAAAFrBQAA\"]Ag[\"AAACrgIAADEDAAA=\",\"\"]Dg[\"AAAAAAABMgAAAAAAASMBAAA=\",\"AAAAAAAAAAAAAAAAAAAAAzoUAAA7FAAAPBQAAA==\"]Ck[\"AAAAAAABZAAAAAGYAQAA\",\"AAAAAAAAAAAAAAAAAAAAAj0UAAA+FAAA\"]Ag[\"AAACtgIAADoDAAA=\",\"\"]Do[\"AAAAAYcHAAACsRAAAMQQAAAAARAMAAAB8RAAAA==\",\"AALMAQAAxwcAAAFPCQAAAAAAAAHxEAAA\"]Ec[\"AAAAAlIFAACIBwAAArIQAADFEAAAAAGJAQAAAfIQAAA=\",\"AAHZBwAAAdQBAAABUgUAAAAAAAHyEAAA\"]Bg[\"AAAAAAAAAAABfQUAAA==\",\"AAABywEAAA==\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAlcJAAC/EAAA\",\"\"]Bk[\"AAAAAAAAAAAAAAAAAAAAAAAAAlkJAADBEAAA\",\"\"]") \ No newline at end of file diff --git a/pr/617/docs/src/noq/connection.rs.html b/pr/617/docs/src/noq/connection.rs.html index 281f5842e..a7d0b4b35 100644 --- a/pr/617/docs/src/noq/connection.rs.html +++ b/pr/617/docs/src/noq/connection.rs.html @@ -187,13 +187,13 @@ 187 /// 188 /// Will panic if called after `poll` has returned `Ready`. 189 pub fn local_ip(&self) -> Option<IpAddr> { -190 let conn = self.conn.as_ref().unwrap(); +190 let conn = self.conn.as_ref().expect("used after yielding Ready"); 191 let inner = conn.lock_without_waking("local_ip"); 192 193 inner 194 .inner 195 .network_path(PathId::ZERO) -196 .expect("path exists when connecting") +196 .expect("PathId::ZERO is the only path during the handshake") 197 .local_ip() 198 } 199 @@ -202,1739 +202,1693 @@ 202 /// Will panic if called after `poll` has returned `Ready`. 203 pub fn remote_address(&self) -> SocketAddr { 204 let conn_ref: &ConnectionRef = self.conn.as_ref().expect("used after yielding Ready"); -205 // TODO: another unwrap -206 conn_ref -207 .lock_without_waking("remote_address") -208 .inner -209 .network_path(PathId::ZERO) -210 .expect("path exists when connecting") -211 .remote() -212 } -213} -214 -215impl Future for Connecting { -216 type Output = Result<Connection, ConnectionError>; -217 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { -218 Pin::new(&mut self.connected).poll(cx).map(|_| { -219 let conn = self.conn.take().unwrap(); -220 let inner = conn.lock_without_waking("connecting"); -221 if inner.connected { -222 drop(inner); -223 Ok(Connection(conn)) -224 } else { -225 Err(inner -226 .error -227 .clone() -228 .expect("connected signaled without connection success or error")) -229 } -230 }) -231 } -232} -233 -234/// Future that completes when a connection is fully established -235/// -236/// For clients, the resulting value indicates if 0-RTT was accepted. For servers, the resulting -237/// value is meaningless. -238pub struct ZeroRttAccepted(oneshot::Receiver<bool>); -239 -240impl Future for ZeroRttAccepted { -241 type Output = bool; -242 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { -243 Pin::new(&mut self.0).poll(cx).map(|x| x.unwrap_or(false)) -244 } -245} -246 -247/// A future that drives protocol logic for a connection -248/// -249/// This future handles the protocol logic for a single connection, routing events from the -250/// `Connection` API object to the `Endpoint` task and the related stream-related interfaces. -251/// It also keeps track of outstanding timeouts for the `Connection`. -252/// -253/// If the connection encounters an error condition, this future will yield an error. It will -254/// terminate (yielding `Ok(())`) if the connection was closed without error. Unlike other -255/// connection-related futures, this waits for the draining period to complete to ensure that -256/// packets still in flight from the peer are handled gracefully. -257#[must_use = "connection drivers must be spawned for their connections to function"] -258#[derive(Debug)] -259struct ConnectionDriver(ConnectionRef); -260 -261impl Future for ConnectionDriver { -262 type Output = Result<(), io::Error>; -263 -264 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { -265 let conn = &mut *self.0.lock_without_waking("poll"); -266 -267 let span = debug_span!("drive", id = conn.handle.0); -268 let _guard = span.enter(); -269 -270 if let Err(e) = conn.process_conn_events(&self.0.shared, cx) { -271 conn.terminate(e, &self.0.shared); -272 return Poll::Ready(Ok(())); -273 } -274 let mut keep_going = conn.drive_transmit(cx)?; -275 // If a timer expires, there might be more to transmit. When we transmit something, we -276 // might need to reset a timer. Hence, we must loop until neither happens. -277 keep_going |= conn.drive_timer(cx); -278 conn.forward_endpoint_events(); -279 conn.forward_app_events(&self.0.shared); -280 -281 if !conn.inner.is_drained() { -282 if keep_going { -283 // If the connection hasn't processed all tasks, schedule it again -284 cx.waker().wake_by_ref(); -285 } else { -286 conn.driver = Some(cx.waker().clone()); -287 } -288 return Poll::Pending; -289 } -290 if conn.error.is_none() { -291 unreachable!("drained connections always have an error"); -292 } -293 Poll::Ready(Ok(())) -294 } -295} -296 -297/// A QUIC connection. -298/// -299/// If all references to a connection (including every clone of the `Connection` handle, streams of -300/// incoming streams, and the various stream types) have been dropped, then the connection will be -301/// automatically closed with an `error_code` of 0 and an empty `reason`. You can also close the -302/// connection explicitly by calling [`Connection::close()`]. -303/// -304/// Closing the connection immediately abandons efforts to deliver data to the peer. Upon -305/// receiving CONNECTION_CLOSE the peer *may* drop any stream data not yet delivered to the -306/// application. [`Connection::close()`] describes in more detail how to gracefully close a -307/// connection without losing application data. -308/// -309/// May be cloned to obtain another handle to the same connection. -310/// -311/// [`Connection::close()`]: Connection::close -312#[derive(Debug, Clone)] -313pub struct Connection(ConnectionRef); -314 -315impl Connection { -316 /// Returns a weak reference to the inner connection struct. -317 pub fn weak_handle(&self) -> WeakConnectionHandle { -318 self.0.weak_handle() -319 } -320 -321 /// Initiate a new outgoing unidirectional stream. -322 /// -323 /// Streams are cheap and instantaneous to open unless blocked by flow control. As a -324 /// consequence, the peer won't be notified that a stream has been opened until the stream is -325 /// actually used. -326 pub fn open_uni(&self) -> OpenUni<'_> { -327 OpenUni { -328 conn: &self.0, -329 notify: self.0.shared.stream_budget_available[Dir::Uni as usize].notified(), -330 } -331 } -332 -333 /// Initiate a new outgoing bidirectional stream. -334 /// -335 /// Streams are cheap and instantaneous to open unless blocked by flow control. As a -336 /// consequence, the peer won't be notified that a stream has been opened until the stream is -337 /// actually used. Calling [`open_bi()`] then waiting on the [`RecvStream`] without writing -338 /// anything to [`SendStream`] will never succeed. -339 /// -340 /// [`open_bi()`]: crate::Connection::open_bi -341 /// [`SendStream`]: crate::SendStream -342 /// [`RecvStream`]: crate::RecvStream -343 pub fn open_bi(&self) -> OpenBi<'_> { -344 OpenBi { -345 conn: &self.0, -346 notify: self.0.shared.stream_budget_available[Dir::Bi as usize].notified(), -347 } -348 } -349 -350 /// Accept the next incoming uni-directional stream -351 pub fn accept_uni(&self) -> AcceptUni<'_> { -352 AcceptUni { -353 conn: &self.0, -354 notify: self.0.shared.stream_incoming[Dir::Uni as usize].notified(), -355 } -356 } -357 -358 /// Accept the next incoming bidirectional stream -359 /// -360 /// **Important Note**: The `Connection` that calls [`open_bi()`] must write to its [`SendStream`] -361 /// before the other `Connection` is able to `accept_bi()`. Calling [`open_bi()`] then -362 /// waiting on the [`RecvStream`] without writing anything to [`SendStream`] will never succeed. -363 /// -364 /// [`accept_bi()`]: crate::Connection::accept_bi -365 /// [`open_bi()`]: crate::Connection::open_bi -366 /// [`SendStream`]: crate::SendStream -367 /// [`RecvStream`]: crate::RecvStream -368 pub fn accept_bi(&self) -> AcceptBi<'_> { -369 AcceptBi { -370 conn: &self.0, -371 notify: self.0.shared.stream_incoming[Dir::Bi as usize].notified(), -372 } -373 } -374 -375 /// Receive an application datagram -376 pub fn read_datagram(&self) -> ReadDatagram<'_> { -377 ReadDatagram { -378 conn: &self.0, -379 notify: self.0.shared.datagram_received.notified(), -380 } -381 } -382 -383 /// Opens a new path if no path exists yet for the remote address. -384 /// -385 /// Otherwise behaves exactly as [`open_path`]. -386 /// -387 /// [`open_path`]: Self::open_path -388 pub fn open_path_ensure(&self, addr: SocketAddr, initial_status: PathStatus) -> OpenPath { -389 let mut state = self.0.lock_and_wake("open_path"); -390 -391 // If endpoint::State::ipv6 is true we want to keep all our IP addresses as IPv6. -392 // If not, we do not support IPv6. We can not access endpoint::State from here -393 // however, but either all our paths use an IPv6 address, or all our paths use an -394 // IPv4 address. So we can use that information. -395 let ipv6 = state -396 .inner -397 .paths() -398 .iter() -399 .filter_map(|id| { -400 state -401 .inner -402 .network_path(*id) -403 .map(|addrs| addrs.remote().is_ipv6()) -404 .ok() -405 }) -406 .next() -407 .unwrap_or_default(); -408 if addr.is_ipv6() && !ipv6 { -409 return OpenPath::rejected(PathError::InvalidRemoteAddress(addr)); -410 } -411 let addr = if ipv6 { -412 SocketAddr::V6(ensure_ipv6(addr)) -413 } else { -414 addr -415 }; -416 -417 let now = state.runtime.now(); -418 // TODO(matheus23): For now this means it's impossible to make use of short-circuiting path validation currently. -419 // However, changing that would mean changing the API. -420 let addrs = FourTuple::from_remote(addr); -421 let open_res = state.inner.open_path_ensure(addrs, initial_status, now); -422 match open_res { -423 Ok((path_id, existed)) if existed => { -424 let recv = state.open_path.get(&path_id).map(|tx| tx.subscribe()); -425 drop(state); -426 match recv { -427 Some(recv) => OpenPath::new(path_id, recv, self.0.clone()), -428 None => OpenPath::ready(path_id, self.0.clone()), -429 } -430 } -431 Ok((path_id, _)) => { -432 let (tx, rx) = watch::channel(Ok(())); -433 state.open_path.insert(path_id, tx); -434 drop(state); -435 OpenPath::new(path_id, rx, self.0.clone()) -436 } -437 Err(err) => OpenPath::rejected(err), -438 } -439 } -440 -441 /// Opens an additional path if the multipath extension is negotiated. -442 /// -443 /// The returned future completes once the path is either fully opened and ready to -444 /// carry application data, or if there was an error. -445 /// -446 /// Dropping the returned future does not cancel the opening of the path, the -447 /// [`PathEvent::Opened`] event will still be emitted from [`Self::path_events`] if the -448 /// path opens. The [`PathId`] for the events can be extracted from -449 /// [`OpenPath::path_id`]. -450 /// -451 /// Failure to open a path can either occur immediately, before polling the returned -452 /// future, or at a later time. If the failure is immediate [`OpenPath::path_id`] will -453 /// return `None` and the future will be ready immediately. If the failure happens -454 /// later, a [`PathEvent`] will be emitted. -455 pub fn open_path(&self, addr: SocketAddr, initial_status: PathStatus) -> OpenPath { -456 let mut state = self.0.lock_and_wake("open_path"); -457 -458 // If endpoint::State::ipv6 is true we want to keep all our IP addresses as IPv6. -459 // If not, we do not support IPv6. We can not access endpoint::State from here -460 // however, but either all our paths use an IPv6 address, or all our paths use an -461 // IPv4 address. So we can use that information. -462 let ipv6 = state -463 .inner -464 .paths() -465 .iter() -466 .filter_map(|id| { -467 state -468 .inner -469 .network_path(*id) -470 .map(|addrs| addrs.remote().is_ipv6()) -471 .ok() -472 }) -473 .next() -474 .unwrap_or_default(); -475 if addr.is_ipv6() && !ipv6 { -476 return OpenPath::rejected(PathError::InvalidRemoteAddress(addr)); -477 } -478 let addr = if ipv6 { -479 SocketAddr::V6(ensure_ipv6(addr)) -480 } else { -481 addr -482 }; -483 -484 let (on_open_path_send, on_open_path_recv) = watch::channel(Ok(())); -485 let now = state.runtime.now(); -486 // TODO(matheus23): For now this means it's impossible to make use of short-circuiting path validation currently. -487 // However, changing that would mean changing the API. -488 let addrs = FourTuple::from_remote(addr); -489 let open_res = state.inner.open_path(addrs, initial_status, now); -490 match open_res { -491 Ok(path_id) => { -492 state.open_path.insert(path_id, on_open_path_send); -493 drop(state); -494 OpenPath::new(path_id, on_open_path_recv, self.0.clone()) -495 } -496 Err(err) => OpenPath::rejected(err), -497 } -498 } -499 -500 /// Returns the [`Path`] structure of an open path -501 pub fn path(&self, id: PathId) -> Option<Path> { -502 Path::new(&self.0, id) -503 } -504 -505 /// A stream of [`PathEvent`]s for all paths in this connection. -506 /// -507 /// The stream will yield a [`PathEvent`] whenever there is a change in the state of any path in this connection. -508 /// The events need to be processed immediately, since there isn't an unbounded buffer for them. -509 /// -510 /// If processing of events lags behind too much, you will get an error of type [`crate::Lagged`] indicating -511 /// how many events were lost. The stream continues after a lag, delivering the oldest retained message next. -512 pub fn path_events(&self) -> crate::PathEvents { -513 crate::PathEvents::new( -514 self.0 -515 .lock_without_waking("path_events") -516 .path_events -517 .subscribe(), -518 ) -519 } -520 -521 /// A stream of NAT traversal updates for this connection. -522 /// -523 /// The events need to be processed immediately, since there isn't an unbounded buffer for them. -524 /// -525 /// If processing of events lags behind too much, you will get an error of type [`crate::Lagged`] indicating -526 /// how many events were lost. The stream continues after a lag, delivering the oldest retained message next. -527 pub fn nat_traversal_updates(&self) -> crate::NatTraversalUpdates { -528 crate::NatTraversalUpdates::new( -529 self.0 -530 .lock_without_waking("nat_traversal_updates") -531 .nat_traversal_updates -532 .subscribe(), -533 ) -534 } -535 -536 /// Wait for the connection to be closed for any reason -537 /// -538 /// Despite the return type's name, closed connections are often not an error condition at the -539 /// application layer. Cases that might be routine include [`ConnectionError::LocallyClosed`] -540 /// and [`ConnectionError::ApplicationClosed`]. -541 pub async fn closed(&self) -> ConnectionError { -542 { -543 let conn = self.0.lock_without_waking("closed"); -544 if let Some(error) = conn.error.as_ref() { -545 return error.clone(); -546 } -547 // Construct the future while the lock is held to ensure we can't miss a wakeup if -548 // the `Notify` is signaled immediately after we release the lock. `await` it after -549 // the lock guard is out of scope. -550 self.0.shared.closed.notified() -551 } -552 .await; -553 self.0 -554 .lock_without_waking("closed") -555 .error -556 .as_ref() -557 .expect("closed without an error") -558 .clone() -559 } -560 -561 /// Wait for the connection to be closed without keeping a strong reference to the connection -562 /// -563 /// Returns a future that resolves, once the connection is closed, to a [`Closed`] struct -564 /// describing the close reason and final connection and per-path statistics. -565 /// -566 /// Calling [`Self::closed`] keeps the connection alive until it is either closed locally via [`Connection::close`] -567 /// or closed by the remote peer. This function instead does not keep the connection itself alive, -568 /// so if all *other* clones of the connection are dropped, the connection will be closed implicitly even -569 /// if there are futures returned from this function still being awaited. -570 pub fn on_closed(&self) -> OnClosed { -571 let (tx, rx) = oneshot::channel(); -572 let mut state = self.0.lock_without_waking("on_closed"); -573 if state.error.is_some() { -574 // Connection already closed, send immediately -575 let _ = tx.send(Closed::new(&mut state)); -576 } else { -577 state.on_closed.push(tx); -578 } -579 drop(state); -580 OnClosed { -581 conn: self.weak_handle(), -582 rx, -583 } -584 } -585 -586 /// Whether the connection is closed, and why. -587 /// -588 /// The close_reason is always set to `Some(ConnectionError)` when a socket is -589 /// closed; whether it was closed manually by calling [`Connection::close()`] or due to -590 /// an internal error (such as an idle timeout or the peer closing the -591 /// connection). -592 /// -593 /// Note: when the connection is closed, `connection.close_reason().is_some()` will always be true. -594 pub fn close_reason(&self) -> Option<ConnectionError> { -595 self.0.lock_without_waking("close_reason").error.clone() -596 } -597 -598 /// Close the connection immediately. -599 /// -600 /// Pending operations will fail immediately with [`ConnectionError::LocallyClosed`]. No -601 /// more data is sent to the peer and the peer may drop buffered data upon receiving -602 /// the CONNECTION_CLOSE frame. -603 /// -604 /// `error_code` and `reason` are not interpreted, and are provided directly to the peer. -605 /// -606 /// `reason` will be truncated to fit in a single packet with overhead; to improve odds that it -607 /// is preserved in full, it should be kept under 1KiB. -608 /// -609 /// # Gracefully closing a connection -610 /// -611 /// Only the peer last receiving application data can be certain that all data is -612 /// delivered. The only reliable action it can then take is to close the connection, -613 /// potentially with a custom error code. The delivery of the final CONNECTION_CLOSE -614 /// frame is very likely if both endpoints stay online long enough, and -615 /// [`Endpoint::wait_idle()`] can be used to provide sufficient time. Otherwise, the -616 /// remote peer will time out the connection, provided that the idle timeout is not -617 /// disabled. -618 /// -619 /// The sending side can not guarantee all stream data is delivered to the remote -620 /// application. It only knows the data is delivered to the QUIC stack of the remote -621 /// endpoint. Once the local side sends a CONNECTION_CLOSE frame in response to calling -622 /// [`close()`] the remote endpoint may drop any data it received but is as yet -623 /// undelivered to the application, including data that was acknowledged as received to -624 /// the local endpoint. -625 /// -626 /// [`ConnectionError::LocallyClosed`]: crate::ConnectionError::LocallyClosed -627 /// [`Endpoint::wait_idle()`]: crate::Endpoint::wait_idle -628 /// [`close()`]: Connection::close -629 pub fn close(&self, error_code: VarInt, reason: &[u8]) { -630 let conn = &mut *self.0.lock_without_waking("close"); // conn.close self-wakes -631 conn.close(error_code, Bytes::copy_from_slice(reason), &self.0.shared); -632 } -633 -634 /// Wait for the handshake to be confirmed. -635 /// -636 /// As a server, who must be authenticated by clients, -637 /// this happens when the handshake completes -638 /// upon receiving a TLS Finished message from the client. -639 /// In return, the server send a HANDSHAKE_DONE frame. -640 /// -641 /// As a client, this happens when receiving a HANDSHAKE_DONE frame. -642 /// At this point, the server has either accepted our authentication, -643 /// or, if client authentication is not required, accepted our lack of authentication. -644 pub async fn handshake_confirmed(&self) -> Result<(), ConnectionError> { -645 { -646 let conn = self.0.lock_without_waking("handshake_confirmed"); -647 if let Some(error) = conn.error.as_ref() { -648 return Err(error.clone()); -649 } -650 if conn.handshake_confirmed { -651 return Ok(()); -652 } -653 // Construct the future while the lock is held to ensure we can't miss a wakeup if -654 // the `Notify` is signaled immediately after we release the lock. `await` it after -655 // the lock guard is out of scope. -656 self.0.shared.handshake_confirmed.notified() -657 } -658 .await; -659 if let Some(error) = self -660 .0 -661 .lock_without_waking("handshake_confirmed") -662 .error -663 .as_ref() -664 { -665 Err(error.clone()) -666 } else { -667 Ok(()) -668 } -669 } -670 -671 /// Transmit `data` as an unreliable, unordered application datagram -672 /// -673 /// Application datagrams are a low-level primitive. They may be lost or delivered out of order, -674 /// and `data` must both fit inside a single QUIC packet and be smaller than the maximum -675 /// dictated by the peer. -676 /// -677 /// Previously queued datagrams which are still unsent may be discarded to make space for this -678 /// datagram, in order of oldest to newest. -679 pub fn send_datagram(&self, data: Bytes) -> Result<(), SendDatagramError> { -680 let conn = &mut *self.0.lock_and_wake("send_datagram"); -681 if let Some(ref x) = conn.error { -682 return Err(SendDatagramError::ConnectionLost(x.clone())); -683 } -684 use proto::SendDatagramError::*; -685 match conn.inner.datagrams().send(data, true) { -686 Ok(()) => Ok(()), -687 Err(e) => Err(match e { -688 Blocked(..) => unreachable!(), -689 UnsupportedByPeer => SendDatagramError::UnsupportedByPeer, -690 Disabled => SendDatagramError::Disabled, -691 TooLarge => SendDatagramError::TooLarge, -692 }), -693 } -694 } -695 -696 /// Transmit `data` as an unreliable, unordered application datagram -697 /// -698 /// Unlike [`send_datagram()`], this method will wait for buffer space during congestion -699 /// conditions, which effectively prioritizes old datagrams over new datagrams. -700 /// -701 /// See [`send_datagram()`] for details. -702 /// -703 /// [`send_datagram()`]: Connection::send_datagram -704 pub fn send_datagram_wait(&self, data: Bytes) -> SendDatagram<'_> { -705 SendDatagram { -706 conn: &self.0, -707 data: Some(data), -708 notify: self.0.shared.datagrams_unblocked.notified(), -709 } -710 } -711 -712 /// Compute the maximum size of datagrams that may be passed to [`send_datagram()`]. -713 /// -714 /// Returns `None` if datagrams are unsupported by the peer or disabled locally. -715 /// -716 /// This may change over the lifetime of a connection according to variation in the path MTU -717 /// estimate. The peer can also enforce an arbitrarily small fixed limit, but if the peer's -718 /// limit is large this is guaranteed to be a little over a kilobyte at minimum. -719 /// -720 /// Not necessarily the maximum size of received datagrams. -721 /// -722 /// [`send_datagram()`]: Connection::send_datagram -723 pub fn max_datagram_size(&self) -> Option<usize> { -724 self.0 -725 .lock_without_waking("max_datagram_size") -726 .inner -727 .datagrams() -728 .max_size() -729 } -730 -731 /// Bytes available in the outgoing datagram buffer -732 /// -733 /// When greater than zero, calling [`send_datagram()`](Self::send_datagram) with a datagram of -734 /// at most this size is guaranteed not to cause older datagrams to be dropped. -735 pub fn datagram_send_buffer_space(&self) -> usize { -736 self.0 -737 .lock_without_waking("datagram_send_buffer_space") -738 .inner -739 .datagrams() -740 .send_buffer_space() -741 } -742 -743 /// The side of the connection (client or server) -744 pub fn side(&self) -> Side { -745 self.0.lock_without_waking("side").inner.side() -746 } -747 -748 /// The peer's UDP address -749 /// -750 /// If [`ServerConfig::migration`] is `true`, clients may change addresses at will, -751 /// e.g. when switching to a cellular internet connection. -752 /// -753 /// If [`multipath`] is enabled this will return the address of *any* -754 /// path, and may not be consistent. Prefer [`Path::remote_address`] instead. -755 /// -756 /// [`ServerConfig::migration`]: crate::ServerConfig::migration -757 /// [`multipath`]: crate::TransportConfig::max_concurrent_multipath_paths -758 pub fn remote_address(&self) -> SocketAddr { -759 // TODO: an unwrap again -760 let state = self.0.lock_without_waking("remote_address"); -761 state -762 .inner -763 .paths() -764 .iter() -765 .filter_map(|id| state.inner.network_path(*id).ok()) -766 .next() -767 .unwrap() -768 .remote() +205 conn_ref +206 .lock_without_waking("remote_address") +207 .inner +208 .network_path(PathId::ZERO) +209 .expect("PathId::ZERO is the only path during the handshake") +210 .remote() +211 } +212} +213 +214impl Future for Connecting { +215 type Output = Result<Connection, ConnectionError>; +216 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { +217 Pin::new(&mut self.connected).poll(cx).map(|_| { +218 let conn = self.conn.take().unwrap(); +219 let inner = conn.lock_without_waking("connecting"); +220 if inner.connected { +221 drop(inner); +222 Ok(Connection(conn)) +223 } else { +224 Err(inner +225 .error +226 .clone() +227 .expect("connected signaled without connection success or error")) +228 } +229 }) +230 } +231} +232 +233/// Future that completes when a connection is fully established +234/// +235/// For clients, the resulting value indicates if 0-RTT was accepted. For servers, the resulting +236/// value is meaningless. +237pub struct ZeroRttAccepted(oneshot::Receiver<bool>); +238 +239impl Future for ZeroRttAccepted { +240 type Output = bool; +241 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { +242 Pin::new(&mut self.0).poll(cx).map(|x| x.unwrap_or(false)) +243 } +244} +245 +246/// A future that drives protocol logic for a connection +247/// +248/// This future handles the protocol logic for a single connection, routing events from the +249/// `Connection` API object to the `Endpoint` task and the related stream-related interfaces. +250/// It also keeps track of outstanding timeouts for the `Connection`. +251/// +252/// If the connection encounters an error condition, this future will yield an error. It will +253/// terminate (yielding `Ok(())`) if the connection was closed without error. Unlike other +254/// connection-related futures, this waits for the draining period to complete to ensure that +255/// packets still in flight from the peer are handled gracefully. +256#[must_use = "connection drivers must be spawned for their connections to function"] +257#[derive(Debug)] +258struct ConnectionDriver(ConnectionRef); +259 +260impl Future for ConnectionDriver { +261 type Output = Result<(), io::Error>; +262 +263 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { +264 let conn = &mut *self.0.lock_without_waking("poll"); +265 +266 let span = debug_span!("drive", id = conn.handle.0); +267 let _guard = span.enter(); +268 +269 if let Err(e) = conn.process_conn_events(&self.0.shared, cx) { +270 conn.terminate(e, &self.0.shared); +271 return Poll::Ready(Ok(())); +272 } +273 let mut keep_going = conn.drive_transmit(cx)?; +274 // If a timer expires, there might be more to transmit. When we transmit something, we +275 // might need to reset a timer. Hence, we must loop until neither happens. +276 keep_going |= conn.drive_timer(cx); +277 conn.forward_endpoint_events(); +278 conn.forward_app_events(&self.0.shared); +279 +280 if !conn.inner.is_drained() { +281 if keep_going { +282 // If the connection hasn't processed all tasks, schedule it again +283 cx.waker().wake_by_ref(); +284 } else { +285 conn.driver = Some(cx.waker().clone()); +286 } +287 return Poll::Pending; +288 } +289 if conn.error.is_none() { +290 unreachable!("drained connections always have an error"); +291 } +292 Poll::Ready(Ok(())) +293 } +294} +295 +296/// A QUIC connection. +297/// +298/// If all references to a connection (including every clone of the `Connection` handle, streams of +299/// incoming streams, and the various stream types) have been dropped, then the connection will be +300/// automatically closed with an `error_code` of 0 and an empty `reason`. You can also close the +301/// connection explicitly by calling [`Connection::close()`]. +302/// +303/// Closing the connection immediately abandons efforts to deliver data to the peer. Upon +304/// receiving CONNECTION_CLOSE the peer *may* drop any stream data not yet delivered to the +305/// application. [`Connection::close()`] describes in more detail how to gracefully close a +306/// connection without losing application data. +307/// +308/// May be cloned to obtain another handle to the same connection. +309/// +310/// [`Connection::close()`]: Connection::close +311#[derive(Debug, Clone)] +312pub struct Connection(ConnectionRef); +313 +314impl Connection { +315 /// Returns a weak reference to the inner connection struct. +316 pub fn weak_handle(&self) -> WeakConnectionHandle { +317 self.0.weak_handle() +318 } +319 +320 /// Initiate a new outgoing unidirectional stream. +321 /// +322 /// Streams are cheap and instantaneous to open unless blocked by flow control. As a +323 /// consequence, the peer won't be notified that a stream has been opened until the stream is +324 /// actually used. +325 pub fn open_uni(&self) -> OpenUni<'_> { +326 OpenUni { +327 conn: &self.0, +328 notify: self.0.shared.stream_budget_available[Dir::Uni as usize].notified(), +329 } +330 } +331 +332 /// Initiate a new outgoing bidirectional stream. +333 /// +334 /// Streams are cheap and instantaneous to open unless blocked by flow control. As a +335 /// consequence, the peer won't be notified that a stream has been opened until the stream is +336 /// actually used. Calling [`open_bi()`] then waiting on the [`RecvStream`] without writing +337 /// anything to [`SendStream`] will never succeed. +338 /// +339 /// [`open_bi()`]: crate::Connection::open_bi +340 /// [`SendStream`]: crate::SendStream +341 /// [`RecvStream`]: crate::RecvStream +342 pub fn open_bi(&self) -> OpenBi<'_> { +343 OpenBi { +344 conn: &self.0, +345 notify: self.0.shared.stream_budget_available[Dir::Bi as usize].notified(), +346 } +347 } +348 +349 /// Accept the next incoming uni-directional stream +350 pub fn accept_uni(&self) -> AcceptUni<'_> { +351 AcceptUni { +352 conn: &self.0, +353 notify: self.0.shared.stream_incoming[Dir::Uni as usize].notified(), +354 } +355 } +356 +357 /// Accept the next incoming bidirectional stream +358 /// +359 /// **Important Note**: The `Connection` that calls [`open_bi()`] must write to its [`SendStream`] +360 /// before the other `Connection` is able to `accept_bi()`. Calling [`open_bi()`] then +361 /// waiting on the [`RecvStream`] without writing anything to [`SendStream`] will never succeed. +362 /// +363 /// [`accept_bi()`]: crate::Connection::accept_bi +364 /// [`open_bi()`]: crate::Connection::open_bi +365 /// [`SendStream`]: crate::SendStream +366 /// [`RecvStream`]: crate::RecvStream +367 pub fn accept_bi(&self) -> AcceptBi<'_> { +368 AcceptBi { +369 conn: &self.0, +370 notify: self.0.shared.stream_incoming[Dir::Bi as usize].notified(), +371 } +372 } +373 +374 /// Receive an application datagram +375 pub fn read_datagram(&self) -> ReadDatagram<'_> { +376 ReadDatagram { +377 conn: &self.0, +378 notify: self.0.shared.datagram_received.notified(), +379 } +380 } +381 +382 /// Opens a new path if no path exists yet for the remote address. +383 /// +384 /// Otherwise behaves exactly as [`open_path`]. +385 /// +386 /// [`open_path`]: Self::open_path +387 pub fn open_path_ensure(&self, addr: SocketAddr, initial_status: PathStatus) -> OpenPath { +388 let mut state = self.0.lock_and_wake("open_path"); +389 +390 // If endpoint::State::ipv6 is true we want to keep all our IP addresses as IPv6. +391 // If not, we do not support IPv6. We can not access endpoint::State from here +392 // however, but either all our paths use an IPv6 address, or all our paths use an +393 // IPv4 address. So we can use that information. +394 let ipv6 = state +395 .inner +396 .paths() +397 .iter() +398 .filter_map(|id| { +399 state +400 .inner +401 .network_path(*id) +402 .map(|addrs| addrs.remote().is_ipv6()) +403 .ok() +404 }) +405 .next() +406 .unwrap_or_default(); +407 if addr.is_ipv6() && !ipv6 { +408 return OpenPath::rejected(PathError::InvalidRemoteAddress(addr)); +409 } +410 let addr = if ipv6 { +411 SocketAddr::V6(ensure_ipv6(addr)) +412 } else { +413 addr +414 }; +415 +416 let now = state.runtime.now(); +417 // TODO(matheus23): For now this means it's impossible to make use of short-circuiting path validation currently. +418 // However, changing that would mean changing the API. +419 let addrs = FourTuple::from_remote(addr); +420 let open_res = state.inner.open_path_ensure(addrs, initial_status, now); +421 match open_res { +422 Ok((path_id, existed)) if existed => { +423 let recv = state.open_path.get(&path_id).map(|tx| tx.subscribe()); +424 drop(state); +425 match recv { +426 Some(recv) => OpenPath::new(path_id, recv, self.0.clone()), +427 None => OpenPath::ready(path_id, self.0.clone()), +428 } +429 } +430 Ok((path_id, _)) => { +431 let (tx, rx) = watch::channel(Ok(())); +432 state.open_path.insert(path_id, tx); +433 drop(state); +434 OpenPath::new(path_id, rx, self.0.clone()) +435 } +436 Err(err) => OpenPath::rejected(err), +437 } +438 } +439 +440 /// Opens an additional path if the multipath extension is negotiated. +441 /// +442 /// The returned future completes once the path is either fully opened and ready to +443 /// carry application data, or if there was an error. +444 /// +445 /// Dropping the returned future does not cancel the opening of the path, the +446 /// [`PathEvent::Opened`] event will still be emitted from [`Self::path_events`] if the +447 /// path opens. The [`PathId`] for the events can be extracted from +448 /// [`OpenPath::path_id`]. +449 /// +450 /// Failure to open a path can either occur immediately, before polling the returned +451 /// future, or at a later time. If the failure is immediate [`OpenPath::path_id`] will +452 /// return `None` and the future will be ready immediately. If the failure happens +453 /// later, a [`PathEvent`] will be emitted. +454 pub fn open_path(&self, addr: SocketAddr, initial_status: PathStatus) -> OpenPath { +455 let mut state = self.0.lock_and_wake("open_path"); +456 +457 // If endpoint::State::ipv6 is true we want to keep all our IP addresses as IPv6. +458 // If not, we do not support IPv6. We can not access endpoint::State from here +459 // however, but either all our paths use an IPv6 address, or all our paths use an +460 // IPv4 address. So we can use that information. +461 let ipv6 = state +462 .inner +463 .paths() +464 .iter() +465 .filter_map(|id| { +466 state +467 .inner +468 .network_path(*id) +469 .map(|addrs| addrs.remote().is_ipv6()) +470 .ok() +471 }) +472 .next() +473 .unwrap_or_default(); +474 if addr.is_ipv6() && !ipv6 { +475 return OpenPath::rejected(PathError::InvalidRemoteAddress(addr)); +476 } +477 let addr = if ipv6 { +478 SocketAddr::V6(ensure_ipv6(addr)) +479 } else { +480 addr +481 }; +482 +483 let (on_open_path_send, on_open_path_recv) = watch::channel(Ok(())); +484 let now = state.runtime.now(); +485 // TODO(matheus23): For now this means it's impossible to make use of short-circuiting path validation currently. +486 // However, changing that would mean changing the API. +487 let addrs = FourTuple::from_remote(addr); +488 let open_res = state.inner.open_path(addrs, initial_status, now); +489 match open_res { +490 Ok(path_id) => { +491 state.open_path.insert(path_id, on_open_path_send); +492 drop(state); +493 OpenPath::new(path_id, on_open_path_recv, self.0.clone()) +494 } +495 Err(err) => OpenPath::rejected(err), +496 } +497 } +498 +499 /// Returns the [`Path`] structure of an open path +500 pub fn path(&self, id: PathId) -> Option<Path> { +501 Path::new(&self.0, id) +502 } +503 +504 /// A stream of [`PathEvent`]s for all paths in this connection. +505 /// +506 /// The stream will yield a [`PathEvent`] whenever there is a change in the state of any path in this connection. +507 /// The events need to be processed immediately, since there isn't an unbounded buffer for them. +508 /// +509 /// If processing of events lags behind too much, you will get an error of type [`crate::Lagged`] indicating +510 /// how many events were lost. The stream continues after a lag, delivering the oldest retained message next. +511 pub fn path_events(&self) -> crate::PathEvents { +512 crate::PathEvents::new( +513 self.0 +514 .lock_without_waking("path_events") +515 .path_events +516 .subscribe(), +517 ) +518 } +519 +520 /// A stream of NAT traversal updates for this connection. +521 /// +522 /// The events need to be processed immediately, since there isn't an unbounded buffer for them. +523 /// +524 /// If processing of events lags behind too much, you will get an error of type [`crate::Lagged`] indicating +525 /// how many events were lost. The stream continues after a lag, delivering the oldest retained message next. +526 pub fn nat_traversal_updates(&self) -> crate::NatTraversalUpdates { +527 crate::NatTraversalUpdates::new( +528 self.0 +529 .lock_without_waking("nat_traversal_updates") +530 .nat_traversal_updates +531 .subscribe(), +532 ) +533 } +534 +535 /// Wait for the connection to be closed for any reason +536 /// +537 /// Despite the return type's name, closed connections are often not an error condition at the +538 /// application layer. Cases that might be routine include [`ConnectionError::LocallyClosed`] +539 /// and [`ConnectionError::ApplicationClosed`]. +540 pub async fn closed(&self) -> ConnectionError { +541 { +542 let conn = self.0.lock_without_waking("closed"); +543 if let Some(error) = conn.error.as_ref() { +544 return error.clone(); +545 } +546 // Construct the future while the lock is held to ensure we can't miss a wakeup if +547 // the `Notify` is signaled immediately after we release the lock. `await` it after +548 // the lock guard is out of scope. +549 self.0.shared.closed.notified() +550 } +551 .await; +552 self.0 +553 .lock_without_waking("closed") +554 .error +555 .as_ref() +556 .expect("closed without an error") +557 .clone() +558 } +559 +560 /// Wait for the connection to be closed without keeping a strong reference to the connection +561 /// +562 /// Returns a future that resolves, once the connection is closed, to a [`Closed`] struct +563 /// describing the close reason and final connection and per-path statistics. +564 /// +565 /// Calling [`Self::closed`] keeps the connection alive until it is either closed locally via [`Connection::close`] +566 /// or closed by the remote peer. This function instead does not keep the connection itself alive, +567 /// so if all *other* clones of the connection are dropped, the connection will be closed implicitly even +568 /// if there are futures returned from this function still being awaited. +569 pub fn on_closed(&self) -> OnClosed { +570 let (tx, rx) = oneshot::channel(); +571 let mut state = self.0.lock_without_waking("on_closed"); +572 if state.error.is_some() { +573 // Connection already closed, send immediately +574 let _ = tx.send(Closed::new(&mut state)); +575 } else { +576 state.on_closed.push(tx); +577 } +578 drop(state); +579 OnClosed { +580 conn: self.weak_handle(), +581 rx, +582 } +583 } +584 +585 /// Whether the connection is closed, and why. +586 /// +587 /// The close_reason is always set to `Some(ConnectionError)` when a socket is +588 /// closed; whether it was closed manually by calling [`Connection::close()`] or due to +589 /// an internal error (such as an idle timeout or the peer closing the +590 /// connection). +591 /// +592 /// Note: when the connection is closed, `connection.close_reason().is_some()` will always be true. +593 pub fn close_reason(&self) -> Option<ConnectionError> { +594 self.0.lock_without_waking("close_reason").error.clone() +595 } +596 +597 /// Close the connection immediately. +598 /// +599 /// Pending operations will fail immediately with [`ConnectionError::LocallyClosed`]. No +600 /// more data is sent to the peer and the peer may drop buffered data upon receiving +601 /// the CONNECTION_CLOSE frame. +602 /// +603 /// `error_code` and `reason` are not interpreted, and are provided directly to the peer. +604 /// +605 /// `reason` will be truncated to fit in a single packet with overhead; to improve odds that it +606 /// is preserved in full, it should be kept under 1KiB. +607 /// +608 /// # Gracefully closing a connection +609 /// +610 /// Only the peer last receiving application data can be certain that all data is +611 /// delivered. The only reliable action it can then take is to close the connection, +612 /// potentially with a custom error code. The delivery of the final CONNECTION_CLOSE +613 /// frame is very likely if both endpoints stay online long enough, and +614 /// [`Endpoint::wait_idle()`] can be used to provide sufficient time. Otherwise, the +615 /// remote peer will time out the connection, provided that the idle timeout is not +616 /// disabled. +617 /// +618 /// The sending side can not guarantee all stream data is delivered to the remote +619 /// application. It only knows the data is delivered to the QUIC stack of the remote +620 /// endpoint. Once the local side sends a CONNECTION_CLOSE frame in response to calling +621 /// [`close()`] the remote endpoint may drop any data it received but is as yet +622 /// undelivered to the application, including data that was acknowledged as received to +623 /// the local endpoint. +624 /// +625 /// [`ConnectionError::LocallyClosed`]: crate::ConnectionError::LocallyClosed +626 /// [`Endpoint::wait_idle()`]: crate::Endpoint::wait_idle +627 /// [`close()`]: Connection::close +628 pub fn close(&self, error_code: VarInt, reason: &[u8]) { +629 let conn = &mut *self.0.lock_without_waking("close"); // conn.close self-wakes +630 conn.close(error_code, Bytes::copy_from_slice(reason), &self.0.shared); +631 } +632 +633 /// Wait for the handshake to be confirmed. +634 /// +635 /// As a server, who must be authenticated by clients, +636 /// this happens when the handshake completes +637 /// upon receiving a TLS Finished message from the client. +638 /// In return, the server send a HANDSHAKE_DONE frame. +639 /// +640 /// As a client, this happens when receiving a HANDSHAKE_DONE frame. +641 /// At this point, the server has either accepted our authentication, +642 /// or, if client authentication is not required, accepted our lack of authentication. +643 pub async fn handshake_confirmed(&self) -> Result<(), ConnectionError> { +644 { +645 let conn = self.0.lock_without_waking("handshake_confirmed"); +646 if let Some(error) = conn.error.as_ref() { +647 return Err(error.clone()); +648 } +649 if conn.handshake_confirmed { +650 return Ok(()); +651 } +652 // Construct the future while the lock is held to ensure we can't miss a wakeup if +653 // the `Notify` is signaled immediately after we release the lock. `await` it after +654 // the lock guard is out of scope. +655 self.0.shared.handshake_confirmed.notified() +656 } +657 .await; +658 if let Some(error) = self +659 .0 +660 .lock_without_waking("handshake_confirmed") +661 .error +662 .as_ref() +663 { +664 Err(error.clone()) +665 } else { +666 Ok(()) +667 } +668 } +669 +670 /// Transmit `data` as an unreliable, unordered application datagram +671 /// +672 /// Application datagrams are a low-level primitive. They may be lost or delivered out of order, +673 /// and `data` must both fit inside a single QUIC packet and be smaller than the maximum +674 /// dictated by the peer. +675 /// +676 /// Previously queued datagrams which are still unsent may be discarded to make space for this +677 /// datagram, in order of oldest to newest. +678 pub fn send_datagram(&self, data: Bytes) -> Result<(), SendDatagramError> { +679 let conn = &mut *self.0.lock_and_wake("send_datagram"); +680 if let Some(ref x) = conn.error { +681 return Err(SendDatagramError::ConnectionLost(x.clone())); +682 } +683 use proto::SendDatagramError::*; +684 match conn.inner.datagrams().send(data, true) { +685 Ok(()) => Ok(()), +686 Err(e) => Err(match e { +687 Blocked(..) => unreachable!(), +688 UnsupportedByPeer => SendDatagramError::UnsupportedByPeer, +689 Disabled => SendDatagramError::Disabled, +690 TooLarge => SendDatagramError::TooLarge, +691 }), +692 } +693 } +694 +695 /// Transmit `data` as an unreliable, unordered application datagram +696 /// +697 /// Unlike [`send_datagram()`], this method will wait for buffer space during congestion +698 /// conditions, which effectively prioritizes old datagrams over new datagrams. +699 /// +700 /// See [`send_datagram()`] for details. +701 /// +702 /// [`send_datagram()`]: Connection::send_datagram +703 pub fn send_datagram_wait(&self, data: Bytes) -> SendDatagram<'_> { +704 SendDatagram { +705 conn: &self.0, +706 data: Some(data), +707 notify: self.0.shared.datagrams_unblocked.notified(), +708 } +709 } +710 +711 /// Compute the maximum size of datagrams that may be passed to [`send_datagram()`]. +712 /// +713 /// Returns `None` if datagrams are unsupported by the peer or disabled locally. +714 /// +715 /// This may change over the lifetime of a connection according to variation in the path MTU +716 /// estimate. The peer can also enforce an arbitrarily small fixed limit, but if the peer's +717 /// limit is large this is guaranteed to be a little over a kilobyte at minimum. +718 /// +719 /// Not necessarily the maximum size of received datagrams. +720 /// +721 /// [`send_datagram()`]: Connection::send_datagram +722 pub fn max_datagram_size(&self) -> Option<usize> { +723 self.0 +724 .lock_without_waking("max_datagram_size") +725 .inner +726 .datagrams() +727 .max_size() +728 } +729 +730 /// Bytes available in the outgoing datagram buffer +731 /// +732 /// When greater than zero, calling [`send_datagram()`](Self::send_datagram) with a datagram of +733 /// at most this size is guaranteed not to cause older datagrams to be dropped. +734 pub fn datagram_send_buffer_space(&self) -> usize { +735 self.0 +736 .lock_without_waking("datagram_send_buffer_space") +737 .inner +738 .datagrams() +739 .send_buffer_space() +740 } +741 +742 /// The side of the connection (client or server) +743 pub fn side(&self) -> Side { +744 self.0.lock_without_waking("side").inner.side() +745 } +746 +747 /// Current best estimate of this connection's latency (round-trip-time) +748 pub fn rtt(&self, path_id: PathId) -> Option<Duration> { +749 self.0.lock_without_waking("rtt").inner.rtt(path_id) +750 } +751 +752 /// Returns connection statistics +753 pub fn stats(&self) -> ConnectionStats { +754 self.0.lock_without_waking("stats").inner.stats() +755 } +756 +757 /// Returns path statistics +758 pub fn path_stats(&self, path_id: PathId) -> Option<PathStats> { +759 self.0.lock_without_waking("path_stats").path_stats(path_id) +760 } +761 +762 /// Current state of the congestion control algorithm, for debugging purposes +763 pub fn congestion_state(&self, path_id: PathId) -> Option<Box<dyn Controller>> { +764 self.0 +765 .lock_without_waking("congestion_state") +766 .inner +767 .congestion_state(path_id) +768 .map(|c| c.clone_box()) 769 } 770 -771 /// The local IP address which was used when the peer established -772 /// the connection -773 /// -774 /// This can be different from the address the endpoint is bound to, in case -775 /// the endpoint is bound to a wildcard address like `0.0.0.0` or `::`. +771 /// Parameters negotiated during the handshake +772 /// +773 /// Guaranteed to return `Some` on fully established connections or after +774 /// [`Connecting::handshake_data()`] succeeds. See that method's documentations for details on +775 /// the returned value. 776 /// -777 /// This will return `None` for clients, or when the platform does not expose this -778 /// information. See [`noq_udp::RecvMeta::dst_ip`](udp::RecvMeta::dst_ip) for a list of -779 /// supported platforms when using [`noq_udp`](udp) for I/O, which is the default. -780 pub fn local_ip(&self) -> Option<IpAddr> { -781 // TODO: an unwrap again -782 let state = self.0.lock_without_waking("remote_address"); -783 state -784 .inner -785 .paths() -786 .iter() -787 .filter_map(|id| state.inner.network_path(*id).ok()) -788 .next() -789 .unwrap() -790 .local_ip() -791 } -792 -793 /// Current best estimate of this connection's latency (round-trip-time) -794 pub fn rtt(&self, path_id: PathId) -> Option<Duration> { -795 self.0.lock_without_waking("rtt").inner.rtt(path_id) -796 } -797 -798 /// Returns connection statistics -799 pub fn stats(&self) -> ConnectionStats { -800 self.0.lock_without_waking("stats").inner.stats() -801 } -802 -803 /// Returns path statistics -804 pub fn path_stats(&self, path_id: PathId) -> Option<PathStats> { -805 self.0.lock_without_waking("path_stats").path_stats(path_id) -806 } -807 -808 /// Current state of the congestion control algorithm, for debugging purposes -809 pub fn congestion_state(&self, path_id: PathId) -> Option<Box<dyn Controller>> { -810 self.0 -811 .lock_without_waking("congestion_state") -812 .inner -813 .congestion_state(path_id) -814 .map(|c| c.clone_box()) +777 /// [`Connection::handshake_data()`]: crate::Connecting::handshake_data +778 pub fn handshake_data(&self) -> Option<Box<dyn Any>> { +779 self.0 +780 .lock_without_waking("handshake_data") +781 .inner +782 .crypto_session() +783 .handshake_data() +784 } +785 +786 /// Cryptographic identity of the peer +787 /// +788 /// The dynamic type returned is determined by the configured +789 /// [`Session`](proto::crypto::Session). For the default `rustls` session, the return value can +790 /// be [`downcast`](Box::downcast) to a <code>Vec<[rustls::pki_types::CertificateDer]></code> +791 pub fn peer_identity(&self) -> Option<Box<dyn Any>> { +792 self.0 +793 .lock_without_waking("peer_identity") +794 .inner +795 .crypto_session() +796 .peer_identity() +797 } +798 +799 /// A stable identifier for this connection +800 /// +801 /// Peer addresses and connection IDs can change, but this value will remain +802 /// fixed for the lifetime of the connection. +803 pub fn stable_id(&self) -> usize { +804 self.0.stable_id() +805 } +806 +807 /// Update traffic keys spontaneously +808 /// +809 /// This primarily exists for testing purposes. +810 pub fn force_key_update(&self) { +811 self.0 +812 .lock_and_wake("force_key_update") +813 .inner +814 .force_key_update() 815 } 816 -817 /// Parameters negotiated during the handshake +817 /// Derive keying material from this connection's TLS session secrets. 818 /// -819 /// Guaranteed to return `Some` on fully established connections or after -820 /// [`Connecting::handshake_data()`] succeeds. See that method's documentations for details on -821 /// the returned value. -822 /// -823 /// [`Connection::handshake_data()`]: crate::Connecting::handshake_data -824 pub fn handshake_data(&self) -> Option<Box<dyn Any>> { -825 self.0 -826 .lock_without_waking("handshake_data") -827 .inner -828 .crypto_session() -829 .handshake_data() -830 } -831 -832 /// Cryptographic identity of the peer -833 /// -834 /// The dynamic type returned is determined by the configured -835 /// [`Session`](proto::crypto::Session). For the default `rustls` session, the return value can -836 /// be [`downcast`](Box::downcast) to a <code>Vec<[rustls::pki_types::CertificateDer]></code> -837 pub fn peer_identity(&self) -> Option<Box<dyn Any>> { -838 self.0 -839 .lock_without_waking("peer_identity") -840 .inner -841 .crypto_session() -842 .peer_identity() -843 } -844 -845 /// A stable identifier for this connection -846 /// -847 /// Peer addresses and connection IDs can change, but this value will remain -848 /// fixed for the lifetime of the connection. -849 pub fn stable_id(&self) -> usize { -850 self.0.stable_id() +819 /// When both peers call this method with the same `label` and `context` +820 /// arguments and `output` buffers of equal length, they will get the +821 /// same sequence of bytes in `output`. These bytes are cryptographically +822 /// strong and pseudorandom, and are suitable for use as keying material. +823 /// +824 /// See [RFC5705](https://tools.ietf.org/html/rfc5705) for more information. +825 pub fn export_keying_material( +826 &self, +827 output: &mut [u8], +828 label: &[u8], +829 context: &[u8], +830 ) -> Result<(), proto::crypto::ExportKeyingMaterialError> { +831 self.0 +832 .lock_without_waking("export_keying_material") +833 .inner +834 .crypto_session() +835 .export_keying_material(output, label, context) +836 } +837 +838 /// Modify the number of remotely initiated unidirectional streams that may be concurrently open +839 /// +840 /// No streams may be opened by the peer unless fewer than `count` are already open. Large +841 /// `count`s increase both minimum and worst-case memory consumption. +842 pub fn set_max_concurrent_uni_streams(&self, count: VarInt) { +843 let mut conn = self.0.lock_and_wake("set_max_concurrent_uni_streams"); +844 conn.inner.set_max_concurrent_streams(Dir::Uni, count); +845 } +846 +847 /// See [`proto::TransportConfig::send_window()`] +848 pub fn set_send_window(&self, send_window: u64) { +849 let mut conn = self.0.lock_and_wake("set_send_window"); +850 conn.inner.set_send_window(send_window); 851 } 852 -853 /// Update traffic keys spontaneously -854 /// -855 /// This primarily exists for testing purposes. -856 pub fn force_key_update(&self) { -857 self.0 -858 .lock_and_wake("force_key_update") -859 .inner -860 .force_key_update() -861 } -862 -863 /// Derive keying material from this connection's TLS session secrets. -864 /// -865 /// When both peers call this method with the same `label` and `context` -866 /// arguments and `output` buffers of equal length, they will get the -867 /// same sequence of bytes in `output`. These bytes are cryptographically -868 /// strong and pseudorandom, and are suitable for use as keying material. -869 /// -870 /// See [RFC5705](https://tools.ietf.org/html/rfc5705) for more information. -871 pub fn export_keying_material( -872 &self, -873 output: &mut [u8], -874 label: &[u8], -875 context: &[u8], -876 ) -> Result<(), proto::crypto::ExportKeyingMaterialError> { -877 self.0 -878 .lock_without_waking("export_keying_material") -879 .inner -880 .crypto_session() -881 .export_keying_material(output, label, context) -882 } -883 -884 /// Modify the number of remotely initiated unidirectional streams that may be concurrently open -885 /// -886 /// No streams may be opened by the peer unless fewer than `count` are already open. Large -887 /// `count`s increase both minimum and worst-case memory consumption. -888 pub fn set_max_concurrent_uni_streams(&self, count: VarInt) { -889 let mut conn = self.0.lock_and_wake("set_max_concurrent_uni_streams"); -890 conn.inner.set_max_concurrent_streams(Dir::Uni, count); -891 } -892 -893 /// See [`proto::TransportConfig::send_window()`] -894 pub fn set_send_window(&self, send_window: u64) { -895 let mut conn = self.0.lock_and_wake("set_send_window"); -896 conn.inner.set_send_window(send_window); -897 } -898 -899 /// See [`proto::TransportConfig::receive_window()`] -900 pub fn set_receive_window(&self, receive_window: VarInt) { -901 let mut conn = self.0.lock_and_wake("set_receive_window"); -902 conn.inner.set_receive_window(receive_window); -903 } -904 -905 /// Modify the number of remotely initiated bidirectional streams that may be concurrently open -906 /// -907 /// No streams may be opened by the peer unless fewer than `count` are already open. Large -908 /// `count`s increase both minimum and worst-case memory consumption. -909 pub fn set_max_concurrent_bi_streams(&self, count: VarInt) { -910 let mut conn = self.0.lock_and_wake("set_max_concurrent_bi_streams"); -911 conn.inner.set_max_concurrent_streams(Dir::Bi, count); -912 } -913 -914 /// Track changes on our external address as reported by the peer. -915 pub fn observed_external_addr(&self) -> crate::ObservedExternalAddr { -916 let conn = self.0.lock_without_waking("external_addr"); -917 crate::ObservedExternalAddr::new(conn.observed_external_addr.subscribe()) -918 } -919 -920 /// Is multipath enabled? -921 // TODO(flub): not a useful API, once we do real things with multipath we can remove -922 // this again. -923 pub fn is_multipath_enabled(&self) -> bool { -924 let conn = self.0.lock_without_waking("is_multipath_enabled"); -925 conn.inner.is_multipath_negotiated() -926 } -927 -928 /// Registers one address at which this endpoint might be reachable -929 /// -930 /// When the NAT traversal extension is negotiated, servers send these addresses to clients in -931 /// `ADD_ADDRESS` frames. This allows clients to obtain server address candidates to initiate -932 /// NAT traversal attempts. Clients provide their own reachable addresses in `REACH_OUT` frames -933 /// when [`Self::initiate_nat_traversal_round`] is called. -934 pub fn add_nat_traversal_address( -935 &self, -936 address: SocketAddr, -937 ) -> Result<(), n0_nat_traversal::Error> { -938 let mut conn = self.0.lock_and_wake("add_nat_traversal_addresses"); -939 conn.inner.add_nat_traversal_address(address) -940 } -941 -942 /// Removes one or more addresses from the set of addresses at which this endpoint is reachable -943 /// -944 /// When the NAT traversal extension is negotiated, servers send address removals to -945 /// clients in `REMOVE_ADDRESS` frames. This allows clients to stop using outdated -946 /// server address candidates that are no longer valid for NAT traversal. -947 /// -948 /// For clients, removed addresses will no longer be advertised in `REACH_OUT` frames. -949 /// -950 /// Addresses not present in the set will be silently ignored. -951 pub fn remove_nat_traversal_address( -952 &self, -953 address: SocketAddr, -954 ) -> Result<(), n0_nat_traversal::Error> { -955 let mut conn = self.0.lock_and_wake("remove_nat_traversal_addresses"); -956 conn.inner.remove_nat_traversal_address(address) -957 } -958 -959 /// Get the current local nat traversal addresses -960 pub fn get_local_nat_traversal_addresses( -961 &self, -962 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { -963 let conn = self -964 .0 -965 .lock_without_waking("get_local_nat_traversal_addresses"); -966 conn.inner.get_local_nat_traversal_addresses() -967 } -968 -969 /// Get the currently advertised nat traversal addresses by the server -970 pub fn get_remote_nat_traversal_addresses( -971 &self, -972 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { -973 let conn = self -974 .0 -975 .lock_without_waking("get_remote_nat_traversal_addresses"); -976 conn.inner.get_remote_nat_traversal_addresses() -977 } -978 -979 /// Initiates a new nat traversal round -980 /// -981 /// A nat traversal round involves advertising the client's local addresses in `REACH_OUT` -982 /// frames, and initiating probing of the known remote addresses. When a new round is -983 /// initiated, the previous one is cancelled, and paths that have not been opened are closed. -984 /// -985 /// Returns the server addresses that are now being probed. -986 pub fn initiate_nat_traversal_round(&self) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { -987 let mut conn = self.0.lock_and_wake("initiate_nat_traversal_round"); -988 let now = conn.runtime.now(); -989 conn.inner.initiate_nat_traversal_round(now) -990 } -991} -992 -993pin_project! { -994 /// Future produced by [`Connection::open_uni`] -995 pub struct OpenUni<'a> { -996 conn: &'a ConnectionRef, -997 #[pin] -998 notify: Notified<'a>, -999 } -1000} -1001 -1002impl Future for OpenUni<'_> { -1003 type Output = Result<SendStream, ConnectionError>; -1004 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { -1005 let this = self.project(); -1006 let (conn, id, is_0rtt) = ready!(poll_open(ctx, this.conn, this.notify, Dir::Uni))?; -1007 Poll::Ready(Ok(SendStream::new(conn, id, is_0rtt))) -1008 } -1009} -1010 -1011pin_project! { -1012 /// Future produced by [`Connection::open_bi`] -1013 pub struct OpenBi<'a> { -1014 conn: &'a ConnectionRef, -1015 #[pin] -1016 notify: Notified<'a>, -1017 } -1018} -1019 -1020impl Future for OpenBi<'_> { -1021 type Output = Result<(SendStream, RecvStream), ConnectionError>; -1022 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { -1023 let this = self.project(); -1024 let (conn, id, is_0rtt) = ready!(poll_open(ctx, this.conn, this.notify, Dir::Bi))?; -1025 -1026 Poll::Ready(Ok(( -1027 SendStream::new(conn.clone(), id, is_0rtt), -1028 RecvStream::new(conn, id, is_0rtt), -1029 ))) -1030 } -1031} -1032 -1033fn poll_open<'a>( -1034 ctx: &mut Context<'_>, -1035 conn: &'a ConnectionRef, -1036 mut notify: Pin<&mut Notified<'a>>, -1037 dir: Dir, -1038) -> Poll<Result<(ConnectionRef, StreamId, bool), ConnectionError>> { -1039 let mut state = conn.lock_without_waking("poll_open"); -1040 if let Some(ref e) = state.error { -1041 return Poll::Ready(Err(e.clone())); -1042 } else if let Some(id) = state.inner.streams().open(dir) { -1043 let is_0rtt = state.inner.side().is_client() && state.inner.is_handshaking(); -1044 drop(state); // Release the lock so clone can take it -1045 return Poll::Ready(Ok((conn.clone(), id, is_0rtt))); -1046 } -1047 loop { -1048 match notify.as_mut().poll(ctx) { -1049 // `state` lock ensures we didn't race with readiness -1050 Poll::Pending => return Poll::Pending, -1051 // Spurious wakeup, get a new future -1052 Poll::Ready(()) => { -1053 notify.set(conn.shared.stream_budget_available[dir as usize].notified()) -1054 } -1055 } -1056 } -1057} -1058 -1059pin_project! { -1060 /// Future produced by [`Connection::accept_uni`] -1061 pub struct AcceptUni<'a> { -1062 conn: &'a ConnectionRef, -1063 #[pin] -1064 notify: Notified<'a>, -1065 } -1066} -1067 -1068impl Future for AcceptUni<'_> { -1069 type Output = Result<RecvStream, ConnectionError>; -1070 -1071 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { -1072 let this = self.project(); -1073 let (conn, id, is_0rtt) = ready!(poll_accept(ctx, this.conn, this.notify, Dir::Uni))?; -1074 Poll::Ready(Ok(RecvStream::new(conn, id, is_0rtt))) -1075 } -1076} -1077 -1078pin_project! { -1079 /// Future produced by [`Connection::accept_bi`] -1080 pub struct AcceptBi<'a> { -1081 conn: &'a ConnectionRef, -1082 #[pin] -1083 notify: Notified<'a>, -1084 } -1085} -1086 -1087impl Future for AcceptBi<'_> { -1088 type Output = Result<(SendStream, RecvStream), ConnectionError>; -1089 -1090 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { -1091 let this = self.project(); -1092 let (conn, id, is_0rtt) = ready!(poll_accept(ctx, this.conn, this.notify, Dir::Bi))?; -1093 Poll::Ready(Ok(( -1094 SendStream::new(conn.clone(), id, is_0rtt), -1095 RecvStream::new(conn, id, is_0rtt), -1096 ))) -1097 } -1098} -1099 -1100fn poll_accept<'a>( -1101 ctx: &mut Context<'_>, -1102 conn: &'a ConnectionRef, -1103 mut notify: Pin<&mut Notified<'a>>, -1104 dir: Dir, -1105) -> Poll<Result<(ConnectionRef, StreamId, bool), ConnectionError>> { -1106 let mut state = conn.lock_and_wake("poll_accept"); -1107 // Check for incoming streams before checking `state.error` so that already-received streams, -1108 // which are necessarily finite, can be drained from a closed connection. -1109 if let Some(id) = state.inner.streams().accept(dir) { -1110 let is_0rtt = state.inner.is_handshaking(); -1111 drop(state); // Release the lock (wake on drop) so clone can take it -1112 return Poll::Ready(Ok((conn.clone(), id, is_0rtt))); -1113 } else if let Some(ref e) = state.error { -1114 return Poll::Ready(Err(e.clone())); -1115 } -1116 loop { -1117 match notify.as_mut().poll(ctx) { -1118 // `state` lock ensures we didn't race with readiness -1119 Poll::Pending => return Poll::Pending, -1120 // Spurious wakeup, get a new future -1121 Poll::Ready(()) => notify.set(conn.shared.stream_incoming[dir as usize].notified()), -1122 } -1123 } -1124} -1125 -1126pin_project! { -1127 /// Future produced by [`Connection::read_datagram`] -1128 pub struct ReadDatagram<'a> { -1129 conn: &'a ConnectionRef, -1130 #[pin] -1131 notify: Notified<'a>, -1132 } -1133} -1134 -1135impl Future for ReadDatagram<'_> { -1136 type Output = Result<Bytes, ConnectionError>; -1137 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { -1138 let mut this = self.project(); -1139 let mut state = this.conn.lock_without_waking("ReadDatagram::poll"); -1140 // Check for buffered datagrams before checking `state.error` so that already-received -1141 // datagrams, which are necessarily finite, can be drained from a closed connection. -1142 if let Some(x) = state.inner.datagrams().recv() { -1143 return Poll::Ready(Ok(x)); -1144 } else if let Some(ref e) = state.error { -1145 return Poll::Ready(Err(e.clone())); -1146 } -1147 loop { -1148 match this.notify.as_mut().poll(ctx) { -1149 // `state` lock ensures we didn't race with readiness -1150 Poll::Pending => return Poll::Pending, -1151 // Spurious wakeup, get a new future -1152 Poll::Ready(()) => this -1153 .notify -1154 .set(this.conn.shared.datagram_received.notified()), -1155 } +853 /// See [`proto::TransportConfig::receive_window()`] +854 pub fn set_receive_window(&self, receive_window: VarInt) { +855 let mut conn = self.0.lock_and_wake("set_receive_window"); +856 conn.inner.set_receive_window(receive_window); +857 } +858 +859 /// Modify the number of remotely initiated bidirectional streams that may be concurrently open +860 /// +861 /// No streams may be opened by the peer unless fewer than `count` are already open. Large +862 /// `count`s increase both minimum and worst-case memory consumption. +863 pub fn set_max_concurrent_bi_streams(&self, count: VarInt) { +864 let mut conn = self.0.lock_and_wake("set_max_concurrent_bi_streams"); +865 conn.inner.set_max_concurrent_streams(Dir::Bi, count); +866 } +867 +868 /// Track changes on our external address as reported by the peer. +869 pub fn observed_external_addr(&self) -> crate::ObservedExternalAddr { +870 let conn = self.0.lock_without_waking("external_addr"); +871 crate::ObservedExternalAddr::new(conn.observed_external_addr.subscribe()) +872 } +873 +874 /// Is multipath enabled? +875 // TODO(flub): not a useful API, once we do real things with multipath we can remove +876 // this again. +877 pub fn is_multipath_enabled(&self) -> bool { +878 let conn = self.0.lock_without_waking("is_multipath_enabled"); +879 conn.inner.is_multipath_negotiated() +880 } +881 +882 /// Registers one address at which this endpoint might be reachable +883 /// +884 /// When the NAT traversal extension is negotiated, servers send these addresses to clients in +885 /// `ADD_ADDRESS` frames. This allows clients to obtain server address candidates to initiate +886 /// NAT traversal attempts. Clients provide their own reachable addresses in `REACH_OUT` frames +887 /// when [`Self::initiate_nat_traversal_round`] is called. +888 pub fn add_nat_traversal_address( +889 &self, +890 address: SocketAddr, +891 ) -> Result<(), n0_nat_traversal::Error> { +892 let mut conn = self.0.lock_and_wake("add_nat_traversal_addresses"); +893 conn.inner.add_nat_traversal_address(address) +894 } +895 +896 /// Removes one or more addresses from the set of addresses at which this endpoint is reachable +897 /// +898 /// When the NAT traversal extension is negotiated, servers send address removals to +899 /// clients in `REMOVE_ADDRESS` frames. This allows clients to stop using outdated +900 /// server address candidates that are no longer valid for NAT traversal. +901 /// +902 /// For clients, removed addresses will no longer be advertised in `REACH_OUT` frames. +903 /// +904 /// Addresses not present in the set will be silently ignored. +905 pub fn remove_nat_traversal_address( +906 &self, +907 address: SocketAddr, +908 ) -> Result<(), n0_nat_traversal::Error> { +909 let mut conn = self.0.lock_and_wake("remove_nat_traversal_addresses"); +910 conn.inner.remove_nat_traversal_address(address) +911 } +912 +913 /// Get the current local nat traversal addresses +914 pub fn get_local_nat_traversal_addresses( +915 &self, +916 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { +917 let conn = self +918 .0 +919 .lock_without_waking("get_local_nat_traversal_addresses"); +920 conn.inner.get_local_nat_traversal_addresses() +921 } +922 +923 /// Get the currently advertised nat traversal addresses by the server +924 pub fn get_remote_nat_traversal_addresses( +925 &self, +926 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { +927 let conn = self +928 .0 +929 .lock_without_waking("get_remote_nat_traversal_addresses"); +930 conn.inner.get_remote_nat_traversal_addresses() +931 } +932 +933 /// Initiates a new nat traversal round +934 /// +935 /// A nat traversal round involves advertising the client's local addresses in `REACH_OUT` +936 /// frames, and initiating probing of the known remote addresses. When a new round is +937 /// initiated, the previous one is cancelled, and paths that have not been opened are closed. +938 /// +939 /// Returns the server addresses that are now being probed. +940 pub fn initiate_nat_traversal_round(&self) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { +941 let mut conn = self.0.lock_and_wake("initiate_nat_traversal_round"); +942 let now = conn.runtime.now(); +943 conn.inner.initiate_nat_traversal_round(now) +944 } +945} +946 +947pin_project! { +948 /// Future produced by [`Connection::open_uni`] +949 pub struct OpenUni<'a> { +950 conn: &'a ConnectionRef, +951 #[pin] +952 notify: Notified<'a>, +953 } +954} +955 +956impl Future for OpenUni<'_> { +957 type Output = Result<SendStream, ConnectionError>; +958 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { +959 let this = self.project(); +960 let (conn, id, is_0rtt) = ready!(poll_open(ctx, this.conn, this.notify, Dir::Uni))?; +961 Poll::Ready(Ok(SendStream::new(conn, id, is_0rtt))) +962 } +963} +964 +965pin_project! { +966 /// Future produced by [`Connection::open_bi`] +967 pub struct OpenBi<'a> { +968 conn: &'a ConnectionRef, +969 #[pin] +970 notify: Notified<'a>, +971 } +972} +973 +974impl Future for OpenBi<'_> { +975 type Output = Result<(SendStream, RecvStream), ConnectionError>; +976 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { +977 let this = self.project(); +978 let (conn, id, is_0rtt) = ready!(poll_open(ctx, this.conn, this.notify, Dir::Bi))?; +979 +980 Poll::Ready(Ok(( +981 SendStream::new(conn.clone(), id, is_0rtt), +982 RecvStream::new(conn, id, is_0rtt), +983 ))) +984 } +985} +986 +987fn poll_open<'a>( +988 ctx: &mut Context<'_>, +989 conn: &'a ConnectionRef, +990 mut notify: Pin<&mut Notified<'a>>, +991 dir: Dir, +992) -> Poll<Result<(ConnectionRef, StreamId, bool), ConnectionError>> { +993 let mut state = conn.lock_without_waking("poll_open"); +994 if let Some(ref e) = state.error { +995 return Poll::Ready(Err(e.clone())); +996 } else if let Some(id) = state.inner.streams().open(dir) { +997 let is_0rtt = state.inner.side().is_client() && state.inner.is_handshaking(); +998 drop(state); // Release the lock so clone can take it +999 return Poll::Ready(Ok((conn.clone(), id, is_0rtt))); +1000 } +1001 loop { +1002 match notify.as_mut().poll(ctx) { +1003 // `state` lock ensures we didn't race with readiness +1004 Poll::Pending => return Poll::Pending, +1005 // Spurious wakeup, get a new future +1006 Poll::Ready(()) => { +1007 notify.set(conn.shared.stream_budget_available[dir as usize].notified()) +1008 } +1009 } +1010 } +1011} +1012 +1013pin_project! { +1014 /// Future produced by [`Connection::accept_uni`] +1015 pub struct AcceptUni<'a> { +1016 conn: &'a ConnectionRef, +1017 #[pin] +1018 notify: Notified<'a>, +1019 } +1020} +1021 +1022impl Future for AcceptUni<'_> { +1023 type Output = Result<RecvStream, ConnectionError>; +1024 +1025 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { +1026 let this = self.project(); +1027 let (conn, id, is_0rtt) = ready!(poll_accept(ctx, this.conn, this.notify, Dir::Uni))?; +1028 Poll::Ready(Ok(RecvStream::new(conn, id, is_0rtt))) +1029 } +1030} +1031 +1032pin_project! { +1033 /// Future produced by [`Connection::accept_bi`] +1034 pub struct AcceptBi<'a> { +1035 conn: &'a ConnectionRef, +1036 #[pin] +1037 notify: Notified<'a>, +1038 } +1039} +1040 +1041impl Future for AcceptBi<'_> { +1042 type Output = Result<(SendStream, RecvStream), ConnectionError>; +1043 +1044 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { +1045 let this = self.project(); +1046 let (conn, id, is_0rtt) = ready!(poll_accept(ctx, this.conn, this.notify, Dir::Bi))?; +1047 Poll::Ready(Ok(( +1048 SendStream::new(conn.clone(), id, is_0rtt), +1049 RecvStream::new(conn, id, is_0rtt), +1050 ))) +1051 } +1052} +1053 +1054fn poll_accept<'a>( +1055 ctx: &mut Context<'_>, +1056 conn: &'a ConnectionRef, +1057 mut notify: Pin<&mut Notified<'a>>, +1058 dir: Dir, +1059) -> Poll<Result<(ConnectionRef, StreamId, bool), ConnectionError>> { +1060 let mut state = conn.lock_and_wake("poll_accept"); +1061 // Check for incoming streams before checking `state.error` so that already-received streams, +1062 // which are necessarily finite, can be drained from a closed connection. +1063 if let Some(id) = state.inner.streams().accept(dir) { +1064 let is_0rtt = state.inner.is_handshaking(); +1065 drop(state); // Release the lock (wake on drop) so clone can take it +1066 return Poll::Ready(Ok((conn.clone(), id, is_0rtt))); +1067 } else if let Some(ref e) = state.error { +1068 return Poll::Ready(Err(e.clone())); +1069 } +1070 loop { +1071 match notify.as_mut().poll(ctx) { +1072 // `state` lock ensures we didn't race with readiness +1073 Poll::Pending => return Poll::Pending, +1074 // Spurious wakeup, get a new future +1075 Poll::Ready(()) => notify.set(conn.shared.stream_incoming[dir as usize].notified()), +1076 } +1077 } +1078} +1079 +1080pin_project! { +1081 /// Future produced by [`Connection::read_datagram`] +1082 pub struct ReadDatagram<'a> { +1083 conn: &'a ConnectionRef, +1084 #[pin] +1085 notify: Notified<'a>, +1086 } +1087} +1088 +1089impl Future for ReadDatagram<'_> { +1090 type Output = Result<Bytes, ConnectionError>; +1091 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { +1092 let mut this = self.project(); +1093 let mut state = this.conn.lock_without_waking("ReadDatagram::poll"); +1094 // Check for buffered datagrams before checking `state.error` so that already-received +1095 // datagrams, which are necessarily finite, can be drained from a closed connection. +1096 if let Some(x) = state.inner.datagrams().recv() { +1097 return Poll::Ready(Ok(x)); +1098 } else if let Some(ref e) = state.error { +1099 return Poll::Ready(Err(e.clone())); +1100 } +1101 loop { +1102 match this.notify.as_mut().poll(ctx) { +1103 // `state` lock ensures we didn't race with readiness +1104 Poll::Pending => return Poll::Pending, +1105 // Spurious wakeup, get a new future +1106 Poll::Ready(()) => this +1107 .notify +1108 .set(this.conn.shared.datagram_received.notified()), +1109 } +1110 } +1111 } +1112} +1113 +1114pin_project! { +1115 /// Future produced by [`Connection::send_datagram_wait`] +1116 pub struct SendDatagram<'a> { +1117 conn: &'a ConnectionRef, +1118 data: Option<Bytes>, +1119 #[pin] +1120 notify: Notified<'a>, +1121 } +1122} +1123 +1124impl Future for SendDatagram<'_> { +1125 type Output = Result<(), SendDatagramError>; +1126 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { +1127 let mut this = self.project(); +1128 let mut state = this.conn.lock_and_wake("SendDatagram::poll"); +1129 if let Some(ref e) = state.error { +1130 return Poll::Ready(Err(SendDatagramError::ConnectionLost(e.clone()))); +1131 } +1132 use proto::SendDatagramError::*; +1133 match state +1134 .inner +1135 .datagrams() +1136 .send(this.data.take().unwrap(), false) +1137 { +1138 Ok(()) => Poll::Ready(Ok(())), +1139 Err(e) => Poll::Ready(Err(match e { +1140 Blocked(data) => { +1141 this.data.replace(data); +1142 loop { +1143 match this.notify.as_mut().poll(ctx) { +1144 Poll::Pending => return Poll::Pending, +1145 // Spurious wakeup, get a new future +1146 Poll::Ready(()) => this +1147 .notify +1148 .set(this.conn.shared.datagrams_unblocked.notified()), +1149 } +1150 } +1151 } +1152 UnsupportedByPeer => SendDatagramError::UnsupportedByPeer, +1153 Disabled => SendDatagramError::Disabled, +1154 TooLarge => SendDatagramError::TooLarge, +1155 })), 1156 } 1157 } 1158} 1159 -1160pin_project! { -1161 /// Future produced by [`Connection::send_datagram_wait`] -1162 pub struct SendDatagram<'a> { -1163 conn: &'a ConnectionRef, -1164 data: Option<Bytes>, -1165 #[pin] -1166 notify: Notified<'a>, -1167 } -1168} -1169 -1170impl Future for SendDatagram<'_> { -1171 type Output = Result<(), SendDatagramError>; -1172 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { -1173 let mut this = self.project(); -1174 let mut state = this.conn.lock_and_wake("SendDatagram::poll"); -1175 if let Some(ref e) = state.error { -1176 return Poll::Ready(Err(SendDatagramError::ConnectionLost(e.clone()))); -1177 } -1178 use proto::SendDatagramError::*; -1179 match state -1180 .inner -1181 .datagrams() -1182 .send(this.data.take().unwrap(), false) -1183 { -1184 Ok(()) => Poll::Ready(Ok(())), -1185 Err(e) => Poll::Ready(Err(match e { -1186 Blocked(data) => { -1187 this.data.replace(data); -1188 loop { -1189 match this.notify.as_mut().poll(ctx) { -1190 Poll::Pending => return Poll::Pending, -1191 // Spurious wakeup, get a new future -1192 Poll::Ready(()) => this -1193 .notify -1194 .set(this.conn.shared.datagrams_unblocked.notified()), -1195 } -1196 } -1197 } -1198 UnsupportedByPeer => SendDatagramError::UnsupportedByPeer, -1199 Disabled => SendDatagramError::Disabled, -1200 TooLarge => SendDatagramError::TooLarge, -1201 })), -1202 } -1203 } -1204} -1205 -1206/// State of a [`Connection`] at the moment it was closed. -1207/// -1208/// Returned by the [`OnClosed`] future from [`Connection::on_closed`]. -1209#[derive(Debug, Clone)] -1210#[non_exhaustive] -1211pub struct Closed { -1212 /// The reason the connection was closed. -1213 pub reason: ConnectionError, -1214 /// Aggregate connection statistics at the moment of close. -1215 pub stats: ConnectionStats, -1216 /// Per-path statistics for every path the connection knew about at close time. -1217 /// -1218 /// This includes paths that haven't been discarded at close time, plus any -1219 /// already-discarded paths whose final stats had been retained because a [`Path`] -1220 /// or [`WeakPathHandle`] handle was kept alive. -1221 /// -1222 /// [`WeakPathHandle`]: crate::WeakPathHandle -1223 pub path_stats: BTreeMap<PathId, PathStats>, -1224} -1225 -1226impl Closed { -1227 /// Snapshot the current connection state into a [`Closed`] value. -1228 /// -1229 /// Must only be called once `state.error` has been set. -1230 pub(crate) fn new(state: &mut State) -> Self { -1231 let reason = state -1232 .error -1233 .clone() -1234 .expect("Closed::new called before error was set"); -1235 let stats = state.inner.stats(); -1236 let mut path_stats = BTreeMap::new(); -1237 // Non-discarded paths are tracked by proto::Connection. -1238 for path_id in state.inner.paths() { -1239 if let Some(stats) = state.inner.path_stats(path_id) { -1240 path_stats.insert(path_id, stats); -1241 } -1242 } -1243 // Already-discarded paths whose final stats we kept around. -1244 for (path_id, ps) in &state.final_path_stats { -1245 path_stats.entry(*path_id).or_insert(*ps); -1246 } -1247 Self { -1248 reason, -1249 stats, -1250 path_stats, -1251 } -1252 } -1253} -1254 -1255/// Future returned by [`Connection::on_closed`] -1256/// -1257/// Resolves to [`Closed`]. -1258pub struct OnClosed { -1259 rx: oneshot::Receiver<Closed>, -1260 conn: WeakConnectionHandle, -1261} -1262 -1263impl Drop for OnClosed { -1264 fn drop(&mut self) { -1265 if self.rx.is_terminated() { -1266 return; -1267 }; -1268 if let Some(conn) = self.conn.upgrade() { -1269 self.rx.close(); -1270 conn.0 -1271 .lock_without_waking("OnClosed::drop") -1272 .on_closed -1273 .retain(|tx| !tx.is_closed()); -1274 } -1275 } -1276} -1277 -1278impl Future for OnClosed { -1279 type Output = Closed; -1280 -1281 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { -1282 let this = self.get_mut(); -1283 // The `expect` is safe because `State::drop` ensures that all senders are triggered -1284 // before being dropped. -1285 Pin::new(&mut this.rx) -1286 .poll(cx) -1287 .map(|x| x.expect("on_close sender is never dropped before sending")) -1288 } -1289} -1290 -1291#[derive(Debug)] -1292#[allow(clippy::redundant_allocation)] -1293pub(crate) struct ConnectionRef(Arc<Arc<ConnectionInner>>); -1294 -1295impl ConnectionRef { -1296 #[allow(clippy::redundant_allocation)] -1297 fn from_arc(inner: Arc<Arc<ConnectionInner>>) -> Self { -1298 inner.shared.ref_count.fetch_add(1, Ordering::Relaxed); -1299 Self(inner) -1300 } -1301 -1302 pub(crate) fn stable_id(&self) -> usize { -1303 &*self.0 as *const _ as usize -1304 } -1305 -1306 pub(crate) fn weak_handle(&self) -> WeakConnectionHandle { -1307 WeakConnectionHandle(Arc::downgrade(&self.0)) -1308 } -1309} -1310 -1311impl Clone for ConnectionRef { -1312 fn clone(&self) -> Self { -1313 Self::from_arc(Arc::clone(&self.0)) -1314 } -1315} -1316 -1317impl Drop for ConnectionRef { -1318 fn drop(&mut self) { -1319 if self.shared.ref_count.fetch_sub(1, Ordering::Relaxed) > 1 { -1320 return; -1321 } -1322 -1323 let conn = &mut *self.lock_without_waking("drop"); -1324 -1325 if !conn.inner.is_closed() { -1326 // If the driver is alive, it's just it and us, so we'd better shut it down. If it's -1327 // not, we can't do any harm. If there were any streams being opened, then either -1328 // the connection will be closed for an unrelated reason or a fresh reference will -1329 // be constructed for the newly opened stream. -1330 conn.implicit_close(&self.shared); -1331 } -1332 } -1333} -1334 -1335impl std::ops::Deref for ConnectionRef { -1336 type Target = ConnectionInner; -1337 fn deref(&self) -> &Self::Target { -1338 &self.0 -1339 } +1160/// State of a [`Connection`] at the moment it was closed. +1161/// +1162/// Returned by the [`OnClosed`] future from [`Connection::on_closed`]. +1163#[derive(Debug, Clone)] +1164#[non_exhaustive] +1165pub struct Closed { +1166 /// The reason the connection was closed. +1167 pub reason: ConnectionError, +1168 /// Aggregate connection statistics at the moment of close. +1169 pub stats: ConnectionStats, +1170 /// Per-path statistics for every path the connection knew about at close time. +1171 /// +1172 /// This includes paths that haven't been discarded at close time, plus any +1173 /// already-discarded paths whose final stats had been retained because a [`Path`] +1174 /// or [`WeakPathHandle`] handle was kept alive. +1175 /// +1176 /// [`WeakPathHandle`]: crate::WeakPathHandle +1177 pub path_stats: BTreeMap<PathId, PathStats>, +1178} +1179 +1180impl Closed { +1181 /// Snapshot the current connection state into a [`Closed`] value. +1182 /// +1183 /// Must only be called once `state.error` has been set. +1184 pub(crate) fn new(state: &mut State) -> Self { +1185 let reason = state +1186 .error +1187 .clone() +1188 .expect("Closed::new called before error was set"); +1189 let stats = state.inner.stats(); +1190 let mut path_stats = BTreeMap::new(); +1191 // Non-discarded paths are tracked by proto::Connection. +1192 for path_id in state.inner.paths() { +1193 if let Some(stats) = state.inner.path_stats(path_id) { +1194 path_stats.insert(path_id, stats); +1195 } +1196 } +1197 // Already-discarded paths whose final stats we kept around. +1198 for (path_id, ps) in &state.final_path_stats { +1199 path_stats.entry(*path_id).or_insert(*ps); +1200 } +1201 Self { +1202 reason, +1203 stats, +1204 path_stats, +1205 } +1206 } +1207} +1208 +1209/// Future returned by [`Connection::on_closed`] +1210/// +1211/// Resolves to [`Closed`]. +1212pub struct OnClosed { +1213 rx: oneshot::Receiver<Closed>, +1214 conn: WeakConnectionHandle, +1215} +1216 +1217impl Drop for OnClosed { +1218 fn drop(&mut self) { +1219 if self.rx.is_terminated() { +1220 return; +1221 }; +1222 if let Some(conn) = self.conn.upgrade() { +1223 self.rx.close(); +1224 conn.0 +1225 .lock_without_waking("OnClosed::drop") +1226 .on_closed +1227 .retain(|tx| !tx.is_closed()); +1228 } +1229 } +1230} +1231 +1232impl Future for OnClosed { +1233 type Output = Closed; +1234 +1235 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { +1236 let this = self.get_mut(); +1237 // The `expect` is safe because `State::drop` ensures that all senders are triggered +1238 // before being dropped. +1239 Pin::new(&mut this.rx) +1240 .poll(cx) +1241 .map(|x| x.expect("on_close sender is never dropped before sending")) +1242 } +1243} +1244 +1245#[derive(Debug)] +1246#[allow(clippy::redundant_allocation)] +1247pub(crate) struct ConnectionRef(Arc<Arc<ConnectionInner>>); +1248 +1249impl ConnectionRef { +1250 #[allow(clippy::redundant_allocation)] +1251 fn from_arc(inner: Arc<Arc<ConnectionInner>>) -> Self { +1252 inner.shared.ref_count.fetch_add(1, Ordering::Relaxed); +1253 Self(inner) +1254 } +1255 +1256 pub(crate) fn stable_id(&self) -> usize { +1257 &*self.0 as *const _ as usize +1258 } +1259 +1260 pub(crate) fn weak_handle(&self) -> WeakConnectionHandle { +1261 WeakConnectionHandle(Arc::downgrade(&self.0)) +1262 } +1263} +1264 +1265impl Clone for ConnectionRef { +1266 fn clone(&self) -> Self { +1267 Self::from_arc(Arc::clone(&self.0)) +1268 } +1269} +1270 +1271impl Drop for ConnectionRef { +1272 fn drop(&mut self) { +1273 if self.shared.ref_count.fetch_sub(1, Ordering::Relaxed) > 1 { +1274 return; +1275 } +1276 +1277 let conn = &mut *self.lock_without_waking("drop"); +1278 +1279 if !conn.inner.is_closed() { +1280 // If the driver is alive, it's just it and us, so we'd better shut it down. If it's +1281 // not, we can't do any harm. If there were any streams being opened, then either +1282 // the connection will be closed for an unrelated reason or a fresh reference will +1283 // be constructed for the newly opened stream. +1284 conn.implicit_close(&self.shared); +1285 } +1286 } +1287} +1288 +1289impl std::ops::Deref for ConnectionRef { +1290 type Target = ConnectionInner; +1291 fn deref(&self) -> &Self::Target { +1292 &self.0 +1293 } +1294} +1295 +1296#[derive(Debug)] +1297pub(crate) struct ConnectionInner { +1298 /// Kept private intentionally, use [`Self::lock_and_wake`]. +1299 state: Mutex<State>, +1300 pub(crate) shared: Shared, +1301} +1302 +1303impl ConnectionInner { +1304 /// Lock the state and return a guard that wakes the connection driver on drop. +1305 /// +1306 /// Use this for operations that may queue frames. The wake ensures the driver sends queued frames. +1307 /// If that's not needed, use [`Self::lock_without_waking`]. +1308 pub(crate) fn lock_and_wake(&self, purpose: &'static str) -> WakeGuard<'_> { +1309 WakeGuard { +1310 guard: self.state.lock(purpose), +1311 wake: true, +1312 } +1313 } +1314 +1315 /// Lock the state and return a guard that unlocks once dropped. +1316 /// +1317 /// Use this for operations that don't require any action from the connection driver. +1318 /// Otherwise, use [`Self::lock_and_wake`] instead. +1319 pub(crate) fn lock_without_waking(&self, purpose: &'static str) -> WakeGuard<'_> { +1320 WakeGuard { +1321 guard: self.state.lock(purpose), +1322 wake: false, +1323 } +1324 } +1325} +1326 +1327/// [`MutexGuard`] wrapper that calls [`State::wake`] on drop. +1328#[derive(derive_more::Deref, derive_more::DerefMut)] +1329pub(crate) struct WakeGuard<'a> { +1330 #[deref] +1331 #[deref_mut] +1332 guard: MutexGuard<'a, State>, +1333 wake: bool, +1334} +1335 +1336impl WakeGuard<'_> { +1337 pub(crate) fn skip_waking(&mut self) { +1338 self.wake = false; +1339 } 1340} 1341 -1342#[derive(Debug)] -1343pub(crate) struct ConnectionInner { -1344 /// Kept private intentionally, use [`Self::lock_and_wake`]. -1345 state: Mutex<State>, -1346 pub(crate) shared: Shared, -1347} -1348 -1349impl ConnectionInner { -1350 /// Lock the state and return a guard that wakes the connection driver on drop. -1351 /// -1352 /// Use this for operations that may queue frames. The wake ensures the driver sends queued frames. -1353 /// If that's not needed, use [`Self::lock_without_waking`]. -1354 pub(crate) fn lock_and_wake(&self, purpose: &'static str) -> WakeGuard<'_> { -1355 WakeGuard { -1356 guard: self.state.lock(purpose), -1357 wake: true, -1358 } -1359 } -1360 -1361 /// Lock the state and return a guard that unlocks once dropped. -1362 /// -1363 /// Use this for operations that don't require any action from the connection driver. -1364 /// Otherwise, use [`Self::lock_and_wake`] instead. -1365 pub(crate) fn lock_without_waking(&self, purpose: &'static str) -> WakeGuard<'_> { -1366 WakeGuard { -1367 guard: self.state.lock(purpose), -1368 wake: false, -1369 } +1342impl Drop for WakeGuard<'_> { +1343 fn drop(&mut self) { +1344 if self.wake { +1345 self.guard.wake(); +1346 } +1347 } +1348} +1349 +1350/// A handle to some connection internals, use with care. +1351/// +1352/// This contains a weak reference to the connection so will not itself keep the connection +1353/// alive. +1354#[derive(Debug, Clone)] +1355pub struct WeakConnectionHandle(Weak<Arc<ConnectionInner>>); +1356 +1357impl WeakConnectionHandle { +1358 /// Returns `true` if the [`Connection`] associated with this handle is still alive. +1359 pub fn is_alive(&self) -> bool { +1360 self.0.upgrade().is_some() +1361 } +1362 +1363 /// Upgrade the handle to a full `Connection` +1364 pub fn upgrade(&self) -> Option<Connection> { +1365 self.upgrade_to_ref().map(Connection) +1366 } +1367 +1368 pub(crate) fn upgrade_to_ref(&self) -> Option<ConnectionRef> { +1369 self.0.upgrade().map(ConnectionRef::from_arc) 1370 } -1371} -1372 -1373/// [`MutexGuard`] wrapper that calls [`State::wake`] on drop. -1374#[derive(derive_more::Deref, derive_more::DerefMut)] -1375pub(crate) struct WakeGuard<'a> { -1376 #[deref] -1377 #[deref_mut] -1378 guard: MutexGuard<'a, State>, -1379 wake: bool, -1380} -1381 -1382impl WakeGuard<'_> { -1383 pub(crate) fn skip_waking(&mut self) { -1384 self.wake = false; -1385 } -1386} -1387 -1388impl Drop for WakeGuard<'_> { -1389 fn drop(&mut self) { -1390 if self.wake { -1391 self.guard.wake(); -1392 } -1393 } -1394} -1395 -1396/// A handle to some connection internals, use with care. -1397/// -1398/// This contains a weak reference to the connection so will not itself keep the connection -1399/// alive. -1400#[derive(Debug, Clone)] -1401pub struct WeakConnectionHandle(Weak<Arc<ConnectionInner>>); -1402 -1403impl WeakConnectionHandle { -1404 /// Returns `true` if the [`Connection`] associated with this handle is still alive. -1405 pub fn is_alive(&self) -> bool { -1406 self.0.upgrade().is_some() -1407 } -1408 -1409 /// Upgrade the handle to a full `Connection` -1410 pub fn upgrade(&self) -> Option<Connection> { -1411 self.upgrade_to_ref().map(Connection) -1412 } -1413 -1414 pub(crate) fn upgrade_to_ref(&self) -> Option<ConnectionRef> { -1415 self.0.upgrade().map(ConnectionRef::from_arc) -1416 } -1417 -1418 /// Returns `true` if the two [`WeakConnectionHandle`] point at the same connection. -1419 pub fn is_same_connection(&self, other: &Self) -> bool { -1420 self.0.ptr_eq(&other.0) -1421 } -1422} -1423 -1424#[derive(Debug, Default)] -1425pub(crate) struct Shared { -1426 handshake_confirmed: Notify, -1427 /// Notified when new streams may be locally initiated due to an increase in stream ID flow -1428 /// control budget -1429 stream_budget_available: [Notify; 2], -1430 /// Notified when the peer has initiated a new stream -1431 stream_incoming: [Notify; 2], -1432 datagram_received: Notify, -1433 datagrams_unblocked: Notify, -1434 closed: Notify, -1435 /// Number of live handles that can be used to initiate or handle I/O; excludes the driver -1436 ref_count: AtomicUsize, -1437} -1438 -1439pub(crate) struct State { -1440 pub(crate) inner: proto::Connection, -1441 driver: Option<Waker>, -1442 handle: ConnectionHandle, -1443 on_handshake_data: Option<oneshot::Sender<()>>, -1444 on_connected: Option<oneshot::Sender<bool>>, -1445 connected: bool, -1446 handshake_confirmed: bool, -1447 timer: Option<Pin<Box<dyn AsyncTimer>>>, -1448 timer_deadline: Option<Instant>, -1449 conn_events: mpsc::UnboundedReceiver<ConnectionEvent>, -1450 endpoint_events: mpsc::UnboundedSender<(ConnectionHandle, EndpointEvent)>, -1451 pub(crate) blocked_writers: FxHashMap<StreamId, Waker>, -1452 pub(crate) blocked_readers: FxHashMap<StreamId, Waker>, -1453 pub(crate) stopped: FxHashMap<StreamId, Arc<Notify>>, -1454 /// Always set to Some before the connection becomes drained -1455 pub(crate) error: Option<ConnectionError>, -1456 /// Tracks paths being opened -1457 open_path: FxHashMap<PathId, watch::Sender<Result<(), PathError>>>, -1458 /// Tracks reference counts for paths. -1459 /// -1460 /// I.e. how many [`Path`] and [`WeakPathHandle`] structs are alive for a path. -1461 /// -1462 /// [`WeakPathHandle`]: crate::path::WeakPathHandle -1463 pub(crate) path_refs: FxHashMap<PathId, usize>, -1464 /// Final path stats for discarded paths. -1465 /// -1466 /// We only insert entries if the discarded path has a non-zero reference count in [`Self::path_refs`]. -1467 /// When the last reference to a path is dropped via [`Self::decrement_path_refs`] its value is cleared. -1468 pub(crate) final_path_stats: FxHashMap<PathId, PathStats>, -1469 pub(crate) path_events: tokio::sync::broadcast::Sender<PathEvent>, -1470 sender: Pin<Box<dyn UdpSender>>, -1471 pub(crate) runtime: Arc<dyn Runtime>, -1472 send_buffer: Vec<u8>, -1473 /// We buffer a transmit when the underlying I/O would block -1474 buffered_transmit: Option<proto::Transmit>, -1475 /// Our last external address reported by the peer. When multipath is enabled, this will be the -1476 /// last report across all paths. -1477 pub(crate) observed_external_addr: watch::Sender<Option<SocketAddr>>, -1478 pub(crate) nat_traversal_updates: tokio::sync::broadcast::Sender<n0_nat_traversal::Event>, -1479 on_closed: Vec<oneshot::Sender<Closed>>, -1480} +1371 +1372 /// Returns `true` if the two [`WeakConnectionHandle`] point at the same connection. +1373 pub fn is_same_connection(&self, other: &Self) -> bool { +1374 self.0.ptr_eq(&other.0) +1375 } +1376} +1377 +1378#[derive(Debug, Default)] +1379pub(crate) struct Shared { +1380 handshake_confirmed: Notify, +1381 /// Notified when new streams may be locally initiated due to an increase in stream ID flow +1382 /// control budget +1383 stream_budget_available: [Notify; 2], +1384 /// Notified when the peer has initiated a new stream +1385 stream_incoming: [Notify; 2], +1386 datagram_received: Notify, +1387 datagrams_unblocked: Notify, +1388 closed: Notify, +1389 /// Number of live handles that can be used to initiate or handle I/O; excludes the driver +1390 ref_count: AtomicUsize, +1391} +1392 +1393pub(crate) struct State { +1394 pub(crate) inner: proto::Connection, +1395 driver: Option<Waker>, +1396 handle: ConnectionHandle, +1397 on_handshake_data: Option<oneshot::Sender<()>>, +1398 on_connected: Option<oneshot::Sender<bool>>, +1399 connected: bool, +1400 handshake_confirmed: bool, +1401 timer: Option<Pin<Box<dyn AsyncTimer>>>, +1402 timer_deadline: Option<Instant>, +1403 conn_events: mpsc::UnboundedReceiver<ConnectionEvent>, +1404 endpoint_events: mpsc::UnboundedSender<(ConnectionHandle, EndpointEvent)>, +1405 pub(crate) blocked_writers: FxHashMap<StreamId, Waker>, +1406 pub(crate) blocked_readers: FxHashMap<StreamId, Waker>, +1407 pub(crate) stopped: FxHashMap<StreamId, Arc<Notify>>, +1408 /// Always set to Some before the connection becomes drained +1409 pub(crate) error: Option<ConnectionError>, +1410 /// Tracks paths being opened +1411 open_path: FxHashMap<PathId, watch::Sender<Result<(), PathError>>>, +1412 /// Tracks reference counts for paths. +1413 /// +1414 /// I.e. how many [`Path`] and [`WeakPathHandle`] structs are alive for a path. +1415 /// +1416 /// [`WeakPathHandle`]: crate::path::WeakPathHandle +1417 pub(crate) path_refs: FxHashMap<PathId, usize>, +1418 /// Final path stats for discarded paths. +1419 /// +1420 /// We only insert entries if the discarded path has a non-zero reference count in [`Self::path_refs`]. +1421 /// When the last reference to a path is dropped via [`Self::decrement_path_refs`] its value is cleared. +1422 pub(crate) final_path_stats: FxHashMap<PathId, PathStats>, +1423 pub(crate) path_events: tokio::sync::broadcast::Sender<PathEvent>, +1424 sender: Pin<Box<dyn UdpSender>>, +1425 pub(crate) runtime: Arc<dyn Runtime>, +1426 send_buffer: Vec<u8>, +1427 /// We buffer a transmit when the underlying I/O would block +1428 buffered_transmit: Option<proto::Transmit>, +1429 /// Our last external address reported by the peer. When multipath is enabled, this will be the +1430 /// last report across all paths. +1431 pub(crate) observed_external_addr: watch::Sender<Option<SocketAddr>>, +1432 pub(crate) nat_traversal_updates: tokio::sync::broadcast::Sender<n0_nat_traversal::Event>, +1433 on_closed: Vec<oneshot::Sender<Closed>>, +1434} +1435 +1436impl State { +1437 #[allow(clippy::too_many_arguments)] +1438 fn new( +1439 inner: proto::Connection, +1440 handle: ConnectionHandle, +1441 endpoint_events: mpsc::UnboundedSender<(ConnectionHandle, EndpointEvent)>, +1442 conn_events: mpsc::UnboundedReceiver<ConnectionEvent>, +1443 on_handshake_data: oneshot::Sender<()>, +1444 on_connected: oneshot::Sender<bool>, +1445 sender: Pin<Box<dyn UdpSender>>, +1446 runtime: Arc<dyn Runtime>, +1447 ) -> Self { +1448 Self { +1449 inner, +1450 driver: None, +1451 handle, +1452 on_handshake_data: Some(on_handshake_data), +1453 on_connected: Some(on_connected), +1454 connected: false, +1455 handshake_confirmed: false, +1456 timer: None, +1457 timer_deadline: None, +1458 conn_events, +1459 endpoint_events, +1460 blocked_writers: FxHashMap::default(), +1461 blocked_readers: FxHashMap::default(), +1462 stopped: FxHashMap::default(), +1463 open_path: FxHashMap::default(), +1464 error: None, +1465 sender, +1466 runtime, +1467 send_buffer: Vec::new(), +1468 buffered_transmit: None, +1469 path_events: tokio::sync::broadcast::channel(32).0, +1470 observed_external_addr: watch::Sender::new(None), +1471 nat_traversal_updates: tokio::sync::broadcast::channel(32).0, +1472 on_closed: Vec::new(), +1473 final_path_stats: Default::default(), +1474 path_refs: Default::default(), +1475 } +1476 } +1477 +1478 fn drive_transmit(&mut self, cx: &mut Context<'_>) -> io::Result<bool> { +1479 let now = self.runtime.now(); +1480 let mut transmits = 0; 1481 -1482impl State { -1483 #[allow(clippy::too_many_arguments)] -1484 fn new( -1485 inner: proto::Connection, -1486 handle: ConnectionHandle, -1487 endpoint_events: mpsc::UnboundedSender<(ConnectionHandle, EndpointEvent)>, -1488 conn_events: mpsc::UnboundedReceiver<ConnectionEvent>, -1489 on_handshake_data: oneshot::Sender<()>, -1490 on_connected: oneshot::Sender<bool>, -1491 sender: Pin<Box<dyn UdpSender>>, -1492 runtime: Arc<dyn Runtime>, -1493 ) -> Self { -1494 Self { -1495 inner, -1496 driver: None, -1497 handle, -1498 on_handshake_data: Some(on_handshake_data), -1499 on_connected: Some(on_connected), -1500 connected: false, -1501 handshake_confirmed: false, -1502 timer: None, -1503 timer_deadline: None, -1504 conn_events, -1505 endpoint_events, -1506 blocked_writers: FxHashMap::default(), -1507 blocked_readers: FxHashMap::default(), -1508 stopped: FxHashMap::default(), -1509 open_path: FxHashMap::default(), -1510 error: None, -1511 sender, -1512 runtime, -1513 send_buffer: Vec::new(), -1514 buffered_transmit: None, -1515 path_events: tokio::sync::broadcast::channel(32).0, -1516 observed_external_addr: watch::Sender::new(None), -1517 nat_traversal_updates: tokio::sync::broadcast::channel(32).0, -1518 on_closed: Vec::new(), -1519 final_path_stats: Default::default(), -1520 path_refs: Default::default(), -1521 } -1522 } -1523 -1524 fn drive_transmit(&mut self, cx: &mut Context<'_>) -> io::Result<bool> { -1525 let now = self.runtime.now(); -1526 let mut transmits = 0; -1527 -1528 let max_datagrams = self -1529 .sender -1530 .max_transmit_segments() -1531 .min(MAX_TRANSMIT_SEGMENTS); -1532 -1533 loop { -1534 // Retry the last transmit, or get a new one. -1535 let t = match self.buffered_transmit.take() { -1536 Some(t) => t, -1537 None => { -1538 self.send_buffer.clear(); -1539 match self -1540 .inner -1541 .poll_transmit(now, max_datagrams, &mut self.send_buffer) -1542 { -1543 Some(t) => { -1544 transmits += match t.segment_size { -1545 None => 1, -1546 Some(s) => t.size.div_ceil(s), // round up -1547 }; -1548 t -1549 } -1550 None => break, -1551 } -1552 } -1553 }; -1554 -1555 let len = t.size; -1556 match self -1557 .sender -1558 .as_mut() -1559 .poll_send(&udp_transmit(&t, &self.send_buffer[..len]), cx) -1560 { -1561 Poll::Pending => { -1562 self.buffered_transmit = Some(t); -1563 return Ok(false); -1564 } -1565 Poll::Ready(Err(e)) => return Err(e), -1566 Poll::Ready(Ok(())) => {} -1567 } -1568 -1569 if transmits >= MAX_TRANSMIT_DATAGRAMS { -1570 // TODO: What isn't ideal here yet is that if we don't poll all -1571 // datagrams that could be sent we don't go into the `app_limited` -1572 // state and CWND continues to grow until we get here the next time. -1573 // See https://github.com/quinn-rs/quinn/issues/1126 -1574 return Ok(true); -1575 } -1576 } -1577 -1578 Ok(false) -1579 } -1580 -1581 fn forward_endpoint_events(&mut self) { -1582 while let Some(event) = self.inner.poll_endpoint_events() { -1583 // If the endpoint driver is gone, noop. -1584 let _ = self.endpoint_events.send((self.handle, event)); -1585 } -1586 } -1587 -1588 /// If this returns `Err`, the endpoint is dead, so the driver should exit immediately. -1589 fn process_conn_events( -1590 &mut self, -1591 shared: &Shared, -1592 cx: &mut Context<'_>, -1593 ) -> Result<(), ConnectionError> { -1594 loop { -1595 match self.conn_events.poll_recv(cx) { -1596 Poll::Ready(Some(ConnectionEvent::Rebind(sender))) => { -1597 self.sender = sender; -1598 self.inner.handle_network_change(None, self.runtime.now()); +1482 let max_datagrams = self +1483 .sender +1484 .max_transmit_segments() +1485 .min(MAX_TRANSMIT_SEGMENTS); +1486 +1487 loop { +1488 // Retry the last transmit, or get a new one. +1489 let t = match self.buffered_transmit.take() { +1490 Some(t) => t, +1491 None => { +1492 self.send_buffer.clear(); +1493 match self +1494 .inner +1495 .poll_transmit(now, max_datagrams, &mut self.send_buffer) +1496 { +1497 Some(t) => { +1498 transmits += match t.segment_size { +1499 None => 1, +1500 Some(s) => t.size.div_ceil(s), // round up +1501 }; +1502 t +1503 } +1504 None => break, +1505 } +1506 } +1507 }; +1508 +1509 let len = t.size; +1510 match self +1511 .sender +1512 .as_mut() +1513 .poll_send(&udp_transmit(&t, &self.send_buffer[..len]), cx) +1514 { +1515 Poll::Pending => { +1516 self.buffered_transmit = Some(t); +1517 return Ok(false); +1518 } +1519 Poll::Ready(Err(e)) => return Err(e), +1520 Poll::Ready(Ok(())) => {} +1521 } +1522 +1523 if transmits >= MAX_TRANSMIT_DATAGRAMS { +1524 // TODO: What isn't ideal here yet is that if we don't poll all +1525 // datagrams that could be sent we don't go into the `app_limited` +1526 // state and CWND continues to grow until we get here the next time. +1527 // See https://github.com/quinn-rs/quinn/issues/1126 +1528 return Ok(true); +1529 } +1530 } +1531 +1532 Ok(false) +1533 } +1534 +1535 fn forward_endpoint_events(&mut self) { +1536 while let Some(event) = self.inner.poll_endpoint_events() { +1537 // If the endpoint driver is gone, noop. +1538 let _ = self.endpoint_events.send((self.handle, event)); +1539 } +1540 } +1541 +1542 /// If this returns `Err`, the endpoint is dead, so the driver should exit immediately. +1543 fn process_conn_events( +1544 &mut self, +1545 shared: &Shared, +1546 cx: &mut Context<'_>, +1547 ) -> Result<(), ConnectionError> { +1548 loop { +1549 match self.conn_events.poll_recv(cx) { +1550 Poll::Ready(Some(ConnectionEvent::Rebind(sender))) => { +1551 self.sender = sender; +1552 self.inner.handle_network_change(None, self.runtime.now()); +1553 } +1554 Poll::Ready(Some(ConnectionEvent::LocalAddressChanged(hint))) => { +1555 self.inner +1556 .handle_network_change(hint.as_deref().map(|x| x as _), self.runtime.now()); +1557 } +1558 Poll::Ready(Some(ConnectionEvent::Proto(event))) => { +1559 self.inner.handle_event(event); +1560 } +1561 Poll::Ready(Some(ConnectionEvent::Close { reason, error_code })) => { +1562 self.close(error_code, reason, shared); +1563 } +1564 Poll::Ready(None) => { +1565 return Err(ConnectionError::TransportError(TransportError::new( +1566 TransportErrorCode::INTERNAL_ERROR, +1567 "endpoint driver future was dropped".to_string(), +1568 ))); +1569 } +1570 Poll::Pending => { +1571 return Ok(()); +1572 } +1573 } +1574 } +1575 } +1576 +1577 fn forward_app_events(&mut self, shared: &Shared) { +1578 while let Some(event) = self.inner.poll() { +1579 use proto::Event::*; +1580 match event { +1581 HandshakeDataReady => { +1582 if let Some(x) = self.on_handshake_data.take() { +1583 let _ = x.send(()); +1584 } +1585 } +1586 Connected => { +1587 self.connected = true; +1588 if let Some(x) = self.on_connected.take() { +1589 // We don't care if the on-connected future was dropped +1590 let _ = x.send(self.inner.accepted_0rtt()); +1591 } +1592 if self.inner.side().is_client() && !self.inner.accepted_0rtt() { +1593 // Wake up rejected 0-RTT streams so they can fail immediately with +1594 // `ZeroRttRejected` errors. +1595 wake_all(&mut self.blocked_writers); +1596 wake_all(&mut self.blocked_readers); +1597 wake_all_notify(&mut self.stopped); +1598 } 1599 } -1600 Poll::Ready(Some(ConnectionEvent::LocalAddressChanged(hint))) => { -1601 self.inner -1602 .handle_network_change(hint.as_deref().map(|x| x as _), self.runtime.now()); +1600 HandshakeConfirmed => { +1601 self.handshake_confirmed = true; +1602 shared.handshake_confirmed.notify_waiters(); 1603 } -1604 Poll::Ready(Some(ConnectionEvent::Proto(event))) => { -1605 self.inner.handle_event(event); +1604 ConnectionLost { reason } => { +1605 self.terminate(reason, shared); 1606 } -1607 Poll::Ready(Some(ConnectionEvent::Close { reason, error_code })) => { -1608 self.close(error_code, reason, shared); -1609 } -1610 Poll::Ready(None) => { -1611 return Err(ConnectionError::TransportError(TransportError::new( -1612 TransportErrorCode::INTERNAL_ERROR, -1613 "endpoint driver future was dropped".to_string(), -1614 ))); -1615 } -1616 Poll::Pending => { -1617 return Ok(()); -1618 } -1619 } -1620 } -1621 } -1622 -1623 fn forward_app_events(&mut self, shared: &Shared) { -1624 while let Some(event) = self.inner.poll() { -1625 use proto::Event::*; -1626 match event { -1627 HandshakeDataReady => { -1628 if let Some(x) = self.on_handshake_data.take() { -1629 let _ = x.send(()); -1630 } -1631 } -1632 Connected => { -1633 self.connected = true; -1634 if let Some(x) = self.on_connected.take() { -1635 // We don't care if the on-connected future was dropped -1636 let _ = x.send(self.inner.accepted_0rtt()); -1637 } -1638 if self.inner.side().is_client() && !self.inner.accepted_0rtt() { -1639 // Wake up rejected 0-RTT streams so they can fail immediately with -1640 // `ZeroRttRejected` errors. -1641 wake_all(&mut self.blocked_writers); -1642 wake_all(&mut self.blocked_readers); -1643 wake_all_notify(&mut self.stopped); -1644 } -1645 } -1646 HandshakeConfirmed => { -1647 self.handshake_confirmed = true; -1648 shared.handshake_confirmed.notify_waiters(); -1649 } -1650 ConnectionLost { reason } => { -1651 self.terminate(reason, shared); -1652 } -1653 Stream(StreamEvent::Writable { id }) => wake_stream(id, &mut self.blocked_writers), -1654 Stream(StreamEvent::Opened { dir: Dir::Uni }) => { -1655 shared.stream_incoming[Dir::Uni as usize].notify_waiters(); -1656 } -1657 Stream(StreamEvent::Opened { dir: Dir::Bi }) => { -1658 shared.stream_incoming[Dir::Bi as usize].notify_waiters(); -1659 } -1660 DatagramReceived => { -1661 shared.datagram_received.notify_waiters(); -1662 } -1663 DatagramsUnblocked => { -1664 shared.datagrams_unblocked.notify_waiters(); -1665 } -1666 Stream(StreamEvent::Readable { id }) => wake_stream(id, &mut self.blocked_readers), -1667 Stream(StreamEvent::Available { dir }) => { -1668 // Might mean any number of streams are ready, so we wake up everyone -1669 shared.stream_budget_available[dir as usize].notify_waiters(); -1670 } -1671 Stream(StreamEvent::Finished { id }) => wake_stream_notify(id, &mut self.stopped), -1672 Stream(StreamEvent::Stopped { id, .. }) => { -1673 wake_stream_notify(id, &mut self.stopped); -1674 wake_stream(id, &mut self.blocked_writers); -1675 } -1676 Path(ref evt @ PathEvent::ObservedAddr { addr: observed, .. }) => { -1677 self.path_events.send(evt.clone()).ok(); -1678 self.observed_external_addr.send_if_modified(|addr| { -1679 let old = addr.replace(observed); -1680 old != *addr -1681 }); -1682 } -1683 Path(ref evt @ PathEvent::Opened { id }) => { -1684 self.path_events.send(evt.clone()).ok(); -1685 if let Some(sender) = self.open_path.remove(&id) { -1686 sender.send_modify(|value| *value = Ok(())); -1687 } -1688 } -1689 Path(ref evt @ PathEvent::Discarded { id, ref path_stats }) => { -1690 if self.path_refs.contains_key(&id) { -1691 self.final_path_stats.insert(id, *path_stats.clone()); -1692 } -1693 self.path_events.send(evt.clone()).ok(); -1694 } -1695 Path(ref evt @ PathEvent::Abandoned { id, .. }) => { -1696 if let Some(sender) = self.open_path.remove(&id) { -1697 // We don't care for the reason why this path was closed here, because semantically -1698 // all close reasons for a path that has not yet been opened equals to `ValidationFailed`. -1699 // With the noq API, there is no way to application-close a not-yet-opened path, so -1700 // `ApplicationClosed` cannot occur. And all other variants will only occur for paths -1701 // that have already been opened. -1702 // The previous iteration of this code had another event `PathEvent::LocallyClosed` which -1703 // contained a `PathError`, but that was only ever set to `ValidationFailed`. -1704 let error = PathError::ValidationFailed; -1705 sender.send_modify(|value| *value = Err(error)); -1706 } -1707 // this will happen also for already opened paths -1708 self.path_events.send(evt.clone()).ok(); -1709 } -1710 Path(evt @ PathEvent::RemoteStatus { .. }) => { -1711 self.path_events.send(evt).ok(); -1712 } -1713 NatTraversal(update) => { -1714 self.nat_traversal_updates.send(update).ok(); -1715 } -1716 } -1717 } -1718 } -1719 -1720 fn drive_timer(&mut self, cx: &mut Context<'_>) -> bool { -1721 // Check whether we need to (re)set the timer. If so, we must poll again to ensure the -1722 // timer is registered with the runtime (and check whether it's already -1723 // expired). -1724 match self.inner.poll_timeout() { -1725 Some(deadline) => { -1726 if let Some(delay) = &mut self.timer { -1727 // There is no need to reset the tokio timer if the deadline -1728 // did not change -1729 if self -1730 .timer_deadline -1731 .map(|current_deadline| current_deadline != deadline) -1732 .unwrap_or(true) -1733 { -1734 delay.as_mut().reset(deadline); -1735 } -1736 } else { -1737 self.timer = Some(self.runtime.new_timer(deadline)); -1738 } -1739 // Store the actual expiration time of the timer -1740 self.timer_deadline = Some(deadline); -1741 } -1742 None => { -1743 self.timer_deadline = None; -1744 return false; -1745 } -1746 } -1747 -1748 if self.timer_deadline.is_none() { -1749 return false; -1750 } +1607 Stream(StreamEvent::Writable { id }) => wake_stream(id, &mut self.blocked_writers), +1608 Stream(StreamEvent::Opened { dir: Dir::Uni }) => { +1609 shared.stream_incoming[Dir::Uni as usize].notify_waiters(); +1610 } +1611 Stream(StreamEvent::Opened { dir: Dir::Bi }) => { +1612 shared.stream_incoming[Dir::Bi as usize].notify_waiters(); +1613 } +1614 DatagramReceived => { +1615 shared.datagram_received.notify_waiters(); +1616 } +1617 DatagramsUnblocked => { +1618 shared.datagrams_unblocked.notify_waiters(); +1619 } +1620 Stream(StreamEvent::Readable { id }) => wake_stream(id, &mut self.blocked_readers), +1621 Stream(StreamEvent::Available { dir }) => { +1622 // Might mean any number of streams are ready, so we wake up everyone +1623 shared.stream_budget_available[dir as usize].notify_waiters(); +1624 } +1625 Stream(StreamEvent::Finished { id }) => wake_stream_notify(id, &mut self.stopped), +1626 Stream(StreamEvent::Stopped { id, .. }) => { +1627 wake_stream_notify(id, &mut self.stopped); +1628 wake_stream(id, &mut self.blocked_writers); +1629 } +1630 Path(ref evt @ PathEvent::ObservedAddr { addr: observed, .. }) => { +1631 self.path_events.send(evt.clone()).ok(); +1632 self.observed_external_addr.send_if_modified(|addr| { +1633 let old = addr.replace(observed); +1634 old != *addr +1635 }); +1636 } +1637 Path(ref evt @ PathEvent::Opened { id }) => { +1638 self.path_events.send(evt.clone()).ok(); +1639 if let Some(sender) = self.open_path.remove(&id) { +1640 sender.send_modify(|value| *value = Ok(())); +1641 } +1642 } +1643 Path(ref evt @ PathEvent::Discarded { id, ref path_stats }) => { +1644 if self.path_refs.contains_key(&id) { +1645 self.final_path_stats.insert(id, *path_stats.clone()); +1646 } +1647 self.path_events.send(evt.clone()).ok(); +1648 } +1649 Path(ref evt @ PathEvent::Abandoned { id, .. }) => { +1650 if let Some(sender) = self.open_path.remove(&id) { +1651 // We don't care for the reason why this path was closed here, because semantically +1652 // all close reasons for a path that has not yet been opened equals to `ValidationFailed`. +1653 // With the noq API, there is no way to application-close a not-yet-opened path, so +1654 // `ApplicationClosed` cannot occur. And all other variants will only occur for paths +1655 // that have already been opened. +1656 // The previous iteration of this code had another event `PathEvent::LocallyClosed` which +1657 // contained a `PathError`, but that was only ever set to `ValidationFailed`. +1658 let error = PathError::ValidationFailed; +1659 sender.send_modify(|value| *value = Err(error)); +1660 } +1661 // this will happen also for already opened paths +1662 self.path_events.send(evt.clone()).ok(); +1663 } +1664 Path(evt @ PathEvent::RemoteStatus { .. }) => { +1665 self.path_events.send(evt).ok(); +1666 } +1667 NatTraversal(update) => { +1668 self.nat_traversal_updates.send(update).ok(); +1669 } +1670 } +1671 } +1672 } +1673 +1674 fn drive_timer(&mut self, cx: &mut Context<'_>) -> bool { +1675 // Check whether we need to (re)set the timer. If so, we must poll again to ensure the +1676 // timer is registered with the runtime (and check whether it's already +1677 // expired). +1678 match self.inner.poll_timeout() { +1679 Some(deadline) => { +1680 if let Some(delay) = &mut self.timer { +1681 // There is no need to reset the tokio timer if the deadline +1682 // did not change +1683 if self +1684 .timer_deadline +1685 .map(|current_deadline| current_deadline != deadline) +1686 .unwrap_or(true) +1687 { +1688 delay.as_mut().reset(deadline); +1689 } +1690 } else { +1691 self.timer = Some(self.runtime.new_timer(deadline)); +1692 } +1693 // Store the actual expiration time of the timer +1694 self.timer_deadline = Some(deadline); +1695 } +1696 None => { +1697 self.timer_deadline = None; +1698 return false; +1699 } +1700 } +1701 +1702 if self.timer_deadline.is_none() { +1703 return false; +1704 } +1705 +1706 let delay = self +1707 .timer +1708 .as_mut() +1709 .expect("timer must exist in this state") +1710 .as_mut(); +1711 if delay.poll(cx).is_pending() { +1712 // Since there wasn't a timeout event, there is nothing new +1713 // for the connection to do +1714 return false; +1715 } +1716 +1717 // A timer expired, so the caller needs to check for +1718 // new transmits, which might cause new timers to be set. +1719 self.inner.handle_timeout(self.runtime.now()); +1720 self.timer_deadline = None; +1721 true +1722 } +1723 +1724 /// Wake up a blocked `Driver` task to process I/O +1725 pub(crate) fn wake(&mut self) { +1726 if let Some(x) = self.driver.take() { +1727 x.wake(); +1728 } +1729 } +1730 +1731 /// Used to wake up all blocked futures when the connection becomes closed for any reason +1732 fn terminate(&mut self, reason: ConnectionError, shared: &Shared) { +1733 self.error = Some(reason); +1734 if let Some(x) = self.on_handshake_data.take() { +1735 let _ = x.send(()); +1736 } +1737 wake_all(&mut self.blocked_writers); +1738 wake_all(&mut self.blocked_readers); +1739 shared.stream_budget_available[Dir::Uni as usize].notify_waiters(); +1740 shared.stream_budget_available[Dir::Bi as usize].notify_waiters(); +1741 shared.stream_incoming[Dir::Uni as usize].notify_waiters(); +1742 shared.stream_incoming[Dir::Bi as usize].notify_waiters(); +1743 shared.datagram_received.notify_waiters(); +1744 shared.datagrams_unblocked.notify_waiters(); +1745 if let Some(x) = self.on_connected.take() { +1746 let _ = x.send(false); +1747 } +1748 shared.handshake_confirmed.notify_waiters(); +1749 wake_all_notify(&mut self.stopped); +1750 shared.closed.notify_waiters(); 1751 -1752 let delay = self -1753 .timer -1754 .as_mut() -1755 .expect("timer must exist in this state") -1756 .as_mut(); -1757 if delay.poll(cx).is_pending() { -1758 // Since there wasn't a timeout event, there is nothing new -1759 // for the connection to do -1760 return false; -1761 } -1762 -1763 // A timer expired, so the caller needs to check for -1764 // new transmits, which might cause new timers to be set. -1765 self.inner.handle_timeout(self.runtime.now()); -1766 self.timer_deadline = None; -1767 true -1768 } -1769 -1770 /// Wake up a blocked `Driver` task to process I/O -1771 pub(crate) fn wake(&mut self) { -1772 if let Some(x) = self.driver.take() { -1773 x.wake(); -1774 } -1775 } -1776 -1777 /// Used to wake up all blocked futures when the connection becomes closed for any reason -1778 fn terminate(&mut self, reason: ConnectionError, shared: &Shared) { -1779 self.error = Some(reason); -1780 if let Some(x) = self.on_handshake_data.take() { -1781 let _ = x.send(()); -1782 } -1783 wake_all(&mut self.blocked_writers); -1784 wake_all(&mut self.blocked_readers); -1785 shared.stream_budget_available[Dir::Uni as usize].notify_waiters(); -1786 shared.stream_budget_available[Dir::Bi as usize].notify_waiters(); -1787 shared.stream_incoming[Dir::Uni as usize].notify_waiters(); -1788 shared.stream_incoming[Dir::Bi as usize].notify_waiters(); -1789 shared.datagram_received.notify_waiters(); -1790 shared.datagrams_unblocked.notify_waiters(); -1791 if let Some(x) = self.on_connected.take() { -1792 let _ = x.send(false); -1793 } -1794 shared.handshake_confirmed.notify_waiters(); -1795 wake_all_notify(&mut self.stopped); -1796 shared.closed.notify_waiters(); -1797 -1798 // Send to the registered on_closed futures. -1799 if !self.on_closed.is_empty() { -1800 let closed = Closed::new(self); -1801 for tx in self.on_closed.drain(..) { -1802 tx.send(closed.clone()).ok(); -1803 } -1804 } -1805 } -1806 -1807 fn close(&mut self, error_code: VarInt, reason: Bytes, shared: &Shared) { -1808 self.inner.close(self.runtime.now(), error_code, reason); -1809 self.terminate(ConnectionError::LocallyClosed, shared); -1810 self.wake(); -1811 } -1812 -1813 /// Close for a reason other than the application's explicit request -1814 pub(crate) fn implicit_close(&mut self, shared: &Shared) { -1815 self.close(0u32.into(), Bytes::new(), shared); -1816 } -1817 -1818 pub(crate) fn check_0rtt(&self) -> Result<(), ()> { -1819 if self.inner.is_handshaking() -1820 || self.inner.accepted_0rtt() -1821 || self.inner.side().is_server() -1822 { -1823 Ok(()) -1824 } else { -1825 Err(()) -1826 } -1827 } -1828 -1829 /// Returns [`PathStats`] for a path, if available. -1830 /// -1831 /// This gets the stats from [`proto::Connection`]. If that returns `None` -1832 /// it gets them from `Self::final_path_stats` instead. -1833 pub(crate) fn path_stats(&mut self, path_id: PathId) -> Option<PathStats> { -1834 self.inner -1835 .path_stats(path_id) -1836 .or_else(|| self.final_path_stats.get(&path_id).copied()) -1837 } -1838 -1839 /// Increment the reference counter for a path in this connection. -1840 /// -1841 /// This counts how many [`Path`] or [`WeakPathHandle`] structs exist for a path. -1842 /// Currently this is used to determine whether to store the final stats after a path is -1843 /// abandoned. -1844 /// -1845 /// [`WeakPathHandle`]: crate::path::WeakPathHandle -1846 pub(crate) fn increment_path_refs(&mut self, path_id: PathId) { -1847 *self.path_refs.entry(path_id).or_default() += 1; -1848 } -1849 -1850 /// Decrement the reference counter for a path in this connection. -1851 pub(crate) fn decrement_path_refs(&mut self, path_id: PathId) { -1852 if let Some(refs) = self.path_refs.get_mut(&path_id) { -1853 *refs = refs.saturating_sub(1); -1854 if *refs == 0 { -1855 self.path_refs.remove(&path_id); -1856 self.final_path_stats.remove(&path_id); -1857 } -1858 } -1859 } -1860} -1861 -1862impl Drop for State { -1863 fn drop(&mut self) { -1864 if !self.inner.is_drained() { -1865 // Ensure the endpoint can tidy up -1866 let _ = self -1867 .endpoint_events -1868 .send((self.handle, proto::EndpointEvent::drained())); -1869 } -1870 -1871 if !self.on_closed.is_empty() { -1872 // Ensure that all on_closed oneshot senders are triggered before dropping. -1873 let closed = Closed::new(self); -1874 for tx in self.on_closed.drain(..) { -1875 tx.send(closed.clone()).ok(); -1876 } -1877 } -1878 } -1879} -1880 -1881impl fmt::Debug for State { -1882 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -1883 f.debug_struct("State").field("inner", &self.inner).finish() -1884 } -1885} -1886 -1887fn wake_stream(stream_id: StreamId, wakers: &mut FxHashMap<StreamId, Waker>) { -1888 if let Some(waker) = wakers.remove(&stream_id) { -1889 waker.wake(); -1890 } -1891} -1892 -1893fn wake_all(wakers: &mut FxHashMap<StreamId, Waker>) { -1894 wakers.drain().for_each(|(_, waker)| waker.wake()) -1895} -1896 -1897fn wake_stream_notify(stream_id: StreamId, wakers: &mut FxHashMap<StreamId, Arc<Notify>>) { -1898 if let Some(notify) = wakers.remove(&stream_id) { -1899 notify.notify_waiters() -1900 } -1901} -1902 -1903fn wake_all_notify(wakers: &mut FxHashMap<StreamId, Arc<Notify>>) { -1904 wakers -1905 .drain() -1906 .for_each(|(_, notify)| notify.notify_waiters()) -1907} -1908 -1909/// Errors that can arise when sending a datagram -1910#[derive(Debug, Error, Clone, Eq, PartialEq)] -1911pub enum SendDatagramError { -1912 /// The peer does not support receiving datagram frames -1913 #[error("datagrams not supported by peer")] -1914 UnsupportedByPeer, -1915 /// Datagram support is disabled locally -1916 #[error("datagram support disabled")] -1917 Disabled, -1918 /// The datagram is larger than the connection can currently accommodate -1919 /// -1920 /// Indicates that the path MTU minus overhead or the limit advertised by the peer has been -1921 /// exceeded. -1922 #[error("datagram too large")] -1923 TooLarge, -1924 /// The connection was lost -1925 #[error("connection lost")] -1926 ConnectionLost(#[from] ConnectionError), -1927} -1928 -1929/// The maximum amount of datagrams which will be produced in a single `drive_transmit` call -1930/// -1931/// This limits the amount of CPU resources consumed by datagram generation, -1932/// and allows other tasks (like receiving ACKs) to run in between. -1933const MAX_TRANSMIT_DATAGRAMS: usize = 20; -1934 -1935/// The maximum amount of datagrams that are sent in a single transmit -1936/// -1937/// This can be lower than the maximum platform capabilities, to avoid excessive -1938/// memory allocations when calling `poll_transmit()`. Benchmarks have shown -1939/// that numbers around 10 are a good compromise. -1940const MAX_TRANSMIT_SEGMENTS: NonZeroUsize = NonZeroUsize::new(10).expect("known");

    \ No newline at end of file +1752 // Send to the registered on_closed futures. +1753 if !self.on_closed.is_empty() { +1754 let closed = Closed::new(self); +1755 for tx in self.on_closed.drain(..) { +1756 tx.send(closed.clone()).ok(); +1757 } +1758 } +1759 } +1760 +1761 fn close(&mut self, error_code: VarInt, reason: Bytes, shared: &Shared) { +1762 self.inner.close(self.runtime.now(), error_code, reason); +1763 self.terminate(ConnectionError::LocallyClosed, shared); +1764 self.wake(); +1765 } +1766 +1767 /// Close for a reason other than the application's explicit request +1768 pub(crate) fn implicit_close(&mut self, shared: &Shared) { +1769 self.close(0u32.into(), Bytes::new(), shared); +1770 } +1771 +1772 pub(crate) fn check_0rtt(&self) -> Result<(), ()> { +1773 if self.inner.is_handshaking() +1774 || self.inner.accepted_0rtt() +1775 || self.inner.side().is_server() +1776 { +1777 Ok(()) +1778 } else { +1779 Err(()) +1780 } +1781 } +1782 +1783 /// Returns [`PathStats`] for a path, if available. +1784 /// +1785 /// This gets the stats from [`proto::Connection`]. If that returns `None` +1786 /// it gets them from `Self::final_path_stats` instead. +1787 pub(crate) fn path_stats(&mut self, path_id: PathId) -> Option<PathStats> { +1788 self.inner +1789 .path_stats(path_id) +1790 .or_else(|| self.final_path_stats.get(&path_id).copied()) +1791 } +1792 +1793 /// Increment the reference counter for a path in this connection. +1794 /// +1795 /// This counts how many [`Path`] or [`WeakPathHandle`] structs exist for a path. +1796 /// Currently this is used to determine whether to store the final stats after a path is +1797 /// abandoned. +1798 /// +1799 /// [`WeakPathHandle`]: crate::path::WeakPathHandle +1800 pub(crate) fn increment_path_refs(&mut self, path_id: PathId) { +1801 *self.path_refs.entry(path_id).or_default() += 1; +1802 } +1803 +1804 /// Decrement the reference counter for a path in this connection. +1805 pub(crate) fn decrement_path_refs(&mut self, path_id: PathId) { +1806 if let Some(refs) = self.path_refs.get_mut(&path_id) { +1807 *refs = refs.saturating_sub(1); +1808 if *refs == 0 { +1809 self.path_refs.remove(&path_id); +1810 self.final_path_stats.remove(&path_id); +1811 } +1812 } +1813 } +1814} +1815 +1816impl Drop for State { +1817 fn drop(&mut self) { +1818 if !self.inner.is_drained() { +1819 // Ensure the endpoint can tidy up +1820 let _ = self +1821 .endpoint_events +1822 .send((self.handle, proto::EndpointEvent::drained())); +1823 } +1824 +1825 if !self.on_closed.is_empty() { +1826 // Ensure that all on_closed oneshot senders are triggered before dropping. +1827 let closed = Closed::new(self); +1828 for tx in self.on_closed.drain(..) { +1829 tx.send(closed.clone()).ok(); +1830 } +1831 } +1832 } +1833} +1834 +1835impl fmt::Debug for State { +1836 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +1837 f.debug_struct("State").field("inner", &self.inner).finish() +1838 } +1839} +1840 +1841fn wake_stream(stream_id: StreamId, wakers: &mut FxHashMap<StreamId, Waker>) { +1842 if let Some(waker) = wakers.remove(&stream_id) { +1843 waker.wake(); +1844 } +1845} +1846 +1847fn wake_all(wakers: &mut FxHashMap<StreamId, Waker>) { +1848 wakers.drain().for_each(|(_, waker)| waker.wake()) +1849} +1850 +1851fn wake_stream_notify(stream_id: StreamId, wakers: &mut FxHashMap<StreamId, Arc<Notify>>) { +1852 if let Some(notify) = wakers.remove(&stream_id) { +1853 notify.notify_waiters() +1854 } +1855} +1856 +1857fn wake_all_notify(wakers: &mut FxHashMap<StreamId, Arc<Notify>>) { +1858 wakers +1859 .drain() +1860 .for_each(|(_, notify)| notify.notify_waiters()) +1861} +1862 +1863/// Errors that can arise when sending a datagram +1864#[derive(Debug, Error, Clone, Eq, PartialEq)] +1865pub enum SendDatagramError { +1866 /// The peer does not support receiving datagram frames +1867 #[error("datagrams not supported by peer")] +1868 UnsupportedByPeer, +1869 /// Datagram support is disabled locally +1870 #[error("datagram support disabled")] +1871 Disabled, +1872 /// The datagram is larger than the connection can currently accommodate +1873 /// +1874 /// Indicates that the path MTU minus overhead or the limit advertised by the peer has been +1875 /// exceeded. +1876 #[error("datagram too large")] +1877 TooLarge, +1878 /// The connection was lost +1879 #[error("connection lost")] +1880 ConnectionLost(#[from] ConnectionError), +1881} +1882 +1883/// The maximum amount of datagrams which will be produced in a single `drive_transmit` call +1884/// +1885/// This limits the amount of CPU resources consumed by datagram generation, +1886/// and allows other tasks (like receiving ACKs) to run in between. +1887const MAX_TRANSMIT_DATAGRAMS: usize = 20; +1888 +1889/// The maximum amount of datagrams that are sent in a single transmit +1890/// +1891/// This can be lower than the maximum platform capabilities, to avoid excessive +1892/// memory allocations when calling `poll_transmit()`. Benchmarks have shown +1893/// that numbers around 10 are a good compromise. +1894const MAX_TRANSMIT_SEGMENTS: NonZeroUsize = NonZeroUsize::new(10).expect("known");

    \ No newline at end of file diff --git a/pr/617/docs/src/noq/path.rs.html b/pr/617/docs/src/noq/path.rs.html index 51350e088..43cd32330 100644 --- a/pr/617/docs/src/noq/path.rs.html +++ b/pr/617/docs/src/noq/path.rs.html @@ -1,5 +1,5 @@ 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};
    @@ -275,140 +275,149 @@
     275        Ok(state.inner.network_path(self.id)?.remote())
     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)
    -282    }
    -283}
    -284
    -285impl PartialEq for Path {
    -286    fn eq(&self, other: &Self) -> bool {
    -287        self.id == other.id && self.conn.stable_id() == other.conn.stable_id()
    -288    }
    -289}
    -290
    -291/// Weak handle for a [`Path`] that does not keep the connection alive.
    -292///
    -293/// As long as a [`WeakPathHandle`] for a path exists, that path's final stats will not be dropped even if
    -294/// the path was abandoned.
    -295///
    -296/// The [`WeakPathHandle`] can be upgraded to a [`Path`] as long as its [`Connection`] has not been dropped.
    -297///
    -298/// [`Connection`]: crate::Connection
    -299#[derive(Debug)]
    -300pub struct WeakPathHandle {
    -301    id: PathId,
    -302    conn: WeakConnectionHandle,
    -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}
    +278    /// The local IP used for this path, if known.
    +279    ///
    +280    /// Returns `Ok(None)` for clients or when the platform does not expose this information; see
    +281    /// [`noq_udp::RecvMeta::dst_ip`](udp::RecvMeta::dst_ip) for supported platforms.
    +282    pub fn local_ip(&self) -> Result<Option<IpAddr>, ClosedPath> {
    +283        let state = self.conn.lock_without_waking("per_path_local_ip");
    +284        Ok(state.inner.network_path(self.id)?.local_ip())
    +285    }
    +286
    +287    /// Ping the remote endpoint over this path.
    +288    pub fn ping(&self) -> Result<(), ClosedPath> {
    +289        let mut state = self.conn.lock_and_wake("ping");
    +290        state.inner.ping_path(self.id)
    +291    }
    +292}
    +293
    +294impl PartialEq for Path {
    +295    fn eq(&self, other: &Self) -> bool {
    +296        self.id == other.id && self.conn.stable_id() == other.conn.stable_id()
    +297    }
    +298}
    +299
    +300/// Weak handle for a [`Path`] that does not keep the connection alive.
    +301///
    +302/// As long as a [`WeakPathHandle`] for a path exists, that path's final stats will not be dropped even if
    +303/// the path was abandoned.
    +304///
    +305/// The [`WeakPathHandle`] can be upgraded to a [`Path`] as long as its [`Connection`] has not been dropped.
    +306///
    +307/// [`Connection`]: crate::Connection
    +308#[derive(Debug)]
    +309pub struct WeakPathHandle {
    +310    id: PathId,
    +311    conn: WeakConnectionHandle,
    +312}
    +313
    +314impl Clone for WeakPathHandle {
    +315    fn clone(&self) -> Self {
    +316        if let Some(conn) = self.conn.upgrade_to_ref() {
    +317            conn.lock_without_waking("WeakPathHandle::clone")
    +318                .increment_path_refs(self.id);
    +319        }
    +320        Self {
    +321            id: self.id,
    +322            conn: self.conn.clone(),
    +323        }
    +324    }
    +325}
    +326
    +327impl PartialEq for WeakPathHandle {
    +328    fn eq(&self, other: &Self) -> bool {
    +329        self.id == other.id && self.conn.is_same_connection(&other.conn)
    +330    }
    +331}
    +332
    +333impl Eq for WeakPathHandle {}
     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    }
    -348}
    +335impl Drop for WeakPathHandle {
    +336    fn drop(&mut self) {
    +337        if let Some(conn) = self.conn.upgrade_to_ref() {
    +338            conn.lock_without_waking("WeakPathHandle::drop")
    +339                .decrement_path_refs(self.id);
    +340        }
    +341    }
    +342}
    +343
    +344impl WeakPathHandle {
    +345    /// Returns the [`PathId`] of this path.
    +346    pub fn id(&self) -> PathId {
    +347        self.id
    +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>,
    +350    /// Upgrades to a [`Path`].
    +351    ///
    +352    /// Returns `None` if the connection was dropped.
    +353    pub fn upgrade(&self) -> Option<Path> {
    +354        let conn = self.conn.upgrade_to_ref()?;
    +355        Some(Path::new_unchecked(conn, self.id))
    +356    }
     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
    +359/// Stream produced by [`Path::observed_external_addr`]
    +360///
    +361/// This will always return the external address most recently reported by the remote over this
    +362/// path. If the extension is not negotiated, this stream will never return.
    +363// TODO(@divma): provide a way to check if the extension is negotiated.
    +364pub struct AddressDiscovery {
    +365    watcher: WatchStream<SocketAddr>,
    +366}
    +367
    +368impl AddressDiscovery {
    +369    pub(super) fn new(
    +370        path_id: PathId,
    +371        mut path_events: tokio::sync::broadcast::Receiver<PathEvent>,
    +372        initial_value: Option<SocketAddr>,
    +373        runtime: Arc<dyn Runtime>,
    +374    ) -> Self {
    +375        let (tx, rx) = watch::channel(initial_value.unwrap_or_else(||
    +376                // if the dummy value is used, it will be ignored
    +377                SocketAddr::new([0, 0, 0, 0].into(), 0)));
    +378        let filter = async move {
    +379            loop {
    +380                match path_events.recv().await {
    +381                    Ok(PathEvent::ObservedAddr { id, addr: observed }) if id == path_id => {
    +382                        tx.send_if_modified(|addr| {
    +383                            let old = std::mem::replace(addr, observed);
    +384                            old != *addr
    +385                        });
    +386                    }
    +387                    Ok(PathEvent::Discarded { id, .. }) if id == path_id => {
    +388                        // If the path is closed, terminate the stream
     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 +391 Ok(_) => { +392 // ignore any other event +393 } +394 Err(_) => { +395 // A lagged error should never happen since this (detached) task is +396 // constantly reading from the channel. Therefore, if an error does happen, +397 // the stream can terminate +398 break; +399 } +400 } +401 } +402 }; +403 +404 let watcher = if initial_value.is_some() { +405 WatchStream::new(rx) +406 } else { +407 WatchStream::from_changes(rx) +408 }; +409 +410 runtime.spawn(Box::pin(filter)); +411 // TODO(@divma): check if there's a way to ensure the future ends. AbortHandle is not an +412 // option +413 Self { watcher } +414 } +415} +416 +417impl Stream for AddressDiscovery { +418 type Item = SocketAddr; +419 +420 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> { +421 Pin::new(&mut self.watcher).poll_next(cx) +422 } +423}

    \ No newline at end of file diff --git a/pr/617/docs/src/noq_proto/config/mod.rs.html b/pr/617/docs/src/noq_proto/config/mod.rs.html index c734dbc04..48ebae7a0 100644 --- a/pr/617/docs/src/noq_proto/config/mod.rs.html +++ b/pr/617/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( -84 &mut self, -85 factory: Arc<dyn Fn() -> Box<dyn ConnectionIdGenerator> + Send + Sync>, -86 ) -> &mut Self { -87 self.connection_id_generator_factory = 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/617/docs/src/noq_proto/config/transport.rs.html b/pr/617/docs/src/noq_proto/config/transport.rs.html index 8a0111deb..2f2e14d12 100644 --- a/pr/617/docs/src/noq_proto/config/transport.rs.html +++ b/pr/617/docs/src/noq_proto/config/transport.rs.html @@ -14,914 +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>, -63 pub(crate) max_outgoing_bytes_per_second: Option<u64>, -64 -65 pub(crate) persistent_congestion_threshold: u32, -66 pub(crate) keep_alive_interval: Option<Duration>, -67 pub(crate) crypto_buffer_size: usize, -68 pub(crate) allow_spin: bool, -69 pub(crate) datagram_receive_buffer_size: Option<usize>, -70 pub(crate) datagram_send_buffer_size: usize, -71 #[cfg(test)] -72 pub(crate) deterministic_packet_numbers: bool, -73 -74 pub(crate) congestion_controller_factory: Arc<dyn congestion::ControllerFactory + Send + Sync>, -75 -76 pub(crate) enable_segmentation_offload: bool, -77 -78 pub(crate) address_discovery_role: address_discovery::Role, -79 -80 pub(crate) max_concurrent_multipath_paths: Option<NonZeroU32>, -81 -82 pub(crate) default_path_max_idle_timeout: Option<Duration>, -83 pub(crate) default_path_keep_alive_interval: Option<Duration>, -84 -85 pub(crate) max_remote_nat_traversal_addresses: Option<NonZeroU8>, +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) 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 +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 #[cfg(feature = "qlog")] -88 pub(crate) qlog_factory: Option<Arc<dyn QlogFactory>>, -89} -90 -91impl TransportConfig { -92 /// Maximum number of incoming bidirectional streams that may be open concurrently -93 /// -94 /// Must be nonzero for the peer to open any bidirectional streams. -95 /// -96 /// Worst-case memory use is directly proportional to `max_concurrent_bidi_streams * -97 /// stream_receive_window`, with an upper bound proportional to `receive_window`. -98 pub fn max_concurrent_bidi_streams(&mut self, value: VarInt) -> &mut Self { -99 self.max_concurrent_bidi_streams = value; -100 self -101 } -102 -103 /// Variant of `max_concurrent_bidi_streams` affecting unidirectional streams -104 pub fn max_concurrent_uni_streams(&mut self, value: VarInt) -> &mut Self { -105 self.max_concurrent_uni_streams = value; -106 self -107 } -108 -109 /// Maximum duration of inactivity to accept before timing out the connection. -110 /// -111 /// The true idle timeout is the minimum of this and the peer's own max idle timeout. `None` -112 /// represents an infinite timeout. Defaults to 30 seconds. -113 /// -114 /// **WARNING**: If a peer or its network path malfunctions or acts maliciously, an infinite -115 /// idle timeout can result in permanently hung futures! -116 /// -117 /// ``` -118 /// # use std::{convert::TryInto, time::Duration}; -119 /// # use noq_proto::{TransportConfig, VarInt, VarIntBoundsExceeded}; -120 /// # fn main() -> Result<(), VarIntBoundsExceeded> { -121 /// let mut config = TransportConfig::default(); +87 /// Variant of `max_concurrent_bidi_streams` affecting unidirectional streams +88 pub fn max_concurrent_uni_streams(&mut self, value: VarInt) -> &mut Self { +89 self.max_concurrent_uni_streams = value; +90 self +91 } +92 +93 /// Maximum duration of inactivity to accept before timing out the connection. +94 /// +95 /// The true idle timeout is the minimum of this and the peer's own max idle timeout. `None` +96 /// represents an infinite timeout. Defaults to 30 seconds. +97 /// +98 /// **WARNING**: If a peer or its network path malfunctions or acts maliciously, an infinite +99 /// idle timeout can result in permanently hung futures! +100 /// +101 /// ``` +102 /// # use std::{convert::TryInto, time::Duration}; +103 /// # use noq_proto::{TransportConfig, VarInt, VarIntBoundsExceeded}; +104 /// # fn main() -> Result<(), VarIntBoundsExceeded> { +105 /// let mut config = TransportConfig::default(); +106 /// +107 /// // Set the idle timeout as `VarInt`-encoded milliseconds +108 /// config.max_idle_timeout(Some(VarInt::from_u32(10_000).into())); +109 /// +110 /// // Set the idle timeout as a `Duration` +111 /// config.max_idle_timeout(Some(Duration::from_secs(10).try_into()?)); +112 /// # Ok(()) +113 /// # } +114 /// ``` +115 pub fn max_idle_timeout(&mut self, value: Option<IdleTimeout>) -> &mut Self { +116 self.max_idle_timeout = value.map(|t| t.0); +117 self +118 } +119 +120 /// Maximum number of bytes the peer may transmit without acknowledgement on any one stream +121 /// before becoming blocked. 122 /// -123 /// // Set the idle timeout as `VarInt`-encoded milliseconds -124 /// config.max_idle_timeout(Some(VarInt::from_u32(10_000).into())); -125 /// -126 /// // Set the idle timeout as a `Duration` -127 /// config.max_idle_timeout(Some(Duration::from_secs(10).try_into()?)); -128 /// # Ok(()) -129 /// # } -130 /// ``` -131 pub fn max_idle_timeout(&mut self, value: Option<IdleTimeout>) -> &mut Self { -132 self.max_idle_timeout = value.map(|t| t.0); -133 self -134 } -135 -136 /// Maximum number of bytes the peer may transmit without acknowledgement on any one stream -137 /// before becoming blocked. -138 /// -139 /// This should be set to at least the expected connection latency multiplied by the maximum -140 /// desired throughput. Setting this smaller than `receive_window` helps ensure that a single -141 /// stream doesn't monopolize receive buffers, which may otherwise occur if the application -142 /// chooses not to read from a large stream for a time while still requiring data on other -143 /// streams. -144 pub fn stream_receive_window(&mut self, value: VarInt) -> &mut Self { -145 self.stream_receive_window = value; -146 self -147 } -148 -149 /// Maximum number of bytes the peer may transmit across all streams of a connection before -150 /// becoming blocked. -151 /// -152 /// This should be set to at least the expected connection latency multiplied by the maximum -153 /// desired throughput. Larger values can be useful to allow maximum throughput within a -154 /// stream while another is blocked. -155 pub fn receive_window(&mut self, value: VarInt) -> &mut Self { -156 self.receive_window = value; -157 self -158 } -159 -160 /// Maximum number of bytes to transmit to a peer without acknowledgment -161 /// -162 /// Provides an upper bound on memory when communicating with peers that issue large amounts of -163 /// flow control credit. Endpoints that wish to handle large numbers of connections robustly -164 /// should take care to set this low enough to guarantee memory exhaustion does not occur if -165 /// every connection uses the entire window. -166 pub fn send_window(&mut self, value: u64) -> &mut Self { -167 self.send_window = value; -168 self -169 } -170 -171 /// Whether to implement fair queuing for send streams having the same priority. -172 /// -173 /// When enabled, connections schedule data from outgoing streams having the same priority in a -174 /// round-robin fashion. When disabled, streams are scheduled in the order they are written to. -175 /// -176 /// Note that this only affects streams with the same priority. Higher priority streams always -177 /// take precedence over lower priority streams. -178 /// -179 /// Disabling fairness can reduce fragmentation and protocol overhead for workloads that use -180 /// many small streams. -181 pub fn send_fairness(&mut self, value: bool) -> &mut Self { -182 self.send_fairness = value; -183 self -184 } -185 -186 /// Maximum reordering in packet number space before FACK style loss detection considers a -187 /// packet lost. Should not be less than 3, per RFC5681. -188 pub fn packet_threshold(&mut self, value: u32) -> &mut Self { -189 self.packet_threshold = value; -190 self -191 } -192 -193 /// Maximum reordering in time space before time based loss detection considers a packet lost, -194 /// as a factor of RTT -195 pub fn time_threshold(&mut self, value: f32) -> &mut Self { -196 self.time_threshold = value; -197 self -198 } -199 -200 /// The RTT used before an RTT sample is taken -201 pub fn initial_rtt(&mut self, value: Duration) -> &mut Self { -202 self.initial_rtt = value; -203 self -204 } -205 -206 /// The initial value to be used as the maximum UDP payload size before running MTU discovery -207 /// (see [`TransportConfig::mtu_discovery_config`]). +123 /// This should be set to at least the expected connection latency multiplied by the maximum +124 /// desired throughput. Setting this smaller than `receive_window` helps ensure that a single +125 /// stream doesn't monopolize receive buffers, which may otherwise occur if the application +126 /// chooses not to read from a large stream for a time while still requiring data on other +127 /// streams. +128 pub fn stream_receive_window(&mut self, value: VarInt) -> &mut Self { +129 self.stream_receive_window = value; +130 self +131 } +132 +133 /// Maximum number of bytes the peer may transmit across all streams of a connection before +134 /// becoming blocked. +135 /// +136 /// This should be set to at least the expected connection latency multiplied by the maximum +137 /// desired throughput. Larger values can be useful to allow maximum throughput within a +138 /// stream while another is blocked. +139 pub fn receive_window(&mut self, value: VarInt) -> &mut Self { +140 self.receive_window = value; +141 self +142 } +143 +144 /// Maximum number of bytes to transmit to a peer without acknowledgment +145 /// +146 /// Provides an upper bound on memory when communicating with peers that issue large amounts of +147 /// flow control credit. Endpoints that wish to handle large numbers of connections robustly +148 /// should take care to set this low enough to guarantee memory exhaustion does not occur if +149 /// every connection uses the entire window. +150 pub fn send_window(&mut self, value: u64) -> &mut Self { +151 self.send_window = value; +152 self +153 } +154 +155 /// Whether to implement fair queuing for send streams having the same priority. +156 /// +157 /// When enabled, connections schedule data from outgoing streams having the same priority in a +158 /// round-robin fashion. When disabled, streams are scheduled in the order they are written to. +159 /// +160 /// Note that this only affects streams with the same priority. Higher priority streams always +161 /// take precedence over lower priority streams. +162 /// +163 /// Disabling fairness can reduce fragmentation and protocol overhead for workloads that use +164 /// many small streams. +165 pub fn send_fairness(&mut self, value: bool) -> &mut Self { +166 self.send_fairness = value; +167 self +168 } +169 +170 /// Maximum reordering in packet number space before FACK style loss detection considers a +171 /// packet lost. Should not be less than 3, per RFC5681. +172 pub fn packet_threshold(&mut self, value: u32) -> &mut Self { +173 self.packet_threshold = value; +174 self +175 } +176 +177 /// Maximum reordering in time space before time based loss detection considers a packet lost, +178 /// as a factor of RTT +179 pub fn time_threshold(&mut self, value: f32) -> &mut Self { +180 self.time_threshold = value; +181 self +182 } +183 +184 /// The RTT used before an RTT sample is taken +185 pub fn initial_rtt(&mut self, value: Duration) -> &mut Self { +186 self.initial_rtt = value; +187 self +188 } +189 +190 /// The initial value to be used as the maximum UDP payload size before running MTU discovery +191 /// (see [`TransportConfig::mtu_discovery_config`]). +192 /// +193 /// Must be at least 1200, which is the default, and known to be safe for typical internet +194 /// applications. Larger values are more efficient, but increase the risk of packet loss due to +195 /// exceeding the network path's IP MTU. If the provided value is higher than what the network +196 /// path actually supports, packet loss will eventually trigger black hole detection and bring +197 /// it down to [`TransportConfig::min_mtu`]. +198 pub fn initial_mtu(&mut self, value: u16) -> &mut Self { +199 self.initial_mtu = value.max(INITIAL_MTU); +200 self +201 } +202 +203 pub(crate) fn get_initial_mtu(&self) -> u16 { +204 self.initial_mtu.max(self.min_mtu) +205 } +206 +207 /// The maximum UDP payload size guaranteed to be supported by the network. 208 /// -209 /// Must be at least 1200, which is the default, and known to be safe for typical internet -210 /// applications. Larger values are more efficient, but increase the risk of packet loss due to -211 /// exceeding the network path's IP MTU. If the provided value is higher than what the network -212 /// path actually supports, packet loss will eventually trigger black hole detection and bring -213 /// it down to [`TransportConfig::min_mtu`]. -214 pub fn initial_mtu(&mut self, value: u16) -> &mut Self { -215 self.initial_mtu = value.max(INITIAL_MTU); -216 self -217 } -218 -219 pub(crate) fn get_initial_mtu(&self) -> u16 { -220 self.initial_mtu.max(self.min_mtu) -221 } -222 -223 /// The maximum UDP payload size guaranteed to be supported by the network. -224 /// -225 /// Must be at least 1200, which is the default, and lower than or equal to -226 /// [`TransportConfig::initial_mtu`]. -227 /// -228 /// Real-world MTUs can vary according to ISP, VPN, and properties of intermediate network links -229 /// outside of either endpoint's control. Extreme care should be used when raising this value -230 /// outside of private networks where these factors are fully controlled. If the provided value -231 /// is higher than what the network path actually supports, the result will be unpredictable and -232 /// catastrophic packet loss, without a possibility of repair. Prefer -233 /// [`TransportConfig::initial_mtu`] together with -234 /// [`TransportConfig::mtu_discovery_config`] to set a maximum UDP payload size that robustly -235 /// adapts to the network. -236 pub fn min_mtu(&mut self, value: u16) -> &mut Self { -237 self.min_mtu = value.max(INITIAL_MTU); -238 self -239 } -240 -241 /// Specifies the MTU discovery config (see [`MtuDiscoveryConfig`] for details). -242 /// -243 /// Enabled by default. -244 pub fn mtu_discovery_config(&mut self, value: Option<MtuDiscoveryConfig>) -> &mut Self { -245 self.mtu_discovery_config = value; -246 self -247 } -248 -249 /// Pad UDP datagrams carrying application data to current maximum UDP payload size -250 /// -251 /// Disabled by default. UDP datagrams containing loss probes are exempt from padding. -252 /// -253 /// Enabling this helps mitigate traffic analysis by network observers, but it increases -254 /// bandwidth usage. Without this mitigation precise plain text size of application datagrams as -255 /// well as the total size of stream write bursts can be inferred by observers under certain -256 /// conditions. This analysis requires either an uncongested connection or application datagrams -257 /// too large to be coalesced. -258 pub fn pad_to_mtu(&mut self, value: bool) -> &mut Self { -259 self.pad_to_mtu = value; -260 self -261 } -262 -263 /// Specifies the ACK frequency config (see [`AckFrequencyConfig`] for details) -264 /// -265 /// The provided configuration will be ignored if the peer does not support the acknowledgement -266 /// frequency QUIC extension. -267 /// -268 /// Defaults to `None`, which disables controlling the peer's acknowledgement frequency. Even -269 /// if set to `None`, the local side still supports the acknowledgement frequency QUIC -270 /// extension and may use it in other ways. -271 pub fn ack_frequency_config(&mut self, value: Option<AckFrequencyConfig>) -> &mut Self { -272 self.ack_frequency_config = value; -273 self -274 } -275 -276 /// Configures an outbound rate limit (in bytes per second) for each connection. +209 /// Must be at least 1200, which is the default, and lower than or equal to +210 /// [`TransportConfig::initial_mtu`]. +211 /// +212 /// Real-world MTUs can vary according to ISP, VPN, and properties of intermediate network links +213 /// outside of either endpoint's control. Extreme care should be used when raising this value +214 /// outside of private networks where these factors are fully controlled. If the provided value +215 /// is higher than what the network path actually supports, the result will be unpredictable and +216 /// catastrophic packet loss, without a possibility of repair. Prefer +217 /// [`TransportConfig::initial_mtu`] together with +218 /// [`TransportConfig::mtu_discovery_config`] to set a maximum UDP payload size that robustly +219 /// adapts to the network. +220 pub fn min_mtu(&mut self, value: u16) -> &mut Self { +221 self.min_mtu = value.max(INITIAL_MTU); +222 self +223 } +224 +225 /// Specifies the MTU discovery config (see [`MtuDiscoveryConfig`] for details). +226 /// +227 /// Enabled by default. +228 pub fn mtu_discovery_config(&mut self, value: Option<MtuDiscoveryConfig>) -> &mut Self { +229 self.mtu_discovery_config = value; +230 self +231 } +232 +233 /// Pad UDP datagrams carrying application data to current maximum UDP payload size +234 /// +235 /// Disabled by default. UDP datagrams containing loss probes are exempt from padding. +236 /// +237 /// Enabling this helps mitigate traffic analysis by network observers, but it increases +238 /// bandwidth usage. Without this mitigation precise plain text size of application datagrams as +239 /// well as the total size of stream write bursts can be inferred by observers under certain +240 /// conditions. This analysis requires either an uncongested connection or application datagrams +241 /// too large to be coalesced. +242 pub fn pad_to_mtu(&mut self, value: bool) -> &mut Self { +243 self.pad_to_mtu = value; +244 self +245 } +246 +247 /// Specifies the ACK frequency config (see [`AckFrequencyConfig`] for details) +248 /// +249 /// The provided configuration will be ignored if the peer does not support the acknowledgement +250 /// frequency QUIC extension. +251 /// +252 /// Defaults to `None`, which disables controlling the peer's acknowledgement frequency. Even +253 /// if set to `None`, the local side still supports the acknowledgement frequency QUIC +254 /// extension and may use it in other ways. +255 pub fn ack_frequency_config(&mut self, value: Option<AckFrequencyConfig>) -> &mut Self { +256 self.ack_frequency_config = value; +257 self +258 } +259 +260 /// Configures an outbound rate limit (in bytes per second) for each connection. +261 /// +262 /// Defaults to `None`, which disables rate limiting. +263 pub fn max_outgoing_bytes_per_second(&mut self, value: Option<u64>) -> &mut Self { +264 self.max_outgoing_bytes_per_second = value; +265 self +266 } +267 +268 /// Number of consecutive PTOs after which network is considered to be experiencing persistent congestion. +269 pub fn persistent_congestion_threshold(&mut self, value: u32) -> &mut Self { +270 self.persistent_congestion_threshold = value; +271 self +272 } +273 +274 /// Period of inactivity before sending a keep-alive packet +275 /// +276 /// Keep-alive packets prevent an inactive but otherwise healthy connection from timing out. 277 /// -278 /// Defaults to `None`, which disables rate limiting. -279 pub fn max_outgoing_bytes_per_second(&mut self, value: Option<u64>) -> &mut Self { -280 self.max_outgoing_bytes_per_second = value; -281 self -282 } -283 -284 /// Number of consecutive PTOs after which network is considered to be experiencing persistent congestion. -285 pub fn persistent_congestion_threshold(&mut self, value: u32) -> &mut Self { -286 self.persistent_congestion_threshold = value; -287 self -288 } -289 -290 /// Period of inactivity before sending a keep-alive packet -291 /// -292 /// Keep-alive packets prevent an inactive but otherwise healthy connection from timing out. +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 /// `None` to disable, which is the default. Only one side of any given connection needs keep-alive -295 /// enabled for the connection to be preserved. Must be set lower than the idle_timeout of both -296 /// peers to be effective. -297 pub fn keep_alive_interval(&mut self, value: Option<Duration>) -> &mut Self { -298 self.keep_alive_interval = value; -299 self -300 } -301 -302 /// Maximum quantity of out-of-order crypto layer data to buffer -303 pub fn crypto_buffer_size(&mut self, value: usize) -> &mut Self { -304 self.crypto_buffer_size = value; -305 self -306 } -307 -308 /// Whether the implementation is permitted to set the spin bit on this connection -309 /// -310 /// This allows passive observers to easily judge the round trip time of a connection, which can -311 /// be useful for network administration but sacrifices a small amount of privacy. -312 pub fn allow_spin(&mut self, value: bool) -> &mut Self { -313 self.allow_spin = value; -314 self -315 } -316 -317 /// Maximum number of incoming application datagram bytes to buffer, or None to disable -318 /// incoming datagrams -319 /// -320 /// The peer is forbidden to send single datagrams larger than this size. If the aggregate size -321 /// of all datagrams that have been received from the peer but not consumed by the application -322 /// exceeds this value, old datagrams are dropped until it is no longer exceeded. -323 pub fn datagram_receive_buffer_size(&mut self, value: Option<usize>) -> &mut Self { -324 self.datagram_receive_buffer_size = value; -325 self -326 } -327 -328 /// Maximum number of outgoing application datagram bytes to buffer -329 /// -330 /// While datagrams are sent ASAP, it is possible for an application to generate data faster -331 /// than the link, or even the underlying hardware, can transmit them. This limits the amount of -332 /// memory that may be consumed in that case. When the send buffer is full and a new datagram is -333 /// sent, older datagrams are dropped until sufficient space is available. -334 pub fn datagram_send_buffer_size(&mut self, value: usize) -> &mut Self { -335 self.datagram_send_buffer_size = value; -336 self -337 } -338 -339 /// Whether to force every packet number to be used -340 /// -341 /// By default, packet numbers are occasionally skipped to ensure peers aren't ACKing packets -342 /// before they see them. -343 #[cfg(test)] -344 pub(crate) fn deterministic_packet_numbers(&mut self, enabled: bool) -> &mut Self { -345 self.deterministic_packet_numbers = enabled; -346 self -347 } -348 -349 /// How to construct new `congestion::Controller`s -350 /// -351 /// Typically the refcounted configuration of a `congestion::Controller`, -352 /// e.g. a `congestion::NewRenoConfig`. -353 /// -354 /// # Example -355 /// ``` -356 /// # use noq_proto::*; use std::sync::Arc; -357 /// let mut config = TransportConfig::default(); -358 /// config.congestion_controller_factory(Arc::new(congestion::NewRenoConfig::default())); -359 /// ``` -360 pub fn congestion_controller_factory( -361 &mut self, -362 factory: Arc<dyn congestion::ControllerFactory + Send + Sync + 'static>, -363 ) -> &mut Self { -364 self.congestion_controller_factory = factory; -365 self -366 } -367 -368 /// Whether to use "Generic Segmentation Offload" to accelerate transmits, when supported by the -369 /// environment -370 /// -371 /// Defaults to `true`. -372 /// -373 /// GSO dramatically reduces CPU consumption when sending large numbers of packets with the same -374 /// headers, such as when transmitting bulk data on a connection. However, it is not supported -375 /// by all network interface drivers or packet inspection tools. `noq-udp` will attempt to -376 /// disable GSO automatically when unavailable, but this can lead to spurious packet loss at -377 /// startup, temporarily degrading performance. -378 pub fn enable_segmentation_offload(&mut self, enabled: bool) -> &mut Self { -379 self.enable_segmentation_offload = enabled; -380 self -381 } -382 -383 /// Whether to send observed address reports to peers. -384 /// -385 /// This will aid peers in inferring their reachable address, which in most NATd networks -386 /// will not be easily available to them. -387 pub fn send_observed_address_reports(&mut self, enabled: bool) -> &mut Self { -388 self.address_discovery_role.send_reports_to_peers(enabled); -389 self -390 } -391 -392 /// Whether to receive observed address reports from other peers. -393 /// -394 /// Peers with the address discovery extension enabled that are willing to provide observed -395 /// address reports will do so if this transport parameter is set. In general, observed address -396 /// reports cannot be trusted. This, however, can aid the current endpoint in inferring its -397 /// reachable address, which in most NATd networks will not be easily available. -398 pub fn receive_observed_address_reports(&mut self, enabled: bool) -> &mut Self { -399 self.address_discovery_role -400 .receive_reports_from_peers(enabled); -401 self -402 } -403 -404 /// Enables the Multipath Extension for QUIC. -405 /// -406 /// Setting this to any nonzero value will enable the Multipath Extension for QUIC, -407 /// <https://datatracker.ietf.org/doc/draft-ietf-quic-multipath/>. -408 /// -409 /// The value provided specifies the number maximum number of paths this endpoint may open -410 /// concurrently when multipath is negotiated. For any path to be opened, the remote must -411 /// enable multipath as well. -412 pub fn max_concurrent_multipath_paths(&mut self, max_concurrent: u32) -> &mut Self { -413 self.max_concurrent_multipath_paths = NonZeroU32::new(max_concurrent); -414 self -415 } -416 -417 /// Sets a default per-path maximum idle timeout -418 /// -419 /// If the path is idle for this long the path will be abandoned. Bear in mind this will -420 /// interact with the [`TransportConfig::max_idle_timeout`], if the last path is -421 /// abandoned the entire connection will be closed. +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 /// [`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_max_idle_timeout`] for -424 /// existing paths. +423 /// You can also change this using [`Connection::set_path_keep_alive_interval`] for +424 /// existing path. 425 /// -426 /// [`Connection::set_path_max_idle_timeout`]: crate::Connection::set_path_max_idle_timeout -427 pub fn default_path_max_idle_timeout(&mut self, timeout: Option<Duration>) -> &mut Self { -428 self.default_path_max_idle_timeout = timeout; +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 /// Sets a default per-path keep alive interval +432 /// Get the initial max [`crate::PathId`] this endpoint allows. 433 /// -434 /// Note that this does not interact with the connection-wide -435 /// [`TransportConfig::keep_alive_interval`]. This setting will keep this path active, -436 /// [`TransportConfig::keep_alive_interval`] will keep the connection active, with no -437 /// control over which path is used for this. -438 /// -439 /// You can also change this using [`Connection::set_path_keep_alive_interval`] for -440 /// existing path. -441 /// -442 /// [`Connection::set_path_keep_alive_interval`]: crate::Connection::set_path_keep_alive_interval -443 pub fn default_path_keep_alive_interval(&mut self, interval: Option<Duration>) -> &mut Self { -444 self.default_path_keep_alive_interval = interval; -445 self -446 } -447 -448 /// Get the initial max [`crate::PathId`] this endpoint allows. +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 /// Returns `None` if multipath is disabled. -451 pub(crate) fn get_initial_max_path_id(&self) -> Option<crate::PathId> { -452 self.max_concurrent_multipath_paths -453 // a max_concurrent_multipath_paths value of 1 only allows the first path, which -454 // has id 0 -455 .map(|nonzero_concurrent| nonzero_concurrent.get() - 1) -456 .map(Into::into) -457 } -458 -459 /// Sets the maximum number of nat traversal addresses this endpoint allows the remote to -460 /// advertise +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 /// Setting this to any nonzero value will enable n0's nat traversal protocol, loosely based in -463 /// the Nat Traversal Extension for QUIC, see -464 /// <https://www.ietf.org/archive/id/draft-seemann-quic-nat-traversal-02.html> -465 /// -466 /// This implementation expects the multipath extension to be enabled as well. if not yet -467 /// enabled via [`Self::max_concurrent_multipath_paths`], a default value of -468 /// [`DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED`] will be used. -469 pub fn set_max_remote_nat_traversal_addresses(&mut self, max_addresses: u8) -> &mut Self { -470 self.max_remote_nat_traversal_addresses = NonZeroU8::new(max_addresses); -471 if max_addresses != 0 && self.max_concurrent_multipath_paths.is_none() { -472 self.max_concurrent_multipath_paths( -473 DEFAULT_CONCURRENT_MULTIPATH_PATHS_WHEN_ENABLED_.get(), -474 ); -475 } -476 self -477 } -478 -479 /// Configures qlog capturing by setting a [`QlogFactory`]. -480 /// -481 /// This assigns a [`QlogFactory`] that produces qlog capture configurations for -482 /// individual connections. -483 #[cfg(feature = "qlog")] -484 pub fn qlog_factory(&mut self, factory: Arc<dyn QlogFactory>) -> &mut Self { -485 self.qlog_factory = Some(factory); -486 self -487 } -488 -489 /// Configures qlog capturing through the `QLOGDIR` environment variable. -490 /// -491 /// This uses [`QlogFileFactory::from_env`] to create a factory to write qlog traces -492 /// into the directory set through the `QLOGDIR` environment variable. -493 /// -494 /// If `QLOGDIR` is not set, no traces will be written. If `QLOGDIR` is set to a path -495 /// that does not exist, it will be created. -496 /// -497 /// The files will be prefixed with `prefix`. -498 #[cfg(feature = "qlog")] -499 pub fn qlog_from_env(&mut self, prefix: &str) -> &mut Self { -500 self.qlog_factory(Arc::new(QlogFileFactory::from_env().with_prefix(prefix))) -501 } -502 -503 /// Configures qlog capturing into a directory. -504 /// -505 /// This uses [`QlogFileFactory`] to create a factory to write qlog traces into -506 /// the specified directory. The files will be prefixed with `prefix`. -507 #[cfg(feature = "qlog")] -508 pub fn qlog_from_path(&mut self, path: impl AsRef<Path>, prefix: &str) -> &mut Self { -509 self.qlog_factory(Arc::new( -510 QlogFileFactory::new(path.as_ref().to_owned()).with_prefix(prefix), -511 )) -512 } -513 -514 pub(crate) fn create_qlog_sink( -515 &self, -516 side: Side, -517 remote: SocketAddr, -518 initial_dst_cid: ConnectionId, -519 now: Instant, -520 ) -> QlogSink { -521 #[cfg(not(feature = "qlog"))] -522 let sink = { -523 let _ = (side, remote, initial_dst_cid, now); -524 QlogSink::default() -525 }; -526 -527 #[cfg(feature = "qlog")] -528 let sink = { -529 if let Some(config) = self -530 .qlog_factory -531 .as_ref() -532 .and_then(|factory| factory.for_connection(side, remote, initial_dst_cid, now)) -533 { -534 QlogSink::new(config, initial_dst_cid, side, now) -535 } else { -536 QlogSink::default() -537 } -538 }; -539 -540 sink -541 } -542} -543 -544impl Default for TransportConfig { -545 fn default() -> Self { -546 const EXPECTED_RTT: u32 = 100; // ms -547 const MAX_STREAM_BANDWIDTH: u32 = 12500 * 1000; // bytes/s -548 // Window size needed to avoid pipeline -549 // stalls -550 const STREAM_RWND: u32 = MAX_STREAM_BANDWIDTH / 1000 * EXPECTED_RTT; -551 -552 Self { -553 max_concurrent_bidi_streams: 100u32.into(), -554 max_concurrent_uni_streams: 100u32.into(), -555 // 30 second default recommended by RFC 9308 § 3.2 -556 max_idle_timeout: Some(VarInt(30_000)), -557 stream_receive_window: STREAM_RWND.into(), -558 receive_window: VarInt::MAX, -559 send_window: (8 * STREAM_RWND).into(), -560 send_fairness: true, +462 /// This assigns a [`QlogFactory`] that produces qlog capture configurations for +463 /// individual connections. +464 #[cfg(feature = "qlog")] +465 pub fn qlog_factory(&mut self, factory: Arc<dyn QlogFactory>) -> &mut Self { +466 self.qlog_factory = Some(factory); +467 self +468 } +469 +470 /// Configures qlog capturing through the `QLOGDIR` environment variable. +471 /// +472 /// This uses [`QlogFileFactory::from_env`] to create a factory to write qlog traces +473 /// into the directory set through the `QLOGDIR` environment variable. +474 /// +475 /// If `QLOGDIR` is not set, no traces will be written. If `QLOGDIR` is set to a path +476 /// that does not exist, it will be created. +477 /// +478 /// The files will be prefixed with `prefix`. +479 #[cfg(feature = "qlog")] +480 pub fn qlog_from_env(&mut self, prefix: &str) -> &mut Self { +481 self.qlog_factory(Arc::new(QlogFileFactory::from_env().with_prefix(prefix))) +482 } +483 +484 /// Configures qlog capturing into a directory. +485 /// +486 /// This uses [`QlogFileFactory`] to create a factory to write qlog traces into +487 /// the specified directory. The files will be prefixed with `prefix`. +488 #[cfg(feature = "qlog")] +489 pub fn qlog_from_path(&mut self, path: impl AsRef<Path>, prefix: &str) -> &mut Self { +490 self.qlog_factory(Arc::new( +491 QlogFileFactory::new(path.as_ref().to_owned()).with_prefix(prefix), +492 )) +493 } +494 +495 pub(crate) fn create_qlog_sink( +496 &self, +497 side: Side, +498 remote: SocketAddr, +499 initial_dst_cid: ConnectionId, +500 now: Instant, +501 ) -> QlogSink { +502 #[cfg(not(feature = "qlog"))] +503 let sink = { +504 let _ = (side, remote, initial_dst_cid, now); +505 QlogSink::default() +506 }; +507 +508 #[cfg(feature = "qlog")] +509 let sink = { +510 if let Some(config) = self +511 .qlog_factory +512 .as_ref() +513 .and_then(|factory| factory.for_connection(side, remote, initial_dst_cid, now)) +514 { +515 QlogSink::new(config, initial_dst_cid, side, now) +516 } else { +517 QlogSink::default() +518 } +519 }; +520 +521 sink +522 } +523} +524 +525impl Default for TransportConfig { +526 fn default() -> Self { +527 const EXPECTED_RTT: u32 = 100; // ms +528 const MAX_STREAM_BANDWIDTH: u32 = 12500 * 1000; // bytes/s +529 // Window size needed to avoid pipeline +530 // stalls +531 const STREAM_RWND: u32 = MAX_STREAM_BANDWIDTH / 1000 * EXPECTED_RTT; +532 +533 Self { +534 max_concurrent_bidi_streams: 100u32.into(), +535 max_concurrent_uni_streams: 100u32.into(), +536 // 30 second default recommended by RFC 9308 § 3.2 +537 max_idle_timeout: Some(VarInt(30_000)), +538 stream_receive_window: STREAM_RWND.into(), +539 receive_window: VarInt::MAX, +540 send_window: (8 * STREAM_RWND).into(), +541 send_fairness: true, +542 +543 packet_threshold: 3, +544 time_threshold: 9.0 / 8.0, +545 initial_rtt: Duration::from_millis(333), // per spec, intentionally distinct from EXPECTED_RTT +546 initial_mtu: INITIAL_MTU, +547 min_mtu: INITIAL_MTU, +548 mtu_discovery_config: Some(MtuDiscoveryConfig::default()), +549 pad_to_mtu: false, +550 ack_frequency_config: None, +551 max_outgoing_bytes_per_second: None, +552 +553 persistent_congestion_threshold: 3, +554 keep_alive_interval: None, +555 crypto_buffer_size: 16 * 1024, +556 allow_spin: true, +557 datagram_receive_buffer_size: Some(STREAM_RWND as usize), +558 datagram_send_buffer_size: 1024 * 1024, +559 #[cfg(test)] +560 deterministic_packet_numbers: false, 561 -562 packet_threshold: 3, -563 time_threshold: 9.0 / 8.0, -564 initial_rtt: Duration::from_millis(333), // per spec, intentionally distinct from EXPECTED_RTT -565 initial_mtu: INITIAL_MTU, -566 min_mtu: INITIAL_MTU, -567 mtu_discovery_config: Some(MtuDiscoveryConfig::default()), -568 pad_to_mtu: false, -569 ack_frequency_config: None, -570 max_outgoing_bytes_per_second: None, -571 -572 persistent_congestion_threshold: 3, -573 keep_alive_interval: None, -574 crypto_buffer_size: 16 * 1024, -575 allow_spin: true, -576 datagram_receive_buffer_size: Some(STREAM_RWND as usize), -577 datagram_send_buffer_size: 1024 * 1024, -578 #[cfg(test)] -579 deterministic_packet_numbers: false, -580 -581 congestion_controller_factory: Arc::new(congestion::CubicConfig::default()), -582 -583 enable_segmentation_offload: true, -584 -585 address_discovery_role: address_discovery::Role::default(), -586 -587 // disabled multipath by default -588 max_concurrent_multipath_paths: None, -589 default_path_max_idle_timeout: None, -590 default_path_keep_alive_interval: None, -591 -592 // nat traversal disabled by default -593 max_remote_nat_traversal_addresses: None, -594 -595 #[cfg(feature = "qlog")] -596 qlog_factory: None, -597 } -598 } -599} -600 -601impl fmt::Debug for TransportConfig { -602 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { -603 let Self { -604 max_concurrent_bidi_streams, -605 max_concurrent_uni_streams, -606 max_idle_timeout, -607 stream_receive_window, -608 receive_window, -609 send_window, -610 send_fairness, -611 packet_threshold, -612 time_threshold, -613 initial_rtt, -614 initial_mtu, -615 min_mtu, -616 mtu_discovery_config, -617 pad_to_mtu, -618 ack_frequency_config, -619 max_outgoing_bytes_per_second, -620 persistent_congestion_threshold, -621 keep_alive_interval, -622 crypto_buffer_size, -623 allow_spin, -624 datagram_receive_buffer_size, -625 datagram_send_buffer_size, -626 #[cfg(test)] -627 deterministic_packet_numbers: _, -628 congestion_controller_factory: _, -629 enable_segmentation_offload, -630 address_discovery_role, -631 max_concurrent_multipath_paths, -632 default_path_max_idle_timeout, -633 default_path_keep_alive_interval, -634 max_remote_nat_traversal_addresses, -635 #[cfg(feature = "qlog")] -636 qlog_factory, -637 } = self; -638 let mut s = fmt.debug_struct("TransportConfig"); -639 -640 s.field("max_concurrent_bidi_streams", max_concurrent_bidi_streams) -641 .field("max_concurrent_uni_streams", max_concurrent_uni_streams) -642 .field("max_idle_timeout", max_idle_timeout) -643 .field("stream_receive_window", stream_receive_window) -644 .field("receive_window", receive_window) -645 .field("send_window", send_window) -646 .field("send_fairness", send_fairness) -647 .field("packet_threshold", packet_threshold) -648 .field("time_threshold", time_threshold) -649 .field("initial_rtt", initial_rtt) -650 .field("initial_mtu", initial_mtu) -651 .field("min_mtu", min_mtu) -652 .field("mtu_discovery_config", mtu_discovery_config) -653 .field("pad_to_mtu", pad_to_mtu) -654 .field("ack_frequency_config", ack_frequency_config) -655 .field( -656 "max_outgoing_bytes_per_second", -657 max_outgoing_bytes_per_second, -658 ) -659 .field( -660 "persistent_congestion_threshold", -661 persistent_congestion_threshold, -662 ) -663 .field("keep_alive_interval", keep_alive_interval) -664 .field("crypto_buffer_size", crypto_buffer_size) -665 .field("allow_spin", allow_spin) -666 .field("datagram_receive_buffer_size", datagram_receive_buffer_size) -667 .field("datagram_send_buffer_size", datagram_send_buffer_size) -668 // congestion_controller_factory not debug -669 .field("enable_segmentation_offload", enable_segmentation_offload) -670 .field("address_discovery_role", address_discovery_role) -671 .field( -672 "max_concurrent_multipath_paths", -673 max_concurrent_multipath_paths, -674 ) -675 .field( -676 "default_path_max_idle_timeout", -677 default_path_max_idle_timeout, -678 ) -679 .field( -680 "default_path_keep_alive_interval", -681 default_path_keep_alive_interval, -682 ) -683 .field( -684 "max_remote_nat_traversal_addresses", -685 max_remote_nat_traversal_addresses, -686 ); -687 #[cfg(feature = "qlog")] -688 s.field("qlog_factory", &qlog_factory.is_some()); -689 -690 s.finish_non_exhaustive() -691 } -692} -693 -694/// Parameters for controlling the peer's acknowledgement frequency -695/// -696/// The parameters provided in this config will be sent to the peer at the beginning of the -697/// connection, so it can take them into account when sending acknowledgements (see each parameter's -698/// description for details on how it influences acknowledgement frequency). -699/// -700/// noq's implementation follows the fourth draft of the -701/// [QUIC Acknowledgement Frequency extension](https://datatracker.ietf.org/doc/html/draft-ietf-quic-ack-frequency-04). -702/// The defaults produce behavior slightly different than the behavior without this extension, -703/// because they change the way reordered packets are handled (see -704/// [`AckFrequencyConfig::reordering_threshold`] for details). -705#[derive(Clone, Debug)] -706pub struct AckFrequencyConfig { -707 pub(crate) ack_eliciting_threshold: VarInt, -708 pub(crate) max_ack_delay: Option<Duration>, -709 pub(crate) reordering_threshold: VarInt, -710} -711 -712impl AckFrequencyConfig { -713 /// The ack-eliciting threshold we will request the peer to use -714 /// -715 /// This threshold represents the number of ack-eliciting packets an endpoint may receive -716 /// without immediately sending an ACK. -717 /// -718 /// The remote peer should send at least one ACK frame when more than this number of -719 /// ack-eliciting packets have been received. A value of 0 results in a receiver immediately -720 /// acknowledging every ack-eliciting packet. -721 /// -722 /// Defaults to 1, which sends ACK frames for every other ack-eliciting packet. -723 pub fn ack_eliciting_threshold(&mut self, value: VarInt) -> &mut Self { -724 self.ack_eliciting_threshold = value; -725 self -726 } -727 -728 /// The `max_ack_delay` we will request the peer to use +562 congestion_controller_factory: Arc::new(congestion::CubicConfig::default()), +563 +564 enable_segmentation_offload: true, +565 +566 address_discovery_role: address_discovery::Role::default(), +567 +568 // disabled multipath by default +569 max_concurrent_multipath_paths: None, +570 default_path_max_idle_timeout: None, +571 default_path_keep_alive_interval: None, +572 +573 // nat traversal disabled by default +574 max_remote_nat_traversal_addresses: None, +575 +576 #[cfg(feature = "qlog")] +577 qlog_factory: None, +578 } +579 } +580} +581 +582impl fmt::Debug for TransportConfig { +583 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { +584 let Self { +585 max_concurrent_bidi_streams, +586 max_concurrent_uni_streams, +587 max_idle_timeout, +588 stream_receive_window, +589 receive_window, +590 send_window, +591 send_fairness, +592 packet_threshold, +593 time_threshold, +594 initial_rtt, +595 initial_mtu, +596 min_mtu, +597 mtu_discovery_config, +598 pad_to_mtu, +599 ack_frequency_config, +600 max_outgoing_bytes_per_second, +601 persistent_congestion_threshold, +602 keep_alive_interval, +603 crypto_buffer_size, +604 allow_spin, +605 datagram_receive_buffer_size, +606 datagram_send_buffer_size, +607 #[cfg(test)] +608 deterministic_packet_numbers: _, +609 congestion_controller_factory: _, +610 enable_segmentation_offload, +611 address_discovery_role, +612 max_concurrent_multipath_paths, +613 default_path_max_idle_timeout, +614 default_path_keep_alive_interval, +615 max_remote_nat_traversal_addresses, +616 #[cfg(feature = "qlog")] +617 qlog_factory, +618 } = self; +619 let mut s = fmt.debug_struct("TransportConfig"); +620 +621 s.field("max_concurrent_bidi_streams", max_concurrent_bidi_streams) +622 .field("max_concurrent_uni_streams", max_concurrent_uni_streams) +623 .field("max_idle_timeout", max_idle_timeout) +624 .field("stream_receive_window", stream_receive_window) +625 .field("receive_window", receive_window) +626 .field("send_window", send_window) +627 .field("send_fairness", send_fairness) +628 .field("packet_threshold", packet_threshold) +629 .field("time_threshold", time_threshold) +630 .field("initial_rtt", initial_rtt) +631 .field("initial_mtu", initial_mtu) +632 .field("min_mtu", min_mtu) +633 .field("mtu_discovery_config", mtu_discovery_config) +634 .field("pad_to_mtu", pad_to_mtu) +635 .field("ack_frequency_config", ack_frequency_config) +636 .field( +637 "max_outgoing_bytes_per_second", +638 max_outgoing_bytes_per_second, +639 ) +640 .field( +641 "persistent_congestion_threshold", +642 persistent_congestion_threshold, +643 ) +644 .field("keep_alive_interval", keep_alive_interval) +645 .field("crypto_buffer_size", crypto_buffer_size) +646 .field("allow_spin", allow_spin) +647 .field("datagram_receive_buffer_size", datagram_receive_buffer_size) +648 .field("datagram_send_buffer_size", datagram_send_buffer_size) +649 // congestion_controller_factory not debug +650 .field("enable_segmentation_offload", enable_segmentation_offload) +651 .field("address_discovery_role", address_discovery_role) +652 .field( +653 "max_concurrent_multipath_paths", +654 max_concurrent_multipath_paths, +655 ) +656 .field( +657 "default_path_max_idle_timeout", +658 default_path_max_idle_timeout, +659 ) +660 .field( +661 "default_path_keep_alive_interval", +662 default_path_keep_alive_interval, +663 ) +664 .field( +665 "max_remote_nat_traversal_addresses", +666 max_remote_nat_traversal_addresses, +667 ); +668 #[cfg(feature = "qlog")] +669 s.field("qlog_factory", &qlog_factory.is_some()); +670 +671 s.finish_non_exhaustive() +672 } +673} +674 +675/// Parameters for controlling the peer's acknowledgement frequency +676/// +677/// The parameters provided in this config will be sent to the peer at the beginning of the +678/// connection, so it can take them into account when sending acknowledgements (see each parameter's +679/// description for details on how it influences acknowledgement frequency). +680/// +681/// noq's implementation follows the fourth draft of the +682/// [QUIC Acknowledgement Frequency extension](https://datatracker.ietf.org/doc/html/draft-ietf-quic-ack-frequency-04). +683/// The defaults produce behavior slightly different than the behavior without this extension, +684/// because they change the way reordered packets are handled (see +685/// [`AckFrequencyConfig::reordering_threshold`] for details). +686#[derive(Clone, Debug)] +687pub struct AckFrequencyConfig { +688 pub(crate) ack_eliciting_threshold: VarInt, +689 pub(crate) max_ack_delay: Option<Duration>, +690 pub(crate) reordering_threshold: VarInt, +691} +692 +693impl AckFrequencyConfig { +694 /// The ack-eliciting threshold we will request the peer to use +695 /// +696 /// This threshold represents the number of ack-eliciting packets an endpoint may receive +697 /// without immediately sending an ACK. +698 /// +699 /// The remote peer should send at least one ACK frame when more than this number of +700 /// ack-eliciting packets have been received. A value of 0 results in a receiver immediately +701 /// acknowledging every ack-eliciting packet. +702 /// +703 /// Defaults to 1, which sends ACK frames for every other ack-eliciting packet. +704 pub fn ack_eliciting_threshold(&mut self, value: VarInt) -> &mut Self { +705 self.ack_eliciting_threshold = value; +706 self +707 } +708 +709 /// The `max_ack_delay` we will request the peer to use +710 /// +711 /// This parameter represents the maximum amount of time that an endpoint waits before sending +712 /// an ACK when the ack-eliciting threshold hasn't been reached. +713 /// +714 /// The effective `max_ack_delay` will be clamped to be at least the peer's `min_ack_delay` +715 /// transport parameter, and at most the greater of the current path RTT or 25ms. +716 /// +717 /// Defaults to `None`, in which case the peer's original `max_ack_delay` will be used, as +718 /// obtained from its transport parameters. +719 pub fn max_ack_delay(&mut self, value: Option<Duration>) -> &mut Self { +720 self.max_ack_delay = value; +721 self +722 } +723 +724 /// The reordering threshold we will request the peer to use +725 /// +726 /// This threshold represents the amount of out-of-order packets that will trigger an endpoint +727 /// to send an ACK, without waiting for `ack_eliciting_threshold` to be exceeded or for +728 /// `max_ack_delay` to be elapsed. 729 /// -730 /// This parameter represents the maximum amount of time that an endpoint waits before sending -731 /// an ACK when the ack-eliciting threshold hasn't been reached. -732 /// -733 /// The effective `max_ack_delay` will be clamped to be at least the peer's `min_ack_delay` -734 /// transport parameter, and at most the greater of the current path RTT or 25ms. -735 /// -736 /// Defaults to `None`, in which case the peer's original `max_ack_delay` will be used, as -737 /// obtained from its transport parameters. -738 pub fn max_ack_delay(&mut self, value: Option<Duration>) -> &mut Self { -739 self.max_ack_delay = value; -740 self -741 } +730 /// A value of 0 indicates out-of-order packets do not elicit an immediate ACK. A value of 1 +731 /// immediately acknowledges any packets that are received out of order (this is also the +732 /// behavior when the extension is disabled). +733 /// +734 /// It is recommended to set this value to [`TransportConfig::packet_threshold`] minus one. +735 /// Since the default value for [`TransportConfig::packet_threshold`] is 3, this value defaults +736 /// to 2. +737 pub fn reordering_threshold(&mut self, value: VarInt) -> &mut Self { +738 self.reordering_threshold = value; +739 self +740 } +741} 742 -743 /// The reordering threshold we will request the peer to use -744 /// -745 /// This threshold represents the amount of out-of-order packets that will trigger an endpoint -746 /// to send an ACK, without waiting for `ack_eliciting_threshold` to be exceeded or for -747 /// `max_ack_delay` to be elapsed. -748 /// -749 /// A value of 0 indicates out-of-order packets do not elicit an immediate ACK. A value of 1 -750 /// immediately acknowledges any packets that are received out of order (this is also the -751 /// behavior when the extension is disabled). -752 /// -753 /// It is recommended to set this value to [`TransportConfig::packet_threshold`] minus one. -754 /// Since the default value for [`TransportConfig::packet_threshold`] is 3, this value defaults -755 /// to 2. -756 pub fn reordering_threshold(&mut self, value: VarInt) -> &mut Self { -757 self.reordering_threshold = value; -758 self -759 } -760} -761 -762impl Default for AckFrequencyConfig { -763 fn default() -> Self { -764 Self { -765 ack_eliciting_threshold: VarInt(1), -766 max_ack_delay: None, -767 reordering_threshold: VarInt(2), -768 } -769 } -770} -771 -772/// Parameters governing MTU discovery. +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/// # The why of MTU discovery +774/// # MTU discovery internals 775/// -776/// By design, QUIC ensures during the handshake that the network path between the client and the -777/// server is able to transmit unfragmented UDP packets with a body of 1200 bytes. In other words, -778/// once the connection is established, we know that the network path's maximum transmission unit -779/// (MTU) is of at least 1200 bytes (plus IP and UDP headers). Because of this, a QUIC endpoint can -780/// split outgoing data in packets of 1200 bytes, with confidence that the network will be able to -781/// deliver them (if the endpoint were to send bigger packets, they could prove too big and end up -782/// being dropped). -783/// -784/// There is, however, a significant overhead associated to sending a packet. If the same -785/// information can be sent in fewer packets, that results in higher throughput. The amount of -786/// packets that need to be sent is inversely proportional to the MTU: the higher the MTU, the -787/// bigger the packets that can be sent, and the fewer packets that are needed to transmit a given -788/// amount of bytes. +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/// Most networks have an MTU higher than 1200. Through MTU discovery, endpoints can detect the -791/// path's MTU and, if it turns out to be higher, start sending bigger packets. -792/// -793/// # MTU discovery internals -794/// -795/// noq implements MTU discovery through DPLPMTUD (Datagram Packetization Layer Path MTU -796/// Discovery), described in [section 14.3 of RFC -797/// 9000](https://www.rfc-editor.org/rfc/rfc9000.html#section-14.3). This method consists of sending -798/// QUIC packets padded to a particular size (called PMTU probes), and waiting to see if the remote -799/// peer responds with an ACK. If an ACK is received, that means the probe arrived at the remote -800/// peer, which in turn means that the network path's MTU is of at least the packet's size. If the -801/// probe is lost, it is sent another 2 times before concluding that the MTU is lower than the -802/// packet's size. +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/// MTU discovery runs on a schedule (e.g. every 600 seconds) specified through -805/// [`MtuDiscoveryConfig::interval`]. The first run happens right after the handshake, and -806/// subsequent discoveries are scheduled to run when the interval has elapsed, starting from the -807/// last time when MTU discovery completed. -808/// -809/// Since the search space for MTUs is quite big (the smallest possible MTU is 1200, and the highest -810/// is 65527), noq performs a binary search to keep the number of probes as low as possible. The -811/// lower bound of the search is equal to [`TransportConfig::initial_mtu`] in the -812/// initial MTU discovery run, and is equal to the currently discovered MTU in subsequent runs. The -813/// upper bound is determined by the minimum of [`MtuDiscoveryConfig::upper_bound`] and the -814/// `max_udp_payload_size` transport parameter received from the peer during the handshake. -815/// -816/// # Black hole detection -817/// -818/// If, at some point, the network path no longer accepts packets of the detected size, packet loss -819/// will eventually trigger black hole detection and reset the detected MTU to 1200. In that case, -820/// MTU discovery will be triggered after [`MtuDiscoveryConfig::black_hole_cooldown`] (ignoring the -821/// timer that was set based on [`MtuDiscoveryConfig::interval`]). -822/// -823/// # Interaction between peers -824/// -825/// There is no guarantee that the MTU on the path between A and B is the same as the MTU of the -826/// path between B and A. Therefore, each peer in the connection needs to run MTU discovery -827/// independently in order to discover the path's MTU. -828#[derive(Clone, Debug)] -829pub struct MtuDiscoveryConfig { -830 pub(crate) interval: Duration, -831 pub(crate) upper_bound: u16, -832 pub(crate) minimum_change: u16, -833 pub(crate) black_hole_cooldown: Duration, -834} -835 -836impl MtuDiscoveryConfig { -837 /// Specifies the time to wait after completing MTU discovery before starting a new MTU -838 /// discovery run. -839 /// -840 /// Defaults to 600 seconds, as recommended by [RFC -841 /// 8899](https://www.rfc-editor.org/rfc/rfc8899). -842 pub fn interval(&mut self, value: Duration) -> &mut Self { -843 self.interval = value; -844 self -845 } -846 -847 /// Specifies the upper bound to the max UDP payload size that MTU discovery will search for. -848 /// -849 /// Defaults to 1452, to stay within Ethernet's MTU when using IPv4 and IPv6. The highest -850 /// allowed value is 65527, which corresponds to the maximum permitted UDP payload on IPv6. -851 /// -852 /// It is safe to use an arbitrarily high upper bound, regardless of the network path's MTU. The -853 /// only drawback is that MTU discovery might take more time to finish. -854 pub fn upper_bound(&mut self, value: u16) -> &mut Self { -855 self.upper_bound = value.min(MAX_UDP_PAYLOAD); -856 self -857 } -858 -859 /// Specifies the amount of time that MTU discovery should wait after a black hole was detected -860 /// before running again. Defaults to one minute. -861 /// -862 /// Black hole detection can be spuriously triggered in case of congestion, so it makes sense to -863 /// try MTU discovery again after a short period of time. -864 pub fn black_hole_cooldown(&mut self, value: Duration) -> &mut Self { -865 self.black_hole_cooldown = value; -866 self -867 } +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 /// Specifies the minimum MTU change to stop the MTU discovery phase. -870 /// Defaults to 20. -871 pub fn minimum_change(&mut self, value: u16) -> &mut Self { -872 self.minimum_change = value; -873 self -874 } -875} -876 -877impl Default for MtuDiscoveryConfig { -878 fn default() -> Self { -879 Self { -880 interval: Duration::from_secs(600), -881 upper_bound: 1452, -882 black_hole_cooldown: Duration::from_secs(60), -883 minimum_change: 20, -884 } -885 } -886} -887 -888/// Maximum duration of inactivity to accept before timing out the connection -889/// -890/// This wraps an underlying [`VarInt`], representing the duration in milliseconds. Values can be -891/// constructed by converting directly from `VarInt`, or using `TryFrom<Duration>`. -892/// -893/// ``` -894/// # use std::{convert::TryFrom, time::Duration}; -895/// # use noq_proto::{IdleTimeout, VarIntBoundsExceeded, VarInt}; -896/// # fn main() -> Result<(), VarIntBoundsExceeded> { -897/// // A `VarInt`-encoded value in milliseconds -898/// let timeout = IdleTimeout::from(VarInt::from_u32(10_000)); -899/// -900/// // Try to convert a `Duration` into a `VarInt`-encoded timeout -901/// let timeout = IdleTimeout::try_from(Duration::from_secs(10))?; -902/// # Ok(()) -903/// # } -904/// ``` -905#[derive(Default, Copy, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)] -906pub struct IdleTimeout(VarInt); -907 -908impl From<VarInt> for IdleTimeout { -909 fn from(inner: VarInt) -> Self { -910 Self(inner) -911 } -912} -913 -914impl std::convert::TryFrom<Duration> for IdleTimeout { -915 type Error = VarIntBoundsExceeded; -916 -917 fn try_from(timeout: Duration) -> Result<Self, Self::Error> { -918 let inner = VarInt::try_from(timeout.as_millis())?; -919 Ok(Self(inner)) -920 } -921} -922 -923impl fmt::Debug for IdleTimeout { -924 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -925 self.0.fmt(f) -926 } -927} \ No newline at end of file +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/617/docs/src/noq_proto/connection/mod.rs.html b/pr/617/docs/src/noq_proto/connection/mod.rs.html index f6d2e514c..7d29ca764 100644 --- a/pr/617/docs/src/noq_proto/connection/mod.rs.html +++ b/pr/617/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))] @@ -1367,6411 +1367,6321 @@ 1367 if last_packet_number.is_some() || congestion_blocked { 1368 self.qlog.emit_recovery_metrics( 1369 path_id, -1370 &mut self.paths.get_mut(&path_id).unwrap().data, -1371 now, -1372 ); -1373 } -1374 -1375 self.path_data_mut(path_id).app_limited = -1376 last_packet_number.is_none() && !congestion_blocked; -1377 -1378 match last_packet_number { -1379 Some(last_packet_number) => { -1380 // Note that when sending in multiple spaces the last packet number will be -1381 // the one from the highest space. -1382 self.path_data_mut(path_id).congestion.on_sent( -1383 now, -1384 transmit.len() as u64, -1385 last_packet_number, -1386 ); -1387 Some(self.build_transmit(path_id, transmit)) -1388 } -1389 None => None, -1390 } -1391 } -1392 -1393 /// poll_transmit logic for a QUIC-MULTIPATH packet number space (PathID + SpaceId). -1394 #[must_use] -1395 fn poll_transmit_path_space( -1396 &mut self, -1397 now: Instant, -1398 transmit: &mut TransmitBuf<'_>, -1399 path_id: PathId, -1400 space_id: SpaceId, -1401 remote_cid: ConnectionId, -1402 scheduling_info: &PathSchedulingInfo, -1403 // If we need to send a CONNECTION_CLOSE frame. -1404 connection_close_pending: bool, -1405 // Whether the current datagram needs to be padded to a certain size. -1406 mut pad_datagram: PadDatagram, -1407 ) -> PollPathSpaceStatus { -1408 // Keep track of the last packet number we wrote. If None we did not write any -1409 // packets. -1410 let mut last_packet_number = None; -1411 -1412 // Each loop of this may build one packet. It works logically as follows: -1413 // -1414 // - Check if something *needs* to be sent in this space and *can* be sent. -1415 // - If not, return to the caller who will call us again for the next space. -1416 // - Start a new datagram. -1417 // - Unless coalescing the packet into an existing datagram. -1418 // - Write the packet header and payload. -1419 // - Check if coalescing a next packet into the datagram is possible. -1420 // - If coalescing, finish packet without padding to leave space in the datagram. -1421 // - If not coalescing, complete the datagram: -1422 // - Finish packet with padding. -1423 // - Set the transmit segment size if this is the first datagram. -1424 // - Loop: next iteration will exit the loop if nothing more to send in this -1425 // space. The TransmitBuf will contain a started datagram with space if -1426 // coalescing, or completely filled datagram if not coalescing. -1427 loop { -1428 // Determine if anything can be sent in this packet number space. -1429 let max_packet_size = if transmit.datagram_remaining_mut() > 0 { -1430 // A datagram is started already, we are coalescing another packet into it. -1431 transmit.datagram_remaining_mut() -1432 } else { -1433 // A new datagram needs to be started. -1434 transmit.segment_size() -1435 }; -1436 let can_send = -1437 self.space_can_send(space_id, path_id, max_packet_size, connection_close_pending); -1438 let needs_loss_probe = self.spaces[space_id].for_path(path_id).loss_probes > 0; -1439 let space_will_send = { -1440 if scheduling_info.is_abandoned { -1441 // If this path is abandoned then we might still have to send -1442 // PATH_ABANDON itself on it if there was no better space -1443 // available. Otherwise we want to send the PATH_ABANDON as permitted by -1444 // may_send_data however. -1445 scheduling_info.may_self_abandon -1446 && self.spaces[space_id] -1447 .pending -1448 .path_abandon -1449 .contains_key(&path_id) -1450 } else if can_send.close && scheduling_info.may_send_close { -1451 // This is the best path to send a CONNECTION_CLOSE on. -1452 true -1453 } else if needs_loss_probe || can_send.space_specific { -1454 // We always send a loss probe or space-specific frames if the path is -1455 // not abandoned. +1370 &mut self +1371 .paths +1372 .get_mut(&path_id) +1373 .expect("path_id was iterated from self.paths above") +1374 .data, +1375 now, +1376 ); +1377 } +1378 +1379 self.path_data_mut(path_id).app_limited = +1380 last_packet_number.is_none() && !congestion_blocked; +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 Some(self.build_transmit(path_id, transmit)) +1392 } +1393 None => None, +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 { -1458 // Anything else we only send if we're the best path for SpaceKind::Data -1459 // frames. -1460 !can_send.is_empty() && scheduling_info.may_send_data -1461 } -1462 }; -1463 -1464 if !space_will_send { -1465 // Nothing more to send. Previous iterations of this loop may have built -1466 // packets already. -1467 return match last_packet_number { -1468 Some(pn) => PollPathSpaceStatus::WrotePacket { -1469 last_packet_number: pn, -1470 pad_datagram, -1471 }, -1472 None => { -1473 // Only log for spaces which have crypto. -1474 if self.crypto_state.has_keys(space_id.encryption_level()) -1475 || (space_id == SpaceId::Data -1476 && self.crypto_state.has_keys(EncryptionLevel::ZeroRtt)) -1477 { -1478 trace!(?space_id, %path_id, "nothing to send in space"); -1479 } -1480 PollPathSpaceStatus::NothingToSend { -1481 congestion_blocked: false, -1482 } -1483 } -1484 }; -1485 } -1486 -1487 // We want to send on this space, check congestion control if we can. But only -1488 // if we will need to start a new datagram. If we are coalescing into an already -1489 // started datagram we do not need to check congestion control again. -1490 if transmit.datagram_remaining_mut() == 0 { -1491 let congestion_blocked = -1492 self.path_congestion_check(space_id, path_id, transmit, &can_send, now); -1493 if congestion_blocked != PathBlocked::No { -1494 // Previous iterations of this loop may have built packets already. -1495 return match last_packet_number { -1496 Some(pn) => PollPathSpaceStatus::WrotePacket { -1497 last_packet_number: pn, -1498 pad_datagram, -1499 }, -1500 None => { -1501 return PollPathSpaceStatus::NothingToSend { -1502 congestion_blocked: true, -1503 }; -1504 } -1505 }; -1506 } -1507 -1508 // If the datagram is full (or there never was one started), we need to start a -1509 // new one. -1510 if transmit.num_datagrams() >= transmit.max_datagrams().get() { -1511 // No more datagrams allowed. -1512 // Previous iterations of this loop may have built packets already. -1513 return match last_packet_number { -1514 Some(pn) => PollPathSpaceStatus::WrotePacket { -1515 last_packet_number: pn, -1516 pad_datagram, -1517 }, -1518 None => { -1519 return PollPathSpaceStatus::NothingToSend { -1520 congestion_blocked: false, -1521 }; -1522 } -1523 }; -1524 } -1525 -1526 if needs_loss_probe { -1527 // Ensure we have something to send for a tail-loss probe. -1528 let request_immediate_ack = -1529 space_id == SpaceId::Data && self.peer_supports_ack_frequency(); -1530 self.spaces[space_id].queue_tail_loss_probe( -1531 path_id, -1532 request_immediate_ack, -1533 &self.streams, -1534 ); -1535 -1536 self.spaces[space_id].for_path(path_id).loss_probes -= 1; // needs_loss_probe ensures loss_probes > 0 -1537 -1538 // Clamp the datagram to at most the minimum MTU to ensure that loss -1539 // probes can get through and enable recovery even if the path MTU -1540 // has shrank unexpectedly. -1541 transmit.start_new_datagram_with_size(std::cmp::min( -1542 usize::from(INITIAL_MTU), -1543 transmit.segment_size(), -1544 )); -1545 } else { -1546 transmit.start_new_datagram(); -1547 } -1548 trace!(count = transmit.num_datagrams(), "new datagram started"); -1549 -1550 // We started a new datagram, we decide later if it needs padding. -1551 pad_datagram = PadDatagram::No; -1552 } +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 +1512 // If the datagram is full (or there never was one started), we need to start a +1513 // new one. +1514 if transmit.num_datagrams() >= transmit.max_datagrams().get() { +1515 // No more datagrams allowed. +1516 // Previous iterations of this loop may have built packets already. +1517 return match last_packet_number { +1518 Some(pn) => PollPathSpaceStatus::WrotePacket { +1519 last_packet_number: pn, +1520 pad_datagram, +1521 }, +1522 None => { +1523 return PollPathSpaceStatus::NothingToSend { +1524 congestion_blocked: false, +1525 }; +1526 } +1527 }; +1528 } +1529 +1530 if needs_loss_probe { +1531 // Ensure we have something to send for a tail-loss probe. +1532 let request_immediate_ack = +1533 space_id == SpaceId::Data && self.peer_supports_ack_frequency(); +1534 self.spaces[space_id].queue_tail_loss_probe( +1535 path_id, +1536 request_immediate_ack, +1537 &self.streams, +1538 ); +1539 +1540 self.spaces[space_id].for_path(path_id).loss_probes -= 1; // needs_loss_probe ensures loss_probes > 0 +1541 +1542 // Clamp the datagram to at most the minimum MTU to ensure that loss +1543 // probes can get through and enable recovery even if the path MTU +1544 // has shrank unexpectedly. +1545 transmit.start_new_datagram_with_size(std::cmp::min( +1546 usize::from(INITIAL_MTU), +1547 transmit.segment_size(), +1548 )); +1549 } else { +1550 transmit.start_new_datagram(); +1551 } +1552 trace!(count = transmit.num_datagrams(), "new datagram started"); 1553 -1554 // If coalescing another packet into the existing datagram, there should -1555 // still be enough space for a whole packet. -1556 if transmit.datagram_start_offset() < transmit.len() { -1557 debug_assert!(transmit.datagram_remaining_mut() >= MIN_PACKET_SPACE); -1558 } -1559 -1560 // -1561 // From here on, we've determined that a packet will definitely be sent. -1562 // +1554 // We started a new datagram, we decide later if it needs padding. +1555 pad_datagram = PadDatagram::No; +1556 } +1557 +1558 // If coalescing another packet into the existing datagram, there should +1559 // still be enough space for a whole packet. +1560 if transmit.datagram_start_offset() < transmit.len() { +1561 debug_assert!(transmit.datagram_remaining_mut() >= MIN_PACKET_SPACE); +1562 } 1563 -1564 if self.crypto_state.has_keys(EncryptionLevel::Initial) -1565 && space_id == SpaceId::Handshake -1566 && self.side.is_client() -1567 { -1568 // A client stops both sending and processing Initial packets when it -1569 // sends its first Handshake packet. -1570 self.discard_space(now, SpaceKind::Initial); -1571 } -1572 if let Some(ref mut prev) = self.crypto_state.prev_crypto { -1573 prev.update_unacked = false; -1574 } -1575 -1576 let Some(mut builder) = -1577 PacketBuilder::new(now, space_id, path_id, remote_cid, transmit, self) -1578 else { -1579 // Confidentiality limit is exceeded and the connection has been killed. We -1580 // should not send any other packets. This works in a roundabout way: We -1581 // have started a datagram but not written anything into it. So even if we -1582 // get called again for another space we will see an already started -1583 // datagram and try and start another packet here. Then be stopped by the -1584 // same confidentiality limit. -1585 return PollPathSpaceStatus::NothingToSend { -1586 congestion_blocked: false, -1587 }; -1588 }; -1589 last_packet_number = Some(builder.packet_number); -1590 -1591 if space_id == SpaceId::Initial -1592 && (self.side.is_client() || can_send.is_ack_eliciting()) -1593 { -1594 // https://www.rfc-editor.org/rfc/rfc9000.html#section-14.1 -1595 pad_datagram |= PadDatagram::ToMinMtu; -1596 } -1597 if space_id == SpaceId::Data && self.config.pad_to_mtu { -1598 pad_datagram |= PadDatagram::ToSegmentSize; -1599 } -1600 -1601 if scheduling_info.may_send_close && can_send.close { -1602 trace!("sending CONNECTION_CLOSE"); -1603 // Encode ACKs before the ConnectionClose message, to give the receiver -1604 // a better approximate on what data has been processed. This is -1605 // especially important with ack delay, since the peer might not -1606 // have gotten any other ACK for the data earlier on. -1607 let is_multipath_negotiated = self.is_multipath_negotiated(); -1608 for path_id in self.spaces[space_id] -1609 .number_spaces -1610 .iter() -1611 .filter(|(_, pns)| !pns.pending_acks.ranges().is_empty()) -1612 .map(|(&path_id, _)| path_id) -1613 .collect::<Vec<_>>() -1614 { -1615 Self::populate_acks( -1616 now, -1617 self.receiving_ecn, -1618 path_id, -1619 space_id, -1620 &mut self.spaces[space_id], -1621 is_multipath_negotiated, -1622 &mut builder, -1623 &mut self.path_stats.for_path(path_id).frame_tx, -1624 self.crypto_state.has_keys(space_id.encryption_level()), -1625 ); -1626 } -1627 -1628 // Since there only 64 ACK frames there will always be enough space -1629 // to encode the ConnectionClose frame too. However we still have the -1630 // check here to prevent crashes if something changes. +1564 // +1565 // From here on, we've determined that a packet will definitely be sent. +1566 // +1567 +1568 if self.crypto_state.has_keys(EncryptionLevel::Initial) +1569 && space_id == SpaceId::Handshake +1570 && self.side.is_client() +1571 { +1572 // A client stops both sending and processing Initial packets when it +1573 // sends its first Handshake packet. +1574 self.discard_space(now, SpaceKind::Initial); +1575 } +1576 if let Some(ref mut prev) = self.crypto_state.prev_crypto { +1577 prev.update_unacked = false; +1578 } +1579 +1580 let Some(mut builder) = +1581 PacketBuilder::new(now, space_id, path_id, remote_cid, transmit, self) +1582 else { +1583 // Confidentiality limit is exceeded and the connection has been killed. We +1584 // should not send any other packets. This works in a roundabout way: We +1585 // have started a datagram but not written anything into it. So even if we +1586 // get called again for another space we will see an already started +1587 // datagram and try and start another packet here. Then be stopped by the +1588 // same confidentiality limit. +1589 return PollPathSpaceStatus::NothingToSend { +1590 congestion_blocked: false, +1591 }; +1592 }; +1593 last_packet_number = Some(builder.packet_number); +1594 +1595 if space_id == SpaceId::Initial +1596 && (self.side.is_client() || can_send.is_ack_eliciting()) +1597 { +1598 // https://www.rfc-editor.org/rfc/rfc9000.html#section-14.1 +1599 pad_datagram |= PadDatagram::ToMinMtu; +1600 } +1601 if space_id == SpaceId::Data && self.config.pad_to_mtu { +1602 pad_datagram |= PadDatagram::ToSegmentSize; +1603 } +1604 +1605 if scheduling_info.may_send_close && can_send.close { +1606 trace!("sending CONNECTION_CLOSE"); +1607 // Encode ACKs before the ConnectionClose message, to give the receiver +1608 // a better approximate on what data has been processed. This is +1609 // especially important with ack delay, since the peer might not +1610 // have gotten any other ACK for the data earlier on. +1611 let is_multipath_negotiated = self.is_multipath_negotiated(); +1612 for path_id in self.spaces[space_id] +1613 .number_spaces +1614 .iter() +1615 .filter(|(_, pns)| !pns.pending_acks.ranges().is_empty()) +1616 .map(|(&path_id, _)| path_id) +1617 .collect::<Vec<_>>() +1618 { +1619 Self::populate_acks( +1620 now, +1621 self.receiving_ecn, +1622 path_id, +1623 space_id, +1624 &mut self.spaces[space_id], +1625 is_multipath_negotiated, +1626 &mut builder, +1627 &mut self.path_stats.for_path(path_id).frame_tx, +1628 self.crypto_state.has_keys(space_id.encryption_level()), +1629 ); +1630 } 1631 -1632 // TODO(flub): This needs fixing for multipath, to ensure we can always -1633 // write the CONNECTION_CLOSE even if we have many PATH_ACKs to send: -1634 // https://github.com/n0-computer/noq/issues/367. -1635 debug_assert!( -1636 builder.frame_space_remaining() > frame::ConnectionClose::SIZE_BOUND, -1637 "ACKs should leave space for ConnectionClose" -1638 ); -1639 let stats = &mut self.path_stats.for_path(path_id).frame_tx; -1640 if frame::ConnectionClose::SIZE_BOUND < builder.frame_space_remaining() { -1641 let max_frame_size = builder.frame_space_remaining(); -1642 let close: Close = match self.state.as_type() { -1643 StateType::Closed => { -1644 let reason: Close = -1645 self.state.as_closed().expect("checked").clone().into(); -1646 if space_id == SpaceId::Data || reason.is_transport_layer() { -1647 reason -1648 } else { -1649 TransportError::APPLICATION_ERROR("").into() -1650 } -1651 } -1652 StateType::Draining => TransportError::NO_ERROR("").into(), -1653 _ => unreachable!( -1654 "tried to make a close packet when the connection wasn't closed" -1655 ), -1656 }; -1657 builder.write_frame(close.encoder(max_frame_size), stats); -1658 } -1659 let last_pn = builder.packet_number; -1660 builder.finish_and_track(now, self, path_id, pad_datagram); -1661 if space_id.kind() == self.highest_space { -1662 // Don't send another close packet. Even with multipath we only send -1663 // CONNECTION_CLOSE on a single path since we expect our paths to work. -1664 self.connection_close_pending = false; -1665 } -1666 // Send a close frame in every possible space for robustness, per -1667 // RFC9000 "Immediate Close during the Handshake". Don't bother trying -1668 // to send anything else. -1669 // TODO(flub): This breaks during the handshake if we can not coalesce -1670 // packets due to space reasons: the next space would either fail a -1671 // debug_assert checking for enough packet space or produce an invalid -1672 // packet. We need to keep track of per-space pending CONNECTION_CLOSE to -1673 // be able to send these across multiple calls to poll_transmit. Then -1674 // check for coalescing space here because initial packets need to be in -1675 // padded datagrams. And also add space checks for CONNECTION_CLOSE in -1676 // space_can_send so it would stop a GSO batch if the datagram is too -1677 // small for another CONNECTION_CLOSE packet. -1678 return PollPathSpaceStatus::WrotePacket { -1679 last_packet_number: last_pn, -1680 pad_datagram, -1681 }; -1682 } -1683 -1684 self.populate_packet(now, space_id, path_id, scheduling_info, &mut builder); -1685 -1686 // ACK-only packets should only be sent when explicitly allowed. If we write them due to -1687 // any other reason, there is a bug which leads to one component announcing write -1688 // readiness while not writing any data. This degrades performance. The condition is -1689 // only checked if the full MTU is available and when potentially large fixed-size -1690 // frames aren't queued, so that lack of space in the datagram isn't the reason for just -1691 // writing ACKs. -1692 debug_assert!( -1693 !(builder.sent_frames().is_ack_only(&self.streams) -1694 && !can_send.acks -1695 && (can_send.other || can_send.space_specific) -1696 && builder.buf.segment_size() -1697 == self.path_data(path_id).current_mtu() as usize -1698 && self.datagrams.outgoing.is_empty()), -1699 "SendableFrames was {can_send:?}, but only ACKs have been written" -1700 ); -1701 if builder.sent_frames().requires_padding { -1702 pad_datagram |= PadDatagram::ToMinMtu; -1703 } -1704 -1705 for (path_id, _pn) in builder.sent_frames().largest_acked.iter() { -1706 self.spaces[space_id] -1707 .for_path(*path_id) -1708 .pending_acks -1709 .acks_sent(); -1710 self.timers.stop( -1711 Timer::PerPath(*path_id, PathTimer::MaxAckDelay), -1712 self.qlog.with_time(now), -1713 ); -1714 } -1715 -1716 // Now we need to finish the packet. Before we do so we need to know if we will -1717 // be coalescing the next packet into this one, or will be ending the datagram -1718 // as well. Because if this is the last packet in the datagram more padding -1719 // might be needed because of the packet type, or to fill the GSO segment size. -1720 -1721 // Are we allowed to coalesce AND is there enough space for another *packet* in -1722 // this datagram AND will we definitely send another packet? -1723 if builder.can_coalesce && path_id == PathId::ZERO && { -1724 let max_packet_size = builder -1725 .buf -1726 .datagram_remaining_mut() -1727 .saturating_sub(builder.predict_packet_end()); -1728 max_packet_size > MIN_PACKET_SPACE -1729 && self.has_pending_packet(space_id, max_packet_size, connection_close_pending) -1730 } { -1731 // We can append/coalesce the next packet into the current -1732 // datagram. Finish the current packet without adding extra padding. -1733 trace!("will coalesce with next packet"); -1734 builder.finish_and_track(now, self, path_id, PadDatagram::No); -1735 } else { -1736 // We need a new datagram for the next packet. Finish the current -1737 // packet with padding. -1738 // TODO(flub): if there isn't any more data to be sent, this will still pad -1739 // to the segment size and only discover there is nothing to send before -1740 // starting the next packet. That is wasting up to 32 bytes. -1741 if builder.buf.num_datagrams() > 1 && matches!(pad_datagram, PadDatagram::No) { -1742 // If too many padding bytes would be required to continue the -1743 // GSO batch after this packet, end the GSO batch here. Ensures -1744 // that fixed-size frames with heterogeneous sizes -1745 // (e.g. application datagrams) won't inadvertently waste large -1746 // amounts of bandwidth. The exact threshold is a bit arbitrary -1747 // and might benefit from further tuning, though there's no -1748 // universally optimal value. -1749 const MAX_PADDING: usize = 32; -1750 if builder.buf.datagram_remaining_mut() -1751 > builder.predict_packet_end() + MAX_PADDING -1752 { -1753 trace!( -1754 "GSO truncated by demand for {} padding bytes", -1755 builder.buf.datagram_remaining_mut() - builder.predict_packet_end() -1756 ); -1757 let last_pn = builder.packet_number; -1758 builder.finish_and_track(now, self, path_id, PadDatagram::No); -1759 return PollPathSpaceStatus::Send { -1760 last_packet_number: last_pn, -1761 }; -1762 } -1763 -1764 // Pad the current datagram to GSO segment size so it can be -1765 // included in the GSO batch. -1766 builder.finish_and_track(now, self, path_id, PadDatagram::ToSegmentSize); -1767 } else { -1768 builder.finish_and_track(now, self, path_id, pad_datagram); -1769 } -1770 -1771 // If this is the first datagram we set the segment size to the size of the -1772 // first datagram. -1773 if transmit.num_datagrams() == 1 { -1774 transmit.clip_segment_size(); -1775 } -1776 } -1777 } -1778 } -1779 -1780 fn poll_transmit_mtu_probe( -1781 &mut self, -1782 now: Instant, -1783 buf: &mut Vec<u8>, -1784 path_id: PathId, -1785 ) -> Option<Transmit> { -1786 let (active_cid, probe_size) = self.get_mtu_probe_data(now, path_id)?; -1787 -1788 // We are definitely sending a DPLPMTUD probe. -1789 let mut transmit = TransmitBuf::new(buf, NonZeroUsize::MIN, probe_size as usize); -1790 transmit.start_new_datagram_with_size(probe_size as usize); +1632 // Since there only 64 ACK frames there will always be enough space +1633 // to encode the ConnectionClose frame too. However we still have the +1634 // check here to prevent crashes if something changes. +1635 +1636 // TODO(flub): This needs fixing for multipath, to ensure we can always +1637 // write the CONNECTION_CLOSE even if we have many PATH_ACKs to send: +1638 // https://github.com/n0-computer/noq/issues/367. +1639 debug_assert!( +1640 builder.frame_space_remaining() > frame::ConnectionClose::SIZE_BOUND, +1641 "ACKs should leave space for ConnectionClose" +1642 ); +1643 let stats = &mut self.path_stats.for_path(path_id).frame_tx; +1644 if frame::ConnectionClose::SIZE_BOUND < builder.frame_space_remaining() { +1645 let max_frame_size = builder.frame_space_remaining(); +1646 let close: Close = match self.state.as_type() { +1647 StateType::Closed => { +1648 let reason: Close = +1649 self.state.as_closed().expect("checked").clone().into(); +1650 if space_id == SpaceId::Data || reason.is_transport_layer() { +1651 reason +1652 } else { +1653 TransportError::APPLICATION_ERROR("").into() +1654 } +1655 } +1656 StateType::Draining => TransportError::NO_ERROR("").into(), +1657 _ => unreachable!( +1658 "tried to make a close packet when the connection wasn't closed" +1659 ), +1660 }; +1661 builder.write_frame(close.encoder(max_frame_size), stats); +1662 } +1663 let last_pn = builder.packet_number; +1664 builder.finish_and_track(now, self, path_id, pad_datagram); +1665 if space_id.kind() == self.highest_space { +1666 // Don't send another close packet. Even with multipath we only send +1667 // CONNECTION_CLOSE on a single path since we expect our paths to work. +1668 self.connection_close_pending = false; +1669 } +1670 // Send a close frame in every possible space for robustness, per +1671 // RFC9000 "Immediate Close during the Handshake". Don't bother trying +1672 // to send anything else. +1673 // TODO(flub): This breaks during the handshake if we can not coalesce +1674 // packets due to space reasons: the next space would either fail a +1675 // debug_assert checking for enough packet space or produce an invalid +1676 // packet. We need to keep track of per-space pending CONNECTION_CLOSE to +1677 // be able to send these across multiple calls to poll_transmit. Then +1678 // check for coalescing space here because initial packets need to be in +1679 // padded datagrams. And also add space checks for CONNECTION_CLOSE in +1680 // space_can_send so it would stop a GSO batch if the datagram is too +1681 // small for another CONNECTION_CLOSE packet. +1682 return PollPathSpaceStatus::WrotePacket { +1683 last_packet_number: last_pn, +1684 pad_datagram, +1685 }; +1686 } +1687 +1688 self.populate_packet(now, space_id, path_id, scheduling_info, &mut builder); +1689 +1690 // ACK-only packets should only be sent when explicitly allowed. If we write them due to +1691 // any other reason, there is a bug which leads to one component announcing write +1692 // readiness while not writing any data. This degrades performance. The condition is +1693 // only checked if the full MTU is available and when potentially large fixed-size +1694 // frames aren't queued, so that lack of space in the datagram isn't the reason for just +1695 // writing ACKs. +1696 debug_assert!( +1697 !(builder.sent_frames().is_ack_only(&self.streams) +1698 && !can_send.acks +1699 && (can_send.other || can_send.space_specific) +1700 && builder.buf.segment_size() +1701 == self.path_data(path_id).current_mtu() as usize +1702 && self.datagrams.outgoing.is_empty()), +1703 "SendableFrames was {can_send:?}, but only ACKs have been written" +1704 ); +1705 if builder.sent_frames().requires_padding { +1706 pad_datagram |= PadDatagram::ToMinMtu; +1707 } +1708 +1709 for (path_id, _pn) in builder.sent_frames().largest_acked.iter() { +1710 self.spaces[space_id] +1711 .for_path(*path_id) +1712 .pending_acks +1713 .acks_sent(); +1714 self.timers.stop( +1715 Timer::PerPath(*path_id, PathTimer::MaxAckDelay), +1716 self.qlog.with_time(now), +1717 ); +1718 } +1719 +1720 // Now we need to finish the packet. Before we do so we need to know if we will +1721 // be coalescing the next packet into this one, or will be ending the datagram +1722 // as well. Because if this is the last packet in the datagram more padding +1723 // might be needed because of the packet type, or to fill the GSO segment size. +1724 +1725 // Are we allowed to coalesce AND is there enough space for another *packet* in +1726 // this datagram AND will we definitely send another packet? +1727 if builder.can_coalesce && path_id == PathId::ZERO && { +1728 let max_packet_size = builder +1729 .buf +1730 .datagram_remaining_mut() +1731 .saturating_sub(builder.predict_packet_end()); +1732 max_packet_size > MIN_PACKET_SPACE +1733 && self.has_pending_packet(space_id, max_packet_size, connection_close_pending) +1734 } { +1735 // We can append/coalesce the next packet into the current +1736 // datagram. Finish the current packet without adding extra padding. +1737 trace!("will coalesce with next packet"); +1738 builder.finish_and_track(now, self, path_id, PadDatagram::No); +1739 } else { +1740 // We need a new datagram for the next packet. Finish the current +1741 // packet with padding. +1742 // TODO(flub): if there isn't any more data to be sent, this will still pad +1743 // to the segment size and only discover there is nothing to send before +1744 // starting the next packet. That is wasting up to 32 bytes. +1745 if builder.buf.num_datagrams() > 1 && matches!(pad_datagram, PadDatagram::No) { +1746 // If too many padding bytes would be required to continue the +1747 // GSO batch after this packet, end the GSO batch here. Ensures +1748 // that fixed-size frames with heterogeneous sizes +1749 // (e.g. application datagrams) won't inadvertently waste large +1750 // amounts of bandwidth. The exact threshold is a bit arbitrary +1751 // and might benefit from further tuning, though there's no +1752 // universally optimal value. +1753 const MAX_PADDING: usize = 32; +1754 if builder.buf.datagram_remaining_mut() +1755 > builder.predict_packet_end() + MAX_PADDING +1756 { +1757 trace!( +1758 "GSO truncated by demand for {} padding bytes", +1759 builder.buf.datagram_remaining_mut() - builder.predict_packet_end() +1760 ); +1761 let last_pn = builder.packet_number; +1762 builder.finish_and_track(now, self, path_id, PadDatagram::No); +1763 return PollPathSpaceStatus::Send { +1764 last_packet_number: last_pn, +1765 }; +1766 } +1767 +1768 // Pad the current datagram to GSO segment size so it can be +1769 // included in the GSO batch. +1770 builder.finish_and_track(now, self, path_id, PadDatagram::ToSegmentSize); +1771 } else { +1772 builder.finish_and_track(now, self, path_id, pad_datagram); +1773 } +1774 +1775 // If this is the first datagram we set the segment size to the size of the +1776 // first datagram. +1777 if transmit.num_datagrams() == 1 { +1778 transmit.clip_segment_size(); +1779 } +1780 } +1781 } +1782 } +1783 +1784 fn poll_transmit_mtu_probe( +1785 &mut self, +1786 now: Instant, +1787 buf: &mut Vec<u8>, +1788 path_id: PathId, +1789 ) -> Option<Transmit> { +1790 let (active_cid, probe_size) = self.get_mtu_probe_data(now, path_id)?; 1791 -1792 let mut builder = -1793 PacketBuilder::new(now, SpaceId::Data, path_id, active_cid, &mut transmit, self)?; -1794 -1795 // We implement MTU probes as ping packets padded up to the probe size -1796 trace!(?probe_size, "writing MTUD probe"); -1797 builder.write_frame(frame::Ping, &mut self.path_stats.for_path(path_id).frame_tx); +1792 // We are definitely sending a DPLPMTUD probe. +1793 let mut transmit = TransmitBuf::new(buf, NonZeroUsize::MIN, probe_size as usize); +1794 transmit.start_new_datagram_with_size(probe_size as usize); +1795 +1796 let mut builder = +1797 PacketBuilder::new(now, SpaceId::Data, path_id, active_cid, &mut transmit, self)?; 1798 -1799 // If supported by the peer, we want no delays to the probe's ACK -1800 if self.peer_supports_ack_frequency() { -1801 builder.write_frame( -1802 frame::ImmediateAck, -1803 &mut self.path_stats.for_path(path_id).frame_tx, -1804 ); -1805 } -1806 -1807 builder.finish_and_track(now, self, path_id, PadDatagram::ToSize(probe_size)); -1808 -1809 self.path_stats.for_path(path_id).sent_plpmtud_probes += 1; +1799 // We implement MTU probes as ping packets padded up to the probe size +1800 trace!(?probe_size, "writing MTUD probe"); +1801 builder.write_frame(frame::Ping, &mut self.path_stats.for_path(path_id).frame_tx); +1802 +1803 // If supported by the peer, we want no delays to the probe's ACK +1804 if self.peer_supports_ack_frequency() { +1805 builder.write_frame( +1806 frame::ImmediateAck, +1807 &mut self.path_stats.for_path(path_id).frame_tx, +1808 ); +1809 } 1810 -1811 Some(self.build_transmit(path_id, transmit)) -1812 } -1813 -1814 /// Returns the CID and probe size if a DPLPMTUD probe is needed. -1815 /// -1816 /// We MTU probe all paths for which all of the following is true: -1817 /// - We have an active destination CID for the path. -1818 /// - The remote address *and* path are validated. -1819 /// - The path is not abandoned. -1820 /// - The MTU Discovery subsystem wants to probe the path. -1821 fn get_mtu_probe_data(&mut self, now: Instant, path_id: PathId) -> Option<(ConnectionId, u16)> { -1822 let active_cid = self.remote_cids.get(&path_id).map(CidQueue::active)?; -1823 let is_eligible = self.path_data(path_id).validated -1824 && !self.path_data(path_id).is_validating_path() -1825 && !self.abandoned_paths.contains(&path_id); -1826 -1827 if !is_eligible { -1828 return None; -1829 } -1830 let next_pn = self.spaces[SpaceId::Data] -1831 .for_path(path_id) -1832 .peek_tx_number(); -1833 let probe_size = self -1834 .path_data_mut(path_id) -1835 .mtud -1836 .poll_transmit(now, next_pn)?; -1837 -1838 Some((active_cid, probe_size)) -1839 } -1840 -1841 /// Returns true if there is a further packet to send on [`PathId::ZERO`]. -1842 /// -1843 /// In other words this is predicting whether the next call to -1844 /// [`Connection::space_can_send`] issued will return some frames to be sent. Including -1845 /// having to predict which packet number space it will be invoked with. This depends on -1846 /// how both [`Connection::poll_transmit_on_path`] and -1847 /// [`Connection::poll_transmit_path_space`] behave. -1848 /// -1849 /// This is needed to determine if packet coalescing can happen. Because the last packet -1850 /// in a datagram may need to be padded and thus we must know if another packet will -1851 /// follow or not. +1811 builder.finish_and_track(now, self, path_id, PadDatagram::ToSize(probe_size)); +1812 +1813 self.path_stats.for_path(path_id).sent_plpmtud_probes += 1; +1814 +1815 Some(self.build_transmit(path_id, transmit)) +1816 } +1817 +1818 /// Returns the CID and probe size if a DPLPMTUD probe is needed. +1819 /// +1820 /// We MTU probe all paths for which all of the following is true: +1821 /// - We have an active destination CID for the path. +1822 /// - The remote address *and* path are validated. +1823 /// - The path is not abandoned. +1824 /// - The MTU Discovery subsystem wants to probe the path. +1825 fn get_mtu_probe_data(&mut self, now: Instant, path_id: PathId) -> Option<(ConnectionId, u16)> { +1826 let active_cid = self.remote_cids.get(&path_id).map(CidQueue::active)?; +1827 let is_eligible = self.path_data(path_id).validated +1828 && !self.path_data(path_id).is_validating_path() +1829 && !self.abandoned_paths.contains(&path_id); +1830 +1831 if !is_eligible { +1832 return None; +1833 } +1834 let next_pn = self.spaces[SpaceId::Data] +1835 .for_path(path_id) +1836 .peek_tx_number(); +1837 let probe_size = self +1838 .path_data_mut(path_id) +1839 .mtud +1840 .poll_transmit(now, next_pn)?; +1841 +1842 Some((active_cid, probe_size)) +1843 } +1844 +1845 /// Returns true if there is a further packet to send on [`PathId::ZERO`]. +1846 /// +1847 /// In other words this is predicting whether the next call to +1848 /// [`Connection::space_can_send`] issued will return some frames to be sent. Including +1849 /// having to predict which packet number space it will be invoked with. This depends on +1850 /// how both [`Connection::poll_transmit_on_path`] and +1851 /// [`Connection::poll_transmit_path_space`] behave. 1852 /// -1853 /// The next packet can be either in the same space, or in one of the following spaces -1854 /// on the same path. Because a 0-RTT packet can be coalesced with a 1-RTT packet and -1855 /// both are in the Data(PathId::ZERO) space. Previous spaces are not checked, because -1856 /// packets are built from Initial to Handshake to Data spaces. -1857 fn has_pending_packet( -1858 &mut self, -1859 current_space_id: SpaceId, -1860 max_packet_size: usize, -1861 connection_close_pending: bool, -1862 ) -> bool { -1863 let mut space_id = current_space_id; -1864 loop { -1865 let can_send = self.space_can_send( -1866 space_id, -1867 PathId::ZERO, -1868 max_packet_size, -1869 connection_close_pending, -1870 ); -1871 if !can_send.is_empty() { -1872 return true; -1873 } -1874 match space_id.next() { -1875 Some(next_space_id) => space_id = next_space_id, -1876 None => break, +1853 /// This is needed to determine if packet coalescing can happen. Because the last packet +1854 /// in a datagram may need to be padded and thus we must know if another packet will +1855 /// follow or not. +1856 /// +1857 /// The next packet can be either in the same space, or in one of the following spaces +1858 /// on the same path. Because a 0-RTT packet can be coalesced with a 1-RTT packet and +1859 /// both are in the Data(PathId::ZERO) space. Previous spaces are not checked, because +1860 /// packets are built from Initial to Handshake to Data spaces. +1861 fn has_pending_packet( +1862 &mut self, +1863 current_space_id: SpaceId, +1864 max_packet_size: usize, +1865 connection_close_pending: bool, +1866 ) -> bool { +1867 let mut space_id = current_space_id; +1868 loop { +1869 let can_send = self.space_can_send( +1870 space_id, +1871 PathId::ZERO, +1872 max_packet_size, +1873 connection_close_pending, +1874 ); +1875 if !can_send.is_empty() { +1876 return true; 1877 } -1878 } -1879 false -1880 } -1881 -1882 /// Checks if creating a new datagram would be blocked by congestion control -1883 fn path_congestion_check( -1884 &mut self, -1885 space_id: SpaceId, -1886 path_id: PathId, -1887 transmit: &TransmitBuf<'_>, -1888 can_send: &SendableFrames, -1889 now: Instant, -1890 ) -> PathBlocked { -1891 // Anti-amplification is only based on `total_sent`, which gets updated after -1892 // the transmit is sent. Therefore we pass the amount of bytes for datagrams -1893 // that are already created, as well as 1 byte for starting another datagram. If -1894 // there is any anti-amplification budget left, we always allow a full MTU to be -1895 // sent (see https://github.com/quinn-rs/quinn/issues/1082). -1896 if self.side().is_server() -1897 && self -1898 .path_data(path_id) -1899 .anti_amplification_blocked(transmit.len() as u64 + 1) -1900 { -1901 trace!(?space_id, %path_id, "blocked by anti-amplification"); -1902 return PathBlocked::AntiAmplification; -1903 } -1904 -1905 // Congestion control check. -1906 // Tail loss probes must not be blocked by congestion, or a deadlock could arise. -1907 let bytes_to_send = transmit.segment_size() as u64; -1908 let need_loss_probe = self.spaces[space_id].for_path(path_id).loss_probes > 0; -1909 -1910 if can_send.other && !need_loss_probe && !can_send.close { -1911 let path = self.path_data(path_id); -1912 if path.in_flight.bytes + bytes_to_send >= path.congestion.window() { -1913 trace!( -1914 ?space_id, -1915 %path_id, -1916 in_flight=%path.in_flight.bytes, -1917 congestion_window=%path.congestion.window(), -1918 "blocked by congestion control", -1919 ); -1920 return PathBlocked::Congestion; -1921 } -1922 } -1923 -1924 // Pacing check. -1925 if let Some(delay) = self.path_data_mut(path_id).pacing_delay(bytes_to_send, now) { -1926 let resume_time = now + delay; -1927 self.timers.set( -1928 Timer::PerPath(path_id, PathTimer::Pacing), -1929 resume_time, -1930 self.qlog.with_time(now), -1931 ); -1932 // Loss probes and CONNECTION_CLOSE should be subject to pacing, even though -1933 // they are not congestion controlled. -1934 trace!(?space_id, %path_id, ?delay, "blocked by pacing"); -1935 return PathBlocked::Pacing; -1936 } -1937 -1938 PathBlocked::No -1939 } -1940 -1941 /// Send PATH_CHALLENGE for a previous path if necessary -1942 /// -1943 /// QUIC-TRANSPORT section 9.3.3 -1944 /// <https://www.rfc-editor.org/rfc/rfc9000.html#name-off-path-packet-forwarding> -1945 fn send_prev_path_challenge( -1946 &mut self, -1947 now: Instant, -1948 buf: &mut Vec<u8>, -1949 path_id: PathId, -1950 ) -> Option<Transmit> { -1951 let (prev_cid, prev_path) = self.paths.get_mut(&path_id)?.prev.as_mut()?; -1952 if !prev_path.pending_on_path_challenge { -1953 return None; -1954 }; -1955 prev_path.pending_on_path_challenge = false; -1956 let token = self.rng.random(); -1957 let network_path = prev_path.network_path; -1958 prev_path.record_path_challenge_sent(now, token, network_path); -1959 -1960 debug_assert_eq!( -1961 self.highest_space, -1962 SpaceKind::Data, -1963 "PATH_CHALLENGE queued without 1-RTT keys" -1964 ); -1965 let buf = &mut TransmitBuf::new(buf, NonZeroUsize::MIN, MIN_INITIAL_SIZE.into()); -1966 buf.start_new_datagram(); -1967 -1968 // Use the previous CID to avoid linking the new path with the previous path. We -1969 // don't bother accounting for possible retirement of that prev_cid because this is -1970 // sent once, immediately after migration, when the CID is known to be valid. Even -1971 // if a post-migration packet caused the CID to be retired, it's fair to pretend -1972 // this is sent first. -1973 let mut builder = PacketBuilder::new(now, SpaceId::Data, path_id, *prev_cid, buf, self)?; -1974 let challenge = frame::PathChallenge(token); -1975 let stats = &mut self.path_stats.for_path(path_id).frame_tx; -1976 builder.write_frame_with_log_msg(challenge, stats, Some("validating previous path")); -1977 -1978 // An endpoint MUST expand datagrams that contain a PATH_CHALLENGE frame -1979 // to at least the smallest allowed maximum datagram size of 1200 bytes, -1980 // unless the anti-amplification limit for the path does not permit -1981 // sending a datagram of this size -1982 builder.pad_to(MIN_INITIAL_SIZE); -1983 -1984 builder.finish(self, now); -1985 self.path_stats -1986 .for_path(path_id) -1987 .udp_tx -1988 .on_sent(1, buf.len()); -1989 -1990 Some(Transmit { -1991 destination: network_path.remote, -1992 size: buf.len(), -1993 ecn: None, -1994 segment_size: None, -1995 src_ip: network_path.local_ip, -1996 }) -1997 } -1998 -1999 fn send_off_path_path_response( -2000 &mut self, -2001 now: Instant, -2002 buf: &mut Vec<u8>, -2003 path_id: PathId, -2004 ) -> Option<Transmit> { -2005 let path = self.paths.get_mut(&path_id).map(|state| &mut state.data)?; -2006 let cid_queue = self.remote_cids.get_mut(&path_id)?; -2007 let (token, network_path) = path.path_responses.pop_off_path(path.network_path)?; +1878 match space_id.next() { +1879 Some(next_space_id) => space_id = next_space_id, +1880 None => break, +1881 } +1882 } +1883 false +1884 } +1885 +1886 /// Checks if creating a new datagram would be blocked by congestion control +1887 fn path_congestion_check( +1888 &mut self, +1889 space_id: SpaceId, +1890 path_id: PathId, +1891 transmit: &TransmitBuf<'_>, +1892 can_send: &SendableFrames, +1893 now: Instant, +1894 ) -> PathBlocked { +1895 // Anti-amplification is only based on `total_sent`, which gets updated after +1896 // the transmit is sent. Therefore we pass the amount of bytes for datagrams +1897 // that are already created, as well as 1 byte for starting another datagram. If +1898 // there is any anti-amplification budget left, we always allow a full MTU to be +1899 // sent (see https://github.com/quinn-rs/quinn/issues/1082). +1900 if self.side().is_server() +1901 && self +1902 .path_data(path_id) +1903 .anti_amplification_blocked(transmit.len() as u64 + 1) +1904 { +1905 trace!(?space_id, %path_id, "blocked by anti-amplification"); +1906 return PathBlocked::AntiAmplification; +1907 } +1908 +1909 // Congestion control check. +1910 // Tail loss probes must not be blocked by congestion, or a deadlock could arise. +1911 let bytes_to_send = transmit.segment_size() as u64; +1912 let need_loss_probe = self.spaces[space_id].for_path(path_id).loss_probes > 0; +1913 +1914 if can_send.other && !need_loss_probe && !can_send.close { +1915 let path = self.path_data(path_id); +1916 if path.in_flight.bytes + bytes_to_send >= path.congestion.window() { +1917 trace!( +1918 ?space_id, +1919 %path_id, +1920 in_flight=%path.in_flight.bytes, +1921 congestion_window=%path.congestion.window(), +1922 "blocked by congestion control", +1923 ); +1924 return PathBlocked::Congestion; +1925 } +1926 } +1927 +1928 // Pacing check. +1929 if let Some(delay) = self.path_data_mut(path_id).pacing_delay(bytes_to_send, now) { +1930 let resume_time = now + delay; +1931 self.timers.set( +1932 Timer::PerPath(path_id, PathTimer::Pacing), +1933 resume_time, +1934 self.qlog.with_time(now), +1935 ); +1936 // Loss probes and CONNECTION_CLOSE should be subject to pacing, even though +1937 // they are not congestion controlled. +1938 trace!(?space_id, %path_id, ?delay, "blocked by pacing"); +1939 return PathBlocked::Pacing; +1940 } +1941 +1942 PathBlocked::No +1943 } +1944 +1945 /// Send PATH_CHALLENGE for a previous path if necessary +1946 /// +1947 /// QUIC-TRANSPORT section 9.3.3 +1948 /// <https://www.rfc-editor.org/rfc/rfc9000.html#name-off-path-packet-forwarding> +1949 fn send_prev_path_challenge( +1950 &mut self, +1951 now: Instant, +1952 buf: &mut Vec<u8>, +1953 path_id: PathId, +1954 ) -> Option<Transmit> { +1955 let (prev_cid, prev_path) = self.paths.get_mut(&path_id)?.prev.as_mut()?; +1956 if !prev_path.pending_on_path_challenge { +1957 return None; +1958 }; +1959 prev_path.pending_on_path_challenge = false; +1960 let token = self.rng.random(); +1961 let network_path = prev_path.network_path; +1962 prev_path.record_path_challenge_sent(now, token, network_path); +1963 +1964 debug_assert_eq!( +1965 self.highest_space, +1966 SpaceKind::Data, +1967 "PATH_CHALLENGE queued without 1-RTT keys" +1968 ); +1969 let buf = &mut TransmitBuf::new(buf, NonZeroUsize::MIN, MIN_INITIAL_SIZE.into()); +1970 buf.start_new_datagram(); +1971 +1972 // Use the previous CID to avoid linking the new path with the previous path. We +1973 // don't bother accounting for possible retirement of that prev_cid because this is +1974 // sent once, immediately after migration, when the CID is known to be valid. Even +1975 // if a post-migration packet caused the CID to be retired, it's fair to pretend +1976 // this is sent first. +1977 let mut builder = PacketBuilder::new(now, SpaceId::Data, path_id, *prev_cid, buf, self)?; +1978 let challenge = frame::PathChallenge(token); +1979 let stats = &mut self.path_stats.for_path(path_id).frame_tx; +1980 builder.write_frame_with_log_msg(challenge, stats, Some("validating previous path")); +1981 +1982 // An endpoint MUST expand datagrams that contain a PATH_CHALLENGE frame +1983 // to at least the smallest allowed maximum datagram size of 1200 bytes, +1984 // unless the anti-amplification limit for the path does not permit +1985 // sending a datagram of this size +1986 builder.pad_to(MIN_INITIAL_SIZE); +1987 +1988 builder.finish(self, now); +1989 self.path_stats +1990 .for_path(path_id) +1991 .udp_tx +1992 .on_sent(1, buf.len()); +1993 +1994 trace!( +1995 dst = ?network_path.remote, +1996 src = ?network_path.local_ip, +1997 len = buf.len(), +1998 "sending prev_path off-path challenge", +1999 ); +2000 Some(Transmit { +2001 destination: network_path.remote, +2002 size: buf.len(), +2003 ecn: None, +2004 segment_size: None, +2005 src_ip: network_path.local_ip, +2006 }) +2007 } 2008 -2009 let cid = cid_queue -2010 .next_reserved() -2011 .unwrap_or_else(|| cid_queue.active()); -2012 // TODO(@divma): we should take a different approach when there is no fresh CID to use. -2013 // https://github.com/quinn-rs/quinn/issues/2184 -2014 -2015 let frame = frame::PathResponse(token); -2016 -2017 let buf = &mut TransmitBuf::new(buf, NonZeroUsize::MIN, MIN_INITIAL_SIZE.into()); -2018 buf.start_new_datagram(); -2019 -2020 let mut builder = PacketBuilder::new(now, SpaceId::Data, path_id, cid, buf, self)?; -2021 let stats = &mut self.path_stats.for_path(path_id).frame_tx; -2022 builder.write_frame_with_log_msg(frame, stats, Some("(off-path)")); -2023 // Off-path: not tracked in congestion control. The packet is sent to a -2024 // different destination than path_id's network path. -2025 builder.pad_to(MIN_INITIAL_SIZE); -2026 builder.finish(self, now); -2027 -2028 let size = buf.len(); +2009 fn send_off_path_path_response( +2010 &mut self, +2011 now: Instant, +2012 buf: &mut Vec<u8>, +2013 path_id: PathId, +2014 ) -> Option<Transmit> { +2015 let path = self.paths.get_mut(&path_id).map(|state| &mut state.data)?; +2016 let cid_queue = self.remote_cids.get_mut(&path_id)?; +2017 let (token, network_path) = path.path_responses.pop_off_path(path.network_path)?; +2018 +2019 let cid = cid_queue +2020 .next_reserved() +2021 .unwrap_or_else(|| cid_queue.active()); +2022 // TODO(@divma): we should take a different approach when there is no fresh CID to use. +2023 // https://github.com/quinn-rs/quinn/issues/2184 +2024 +2025 let frame = frame::PathResponse(token); +2026 +2027 let buf = &mut TransmitBuf::new(buf, NonZeroUsize::MIN, MIN_INITIAL_SIZE.into()); +2028 buf.start_new_datagram(); 2029 -2030 self.path_stats.for_path(path_id).udp_tx.on_sent(1, size); -2031 Some(Transmit { -2032 destination: network_path.remote, -2033 size, -2034 ecn: None, -2035 segment_size: None, -2036 src_ip: network_path.local_ip, -2037 }) -2038 } -2039 -2040 /// Send a nat traversal challenge (off-path) on this path if possible. -2041 fn send_nat_traversal_path_challenge( -2042 &mut self, -2043 now: Instant, -2044 buf: &mut Vec<u8>, -2045 path_id: PathId, -2046 ) -> Option<Transmit> { -2047 let remote = self -2048 .n0_nat_traversal -2049 .server_side_mut() -2050 .ok()? -2051 .next_probe_addr()?; -2052 -2053 if !self.paths.get(&path_id)?.data.validated { -2054 // Path is not usable for probing -2055 return None; -2056 } -2057 -2058 // TODO: Using the active CID here makes the paths linkable. This is a violation of -2059 // RFC9000 but something we want to accept in the short term. Eventually we aim -2060 // to fix up the supply of CIDs sufficiently so that we can keep paths unlinkable -2061 // again. -2062 let Some(cid) = self -2063 .remote_cids -2064 .get(&path_id) -2065 .map(|cid_queue| cid_queue.active()) -2066 else { -2067 trace!(%path_id, "Not sending NAT traversal probe for path with no CIDs"); -2068 return None; -2069 }; -2070 let token = self.rng.random(); -2071 -2072 let frame = frame::PathChallenge(token); -2073 -2074 let mut buf = TransmitBuf::new(buf, NonZeroUsize::MIN, MIN_INITIAL_SIZE.into()); -2075 buf.start_new_datagram(); -2076 -2077 let mut builder = PacketBuilder::new(now, SpaceId::Data, path_id, cid, &mut buf, self)?; -2078 let stats = &mut self.path_stats.for_path(path_id).frame_tx; -2079 builder.write_frame_with_log_msg(frame, stats, Some("(nat-traversal)")); -2080 // Off-path: not tracked in congestion control. The packet is sent to a -2081 // different destination than path_id's network path. -2082 builder.finish(self, now); +2030 let mut builder = PacketBuilder::new(now, SpaceId::Data, path_id, cid, buf, self)?; +2031 let stats = &mut self.path_stats.for_path(path_id).frame_tx; +2032 builder.write_frame_with_log_msg(frame, stats, Some("(off-path)")); +2033 // Off-path: not tracked in congestion control. The packet is sent to a +2034 // different destination than path_id's network path. +2035 builder.pad_to(MIN_INITIAL_SIZE); +2036 builder.finish(self, now); +2037 +2038 let size = buf.len(); +2039 self.path_stats.for_path(path_id).udp_tx.on_sent(1, size); +2040 +2041 trace!( +2042 dst = ?network_path.remote, +2043 src = ?network_path.local_ip, +2044 len = buf.len(), +2045 "sending off-path PATH_RESPONSE", +2046 ); +2047 Some(Transmit { +2048 destination: network_path.remote, +2049 size, +2050 ecn: None, +2051 segment_size: None, +2052 src_ip: network_path.local_ip, +2053 }) +2054 } +2055 +2056 /// Send a nat traversal challenge (off-path) on this path if possible. +2057 fn send_nat_traversal_path_challenge( +2058 &mut self, +2059 now: Instant, +2060 buf: &mut Vec<u8>, +2061 path_id: PathId, +2062 ) -> Option<Transmit> { +2063 let remote = self.n0_nat_traversal.next_probe_addr()?; +2064 +2065 if !self.paths.get(&path_id)?.data.validated { +2066 // Path is not usable for probing +2067 return None; +2068 } +2069 +2070 // TODO: Using the active CID here makes the paths linkable. This is a violation of +2071 // RFC9000 but something we want to accept in the short term. Eventually we aim +2072 // to fix up the supply of CIDs sufficiently so that we can keep paths unlinkable +2073 // again. +2074 let Some(cid) = self +2075 .remote_cids +2076 .get(&path_id) +2077 .map(|cid_queue| cid_queue.active()) +2078 else { +2079 trace!(%path_id, "Not sending NAT traversal probe for path with no CIDs"); +2080 return None; +2081 }; +2082 let token = self.rng.random(); 2083 -2084 // Mark as sent after packet build succeeds. -2085 if let Ok(server_state) = self.n0_nat_traversal.server_side_mut() { -2086 server_state.mark_probe_sent((remote.ip(), remote.port()), token); -2087 } +2084 let frame = frame::PathChallenge(token); +2085 +2086 let mut buf = TransmitBuf::new(buf, NonZeroUsize::MIN, MIN_INITIAL_SIZE.into()); +2087 buf.start_new_datagram(); 2088 -2089 let size = buf.len(); -2090 -2091 self.path_stats.for_path(path_id).udp_tx.on_sent(1, size); -2092 -2093 Some(Transmit { -2094 destination: remote, -2095 size, -2096 ecn: None, -2097 segment_size: None, -2098 src_ip: None, -2099 }) -2100 } +2089 let mut builder = PacketBuilder::new(now, SpaceId::Data, path_id, cid, &mut buf, self)?; +2090 let stats = &mut self.path_stats.for_path(path_id).frame_tx; +2091 builder.write_frame_with_log_msg(frame, stats, Some("(nat-traversal)")); +2092 // Off-path: not tracked in congestion control. The packet is sent to a +2093 // different destination than path_id's network path. +2094 builder.finish(self, now); +2095 +2096 // Mark as sent after packet build succeeds. +2097 self.n0_nat_traversal.mark_probe_sent(remote, token); +2098 +2099 let size = buf.len(); +2100 self.path_stats.for_path(path_id).udp_tx.on_sent(1, size); 2101 -2102 /// Indicate what types of frames are ready to send for the given space. -2103 /// -2104 /// Only for on-path data. -2105 /// -2106 /// *packet_size* is the number of bytes available to build the next packet. -2107 /// *connection_close_pending* indicates whether a CONNECTION_CLOSE frame needs to be -2108 /// sent. -2109 fn space_can_send( -2110 &mut self, -2111 space_id: SpaceId, -2112 path_id: PathId, -2113 packet_size: usize, -2114 connection_close_pending: bool, -2115 ) -> SendableFrames { -2116 let space = &mut self.spaces[space_id]; -2117 let space_has_crypto = self.crypto_state.has_keys(space_id.encryption_level()); -2118 -2119 if !space_has_crypto -2120 && (space_id != SpaceId::Data -2121 || !self.crypto_state.has_keys(EncryptionLevel::ZeroRtt) -2122 || self.side.is_server()) -2123 { -2124 // Nothing to send in this space -2125 return SendableFrames::empty(); -2126 } -2127 -2128 let mut can_send = space.can_send(path_id, &self.streams); -2129 -2130 // Check for 1RTT space. -2131 if space_id == SpaceId::Data { -2132 let pn = space.for_path(path_id).peek_tx_number(); -2133 // Number of bytes available for frames if this is a 1-RTT packet. We're -2134 // guaranteed to be able to send an individual frame at least this large in the -2135 // next 1-RTT packet. This could be generalized to support every space, but it's -2136 // only needed to handle large fixed-size frames, which only exist in 1-RTT -2137 // (application datagrams). -2138 let frame_space_1rtt = -2139 packet_size.saturating_sub(self.predict_1rtt_overhead(pn, path_id)); -2140 can_send |= self.can_send_1rtt(path_id, frame_space_1rtt); -2141 } -2142 -2143 can_send.close = connection_close_pending && space_has_crypto; -2144 -2145 can_send -2146 } -2147 -2148 /// Process `ConnectionEvent`s generated by the associated `Endpoint` -2149 /// -2150 /// Will execute protocol logic upon receipt of a connection event, in turn preparing signals -2151 /// (including application `Event`s, `EndpointEvent`s and outgoing datagrams) that should be -2152 /// extracted through the relevant methods. -2153 pub fn handle_event(&mut self, event: ConnectionEvent) { -2154 use ConnectionEventInner::*; -2155 match event.0 { -2156 Datagram(DatagramConnectionEvent { -2157 now, -2158 network_path, -2159 path_id, -2160 ecn, -2161 first_decode, -2162 remaining, -2163 }) => { -2164 let span = trace_span!("pkt", %path_id); -2165 let _guard = span.enter(); -2166 -2167 if self.early_discard_packet(network_path, path_id) { -2168 // A return value of true indicates we should discard this packet. -2169 return; -2170 } -2171 -2172 let was_anti_amplification_blocked = self -2173 .path(path_id) -2174 .map(|path| path.anti_amplification_blocked(1)) -2175 // We never tried to send on an non-existing (new) path so have not been -2176 // anti-amplification blocked for it previously. -2177 .unwrap_or(false); -2178 -2179 let rx = &mut self.path_stats.for_path(path_id).udp_rx; -2180 rx.datagrams += 1; -2181 rx.bytes += first_decode.len() as u64; -2182 let data_len = first_decode.len(); -2183 -2184 self.handle_decode(now, network_path, path_id, ecn, first_decode); -2185 // The current `path` might have changed inside `handle_decode` since the packet -2186 // could have triggered a migration. The packet might also belong to an unknown -2187 // path and have been rejected. Make sure the data received is accounted for the -2188 // most recent path by accessing `path` after `handle_decode`. -2189 if let Some(path) = self.path_mut(path_id) { -2190 path.inc_total_recvd(data_len as u64); -2191 } -2192 -2193 if let Some(data) = remaining { -2194 self.path_stats.for_path(path_id).udp_rx.bytes += data.len() as u64; -2195 self.handle_coalesced(now, network_path, path_id, ecn, data); -2196 } -2197 -2198 if let Some(path) = self.paths.get_mut(&path_id) { -2199 self.qlog -2200 .emit_recovery_metrics(path_id, &mut path.data, now); +2102 trace!(dst = ?remote, len = buf.len(), "sending off-path NAT probe"); +2103 Some(Transmit { +2104 destination: remote.into(), +2105 size, +2106 ecn: None, +2107 segment_size: None, +2108 src_ip: None, +2109 }) +2110 } +2111 +2112 /// Indicate what types of frames are ready to send for the given space. +2113 /// +2114 /// Only for on-path data. +2115 /// +2116 /// *packet_size* is the number of bytes available to build the next packet. +2117 /// *connection_close_pending* indicates whether a CONNECTION_CLOSE frame needs to be +2118 /// sent. +2119 fn space_can_send( +2120 &mut self, +2121 space_id: SpaceId, +2122 path_id: PathId, +2123 packet_size: usize, +2124 connection_close_pending: bool, +2125 ) -> SendableFrames { +2126 let space = &mut self.spaces[space_id]; +2127 let space_has_crypto = self.crypto_state.has_keys(space_id.encryption_level()); +2128 +2129 if !space_has_crypto +2130 && (space_id != SpaceId::Data +2131 || !self.crypto_state.has_keys(EncryptionLevel::ZeroRtt) +2132 || self.side.is_server()) +2133 { +2134 // Nothing to send in this space +2135 return SendableFrames::empty(); +2136 } +2137 +2138 let mut can_send = space.can_send(path_id, &self.streams); +2139 +2140 // Check for 1RTT space. +2141 if space_id == SpaceId::Data { +2142 let pn = space.for_path(path_id).peek_tx_number(); +2143 // Number of bytes available for frames if this is a 1-RTT packet. We're +2144 // guaranteed to be able to send an individual frame at least this large in the +2145 // next 1-RTT packet. This could be generalized to support every space, but it's +2146 // only needed to handle large fixed-size frames, which only exist in 1-RTT +2147 // (application datagrams). +2148 let frame_space_1rtt = +2149 packet_size.saturating_sub(self.predict_1rtt_overhead(pn, path_id)); +2150 can_send |= self.can_send_1rtt(path_id, frame_space_1rtt); +2151 } +2152 +2153 can_send.close = connection_close_pending && space_has_crypto; +2154 +2155 can_send +2156 } +2157 +2158 /// Process `ConnectionEvent`s generated by the associated `Endpoint` +2159 /// +2160 /// Will execute protocol logic upon receipt of a connection event, in turn preparing signals +2161 /// (including application `Event`s, `EndpointEvent`s and outgoing datagrams) that should be +2162 /// extracted through the relevant methods. +2163 pub fn handle_event(&mut self, event: ConnectionEvent) { +2164 use ConnectionEventInner::*; +2165 match event.0 { +2166 Datagram(DatagramConnectionEvent { +2167 now, +2168 network_path, +2169 path_id, +2170 ecn, +2171 first_decode, +2172 remaining, +2173 }) => { +2174 let span = trace_span!("pkt", %path_id); +2175 let _guard = span.enter(); +2176 +2177 if self.early_discard_packet(network_path, path_id) { +2178 // A return value of true indicates we should discard this packet. +2179 return; +2180 } +2181 +2182 let was_anti_amplification_blocked = self +2183 .path(path_id) +2184 .map(|path| path.anti_amplification_blocked(1)) +2185 // We never tried to send on an non-existing (new) path so have not been +2186 // anti-amplification blocked for it previously. +2187 .unwrap_or(false); +2188 +2189 let rx = &mut self.path_stats.for_path(path_id).udp_rx; +2190 rx.datagrams += 1; +2191 rx.bytes += first_decode.len() as u64; +2192 let data_len = first_decode.len(); +2193 +2194 self.handle_decode(now, network_path, path_id, ecn, first_decode); +2195 // The current `path` might have changed inside `handle_decode` since the packet +2196 // could have triggered a migration. The packet might also belong to an unknown +2197 // path and have been rejected. Make sure the data received is accounted for the +2198 // most recent path by accessing `path` after `handle_decode`. +2199 if let Some(path) = self.path_mut(path_id) { +2200 path.inc_total_recvd(data_len as u64); 2201 } 2202 -2203 if was_anti_amplification_blocked { -2204 // A prior attempt to set the loss detection timer may have failed due to -2205 // anti-amplification, so ensure it's set now. Prevents a handshake deadlock if -2206 // the server's first flight is lost. -2207 self.set_loss_detection_timer(now, path_id); -2208 } -2209 } -2210 NewIdentifiers(ids, now, cid_len, cid_lifetime) => { -2211 let path_id = ids.first().map(|issued| issued.path_id).unwrap_or_default(); -2212 debug_assert!(ids.iter().all(|issued| issued.path_id == path_id)); -2213 let cid_state = self -2214 .local_cid_state -2215 .entry(path_id) -2216 .or_insert_with(|| CidState::new(cid_len, cid_lifetime, now, 0)); -2217 cid_state.new_cids(&ids, now); -2218 -2219 ids.into_iter().rev().for_each(|frame| { -2220 self.spaces[SpaceId::Data].pending.new_cids.push(frame); -2221 }); -2222 // Always update Timer::PushNewCid -2223 self.reset_cid_retirement(now); -2224 } -2225 } -2226 } -2227 -2228 /// Returns whether a packet can be discarded early. -2229 /// -2230 /// Packets sent on the wrong network path can be entirely ignored, saving further -2231 /// processing. -2232 /// -2233 /// Returns true if a packet coming in for this `path_id` over given `network_path` -2234 /// should be discarded. -2235 fn early_discard_packet(&mut self, network_path: FourTuple, path_id: PathId) -> bool { -2236 if self.is_handshaking() && path_id != PathId::ZERO { -2237 debug!(%network_path, %path_id, "discarding multipath packet during handshake"); -2238 return true; -2239 } +2203 if let Some(data) = remaining { +2204 self.path_stats.for_path(path_id).udp_rx.bytes += data.len() as u64; +2205 self.handle_coalesced(now, network_path, path_id, ecn, data); +2206 } +2207 +2208 if let Some(path) = self.paths.get_mut(&path_id) { +2209 self.qlog +2210 .emit_recovery_metrics(path_id, &mut path.data, now); +2211 } +2212 +2213 if was_anti_amplification_blocked { +2214 // A prior attempt to set the loss detection timer may have failed due to +2215 // anti-amplification, so ensure it's set now. Prevents a handshake deadlock if +2216 // the server's first flight is lost. +2217 self.set_loss_detection_timer(now, path_id); +2218 } +2219 } +2220 NewIdentifiers(ids, now, cid_len, cid_lifetime) => { +2221 let path_id = ids.first().map(|issued| issued.path_id).unwrap_or_default(); +2222 debug_assert!(ids.iter().all(|issued| issued.path_id == path_id)); +2223 +2224 // Path may have been abandoned while this reply was in flight, +2225 // retire the CIDs instead of queuing them. +2226 if self.abandoned_paths.contains(&path_id) { +2227 if !self.state.is_drained() { +2228 for issued in &ids { +2229 self.endpoint_events +2230 .push_back(EndpointEventInner::RetireConnectionId( +2231 now, +2232 path_id, +2233 issued.sequence, +2234 false, +2235 )); +2236 } +2237 } +2238 return; +2239 } 2240 -2241 // TODO(flub): In RFC9000 the server is allowed to send off-path probing packets -2242 // once the client has been probing such a 4-tuple. These probes are currently -2243 // not yet recognised and discarded here. -2244 // See https://github.com/n0-computer/noq/issues/607. -2245 let remote_may_migrate = self.remote_may_migrate(); +2241 let cid_state = self +2242 .local_cid_state +2243 .entry(path_id) +2244 .or_insert_with(|| CidState::new(cid_len, cid_lifetime, now, 0)); +2245 cid_state.new_cids(&ids, now); 2246 -2247 let local_ip_may_migrate = self.local_ip_may_migrate(); -2248 -2249 // If this packet could initiate a migration and we're a client or a server that -2250 // forbids migration, drop the datagram. This could be relaxed to heuristically -2251 // permit NAT-rebinding-like migration. -2252 if let Some(known_path) = self.path_mut(path_id) { -2253 if network_path.remote != known_path.network_path.remote && !remote_may_migrate { -2254 trace!( -2255 %path_id, -2256 %network_path, -2257 %known_path.network_path, -2258 "discarding packet from unrecognized peer" -2259 ); -2260 return true; -2261 } -2262 -2263 if known_path.network_path.local_ip.is_some() -2264 && network_path.local_ip.is_some() -2265 && known_path.network_path.local_ip != network_path.local_ip -2266 && !local_ip_may_migrate -2267 { -2268 trace!( -2269 %path_id, -2270 %network_path, -2271 %known_path.network_path, -2272 "discarding packet sent to incorrect interface" -2273 ); -2274 return true; -2275 } -2276 } -2277 false -2278 } -2279 -2280 /// Whether a remote is allowed to migrate. -2281 /// -2282 /// QUIC relies on stable endpoints during the handshake. So other than the server's -2283 /// preferred_address transport parameter no side may migrate before the handshake is -2284 /// completed. -2285 /// -2286 /// In RFC9000 only the client may migrate. If QNT is negotiated the server may migrate -2287 /// as well. -2288 /// -2289 /// Additionally for iroh we allow the server to migrate once during the handshake as -2290 /// long as the client has not received an authenticated Handshake packet. This allows -2291 /// us to duplicate client Initial packets to multiple destinations. See -2292 /// [`state::Handshake::allow_server_migration`]. -2293 fn remote_may_migrate(&self) -> bool { -2294 match &self.side { -2295 ConnectionSide::Server { server_config } => { -2296 server_config.migration && self.is_handshake_confirmed() -2297 } -2298 ConnectionSide::Client { .. } => { -2299 if let Some(hs) = self.state.as_handshake() { -2300 hs.allow_server_migration -2301 } else { -2302 self.n0_nat_traversal.is_negotiated() && self.is_handshake_confirmed() -2303 } -2304 } -2305 } -2306 } +2247 ids.into_iter().rev().for_each(|frame| { +2248 self.spaces[SpaceId::Data].pending.new_cids.push(frame); +2249 }); +2250 // Always update Timer::PushNewCid +2251 self.reset_cid_retirement(now); +2252 } +2253 } +2254 } +2255 +2256 /// Returns whether a packet can be discarded early. +2257 /// +2258 /// Packets sent on the wrong network path can be entirely ignored, saving further +2259 /// processing. +2260 /// +2261 /// Returns true if a packet coming in for this `path_id` over given `network_path` +2262 /// should be discarded. +2263 fn early_discard_packet(&mut self, network_path: FourTuple, path_id: PathId) -> bool { +2264 if self.is_handshaking() && path_id != PathId::ZERO { +2265 debug!(%network_path, %path_id, "discarding multipath packet during handshake"); +2266 return true; +2267 } +2268 +2269 // TODO(flub): In RFC9000 the server is allowed to send off-path probing packets +2270 // once the client has been probing such a 4-tuple. These probes are currently +2271 // not yet recognised and discarded here. +2272 // See https://github.com/n0-computer/noq/issues/607. +2273 let remote_may_migrate = self.remote_may_migrate(); +2274 +2275 let local_ip_may_migrate = self.local_ip_may_migrate(); +2276 +2277 // If this packet could initiate a migration and we're a client or a server that +2278 // forbids migration, drop the datagram. This could be relaxed to heuristically +2279 // permit NAT-rebinding-like migration. +2280 if let Some(known_path) = self.path_mut(path_id) { +2281 if network_path.remote != known_path.network_path.remote && !remote_may_migrate { +2282 trace!( +2283 %path_id, +2284 %network_path, +2285 %known_path.network_path, +2286 "discarding packet from unrecognized peer" +2287 ); +2288 return true; +2289 } +2290 +2291 if known_path.network_path.local_ip.is_some() +2292 && network_path.local_ip.is_some() +2293 && known_path.network_path.local_ip != network_path.local_ip +2294 && !local_ip_may_migrate +2295 { +2296 trace!( +2297 %path_id, +2298 %network_path, +2299 %known_path.network_path, +2300 "discarding packet sent to incorrect interface" +2301 ); +2302 return true; +2303 } +2304 } +2305 false +2306 } 2307 -2308 /// Whether our local IP address is allowed to change with new incoming packets. +2308 /// Whether a remote is allowed to migrate. 2309 /// -2310 /// Incoming packets show us the local IP address we received a packet on, which could -2311 /// be different from what we thought due to e.g. NAT rebinding or moving from mobile -2312 /// data to WiFi without being notified of the network change. +2310 /// QUIC relies on stable endpoints during the handshake. So other than the server's +2311 /// preferred_address transport parameter no side may migrate before the handshake is +2312 /// completed. 2313 /// -2314 /// This is only allowed to happen after the handshake is confirmed and when we are the -2315 /// client. Unless QNT is negotiated in which case the server is also allowed to -2316 /// migrate. -2317 /// -2318 /// Be aware that probing packets, which do not exist in Multipath without QNT, are -2319 /// exempt from this. -2320 fn local_ip_may_migrate(&self) -> bool { -2321 (self.side.is_client() || self.n0_nat_traversal.is_negotiated()) -2322 && self.is_handshake_confirmed() -2323 } -2324 /// Process timer expirations -2325 /// -2326 /// Executes protocol logic, potentially preparing signals (including application `Event`s, -2327 /// `EndpointEvent`s and outgoing datagrams) that should be extracted through the relevant -2328 /// methods. -2329 /// -2330 /// It is most efficient to call this immediately after the system clock reaches the latest -2331 /// `Instant` that was output by `poll_timeout`; however spurious extra calls will simply -2332 /// no-op and therefore are safe. -2333 pub fn handle_timeout(&mut self, now: Instant) { -2334 while let Some((timer, _time)) = self.timers.expire_before(now, &self.qlog) { -2335 let span = match timer { -2336 Timer::Conn(timer) => trace_span!("timeout", scope = "conn", ?timer), -2337 Timer::PerPath(path_id, timer) => { -2338 trace_span!("timer_fired", scope="path", %path_id, ?timer) -2339 } -2340 }; -2341 let _guard = span.enter(); -2342 trace!("timeout"); -2343 match timer { -2344 Timer::Conn(timer) => match timer { -2345 ConnTimer::Close => { -2346 self.state.move_to_drained(None); -2347 // move_to_drained checks that we weren't in drained before. -2348 // Adding events to endpoint_events is only legal if `Drained` was never queued before. -2349 self.endpoint_events.push_back(EndpointEventInner::Drained); -2350 } -2351 ConnTimer::Idle => { -2352 self.kill(ConnectionError::TimedOut); -2353 } -2354 ConnTimer::KeepAlive => { -2355 self.ping(); -2356 } -2357 ConnTimer::KeyDiscard => { -2358 self.crypto_state.discard_temporary_keys(); -2359 } -2360 ConnTimer::PushNewCid => { -2361 while let Some((path_id, when)) = self.next_cid_retirement() { -2362 if when > now { -2363 break; -2364 } -2365 match self.local_cid_state.get_mut(&path_id) { -2366 None => error!(%path_id, "No local CID state for path"), -2367 Some(cid_state) => { -2368 // Update `retire_prior_to` field in NEW_CONNECTION_ID frame -2369 let num_new_cid = cid_state.on_cid_timeout().into(); -2370 if !self.state.is_closed() { -2371 trace!( -2372 "push a new CID to peer RETIRE_PRIOR_TO field {}", -2373 cid_state.retire_prior_to() -2374 ); -2375 self.endpoint_events.push_back( -2376 EndpointEventInner::NeedIdentifiers( -2377 path_id, -2378 now, -2379 num_new_cid, -2380 ), -2381 ); -2382 } -2383 } -2384 } -2385 } -2386 } -2387 ConnTimer::NoAvailablePath => { -2388 // Grace period expired: all paths were abandoned and no new path -2389 // was opened. Close the connection. There are no paths left to -2390 // send CONNECTION_CLOSE on, so this is a silent close. -2391 // https://www.ietf.org/archive/id/draft-ietf-quic-multipath-21.html#section-3.4-8 -2392 if self.state.is_closed() || self.state.is_drained() { -2393 // Connection already closing/drained (e.g. application called -2394 // close() before the grace timer fired). Nothing to do. -2395 error!("no viable path timer fired, but connection already closing"); -2396 } else { -2397 trace!("no viable path grace period expired, closing connection"); -2398 let err = TransportError::NO_VIABLE_PATH( -2399 "last path abandoned, no new path opened", -2400 ); -2401 self.close_common(); -2402 self.set_close_timer(now); -2403 self.connection_close_pending = true; -2404 self.state.move_to_closed(err); -2405 } -2406 } -2407 ConnTimer::NatTraversalProbeRetry => { -2408 if let Ok(server_state) = self.n0_nat_traversal.server_side_mut() -2409 && server_state.queue_retries() -2410 { -2411 let delay = -2412 RttEstimator::new(self.config.initial_rtt).pto_base() * 2 / 3; -2413 self.timers.set( -2414 Timer::Conn(ConnTimer::NatTraversalProbeRetry), -2415 now + delay, -2416 self.qlog.with_time(now), -2417 ); -2418 trace!("off-path probe retry timer fired, re-queued probes"); -2419 } -2420 } -2421 }, -2422 Timer::PerPath(path_id, timer) => { -2423 match timer { -2424 PathTimer::PathIdle => { -2425 if let Err(err) = -2426 self.close_path_inner(now, path_id, PathAbandonReason::TimedOut) -2427 { -2428 warn!(?err, "failed closing path"); -2429 } -2430 } -2431 -2432 PathTimer::PathKeepAlive => { -2433 self.ping_path(path_id).ok(); -2434 } -2435 PathTimer::LossDetection => { -2436 self.on_loss_detection_timeout(now, path_id); -2437 self.qlog.emit_recovery_metrics( -2438 path_id, -2439 &mut self.paths.get_mut(&path_id).unwrap().data, -2440 now, -2441 ); -2442 } -2443 PathTimer::PathValidationFailed => { -2444 let Some(path) = self.paths.get_mut(&path_id) else { -2445 continue; -2446 }; -2447 self.timers.stop( -2448 Timer::PerPath(path_id, PathTimer::PathChallengeLost), -2449 self.qlog.with_time(now), -2450 ); -2451 debug!("path migration validation failed"); -2452 if let Some((_, prev)) = path.prev.take() { -2453 path.data = prev; -2454 } -2455 path.data.reset_on_path_challenges(); -2456 } -2457 PathTimer::PathChallengeLost => { -2458 let Some(path) = self.paths.get_mut(&path_id) else { -2459 continue; -2460 }; -2461 trace!("path challenge deemed lost"); -2462 path.data.pending_on_path_challenge = true; -2463 } -2464 PathTimer::AbandonFromValidation => { -2465 let Some(path) = self.paths.get_mut(&path_id) else { -2466 continue; -2467 }; -2468 path.data.reset_on_path_challenges(); -2469 self.timers.stop( -2470 Timer::PerPath(path_id, PathTimer::PathChallengeLost), -2471 self.qlog.with_time(now), -2472 ); -2473 debug!("new path validation failed"); -2474 if let Err(err) = self.close_path_inner( -2475 now, -2476 path_id, -2477 PathAbandonReason::ValidationFailed, -2478 ) { -2479 warn!(?err, "failed closing path"); -2480 } -2481 } -2482 PathTimer::Pacing => {} -2483 PathTimer::MaxAckDelay => { -2484 // This timer is only armed in the Data space -2485 self.spaces[SpaceId::Data] -2486 .for_path(path_id) -2487 .pending_acks -2488 .on_max_ack_delay_timeout() -2489 } -2490 PathTimer::PathDrained => { -2491 // The path was abandoned and 3*PTO has expired since. Clean up all -2492 // remaining state and install stateless reset token. -2493 self.timers.stop_per_path(path_id, self.qlog.with_time(now)); -2494 if let Some(local_cid_state) = self.local_cid_state.remove(&path_id) { -2495 debug_assert!(!self.state.is_drained()); // requirement for endpoint_events. All timers should be cleared in drained connections. -2496 let (min_seq, max_seq) = local_cid_state.active_seq(); -2497 for seq in min_seq..=max_seq { -2498 self.endpoint_events.push_back( -2499 EndpointEventInner::RetireConnectionId( -2500 now, path_id, seq, false, -2501 ), -2502 ); -2503 } -2504 } -2505 self.discard_path(path_id, now); -2506 } -2507 } -2508 } -2509 } -2510 } -2511 } -2512 -2513 /// Close a connection immediately -2514 /// -2515 /// This does not ensure delivery of outstanding data. It is the application's responsibility to -2516 /// call this only when all important communications have been completed, e.g. by calling -2517 /// [`SendStream::finish`] on outstanding streams and waiting for the corresponding -2518 /// [`StreamEvent::Finished`] event. -2519 /// -2520 /// If [`Streams::send_streams`] returns 0, all outstanding stream data has been -2521 /// delivered. There may still be data from the peer that has not been received. -2522 /// -2523 /// [`StreamEvent::Finished`]: crate::StreamEvent::Finished -2524 pub fn close(&mut self, now: Instant, error_code: VarInt, reason: Bytes) { -2525 self.close_inner( -2526 now, -2527 Close::Application(frame::ApplicationClose { error_code, reason }), -2528 ) -2529 } -2530 -2531 /// Close the connection immediately, initiated by an API call. -2532 /// -2533 /// This will not produce a [`ConnectionLost`] event propagated by the -2534 /// [`Connection::poll`] call, because the API call already propagated the error to the -2535 /// user. -2536 /// -2537 /// Not to be used when entering immediate close due to an internal state change based -2538 /// on an event. See [`State::move_to_closed_local`] for details. -2539 /// -2540 /// This initiates immediate close from -2541 /// <https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2>, moving to the closed -2542 /// state. -2543 /// -2544 /// [`ConnectionLost`]: crate::Event::ConnectionLost -2545 /// [`Connection::poll`]: super::Connection::poll -2546 fn close_inner(&mut self, now: Instant, reason: Close) { -2547 let was_closed = self.state.is_closed(); -2548 if !was_closed { -2549 self.close_common(); -2550 self.set_close_timer(now); -2551 self.connection_close_pending = true; -2552 self.state.move_to_closed_local(reason); -2553 } -2554 } -2555 -2556 /// Control datagrams -2557 pub fn datagrams(&mut self) -> Datagrams<'_> { -2558 Datagrams { conn: self } -2559 } -2560 -2561 /// Returns connection statistics -2562 pub fn stats(&mut self) -> ConnectionStats { -2563 let mut stats = self.partial_stats.clone(); -2564 -2565 for path_stats in self.path_stats.iter_stats() { -2566 // Self::path_stats() computes the path rtt, cwnd and current_mtu on access -2567 // because they are not simple counters. When computing the connection stats we -2568 // can skip that effort since those fields are not used in the `impl -2569 // Add<PathStats> for ConnectionStats`. -2570 stats += *path_stats; -2571 } -2572 -2573 stats -2574 } -2575 -2576 /// Returns path statistics -2577 pub fn path_stats(&mut self, path_id: PathId) -> Option<PathStats> { -2578 let path = self.paths.get(&path_id)?; -2579 let stats = self.path_stats.for_path(path_id); -2580 stats.rtt = path.data.rtt.get(); -2581 stats.cwnd = path.data.congestion.window(); -2582 stats.current_mtu = path.data.mtud.current_mtu(); -2583 Some(*stats) -2584 } -2585 -2586 /// Ping the remote endpoint -2587 /// -2588 /// Causes an ACK-eliciting packet to be transmitted on the connection. -2589 pub fn ping(&mut self) { -2590 // TODO(flub): This is very brute-force: it pings *all* the paths. Instead it would -2591 // be nice if we could only send a single packet for this. -2592 for path_data in self.spaces[self.highest_space].number_spaces.values_mut() { -2593 path_data.ping_pending = true; -2594 } -2595 } +2314 /// In RFC9000 only the client may migrate. If QNT is negotiated the server may migrate +2315 /// as well. +2316 /// +2317 /// Additionally for iroh we allow the server to migrate once during the handshake as +2318 /// long as the client has not received an authenticated Handshake packet. This allows +2319 /// us to duplicate client Initial packets to multiple destinations. See +2320 /// [`state::Handshake::allow_server_migration`]. +2321 fn remote_may_migrate(&self) -> bool { +2322 match &self.side { +2323 ConnectionSide::Server { server_config } => { +2324 server_config.migration && self.is_handshake_confirmed() +2325 } +2326 ConnectionSide::Client { .. } => { +2327 if let Some(hs) = self.state.as_handshake() { +2328 hs.allow_server_migration +2329 } else { +2330 self.n0_nat_traversal.is_negotiated() && self.is_handshake_confirmed() +2331 } +2332 } +2333 } +2334 } +2335 +2336 /// Whether our local IP address is allowed to change with new incoming packets. +2337 /// +2338 /// Incoming packets show us the local IP address we received a packet on, which could +2339 /// be different from what we thought due to e.g. NAT rebinding or moving from mobile +2340 /// data to WiFi without being notified of the network change. +2341 /// +2342 /// This is only allowed to happen after the handshake is confirmed and when we are the +2343 /// client. Unless QNT is negotiated in which case the server is also allowed to +2344 /// migrate. +2345 /// +2346 /// Be aware that probing packets, which do not exist in Multipath without QNT, are +2347 /// exempt from this. +2348 fn local_ip_may_migrate(&self) -> bool { +2349 (self.side.is_client() || self.n0_nat_traversal.is_negotiated()) +2350 && self.is_handshake_confirmed() +2351 } +2352 /// Process timer expirations +2353 /// +2354 /// Executes protocol logic, potentially preparing signals (including application `Event`s, +2355 /// `EndpointEvent`s and outgoing datagrams) that should be extracted through the relevant +2356 /// methods. +2357 /// +2358 /// It is most efficient to call this immediately after the system clock reaches the latest +2359 /// `Instant` that was output by `poll_timeout`; however spurious extra calls will simply +2360 /// no-op and therefore are safe. +2361 pub fn handle_timeout(&mut self, now: Instant) { +2362 while let Some((timer, _time)) = self.timers.expire_before(now, &self.qlog) { +2363 let span = match timer { +2364 Timer::Conn(timer) => trace_span!("timeout", scope = "conn", ?timer), +2365 Timer::PerPath(path_id, timer) => { +2366 trace_span!("timer_fired", scope="path", %path_id, ?timer) +2367 } +2368 }; +2369 let _guard = span.enter(); +2370 trace!("timeout"); +2371 match timer { +2372 Timer::Conn(timer) => match timer { +2373 ConnTimer::Close => { +2374 self.state.move_to_drained(None); +2375 // move_to_drained checks that we weren't in drained before. +2376 // Adding events to endpoint_events is only legal if `Drained` was never queued before. +2377 self.endpoint_events.push_back(EndpointEventInner::Drained); +2378 } +2379 ConnTimer::Idle => { +2380 self.kill(ConnectionError::TimedOut); +2381 } +2382 ConnTimer::KeepAlive => { +2383 self.ping(); +2384 } +2385 ConnTimer::KeyDiscard => { +2386 self.crypto_state.discard_temporary_keys(); +2387 } +2388 ConnTimer::PushNewCid => { +2389 while let Some((path_id, when)) = self.next_cid_retirement() { +2390 if when > now { +2391 break; +2392 } +2393 match self.local_cid_state.get_mut(&path_id) { +2394 None => error!(%path_id, "No local CID state for path"), +2395 Some(cid_state) => { +2396 // Update `retire_prior_to` field in NEW_CONNECTION_ID frame +2397 let num_new_cid = cid_state.on_cid_timeout().into(); +2398 if !self.state.is_closed() { +2399 trace!( +2400 "push a new CID to peer RETIRE_PRIOR_TO field {}", +2401 cid_state.retire_prior_to() +2402 ); +2403 self.endpoint_events.push_back( +2404 EndpointEventInner::NeedIdentifiers( +2405 path_id, +2406 now, +2407 num_new_cid, +2408 ), +2409 ); +2410 } +2411 } +2412 } +2413 } +2414 } +2415 ConnTimer::NoAvailablePath => { +2416 // Grace period expired: all paths were abandoned and no new path +2417 // was opened. Close the connection. There are no paths left to +2418 // send CONNECTION_CLOSE on, so this is a silent close. +2419 // https://www.ietf.org/archive/id/draft-ietf-quic-multipath-21.html#section-3.4-8 +2420 if self.state.is_closed() || self.state.is_drained() { +2421 // Connection already closing/drained (e.g. application called +2422 // close() before the grace timer fired). Nothing to do. +2423 error!("no viable path timer fired, but connection already closing"); +2424 } else { +2425 trace!("no viable path grace period expired, closing connection"); +2426 let err = TransportError::NO_VIABLE_PATH( +2427 "last path abandoned, no new path opened", +2428 ); +2429 self.close_common(); +2430 self.set_close_timer(now); +2431 self.connection_close_pending = true; +2432 self.state.move_to_closed(err); +2433 } +2434 } +2435 ConnTimer::NatTraversalProbeRetry => { +2436 if self.n0_nat_traversal.queue_retries(self.is_ipv6()) { +2437 let delay = +2438 RttEstimator::new(self.config.initial_rtt).pto_base() * 2 / 3; +2439 self.timers.set( +2440 Timer::Conn(ConnTimer::NatTraversalProbeRetry), +2441 now + delay, +2442 self.qlog.with_time(now), +2443 ); +2444 trace!("re-queued NAT probes"); +2445 } else { +2446 trace!("no more NAT probes remaining"); +2447 } +2448 } +2449 }, +2450 Timer::PerPath(path_id, timer) => { +2451 match timer { +2452 PathTimer::PathIdle => { +2453 if let Err(err) = +2454 self.close_path_inner(now, path_id, PathAbandonReason::TimedOut) +2455 { +2456 warn!(?err, "failed closing path"); +2457 } +2458 } +2459 +2460 PathTimer::PathKeepAlive => { +2461 self.ping_path(path_id).ok(); +2462 } +2463 PathTimer::LossDetection => { +2464 self.on_loss_detection_timeout(now, path_id); +2465 self.qlog.emit_recovery_metrics( +2466 path_id, +2467 &mut self +2468 .paths +2469 .get_mut(&path_id) +2470 .expect("loss-detection timer fires only on live paths") +2471 .data, +2472 now, +2473 ); +2474 } +2475 PathTimer::PathValidationFailed => { +2476 let Some(path) = self.paths.get_mut(&path_id) else { +2477 continue; +2478 }; +2479 self.timers.stop( +2480 Timer::PerPath(path_id, PathTimer::PathChallengeLost), +2481 self.qlog.with_time(now), +2482 ); +2483 debug!("path migration validation failed"); +2484 if let Some((_, prev)) = path.prev.take() { +2485 path.data = prev; +2486 } +2487 path.data.reset_on_path_challenges(); +2488 } +2489 PathTimer::PathChallengeLost => { +2490 let Some(path) = self.paths.get_mut(&path_id) else { +2491 continue; +2492 }; +2493 trace!("path challenge deemed lost"); +2494 path.data.pending_on_path_challenge = true; +2495 } +2496 PathTimer::AbandonFromValidation => { +2497 let Some(path) = self.paths.get_mut(&path_id) else { +2498 continue; +2499 }; +2500 path.data.reset_on_path_challenges(); +2501 self.timers.stop( +2502 Timer::PerPath(path_id, PathTimer::PathChallengeLost), +2503 self.qlog.with_time(now), +2504 ); +2505 debug!("new path validation failed"); +2506 if let Err(err) = self.close_path_inner( +2507 now, +2508 path_id, +2509 PathAbandonReason::ValidationFailed, +2510 ) { +2511 warn!(?err, "failed closing path"); +2512 } +2513 } +2514 PathTimer::Pacing => {} +2515 PathTimer::MaxAckDelay => { +2516 // This timer is only armed in the Data space +2517 self.spaces[SpaceId::Data] +2518 .for_path(path_id) +2519 .pending_acks +2520 .on_max_ack_delay_timeout() +2521 } +2522 PathTimer::PathDrained => { +2523 // The path was abandoned and 3*PTO has expired since. Clean up all +2524 // remaining state and install stateless reset token. +2525 self.timers.stop_per_path(path_id, self.qlog.with_time(now)); +2526 if let Some(local_cid_state) = self.local_cid_state.remove(&path_id) { +2527 debug_assert!(!self.state.is_drained()); // requirement for endpoint_events. All timers should be cleared in drained connections. +2528 let (min_seq, max_seq) = local_cid_state.active_seq(); +2529 for seq in min_seq..=max_seq { +2530 self.endpoint_events.push_back( +2531 EndpointEventInner::RetireConnectionId( +2532 now, path_id, seq, false, +2533 ), +2534 ); +2535 } +2536 } +2537 self.discard_path(path_id, now); +2538 } +2539 } +2540 } +2541 } +2542 } +2543 } +2544 +2545 /// Close a connection immediately +2546 /// +2547 /// This does not ensure delivery of outstanding data. It is the application's responsibility to +2548 /// call this only when all important communications have been completed, e.g. by calling +2549 /// [`SendStream::finish`] on outstanding streams and waiting for the corresponding +2550 /// [`StreamEvent::Finished`] event. +2551 /// +2552 /// If [`Streams::send_streams`] returns 0, all outstanding stream data has been +2553 /// delivered. There may still be data from the peer that has not been received. +2554 /// +2555 /// [`StreamEvent::Finished`]: crate::StreamEvent::Finished +2556 pub fn close(&mut self, now: Instant, error_code: VarInt, reason: Bytes) { +2557 self.close_inner( +2558 now, +2559 Close::Application(frame::ApplicationClose { error_code, reason }), +2560 ) +2561 } +2562 +2563 /// Close the connection immediately, initiated by an API call. +2564 /// +2565 /// This will not produce a [`ConnectionLost`] event propagated by the +2566 /// [`Connection::poll`] call, because the API call already propagated the error to the +2567 /// user. +2568 /// +2569 /// Not to be used when entering immediate close due to an internal state change based +2570 /// on an event. See [`State::move_to_closed_local`] for details. +2571 /// +2572 /// This initiates immediate close from +2573 /// <https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2>, moving to the closed +2574 /// state. +2575 /// +2576 /// [`ConnectionLost`]: crate::Event::ConnectionLost +2577 /// [`Connection::poll`]: super::Connection::poll +2578 fn close_inner(&mut self, now: Instant, reason: Close) { +2579 let was_closed = self.state.is_closed(); +2580 if !was_closed { +2581 self.close_common(); +2582 self.set_close_timer(now); +2583 self.connection_close_pending = true; +2584 self.state.move_to_closed_local(reason); +2585 } +2586 } +2587 +2588 /// Control datagrams +2589 pub fn datagrams(&mut self) -> Datagrams<'_> { +2590 Datagrams { conn: self } +2591 } +2592 +2593 /// Returns connection statistics +2594 pub fn stats(&mut self) -> ConnectionStats { +2595 let mut stats = self.partial_stats.clone(); 2596 -2597 /// Ping the remote endpoint over a specific path -2598 /// -2599 /// Causes an ACK-eliciting packet to be transmitted on the path. -2600 pub fn ping_path(&mut self, path: PathId) -> Result<(), ClosedPath> { -2601 let path_data = self.spaces[self.highest_space] -2602 .number_spaces -2603 .get_mut(&path) -2604 .ok_or(ClosedPath { _private: () })?; -2605 path_data.ping_pending = true; -2606 Ok(()) -2607 } -2608 -2609 /// Update traffic keys spontaneously -2610 /// -2611 /// This can be useful for testing key updates, as they otherwise only happen infrequently. -2612 pub fn force_key_update(&mut self) { -2613 if !self.state.is_established() { -2614 debug!("ignoring forced key update in illegal state"); -2615 return; -2616 } -2617 if self.crypto_state.prev_crypto.is_some() { -2618 // We already just updated, or are currently updating, the keys. Concurrent key updates -2619 // are illegal. -2620 debug!("ignoring redundant forced key update"); -2621 return; -2622 } -2623 self.crypto_state.update_keys(None, false); -2624 } -2625 -2626 /// Get a session reference -2627 pub fn crypto_session(&self) -> &dyn crypto::Session { -2628 self.crypto_state.session.as_ref() -2629 } -2630 -2631 /// Whether the connection is in the process of being established -2632 /// -2633 /// If this returns `false`, the connection may be either established or closed, signaled by the -2634 /// emission of a [`Connected`](Event::Connected) or [`ConnectionLost`](Event::ConnectionLost) -2635 /// event respectively. Note that locally-initiated closes via [`close()`](Self::close) do not -2636 /// emit a `ConnectionLost` event. -2637 /// -2638 /// For an established connection this essentially means the handshake is **completed**, -2639 /// but not necessarily yet confirmed. -2640 pub fn is_handshaking(&self) -> bool { -2641 self.state.is_handshake() -2642 } -2643 -2644 /// Whether the connection is closed -2645 /// -2646 /// Closed connections cannot transport any further data. A connection becomes closed when -2647 /// either peer application intentionally closes it, or when either transport layer detects an -2648 /// error such as a time-out or certificate validation failure. -2649 /// -2650 /// A [`ConnectionLost`](Event::ConnectionLost) event is emitted with details when the -2651 /// connection is closed by the peer or due to an error. When the local application closes -2652 /// the connection via [`close()`](Self::close), no `ConnectionLost` event is emitted; -2653 /// instead, pending operations fail with [`ConnectionError::LocallyClosed`]. -2654 pub fn is_closed(&self) -> bool { -2655 self.state.is_closed() +2597 for path_stats in self.path_stats.iter_stats() { +2598 // Self::path_stats() computes the path rtt, cwnd and current_mtu on access +2599 // because they are not simple counters. When computing the connection stats we +2600 // can skip that effort since those fields are not used in the `impl +2601 // Add<PathStats> for ConnectionStats`. +2602 stats += *path_stats; +2603 } +2604 +2605 stats +2606 } +2607 +2608 /// Returns path statistics +2609 pub fn path_stats(&mut self, path_id: PathId) -> Option<PathStats> { +2610 let path = self.paths.get(&path_id)?; +2611 let stats = self.path_stats.for_path(path_id); +2612 stats.rtt = path.data.rtt.get(); +2613 stats.cwnd = path.data.congestion.window(); +2614 stats.current_mtu = path.data.mtud.current_mtu(); +2615 Some(*stats) +2616 } +2617 +2618 /// Ping the remote endpoint +2619 /// +2620 /// Causes an ACK-eliciting packet to be transmitted on the connection. +2621 pub fn ping(&mut self) { +2622 // TODO(flub): This is very brute-force: it pings *all* the paths. Instead it would +2623 // be nice if we could only send a single packet for this. +2624 for path_data in self.spaces[self.highest_space].number_spaces.values_mut() { +2625 path_data.ping_pending = true; +2626 } +2627 } +2628 +2629 /// Ping the remote endpoint over a specific path +2630 /// +2631 /// Causes an ACK-eliciting packet to be transmitted on the path. +2632 pub fn ping_path(&mut self, path: PathId) -> Result<(), ClosedPath> { +2633 let path_data = self.spaces[self.highest_space] +2634 .number_spaces +2635 .get_mut(&path) +2636 .ok_or(ClosedPath { _private: () })?; +2637 path_data.ping_pending = true; +2638 Ok(()) +2639 } +2640 +2641 /// Update traffic keys spontaneously +2642 /// +2643 /// This can be useful for testing key updates, as they otherwise only happen infrequently. +2644 pub fn force_key_update(&mut self) { +2645 if !self.state.is_established() { +2646 debug!("ignoring forced key update in illegal state"); +2647 return; +2648 } +2649 if self.crypto_state.prev_crypto.is_some() { +2650 // We already just updated, or are currently updating, the keys. Concurrent key updates +2651 // are illegal. +2652 debug!("ignoring redundant forced key update"); +2653 return; +2654 } +2655 self.crypto_state.update_keys(None, false); 2656 } 2657 -2658 /// Whether there is no longer any need to keep the connection around -2659 /// -2660 /// Closed connections become drained after a brief timeout to absorb any remaining in-flight -2661 /// packets from the peer. All drained connections have been closed. -2662 pub fn is_drained(&self) -> bool { -2663 self.state.is_drained() -2664 } -2665 -2666 /// For clients, if the peer accepted the 0-RTT data packets -2667 /// -2668 /// The value is meaningless until after the handshake completes. -2669 pub fn accepted_0rtt(&self) -> bool { -2670 self.crypto_state.accepted_0rtt -2671 } -2672 -2673 /// Whether 0-RTT is/was possible during the handshake -2674 pub fn has_0rtt(&self) -> bool { -2675 self.crypto_state.zero_rtt_enabled -2676 } -2677 -2678 /// Whether there are any pending retransmits -2679 pub fn has_pending_retransmits(&self) -> bool { -2680 !self.spaces[SpaceId::Data].pending.is_empty(&self.streams) -2681 } -2682 -2683 /// Look up whether we're the client or server of this Connection -2684 pub fn side(&self) -> Side { -2685 self.side.side() -2686 } -2687 -2688 /// Get the address observed by the remote over the given path -2689 pub fn path_observed_address(&self, path_id: PathId) -> Result<Option<SocketAddr>, ClosedPath> { -2690 self.path(path_id) -2691 .map(|path_data| { -2692 path_data -2693 .last_observed_addr_report -2694 .as_ref() -2695 .map(|observed| observed.socket_addr()) -2696 }) -2697 .ok_or(ClosedPath { _private: () }) -2698 } -2699 -2700 /// Current best estimate of this connection's latency (round-trip-time) -2701 pub fn rtt(&self, path_id: PathId) -> Option<Duration> { -2702 self.path(path_id).map(|d| d.rtt.get()) +2658 /// Get a session reference +2659 pub fn crypto_session(&self) -> &dyn crypto::Session { +2660 self.crypto_state.session.as_ref() +2661 } +2662 +2663 /// Whether the connection is in the process of being established +2664 /// +2665 /// If this returns `false`, the connection may be either established or closed, signaled by the +2666 /// emission of a [`Connected`](Event::Connected) or [`ConnectionLost`](Event::ConnectionLost) +2667 /// event respectively. Note that locally-initiated closes via [`close()`](Self::close) do not +2668 /// emit a `ConnectionLost` event. +2669 /// +2670 /// For an established connection this essentially means the handshake is **completed**, +2671 /// but not necessarily yet confirmed. +2672 pub fn is_handshaking(&self) -> bool { +2673 self.state.is_handshake() +2674 } +2675 +2676 /// Whether the connection is closed +2677 /// +2678 /// Closed connections cannot transport any further data. A connection becomes closed when +2679 /// either peer application intentionally closes it, or when either transport layer detects an +2680 /// error such as a time-out or certificate validation failure. +2681 /// +2682 /// A [`ConnectionLost`](Event::ConnectionLost) event is emitted with details when the +2683 /// connection is closed by the peer or due to an error. When the local application closes +2684 /// the connection via [`close()`](Self::close), no `ConnectionLost` event is emitted; +2685 /// instead, pending operations fail with [`ConnectionError::LocallyClosed`]. +2686 pub fn is_closed(&self) -> bool { +2687 self.state.is_closed() +2688 } +2689 +2690 /// Whether there is no longer any need to keep the connection around +2691 /// +2692 /// Closed connections become drained after a brief timeout to absorb any remaining in-flight +2693 /// packets from the peer. All drained connections have been closed. +2694 pub fn is_drained(&self) -> bool { +2695 self.state.is_drained() +2696 } +2697 +2698 /// For clients, if the peer accepted the 0-RTT data packets +2699 /// +2700 /// The value is meaningless until after the handshake completes. +2701 pub fn accepted_0rtt(&self) -> bool { +2702 self.crypto_state.accepted_0rtt 2703 } 2704 -2705 /// Current state of this connection's congestion controller, for debugging purposes -2706 pub fn congestion_state(&self, path_id: PathId) -> Option<&dyn Controller> { -2707 self.path(path_id).map(|d| d.congestion.as_ref()) +2705 /// Whether 0-RTT is/was possible during the handshake +2706 pub fn has_0rtt(&self) -> bool { +2707 self.crypto_state.zero_rtt_enabled 2708 } 2709 -2710 /// Modify the number of remotely initiated streams that may be concurrently open -2711 /// -2712 /// No streams may be opened by the peer unless fewer than `count` are already open. Large -2713 /// `count`s increase both minimum and worst-case memory consumption. -2714 pub fn set_max_concurrent_streams(&mut self, dir: Dir, count: VarInt) { -2715 self.streams.set_max_concurrent(dir, count); -2716 // If the limit was reduced, then a flow control update previously deemed insignificant may -2717 // now be significant. -2718 let pending = &mut self.spaces[SpaceId::Data].pending; -2719 self.streams.queue_max_stream_id(pending); -2720 } -2721 -2722 /// Modify the number of open paths allowed when multipath is enabled -2723 /// -2724 /// When reducing the number of concurrent paths this will only affect delaying sending -2725 /// new MAX_PATH_ID frames until fewer than this number of paths are possible. To -2726 /// actively reduce paths they must be closed using [`Connection::close_path`], which -2727 /// can also be used to close not-yet-opened paths. -2728 /// -2729 /// If multipath is not negotiated (see the [`TransportConfig`]) this can not enable -2730 /// multipath and will fail. -2731 pub fn set_max_concurrent_paths( -2732 &mut self, -2733 now: Instant, -2734 count: NonZeroU32, -2735 ) -> Result<(), MultipathNotNegotiated> { -2736 if !self.is_multipath_negotiated() { -2737 return Err(MultipathNotNegotiated { _private: () }); -2738 } -2739 self.max_concurrent_paths = count; -2740 -2741 let in_use_count = self -2742 .local_max_path_id -2743 .next() -2744 .saturating_sub(self.abandoned_paths.len() as u32) -2745 .as_u32(); -2746 let extra_needed = count.get().saturating_sub(in_use_count); -2747 let new_max_path_id = self.local_max_path_id.saturating_add(extra_needed); -2748 -2749 self.set_max_path_id(now, new_max_path_id); -2750 -2751 Ok(()) +2710 /// Whether there are any pending retransmits +2711 pub fn has_pending_retransmits(&self) -> bool { +2712 !self.spaces[SpaceId::Data].pending.is_empty(&self.streams) +2713 } +2714 +2715 /// Look up whether we're the client or server of this Connection +2716 pub fn side(&self) -> Side { +2717 self.side.side() +2718 } +2719 +2720 /// Get the address observed by the remote over the given path +2721 pub fn path_observed_address(&self, path_id: PathId) -> Result<Option<SocketAddr>, ClosedPath> { +2722 self.path(path_id) +2723 .map(|path_data| { +2724 path_data +2725 .last_observed_addr_report +2726 .as_ref() +2727 .map(|observed| observed.socket_addr()) +2728 }) +2729 .ok_or(ClosedPath { _private: () }) +2730 } +2731 +2732 /// Current best estimate of this connection's latency (round-trip-time) +2733 pub fn rtt(&self, path_id: PathId) -> Option<Duration> { +2734 self.path(path_id).map(|d| d.rtt.get()) +2735 } +2736 +2737 /// Current state of this connection's congestion controller, for debugging purposes +2738 pub fn congestion_state(&self, path_id: PathId) -> Option<&dyn Controller> { +2739 self.path(path_id).map(|d| d.congestion.as_ref()) +2740 } +2741 +2742 /// Modify the number of remotely initiated streams that may be concurrently open +2743 /// +2744 /// No streams may be opened by the peer unless fewer than `count` are already open. Large +2745 /// `count`s increase both minimum and worst-case memory consumption. +2746 pub fn set_max_concurrent_streams(&mut self, dir: Dir, count: VarInt) { +2747 self.streams.set_max_concurrent(dir, count); +2748 // If the limit was reduced, then a flow control update previously deemed insignificant may +2749 // now be significant. +2750 let pending = &mut self.spaces[SpaceId::Data].pending; +2751 self.streams.queue_max_stream_id(pending); 2752 } 2753 -2754 /// If needed, issues a new MAX_PATH_ID frame and new CIDs for any newly allowed paths -2755 fn set_max_path_id(&mut self, now: Instant, max_path_id: PathId) { -2756 if max_path_id <= self.local_max_path_id { -2757 return; -2758 } -2759 -2760 self.local_max_path_id = max_path_id; -2761 self.spaces[SpaceId::Data].pending.max_path_id = true; -2762 -2763 self.issue_first_path_cids(now); -2764 } -2765 -2766 /// Current number of remotely initiated streams that may be concurrently open -2767 /// -2768 /// If the target for this limit is reduced using [`set_max_concurrent_streams`](Self::set_max_concurrent_streams), -2769 /// it will not change immediately, even if fewer streams are open. Instead, it will -2770 /// decrement by one for each time a remotely initiated stream of matching directionality is closed. -2771 pub fn max_concurrent_streams(&self, dir: Dir) -> u64 { -2772 self.streams.max_concurrent(dir) -2773 } -2774 -2775 /// See [`TransportConfig::send_window()`] -2776 pub fn set_send_window(&mut self, send_window: u64) { -2777 self.streams.set_send_window(send_window); -2778 } -2779 -2780 /// See [`TransportConfig::receive_window()`] -2781 pub fn set_receive_window(&mut self, receive_window: VarInt) { -2782 if self.streams.set_receive_window(receive_window) { -2783 self.spaces[SpaceId::Data].pending.max_data = true; -2784 } -2785 } -2786 -2787 /// Whether the Multipath for QUIC extension is enabled. -2788 /// -2789 /// Multipath is only enabled after the handshake is completed and if it was enabled by both -2790 /// peers. -2791 pub fn is_multipath_negotiated(&self) -> bool { -2792 !self.is_handshaking() -2793 && self.config.max_concurrent_multipath_paths.is_some() -2794 && self.peer_params.initial_max_path_id.is_some() -2795 } -2796 -2797 fn on_ack_received( -2798 &mut self, -2799 now: Instant, -2800 space: SpaceId, -2801 ack: frame::Ack, -2802 ) -> Result<(), TransportError> { -2803 // All ACKs are referencing path 0 -2804 let path = PathId::ZERO; -2805 self.inner_on_ack_received(now, space, path, ack) -2806 } -2807 -2808 fn on_path_ack_received( -2809 &mut self, -2810 now: Instant, -2811 space: SpaceId, -2812 path_ack: frame::PathAck, -2813 ) -> Result<(), TransportError> { -2814 let (ack, path) = path_ack.into_ack(); -2815 self.inner_on_ack_received(now, space, path, ack) -2816 } -2817 -2818 /// Handles an ACK frame acknowledging packets sent on *path*. -2819 fn inner_on_ack_received( -2820 &mut self, -2821 now: Instant, -2822 space: SpaceId, -2823 path: PathId, -2824 ack: frame::Ack, -2825 ) -> Result<(), TransportError> { -2826 if !self.spaces[space].number_spaces.contains_key(&path) { -2827 if self.abandoned_paths.contains(&path) { -2828 // See also -2829 // https://www.ietf.org/archive/id/draft-ietf-quic-multipath-21.html#section-3.4.3-3 -2830 // > When an endpoint finally deletes all state associated with the path [...] -2831 // > PATH_ACK frames received with an abandoned path ID are silently ignored, -2832 // > as specified in Section 4. -2833 trace!("silently ignoring PATH_ACK on discarded path"); -2834 return Ok(()); -2835 } else { -2836 return Err(TransportError::PROTOCOL_VIOLATION( -2837 "received PATH_ACK with path ID never used", -2838 )); -2839 } -2840 } -2841 if ack.largest >= self.spaces[space].for_path(path).next_packet_number { -2842 return Err(TransportError::PROTOCOL_VIOLATION("unsent packet acked")); -2843 } -2844 // `Some(pn)` if this ACK raised `largest_acked_packet_pn`. -2845 let new_largest_pn = { -2846 let space = &mut self.spaces[space].for_path(path); -2847 if space -2848 .largest_acked_packet_pn -2849 .is_none_or(|pn| ack.largest > pn) -2850 { -2851 space.largest_acked_packet_pn = Some(ack.largest); -2852 if let Some(info) = space.sent_packets.get(ack.largest) { -2853 // This should always succeed, but a misbehaving peer might ACK a packet we -2854 // haven't sent. At worst, that will result in us spuriously reducing the -2855 // congestion window. -2856 space.largest_acked_packet_send_time = info.time_sent; -2857 } -2858 Some(ack.largest) -2859 } else { -2860 None -2861 } -2862 }; -2863 -2864 if self.detect_spurious_loss(&ack, space, path) { -2865 self.path_stats.for_path(path).spurious_congestion_events += 1; -2866 self.path_data_mut(path) -2867 .congestion -2868 .on_spurious_congestion_event(); -2869 } -2870 -2871 // Avoid DoS from unreasonably huge ack ranges by filtering out just the new acks. -2872 let mut newly_acked = ArrayRangeSet::new(); -2873 for range in ack.iter() { -2874 self.spaces[space].for_path(path).check_ack(range.clone())?; -2875 for (pn, _) in self.spaces[space] -2876 .for_path(path) -2877 .sent_packets -2878 .iter_range(range) -2879 { -2880 newly_acked.insert_one(pn); -2881 } -2882 } -2883 -2884 if newly_acked.is_empty() { -2885 return Ok(()); -2886 } -2887 -2888 let mut ack_eliciting_acked = false; -2889 for packet in newly_acked.elts() { -2890 if let Some(info) = self.spaces[space].for_path(path).take(packet) { -2891 for (acked_path_id, acked_pn) in info.largest_acked.iter() { -2892 // Assume ACKs for all packets below the largest acknowledged in -2893 // `packet` have been received. This can cause the peer to spuriously -2894 // retransmit if some of our earlier ACKs were lost, but allows for -2895 // simpler state tracking. See discussion at -2896 // https://www.rfc-editor.org/rfc/rfc9000.html#name-limiting-ranges-by-tracking -2897 if let Some(pns) = self.spaces[space].path_space_mut(*acked_path_id) { -2898 pns.pending_acks.subtract_below(*acked_pn); -2899 } -2900 } -2901 ack_eliciting_acked |= info.ack_eliciting; +2754 /// Modify the number of open paths allowed when multipath is enabled +2755 /// +2756 /// When reducing the number of concurrent paths this will only affect delaying sending +2757 /// new MAX_PATH_ID frames until fewer than this number of paths are possible. To +2758 /// actively reduce paths they must be closed using [`Connection::close_path`], which +2759 /// can also be used to close not-yet-opened paths. +2760 /// +2761 /// If multipath is not negotiated (see the [`TransportConfig`]) this can not enable +2762 /// multipath and will fail. +2763 pub fn set_max_concurrent_paths( +2764 &mut self, +2765 now: Instant, +2766 count: NonZeroU32, +2767 ) -> Result<(), MultipathNotNegotiated> { +2768 if !self.is_multipath_negotiated() { +2769 return Err(MultipathNotNegotiated { _private: () }); +2770 } +2771 self.max_concurrent_paths = count; +2772 +2773 let in_use_count = self +2774 .local_max_path_id +2775 .next() +2776 .saturating_sub(self.abandoned_paths.len() as u32) +2777 .as_u32(); +2778 let extra_needed = count.get().saturating_sub(in_use_count); +2779 let new_max_path_id = self.local_max_path_id.saturating_add(extra_needed); +2780 +2781 self.set_max_path_id(now, new_max_path_id); +2782 +2783 Ok(()) +2784 } +2785 +2786 /// If needed, issues a new MAX_PATH_ID frame and new CIDs for any newly allowed paths +2787 fn set_max_path_id(&mut self, now: Instant, max_path_id: PathId) { +2788 if max_path_id <= self.local_max_path_id { +2789 return; +2790 } +2791 +2792 self.local_max_path_id = max_path_id; +2793 self.spaces[SpaceId::Data].pending.max_path_id = true; +2794 +2795 self.issue_first_path_cids(now); +2796 } +2797 +2798 /// Current number of remotely initiated streams that may be concurrently open +2799 /// +2800 /// If the target for this limit is reduced using [`set_max_concurrent_streams`](Self::set_max_concurrent_streams), +2801 /// it will not change immediately, even if fewer streams are open. Instead, it will +2802 /// decrement by one for each time a remotely initiated stream of matching directionality is closed. +2803 pub fn max_concurrent_streams(&self, dir: Dir) -> u64 { +2804 self.streams.max_concurrent(dir) +2805 } +2806 +2807 /// See [`TransportConfig::send_window()`] +2808 pub fn set_send_window(&mut self, send_window: u64) { +2809 self.streams.set_send_window(send_window); +2810 } +2811 +2812 /// See [`TransportConfig::receive_window()`] +2813 pub fn set_receive_window(&mut self, receive_window: VarInt) { +2814 if self.streams.set_receive_window(receive_window) { +2815 self.spaces[SpaceId::Data].pending.max_data = true; +2816 } +2817 } +2818 +2819 /// Whether the Multipath for QUIC extension is enabled. +2820 /// +2821 /// Multipath is only enabled after the handshake is completed and if it was enabled by both +2822 /// peers. +2823 pub fn is_multipath_negotiated(&self) -> bool { +2824 !self.is_handshaking() +2825 && self.config.max_concurrent_multipath_paths.is_some() +2826 && self.peer_params.initial_max_path_id.is_some() +2827 } +2828 +2829 fn on_ack_received( +2830 &mut self, +2831 now: Instant, +2832 space: SpaceId, +2833 ack: frame::Ack, +2834 ) -> Result<(), TransportError> { +2835 // All ACKs are referencing path 0 +2836 let path = PathId::ZERO; +2837 self.inner_on_ack_received(now, space, path, ack) +2838 } +2839 +2840 fn on_path_ack_received( +2841 &mut self, +2842 now: Instant, +2843 space: SpaceId, +2844 path_ack: frame::PathAck, +2845 ) -> Result<(), TransportError> { +2846 let (ack, path) = path_ack.into_ack(); +2847 self.inner_on_ack_received(now, space, path, ack) +2848 } +2849 +2850 /// Handles an ACK frame acknowledging packets sent on *path*. +2851 fn inner_on_ack_received( +2852 &mut self, +2853 now: Instant, +2854 space: SpaceId, +2855 path: PathId, +2856 ack: frame::Ack, +2857 ) -> Result<(), TransportError> { +2858 if !self.spaces[space].number_spaces.contains_key(&path) { +2859 if self.abandoned_paths.contains(&path) { +2860 // See also +2861 // https://www.ietf.org/archive/id/draft-ietf-quic-multipath-21.html#section-3.4.3-3 +2862 // > When an endpoint finally deletes all state associated with the path [...] +2863 // > PATH_ACK frames received with an abandoned path ID are silently ignored, +2864 // > as specified in Section 4. +2865 trace!("silently ignoring PATH_ACK on discarded path"); +2866 return Ok(()); +2867 } else { +2868 return Err(TransportError::PROTOCOL_VIOLATION( +2869 "received PATH_ACK with path ID never used", +2870 )); +2871 } +2872 } +2873 if ack.largest >= self.spaces[space].for_path(path).next_packet_number { +2874 return Err(TransportError::PROTOCOL_VIOLATION("unsent packet acked")); +2875 } +2876 // `Some(pn)` if this ACK raised `largest_acked_packet_pn`. +2877 let new_largest_pn = { +2878 let space = &mut self.spaces[space].for_path(path); +2879 if space +2880 .largest_acked_packet_pn +2881 .is_none_or(|pn| ack.largest > pn) +2882 { +2883 space.largest_acked_packet_pn = Some(ack.largest); +2884 if let Some(info) = space.sent_packets.get(ack.largest) { +2885 // This should always succeed, but a misbehaving peer might ACK a packet we +2886 // haven't sent. At worst, that will result in us spuriously reducing the +2887 // congestion window. +2888 space.largest_acked_packet_send_time = info.time_sent; +2889 } +2890 Some(ack.largest) +2891 } else { +2892 None +2893 } +2894 }; +2895 +2896 if self.detect_spurious_loss(&ack, space, path) { +2897 self.path_stats.for_path(path).spurious_congestion_events += 1; +2898 self.path_data_mut(path) +2899 .congestion +2900 .on_spurious_congestion_event(); +2901 } 2902 -2903 // Notify MTU discovery that a packet was acked, because it might be an MTU probe -2904 let path_data = self.path_data_mut(path); -2905 let mtu_updated = path_data.mtud.on_acked(space.kind(), packet, info.size); -2906 if mtu_updated { -2907 path_data -2908 .congestion -2909 .on_mtu_update(path_data.mtud.current_mtu()); -2910 } -2911 -2912 // Notify ack frequency that a packet was acked, because it might contain an ACK_FREQUENCY frame -2913 self.ack_frequency.on_acked(path, packet); -2914 -2915 self.on_packet_acked(now, path, packet, info); -2916 } -2917 } -2918 -2919 let largest_ackd = self.spaces[space].for_path(path).largest_acked_packet_pn; -2920 let path_data = self.path_data_mut(path); -2921 let app_limited = path_data.app_limited; -2922 let in_flight = path_data.in_flight.bytes; -2923 -2924 path_data -2925 .congestion -2926 .on_end_acks(now, in_flight, app_limited, largest_ackd); -2927 -2928 if new_largest_pn.is_some() && ack_eliciting_acked { -2929 let ack_delay = if space != SpaceId::Data { -2930 Duration::from_micros(0) -2931 } else { -2932 cmp::min( -2933 self.ack_frequency.peer_max_ack_delay, -2934 Duration::from_micros(ack.delay << self.peer_params.ack_delay_exponent.0), -2935 ) -2936 }; -2937 let rtt = now.saturating_duration_since( -2938 self.spaces[space] -2939 .for_path(path) -2940 .largest_acked_packet_send_time, -2941 ); -2942 -2943 let next_pn = self.spaces[space].for_path(path).next_packet_number; -2944 let path_data = self.path_data_mut(path); -2945 // TODO(@divma): should be a method of path, should be contained in a single place -2946 path_data.rtt.update(ack_delay, rtt); -2947 if path_data.first_packet_after_rtt_sample.is_none() { -2948 path_data.first_packet_after_rtt_sample = Some((space.kind(), next_pn)); -2949 } -2950 } -2951 -2952 // Must be called before crypto/pto_count are clobbered -2953 self.detect_lost_packets(now, space, path, true); -2954 -2955 // If the peer did not complete the handshake address validation the ACK could be -2956 // spoofed, e.g. in the Initial space. Setting the pto_count back to 0 removes the -2957 // exponential backoff from the PTO timer and would result in too many tail-loss -2958 // probes being sent. -2959 if self.peer_completed_handshake_address_validation() { -2960 self.path_data_mut(path).pto_count = 0; -2961 } -2962 -2963 // Explicit congestion notification -2964 // TODO(@divma): this code is a good example of logic that should be contained in a single -2965 // place but it's split between the path data and the packet number space data, we should -2966 // find a way to make this work without two lookups -2967 if self.path_data(path).sending_ecn { -2968 if let Some(ecn) = ack.ecn { -2969 // We only examine ECN counters from ACKs that we are certain we received in transmit -2970 // order, allowing us to compute an increase in ECN counts to compare against the number -2971 // of newly acked packets that remains well-defined in the presence of arbitrary packet -2972 // reordering. -2973 if let Some(largest_sent_pn) = new_largest_pn { -2974 let sent = self.spaces[space] -2975 .for_path(path) -2976 .largest_acked_packet_send_time; -2977 self.process_ecn( -2978 now, -2979 space, -2980 path, -2981 newly_acked.len() as u64, -2982 ecn, -2983 sent, -2984 largest_sent_pn, -2985 ); -2986 } -2987 } else { -2988 // We always start out sending ECN, so any ack that doesn't acknowledge it disables it. -2989 debug!("ECN not acknowledged by peer"); -2990 self.path_data_mut(path).sending_ecn = false; -2991 } -2992 } -2993 -2994 self.set_loss_detection_timer(now, path); -2995 Ok(()) -2996 } -2997 -2998 fn detect_spurious_loss(&mut self, ack: &frame::Ack, space: SpaceId, path: PathId) -> bool { -2999 let lost_packets = &mut self.spaces[space].for_path(path).lost_packets; -3000 -3001 if lost_packets.is_empty() { -3002 return false; -3003 } -3004 -3005 for range in ack.iter() { -3006 let spurious_losses: Vec<u64> = lost_packets -3007 .iter_range(range.clone()) -3008 .map(|(pn, _info)| pn) -3009 .collect(); -3010 -3011 for pn in spurious_losses { -3012 lost_packets.remove(pn); -3013 } -3014 } -3015 -3016 // If this ACK frame acknowledged all deemed lost packets, -3017 // then we have raised a spurious congestion event in the past. -3018 // We cannot conclude when there are remaining packets, -3019 // but future ACK frames might indicate a spurious loss detection. -3020 lost_packets.is_empty() -3021 } -3022 -3023 /// Drain lost packets that we reasonably think will never arrive -3024 /// -3025 /// The current criterion is copied from `msquic`: -3026 /// discard packets that were sent earlier than 2 probe timeouts ago. -3027 fn drain_lost_packets(&mut self, now: Instant, space: SpaceId, path: PathId) { -3028 let two_pto = 2 * self.path_data(path).rtt.pto_base(); +2903 // Avoid DoS from unreasonably huge ack ranges by filtering out just the new acks. +2904 let mut newly_acked = ArrayRangeSet::new(); +2905 for range in ack.iter() { +2906 self.spaces[space].for_path(path).check_ack(range.clone())?; +2907 for (pn, _) in self.spaces[space] +2908 .for_path(path) +2909 .sent_packets +2910 .iter_range(range) +2911 { +2912 newly_acked.insert_one(pn); +2913 } +2914 } +2915 +2916 if newly_acked.is_empty() { +2917 return Ok(()); +2918 } +2919 +2920 let mut ack_eliciting_acked = false; +2921 for packet in newly_acked.elts() { +2922 if let Some(info) = self.spaces[space].for_path(path).take(packet) { +2923 for (acked_path_id, acked_pn) in info.largest_acked.iter() { +2924 // Assume ACKs for all packets below the largest acknowledged in +2925 // `packet` have been received. This can cause the peer to spuriously +2926 // retransmit if some of our earlier ACKs were lost, but allows for +2927 // simpler state tracking. See discussion at +2928 // https://www.rfc-editor.org/rfc/rfc9000.html#name-limiting-ranges-by-tracking +2929 if let Some(pns) = self.spaces[space].path_space_mut(*acked_path_id) { +2930 pns.pending_acks.subtract_below(*acked_pn); +2931 } +2932 } +2933 ack_eliciting_acked |= info.ack_eliciting; +2934 +2935 // Notify MTU discovery that a packet was acked, because it might be an MTU probe +2936 let path_data = self.path_data_mut(path); +2937 let mtu_updated = path_data.mtud.on_acked(space.kind(), packet, info.size); +2938 if mtu_updated { +2939 path_data +2940 .congestion +2941 .on_mtu_update(path_data.mtud.current_mtu()); +2942 } +2943 +2944 // Notify ack frequency that a packet was acked, because it might contain an ACK_FREQUENCY frame +2945 self.ack_frequency.on_acked(path, packet); +2946 +2947 self.on_packet_acked(now, path, packet, info); +2948 } +2949 } +2950 +2951 let largest_ackd = self.spaces[space].for_path(path).largest_acked_packet_pn; +2952 let path_data = self.path_data_mut(path); +2953 let app_limited = path_data.app_limited; +2954 let in_flight = path_data.in_flight.bytes; +2955 +2956 path_data +2957 .congestion +2958 .on_end_acks(now, in_flight, app_limited, largest_ackd); +2959 +2960 if new_largest_pn.is_some() && ack_eliciting_acked { +2961 let ack_delay = if space != SpaceId::Data { +2962 Duration::from_micros(0) +2963 } else { +2964 cmp::min( +2965 self.ack_frequency.peer_max_ack_delay, +2966 Duration::from_micros(ack.delay << self.peer_params.ack_delay_exponent.0), +2967 ) +2968 }; +2969 let rtt = now.saturating_duration_since( +2970 self.spaces[space] +2971 .for_path(path) +2972 .largest_acked_packet_send_time, +2973 ); +2974 +2975 let next_pn = self.spaces[space].for_path(path).next_packet_number; +2976 let path_data = self.path_data_mut(path); +2977 // TODO(@divma): should be a method of path, should be contained in a single place +2978 path_data.rtt.update(ack_delay, rtt); +2979 if path_data.first_packet_after_rtt_sample.is_none() { +2980 path_data.first_packet_after_rtt_sample = Some((space.kind(), next_pn)); +2981 } +2982 } +2983 +2984 // Must be called before crypto/pto_count are clobbered +2985 self.detect_lost_packets(now, space, path, true); +2986 +2987 // If the peer did not complete the handshake address validation the ACK could be +2988 // spoofed, e.g. in the Initial space. Setting the pto_count back to 0 removes the +2989 // exponential backoff from the PTO timer and would result in too many tail-loss +2990 // probes being sent. +2991 if self.peer_completed_handshake_address_validation() { +2992 self.path_data_mut(path).pto_count = 0; +2993 } +2994 +2995 // Explicit congestion notification +2996 // TODO(@divma): this code is a good example of logic that should be contained in a single +2997 // place but it's split between the path data and the packet number space data, we should +2998 // find a way to make this work without two lookups +2999 if self.path_data(path).sending_ecn { +3000 if let Some(ecn) = ack.ecn { +3001 // We only examine ECN counters from ACKs that we are certain we received in transmit +3002 // order, allowing us to compute an increase in ECN counts to compare against the number +3003 // of newly acked packets that remains well-defined in the presence of arbitrary packet +3004 // reordering. +3005 if let Some(largest_sent_pn) = new_largest_pn { +3006 let sent = self.spaces[space] +3007 .for_path(path) +3008 .largest_acked_packet_send_time; +3009 self.process_ecn( +3010 now, +3011 space, +3012 path, +3013 newly_acked.len() as u64, +3014 ecn, +3015 sent, +3016 largest_sent_pn, +3017 ); +3018 } +3019 } else { +3020 // We always start out sending ECN, so any ack that doesn't acknowledge it disables it. +3021 debug!("ECN not acknowledged by peer"); +3022 self.path_data_mut(path).sending_ecn = false; +3023 } +3024 } +3025 +3026 self.set_loss_detection_timer(now, path); +3027 Ok(()) +3028 } 3029 -3030 let lost_packets = &mut self.spaces[space].for_path(path).lost_packets; -3031 lost_packets.retain(|_pn, info| now.saturating_duration_since(info.time_sent) <= two_pto); -3032 } -3033 -3034 /// Process a new ECN block from an in-order ACK -3035 fn process_ecn( -3036 &mut self, -3037 now: Instant, -3038 space: SpaceId, -3039 path: PathId, -3040 newly_acked_pn: u64, -3041 ecn: frame::EcnCounts, -3042 largest_sent_time: Instant, -3043 largest_sent_pn: u64, -3044 ) { -3045 match self.spaces[space] -3046 .for_path(path) -3047 .detect_ecn(newly_acked_pn, ecn) -3048 { -3049 Err(e) => { -3050 debug!("halting ECN due to verification failure: {}", e); -3051 -3052 self.path_data_mut(path).sending_ecn = false; -3053 // Wipe out the existing value because it might be garbage and could interfere with -3054 // future attempts to use ECN on new paths. -3055 self.spaces[space].for_path(path).ecn_feedback = frame::EcnCounts::ZERO; -3056 } -3057 Ok(false) => {} -3058 Ok(true) => { -3059 self.path_stats.for_path(path).congestion_events += 1; -3060 self.path_data_mut(path).congestion.on_congestion_event( -3061 now, -3062 largest_sent_time, -3063 false, -3064 true, -3065 0, -3066 largest_sent_pn, -3067 ); -3068 } -3069 } -3070 } -3071 -3072 // Not timing-aware, so it's safe to call this for inferred acks, such as arise from -3073 // high-latency handshakes -3074 fn on_packet_acked(&mut self, now: Instant, path_id: PathId, pn: u64, info: SentPacket) { -3075 let path = self.path_data_mut(path_id); -3076 let app_limited = path.app_limited; -3077 path.remove_in_flight(&info); -3078 if info.ack_eliciting && info.path_generation == path.generation() { -3079 // Only pass ACKs to the congestion controller if it belongs to this exact -3080 // generation of the path. Otherwise we might be feeding ACKs from the previous -3081 // 4-tuple into our congestion controller. -3082 let rtt = path.rtt; -3083 path.congestion -3084 .on_ack(now, info.time_sent, info.size.into(), pn, app_limited, &rtt); -3085 } -3086 -3087 // Update state for confirmed delivery of frames -3088 if let Some(retransmits) = info.retransmits.get() { -3089 for (id, _) in retransmits.reset_stream.iter() { -3090 self.streams.reset_acked(*id); -3091 } -3092 } -3093 -3094 for frame in info.stream_frames { -3095 self.streams.received_ack_of(frame); -3096 } -3097 } -3098 -3099 fn set_key_discard_timer(&mut self, now: Instant, space: SpaceKind) { -3100 let start = if self.crypto_state.has_keys(EncryptionLevel::ZeroRtt) { -3101 now -3102 } else { -3103 self.crypto_state -3104 .prev_crypto -3105 .as_ref() -3106 .expect("no previous keys") -3107 .end_packet -3108 .as_ref() -3109 .expect("update not acknowledged yet") -3110 .1 -3111 }; -3112 -3113 // QUIC-MULTIPATH § 2.5 Key Phase Update Process: use largest PTO of all paths. -3114 self.timers.set( -3115 Timer::Conn(ConnTimer::KeyDiscard), -3116 start + self.max_pto_for_space(space) * 3, -3117 self.qlog.with_time(now), -3118 ); -3119 } -3120 -3121 /// Handle a [`PathTimer::LossDetection`] timeout. -3122 /// -3123 /// This timer expires for two reasons: -3124 /// - An ACK-eliciting packet we sent should be considered lost. -3125 /// - The PTO may have expired and a tail-loss probe needs to be scheduled. -3126 /// -3127 /// The former needs us to schedule re-transmission of the lost data. -3128 /// -3129 /// The latter means we have not received an ACK for an ack-eliciting packet we sent -3130 /// within the PTO time-window. We need to schedule a tail-loss probe, an ack-eliciting -3131 /// packet, to try and elicit new acknowledgements. These new acknowledgements will -3132 /// indicate whether the previously sent packets were lost or not. -3133 fn on_loss_detection_timeout(&mut self, now: Instant, path_id: PathId) { -3134 if let Some((_, pn_space)) = self.loss_time_and_space(path_id) { -3135 // Time threshold loss Detection -3136 self.detect_lost_packets(now, pn_space, path_id, false); -3137 self.set_loss_detection_timer(now, path_id); -3138 return; -3139 } -3140 -3141 let Some((_, space)) = self.pto_time_and_space(now, path_id) else { -3142 error!(%path_id, "PTO expired while unset"); -3143 return; -3144 }; -3145 trace!( -3146 in_flight = self.path_data(path_id).in_flight.bytes, -3147 count = self.path_data(path_id).pto_count, -3148 ?space, -3149 %path_id, -3150 "PTO fired" -3151 ); +3030 fn detect_spurious_loss(&mut self, ack: &frame::Ack, space: SpaceId, path: PathId) -> bool { +3031 let lost_packets = &mut self.spaces[space].for_path(path).lost_packets; +3032 +3033 if lost_packets.is_empty() { +3034 return false; +3035 } +3036 +3037 for range in ack.iter() { +3038 let spurious_losses: Vec<u64> = lost_packets +3039 .iter_range(range.clone()) +3040 .map(|(pn, _info)| pn) +3041 .collect(); +3042 +3043 for pn in spurious_losses { +3044 lost_packets.remove(pn); +3045 } +3046 } +3047 +3048 // If this ACK frame acknowledged all deemed lost packets, +3049 // then we have raised a spurious congestion event in the past. +3050 // We cannot conclude when there are remaining packets, +3051 // but future ACK frames might indicate a spurious loss detection. +3052 lost_packets.is_empty() +3053 } +3054 +3055 /// Drain lost packets that we reasonably think will never arrive +3056 /// +3057 /// The current criterion is copied from `msquic`: +3058 /// discard packets that were sent earlier than 2 probe timeouts ago. +3059 fn drain_lost_packets(&mut self, now: Instant, space: SpaceId, path: PathId) { +3060 let two_pto = 2 * self.path_data(path).rtt.pto_base(); +3061 +3062 let lost_packets = &mut self.spaces[space].for_path(path).lost_packets; +3063 lost_packets.retain(|_pn, info| now.saturating_duration_since(info.time_sent) <= two_pto); +3064 } +3065 +3066 /// Process a new ECN block from an in-order ACK +3067 fn process_ecn( +3068 &mut self, +3069 now: Instant, +3070 space: SpaceId, +3071 path: PathId, +3072 newly_acked_pn: u64, +3073 ecn: frame::EcnCounts, +3074 largest_sent_time: Instant, +3075 largest_sent_pn: u64, +3076 ) { +3077 match self.spaces[space] +3078 .for_path(path) +3079 .detect_ecn(newly_acked_pn, ecn) +3080 { +3081 Err(e) => { +3082 debug!("halting ECN due to verification failure: {}", e); +3083 +3084 self.path_data_mut(path).sending_ecn = false; +3085 // Wipe out the existing value because it might be garbage and could interfere with +3086 // future attempts to use ECN on new paths. +3087 self.spaces[space].for_path(path).ecn_feedback = frame::EcnCounts::ZERO; +3088 } +3089 Ok(false) => {} +3090 Ok(true) => { +3091 self.path_stats.for_path(path).congestion_events += 1; +3092 self.path_data_mut(path).congestion.on_congestion_event( +3093 now, +3094 largest_sent_time, +3095 false, +3096 true, +3097 0, +3098 largest_sent_pn, +3099 ); +3100 } +3101 } +3102 } +3103 +3104 // Not timing-aware, so it's safe to call this for inferred acks, such as arise from +3105 // high-latency handshakes +3106 fn on_packet_acked(&mut self, now: Instant, path_id: PathId, pn: u64, info: SentPacket) { +3107 let path = self.path_data_mut(path_id); +3108 let app_limited = path.app_limited; +3109 path.remove_in_flight(&info); +3110 if info.ack_eliciting && info.path_generation == path.generation() { +3111 // Only pass ACKs to the congestion controller if it belongs to this exact +3112 // generation of the path. Otherwise we might be feeding ACKs from the previous +3113 // 4-tuple into our congestion controller. +3114 let rtt = path.rtt; +3115 path.congestion +3116 .on_ack(now, info.time_sent, info.size.into(), pn, app_limited, &rtt); +3117 } +3118 +3119 // Update state for confirmed delivery of frames +3120 if let Some(retransmits) = info.retransmits.get() { +3121 for (id, _) in retransmits.reset_stream.iter() { +3122 self.streams.reset_acked(*id); +3123 } +3124 } +3125 +3126 for frame in info.stream_frames { +3127 self.streams.received_ack_of(frame); +3128 } +3129 } +3130 +3131 fn set_key_discard_timer(&mut self, now: Instant, space: SpaceKind) { +3132 let start = if self.crypto_state.has_keys(EncryptionLevel::ZeroRtt) { +3133 now +3134 } else { +3135 self.crypto_state +3136 .prev_crypto +3137 .as_ref() +3138 .expect("no previous keys") +3139 .end_packet +3140 .as_ref() +3141 .expect("update not acknowledged yet") +3142 .1 +3143 }; +3144 +3145 // QUIC-MULTIPATH § 2.5 Key Phase Update Process: use largest PTO of all paths. +3146 self.timers.set( +3147 Timer::Conn(ConnTimer::KeyDiscard), +3148 start + self.max_pto_for_space(space) * 3, +3149 self.qlog.with_time(now), +3150 ); +3151 } 3152 -3153 let count = match self.path_data(path_id).in_flight.ack_eliciting { -3154 // A PTO when we're not expecting any ACKs must be due to handshake -3155 // anti-amplification deadlock prevention. -3156 0 => { -3157 debug_assert!(!self.peer_completed_handshake_address_validation()); -3158 1 -3159 } -3160 // Conventional loss probe -3161 _ => 2, -3162 }; -3163 let pns = self.spaces[space].for_path(path_id); -3164 pns.loss_probes = pns.loss_probes.saturating_add(count); -3165 let path_data = self.path_data_mut(path_id); -3166 path_data.pto_count = path_data.pto_count.saturating_add(1); -3167 self.set_loss_detection_timer(now, path_id); -3168 } -3169 -3170 /// Detect any lost packets -3171 /// -3172 /// There are two cases in which we detects lost packets: -3173 /// -3174 /// - We received an ACK packet. -3175 /// - The [`PathTimer::LossDetection`] timer expired. So there is an un-acknowledged packet -3176 /// that was followed by an acknowledged packet. The loss timer for this -3177 /// un-acknowledged packet expired and we need to detect that packet as lost. -3178 /// -3179 /// Packets are lost if they are both (See RFC9002 §6.1): -3180 /// -3181 /// - Unacknowledged, in flight and sent prior to an acknowledged packet. -3182 /// - Old enough by either: -3183 /// - Having a packet number [`TransportConfig::packet_threshold`] lower then the last -3184 /// acknowledged packet. -3185 /// - Being sent [`TransportConfig::time_threshold`] * RTT in the past. -3186 fn detect_lost_packets( -3187 &mut self, -3188 now: Instant, -3189 pn_space: SpaceId, -3190 path_id: PathId, -3191 due_to_ack: bool, -3192 ) { -3193 let mut lost_packets = Vec::<u64>::new(); -3194 let mut lost_mtu_probe = None; -3195 let mut in_persistent_congestion = false; -3196 let mut size_of_lost_packets = 0u64; -3197 self.spaces[pn_space].for_path(path_id).loss_time = None; -3198 -3199 // Find all the lost packets, populating all variables initialised above. -3200 -3201 let path = self.path_data(path_id); -3202 let in_flight_mtu_probe = path.mtud.in_flight_mtu_probe(); -3203 let loss_delay = path -3204 .rtt -3205 .conservative() -3206 .mul_f32(self.config.time_threshold) -3207 .max(TIMER_GRANULARITY); -3208 let first_packet_after_rtt_sample = path.first_packet_after_rtt_sample; -3209 -3210 let largest_acked_packet_pn = self.spaces[pn_space] -3211 .for_path(path_id) -3212 .largest_acked_packet_pn -3213 .expect("detect_lost_packets only to be called if path received at least one ACK"); -3214 let packet_threshold = self.config.packet_threshold as u64; -3215 -3216 // InPersistentCongestion: Determine if all packets in the time period before the newest -3217 // lost packet, including the edges, are marked lost. PTO computation must always -3218 // include max ACK delay, i.e. operate as if in Data space (see RFC9001 §7.6.1). -3219 let congestion_period = self -3220 .pto(SpaceKind::Data, path_id) -3221 .saturating_mul(self.config.persistent_congestion_threshold); -3222 let mut persistent_congestion_start: Option<Instant> = None; -3223 let mut prev_packet = None; -3224 let space = self.spaces[pn_space].for_path(path_id); -3225 -3226 for (packet, info) in space.sent_packets.iter_range(0..largest_acked_packet_pn) { -3227 if prev_packet != Some(packet.wrapping_sub(1)) { -3228 // An intervening packet was acknowledged -3229 persistent_congestion_start = None; -3230 } -3231 -3232 // Packets sent before now - loss_delay are deemed lost. -3233 // However, we avoid subtraction as it can panic and there's no -3234 // saturating equivalent of this subtraction operation with a Duration. -3235 let packet_too_old = now.saturating_duration_since(info.time_sent) >= loss_delay; -3236 if packet_too_old || largest_acked_packet_pn >= packet + packet_threshold { -3237 // The packet should be declared lost. -3238 if Some(packet) == in_flight_mtu_probe { -3239 // Lost MTU probes are not included in `lost_packets`, because they -3240 // should not trigger a congestion control response -3241 lost_mtu_probe = in_flight_mtu_probe; -3242 } else { -3243 lost_packets.push(packet); -3244 size_of_lost_packets += info.size as u64; -3245 if info.ack_eliciting && due_to_ack { -3246 match persistent_congestion_start { -3247 // Two ACK-eliciting packets lost more than -3248 // congestion_period apart, with no ACKed packets in between -3249 Some(start) if info.time_sent - start > congestion_period => { -3250 in_persistent_congestion = true; -3251 } -3252 // Persistent congestion must start after the first RTT sample -3253 None if first_packet_after_rtt_sample -3254 .is_some_and(|x| x < (pn_space.kind(), packet)) => -3255 { -3256 persistent_congestion_start = Some(info.time_sent); -3257 } -3258 _ => {} -3259 } -3260 } -3261 } -3262 } else { -3263 // The packet should not yet be declared lost. -3264 if space.loss_time.is_none() { -3265 // Since we iterate in order the lowest packet number's loss time will -3266 // always be the earliest. -3267 space.loss_time = Some(info.time_sent + loss_delay); -3268 } -3269 persistent_congestion_start = None; -3270 } -3271 -3272 prev_packet = Some(packet); -3273 } -3274 -3275 self.handle_lost_packets( -3276 pn_space, -3277 path_id, -3278 now, -3279 lost_packets, -3280 lost_mtu_probe, -3281 loss_delay, -3282 in_persistent_congestion, -3283 size_of_lost_packets, -3284 ); -3285 } -3286 -3287 /// Drops the path state, declaring any remaining in-flight packets as lost -3288 fn discard_path(&mut self, path_id: PathId, now: Instant) { -3289 trace!(%path_id, "dropping path state"); -3290 let path = self.path_data(path_id); -3291 let in_flight_mtu_probe = path.mtud.in_flight_mtu_probe(); -3292 -3293 let mut size_of_lost_packets = 0u64; // add to path_stats.lost_bytes; -3294 let lost_pns: Vec<_> = self.spaces[SpaceId::Data] -3295 .for_path(path_id) -3296 .sent_packets -3297 .iter() -3298 .filter(|(pn, _info)| Some(*pn) != in_flight_mtu_probe) -3299 .map(|(pn, info)| { -3300 size_of_lost_packets += info.size as u64; -3301 pn -3302 }) -3303 .collect(); -3304 -3305 if !lost_pns.is_empty() { -3306 trace!( -3307 %path_id, -3308 count = lost_pns.len(), -3309 lost_bytes = size_of_lost_packets, -3310 "packets lost on path abandon" -3311 ); -3312 self.handle_lost_packets( -3313 SpaceId::Data, -3314 path_id, -3315 now, -3316 lost_pns, -3317 in_flight_mtu_probe, -3318 Duration::ZERO, -3319 false, -3320 size_of_lost_packets, -3321 ); -3322 } -3323 // Before removing the path, we fetch the final path stats via `Self::path_stats`. -3324 // This updates some values for the last time. -3325 let path_stats = self.path_stats.discard(&path_id); -3326 self.partial_stats += path_stats; -3327 self.paths.remove(&path_id); -3328 self.spaces[SpaceId::Data].number_spaces.remove(&path_id); -3329 -3330 self.events.push_back( -3331 PathEvent::Discarded { -3332 id: path_id, -3333 path_stats: Box::new(path_stats), -3334 } -3335 .into(), -3336 ); -3337 } -3338 -3339 fn handle_lost_packets( -3340 &mut self, -3341 pn_space: SpaceId, -3342 path_id: PathId, -3343 now: Instant, -3344 lost_packets: Vec<u64>, -3345 lost_mtu_probe: Option<u64>, -3346 loss_delay: Duration, -3347 in_persistent_congestion: bool, -3348 size_of_lost_packets: u64, -3349 ) { -3350 debug_assert!(lost_packets.is_sorted(), "lost_packets must be sorted"); -3351 -3352 self.drain_lost_packets(now, pn_space, path_id); -3353 -3354 // OnPacketsLost -3355 if let Some(largest_lost) = lost_packets.last().cloned() { -3356 let old_bytes_in_flight = self.path_data_mut(path_id).in_flight.bytes; -3357 let largest_lost_sent = self.spaces[pn_space] -3358 .for_path(path_id) -3359 .sent_packets -3360 .get(largest_lost) -3361 .unwrap() -3362 .time_sent; -3363 let path_stats = self.path_stats.for_path(path_id); -3364 path_stats.lost_packets += lost_packets.len() as u64; -3365 path_stats.lost_bytes += size_of_lost_packets; -3366 trace!( -3367 %path_id, -3368 count = lost_packets.len(), -3369 lost_bytes = size_of_lost_packets, -3370 "packets lost", -3371 ); -3372 -3373 for &packet in &lost_packets { -3374 let Some(info) = self.spaces[pn_space].for_path(path_id).take(packet) else { -3375 continue; -3376 }; -3377 self.qlog -3378 .emit_packet_lost(packet, &info, loss_delay, pn_space.kind(), now); -3379 self.paths -3380 .get_mut(&path_id) -3381 .unwrap() -3382 .remove_in_flight(&info); +3153 /// Handle a [`PathTimer::LossDetection`] timeout. +3154 /// +3155 /// This timer expires for two reasons: +3156 /// - An ACK-eliciting packet we sent should be considered lost. +3157 /// - The PTO may have expired and a tail-loss probe needs to be scheduled. +3158 /// +3159 /// The former needs us to schedule re-transmission of the lost data. +3160 /// +3161 /// The latter means we have not received an ACK for an ack-eliciting packet we sent +3162 /// within the PTO time-window. We need to schedule a tail-loss probe, an ack-eliciting +3163 /// packet, to try and elicit new acknowledgements. These new acknowledgements will +3164 /// indicate whether the previously sent packets were lost or not. +3165 fn on_loss_detection_timeout(&mut self, now: Instant, path_id: PathId) { +3166 if let Some((_, pn_space)) = self.loss_time_and_space(path_id) { +3167 // Time threshold loss Detection +3168 self.detect_lost_packets(now, pn_space, path_id, false); +3169 self.set_loss_detection_timer(now, path_id); +3170 return; +3171 } +3172 +3173 let Some((_, space)) = self.pto_time_and_space(now, path_id) else { +3174 error!(%path_id, "PTO expired while unset"); +3175 return; +3176 }; +3177 trace!( +3178 in_flight = self.path_data(path_id).in_flight.bytes, +3179 count = self.path_data(path_id).pto_count, +3180 ?space, +3181 %path_id, +3182 "PTO fired" +3183 ); +3184 +3185 let count = match self.path_data(path_id).in_flight.ack_eliciting { +3186 // A PTO when we're not expecting any ACKs must be due to handshake +3187 // anti-amplification deadlock prevention. +3188 0 => { +3189 debug_assert!(!self.peer_completed_handshake_address_validation()); +3190 1 +3191 } +3192 // Conventional loss probe +3193 _ => 2, +3194 }; +3195 let pns = self.spaces[space].for_path(path_id); +3196 pns.loss_probes = pns.loss_probes.saturating_add(count); +3197 let path_data = self.path_data_mut(path_id); +3198 path_data.pto_count = path_data.pto_count.saturating_add(1); +3199 self.set_loss_detection_timer(now, path_id); +3200 } +3201 +3202 /// Detect any lost packets +3203 /// +3204 /// There are two cases in which we detects lost packets: +3205 /// +3206 /// - We received an ACK packet. +3207 /// - The [`PathTimer::LossDetection`] timer expired. So there is an un-acknowledged packet +3208 /// that was followed by an acknowledged packet. The loss timer for this +3209 /// un-acknowledged packet expired and we need to detect that packet as lost. +3210 /// +3211 /// Packets are lost if they are both (See RFC9002 §6.1): +3212 /// +3213 /// - Unacknowledged, in flight and sent prior to an acknowledged packet. +3214 /// - Old enough by either: +3215 /// - Having a packet number [`TransportConfig::packet_threshold`] lower then the last +3216 /// acknowledged packet. +3217 /// - Being sent [`TransportConfig::time_threshold`] * RTT in the past. +3218 fn detect_lost_packets( +3219 &mut self, +3220 now: Instant, +3221 pn_space: SpaceId, +3222 path_id: PathId, +3223 due_to_ack: bool, +3224 ) { +3225 let mut lost_packets = Vec::<u64>::new(); +3226 let mut lost_mtu_probe = None; +3227 let mut in_persistent_congestion = false; +3228 let mut size_of_lost_packets = 0u64; +3229 self.spaces[pn_space].for_path(path_id).loss_time = None; +3230 +3231 // Find all the lost packets, populating all variables initialised above. +3232 +3233 let path = self.path_data(path_id); +3234 let in_flight_mtu_probe = path.mtud.in_flight_mtu_probe(); +3235 let loss_delay = path +3236 .rtt +3237 .conservative() +3238 .mul_f32(self.config.time_threshold) +3239 .max(TIMER_GRANULARITY); +3240 let first_packet_after_rtt_sample = path.first_packet_after_rtt_sample; +3241 +3242 let largest_acked_packet_pn = self.spaces[pn_space] +3243 .for_path(path_id) +3244 .largest_acked_packet_pn +3245 .expect("detect_lost_packets only to be called if path received at least one ACK"); +3246 let packet_threshold = self.config.packet_threshold as u64; +3247 +3248 // InPersistentCongestion: Determine if all packets in the time period before the newest +3249 // lost packet, including the edges, are marked lost. PTO computation must always +3250 // include max ACK delay, i.e. operate as if in Data space (see RFC9001 §7.6.1). +3251 let congestion_period = self +3252 .pto(SpaceKind::Data, path_id) +3253 .saturating_mul(self.config.persistent_congestion_threshold); +3254 let mut persistent_congestion_start: Option<Instant> = None; +3255 let mut prev_packet = None; +3256 let space = self.spaces[pn_space].for_path(path_id); +3257 +3258 for (packet, info) in space.sent_packets.iter_range(0..largest_acked_packet_pn) { +3259 if prev_packet != Some(packet.wrapping_sub(1)) { +3260 // An intervening packet was acknowledged +3261 persistent_congestion_start = None; +3262 } +3263 +3264 // Packets sent before now - loss_delay are deemed lost. +3265 // However, we avoid subtraction as it can panic and there's no +3266 // saturating equivalent of this subtraction operation with a Duration. +3267 let packet_too_old = now.saturating_duration_since(info.time_sent) >= loss_delay; +3268 if packet_too_old || largest_acked_packet_pn >= packet + packet_threshold { +3269 // The packet should be declared lost. +3270 if Some(packet) == in_flight_mtu_probe { +3271 // Lost MTU probes are not included in `lost_packets`, because they +3272 // should not trigger a congestion control response +3273 lost_mtu_probe = in_flight_mtu_probe; +3274 } else { +3275 lost_packets.push(packet); +3276 size_of_lost_packets += info.size as u64; +3277 if info.ack_eliciting && due_to_ack { +3278 match persistent_congestion_start { +3279 // Two ACK-eliciting packets lost more than +3280 // congestion_period apart, with no ACKed packets in between +3281 Some(start) if info.time_sent - start > congestion_period => { +3282 in_persistent_congestion = true; +3283 } +3284 // Persistent congestion must start after the first RTT sample +3285 None if first_packet_after_rtt_sample +3286 .is_some_and(|x| x < (pn_space.kind(), packet)) => +3287 { +3288 persistent_congestion_start = Some(info.time_sent); +3289 } +3290 _ => {} +3291 } +3292 } +3293 } +3294 } else { +3295 // The packet should not yet be declared lost. +3296 if space.loss_time.is_none() { +3297 // Since we iterate in order the lowest packet number's loss time will +3298 // always be the earliest. +3299 space.loss_time = Some(info.time_sent + loss_delay); +3300 } +3301 persistent_congestion_start = None; +3302 } +3303 +3304 prev_packet = Some(packet); +3305 } +3306 +3307 self.handle_lost_packets( +3308 pn_space, +3309 path_id, +3310 now, +3311 lost_packets, +3312 lost_mtu_probe, +3313 loss_delay, +3314 in_persistent_congestion, +3315 size_of_lost_packets, +3316 ); +3317 } +3318 +3319 /// Drops the path state, declaring any remaining in-flight packets as lost +3320 fn discard_path(&mut self, path_id: PathId, now: Instant) { +3321 trace!(%path_id, "dropping path state"); +3322 let path = self.path_data(path_id); +3323 let in_flight_mtu_probe = path.mtud.in_flight_mtu_probe(); +3324 +3325 let mut size_of_lost_packets = 0u64; // add to path_stats.lost_bytes; +3326 let lost_pns: Vec<_> = self.spaces[SpaceId::Data] +3327 .for_path(path_id) +3328 .sent_packets +3329 .iter() +3330 .filter(|(pn, _info)| Some(*pn) != in_flight_mtu_probe) +3331 .map(|(pn, info)| { +3332 size_of_lost_packets += info.size as u64; +3333 pn +3334 }) +3335 .collect(); +3336 +3337 if !lost_pns.is_empty() { +3338 trace!( +3339 %path_id, +3340 count = lost_pns.len(), +3341 lost_bytes = size_of_lost_packets, +3342 "packets lost on path abandon" +3343 ); +3344 self.handle_lost_packets( +3345 SpaceId::Data, +3346 path_id, +3347 now, +3348 lost_pns, +3349 in_flight_mtu_probe, +3350 Duration::ZERO, +3351 false, +3352 size_of_lost_packets, +3353 ); +3354 } +3355 // Before removing the path, we fetch the final path stats via `Self::path_stats`. +3356 // This updates some values for the last time. +3357 let path_stats = self.path_stats.discard(&path_id); +3358 self.partial_stats += path_stats; +3359 self.paths.remove(&path_id); +3360 self.spaces[SpaceId::Data].number_spaces.remove(&path_id); +3361 +3362 self.events.push_back( +3363 PathEvent::Discarded { +3364 id: path_id, +3365 path_stats: Box::new(path_stats), +3366 } +3367 .into(), +3368 ); +3369 } +3370 +3371 fn handle_lost_packets( +3372 &mut self, +3373 pn_space: SpaceId, +3374 path_id: PathId, +3375 now: Instant, +3376 lost_packets: Vec<u64>, +3377 lost_mtu_probe: Option<u64>, +3378 loss_delay: Duration, +3379 in_persistent_congestion: bool, +3380 size_of_lost_packets: u64, +3381 ) { +3382 debug_assert!(lost_packets.is_sorted(), "lost_packets must be sorted"); 3383 -3384 for frame in info.stream_frames { -3385 self.streams.retransmit(frame); -3386 } -3387 self.spaces[pn_space].pending |= info.retransmits; -3388 let path = self.path_data_mut(path_id); -3389 path.mtud.on_non_probe_lost(packet, info.size); -3390 path.congestion.on_packet_lost(info.size, packet, now); -3391 -3392 self.spaces[pn_space].for_path(path_id).lost_packets.insert( -3393 packet, -3394 LostPacket { -3395 time_sent: info.time_sent, -3396 }, -3397 ); -3398 } -3399 -3400 let path = self.path_data_mut(path_id); -3401 if path.mtud.black_hole_detected(now) { -3402 path.congestion.on_mtu_update(path.mtud.current_mtu()); -3403 if let Some(max_datagram_size) = self.datagrams().max_size() -3404 && self.datagrams.drop_oversized(max_datagram_size) -3405 && self.datagrams.send_blocked -3406 { -3407 self.datagrams.send_blocked = false; -3408 self.events.push_back(Event::DatagramsUnblocked); -3409 } -3410 self.path_stats.for_path(path_id).black_holes_detected += 1; -3411 } -3412 -3413 // Don't apply congestion penalty for lost ack-only packets -3414 let lost_ack_eliciting = -3415 old_bytes_in_flight != self.path_data_mut(path_id).in_flight.bytes; -3416 -3417 if lost_ack_eliciting { -3418 self.path_stats.for_path(path_id).congestion_events += 1; -3419 self.path_data_mut(path_id).congestion.on_congestion_event( -3420 now, -3421 largest_lost_sent, -3422 in_persistent_congestion, -3423 false, -3424 size_of_lost_packets, -3425 largest_lost, -3426 ); -3427 } -3428 } -3429 -3430 // Handle a lost MTU probe -3431 if let Some(packet) = lost_mtu_probe { -3432 let info = self.spaces[SpaceId::Data] -3433 .for_path(path_id) -3434 .take(packet) -3435 .unwrap(); // safe: lost_mtu_probe is omitted from lost_packets, and -3436 // therefore must not have been removed yet -3437 self.paths -3438 .get_mut(&path_id) -3439 .unwrap() -3440 .remove_in_flight(&info); -3441 self.path_data_mut(path_id).mtud.on_probe_lost(); -3442 self.path_stats.for_path(path_id).lost_plpmtud_probes += 1; -3443 } -3444 } -3445 -3446 /// Returns the earliest time packets should be declared lost for all spaces on a path. -3447 /// -3448 /// If a path has an acknowledged packet with any prior un-acknowledged packets, the -3449 /// earliest un-acknowledged packet can be declared lost after a timeout has elapsed. -3450 /// The time returned is when this packet should be declared lost. -3451 fn loss_time_and_space(&self, path_id: PathId) -> Option<(Instant, SpaceId)> { -3452 SpaceId::iter() -3453 .filter_map(|id| { -3454 self.spaces[id] -3455 .number_spaces -3456 .get(&path_id) -3457 .and_then(|pns| pns.loss_time) -3458 .map(|time| (time, id)) -3459 }) -3460 .min_by_key(|&(time, _)| time) -3461 } -3462 -3463 /// Returns the earliest next PTO should fire for all spaces on a path. -3464 /// -3465 /// This needs to be fully deterministic because it is also used to determine the PTO -3466 /// that fired, not just to set the next timer. So if it fired in the past it needs to -3467 /// return the time from the past at which it fired. -3468 /// -3469 /// This is the next time a tail-loss probe should be sent. -3470 fn pto_time_and_space(&mut self, now: Instant, path_id: PathId) -> Option<(Instant, SpaceId)> { -3471 let path = self.path(path_id)?; -3472 let pto_count = path.pto_count; -3473 -3474 // Cap the maximum interval between two tail-loss probes. -3475 let max_interval = if path.rtt.get() > SLOW_RTT_THRESHOLD { -3476 // For slow links we want to increase the interval beyond 2s. -3477 (path.rtt.get() * 3) / 2 -3478 } else if let Some(idle) = path.idle_timeout.or(self.idle_timeout) -3479 && idle <= MIN_IDLE_FOR_FAST_PTO -3480 { -3481 // If the idle timeout is relatively low, cap at 1s so we get plenty of retries -3482 // before the idle timeout fires. -3483 MAX_PTO_FAST_INTERVAL -3484 } else { -3485 // Otherwise cap to 2s. -3486 MAX_PTO_INTERVAL -3487 }; -3488 -3489 if path_id == PathId::ZERO -3490 && path.in_flight.ack_eliciting == 0 -3491 && !self.peer_completed_handshake_address_validation() -3492 { -3493 // Address Validation during Connection Establishment: -3494 // https://www.rfc-editor.org/rfc/rfc9000.html#section-8.1. To prevent a -3495 // deadlock if an Initial or Handshake packet from the server is lost and the -3496 // server can not send more due to its anti-amplification limit the client must -3497 // send another packet on PTO. -3498 let space = match self.highest_space { -3499 SpaceKind::Handshake => SpaceId::Handshake, -3500 _ => SpaceId::Initial, -3501 }; -3502 -3503 let backoff = 2u32.pow(path.pto_count.min(MAX_BACKOFF_EXPONENT)); -3504 let duration = path.rtt.pto_base() * backoff; -3505 let duration = duration.min(max_interval); -3506 return Some((now + duration, space)); -3507 } -3508 -3509 let mut result = None; -3510 for space in SpaceId::iter() { -3511 let Some(pns) = self.spaces[space].number_spaces.get(&path_id) else { -3512 continue; -3513 }; -3514 -3515 if space == SpaceId::Data && !self.is_handshake_confirmed() { -3516 // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.1-7: -3517 // An endpoint MUST NOT set its PTO timer for the Application Data packet -3518 // number space until the handshake is confirmed. -3519 continue; -3520 } -3521 -3522 if !pns.has_in_flight() { -3523 continue; -3524 } -3525 -3526 // Compute the PTO duration for this space, we want to cap the maximum interval -3527 // between two tail-loss probes so to not do a simple exponential backoff but -3528 // rather iterate through the probes to compute the capped increment for an -3529 // exponential backoff at each step. -3530 let duration = { -3531 let max_ack_delay = if space == SpaceId::Data { -3532 self.ack_frequency.max_ack_delay_for_pto() -3533 } else { -3534 Duration::ZERO -3535 }; -3536 let pto_base = path.rtt.pto_base() + max_ack_delay; -3537 let mut duration = pto_base; -3538 for i in 1..=pto_count { -3539 let exponential_duration = pto_base * 2u32.pow(i.min(MAX_BACKOFF_EXPONENT)); -3540 let max_duration = duration + max_interval; -3541 duration = exponential_duration.min(max_duration); -3542 } -3543 duration -3544 }; -3545 -3546 let Some(last_ack_eliciting) = pns.time_of_last_ack_eliciting_packet else { -3547 continue; -3548 }; -3549 // Base the deadline on when the last probe was sent, so the PTO -3550 // doesn't fire before the response has had time to arrive. -3551 let pto = last_ack_eliciting + duration; -3552 if result.is_none_or(|(earliest_pto, _)| pto < earliest_pto) { -3553 if path.anti_amplification_blocked(1) { -3554 // Nothing would be able to be sent. -3555 continue; -3556 } -3557 if path.in_flight.ack_eliciting == 0 { -3558 // Nothing ack-eliciting, no PTO to arm/fire. -3559 continue; -3560 } -3561 result = Some((pto, space)); -3562 } -3563 } -3564 result -3565 } -3566 -3567 /// Whether the peer validated our address in the connection handshake. -3568 fn peer_completed_handshake_address_validation(&self) -> bool { -3569 if self.side.is_server() || self.state.is_closed() { -3570 return true; -3571 } -3572 // The server is guaranteed to have validated our address if any of our handshake or -3573 // 1-RTT packets are acknowledged or we've seen HANDSHAKE_DONE and discarded -3574 // handshake keys. -3575 self.spaces[SpaceId::Handshake] -3576 .path_space(PathId::ZERO) -3577 .and_then(|pns| pns.largest_acked_packet_pn) -3578 .is_some() -3579 || self.spaces[SpaceId::Data] -3580 .path_space(PathId::ZERO) -3581 .and_then(|pns| pns.largest_acked_packet_pn) -3582 .is_some() -3583 || (self.crypto_state.has_keys(EncryptionLevel::OneRtt) -3584 && !self.crypto_state.has_keys(EncryptionLevel::Handshake)) -3585 } -3586 -3587 /// Resets the the [`PathTimer::LossDetection`] timer to the next instant it may be needed -3588 /// -3589 /// The timer must fire if either: -3590 /// - An ack-eliciting packet we sent needs to be declared lost. -3591 /// - A tail-loss probe needs to be sent. -3592 /// -3593 /// See [`Connection::on_loss_detection_timeout`] for details. -3594 fn set_loss_detection_timer(&mut self, now: Instant, path_id: PathId) { -3595 if self.state.is_closed() { -3596 // No loss detection takes place on closed connections, and `close_common` already -3597 // stopped time timer. Ensure we don't restart it inadvertently, e.g. in response to a -3598 // reordered packet being handled by state-insensitive code. -3599 return; -3600 } -3601 -3602 if let Some((loss_time, _)) = self.loss_time_and_space(path_id) { -3603 // Time threshold loss detection. -3604 self.timers.set( -3605 Timer::PerPath(path_id, PathTimer::LossDetection), -3606 loss_time, -3607 self.qlog.with_time(now), -3608 ); -3609 return; -3610 } -3611 -3612 // Determine which PN space to arm PTO for. -3613 // We can only send tail-loss probes on paths that aren't abandoned yet. -3614 if !self.abandoned_paths.contains(&path_id) -3615 && let Some((timeout, _)) = self.pto_time_and_space(now, path_id) -3616 { -3617 self.timers.set( -3618 Timer::PerPath(path_id, PathTimer::LossDetection), -3619 timeout, -3620 self.qlog.with_time(now), -3621 ); -3622 } else { -3623 self.timers.stop( -3624 Timer::PerPath(path_id, PathTimer::LossDetection), -3625 self.qlog.with_time(now), -3626 ); -3627 } -3628 } -3629 -3630 /// The maximum probe timeout across all paths -3631 /// -3632 /// See [`Connection::pto`] -3633 fn max_pto_for_space(&self, space: SpaceKind) -> Duration { -3634 self.paths -3635 .keys() -3636 .map(|path_id| self.pto(space, *path_id)) -3637 .max() -3638 .unwrap_or_else(|| { -3639 // No paths remain (e.g. last path was abandoned and the NoAvailablePath grace timer -3640 // fired before any new path was opened). Fall back to a PTO derived from the -3641 // configured initial RTT, matching RFC 9002 §6.2.2 initial values. -3642 let rtt = self.config.initial_rtt; -3643 let max_ack_delay = match space { -3644 SpaceKind::Initial | SpaceKind::Handshake => Duration::ZERO, -3645 SpaceKind::Data => self.ack_frequency.max_ack_delay_for_pto(), -3646 }; -3647 rtt + cmp::max(4 * (rtt / 2), TIMER_GRANULARITY) + max_ack_delay -3648 }) -3649 } -3650 -3651 /// Probe Timeout -3652 /// -3653 /// The PTO is logically the time in which you'd expect to receive an acknowledgement -3654 /// for a packet. So approximately RTT + max_ack_delay. -3655 fn pto(&self, space: SpaceKind, path_id: PathId) -> Duration { -3656 let max_ack_delay = match space { -3657 SpaceKind::Initial | SpaceKind::Handshake => Duration::ZERO, -3658 SpaceKind::Data => self.ack_frequency.max_ack_delay_for_pto(), -3659 }; -3660 self.path_data(path_id).rtt.pto_base() + max_ack_delay -3661 } -3662 -3663 fn on_packet_authenticated( -3664 &mut self, -3665 now: Instant, -3666 space_id: SpaceKind, -3667 path_id: PathId, -3668 ecn: Option<EcnCodepoint>, -3669 packet_number: Option<u64>, -3670 spin: bool, -3671 is_1rtt: bool, -3672 remote: &FourTuple, -3673 ) { -3674 // During the handshake we already have discarded packets that do not match the path -3675 // remote. So any off-path packet here is either a probing packet or a -3676 // migration. Handling probing packets here means that the path's idle timeout will -3677 // be reset and will delay detecting the path as idle. However tail-loss probes -3678 // would still not get acknowledged if the path was broken so eventually the path -3679 // would still become idle. -3680 let is_on_path = *remote == self.path_data(path_id).network_path; -3681 -3682 self.total_authed_packets += 1; -3683 self.reset_keep_alive(path_id, now); -3684 self.reset_idle_timeout(now, space_id, path_id); -3685 self.path_data_mut(path_id).permit_idle_reset = true; -3686 -3687 // Do not process ECN for off-path packets. If this is a migration we'll get ECN -3688 // back once we've migrated. -3689 if is_on_path { -3690 self.receiving_ecn |= ecn.is_some(); -3691 if let Some(x) = ecn { -3692 let space = &mut self.spaces[space_id]; -3693 space.for_path(path_id).ecn_counters += x; +3384 self.drain_lost_packets(now, pn_space, path_id); +3385 +3386 // OnPacketsLost +3387 if let Some(largest_lost) = lost_packets.last().cloned() { +3388 let old_bytes_in_flight = self.path_data_mut(path_id).in_flight.bytes; +3389 let largest_lost_sent = self.spaces[pn_space] +3390 .for_path(path_id) +3391 .sent_packets +3392 .get(largest_lost) +3393 .unwrap() +3394 .time_sent; +3395 let path_stats = self.path_stats.for_path(path_id); +3396 path_stats.lost_packets += lost_packets.len() as u64; +3397 path_stats.lost_bytes += size_of_lost_packets; +3398 trace!( +3399 %path_id, +3400 count = lost_packets.len(), +3401 lost_bytes = size_of_lost_packets, +3402 "packets lost", +3403 ); +3404 +3405 for &packet in &lost_packets { +3406 let Some(info) = self.spaces[pn_space].for_path(path_id).take(packet) else { +3407 continue; +3408 }; +3409 self.qlog +3410 .emit_packet_lost(packet, &info, loss_delay, pn_space.kind(), now); +3411 self.paths +3412 .get_mut(&path_id) +3413 .unwrap() +3414 .remove_in_flight(&info); +3415 +3416 for frame in info.stream_frames { +3417 self.streams.retransmit(frame); +3418 } +3419 self.spaces[pn_space].pending |= info.retransmits; +3420 let path = self.path_data_mut(path_id); +3421 path.mtud.on_non_probe_lost(packet, info.size); +3422 path.congestion.on_packet_lost(info.size, packet, now); +3423 +3424 self.spaces[pn_space].for_path(path_id).lost_packets.insert( +3425 packet, +3426 LostPacket { +3427 time_sent: info.time_sent, +3428 }, +3429 ); +3430 } +3431 +3432 let path = self.path_data_mut(path_id); +3433 if path.mtud.black_hole_detected(now) { +3434 path.congestion.on_mtu_update(path.mtud.current_mtu()); +3435 if let Some(max_datagram_size) = self.datagrams().max_size() +3436 && self.datagrams.drop_oversized(max_datagram_size) +3437 && self.datagrams.send_blocked +3438 { +3439 self.datagrams.send_blocked = false; +3440 self.events.push_back(Event::DatagramsUnblocked); +3441 } +3442 self.path_stats.for_path(path_id).black_holes_detected += 1; +3443 } +3444 +3445 // Don't apply congestion penalty for lost ack-only packets +3446 let lost_ack_eliciting = +3447 old_bytes_in_flight != self.path_data_mut(path_id).in_flight.bytes; +3448 +3449 if lost_ack_eliciting { +3450 self.path_stats.for_path(path_id).congestion_events += 1; +3451 self.path_data_mut(path_id).congestion.on_congestion_event( +3452 now, +3453 largest_lost_sent, +3454 in_persistent_congestion, +3455 false, +3456 size_of_lost_packets, +3457 largest_lost, +3458 ); +3459 } +3460 } +3461 +3462 // Handle a lost MTU probe +3463 if let Some(packet) = lost_mtu_probe { +3464 let info = self.spaces[SpaceId::Data] +3465 .for_path(path_id) +3466 .take(packet) +3467 .unwrap(); // safe: lost_mtu_probe is omitted from lost_packets, and +3468 // therefore must not have been removed yet +3469 self.paths +3470 .get_mut(&path_id) +3471 .unwrap() +3472 .remove_in_flight(&info); +3473 self.path_data_mut(path_id).mtud.on_probe_lost(); +3474 self.path_stats.for_path(path_id).lost_plpmtud_probes += 1; +3475 } +3476 } +3477 +3478 /// Returns the earliest time packets should be declared lost for all spaces on a path. +3479 /// +3480 /// If a path has an acknowledged packet with any prior un-acknowledged packets, the +3481 /// earliest un-acknowledged packet can be declared lost after a timeout has elapsed. +3482 /// The time returned is when this packet should be declared lost. +3483 fn loss_time_and_space(&self, path_id: PathId) -> Option<(Instant, SpaceId)> { +3484 SpaceId::iter() +3485 .filter_map(|id| { +3486 self.spaces[id] +3487 .number_spaces +3488 .get(&path_id) +3489 .and_then(|pns| pns.loss_time) +3490 .map(|time| (time, id)) +3491 }) +3492 .min_by_key(|&(time, _)| time) +3493 } +3494 +3495 /// Returns the earliest next PTO should fire for all spaces on a path. +3496 /// +3497 /// This needs to be fully deterministic because it is also used to determine the PTO +3498 /// that fired, not just to set the next timer. So if it fired in the past it needs to +3499 /// return the time from the past at which it fired. +3500 /// +3501 /// This is the next time a tail-loss probe should be sent. +3502 fn pto_time_and_space(&mut self, now: Instant, path_id: PathId) -> Option<(Instant, SpaceId)> { +3503 let path = self.path(path_id)?; +3504 let pto_count = path.pto_count; +3505 +3506 // Cap the maximum interval between two tail-loss probes. +3507 let max_interval = if path.rtt.get() > SLOW_RTT_THRESHOLD { +3508 // For slow links we want to increase the interval beyond 2s. +3509 (path.rtt.get() * 3) / 2 +3510 } else if let Some(idle) = path.idle_timeout.or(self.idle_timeout) +3511 && idle <= MIN_IDLE_FOR_FAST_PTO +3512 { +3513 // If the idle timeout is relatively low, cap at 1s so we get plenty of retries +3514 // before the idle timeout fires. +3515 MAX_PTO_FAST_INTERVAL +3516 } else { +3517 // Otherwise cap to 2s. +3518 MAX_PTO_INTERVAL +3519 }; +3520 +3521 if path_id == PathId::ZERO +3522 && path.in_flight.ack_eliciting == 0 +3523 && !self.peer_completed_handshake_address_validation() +3524 { +3525 // Address Validation during Connection Establishment: +3526 // https://www.rfc-editor.org/rfc/rfc9000.html#section-8.1. To prevent a +3527 // deadlock if an Initial or Handshake packet from the server is lost and the +3528 // server can not send more due to its anti-amplification limit the client must +3529 // send another packet on PTO. +3530 let space = match self.highest_space { +3531 SpaceKind::Handshake => SpaceId::Handshake, +3532 _ => SpaceId::Initial, +3533 }; +3534 +3535 let backoff = 2u32.pow(path.pto_count.min(MAX_BACKOFF_EXPONENT)); +3536 let duration = path.rtt.pto_base() * backoff; +3537 let duration = duration.min(max_interval); +3538 return Some((now + duration, space)); +3539 } +3540 +3541 let mut result = None; +3542 for space in SpaceId::iter() { +3543 let Some(pns) = self.spaces[space].number_spaces.get(&path_id) else { +3544 continue; +3545 }; +3546 +3547 if space == SpaceId::Data && !self.is_handshake_confirmed() { +3548 // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.1-7: +3549 // An endpoint MUST NOT set its PTO timer for the Application Data packet +3550 // number space until the handshake is confirmed. +3551 continue; +3552 } +3553 +3554 if !pns.has_in_flight() { +3555 continue; +3556 } +3557 +3558 // Compute the PTO duration for this space, we want to cap the maximum interval +3559 // between two tail-loss probes so to not do a simple exponential backoff but +3560 // rather iterate through the probes to compute the capped increment for an +3561 // exponential backoff at each step. +3562 let duration = { +3563 let max_ack_delay = if space == SpaceId::Data { +3564 self.ack_frequency.max_ack_delay_for_pto() +3565 } else { +3566 Duration::ZERO +3567 }; +3568 let pto_base = path.rtt.pto_base() + max_ack_delay; +3569 let mut duration = pto_base; +3570 for i in 1..=pto_count { +3571 let exponential_duration = pto_base * 2u32.pow(i.min(MAX_BACKOFF_EXPONENT)); +3572 let max_duration = duration + max_interval; +3573 duration = exponential_duration.min(max_duration); +3574 } +3575 duration +3576 }; +3577 +3578 let Some(last_ack_eliciting) = pns.time_of_last_ack_eliciting_packet else { +3579 continue; +3580 }; +3581 // Base the deadline on when the last probe was sent, so the PTO +3582 // doesn't fire before the response has had time to arrive. +3583 let pto = last_ack_eliciting + duration; +3584 if result.is_none_or(|(earliest_pto, _)| pto < earliest_pto) { +3585 if path.anti_amplification_blocked(1) { +3586 // Nothing would be able to be sent. +3587 continue; +3588 } +3589 if path.in_flight.ack_eliciting == 0 { +3590 // Nothing ack-eliciting, no PTO to arm/fire. +3591 continue; +3592 } +3593 result = Some((pto, space)); +3594 } +3595 } +3596 result +3597 } +3598 +3599 /// Whether the peer validated our address in the connection handshake. +3600 fn peer_completed_handshake_address_validation(&self) -> bool { +3601 if self.side.is_server() || self.state.is_closed() { +3602 return true; +3603 } +3604 // The server is guaranteed to have validated our address if any of our handshake or +3605 // 1-RTT packets are acknowledged or we've seen HANDSHAKE_DONE and discarded +3606 // handshake keys. +3607 self.spaces[SpaceId::Handshake] +3608 .path_space(PathId::ZERO) +3609 .and_then(|pns| pns.largest_acked_packet_pn) +3610 .is_some() +3611 || self.spaces[SpaceId::Data] +3612 .path_space(PathId::ZERO) +3613 .and_then(|pns| pns.largest_acked_packet_pn) +3614 .is_some() +3615 || (self.crypto_state.has_keys(EncryptionLevel::OneRtt) +3616 && !self.crypto_state.has_keys(EncryptionLevel::Handshake)) +3617 } +3618 +3619 /// Resets the the [`PathTimer::LossDetection`] timer to the next instant it may be needed +3620 /// +3621 /// The timer must fire if either: +3622 /// - An ack-eliciting packet we sent needs to be declared lost. +3623 /// - A tail-loss probe needs to be sent. +3624 /// +3625 /// See [`Connection::on_loss_detection_timeout`] for details. +3626 fn set_loss_detection_timer(&mut self, now: Instant, path_id: PathId) { +3627 if self.state.is_closed() { +3628 // No loss detection takes place on closed connections, and `close_common` already +3629 // stopped time timer. Ensure we don't restart it inadvertently, e.g. in response to a +3630 // reordered packet being handled by state-insensitive code. +3631 return; +3632 } +3633 +3634 if let Some((loss_time, _)) = self.loss_time_and_space(path_id) { +3635 // Time threshold loss detection. +3636 self.timers.set( +3637 Timer::PerPath(path_id, PathTimer::LossDetection), +3638 loss_time, +3639 self.qlog.with_time(now), +3640 ); +3641 return; +3642 } +3643 +3644 // Determine which PN space to arm PTO for. +3645 // We can only send tail-loss probes on paths that aren't abandoned yet. +3646 if !self.abandoned_paths.contains(&path_id) +3647 && let Some((timeout, _)) = self.pto_time_and_space(now, path_id) +3648 { +3649 self.timers.set( +3650 Timer::PerPath(path_id, PathTimer::LossDetection), +3651 timeout, +3652 self.qlog.with_time(now), +3653 ); +3654 } else { +3655 self.timers.stop( +3656 Timer::PerPath(path_id, PathTimer::LossDetection), +3657 self.qlog.with_time(now), +3658 ); +3659 } +3660 } +3661 +3662 /// The maximum probe timeout across all paths +3663 /// +3664 /// See [`Connection::pto`] +3665 fn max_pto_for_space(&self, space: SpaceKind) -> Duration { +3666 self.paths +3667 .keys() +3668 .map(|path_id| self.pto(space, *path_id)) +3669 .max() +3670 .unwrap_or_else(|| { +3671 // No paths remain (e.g. last path was abandoned and the NoAvailablePath grace timer +3672 // fired before any new path was opened). Fall back to a PTO derived from the +3673 // configured initial RTT, matching RFC 9002 §6.2.2 initial values. +3674 let rtt = self.config.initial_rtt; +3675 let max_ack_delay = match space { +3676 SpaceKind::Initial | SpaceKind::Handshake => Duration::ZERO, +3677 SpaceKind::Data => self.ack_frequency.max_ack_delay_for_pto(), +3678 }; +3679 rtt + cmp::max(4 * (rtt / 2), TIMER_GRANULARITY) + max_ack_delay +3680 }) +3681 } +3682 +3683 /// Probe Timeout +3684 /// +3685 /// The PTO is logically the time in which you'd expect to receive an acknowledgement +3686 /// for a packet. So approximately RTT + max_ack_delay. +3687 fn pto(&self, space: SpaceKind, path_id: PathId) -> Duration { +3688 let max_ack_delay = match space { +3689 SpaceKind::Initial | SpaceKind::Handshake => Duration::ZERO, +3690 SpaceKind::Data => self.ack_frequency.max_ack_delay_for_pto(), +3691 }; +3692 self.path_data(path_id).rtt.pto_base() + max_ack_delay +3693 } 3694 -3695 if x.is_ce() { -3696 space -3697 .for_path(path_id) -3698 .pending_acks -3699 .set_immediate_ack_required(); -3700 } -3701 } -3702 } -3703 -3704 let Some(packet_number) = packet_number else { -3705 return; -3706 }; -3707 match &self.side { -3708 ConnectionSide::Client { .. } => { -3709 // If we received a handshake packet that authenticated, then we're talking to -3710 // the real server. From now on we should no longer allow the server to migrate -3711 // its address. -3712 if space_id == SpaceKind::Handshake -3713 && let Some(hs) = self.state.as_handshake_mut() -3714 { -3715 hs.allow_server_migration = false; -3716 } -3717 } -3718 ConnectionSide::Server { .. } => { -3719 if self.crypto_state.has_keys(EncryptionLevel::Initial) -3720 && space_id == SpaceKind::Handshake -3721 { -3722 // A server stops sending and processing Initial packets when it receives its first Handshake packet. -3723 self.discard_space(now, SpaceKind::Initial); -3724 } -3725 if self.crypto_state.has_keys(EncryptionLevel::ZeroRtt) && is_1rtt { -3726 // Discard 0-RTT keys soon after receiving a 1-RTT packet -3727 self.set_key_discard_timer(now, space_id) -3728 } -3729 } -3730 } -3731 let space = self.spaces[space_id].for_path(path_id); -3732 -3733 space.pending_acks.insert_one(packet_number, now); -3734 if packet_number >= space.largest_received_packet_number.unwrap_or_default() { -3735 space.largest_received_packet_number = Some(packet_number); -3736 -3737 // Update outgoing spin bit for on-path packets, inverting iff we're the client -3738 if is_on_path { -3739 self.spin = self.side.is_client() ^ spin; -3740 } -3741 } -3742 } -3743 -3744 /// Resets the idle timeout timers -3745 /// -3746 /// Without multipath there is only the connection-wide idle timeout. When multipath is -3747 /// enabled there is an additional per-path idle timeout. -3748 fn reset_idle_timeout(&mut self, now: Instant, space: SpaceKind, path_id: PathId) { -3749 // First reset the global idle timeout. -3750 if let Some(timeout) = self.idle_timeout { -3751 if self.state.is_closed() { -3752 self.timers -3753 .stop(Timer::Conn(ConnTimer::Idle), self.qlog.with_time(now)); -3754 } else { -3755 let dt = cmp::max(timeout, 3 * self.max_pto_for_space(space)); -3756 self.timers.set( -3757 Timer::Conn(ConnTimer::Idle), -3758 now + dt, -3759 self.qlog.with_time(now), -3760 ); +3695 fn on_packet_authenticated( +3696 &mut self, +3697 now: Instant, +3698 space_id: SpaceKind, +3699 path_id: PathId, +3700 ecn: Option<EcnCodepoint>, +3701 packet_number: Option<u64>, +3702 spin: bool, +3703 is_1rtt: bool, +3704 remote: &FourTuple, +3705 ) { +3706 // During the handshake we already have discarded packets that do not match the path +3707 // remote. So any off-path packet here is either a probing packet or a +3708 // migration. Handling probing packets here means that the path's idle timeout will +3709 // be reset and will delay detecting the path as idle. However tail-loss probes +3710 // would still not get acknowledged if the path was broken so eventually the path +3711 // would still become idle. +3712 let is_on_path = *remote == self.path_data(path_id).network_path; +3713 +3714 self.total_authed_packets += 1; +3715 self.reset_keep_alive(path_id, now); +3716 self.reset_idle_timeout(now, space_id, path_id); +3717 self.path_data_mut(path_id).permit_idle_reset = true; +3718 +3719 // Do not process ECN for off-path packets. If this is a migration we'll get ECN +3720 // back once we've migrated. +3721 if is_on_path { +3722 self.receiving_ecn |= ecn.is_some(); +3723 if let Some(x) = ecn { +3724 let space = &mut self.spaces[space_id]; +3725 space.for_path(path_id).ecn_counters += x; +3726 +3727 if x.is_ce() { +3728 space +3729 .for_path(path_id) +3730 .pending_acks +3731 .set_immediate_ack_required(); +3732 } +3733 } +3734 } +3735 +3736 let Some(packet_number) = packet_number else { +3737 return; +3738 }; +3739 match &self.side { +3740 ConnectionSide::Client { .. } => { +3741 // If we received a handshake packet that authenticated, then we're talking to +3742 // the real server. From now on we should no longer allow the server to migrate +3743 // its address. +3744 if space_id == SpaceKind::Handshake +3745 && let Some(hs) = self.state.as_handshake_mut() +3746 { +3747 hs.allow_server_migration = false; +3748 } +3749 } +3750 ConnectionSide::Server { .. } => { +3751 if self.crypto_state.has_keys(EncryptionLevel::Initial) +3752 && space_id == SpaceKind::Handshake +3753 { +3754 // A server stops sending and processing Initial packets when it receives its first Handshake packet. +3755 self.discard_space(now, SpaceKind::Initial); +3756 } +3757 if self.crypto_state.has_keys(EncryptionLevel::ZeroRtt) && is_1rtt { +3758 // Discard 0-RTT keys soon after receiving a 1-RTT packet +3759 self.set_key_discard_timer(now, space_id) +3760 } 3761 } 3762 } -3763 -3764 // Now handle the per-path state -3765 if let Some(timeout) = self.path_data(path_id).idle_timeout { -3766 if self.state.is_closed() { -3767 self.timers.stop( -3768 Timer::PerPath(path_id, PathTimer::PathIdle), -3769 self.qlog.with_time(now), -3770 ); -3771 } else { -3772 let dt = cmp::max(timeout, 3 * self.pto(space, path_id)); -3773 self.timers.set( -3774 Timer::PerPath(path_id, PathTimer::PathIdle), -3775 now + dt, -3776 self.qlog.with_time(now), -3777 ); -3778 } -3779 } -3780 } -3781 -3782 /// Resets both the [`ConnTimer::KeepAlive`] and [`PathTimer::PathKeepAlive`] timers -3783 fn reset_keep_alive(&mut self, path_id: PathId, now: Instant) { -3784 if !self.state.is_established() { -3785 return; -3786 } -3787 -3788 if let Some(interval) = self.config.keep_alive_interval { -3789 self.timers.set( -3790 Timer::Conn(ConnTimer::KeepAlive), -3791 now + interval, -3792 self.qlog.with_time(now), -3793 ); +3763 let space = self.spaces[space_id].for_path(path_id); +3764 +3765 space.pending_acks.insert_one(packet_number, now); +3766 if packet_number >= space.largest_received_packet_number.unwrap_or_default() { +3767 space.largest_received_packet_number = Some(packet_number); +3768 +3769 // Update outgoing spin bit for on-path packets, inverting iff we're the client +3770 if is_on_path { +3771 self.spin = self.side.is_client() ^ spin; +3772 } +3773 } +3774 } +3775 +3776 /// Resets the idle timeout timers +3777 /// +3778 /// Without multipath there is only the connection-wide idle timeout. When multipath is +3779 /// enabled there is an additional per-path idle timeout. +3780 fn reset_idle_timeout(&mut self, now: Instant, space: SpaceKind, path_id: PathId) { +3781 // First reset the global idle timeout. +3782 if let Some(timeout) = self.idle_timeout { +3783 if self.state.is_closed() { +3784 self.timers +3785 .stop(Timer::Conn(ConnTimer::Idle), self.qlog.with_time(now)); +3786 } else { +3787 let dt = cmp::max(timeout, 3 * self.max_pto_for_space(space)); +3788 self.timers.set( +3789 Timer::Conn(ConnTimer::Idle), +3790 now + dt, +3791 self.qlog.with_time(now), +3792 ); +3793 } 3794 } 3795 -3796 if let Some(interval) = self.path_data(path_id).keep_alive { -3797 self.timers.set( -3798 Timer::PerPath(path_id, PathTimer::PathKeepAlive), -3799 now + interval, -3800 self.qlog.with_time(now), -3801 ); -3802 } -3803 } -3804 -3805 /// Sets the timer for when a previously issued CID should be retired next -3806 fn reset_cid_retirement(&mut self, now: Instant) { -3807 if let Some((_path, t)) = self.next_cid_retirement() { -3808 self.timers.set( -3809 Timer::Conn(ConnTimer::PushNewCid), -3810 t, -3811 self.qlog.with_time(now), -3812 ); -3813 } -3814 } -3815 -3816 /// The next time when a previously issued CID should be retired -3817 fn next_cid_retirement(&self) -> Option<(PathId, Instant)> { -3818 self.local_cid_state -3819 .iter() -3820 .filter_map(|(path_id, cid_state)| cid_state.next_timeout().map(|t| (*path_id, t))) -3821 .min_by_key(|(_path_id, timeout)| *timeout) -3822 } -3823 -3824 /// Handle the already-decrypted first packet from the client -3825 /// -3826 /// Decrypting the first packet in the `Endpoint` allows stateless packet handling to be more -3827 /// efficient. -3828 pub(crate) fn handle_first_packet( -3829 &mut self, -3830 now: Instant, -3831 network_path: FourTuple, -3832 ecn: Option<EcnCodepoint>, -3833 packet_number: u64, -3834 packet: InitialPacket, -3835 remaining: Option<BytesMut>, -3836 ) -> Result<(), ConnectionError> { -3837 let span = trace_span!("first recv"); -3838 let _guard = span.enter(); -3839 debug_assert!(self.side.is_server()); -3840 let len = packet.header_data.len() + packet.payload.len(); -3841 let path_id = PathId::ZERO; -3842 self.path_data_mut(path_id).total_recvd = len as u64; -3843 -3844 if let Some(hs) = self.state.as_handshake_mut() { -3845 hs.expected_token = packet.header.token.clone(); -3846 } else { -3847 unreachable!("first packet must be delivered in Handshake state"); -3848 } -3849 -3850 // The first packet is always on PathId::ZERO -3851 self.on_packet_authenticated( -3852 now, -3853 SpaceKind::Initial, -3854 path_id, -3855 ecn, -3856 Some(packet_number), -3857 false, -3858 false, -3859 &network_path, -3860 ); -3861 -3862 let packet: Packet = packet.into(); -3863 -3864 let mut qlog = QlogRecvPacket::new(len); -3865 qlog.header(&packet.header, Some(packet_number), path_id); -3866 -3867 self.process_decrypted_packet( -3868 now, -3869 network_path, -3870 path_id, -3871 Some(packet_number), -3872 packet, -3873 &mut qlog, -3874 )?; -3875 self.qlog.emit_packet_received(qlog, now); -3876 if let Some(data) = remaining { -3877 self.handle_coalesced(now, network_path, path_id, ecn, data); -3878 } -3879 -3880 self.qlog.emit_recovery_metrics( -3881 path_id, -3882 &mut self.paths.get_mut(&path_id).unwrap().data, -3883 now, -3884 ); -3885 -3886 Ok(()) -3887 } -3888 -3889 fn init_0rtt(&mut self, now: Instant) { -3890 let Some((header, packet)) = self.crypto_state.session.early_crypto() else { -3891 return; -3892 }; -3893 if self.side.is_client() { -3894 match self.crypto_state.session.transport_parameters() { -3895 Ok(params) => { -3896 let params = params -3897 .expect("crypto layer didn't supply transport parameters with ticket"); -3898 // Certain values must not be cached -3899 let params = TransportParameters { -3900 initial_src_cid: None, -3901 original_dst_cid: None, -3902 preferred_address: None, -3903 retry_src_cid: None, -3904 stateless_reset_token: None, -3905 min_ack_delay: None, -3906 ack_delay_exponent: TransportParameters::default().ack_delay_exponent, -3907 max_ack_delay: TransportParameters::default().max_ack_delay, -3908 initial_max_path_id: None, -3909 ..params -3910 }; -3911 self.set_peer_params(params); -3912 self.qlog.emit_peer_transport_params_restored(self, now); -3913 } -3914 Err(e) => { -3915 error!("session ticket has malformed transport parameters: {}", e); -3916 return; -3917 } -3918 } -3919 } -3920 trace!("0-RTT enabled"); -3921 self.crypto_state.enable_zero_rtt(header, packet); -3922 } -3923 -3924 fn read_crypto( -3925 &mut self, -3926 space: SpaceId, -3927 crypto: &frame::Crypto, -3928 payload_len: usize, -3929 ) -> Result<(), TransportError> { -3930 let expected = if !self.state.is_handshake() { -3931 SpaceId::Data -3932 } else if self.highest_space == SpaceKind::Initial { -3933 SpaceId::Initial -3934 } else { -3935 // On the server, self.highest_space can be Data after receiving the client's first -3936 // flight, but we expect Handshake CRYPTO until the handshake is complete. -3937 SpaceId::Handshake -3938 }; -3939 // We can't decrypt Handshake packets when highest_space is Initial, CRYPTO frames in 0-RTT -3940 // packets are illegal, and we don't process 1-RTT packets until the handshake is -3941 // complete. Therefore, we will never see CRYPTO data from a later-than-expected space. -3942 debug_assert!(space <= expected, "received out-of-order CRYPTO data"); -3943 -3944 let end = crypto.offset + crypto.data.len() as u64; -3945 if space < expected -3946 && end -3947 > self.crypto_state.spaces[space.kind()] -3948 .crypto_stream -3949 .bytes_read() -3950 { -3951 warn!( -3952 "received new {:?} CRYPTO data when expecting {:?}", -3953 space, expected -3954 ); -3955 return Err(TransportError::PROTOCOL_VIOLATION( -3956 "new data at unexpected encryption level", -3957 )); -3958 } +3796 // Now handle the per-path state +3797 if let Some(timeout) = self.path_data(path_id).idle_timeout { +3798 if self.state.is_closed() { +3799 self.timers.stop( +3800 Timer::PerPath(path_id, PathTimer::PathIdle), +3801 self.qlog.with_time(now), +3802 ); +3803 } else { +3804 let dt = cmp::max(timeout, 3 * self.pto(space, path_id)); +3805 self.timers.set( +3806 Timer::PerPath(path_id, PathTimer::PathIdle), +3807 now + dt, +3808 self.qlog.with_time(now), +3809 ); +3810 } +3811 } +3812 } +3813 +3814 /// Resets both the [`ConnTimer::KeepAlive`] and [`PathTimer::PathKeepAlive`] timers +3815 fn reset_keep_alive(&mut self, path_id: PathId, now: Instant) { +3816 if !self.state.is_established() { +3817 return; +3818 } +3819 +3820 if let Some(interval) = self.config.keep_alive_interval { +3821 self.timers.set( +3822 Timer::Conn(ConnTimer::KeepAlive), +3823 now + interval, +3824 self.qlog.with_time(now), +3825 ); +3826 } +3827 +3828 if let Some(interval) = self.path_data(path_id).keep_alive { +3829 self.timers.set( +3830 Timer::PerPath(path_id, PathTimer::PathKeepAlive), +3831 now + interval, +3832 self.qlog.with_time(now), +3833 ); +3834 } +3835 } +3836 +3837 /// Sets the timer for when a previously issued CID should be retired next +3838 fn reset_cid_retirement(&mut self, now: Instant) { +3839 if let Some((_path, t)) = self.next_cid_retirement() { +3840 self.timers.set( +3841 Timer::Conn(ConnTimer::PushNewCid), +3842 t, +3843 self.qlog.with_time(now), +3844 ); +3845 } +3846 } +3847 +3848 /// The next time when a previously issued CID should be retired +3849 fn next_cid_retirement(&self) -> Option<(PathId, Instant)> { +3850 self.local_cid_state +3851 .iter() +3852 .filter_map(|(path_id, cid_state)| cid_state.next_timeout().map(|t| (*path_id, t))) +3853 .min_by_key(|(_path_id, timeout)| *timeout) +3854 } +3855 +3856 /// Handle the already-decrypted first packet from the client +3857 /// +3858 /// Decrypting the first packet in the `Endpoint` allows stateless packet handling to be more +3859 /// efficient. +3860 pub(crate) fn handle_first_packet( +3861 &mut self, +3862 now: Instant, +3863 network_path: FourTuple, +3864 ecn: Option<EcnCodepoint>, +3865 packet_number: u64, +3866 packet: InitialPacket, +3867 remaining: Option<BytesMut>, +3868 ) -> Result<(), ConnectionError> { +3869 let span = trace_span!("first recv"); +3870 let _guard = span.enter(); +3871 debug_assert!(self.side.is_server()); +3872 let len = packet.header_data.len() + packet.payload.len(); +3873 let path_id = PathId::ZERO; +3874 self.path_data_mut(path_id).total_recvd = len as u64; +3875 +3876 if let Some(hs) = self.state.as_handshake_mut() { +3877 hs.expected_token = packet.header.token.clone(); +3878 } else { +3879 unreachable!("first packet must be delivered in Handshake state"); +3880 } +3881 +3882 // The first packet is always on PathId::ZERO +3883 self.on_packet_authenticated( +3884 now, +3885 SpaceKind::Initial, +3886 path_id, +3887 ecn, +3888 Some(packet_number), +3889 false, +3890 false, +3891 &network_path, +3892 ); +3893 +3894 let packet: Packet = packet.into(); +3895 +3896 let mut qlog = QlogRecvPacket::new(len); +3897 qlog.header(&packet.header, Some(packet_number), path_id); +3898 +3899 self.process_decrypted_packet( +3900 now, +3901 network_path, +3902 path_id, +3903 Some(packet_number), +3904 packet, +3905 &mut qlog, +3906 )?; +3907 self.qlog.emit_packet_received(qlog, now); +3908 if let Some(data) = remaining { +3909 self.handle_coalesced(now, network_path, path_id, ecn, data); +3910 } +3911 +3912 self.qlog.emit_recovery_metrics( +3913 path_id, +3914 &mut self +3915 .paths +3916 .get_mut(&path_id) +3917 .expect("path_id was supplied by the caller for an active path") +3918 .data, +3919 now, +3920 ); +3921 +3922 Ok(()) +3923 } +3924 +3925 fn init_0rtt(&mut self, now: Instant) { +3926 let Some((header, packet)) = self.crypto_state.session.early_crypto() else { +3927 return; +3928 }; +3929 if self.side.is_client() { +3930 match self.crypto_state.session.transport_parameters() { +3931 Ok(params) => { +3932 let params = params +3933 .expect("crypto layer didn't supply transport parameters with ticket"); +3934 // Certain values must not be cached +3935 let params = TransportParameters { +3936 initial_src_cid: None, +3937 original_dst_cid: None, +3938 preferred_address: None, +3939 retry_src_cid: None, +3940 stateless_reset_token: None, +3941 min_ack_delay: None, +3942 ack_delay_exponent: TransportParameters::default().ack_delay_exponent, +3943 max_ack_delay: TransportParameters::default().max_ack_delay, +3944 initial_max_path_id: None, +3945 ..params +3946 }; +3947 self.set_peer_params(params); +3948 self.qlog.emit_peer_transport_params_restored(self, now); +3949 } +3950 Err(e) => { +3951 error!("session ticket has malformed transport parameters: {}", e); +3952 return; +3953 } +3954 } +3955 } +3956 trace!("0-RTT enabled"); +3957 self.crypto_state.enable_zero_rtt(header, packet); +3958 } 3959 -3960 let crypto_space = &mut self.crypto_state.spaces[space.kind()]; -3961 let max = end.saturating_sub(crypto_space.crypto_stream.bytes_read()); -3962 if max > self.config.crypto_buffer_size as u64 { -3963 return Err(TransportError::CRYPTO_BUFFER_EXCEEDED("")); -3964 } -3965 -3966 crypto_space -3967 .crypto_stream -3968 .insert(crypto.offset, crypto.data.clone(), payload_len); -3969 while let Some(chunk) = crypto_space.crypto_stream.read(usize::MAX, true) { -3970 trace!("consumed {} CRYPTO bytes", chunk.bytes.len()); -3971 if self.crypto_state.session.read_handshake(&chunk.bytes)? { -3972 self.events.push_back(Event::HandshakeDataReady); -3973 } -3974 } -3975 -3976 Ok(()) -3977 } -3978 -3979 fn write_crypto(&mut self) { -3980 loop { -3981 let space = self.highest_space; -3982 let mut outgoing = Vec::new(); -3983 if let Some(crypto) = self.crypto_state.session.write_handshake(&mut outgoing) { -3984 match space { -3985 SpaceKind::Initial => { -3986 self.upgrade_crypto(SpaceKind::Handshake, crypto); -3987 } -3988 SpaceKind::Handshake => { -3989 self.upgrade_crypto(SpaceKind::Data, crypto); -3990 } -3991 SpaceKind::Data => unreachable!("got updated secrets during 1-RTT"), -3992 } -3993 } -3994 if outgoing.is_empty() { -3995 if space == self.highest_space { -3996 break; -3997 } else { -3998 // Keys updated, check for more data to send -3999 continue; -4000 } -4001 } -4002 let offset = self.crypto_state.spaces[space].crypto_offset; -4003 let outgoing = Bytes::from(outgoing); -4004 if let Some(hs) = self.state.as_handshake_mut() -4005 && space == SpaceKind::Initial -4006 && offset == 0 -4007 && self.side.is_client() -4008 { -4009 hs.client_hello = Some(outgoing.clone()); -4010 } -4011 self.crypto_state.spaces[space].crypto_offset += outgoing.len() as u64; -4012 trace!("wrote {} {:?} CRYPTO bytes", outgoing.len(), space); -4013 self.spaces[space].pending.crypto.push_back(frame::Crypto { -4014 offset, -4015 data: outgoing, -4016 }); -4017 } -4018 } -4019 -4020 /// Switch to stronger cryptography during handshake -4021 fn upgrade_crypto(&mut self, space: SpaceKind, crypto: Keys) { -4022 debug_assert!( -4023 !self.crypto_state.has_keys(space.encryption_level()), -4024 "already reached packet space {space:?}" -4025 ); -4026 trace!("{:?} keys ready", space); -4027 if space == SpaceKind::Data { -4028 // Precompute the first key update -4029 self.crypto_state.next_crypto = Some( -4030 self.crypto_state -4031 .session -4032 .next_1rtt_keys() -4033 .expect("handshake should be complete"), -4034 ); -4035 } -4036 -4037 self.crypto_state.spaces[space].keys = Some(crypto); -4038 debug_assert!(space > self.highest_space); -4039 self.highest_space = space; -4040 if space == SpaceKind::Data && self.side.is_client() { -4041 // Discard 0-RTT keys because 1-RTT keys are available. -4042 self.crypto_state.discard_zero_rtt(); -4043 } -4044 } -4045 -4046 fn discard_space(&mut self, now: Instant, space: SpaceKind) { -4047 debug_assert!(space != SpaceKind::Data); -4048 trace!("discarding {:?} keys", space); -4049 if space == SpaceKind::Initial { -4050 // No longer needed -4051 if let ConnectionSide::Client { token, .. } = &mut self.side { -4052 *token = Bytes::new(); -4053 } -4054 } -4055 self.crypto_state.spaces[space].keys = None; -4056 let space = &mut self.spaces[space]; -4057 let pns = space.for_path(PathId::ZERO); -4058 pns.time_of_last_ack_eliciting_packet = None; -4059 pns.loss_time = None; -4060 pns.loss_probes = 0; -4061 let sent_packets = mem::take(&mut pns.sent_packets); -4062 let path = self.paths.get_mut(&PathId::ZERO).unwrap(); -4063 for (_, packet) in sent_packets.into_iter() { -4064 path.data.remove_in_flight(&packet); -4065 } -4066 -4067 self.set_loss_detection_timer(now, PathId::ZERO) -4068 } -4069 -4070 fn handle_coalesced( -4071 &mut self, -4072 now: Instant, -4073 network_path: FourTuple, -4074 path_id: PathId, -4075 ecn: Option<EcnCodepoint>, -4076 data: BytesMut, -4077 ) { -4078 self.path_data_mut(path_id) -4079 .inc_total_recvd(data.len() as u64); -4080 let mut remaining = Some(data); -4081 let cid_len = self -4082 .local_cid_state -4083 .values() -4084 .map(|cid_state| cid_state.cid_len()) -4085 .next() -4086 .expect("one cid_state must exist"); -4087 while let Some(data) = remaining { -4088 match PartialDecode::new( -4089 data, -4090 &FixedLengthConnectionIdParser::new(cid_len), -4091 &[self.version], -4092 self.endpoint_config.grease_quic_bit, -4093 ) { -4094 Ok((partial_decode, rest)) => { -4095 remaining = rest; -4096 self.handle_decode(now, network_path, path_id, ecn, partial_decode); -4097 } -4098 Err(e) => { -4099 trace!("malformed header: {}", e); -4100 return; -4101 } -4102 } -4103 } -4104 } +3960 fn read_crypto( +3961 &mut self, +3962 space: SpaceId, +3963 crypto: &frame::Crypto, +3964 payload_len: usize, +3965 ) -> Result<(), TransportError> { +3966 let expected = if !self.state.is_handshake() { +3967 SpaceId::Data +3968 } else if self.highest_space == SpaceKind::Initial { +3969 SpaceId::Initial +3970 } else { +3971 // On the server, self.highest_space can be Data after receiving the client's first +3972 // flight, but we expect Handshake CRYPTO until the handshake is complete. +3973 SpaceId::Handshake +3974 }; +3975 // We can't decrypt Handshake packets when highest_space is Initial, CRYPTO frames in 0-RTT +3976 // packets are illegal, and we don't process 1-RTT packets until the handshake is +3977 // complete. Therefore, we will never see CRYPTO data from a later-than-expected space. +3978 debug_assert!(space <= expected, "received out-of-order CRYPTO data"); +3979 +3980 let end = crypto.offset + crypto.data.len() as u64; +3981 if space < expected +3982 && end +3983 > self.crypto_state.spaces[space.kind()] +3984 .crypto_stream +3985 .bytes_read() +3986 { +3987 warn!( +3988 "received new {:?} CRYPTO data when expecting {:?}", +3989 space, expected +3990 ); +3991 return Err(TransportError::PROTOCOL_VIOLATION( +3992 "new data at unexpected encryption level", +3993 )); +3994 } +3995 +3996 let crypto_space = &mut self.crypto_state.spaces[space.kind()]; +3997 let max = end.saturating_sub(crypto_space.crypto_stream.bytes_read()); +3998 if max > self.config.crypto_buffer_size as u64 { +3999 return Err(TransportError::CRYPTO_BUFFER_EXCEEDED("")); +4000 } +4001 +4002 crypto_space +4003 .crypto_stream +4004 .insert(crypto.offset, crypto.data.clone(), payload_len); +4005 while let Some(chunk) = crypto_space.crypto_stream.read(usize::MAX, true) { +4006 trace!("consumed {} CRYPTO bytes", chunk.bytes.len()); +4007 if self.crypto_state.session.read_handshake(&chunk.bytes)? { +4008 self.events.push_back(Event::HandshakeDataReady); +4009 } +4010 } +4011 +4012 Ok(()) +4013 } +4014 +4015 fn write_crypto(&mut self) { +4016 loop { +4017 let space = self.highest_space; +4018 let mut outgoing = Vec::new(); +4019 if let Some(crypto) = self.crypto_state.session.write_handshake(&mut outgoing) { +4020 match space { +4021 SpaceKind::Initial => { +4022 self.upgrade_crypto(SpaceKind::Handshake, crypto); +4023 } +4024 SpaceKind::Handshake => { +4025 self.upgrade_crypto(SpaceKind::Data, crypto); +4026 } +4027 SpaceKind::Data => unreachable!("got updated secrets during 1-RTT"), +4028 } +4029 } +4030 if outgoing.is_empty() { +4031 if space == self.highest_space { +4032 break; +4033 } else { +4034 // Keys updated, check for more data to send +4035 continue; +4036 } +4037 } +4038 let offset = self.crypto_state.spaces[space].crypto_offset; +4039 let outgoing = Bytes::from(outgoing); +4040 if let Some(hs) = self.state.as_handshake_mut() +4041 && space == SpaceKind::Initial +4042 && offset == 0 +4043 && self.side.is_client() +4044 { +4045 hs.client_hello = Some(outgoing.clone()); +4046 } +4047 self.crypto_state.spaces[space].crypto_offset += outgoing.len() as u64; +4048 trace!("wrote {} {:?} CRYPTO bytes", outgoing.len(), space); +4049 self.spaces[space].pending.crypto.push_back(frame::Crypto { +4050 offset, +4051 data: outgoing, +4052 }); +4053 } +4054 } +4055 +4056 /// Switch to stronger cryptography during handshake +4057 fn upgrade_crypto(&mut self, space: SpaceKind, crypto: Keys) { +4058 debug_assert!( +4059 !self.crypto_state.has_keys(space.encryption_level()), +4060 "already reached packet space {space:?}" +4061 ); +4062 trace!("{:?} keys ready", space); +4063 if space == SpaceKind::Data { +4064 // Precompute the first key update +4065 self.crypto_state.next_crypto = Some( +4066 self.crypto_state +4067 .session +4068 .next_1rtt_keys() +4069 .expect("handshake should be complete"), +4070 ); +4071 } +4072 +4073 self.crypto_state.spaces[space].keys = Some(crypto); +4074 debug_assert!(space > self.highest_space); +4075 self.highest_space = space; +4076 if space == SpaceKind::Data && self.side.is_client() { +4077 // Discard 0-RTT keys because 1-RTT keys are available. +4078 self.crypto_state.discard_zero_rtt(); +4079 } +4080 } +4081 +4082 fn discard_space(&mut self, now: Instant, space: SpaceKind) { +4083 debug_assert!(space != SpaceKind::Data); +4084 trace!("discarding {:?} keys", space); +4085 if space == SpaceKind::Initial { +4086 // No longer needed +4087 if let ConnectionSide::Client { token, .. } = &mut self.side { +4088 *token = Bytes::new(); +4089 } +4090 } +4091 self.crypto_state.spaces[space].keys = None; +4092 let space = &mut self.spaces[space]; +4093 let pns = space.for_path(PathId::ZERO); +4094 pns.time_of_last_ack_eliciting_packet = None; +4095 pns.loss_time = None; +4096 pns.loss_probes = 0; +4097 let sent_packets = mem::take(&mut pns.sent_packets); +4098 let path = self +4099 .paths +4100 .get_mut(&PathId::ZERO) +4101 .expect("PathId::ZERO is alive while Initial/Handshake spaces exist"); +4102 for (_, packet) in sent_packets.into_iter() { +4103 path.data.remove_in_flight(&packet); +4104 } 4105 -4106 /// Decrypts the packet and processes the payload. -4107 /// -4108 /// Processes the entire packet, starting with removing header protection, then handling -4109 /// a stateless reset if needed, and decrypting and processing the frames in the payload -4110 /// if not a stateless reset. -4111 fn handle_decode( -4112 &mut self, -4113 now: Instant, -4114 network_path: FourTuple, -4115 path_id: PathId, -4116 ecn: Option<EcnCodepoint>, -4117 partial_decode: PartialDecode, -4118 ) { -4119 let qlog = QlogRecvPacket::new(partial_decode.len()); -4120 if let Some(decoded) = self -4121 .crypto_state -4122 .unprotect_header(partial_decode, self.peer_params.stateless_reset_token) -4123 { -4124 self.handle_packet( -4125 now, -4126 network_path, -4127 path_id, -4128 ecn, -4129 decoded.packet, -4130 decoded.stateless_reset, -4131 qlog, -4132 ); -4133 } -4134 } -4135 -4136 /// Handles a packet with header protection removed. -4137 /// -4138 /// The packet body is still encrypted at this point. -4139 /// -4140 /// If the datagram was a stateless reset we may have failed to remove header protection -4141 /// and thus `packet` may be `None`. -4142 fn handle_packet( -4143 &mut self, -4144 now: Instant, -4145 network_path: FourTuple, -4146 path_id: PathId, -4147 ecn: Option<EcnCodepoint>, -4148 packet: Option<Packet>, -4149 stateless_reset: bool, -4150 mut qlog: QlogRecvPacket, -4151 ) { -4152 self.path_stats.for_path(path_id).udp_rx.ios += 1; -4153 -4154 if let Some(ref packet) = packet { -4155 trace!( -4156 "got {:?} packet ({} bytes) from {} using id {}", -4157 packet.header.space(), -4158 packet.payload.len() + packet.header_data.len(), -4159 network_path, -4160 packet.header.dst_cid(), -4161 ); -4162 } -4163 -4164 let was_closed = self.state.is_closed(); -4165 let was_drained = self.state.is_drained(); -4166 -4167 // Now decrypt the packet payload in-place. -4168 let decrypted = match packet { -4169 None => Err(None), -4170 Some(mut packet) => self -4171 .decrypt_packet(now, path_id, &mut packet) -4172 .map(move |number| (packet, number)), -4173 }; -4174 let result = match decrypted { -4175 _ if stateless_reset => { -4176 debug!("got stateless reset"); -4177 Err(ConnectionError::Reset) -4178 } -4179 Err(Some(e)) => { -4180 warn!("illegal packet: {}", e); -4181 Err(e.into()) -4182 } -4183 Err(None) => { -4184 debug!("failed to authenticate packet"); -4185 self.authentication_failures += 1; -4186 let integrity_limit = self -4187 .crypto_state -4188 .integrity_limit(self.highest_space) -4189 .unwrap(); -4190 if self.authentication_failures > integrity_limit { -4191 Err(TransportError::AEAD_LIMIT_REACHED("integrity limit violated").into()) -4192 } else { -4193 return; -4194 } -4195 } -4196 Ok((packet, number)) => { -4197 // We received an authenticated packet and decrypted it. -4198 qlog.header(&packet.header, number, path_id); -4199 let span = match number { -4200 Some(pn) => trace_span!("recv", space = ?packet.header.space(), pn), -4201 None => trace_span!("recv", space = ?packet.header.space()), -4202 }; -4203 let _guard = span.enter(); -4204 -4205 // Now the packet is authenticated we do the migration during the -4206 // handshake. See Handshake::allow_server_migration for details. -4207 if self.is_handshaking() && network_path != self.path_data_mut(path_id).network_path -4208 { -4209 if let Some(hs) = self.state.as_handshake() -4210 && hs.allow_server_migration -4211 { -4212 trace!( -4213 %network_path, -4214 prev = %self.path_data(path_id).network_path, -4215 "server migrated to new remote", -4216 ); -4217 self.path_data_mut(path_id).network_path = network_path; -4218 self.qlog.emit_tuple_assigned(path_id, network_path, now); -4219 } else { -4220 debug!("discarding packet with unexpected remote during handshake"); -4221 return; -4222 } -4223 } -4224 -4225 let dedup = self.spaces[packet.header.space()] -4226 .path_space_mut(path_id) -4227 .map(|pns| &mut pns.dedup); -4228 if number.zip(dedup).is_some_and(|(n, d)| d.insert(n)) { -4229 debug!("discarding possible duplicate packet"); -4230 self.qlog.emit_packet_received(qlog, now); -4231 return; -4232 } else if self.state.is_handshake() && packet.header.is_short() { -4233 // TODO: SHOULD buffer these to improve reordering tolerance. -4234 trace!("dropping short packet during handshake"); -4235 self.qlog.emit_packet_received(qlog, now); -4236 return; -4237 } else { -4238 if let Header::Initial(InitialHeader { ref token, .. }) = packet.header -4239 && let Some(hs) = self.state.as_handshake() -4240 && self.side.is_server() -4241 && token != &hs.expected_token -4242 { -4243 // Clients must send the same retry token in every Initial. Initial -4244 // packets can be spoofed, so we discard rather than killing the -4245 // connection. -4246 warn!("discarding Initial with invalid retry token"); -4247 self.qlog.emit_packet_received(qlog, now); -4248 return; -4249 } -4250 -4251 if !self.state.is_closed() { -4252 let spin = match packet.header { -4253 Header::Short { spin, .. } => spin, -4254 _ => false, -4255 }; -4256 -4257 if self.side().is_server() && !self.abandoned_paths.contains(&path_id) { -4258 // Only the client is allowed to open paths -4259 self.ensure_path(path_id, network_path, now, number); -4260 } -4261 if self.paths.contains_key(&path_id) { -4262 self.on_packet_authenticated( -4263 now, -4264 packet.header.space(), -4265 path_id, -4266 ecn, -4267 number, -4268 spin, -4269 packet.header.is_1rtt(), -4270 &network_path, -4271 ); -4272 } -4273 } -4274 -4275 let res = self.process_decrypted_packet( -4276 now, -4277 network_path, -4278 path_id, -4279 number, -4280 packet, -4281 &mut qlog, -4282 ); -4283 -4284 self.qlog.emit_packet_received(qlog, now); -4285 res -4286 } -4287 } -4288 }; +4106 self.set_loss_detection_timer(now, PathId::ZERO) +4107 } +4108 +4109 fn handle_coalesced( +4110 &mut self, +4111 now: Instant, +4112 network_path: FourTuple, +4113 path_id: PathId, +4114 ecn: Option<EcnCodepoint>, +4115 data: BytesMut, +4116 ) { +4117 self.path_data_mut(path_id) +4118 .inc_total_recvd(data.len() as u64); +4119 let mut remaining = Some(data); +4120 let cid_len = self +4121 .local_cid_state +4122 .values() +4123 .map(|cid_state| cid_state.cid_len()) +4124 .next() +4125 .expect("one cid_state must exist"); +4126 while let Some(data) = remaining { +4127 match PartialDecode::new( +4128 data, +4129 &FixedLengthConnectionIdParser::new(cid_len), +4130 &[self.version], +4131 self.endpoint_config.grease_quic_bit, +4132 ) { +4133 Ok((partial_decode, rest)) => { +4134 remaining = rest; +4135 self.handle_decode(now, network_path, path_id, ecn, partial_decode); +4136 } +4137 Err(e) => { +4138 trace!("malformed header: {}", e); +4139 return; +4140 } +4141 } +4142 } +4143 } +4144 +4145 /// Decrypts the packet and processes the payload. +4146 /// +4147 /// Processes the entire packet, starting with removing header protection, then handling +4148 /// a stateless reset if needed, and decrypting and processing the frames in the payload +4149 /// if not a stateless reset. +4150 fn handle_decode( +4151 &mut self, +4152 now: Instant, +4153 network_path: FourTuple, +4154 path_id: PathId, +4155 ecn: Option<EcnCodepoint>, +4156 partial_decode: PartialDecode, +4157 ) { +4158 let qlog = QlogRecvPacket::new(partial_decode.len()); +4159 if let Some(decoded) = self +4160 .crypto_state +4161 .unprotect_header(partial_decode, self.peer_params.stateless_reset_token) +4162 { +4163 self.handle_packet( +4164 now, +4165 network_path, +4166 path_id, +4167 ecn, +4168 decoded.packet, +4169 decoded.stateless_reset, +4170 qlog, +4171 ); +4172 } +4173 } +4174 +4175 /// Handles a packet with header protection removed. +4176 /// +4177 /// The packet body is still encrypted at this point. +4178 /// +4179 /// If the datagram was a stateless reset we may have failed to remove header protection +4180 /// and thus `packet` may be `None`. +4181 fn handle_packet( +4182 &mut self, +4183 now: Instant, +4184 network_path: FourTuple, +4185 path_id: PathId, +4186 ecn: Option<EcnCodepoint>, +4187 packet: Option<Packet>, +4188 stateless_reset: bool, +4189 mut qlog: QlogRecvPacket, +4190 ) { +4191 self.path_stats.for_path(path_id).udp_rx.ios += 1; +4192 +4193 if let Some(ref packet) = packet { +4194 trace!( +4195 "got {:?} packet ({} bytes) from {} using id {}", +4196 packet.header.space(), +4197 packet.payload.len() + packet.header_data.len(), +4198 network_path, +4199 packet.header.dst_cid(), +4200 ); +4201 } +4202 +4203 let was_closed = self.state.is_closed(); +4204 let was_drained = self.state.is_drained(); +4205 +4206 // Now decrypt the packet payload in-place. +4207 let decrypted = match packet { +4208 None => Err(None), +4209 Some(mut packet) => self +4210 .decrypt_packet(now, path_id, &mut packet) +4211 .map(move |number| (packet, number)), +4212 }; +4213 let result = match decrypted { +4214 _ if stateless_reset => { +4215 debug!("got stateless reset"); +4216 Err(ConnectionError::Reset) +4217 } +4218 Err(Some(e)) => { +4219 warn!("illegal packet: {}", e); +4220 Err(e.into()) +4221 } +4222 Err(None) => { +4223 debug!("failed to authenticate packet"); +4224 self.authentication_failures += 1; +4225 let integrity_limit = self +4226 .crypto_state +4227 .integrity_limit(self.highest_space) +4228 .unwrap(); +4229 if self.authentication_failures > integrity_limit { +4230 Err(TransportError::AEAD_LIMIT_REACHED("integrity limit violated").into()) +4231 } else { +4232 return; +4233 } +4234 } +4235 Ok((packet, number)) => { +4236 // We received an authenticated packet and decrypted it. +4237 qlog.header(&packet.header, number, path_id); +4238 let span = match number { +4239 Some(pn) => trace_span!("recv", space = ?packet.header.space(), pn), +4240 None => trace_span!("recv", space = ?packet.header.space()), +4241 }; +4242 let _guard = span.enter(); +4243 +4244 // Now the packet is authenticated we do the migration during the +4245 // handshake. See Handshake::allow_server_migration for details. +4246 if self.is_handshaking() && network_path != self.path_data_mut(path_id).network_path +4247 { +4248 if let Some(hs) = self.state.as_handshake() +4249 && hs.allow_server_migration +4250 { +4251 trace!( +4252 %network_path, +4253 prev = %self.path_data(path_id).network_path, +4254 "server migrated to new remote", +4255 ); +4256 self.path_data_mut(path_id).network_path = network_path; +4257 self.qlog.emit_tuple_assigned(path_id, network_path, now); +4258 } else { +4259 debug!("discarding packet with unexpected remote during handshake"); +4260 return; +4261 } +4262 } +4263 +4264 let dedup = self.spaces[packet.header.space()] +4265 .path_space_mut(path_id) +4266 .map(|pns| &mut pns.dedup); +4267 if number.zip(dedup).is_some_and(|(n, d)| d.insert(n)) { +4268 debug!("discarding possible duplicate packet"); +4269 self.qlog.emit_packet_received(qlog, now); +4270 return; +4271 } else if self.state.is_handshake() && packet.header.is_short() { +4272 // TODO: SHOULD buffer these to improve reordering tolerance. +4273 trace!("dropping short packet during handshake"); +4274 self.qlog.emit_packet_received(qlog, now); +4275 return; +4276 } else { +4277 if let Header::Initial(InitialHeader { ref token, .. }) = packet.header +4278 && let Some(hs) = self.state.as_handshake() +4279 && self.side.is_server() +4280 && token != &hs.expected_token +4281 { +4282 // Clients must send the same retry token in every Initial. Initial +4283 // packets can be spoofed, so we discard rather than killing the +4284 // connection. +4285 warn!("discarding Initial with invalid retry token"); +4286 self.qlog.emit_packet_received(qlog, now); +4287 return; +4288 } 4289 -4290 // State transitions for error cases -4291 if let Err(conn_err) = result { -4292 match conn_err { -4293 ConnectionError::ApplicationClosed(reason) => self.state.move_to_closed(reason), -4294 ConnectionError::ConnectionClosed(reason) => self.state.move_to_closed(reason), -4295 ConnectionError::Reset -4296 | ConnectionError::TransportError(TransportError { -4297 code: TransportErrorCode::AEAD_LIMIT_REACHED, -4298 .. -4299 }) => { -4300 self.state.move_to_drained(Some(conn_err)); -4301 } -4302 ConnectionError::TimedOut => { -4303 unreachable!("timeouts aren't generated by packet processing"); -4304 } -4305 ConnectionError::TransportError(err) => { -4306 debug!("closing connection due to transport error: {}", err); -4307 self.state.move_to_closed(err); -4308 } -4309 ConnectionError::VersionMismatch => { -4310 self.state.move_to_draining(Some(conn_err)); -4311 } -4312 ConnectionError::LocallyClosed => { -4313 unreachable!("LocallyClosed isn't generated by packet processing"); -4314 } -4315 ConnectionError::CidsExhausted => { -4316 unreachable!("CidsExhausted isn't generated by packet processing"); -4317 } -4318 }; -4319 } -4320 -4321 if !was_closed && self.state.is_closed() { -4322 self.close_common(); -4323 if !self.state.is_drained() { -4324 self.set_close_timer(now); -4325 } -4326 } -4327 if !was_drained && self.state.is_drained() { -4328 self.endpoint_events.push_back(EndpointEventInner::Drained); -4329 // Close timer may have been started previously, e.g. if we sent a close and got a -4330 // stateless reset in response -4331 self.timers -4332 .stop(Timer::Conn(ConnTimer::Close), self.qlog.with_time(now)); -4333 } -4334 -4335 // Transmit CONNECTION_CLOSE if necessary. -4336 // -4337 // If we received a valid packet and we are in the closed state we should respond -4338 // with a CONNECTION_CLOSE frame. -4339 // TODO: This SHOULD be rate-limited according to §10.2.1 of QUIC-TRANSPORT, but -4340 // that does not yet happen. This is triggered by each received packet. -4341 if matches!(self.state.as_type(), StateType::Closed) { -4342 // From https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2.1-7 -4343 // -4344 // While in the closing state we must either: -4345 // - discard packets coming from an un-validated remote OR -4346 // - ensure we do not send more than 3 times the received data -4347 // -4348 // Doing the 2nd would mean we would be able to send CONNECTION_CLOSE to a peer -4349 // who was (involuntary) migrated just at the time we initiated immediate -4350 // close. It is a lot more work though. So while we would like to do this for -4351 // now we only do 1. -4352 // -4353 // Another shortcoming of the current implementation is that when we have a -4354 // previous PathData which is validated and the remote matches that path, we -4355 // should schedule CONNECTION_CLOSE on that path. However currently we can not -4356 // schedule such a packet. We should also fix this some day. This makes us -4357 // vulnerable to an attacker faking a migration at the right time and then we'd -4358 // be unable to send the CONNECTION_CLOSE to the real remote. -4359 if self -4360 .paths -4361 .get(&path_id) -4362 .map(|p| p.data.validated && p.data.network_path == network_path) -4363 .unwrap_or(false) -4364 { -4365 self.connection_close_pending = true; -4366 } -4367 } -4368 } -4369 -4370 fn process_decrypted_packet( -4371 &mut self, -4372 now: Instant, -4373 network_path: FourTuple, -4374 path_id: PathId, -4375 number: Option<u64>, -4376 packet: Packet, -4377 qlog: &mut QlogRecvPacket, -4378 ) -> Result<(), ConnectionError> { -4379 if !self.paths.contains_key(&path_id) { -4380 // There is a chance this is a server side, first (for this path) packet, which would -4381 // be a protocol violation. It's more likely, however, that this is a packet of a -4382 // pruned path -4383 trace!(%path_id, ?number, "discarding packet for unknown path"); -4384 return Ok(()); -4385 } -4386 let state = match self.state.as_type() { -4387 StateType::Established => { -4388 match packet.header.space() { -4389 SpaceKind::Data => self.process_payload( -4390 now, -4391 network_path, -4392 path_id, -4393 number.unwrap(), -4394 packet, -4395 qlog, -4396 )?, -4397 _ if packet.header.has_frames() => { -4398 self.process_early_payload(now, path_id, packet, qlog)? -4399 } -4400 _ => { -4401 trace!("discarding unexpected pre-handshake packet"); -4402 } -4403 } -4404 return Ok(()); +4290 if !self.state.is_closed() { +4291 let spin = match packet.header { +4292 Header::Short { spin, .. } => spin, +4293 _ => false, +4294 }; +4295 +4296 if self.side().is_server() && !self.abandoned_paths.contains(&path_id) { +4297 // Only the client is allowed to open paths +4298 self.ensure_path(path_id, network_path, now, number); +4299 } +4300 if self.paths.contains_key(&path_id) { +4301 self.on_packet_authenticated( +4302 now, +4303 packet.header.space(), +4304 path_id, +4305 ecn, +4306 number, +4307 spin, +4308 packet.header.is_1rtt(), +4309 &network_path, +4310 ); +4311 } +4312 } +4313 +4314 let res = self.process_decrypted_packet( +4315 now, +4316 network_path, +4317 path_id, +4318 number, +4319 packet, +4320 &mut qlog, +4321 ); +4322 +4323 self.qlog.emit_packet_received(qlog, now); +4324 res +4325 } +4326 } +4327 }; +4328 +4329 // State transitions for error cases +4330 if let Err(conn_err) = result { +4331 match conn_err { +4332 ConnectionError::ApplicationClosed(reason) => self.state.move_to_closed(reason), +4333 ConnectionError::ConnectionClosed(reason) => self.state.move_to_closed(reason), +4334 ConnectionError::Reset +4335 | ConnectionError::TransportError(TransportError { +4336 code: TransportErrorCode::AEAD_LIMIT_REACHED, +4337 .. +4338 }) => { +4339 self.state.move_to_drained(Some(conn_err)); +4340 } +4341 ConnectionError::TimedOut => { +4342 unreachable!("timeouts aren't generated by packet processing"); +4343 } +4344 ConnectionError::TransportError(err) => { +4345 debug!("closing connection due to transport error: {}", err); +4346 self.state.move_to_closed(err); +4347 } +4348 ConnectionError::VersionMismatch => { +4349 self.state.move_to_draining(Some(conn_err)); +4350 } +4351 ConnectionError::LocallyClosed => { +4352 unreachable!("LocallyClosed isn't generated by packet processing"); +4353 } +4354 ConnectionError::CidsExhausted => { +4355 unreachable!("CidsExhausted isn't generated by packet processing"); +4356 } +4357 }; +4358 } +4359 +4360 if !was_closed && self.state.is_closed() { +4361 self.close_common(); +4362 if !self.state.is_drained() { +4363 self.set_close_timer(now); +4364 } +4365 } +4366 if !was_drained && self.state.is_drained() { +4367 self.endpoint_events.push_back(EndpointEventInner::Drained); +4368 // Close timer may have been started previously, e.g. if we sent a close and got a +4369 // stateless reset in response +4370 self.timers +4371 .stop(Timer::Conn(ConnTimer::Close), self.qlog.with_time(now)); +4372 } +4373 +4374 // Transmit CONNECTION_CLOSE if necessary. +4375 // +4376 // If we received a valid packet and we are in the closed state we should respond +4377 // with a CONNECTION_CLOSE frame. +4378 // TODO: This SHOULD be rate-limited according to §10.2.1 of QUIC-TRANSPORT, but +4379 // that does not yet happen. This is triggered by each received packet. +4380 if matches!(self.state.as_type(), StateType::Closed) { +4381 // From https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2.1-7 +4382 // +4383 // While in the closing state we must either: +4384 // - discard packets coming from an un-validated remote OR +4385 // - ensure we do not send more than 3 times the received data +4386 // +4387 // Doing the 2nd would mean we would be able to send CONNECTION_CLOSE to a peer +4388 // who was (involuntary) migrated just at the time we initiated immediate +4389 // close. It is a lot more work though. So while we would like to do this for +4390 // now we only do 1. +4391 // +4392 // Another shortcoming of the current implementation is that when we have a +4393 // previous PathData which is validated and the remote matches that path, we +4394 // should schedule CONNECTION_CLOSE on that path. However currently we can not +4395 // schedule such a packet. We should also fix this some day. This makes us +4396 // vulnerable to an attacker faking a migration at the right time and then we'd +4397 // be unable to send the CONNECTION_CLOSE to the real remote. +4398 if self +4399 .paths +4400 .get(&path_id) +4401 .map(|p| p.data.validated && p.data.network_path == network_path) +4402 .unwrap_or(false) +4403 { +4404 self.connection_close_pending = true; 4405 } -4406 StateType::Closed => { -4407 for result in frame::Iter::new(packet.payload.freeze())? { -4408 let frame = match result { -4409 Ok(frame) => frame, -4410 Err(err) => { -4411 debug!("frame decoding error: {err:?}"); -4412 continue; -4413 } -4414 }; -4415 qlog.frame(&frame); -4416 -4417 if let Frame::Padding = frame { -4418 continue; -4419 }; -4420 -4421 self.path_stats -4422 .for_path(path_id) -4423 .frame_rx -4424 .record(frame.ty()); -4425 -4426 if let Frame::Close(_error) = frame { -4427 self.state.move_to_draining(None); -4428 break; -4429 } -4430 } -4431 return Ok(()); -4432 } -4433 StateType::Draining | StateType::Drained => return Ok(()), -4434 StateType::Handshake => self.state.as_handshake_mut().expect("checked"), -4435 }; -4436 -4437 match packet.header { -4438 Header::Retry { -4439 src_cid: remote_cid, -4440 .. -4441 } => { -4442 debug_assert_eq!(path_id, PathId::ZERO); -4443 if self.side.is_server() { -4444 return Err(TransportError::PROTOCOL_VIOLATION("client sent Retry").into()); -4445 } -4446 -4447 let is_valid_retry = self -4448 .remote_cids -4449 .get(&path_id) -4450 .map(|cids| cids.active()) -4451 .map(|orig_dst_cid| { -4452 self.crypto_state.session.is_valid_retry( -4453 orig_dst_cid, -4454 &packet.header_data, -4455 &packet.payload, -4456 ) -4457 }) -4458 .unwrap_or_default(); -4459 if self.total_authed_packets > 1 -4460 || packet.payload.len() <= 16 // token + 16 byte tag -4461 || !is_valid_retry -4462 { -4463 trace!("discarding invalid Retry"); -4464 // - After the client has received and processed an Initial or Retry -4465 // packet from the server, it MUST discard any subsequent Retry -4466 // packets that it receives. -4467 // - A client MUST discard a Retry packet with a zero-length Retry Token -4468 // field. -4469 // - Clients MUST discard Retry packets that have a Retry Integrity Tag -4470 // that cannot be validated -4471 return Ok(()); -4472 } -4473 -4474 trace!("retrying with CID {}", remote_cid); -4475 let client_hello = state.client_hello.take().unwrap(); -4476 self.retry_src_cid = Some(remote_cid); -4477 self.remote_cids -4478 .get_mut(&path_id) -4479 .expect("PathId::ZERO not yet abandoned, is_valid_retry would have been false") -4480 .update_initial_cid(remote_cid); -4481 self.remote_handshake_cid = remote_cid; -4482 -4483 let space = &mut self.spaces[SpaceId::Initial]; -4484 if let Some(info) = space.for_path(PathId::ZERO).take(0) { -4485 self.on_packet_acked(now, PathId::ZERO, 0, info); -4486 }; -4487 -4488 self.discard_space(now, SpaceKind::Initial); // Make sure we clean up after -4489 // any retransmitted Initials -4490 let crypto_space = &mut self.crypto_state.spaces[SpaceKind::Initial]; -4491 crypto_space.keys = Some( -4492 self.crypto_state -4493 .session -4494 .initial_keys(remote_cid, self.side.side()), -4495 ); -4496 crypto_space.crypto_offset = client_hello.len() as u64; -4497 -4498 let next_pn = self.spaces[SpaceId::Initial] -4499 .for_path(path_id) -4500 .next_packet_number; -4501 self.spaces[SpaceId::Initial] = { -4502 let mut space = PacketSpace::new(now, SpaceId::Initial, &mut self.rng); -4503 space.for_path(path_id).next_packet_number = next_pn; -4504 space.pending.crypto.push_back(frame::Crypto { -4505 offset: 0, -4506 data: client_hello, -4507 }); -4508 space -4509 }; -4510 -4511 // Retransmit all 0-RTT data -4512 let zero_rtt = mem::take( -4513 &mut self.spaces[SpaceId::Data] -4514 .for_path(PathId::ZERO) -4515 .sent_packets, -4516 ); -4517 for (_, info) in zero_rtt.into_iter() { -4518 self.paths -4519 .get_mut(&PathId::ZERO) -4520 .unwrap() -4521 .remove_in_flight(&info); -4522 self.spaces[SpaceId::Data].pending |= info.retransmits; -4523 } -4524 self.streams.retransmit_all_for_0rtt(); -4525 -4526 let token_len = packet.payload.len() - 16; -4527 let ConnectionSide::Client { ref mut token, .. } = self.side else { -4528 unreachable!("we already short-circuited if we're server"); -4529 }; -4530 *token = packet.payload.freeze().split_to(token_len); -4531 -4532 self.state = State::handshake(state::Handshake { -4533 expected_token: Bytes::new(), -4534 remote_cid_set: false, -4535 client_hello: None, -4536 allow_server_migration: true, -4537 }); -4538 Ok(()) -4539 } -4540 Header::Long { -4541 ty: LongType::Handshake, -4542 src_cid: remote_cid, -4543 dst_cid: local_cid, -4544 .. -4545 } => { -4546 debug_assert_eq!(path_id, PathId::ZERO); -4547 if remote_cid != self.remote_handshake_cid { -4548 debug!( -4549 "discarding packet with mismatched remote CID: {} != {}", -4550 self.remote_handshake_cid, remote_cid -4551 ); -4552 return Ok(()); -4553 } -4554 self.on_path_validated(path_id); -4555 -4556 self.process_early_payload(now, path_id, packet, qlog)?; -4557 if self.state.is_closed() { -4558 return Ok(()); -4559 } -4560 -4561 if self.crypto_state.session.is_handshaking() { -4562 trace!("handshake ongoing"); -4563 return Ok(()); -4564 } -4565 -4566 if self.side.is_client() { -4567 // Client-only because server params were set from the client's Initial -4568 let params = self -4569 .crypto_state -4570 .session -4571 .transport_parameters()? -4572 .ok_or_else(|| { -4573 TransportError::new( -4574 TransportErrorCode::crypto(0x6d), -4575 "transport parameters missing".to_owned(), -4576 ) -4577 })?; -4578 -4579 if self.has_0rtt() { -4580 if !self.crypto_state.session.early_data_accepted().unwrap() { -4581 debug_assert!(self.side.is_client()); -4582 debug!("0-RTT rejected"); -4583 self.crypto_state.accepted_0rtt = false; -4584 self.streams.zero_rtt_rejected(); -4585 -4586 // Discard already-queued frames -4587 self.spaces[SpaceId::Data].pending = Retransmits::default(); -4588 -4589 // Discard 0-RTT packets -4590 let sent_packets = mem::take( -4591 &mut self.spaces[SpaceId::Data].for_path(path_id).sent_packets, -4592 ); -4593 for (_, packet) in sent_packets.into_iter() { -4594 self.paths -4595 .get_mut(&path_id) -4596 .unwrap() -4597 .remove_in_flight(&packet); -4598 } -4599 } else { -4600 self.crypto_state.accepted_0rtt = true; -4601 params.validate_resumption_from(&self.peer_params)?; -4602 } -4603 } -4604 if let Some(token) = params.stateless_reset_token { -4605 let remote = self.path_data(path_id).network_path.remote; -4606 debug_assert!(!self.state.is_drained()); // requirement for endpoint events, checked above -4607 self.endpoint_events -4608 .push_back(EndpointEventInner::ResetToken(path_id, remote, token)); -4609 } -4610 self.handle_peer_params(params, local_cid, remote_cid, now)?; -4611 self.issue_first_cids(now); -4612 } else { -4613 // Server-only -4614 self.spaces[SpaceId::Data].pending.handshake_done = true; -4615 self.discard_space(now, SpaceKind::Handshake); -4616 self.events.push_back(Event::HandshakeConfirmed); -4617 trace!("handshake confirmed"); -4618 } -4619 -4620 self.events.push_back(Event::Connected); -4621 self.state.move_to_established(); -4622 trace!("established"); -4623 -4624 // Multipath can only be enabled after the state has reached Established. -4625 // So this can not happen any earlier. -4626 self.issue_first_path_cids(now); -4627 Ok(()) -4628 } -4629 Header::Initial(InitialHeader { -4630 src_cid: remote_cid, -4631 dst_cid: local_cid, -4632 .. -4633 }) => { -4634 debug_assert_eq!(path_id, PathId::ZERO); -4635 if !state.remote_cid_set { -4636 trace!("switching remote CID to {}", remote_cid); -4637 let mut state = state.clone(); -4638 self.remote_cids -4639 .get_mut(&path_id) -4640 .expect("PathId::ZERO not yet abandoned") -4641 .update_initial_cid(remote_cid); -4642 self.remote_handshake_cid = remote_cid; -4643 self.original_remote_cid = remote_cid; -4644 state.remote_cid_set = true; -4645 self.state.move_to_handshake(state); -4646 } else if remote_cid != self.remote_handshake_cid { -4647 debug!( -4648 "discarding packet with mismatched remote CID: {} != {}", -4649 self.remote_handshake_cid, remote_cid -4650 ); -4651 return Ok(()); -4652 } -4653 -4654 let starting_space = self.highest_space; -4655 self.process_early_payload(now, path_id, packet, qlog)?; -4656 -4657 if self.side.is_server() -4658 && starting_space == SpaceKind::Initial -4659 && self.highest_space != SpaceKind::Initial -4660 { -4661 let params = self -4662 .crypto_state -4663 .session -4664 .transport_parameters()? -4665 .ok_or_else(|| { -4666 TransportError::new( -4667 TransportErrorCode::crypto(0x6d), -4668 "transport parameters missing".to_owned(), -4669 ) -4670 })?; -4671 self.handle_peer_params(params, local_cid, remote_cid, now)?; -4672 self.issue_first_cids(now); -4673 self.init_0rtt(now); -4674 } -4675 Ok(()) -4676 } -4677 Header::Long { -4678 ty: LongType::ZeroRtt, -4679 .. -4680 } => { -4681 self.process_payload(now, network_path, path_id, number.unwrap(), packet, qlog)?; -4682 Ok(()) -4683 } -4684 Header::VersionNegotiate { .. } => { -4685 if self.total_authed_packets > 1 { -4686 return Ok(()); -4687 } -4688 let supported = packet -4689 .payload -4690 .chunks(4) -4691 .any(|x| match <[u8; 4]>::try_from(x) { -4692 Ok(version) => self.version == u32::from_be_bytes(version), -4693 Err(_) => false, -4694 }); -4695 if supported { -4696 return Ok(()); -4697 } -4698 debug!("remote doesn't support our version"); -4699 Err(ConnectionError::VersionMismatch) -4700 } -4701 Header::Short { .. } => unreachable!( -4702 "short packets received during handshake are discarded in handle_packet" -4703 ), -4704 } -4705 } -4706 -4707 /// Process an Initial or Handshake packet payload -4708 fn process_early_payload( -4709 &mut self, -4710 now: Instant, -4711 path_id: PathId, -4712 packet: Packet, -4713 #[allow(unused)] qlog: &mut QlogRecvPacket, -4714 ) -> Result<(), TransportError> { -4715 debug_assert_ne!(packet.header.space(), SpaceKind::Data); -4716 debug_assert_eq!(path_id, PathId::ZERO); -4717 let payload_len = packet.payload.len(); -4718 let mut ack_eliciting = false; -4719 for result in frame::Iter::new(packet.payload.freeze())? { -4720 let frame = result?; -4721 qlog.frame(&frame); -4722 let span = match frame { -4723 Frame::Padding => continue, -4724 _ => Some(trace_span!("frame", ty = %frame.ty(), path = tracing::field::Empty)), -4725 }; -4726 -4727 self.path_stats -4728 .for_path(path_id) -4729 .frame_rx -4730 .record(frame.ty()); -4731 -4732 let _guard = span.as_ref().map(|x| x.enter()); -4733 ack_eliciting |= frame.is_ack_eliciting(); -4734 -4735 // Process frames -4736 if frame.is_1rtt() && packet.header.space() != SpaceKind::Data { -4737 return Err(TransportError::PROTOCOL_VIOLATION( -4738 "illegal frame type in handshake", -4739 )); -4740 } -4741 -4742 match frame { -4743 Frame::Padding | Frame::Ping => {} -4744 Frame::Crypto(frame) => { -4745 self.read_crypto(packet.header.space().into(), &frame, payload_len)?; -4746 } -4747 Frame::Ack(ack) => { -4748 self.on_ack_received(now, packet.header.space().into(), ack)?; -4749 } -4750 Frame::PathAck(ack) => { -4751 span.as_ref() -4752 .map(|span| span.record("path", tracing::field::display(&ack.path_id))); -4753 self.on_path_ack_received(now, packet.header.space().into(), ack)?; -4754 } -4755 Frame::Close(reason) => { -4756 self.state.move_to_draining(Some(reason.into())); -4757 return Ok(()); -4758 } -4759 _ => { -4760 let mut err = -4761 TransportError::PROTOCOL_VIOLATION("illegal frame type in handshake"); -4762 err.frame = frame::MaybeFrame::Known(frame.ty()); -4763 return Err(err); -4764 } -4765 } -4766 } -4767 -4768 if ack_eliciting { -4769 // In the initial and handshake spaces, ACKs must be sent immediately -4770 self.spaces[packet.header.space()] -4771 .for_path(path_id) -4772 .pending_acks -4773 .set_immediate_ack_required(); -4774 } -4775 -4776 self.write_crypto(); -4777 Ok(()) -4778 } -4779 -4780 /// Processes the decrypted packet payload, always in the data space. -4781 fn process_payload( -4782 &mut self, -4783 now: Instant, -4784 network_path: FourTuple, -4785 path_id: PathId, -4786 number: u64, -4787 packet: Packet, -4788 #[allow(unused)] qlog: &mut QlogRecvPacket, -4789 ) -> Result<(), TransportError> { -4790 let is_multipath_negotiated = self.is_multipath_negotiated(); -4791 let payload = packet.payload.freeze(); -4792 let mut is_probing_packet = true; -4793 let mut close = None; -4794 let payload_len = payload.len(); -4795 let mut ack_eliciting = false; -4796 // if this packet triggers a path migration and includes a observed address frame, it's -4797 // stored here -4798 let mut migration_observed_addr = None; -4799 for result in frame::Iter::new(payload)? { -4800 let frame = result?; -4801 qlog.frame(&frame); -4802 let span = match frame { -4803 Frame::Padding => continue, -4804 _ => trace_span!("frame", ty = %frame.ty(), path = tracing::field::Empty), -4805 }; +4406 } +4407 } +4408 +4409 fn process_decrypted_packet( +4410 &mut self, +4411 now: Instant, +4412 network_path: FourTuple, +4413 path_id: PathId, +4414 number: Option<u64>, +4415 packet: Packet, +4416 qlog: &mut QlogRecvPacket, +4417 ) -> Result<(), ConnectionError> { +4418 if !self.paths.contains_key(&path_id) { +4419 // There is a chance this is a server side, first (for this path) packet, which would +4420 // be a protocol violation. It's more likely, however, that this is a packet of a +4421 // pruned path +4422 trace!(%path_id, ?number, "discarding packet for unknown path"); +4423 return Ok(()); +4424 } +4425 let state = match self.state.as_type() { +4426 StateType::Established => { +4427 match packet.header.space() { +4428 SpaceKind::Data => self.process_payload( +4429 now, +4430 network_path, +4431 path_id, +4432 number.unwrap(), +4433 packet, +4434 qlog, +4435 )?, +4436 _ if packet.header.has_frames() => { +4437 self.process_early_payload(now, path_id, packet, qlog)? +4438 } +4439 _ => { +4440 trace!("discarding unexpected pre-handshake packet"); +4441 } +4442 } +4443 return Ok(()); +4444 } +4445 StateType::Closed => { +4446 for result in frame::Iter::new(packet.payload.freeze())? { +4447 let frame = match result { +4448 Ok(frame) => frame, +4449 Err(err) => { +4450 debug!("frame decoding error: {err:?}"); +4451 continue; +4452 } +4453 }; +4454 qlog.frame(&frame); +4455 +4456 if let Frame::Padding = frame { +4457 continue; +4458 }; +4459 +4460 self.path_stats +4461 .for_path(path_id) +4462 .frame_rx +4463 .record(frame.ty()); +4464 +4465 if let Frame::Close(_error) = frame { +4466 self.state.move_to_draining(None); +4467 break; +4468 } +4469 } +4470 return Ok(()); +4471 } +4472 StateType::Draining | StateType::Drained => return Ok(()), +4473 StateType::Handshake => self.state.as_handshake_mut().expect("checked"), +4474 }; +4475 +4476 match packet.header { +4477 Header::Retry { +4478 src_cid: remote_cid, +4479 .. +4480 } => { +4481 debug_assert_eq!(path_id, PathId::ZERO); +4482 if self.side.is_server() { +4483 return Err(TransportError::PROTOCOL_VIOLATION("client sent Retry").into()); +4484 } +4485 +4486 let is_valid_retry = self +4487 .remote_cids +4488 .get(&path_id) +4489 .map(|cids| cids.active()) +4490 .map(|orig_dst_cid| { +4491 self.crypto_state.session.is_valid_retry( +4492 orig_dst_cid, +4493 &packet.header_data, +4494 &packet.payload, +4495 ) +4496 }) +4497 .unwrap_or_default(); +4498 if self.total_authed_packets > 1 +4499 || packet.payload.len() <= 16 // token + 16 byte tag +4500 || !is_valid_retry +4501 { +4502 trace!("discarding invalid Retry"); +4503 // - After the client has received and processed an Initial or Retry +4504 // packet from the server, it MUST discard any subsequent Retry +4505 // packets that it receives. +4506 // - A client MUST discard a Retry packet with a zero-length Retry Token +4507 // field. +4508 // - Clients MUST discard Retry packets that have a Retry Integrity Tag +4509 // that cannot be validated +4510 return Ok(()); +4511 } +4512 +4513 trace!("retrying with CID {}", remote_cid); +4514 let client_hello = state.client_hello.take().unwrap(); +4515 self.retry_src_cid = Some(remote_cid); +4516 self.remote_cids +4517 .get_mut(&path_id) +4518 .expect("PathId::ZERO not yet abandoned, is_valid_retry would have been false") +4519 .update_initial_cid(remote_cid); +4520 self.remote_handshake_cid = remote_cid; +4521 +4522 let space = &mut self.spaces[SpaceId::Initial]; +4523 if let Some(info) = space.for_path(PathId::ZERO).take(0) { +4524 self.on_packet_acked(now, PathId::ZERO, 0, info); +4525 }; +4526 +4527 self.discard_space(now, SpaceKind::Initial); // Make sure we clean up after +4528 // any retransmitted Initials +4529 let crypto_space = &mut self.crypto_state.spaces[SpaceKind::Initial]; +4530 crypto_space.keys = Some( +4531 self.crypto_state +4532 .session +4533 .initial_keys(remote_cid, self.side.side()), +4534 ); +4535 crypto_space.crypto_offset = client_hello.len() as u64; +4536 +4537 let next_pn = self.spaces[SpaceId::Initial] +4538 .for_path(path_id) +4539 .next_packet_number; +4540 self.spaces[SpaceId::Initial] = { +4541 let mut space = PacketSpace::new(now, SpaceId::Initial, &mut self.rng); +4542 space.for_path(path_id).next_packet_number = next_pn; +4543 space.pending.crypto.push_back(frame::Crypto { +4544 offset: 0, +4545 data: client_hello, +4546 }); +4547 space +4548 }; +4549 +4550 // Retransmit all 0-RTT data +4551 let zero_rtt = mem::take( +4552 &mut self.spaces[SpaceId::Data] +4553 .for_path(PathId::ZERO) +4554 .sent_packets, +4555 ); +4556 for (_, info) in zero_rtt.into_iter() { +4557 self.paths +4558 .get_mut(&PathId::ZERO) +4559 .unwrap() +4560 .remove_in_flight(&info); +4561 self.spaces[SpaceId::Data].pending |= info.retransmits; +4562 } +4563 self.streams.retransmit_all_for_0rtt(); +4564 +4565 let token_len = packet.payload.len() - 16; +4566 let ConnectionSide::Client { ref mut token, .. } = self.side else { +4567 unreachable!("we already short-circuited if we're server"); +4568 }; +4569 *token = packet.payload.freeze().split_to(token_len); +4570 +4571 self.state = State::handshake(state::Handshake { +4572 expected_token: Bytes::new(), +4573 remote_cid_set: false, +4574 client_hello: None, +4575 allow_server_migration: true, +4576 }); +4577 Ok(()) +4578 } +4579 Header::Long { +4580 ty: LongType::Handshake, +4581 src_cid: remote_cid, +4582 dst_cid: local_cid, +4583 .. +4584 } => { +4585 debug_assert_eq!(path_id, PathId::ZERO); +4586 if remote_cid != self.remote_handshake_cid { +4587 debug!( +4588 "discarding packet with mismatched remote CID: {} != {}", +4589 self.remote_handshake_cid, remote_cid +4590 ); +4591 return Ok(()); +4592 } +4593 self.on_path_validated(path_id); +4594 +4595 self.process_early_payload(now, path_id, packet, qlog)?; +4596 if self.state.is_closed() { +4597 return Ok(()); +4598 } +4599 +4600 if self.crypto_state.session.is_handshaking() { +4601 trace!("handshake ongoing"); +4602 return Ok(()); +4603 } +4604 +4605 if self.side.is_client() { +4606 // Client-only because server params were set from the client's Initial +4607 let params = self +4608 .crypto_state +4609 .session +4610 .transport_parameters()? +4611 .ok_or_else(|| { +4612 TransportError::new( +4613 TransportErrorCode::crypto(0x6d), +4614 "transport parameters missing".to_owned(), +4615 ) +4616 })?; +4617 +4618 if self.has_0rtt() { +4619 if !self.crypto_state.session.early_data_accepted().unwrap() { +4620 debug_assert!(self.side.is_client()); +4621 debug!("0-RTT rejected"); +4622 self.crypto_state.accepted_0rtt = false; +4623 self.streams.zero_rtt_rejected(); +4624 +4625 // Discard already-queued frames +4626 self.spaces[SpaceId::Data].pending = Retransmits::default(); +4627 +4628 // Discard 0-RTT packets +4629 let sent_packets = mem::take( +4630 &mut self.spaces[SpaceId::Data].for_path(path_id).sent_packets, +4631 ); +4632 for (_, packet) in sent_packets.into_iter() { +4633 self.paths +4634 .get_mut(&path_id) +4635 .unwrap() +4636 .remove_in_flight(&packet); +4637 } +4638 } else { +4639 self.crypto_state.accepted_0rtt = true; +4640 params.validate_resumption_from(&self.peer_params)?; +4641 } +4642 } +4643 if let Some(token) = params.stateless_reset_token { +4644 let remote = self.path_data(path_id).network_path.remote; +4645 debug_assert!(!self.state.is_drained()); // requirement for endpoint events, checked above +4646 self.endpoint_events +4647 .push_back(EndpointEventInner::ResetToken(path_id, remote, token)); +4648 } +4649 self.handle_peer_params(params, local_cid, remote_cid, now)?; +4650 self.issue_first_cids(now); +4651 } else { +4652 // Server-only +4653 self.spaces[SpaceId::Data].pending.handshake_done = true; +4654 self.discard_space(now, SpaceKind::Handshake); +4655 self.events.push_back(Event::HandshakeConfirmed); +4656 trace!("handshake confirmed"); +4657 } +4658 +4659 self.events.push_back(Event::Connected); +4660 self.state.move_to_established(); +4661 trace!("established"); +4662 +4663 // Multipath can only be enabled after the state has reached Established. +4664 // So this can not happen any earlier. +4665 self.issue_first_path_cids(now); +4666 Ok(()) +4667 } +4668 Header::Initial(InitialHeader { +4669 src_cid: remote_cid, +4670 dst_cid: local_cid, +4671 .. +4672 }) => { +4673 debug_assert_eq!(path_id, PathId::ZERO); +4674 if !state.remote_cid_set { +4675 trace!("switching remote CID to {}", remote_cid); +4676 let mut state = state.clone(); +4677 self.remote_cids +4678 .get_mut(&path_id) +4679 .expect("PathId::ZERO not yet abandoned") +4680 .update_initial_cid(remote_cid); +4681 self.remote_handshake_cid = remote_cid; +4682 self.original_remote_cid = remote_cid; +4683 state.remote_cid_set = true; +4684 self.state.move_to_handshake(state); +4685 } else if remote_cid != self.remote_handshake_cid { +4686 debug!( +4687 "discarding packet with mismatched remote CID: {} != {}", +4688 self.remote_handshake_cid, remote_cid +4689 ); +4690 return Ok(()); +4691 } +4692 +4693 let starting_space = self.highest_space; +4694 self.process_early_payload(now, path_id, packet, qlog)?; +4695 +4696 if self.side.is_server() +4697 && starting_space == SpaceKind::Initial +4698 && self.highest_space != SpaceKind::Initial +4699 { +4700 let params = self +4701 .crypto_state +4702 .session +4703 .transport_parameters()? +4704 .ok_or_else(|| { +4705 TransportError::new( +4706 TransportErrorCode::crypto(0x6d), +4707 "transport parameters missing".to_owned(), +4708 ) +4709 })?; +4710 self.handle_peer_params(params, local_cid, remote_cid, now)?; +4711 self.issue_first_cids(now); +4712 self.init_0rtt(now); +4713 } +4714 Ok(()) +4715 } +4716 Header::Long { +4717 ty: LongType::ZeroRtt, +4718 .. +4719 } => { +4720 self.process_payload(now, network_path, path_id, number.unwrap(), packet, qlog)?; +4721 Ok(()) +4722 } +4723 Header::VersionNegotiate { .. } => { +4724 if self.total_authed_packets > 1 { +4725 return Ok(()); +4726 } +4727 let supported = packet +4728 .payload +4729 .chunks(4) +4730 .any(|x| match <[u8; 4]>::try_from(x) { +4731 Ok(version) => self.version == u32::from_be_bytes(version), +4732 Err(_) => false, +4733 }); +4734 if supported { +4735 return Ok(()); +4736 } +4737 debug!("remote doesn't support our version"); +4738 Err(ConnectionError::VersionMismatch) +4739 } +4740 Header::Short { .. } => unreachable!( +4741 "short packets received during handshake are discarded in handle_packet" +4742 ), +4743 } +4744 } +4745 +4746 /// Process an Initial or Handshake packet payload +4747 fn process_early_payload( +4748 &mut self, +4749 now: Instant, +4750 path_id: PathId, +4751 packet: Packet, +4752 #[allow(unused)] qlog: &mut QlogRecvPacket, +4753 ) -> Result<(), TransportError> { +4754 debug_assert_ne!(packet.header.space(), SpaceKind::Data); +4755 debug_assert_eq!(path_id, PathId::ZERO); +4756 let payload_len = packet.payload.len(); +4757 let mut ack_eliciting = false; +4758 for result in frame::Iter::new(packet.payload.freeze())? { +4759 let frame = result?; +4760 qlog.frame(&frame); +4761 let span = match frame { +4762 Frame::Padding => continue, +4763 _ => Some(trace_span!("frame", ty = %frame.ty(), path = tracing::field::Empty)), +4764 }; +4765 +4766 self.path_stats +4767 .for_path(path_id) +4768 .frame_rx +4769 .record(frame.ty()); +4770 +4771 let _guard = span.as_ref().map(|x| x.enter()); +4772 ack_eliciting |= frame.is_ack_eliciting(); +4773 +4774 // Process frames +4775 if frame.is_1rtt() && packet.header.space() != SpaceKind::Data { +4776 return Err(TransportError::PROTOCOL_VIOLATION( +4777 "illegal frame type in handshake", +4778 )); +4779 } +4780 +4781 match frame { +4782 Frame::Padding | Frame::Ping => {} +4783 Frame::Crypto(frame) => { +4784 self.read_crypto(packet.header.space().into(), &frame, payload_len)?; +4785 } +4786 Frame::Ack(ack) => { +4787 self.on_ack_received(now, packet.header.space().into(), ack)?; +4788 } +4789 Frame::PathAck(ack) => { +4790 span.as_ref() +4791 .map(|span| span.record("path", tracing::field::display(&ack.path_id))); +4792 self.on_path_ack_received(now, packet.header.space().into(), ack)?; +4793 } +4794 Frame::Close(reason) => { +4795 self.state.move_to_draining(Some(reason.into())); +4796 return Ok(()); +4797 } +4798 _ => { +4799 let mut err = +4800 TransportError::PROTOCOL_VIOLATION("illegal frame type in handshake"); +4801 err.frame = frame::MaybeFrame::Known(frame.ty()); +4802 return Err(err); +4803 } +4804 } +4805 } 4806 -4807 self.path_stats -4808 .for_path(path_id) -4809 .frame_rx -4810 .record(frame.ty()); -4811 // Crypto, Stream and Datagram frames are special cased in order no pollute -4812 // the log with payload data -4813 match &frame { -4814 Frame::Crypto(f) => { -4815 trace!(offset = f.offset, len = f.data.len(), "got frame CRYPTO"); -4816 } -4817 Frame::Stream(f) => { -4818 trace!(id = %f.id, offset = f.offset, len = f.data.len(), fin = f.fin, "got frame STREAM"); -4819 } -4820 Frame::Datagram(f) => { -4821 trace!(len = f.data.len(), "got frame DATAGRAM"); -4822 } -4823 f => { -4824 trace!("got frame {f}"); -4825 } -4826 } -4827 -4828 let _guard = span.enter(); -4829 if packet.header.is_0rtt() { -4830 match frame { -4831 Frame::Crypto(_) | Frame::Close(Close::Application(_)) => { -4832 return Err(TransportError::PROTOCOL_VIOLATION( -4833 "illegal frame type in 0-RTT", -4834 )); -4835 } -4836 _ => { -4837 if frame.is_1rtt() { -4838 return Err(TransportError::PROTOCOL_VIOLATION( -4839 "illegal frame type in 0-RTT", -4840 )); -4841 } -4842 } -4843 } -4844 } -4845 ack_eliciting |= frame.is_ack_eliciting(); -4846 -4847 // Check whether this could be a probing packet -4848 match frame { -4849 Frame::Padding -4850 | Frame::PathChallenge(_) -4851 | Frame::PathResponse(_) -4852 | Frame::NewConnectionId(_) -4853 | Frame::ObservedAddr(_) => {} -4854 _ => { -4855 is_probing_packet = false; -4856 } -4857 } -4858 -4859 match frame { -4860 Frame::Crypto(frame) => { -4861 self.read_crypto(SpaceId::Data, &frame, payload_len)?; -4862 } -4863 Frame::Stream(frame) => { -4864 if self.streams.received(frame, payload_len)?.should_transmit() { -4865 self.spaces[SpaceId::Data].pending.max_data = true; -4866 } -4867 } -4868 Frame::Ack(ack) => { -4869 self.on_ack_received(now, SpaceId::Data, ack)?; -4870 } -4871 Frame::PathAck(ack) => { -4872 if !self.is_multipath_negotiated() { -4873 return Err(TransportError::PROTOCOL_VIOLATION( -4874 "received PATH_ACK frame when multipath was not negotiated", -4875 )); -4876 } -4877 span.record("path", tracing::field::display(&ack.path_id)); -4878 self.on_path_ack_received(now, SpaceId::Data, ack)?; -4879 } -4880 Frame::Padding | Frame::Ping => {} -4881 Frame::Close(reason) => { -4882 close = Some(reason); -4883 } -4884 Frame::PathChallenge(challenge) => { -4885 let path = &mut self -4886 .path_mut(path_id) -4887 .expect("payload is processed only after the path becomes known"); -4888 path.path_responses.push(number, challenge.0, network_path); -4889 // If we were passively migrated (e.g. NAT rebinding), our local_ip will -4890 // not match. Once we processed a non-probing packet the local_ip will -4891 // finally be updated. -4892 if network_path.remote == path.network_path.remote { -4893 // PATH_CHALLENGE on active path, possible off-path packet -4894 // forwarding attack. Send a non-probing packet to recover the -4895 // active path. See -4896 // https://www.rfc-editor.org/rfc/rfc9000.html#section-9.3.3-3. In -4897 // rare cases NAT probes might also appear on-path and would also -4898 // get a non-probing packet as response. There is little harm in -4899 // this. -4900 match self.peer_supports_ack_frequency() { -4901 true => self.immediate_ack(path_id), -4902 false => { -4903 self.ping_path(path_id).ok(); -4904 } -4905 } -4906 } -4907 } -4908 Frame::PathResponse(response) => { -4909 // First try to see if this is a NAT probe response. -4910 if let Ok(nat_state) = self.n0_nat_traversal.server_side_mut() -4911 && nat_state.handle_path_response(network_path, response.0) -4912 { -4913 trace!( -4914 src = ?network_path, -4915 challenge = response.0, -4916 "Received valid NAT traversal probe response" -4917 ); -4918 // Server-side: nothing else to do. -4919 } else { -4920 // Try to see if this is a response to an on-path PATH_CHALLENGE. -4921 -4922 let path = self -4923 .paths -4924 .get_mut(&path_id) -4925 .expect("payload is processed only after the path becomes known"); -4926 -4927 use PathTimer::*; -4928 use paths::OnPathResponseReceived::*; -4929 match path -4930 .data -4931 .on_path_response_received(now, response.0, network_path) -4932 { -4933 OnPath { was_open } => { -4934 let qlog = self.qlog.with_time(now); -4935 -4936 self.timers.stop( -4937 Timer::PerPath(path_id, PathValidationFailed), -4938 qlog.clone(), -4939 ); -4940 self.timers.stop( -4941 Timer::PerPath(path_id, AbandonFromValidation), -4942 qlog.clone(), -4943 ); -4944 -4945 let next_challenge = path -4946 .data -4947 .earliest_on_path_expiring_challenge() -4948 .map(|time| time + self.ack_frequency.max_ack_delay_for_pto()); -4949 self.timers.set_or_stop( -4950 Timer::PerPath(path_id, PathChallengeLost), -4951 next_challenge, -4952 qlog, -4953 ); -4954 -4955 if !was_open { -4956 if is_multipath_negotiated { -4957 self.events.push_back(Event::Path(PathEvent::Opened { -4958 id: path_id, -4959 })); -4960 } -4961 if let Some(observed) = -4962 path.data.last_observed_addr_report.as_ref() -4963 { -4964 self.events.push_back(Event::Path( -4965 PathEvent::ObservedAddr { -4966 id: path_id, -4967 addr: observed.socket_addr(), -4968 }, -4969 )); -4970 } -4971 } -4972 if let Some((_, ref mut prev)) = path.prev { -4973 // If an on-path response was received while there is a -4974 // previous path from a migration, then the new path is -4975 // validated and we can stop sending challenges that try to -4976 // re-validate the previous path. -4977 prev.reset_on_path_challenges(); -4978 } -4979 } -4980 Ignored { -4981 sent_on, -4982 current_path, -4983 } => { -4984 debug!(%sent_on, %current_path, %response, "ignoring valid PATH_RESPONSE") -4985 } -4986 Unknown => debug!(%response, "ignoring invalid PATH_RESPONSE"), -4987 } -4988 } -4989 } -4990 Frame::MaxData(frame::MaxData(bytes)) => { -4991 self.streams.received_max_data(bytes); -4992 } -4993 Frame::MaxStreamData(frame::MaxStreamData { id, offset }) => { -4994 self.streams.received_max_stream_data(id, offset)?; -4995 } -4996 Frame::MaxStreams(frame::MaxStreams { dir, count }) => { -4997 self.streams.received_max_streams(dir, count)?; -4998 } -4999 Frame::ResetStream(frame) => { -5000 if self.streams.received_reset(frame)?.should_transmit() { -5001 self.spaces[SpaceId::Data].pending.max_data = true; -5002 } -5003 } -5004 Frame::DataBlocked(DataBlocked(offset)) => { -5005 debug!(offset, "peer claims to be blocked at connection level"); -5006 } -5007 Frame::StreamDataBlocked(StreamDataBlocked { id, offset }) => { -5008 if id.initiator() == self.side.side() && id.dir() == Dir::Uni { -5009 debug!("got STREAM_DATA_BLOCKED on send-only {}", id); -5010 return Err(TransportError::STREAM_STATE_ERROR( -5011 "STREAM_DATA_BLOCKED on send-only stream", -5012 )); -5013 } -5014 debug!( -5015 stream = %id, -5016 offset, "peer claims to be blocked at stream level" -5017 ); -5018 } -5019 Frame::StreamsBlocked(StreamsBlocked { dir, limit }) => { -5020 if limit > MAX_STREAM_COUNT { -5021 return Err(TransportError::FRAME_ENCODING_ERROR( -5022 "unrepresentable stream limit", -5023 )); -5024 } -5025 debug!( -5026 "peer claims to be blocked opening more than {} {} streams", -5027 limit, dir -5028 ); -5029 } -5030 Frame::StopSending(frame::StopSending { id, error_code }) => { -5031 if id.initiator() != self.side.side() { -5032 if id.dir() == Dir::Uni { -5033 debug!("got STOP_SENDING on recv-only {}", id); -5034 return Err(TransportError::STREAM_STATE_ERROR( -5035 "STOP_SENDING on recv-only stream", -5036 )); -5037 } -5038 } else if self.streams.is_local_unopened(id) { -5039 return Err(TransportError::STREAM_STATE_ERROR( -5040 "STOP_SENDING on unopened stream", -5041 )); -5042 } -5043 self.streams.received_stop_sending(id, error_code); -5044 } -5045 Frame::RetireConnectionId(frame::RetireConnectionId { path_id, sequence }) => { -5046 if let Some(ref path_id) = path_id { -5047 span.record("path", tracing::field::display(&path_id)); +4807 if ack_eliciting { +4808 // In the initial and handshake spaces, ACKs must be sent immediately +4809 self.spaces[packet.header.space()] +4810 .for_path(path_id) +4811 .pending_acks +4812 .set_immediate_ack_required(); +4813 } +4814 +4815 self.write_crypto(); +4816 Ok(()) +4817 } +4818 +4819 /// Processes the decrypted packet payload, always in the data space. +4820 fn process_payload( +4821 &mut self, +4822 now: Instant, +4823 network_path: FourTuple, +4824 path_id: PathId, +4825 number: u64, +4826 packet: Packet, +4827 #[allow(unused)] qlog: &mut QlogRecvPacket, +4828 ) -> Result<(), TransportError> { +4829 let is_multipath_negotiated = self.is_multipath_negotiated(); +4830 let payload = packet.payload.freeze(); +4831 let mut is_probing_packet = true; +4832 let mut close = None; +4833 let payload_len = payload.len(); +4834 let mut ack_eliciting = false; +4835 // if this packet triggers a path migration and includes a observed address frame, it's +4836 // stored here +4837 let mut migration_observed_addr = None; +4838 for result in frame::Iter::new(payload)? { +4839 let frame = result?; +4840 qlog.frame(&frame); +4841 let span = match frame { +4842 Frame::Padding => continue, +4843 _ => trace_span!("frame", ty = %frame.ty(), path = tracing::field::Empty), +4844 }; +4845 +4846 self.path_stats +4847 .for_path(path_id) +4848 .frame_rx +4849 .record(frame.ty()); +4850 // Crypto, Stream and Datagram frames are special cased in order no pollute +4851 // the log with payload data +4852 match &frame { +4853 Frame::Crypto(f) => { +4854 trace!(offset = f.offset, len = f.data.len(), "got frame CRYPTO"); +4855 } +4856 Frame::Stream(f) => { +4857 trace!(id = %f.id, offset = f.offset, len = f.data.len(), fin = f.fin, "got frame STREAM"); +4858 } +4859 Frame::Datagram(f) => { +4860 trace!(len = f.data.len(), "got frame DATAGRAM"); +4861 } +4862 f => { +4863 trace!("got frame {f}"); +4864 } +4865 } +4866 +4867 let _guard = span.enter(); +4868 if packet.header.is_0rtt() { +4869 match frame { +4870 Frame::Crypto(_) | Frame::Close(Close::Application(_)) => { +4871 return Err(TransportError::PROTOCOL_VIOLATION( +4872 "illegal frame type in 0-RTT", +4873 )); +4874 } +4875 _ => { +4876 if frame.is_1rtt() { +4877 return Err(TransportError::PROTOCOL_VIOLATION( +4878 "illegal frame type in 0-RTT", +4879 )); +4880 } +4881 } +4882 } +4883 } +4884 ack_eliciting |= frame.is_ack_eliciting(); +4885 +4886 // Check whether this could be a probing packet +4887 match frame { +4888 Frame::Padding +4889 | Frame::PathChallenge(_) +4890 | Frame::PathResponse(_) +4891 | Frame::NewConnectionId(_) +4892 | Frame::ObservedAddr(_) => {} +4893 _ => { +4894 is_probing_packet = false; +4895 } +4896 } +4897 +4898 match frame { +4899 Frame::Crypto(frame) => { +4900 self.read_crypto(SpaceId::Data, &frame, payload_len)?; +4901 } +4902 Frame::Stream(frame) => { +4903 if self.streams.received(frame, payload_len)?.should_transmit() { +4904 self.spaces[SpaceId::Data].pending.max_data = true; +4905 } +4906 } +4907 Frame::Ack(ack) => { +4908 self.on_ack_received(now, SpaceId::Data, ack)?; +4909 } +4910 Frame::PathAck(ack) => { +4911 if !self.is_multipath_negotiated() { +4912 return Err(TransportError::PROTOCOL_VIOLATION( +4913 "received PATH_ACK frame when multipath was not negotiated", +4914 )); +4915 } +4916 span.record("path", tracing::field::display(&ack.path_id)); +4917 self.on_path_ack_received(now, SpaceId::Data, ack)?; +4918 } +4919 Frame::Padding | Frame::Ping => {} +4920 Frame::Close(reason) => { +4921 close = Some(reason); +4922 } +4923 Frame::PathChallenge(challenge) => { +4924 let path = &mut self +4925 .path_mut(path_id) +4926 .expect("payload is processed only after the path becomes known"); +4927 path.path_responses.push(number, challenge.0, network_path); +4928 // If we were passively migrated (e.g. NAT rebinding), our local_ip will +4929 // not match. Once we processed a non-probing packet the local_ip will +4930 // finally be updated. +4931 if network_path.remote == path.network_path.remote { +4932 // PATH_CHALLENGE on active path, possible off-path packet +4933 // forwarding attack. Send a non-probing packet to recover the +4934 // active path. See +4935 // https://www.rfc-editor.org/rfc/rfc9000.html#section-9.3.3-3. In +4936 // rare cases NAT probes might also appear on-path and would also +4937 // get a non-probing packet as response. There is little harm in +4938 // this. +4939 match self.peer_supports_ack_frequency() { +4940 true => self.immediate_ack(path_id), +4941 false => { +4942 self.ping_path(path_id).ok(); +4943 } +4944 } +4945 } +4946 } +4947 Frame::PathResponse(response) => { +4948 // First try to see if this is a NAT probe response. +4949 if self +4950 .n0_nat_traversal +4951 .handle_path_response(network_path, response.0) +4952 { +4953 self.open_nat_traversed_paths(now); +4954 } else { +4955 // Try to see if this is a response to an on-path PATH_CHALLENGE. +4956 +4957 let path = self +4958 .paths +4959 .get_mut(&path_id) +4960 .expect("payload is processed only after the path becomes known"); +4961 +4962 use PathTimer::*; +4963 use paths::OnPathResponseReceived::*; +4964 match path +4965 .data +4966 .on_path_response_received(now, response.0, network_path) +4967 { +4968 OnPath { was_open } => { +4969 let qlog = self.qlog.with_time(now); +4970 +4971 self.timers.stop( +4972 Timer::PerPath(path_id, PathValidationFailed), +4973 qlog.clone(), +4974 ); +4975 self.timers.stop( +4976 Timer::PerPath(path_id, AbandonFromValidation), +4977 qlog.clone(), +4978 ); +4979 +4980 let next_challenge = path +4981 .data +4982 .earliest_on_path_expiring_challenge() +4983 .map(|time| time + self.ack_frequency.max_ack_delay_for_pto()); +4984 self.timers.set_or_stop( +4985 Timer::PerPath(path_id, PathChallengeLost), +4986 next_challenge, +4987 qlog, +4988 ); +4989 +4990 if !was_open { +4991 if is_multipath_negotiated { +4992 self.events.push_back(Event::Path(PathEvent::Opened { +4993 id: path_id, +4994 })); +4995 } +4996 if let Some(observed) = +4997 path.data.last_observed_addr_report.as_ref() +4998 { +4999 self.events.push_back(Event::Path( +5000 PathEvent::ObservedAddr { +5001 id: path_id, +5002 addr: observed.socket_addr(), +5003 }, +5004 )); +5005 } +5006 } +5007 if let Some((_, ref mut prev)) = path.prev { +5008 // If an on-path response was received while there is a +5009 // previous path from a migration, then the new path is +5010 // validated and we can stop sending challenges that try to +5011 // re-validate the previous path. +5012 prev.reset_on_path_challenges(); +5013 } +5014 } +5015 Ignored { +5016 sent_on, +5017 current_path, +5018 } => { +5019 debug!(%sent_on, %current_path, %response, "ignoring valid PATH_RESPONSE") +5020 } +5021 Unknown => debug!(%response, "ignoring invalid PATH_RESPONSE"), +5022 } +5023 } +5024 } +5025 Frame::MaxData(frame::MaxData(bytes)) => { +5026 self.streams.received_max_data(bytes); +5027 } +5028 Frame::MaxStreamData(frame::MaxStreamData { id, offset }) => { +5029 self.streams.received_max_stream_data(id, offset)?; +5030 } +5031 Frame::MaxStreams(frame::MaxStreams { dir, count }) => { +5032 self.streams.received_max_streams(dir, count)?; +5033 } +5034 Frame::ResetStream(frame) => { +5035 if self.streams.received_reset(frame)?.should_transmit() { +5036 self.spaces[SpaceId::Data].pending.max_data = true; +5037 } +5038 } +5039 Frame::DataBlocked(DataBlocked(offset)) => { +5040 debug!(offset, "peer claims to be blocked at connection level"); +5041 } +5042 Frame::StreamDataBlocked(StreamDataBlocked { id, offset }) => { +5043 if id.initiator() == self.side.side() && id.dir() == Dir::Uni { +5044 debug!("got STREAM_DATA_BLOCKED on send-only {}", id); +5045 return Err(TransportError::STREAM_STATE_ERROR( +5046 "STREAM_DATA_BLOCKED on send-only stream", +5047 )); 5048 } -5049 let path_id = path_id.unwrap_or_default(); -5050 match self.local_cid_state.get_mut(&path_id) { -5051 None => error!(?path_id, "RETIRE_CONNECTION_ID for unknown path"), -5052 Some(cid_state) => { -5053 let allow_more_cids = cid_state -5054 .on_cid_retirement(sequence, self.peer_params.issue_cids_limit())?; -5055 -5056 // If the path has closed, we do not issue more CIDs for this path -5057 // For details see https://www.ietf.org/archive/id/draft-ietf-quic-multipath-17.html#section-3.2.2 -5058 // > an endpoint SHOULD provide new connection IDs for that path, if still open, using PATH_NEW_CONNECTION_ID frames. -5059 let has_path = !self.abandoned_paths.contains(&path_id); -5060 let allow_more_cids = allow_more_cids && has_path; -5061 -5062 debug_assert!(!self.state.is_drained()); // required for adding endpoint events, process_payload is never called for drained connections -5063 self.endpoint_events -5064 .push_back(EndpointEventInner::RetireConnectionId( -5065 now, -5066 path_id, -5067 sequence, -5068 allow_more_cids, -5069 )); -5070 } -5071 } -5072 } -5073 Frame::NewConnectionId(frame) => { -5074 let path_id = if let Some(path_id) = frame.path_id { -5075 if !self.is_multipath_negotiated() { -5076 return Err(TransportError::PROTOCOL_VIOLATION( -5077 "received PATH_NEW_CONNECTION_ID frame when multipath was not negotiated", -5078 )); -5079 } -5080 if path_id > self.local_max_path_id { -5081 return Err(TransportError::PROTOCOL_VIOLATION( -5082 "PATH_NEW_CONNECTION_ID contains path_id exceeding current max", -5083 )); -5084 } -5085 path_id -5086 } else { -5087 PathId::ZERO -5088 }; -5089 -5090 if let Some(ref path_id) = frame.path_id { -5091 span.record("path", tracing::field::display(&path_id)); -5092 } -5093 -5094 if self.abandoned_paths.contains(&path_id) { -5095 trace!("ignoring issued CID for abandoned path"); -5096 continue; -5097 } -5098 let remote_cids = self -5099 .remote_cids -5100 .entry(path_id) -5101 .or_insert_with(|| CidQueue::new(frame.id)); -5102 if remote_cids.active().is_empty() { -5103 return Err(TransportError::PROTOCOL_VIOLATION( -5104 "NEW_CONNECTION_ID when CIDs aren't in use", -5105 )); +5049 debug!( +5050 stream = %id, +5051 offset, "peer claims to be blocked at stream level" +5052 ); +5053 } +5054 Frame::StreamsBlocked(StreamsBlocked { dir, limit }) => { +5055 if limit > MAX_STREAM_COUNT { +5056 return Err(TransportError::FRAME_ENCODING_ERROR( +5057 "unrepresentable stream limit", +5058 )); +5059 } +5060 debug!( +5061 "peer claims to be blocked opening more than {} {} streams", +5062 limit, dir +5063 ); +5064 } +5065 Frame::StopSending(frame::StopSending { id, error_code }) => { +5066 if id.initiator() != self.side.side() { +5067 if id.dir() == Dir::Uni { +5068 debug!("got STOP_SENDING on recv-only {}", id); +5069 return Err(TransportError::STREAM_STATE_ERROR( +5070 "STOP_SENDING on recv-only stream", +5071 )); +5072 } +5073 } else if self.streams.is_local_unopened(id) { +5074 return Err(TransportError::STREAM_STATE_ERROR( +5075 "STOP_SENDING on unopened stream", +5076 )); +5077 } +5078 self.streams.received_stop_sending(id, error_code); +5079 } +5080 Frame::RetireConnectionId(frame::RetireConnectionId { path_id, sequence }) => { +5081 if let Some(ref path_id) = path_id { +5082 span.record("path", tracing::field::display(&path_id)); +5083 } +5084 let path_id = path_id.unwrap_or_default(); +5085 match self.local_cid_state.get_mut(&path_id) { +5086 None => debug!(?path_id, "RETIRE_CONNECTION_ID for unknown path"), +5087 Some(cid_state) => { +5088 let allow_more_cids = cid_state +5089 .on_cid_retirement(sequence, self.peer_params.issue_cids_limit())?; +5090 +5091 // If the path has closed, we do not issue more CIDs for this path +5092 // For details see https://www.ietf.org/archive/id/draft-ietf-quic-multipath-17.html#section-3.2.2 +5093 // > an endpoint SHOULD provide new connection IDs for that path, if still open, using PATH_NEW_CONNECTION_ID frames. +5094 let has_path = !self.abandoned_paths.contains(&path_id); +5095 let allow_more_cids = allow_more_cids && has_path; +5096 +5097 debug_assert!(!self.state.is_drained()); // required for adding endpoint events, process_payload is never called for drained connections +5098 self.endpoint_events +5099 .push_back(EndpointEventInner::RetireConnectionId( +5100 now, +5101 path_id, +5102 sequence, +5103 allow_more_cids, +5104 )); +5105 } 5106 } -5107 if frame.retire_prior_to > frame.sequence { -5108 return Err(TransportError::PROTOCOL_VIOLATION( -5109 "NEW_CONNECTION_ID retiring unissued CIDs", -5110 )); -5111 } -5112 -5113 use crate::cid_queue::InsertError; -5114 match remote_cids.insert(frame) { -5115 Ok(None) if self.path(path_id).is_none() => { -5116 // TODO(flub): Once the client does off-path NAT probes as well -5117 // we should remove this. -5118 // If this gives us CIDs to open a new path and a nat traversal attempt -5119 // is underway we could try to probe a pending remote -5120 self.continue_nat_traversal_round(now); -5121 } -5122 Ok(None) => {} -5123 Ok(Some((retired, reset_token))) => { -5124 let pending_retired = -5125 &mut self.spaces[SpaceId::Data].pending.retire_cids; -5126 /// Ensure `pending_retired` cannot grow without bound. Limit is -5127 /// somewhat arbitrary but very permissive. -5128 const MAX_PENDING_RETIRED_CIDS: u64 = CidQueue::LEN as u64 * 10; -5129 // We don't bother counting in-flight frames because those are bounded -5130 // by congestion control. -5131 if (pending_retired.len() as u64) -5132 .saturating_add(retired.end.saturating_sub(retired.start)) -5133 > MAX_PENDING_RETIRED_CIDS -5134 { -5135 return Err(TransportError::CONNECTION_ID_LIMIT_ERROR( -5136 "queued too many retired CIDs", -5137 )); -5138 } -5139 pending_retired.extend(retired.map(|seq| (path_id, seq))); -5140 self.set_reset_token(path_id, network_path.remote, reset_token); -5141 } -5142 Err(InsertError::ExceedsLimit) => { -5143 return Err(TransportError::CONNECTION_ID_LIMIT_ERROR("")); -5144 } -5145 Err(InsertError::Retired) => { -5146 trace!("discarding already-retired"); -5147 // RETIRE_CONNECTION_ID might not have been previously sent if e.g. a -5148 // range of connection IDs larger than the active connection ID limit -5149 // was retired all at once via retire_prior_to. -5150 self.spaces[SpaceId::Data] -5151 .pending -5152 .retire_cids -5153 .push((path_id, frame.sequence)); -5154 continue; -5155 } -5156 }; -5157 -5158 if self.side.is_server() -5159 && path_id == PathId::ZERO -5160 && self -5161 .remote_cids -5162 .get(&PathId::ZERO) -5163 .map(|cids| cids.active_seq() == 0) -5164 .unwrap_or_default() -5165 { -5166 // We're a server still using the initial remote CID for the client, so -5167 // let's switch immediately to enable clientside stateless resets. -5168 self.update_remote_cid(PathId::ZERO); -5169 } -5170 } -5171 Frame::NewToken(NewToken { token }) => { -5172 let ConnectionSide::Client { -5173 token_store, -5174 server_name, -5175 .. -5176 } = &self.side -5177 else { -5178 return Err(TransportError::PROTOCOL_VIOLATION("client sent NEW_TOKEN")); -5179 }; -5180 if token.is_empty() { -5181 return Err(TransportError::FRAME_ENCODING_ERROR("empty token")); -5182 } -5183 trace!("got new token"); -5184 token_store.insert(server_name, token); -5185 } -5186 Frame::Datagram(datagram) => { -5187 if self -5188 .datagrams -5189 .received(datagram, &self.config.datagram_receive_buffer_size)? -5190 { -5191 self.events.push_back(Event::DatagramReceived); -5192 } -5193 } -5194 Frame::AckFrequency(ack_frequency) => { -5195 // This frame can only be sent in the Data space -5196 -5197 if !self.ack_frequency.ack_frequency_received(&ack_frequency)? { -5198 // The AckFrequency frame is stale (we have already received a more -5199 // recent one) -5200 continue; -5201 } -5202 -5203 // Update the params for all of our paths -5204 for (path_id, space) in self.spaces[SpaceId::Data].number_spaces.iter_mut() { -5205 space.pending_acks.set_ack_frequency_params(&ack_frequency); -5206 -5207 // Our `max_ack_delay` has been updated, so we may need to adjust -5208 // its associated timeout. -5209 // Packets received on abandoned paths are always acknowledged immediately. -5210 if !self.abandoned_paths.contains(path_id) -5211 && let Some(timeout) = space -5212 .pending_acks -5213 .max_ack_delay_timeout(self.ack_frequency.max_ack_delay) -5214 { -5215 self.timers.set( -5216 Timer::PerPath(*path_id, PathTimer::MaxAckDelay), -5217 timeout, -5218 self.qlog.with_time(now), -5219 ); -5220 } -5221 } -5222 } -5223 Frame::ImmediateAck => { -5224 // This frame can only be sent in the Data space -5225 for pns in self.spaces[SpaceId::Data].iter_paths_mut() { -5226 pns.pending_acks.set_immediate_ack_required(); -5227 } -5228 } -5229 Frame::HandshakeDone => { -5230 if self.side.is_server() { -5231 return Err(TransportError::PROTOCOL_VIOLATION( -5232 "client sent HANDSHAKE_DONE", -5233 )); -5234 } -5235 if self.crypto_state.has_keys(EncryptionLevel::Handshake) { -5236 self.discard_space(now, SpaceKind::Handshake); -5237 self.events.push_back(Event::HandshakeConfirmed); -5238 trace!("handshake confirmed"); -5239 } -5240 } -5241 Frame::ObservedAddr(observed) => { -5242 // check if params allows the peer to send report and this node to receive it -5243 trace!(seq_no = %observed.seq_no, ip = %observed.ip, port = observed.port); -5244 if !self -5245 .peer_params -5246 .address_discovery_role -5247 .should_report(&self.config.address_discovery_role) -5248 { -5249 return Err(TransportError::PROTOCOL_VIOLATION( -5250 "received OBSERVED_ADDRESS frame when not negotiated", -5251 )); +5107 } +5108 Frame::NewConnectionId(frame) => { +5109 let path_id = if let Some(path_id) = frame.path_id { +5110 if !self.is_multipath_negotiated() { +5111 return Err(TransportError::PROTOCOL_VIOLATION( +5112 "received PATH_NEW_CONNECTION_ID frame when multipath was not negotiated", +5113 )); +5114 } +5115 if path_id > self.local_max_path_id { +5116 return Err(TransportError::PROTOCOL_VIOLATION( +5117 "PATH_NEW_CONNECTION_ID contains path_id exceeding current max", +5118 )); +5119 } +5120 path_id +5121 } else { +5122 PathId::ZERO +5123 }; +5124 +5125 if let Some(ref path_id) = frame.path_id { +5126 span.record("path", tracing::field::display(&path_id)); +5127 } +5128 +5129 if self.abandoned_paths.contains(&path_id) { +5130 trace!("ignoring issued CID for abandoned path"); +5131 continue; +5132 } +5133 let remote_cids = self +5134 .remote_cids +5135 .entry(path_id) +5136 .or_insert_with(|| CidQueue::new(frame.id)); +5137 if remote_cids.active().is_empty() { +5138 return Err(TransportError::PROTOCOL_VIOLATION( +5139 "NEW_CONNECTION_ID when CIDs aren't in use", +5140 )); +5141 } +5142 if frame.retire_prior_to > frame.sequence { +5143 return Err(TransportError::PROTOCOL_VIOLATION( +5144 "NEW_CONNECTION_ID retiring unissued CIDs", +5145 )); +5146 } +5147 +5148 use crate::cid_queue::InsertError; +5149 match remote_cids.insert(frame) { +5150 Ok(None) => { +5151 self.open_nat_traversed_paths(now); +5152 } +5153 Ok(Some((retired, reset_token))) => { +5154 let pending_retired = +5155 &mut self.spaces[SpaceId::Data].pending.retire_cids; +5156 /// Ensure `pending_retired` cannot grow without bound. Limit is +5157 /// somewhat arbitrary but very permissive. +5158 const MAX_PENDING_RETIRED_CIDS: u64 = CidQueue::LEN as u64 * 10; +5159 // We don't bother counting in-flight frames because those are bounded +5160 // by congestion control. +5161 if (pending_retired.len() as u64) +5162 .saturating_add(retired.end.saturating_sub(retired.start)) +5163 > MAX_PENDING_RETIRED_CIDS +5164 { +5165 return Err(TransportError::CONNECTION_ID_LIMIT_ERROR( +5166 "queued too many retired CIDs", +5167 )); +5168 } +5169 pending_retired.extend(retired.map(|seq| (path_id, seq))); +5170 self.set_reset_token(path_id, network_path.remote, reset_token); +5171 self.open_nat_traversed_paths(now); +5172 } +5173 Err(InsertError::ExceedsLimit) => { +5174 return Err(TransportError::CONNECTION_ID_LIMIT_ERROR("")); +5175 } +5176 Err(InsertError::Retired) => { +5177 trace!("discarding already-retired"); +5178 // RETIRE_CONNECTION_ID might not have been previously sent if e.g. a +5179 // range of connection IDs larger than the active connection ID limit +5180 // was retired all at once via retire_prior_to. +5181 self.spaces[SpaceId::Data] +5182 .pending +5183 .retire_cids +5184 .push((path_id, frame.sequence)); +5185 continue; +5186 } +5187 }; +5188 +5189 if self.side.is_server() +5190 && path_id == PathId::ZERO +5191 && self +5192 .remote_cids +5193 .get(&PathId::ZERO) +5194 .map(|cids| cids.active_seq() == 0) +5195 .unwrap_or_default() +5196 { +5197 // We're a server still using the initial remote CID for the client, so +5198 // let's switch immediately to enable clientside stateless resets. +5199 self.update_remote_cid(PathId::ZERO); +5200 } +5201 } +5202 Frame::NewToken(NewToken { token }) => { +5203 let ConnectionSide::Client { +5204 token_store, +5205 server_name, +5206 .. +5207 } = &self.side +5208 else { +5209 return Err(TransportError::PROTOCOL_VIOLATION("client sent NEW_TOKEN")); +5210 }; +5211 if token.is_empty() { +5212 return Err(TransportError::FRAME_ENCODING_ERROR("empty token")); +5213 } +5214 trace!("got new token"); +5215 token_store.insert(server_name, token); +5216 } +5217 Frame::Datagram(datagram) => { +5218 if self +5219 .datagrams +5220 .received(datagram, &self.config.datagram_receive_buffer_size)? +5221 { +5222 self.events.push_back(Event::DatagramReceived); +5223 } +5224 } +5225 Frame::AckFrequency(ack_frequency) => { +5226 // This frame can only be sent in the Data space +5227 +5228 if !self.ack_frequency.ack_frequency_received(&ack_frequency)? { +5229 // The AckFrequency frame is stale (we have already received a more +5230 // recent one) +5231 continue; +5232 } +5233 +5234 // Update the params for all of our paths +5235 for (path_id, space) in self.spaces[SpaceId::Data].number_spaces.iter_mut() { +5236 space.pending_acks.set_ack_frequency_params(&ack_frequency); +5237 +5238 // Our `max_ack_delay` has been updated, so we may need to adjust +5239 // its associated timeout. +5240 // Packets received on abandoned paths are always acknowledged immediately. +5241 if !self.abandoned_paths.contains(path_id) +5242 && let Some(timeout) = space +5243 .pending_acks +5244 .max_ack_delay_timeout(self.ack_frequency.max_ack_delay) +5245 { +5246 self.timers.set( +5247 Timer::PerPath(*path_id, PathTimer::MaxAckDelay), +5248 timeout, +5249 self.qlog.with_time(now), +5250 ); +5251 } 5252 } -5253 // must only be sent in data space -5254 if packet.header.space() != SpaceKind::Data { -5255 return Err(TransportError::PROTOCOL_VIOLATION( -5256 "OBSERVED_ADDRESS frame outside data space", -5257 )); +5253 } +5254 Frame::ImmediateAck => { +5255 // This frame can only be sent in the Data space +5256 for pns in self.spaces[SpaceId::Data].iter_paths_mut() { +5257 pns.pending_acks.set_immediate_ack_required(); 5258 } -5259 -5260 let path = self.path_data_mut(path_id); -5261 if network_path == path.network_path { -5262 if let Some(updated) = path.update_observed_addr_report(observed) -5263 && path.open_status == paths::OpenStatus::Informed -5264 { -5265 self.events.push_back(Event::Path(PathEvent::ObservedAddr { -5266 id: path_id, -5267 addr: updated, -5268 })); -5269 // otherwise the event is reported when the path is deemed open -5270 } -5271 } else { -5272 // include in migration -5273 migration_observed_addr = Some(observed) -5274 } -5275 } -5276 Frame::PathAbandon(frame::PathAbandon { -5277 path_id, -5278 error_code, -5279 }) => { -5280 span.record("path", tracing::field::display(&path_id)); -5281 match self.close_path_inner( -5282 now, -5283 path_id, -5284 PathAbandonReason::RemoteAbandoned { -5285 error_code: error_code.into(), -5286 }, -5287 ) { -5288 Ok(()) => { -5289 trace!("peer abandoned path"); -5290 } -5291 Err(ClosePathError::ClosedPath) => { -5292 trace!("peer abandoned already closed path"); -5293 } -5294 Err(ClosePathError::MultipathNotNegotiated) => { -5295 return Err(TransportError::PROTOCOL_VIOLATION( -5296 "received PATH_ABANDON frame when multipath was not negotiated", -5297 )); -5298 } -5299 Err(ClosePathError::LastOpenPath) => { -5300 // Not reachable: close_path_inner allows remote abandons -5301 // for the last path. But handle gracefully just in case. -5302 error!( -5303 "peer abandoned last path but close_path_inner returned LastOpenPath" -5304 ); -5305 } -5306 }; -5307 -5308 // Start draining the path if it still exists and hasn't started draining yet. -5309 if let Some(path) = self.paths.get_mut(&path_id) -5310 && !mem::replace(&mut path.data.draining, true) -5311 { -5312 let ack_delay = self.ack_frequency.max_ack_delay_for_pto(); -5313 let pto = path.data.rtt.pto_base() + ack_delay; -5314 self.timers.set( -5315 Timer::PerPath(path_id, PathTimer::PathDrained), -5316 now + 3 * pto, -5317 self.qlog.with_time(now), -5318 ); -5319 -5320 self.set_max_path_id(now, self.local_max_path_id.saturating_add(1u8)); -5321 } -5322 } -5323 Frame::PathStatusAvailable(info) => { -5324 span.record("path", tracing::field::display(&info.path_id)); -5325 if self.is_multipath_negotiated() { -5326 self.on_path_status( -5327 info.path_id, -5328 PathStatus::Available, -5329 info.status_seq_no, -5330 ); -5331 } else { -5332 return Err(TransportError::PROTOCOL_VIOLATION( -5333 "received PATH_STATUS_AVAILABLE frame when multipath was not negotiated", -5334 )); -5335 } -5336 } -5337 Frame::PathStatusBackup(info) => { -5338 span.record("path", tracing::field::display(&info.path_id)); -5339 if self.is_multipath_negotiated() { -5340 self.on_path_status(info.path_id, PathStatus::Backup, info.status_seq_no); -5341 } else { -5342 return Err(TransportError::PROTOCOL_VIOLATION( -5343 "received PATH_STATUS_BACKUP frame when multipath was not negotiated", -5344 )); -5345 } -5346 } -5347 Frame::MaxPathId(frame::MaxPathId(path_id)) => { -5348 span.record("path", tracing::field::display(&path_id)); -5349 if !self.is_multipath_negotiated() { -5350 return Err(TransportError::PROTOCOL_VIOLATION( -5351 "received MAX_PATH_ID frame when multipath was not negotiated", -5352 )); -5353 } -5354 // frames that do not increase the path id are ignored -5355 if path_id > self.remote_max_path_id { -5356 self.remote_max_path_id = path_id; -5357 self.issue_first_path_cids(now); -5358 // TODO(flub): Once the client sends off-path NAT probes this is no -5359 // longer needed. But new paths that need to be opened may need -5360 // to be notified. -5361 while let Some(true) = self.continue_nat_traversal_round(now) {} -5362 } -5363 } -5364 Frame::PathsBlocked(frame::PathsBlocked(max_path_id)) => { -5365 // Receipt of a value of Maximum Path Identifier or Path Identifier that is higher than the local maximum value MUST -5366 // be treated as a connection error of type PROTOCOL_VIOLATION. -5367 // Ref <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-paths_blocked-and-path_cids> -5368 if self.is_multipath_negotiated() { -5369 if max_path_id > self.local_max_path_id { -5370 return Err(TransportError::PROTOCOL_VIOLATION( -5371 "PATHS_BLOCKED maximum path identifier was larger than local maximum", -5372 )); -5373 } -5374 debug!("received PATHS_BLOCKED({:?})", max_path_id); -5375 // TODO(@divma): ensure max concurrent paths -5376 } else { -5377 return Err(TransportError::PROTOCOL_VIOLATION( -5378 "received PATHS_BLOCKED frame when not multipath was not negotiated", -5379 )); -5380 } -5381 } -5382 Frame::PathCidsBlocked(frame::PathCidsBlocked { path_id, next_seq }) => { -5383 // Nothing to do. This is recorded in the frame stats, but otherwise we -5384 // always issue all CIDs we're allowed to issue, so either this is an -5385 // impatient peer or a bug on our side. -5386 -5387 // Receipt of a value of Maximum Path Identifier or Path Identifier that is higher than the local maximum value MUST -5388 // be treated as a connection error of type PROTOCOL_VIOLATION. -5389 // Ref <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-paths_blocked-and-path_cids> -5390 if self.is_multipath_negotiated() { -5391 if path_id > self.local_max_path_id { -5392 return Err(TransportError::PROTOCOL_VIOLATION( -5393 "PATH_CIDS_BLOCKED path identifier was larger than local maximum", -5394 )); -5395 } -5396 if next_seq.0 -5397 > self -5398 .local_cid_state -5399 .get(&path_id) -5400 .map(|cid_state| cid_state.active_seq().1 + 1) -5401 .unwrap_or_default() -5402 { -5403 return Err(TransportError::PROTOCOL_VIOLATION( -5404 "PATH_CIDS_BLOCKED next sequence number larger than in local state", -5405 )); -5406 } -5407 debug!(%path_id, %next_seq, "received PATH_CIDS_BLOCKED"); -5408 } else { -5409 return Err(TransportError::PROTOCOL_VIOLATION( -5410 "received PATH_CIDS_BLOCKED frame when not multipath was not negotiated", -5411 )); -5412 } -5413 } -5414 Frame::AddAddress(addr) => { -5415 let client_state = match self.n0_nat_traversal.client_side_mut() { -5416 Ok(state) => state, -5417 Err(err) => { -5418 return Err(TransportError::PROTOCOL_VIOLATION(format!( -5419 "Nat traversal(ADD_ADDRESS): {err}" -5420 ))); -5421 } -5422 }; -5423 -5424 if !client_state.check_remote_address(&addr) { -5425 // if the address is not valid we flag it, but update anyway -5426 warn!(?addr, "server sent illegal ADD_ADDRESS frame"); -5427 } -5428 -5429 match client_state.add_remote_address(addr) { -5430 Ok(maybe_added) => { -5431 if let Some(added) = maybe_added { -5432 self.events.push_back(Event::NatTraversal( -5433 n0_nat_traversal::Event::AddressAdded(added), -5434 )); -5435 } -5436 } -5437 Err(e) => { -5438 warn!(%e, "failed to add remote address") -5439 } +5259 } +5260 Frame::HandshakeDone => { +5261 if self.side.is_server() { +5262 return Err(TransportError::PROTOCOL_VIOLATION( +5263 "client sent HANDSHAKE_DONE", +5264 )); +5265 } +5266 if self.crypto_state.has_keys(EncryptionLevel::Handshake) { +5267 self.discard_space(now, SpaceKind::Handshake); +5268 self.events.push_back(Event::HandshakeConfirmed); +5269 trace!("handshake confirmed"); +5270 } +5271 } +5272 Frame::ObservedAddr(observed) => { +5273 // check if params allows the peer to send report and this node to receive it +5274 trace!(seq_no = %observed.seq_no, ip = %observed.ip, port = observed.port); +5275 if !self +5276 .peer_params +5277 .address_discovery_role +5278 .should_report(&self.config.address_discovery_role) +5279 { +5280 return Err(TransportError::PROTOCOL_VIOLATION( +5281 "received OBSERVED_ADDRESS frame when not negotiated", +5282 )); +5283 } +5284 // must only be sent in data space +5285 if packet.header.space() != SpaceKind::Data { +5286 return Err(TransportError::PROTOCOL_VIOLATION( +5287 "OBSERVED_ADDRESS frame outside data space", +5288 )); +5289 } +5290 +5291 let path = self.path_data_mut(path_id); +5292 if network_path == path.network_path { +5293 if let Some(updated) = path.update_observed_addr_report(observed) +5294 && path.open_status == paths::OpenStatus::Informed +5295 { +5296 self.events.push_back(Event::Path(PathEvent::ObservedAddr { +5297 id: path_id, +5298 addr: updated, +5299 })); +5300 // otherwise the event is reported when the path is deemed open +5301 } +5302 } else { +5303 // include in migration +5304 migration_observed_addr = Some(observed) +5305 } +5306 } +5307 Frame::PathAbandon(frame::PathAbandon { +5308 path_id, +5309 error_code, +5310 }) => { +5311 span.record("path", tracing::field::display(&path_id)); +5312 match self.close_path_inner( +5313 now, +5314 path_id, +5315 PathAbandonReason::RemoteAbandoned { +5316 error_code: error_code.into(), +5317 }, +5318 ) { +5319 Ok(()) => { +5320 trace!("peer abandoned path"); +5321 } +5322 Err(ClosePathError::ClosedPath) => { +5323 trace!("peer abandoned already closed path"); +5324 } +5325 Err(ClosePathError::MultipathNotNegotiated) => { +5326 return Err(TransportError::PROTOCOL_VIOLATION( +5327 "received PATH_ABANDON frame when multipath was not negotiated", +5328 )); +5329 } +5330 Err(ClosePathError::LastOpenPath) => { +5331 // Not reachable: close_path_inner allows remote abandons +5332 // for the last path. But handle gracefully just in case. +5333 error!( +5334 "peer abandoned last path but close_path_inner returned LastOpenPath" +5335 ); +5336 } +5337 }; +5338 +5339 // Start draining the path if it still exists and hasn't started draining yet. +5340 if let Some(path) = self.paths.get_mut(&path_id) +5341 && !mem::replace(&mut path.data.draining, true) +5342 { +5343 let ack_delay = self.ack_frequency.max_ack_delay_for_pto(); +5344 let pto = path.data.rtt.pto_base() + ack_delay; +5345 self.timers.set( +5346 Timer::PerPath(path_id, PathTimer::PathDrained), +5347 now + 3 * pto, +5348 self.qlog.with_time(now), +5349 ); +5350 +5351 self.set_max_path_id(now, self.local_max_path_id.saturating_add(1u8)); +5352 } +5353 } +5354 Frame::PathStatusAvailable(info) => { +5355 span.record("path", tracing::field::display(&info.path_id)); +5356 if self.is_multipath_negotiated() { +5357 self.on_path_status( +5358 info.path_id, +5359 PathStatus::Available, +5360 info.status_seq_no, +5361 ); +5362 } else { +5363 return Err(TransportError::PROTOCOL_VIOLATION( +5364 "received PATH_STATUS_AVAILABLE frame when multipath was not negotiated", +5365 )); +5366 } +5367 } +5368 Frame::PathStatusBackup(info) => { +5369 span.record("path", tracing::field::display(&info.path_id)); +5370 if self.is_multipath_negotiated() { +5371 self.on_path_status(info.path_id, PathStatus::Backup, info.status_seq_no); +5372 } else { +5373 return Err(TransportError::PROTOCOL_VIOLATION( +5374 "received PATH_STATUS_BACKUP frame when multipath was not negotiated", +5375 )); +5376 } +5377 } +5378 Frame::MaxPathId(frame::MaxPathId(path_id)) => { +5379 span.record("path", tracing::field::display(&path_id)); +5380 if !self.is_multipath_negotiated() { +5381 return Err(TransportError::PROTOCOL_VIOLATION( +5382 "received MAX_PATH_ID frame when multipath was not negotiated", +5383 )); +5384 } +5385 // frames that do not increase the path id are ignored +5386 if path_id > self.remote_max_path_id { +5387 self.remote_max_path_id = path_id; +5388 self.issue_first_path_cids(now); +5389 self.open_nat_traversed_paths(now); +5390 } +5391 } +5392 Frame::PathsBlocked(frame::PathsBlocked(max_path_id)) => { +5393 // Receipt of a value of Maximum Path Identifier or Path Identifier that is higher than the local maximum value MUST +5394 // be treated as a connection error of type PROTOCOL_VIOLATION. +5395 // Ref <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-paths_blocked-and-path_cids> +5396 if self.is_multipath_negotiated() { +5397 if max_path_id > self.local_max_path_id { +5398 return Err(TransportError::PROTOCOL_VIOLATION( +5399 "PATHS_BLOCKED maximum path identifier was larger than local maximum", +5400 )); +5401 } +5402 debug!("received PATHS_BLOCKED({:?})", max_path_id); +5403 // TODO(@divma): ensure max concurrent paths +5404 } else { +5405 return Err(TransportError::PROTOCOL_VIOLATION( +5406 "received PATHS_BLOCKED frame when not multipath was not negotiated", +5407 )); +5408 } +5409 } +5410 Frame::PathCidsBlocked(frame::PathCidsBlocked { path_id, next_seq }) => { +5411 // Nothing to do. This is recorded in the frame stats, but otherwise we +5412 // always issue all CIDs we're allowed to issue, so either this is an +5413 // impatient peer or a bug on our side. +5414 +5415 // Receipt of a value of Maximum Path Identifier or Path Identifier that is higher than the local maximum value MUST +5416 // be treated as a connection error of type PROTOCOL_VIOLATION. +5417 // Ref <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-paths_blocked-and-path_cids> +5418 if self.is_multipath_negotiated() { +5419 if path_id > self.local_max_path_id { +5420 return Err(TransportError::PROTOCOL_VIOLATION( +5421 "PATH_CIDS_BLOCKED path identifier was larger than local maximum", +5422 )); +5423 } +5424 if next_seq.0 +5425 > self +5426 .local_cid_state +5427 .get(&path_id) +5428 .map(|cid_state| cid_state.active_seq().1 + 1) +5429 .unwrap_or_default() +5430 { +5431 return Err(TransportError::PROTOCOL_VIOLATION( +5432 "PATH_CIDS_BLOCKED next sequence number larger than in local state", +5433 )); +5434 } +5435 debug!(%path_id, %next_seq, "received PATH_CIDS_BLOCKED"); +5436 } else { +5437 return Err(TransportError::PROTOCOL_VIOLATION( +5438 "received PATH_CIDS_BLOCKED frame when not multipath was not negotiated", +5439 )); 5440 } 5441 } -5442 Frame::RemoveAddress(addr) => { +5442 Frame::AddAddress(addr) => { 5443 let client_state = match self.n0_nat_traversal.client_side_mut() { 5444 Ok(state) => state, 5445 Err(err) => { 5446 return Err(TransportError::PROTOCOL_VIOLATION(format!( -5447 "Nat traversal(REMOVE_ADDRESS): {err}" +5447 "Nat traversal(ADD_ADDRESS): {err}" 5448 ))); 5449 } 5450 }; -5451 if let Some(removed_addr) = client_state.remove_remote_address(addr) { -5452 self.events.push_back(Event::NatTraversal( -5453 n0_nat_traversal::Event::AddressRemoved(removed_addr), -5454 )); +5451 +5452 if !client_state.check_remote_address(&addr) { +5453 // if the address is not valid we flag it, but update anyway +5454 warn!(?addr, "server sent illegal ADD_ADDRESS frame"); 5455 } -5456 } -5457 Frame::ReachOut(reach_out) => { -5458 let ipv6 = self.is_ipv6(); -5459 let server_state = match self.n0_nat_traversal.server_side_mut() { -5460 Ok(state) => state, -5461 Err(err) => { -5462 return Err(TransportError::PROTOCOL_VIOLATION(format!( -5463 "Nat traversal(REACH_OUT): {err}" -5464 ))); -5465 } -5466 }; -5467 -5468 let round_before = server_state.current_round(); -5469 -5470 if let Err(err) = server_state.handle_reach_out(reach_out, ipv6) { -5471 return Err(TransportError::PROTOCOL_VIOLATION(format!( -5472 "Nat traversal(REACH_OUT): {err}" -5473 ))); -5474 } -5475 -5476 if server_state.current_round() > round_before { -5477 // A new round was started, reset the NAT probe retry timer. -5478 let delay = RttEstimator::new(self.config.initial_rtt).pto_base() * 2 / 3; -5479 self.timers.set( -5480 Timer::Conn(ConnTimer::NatTraversalProbeRetry), -5481 now + delay, -5482 self.qlog.with_time(now), -5483 ); -5484 } -5485 } -5486 } -5487 } -5488 -5489 let space = self.spaces[SpaceId::Data].for_path(path_id); -5490 if space -5491 .pending_acks -5492 .packet_received(now, number, ack_eliciting, &space.dedup) -5493 { -5494 if self.abandoned_paths.contains(&path_id) { -5495 // § 3.4.3 QUIC-MULTIPATH: promptly send ACKs for packets received from -5496 // abandoned paths. -5497 space.pending_acks.set_immediate_ack_required(); -5498 } else { -5499 self.timers.set( -5500 Timer::PerPath(path_id, PathTimer::MaxAckDelay), -5501 now + self.ack_frequency.max_ack_delay, -5502 self.qlog.with_time(now), -5503 ); -5504 } -5505 } -5506 -5507 // Issue stream ID credit due to ACKs of outgoing finish/resets and incoming finish/resets -5508 // on stopped streams. Incoming finishes/resets on open streams are not handled here as they -5509 // are only freed, and hence only issue credit, once the application has been notified -5510 // during a read on the stream. -5511 let pending = &mut self.spaces[SpaceId::Data].pending; -5512 self.streams.queue_max_stream_id(pending); -5513 -5514 if let Some(reason) = close { -5515 self.state.move_to_draining(Some(reason.into())); -5516 self.connection_close_pending = true; -5517 } -5518 -5519 // For Multipath any packet triggers migration. For RFC9000 or QNT (+ Multipath) -5520 // only non-probing packets trigger migration. -5521 let migrate_on_any_packet = -5522 self.is_multipath_negotiated() && !self.n0_nat_traversal.is_negotiated(); -5523 -5524 // Only migrate if this is the largest packet number seen. -5525 let is_largest_received_pn = Some(number) -5526 == self.spaces[SpaceId::Data] -5527 .for_path(path_id) -5528 .largest_received_packet_number; -5529 -5530 // If we receive a non-probing packet on a new local IP that means we had a NAT -5531 // rebinding-like migration. We update our local address but do not otherwise -5532 // validate the new path, we only need to validate the path if the peer migrates per -5533 // RFC9000 §9: https://www.rfc-editor.org/rfc/rfc9000.html#section-9-4 -5534 if (migrate_on_any_packet || !is_probing_packet) -5535 && is_largest_received_pn -5536 && self.local_ip_may_migrate() -5537 && let Some(new_local_ip) = network_path.local_ip -5538 { -5539 let path_data = self.path_data_mut(path_id); -5540 if path_data -5541 .network_path -5542 .local_ip -5543 .is_some_and(|ip| ip != new_local_ip) -5544 { -5545 debug!( -5546 %path_id, -5547 new_4tuple = %network_path, -5548 prev_4tuple = %path_data.network_path, -5549 "local address passive migration" -5550 ); -5551 } -5552 path_data.network_path.local_ip = Some(new_local_ip) -5553 } -5554 -5555 // If the peer migrated to a new address, trigger migration. -5556 if (migrate_on_any_packet || !is_probing_packet) -5557 && is_largest_received_pn -5558 && network_path.remote != self.path_data(path_id).network_path.remote -5559 && self.remote_may_migrate() -5560 { -5561 self.migrate(path_id, now, network_path, migration_observed_addr); -5562 // Break linkability, if possible -5563 self.update_remote_cid(path_id); -5564 self.spin = false; -5565 } -5566 -5567 Ok(()) -5568 } -5569 -5570 /// Migrates the 4-tuple of the path. -5571 /// -5572 /// This creates a new [`PathData`] for the migrated path and stores the previous -5573 /// [`PathData`] in [`PathState::prev`]. -5574 fn migrate( -5575 &mut self, -5576 path_id: PathId, -5577 now: Instant, -5578 network_path: FourTuple, -5579 observed_addr: Option<ObservedAddr>, -5580 ) { -5581 trace!( -5582 new_4tuple = %network_path, -5583 prev_4tuple = %self.path_data(path_id).network_path, -5584 %path_id, -5585 "migration initiated", -5586 ); -5587 self.path_generation_counter = self.path_generation_counter.wrapping_add(1); -5588 // TODO(@divma): conditions for path migration in multipath are very specific, check them -5589 // again to prevent path migrations that should actually create a new path -5590 -5591 // Reset rtt/congestion state for new path unless it looks like a NAT rebinding. -5592 // Note that the congestion window will not grow until validation terminates. Helps mitigate -5593 // amplification attacks performed by spoofing source addresses. -5594 let prev_pto = self.pto(SpaceKind::Data, path_id); -5595 let path = self.paths.get_mut(&path_id).expect("known path"); -5596 let mut new_path_data = if network_path.remote.is_ipv4() -5597 && network_path.remote.ip() == path.data.network_path.remote.ip() -5598 { -5599 PathData::from_previous(network_path, &path.data, self.path_generation_counter, now) -5600 } else { -5601 let peer_max_udp_payload_size = -5602 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()) -5603 .unwrap_or(u16::MAX); -5604 PathData::new( -5605 network_path, -5606 self.allow_mtud, -5607 Some(peer_max_udp_payload_size), -5608 self.path_generation_counter, -5609 now, -5610 &self.config, -5611 ) -5612 }; -5613 new_path_data.last_observed_addr_report = path.data.last_observed_addr_report.clone(); -5614 if let Some(report) = observed_addr -5615 && let Some(updated) = new_path_data.update_observed_addr_report(report) -5616 { -5617 tracing::info!("adding observed addr event from migration"); -5618 self.events.push_back(Event::Path(PathEvent::ObservedAddr { -5619 id: path_id, -5620 addr: updated, -5621 })); -5622 } -5623 new_path_data.pending_on_path_challenge = true; -5624 -5625 let mut prev_path_data = mem::replace(&mut path.data, new_path_data); -5626 -5627 // Only store this as previous path if it was validated. For all we know there could -5628 // already be a previous path stored which might have been validated in the past, -5629 // which is more valuable than one that's not yet validated. -5630 // -5631 // With multipath it is possible that there are no remote CIDs for the path ID -5632 // yet. In this case we would never have sent on this path yet and would not be able -5633 // to send a PATH_CHALLENGE either, which is currently a fire-and-forget affair -5634 // anyway. So don't store such a path either. -5635 if !prev_path_data.validated -5636 && let Some(cid) = self.remote_cids.get(&path_id).map(CidQueue::active) -5637 { -5638 prev_path_data.pending_on_path_challenge = true; -5639 // We haven't updated the remote CID yet, this captures the remote CID we were using on -5640 // the previous path. -5641 path.prev = Some((cid, prev_path_data)); -5642 } +5456 +5457 match client_state.add_remote_address(addr) { +5458 Ok(maybe_added) => { +5459 if let Some(added) = maybe_added { +5460 self.events.push_back(Event::NatTraversal( +5461 n0_nat_traversal::Event::AddressAdded(added), +5462 )); +5463 } +5464 } +5465 Err(e) => { +5466 warn!(%e, "failed to add remote address") +5467 } +5468 } +5469 } +5470 Frame::RemoveAddress(addr) => { +5471 let client_state = match self.n0_nat_traversal.client_side_mut() { +5472 Ok(state) => state, +5473 Err(err) => { +5474 return Err(TransportError::PROTOCOL_VIOLATION(format!( +5475 "Nat traversal(REMOVE_ADDRESS): {err}" +5476 ))); +5477 } +5478 }; +5479 if let Some(removed_addr) = client_state.remove_remote_address(addr) { +5480 self.events.push_back(Event::NatTraversal( +5481 n0_nat_traversal::Event::AddressRemoved(removed_addr), +5482 )); +5483 } +5484 } +5485 Frame::ReachOut(reach_out) => { +5486 let ipv6 = self.is_ipv6(); +5487 let server_state = match self.n0_nat_traversal.server_side_mut() { +5488 Ok(state) => state, +5489 Err(err) => { +5490 return Err(TransportError::PROTOCOL_VIOLATION(format!( +5491 "Nat traversal(REACH_OUT): {err}" +5492 ))); +5493 } +5494 }; +5495 +5496 let round_before = server_state.current_round(); +5497 +5498 if let Err(err) = server_state.handle_reach_out(reach_out, ipv6) { +5499 return Err(TransportError::PROTOCOL_VIOLATION(format!( +5500 "Nat traversal(REACH_OUT): {err}" +5501 ))); +5502 } +5503 +5504 if server_state.current_round() > round_before { +5505 // A new round was started, reset the NAT probe retry timer. +5506 let delay = RttEstimator::new(self.config.initial_rtt).pto_base() * 2 / 3; +5507 self.timers.set( +5508 Timer::Conn(ConnTimer::NatTraversalProbeRetry), +5509 now + delay, +5510 self.qlog.with_time(now), +5511 ); +5512 } +5513 } +5514 } +5515 } +5516 +5517 let space = self.spaces[SpaceId::Data].for_path(path_id); +5518 if space +5519 .pending_acks +5520 .packet_received(now, number, ack_eliciting, &space.dedup) +5521 { +5522 if self.abandoned_paths.contains(&path_id) { +5523 // § 3.4.3 QUIC-MULTIPATH: promptly send ACKs for packets received from +5524 // abandoned paths. +5525 space.pending_acks.set_immediate_ack_required(); +5526 } else { +5527 self.timers.set( +5528 Timer::PerPath(path_id, PathTimer::MaxAckDelay), +5529 now + self.ack_frequency.max_ack_delay, +5530 self.qlog.with_time(now), +5531 ); +5532 } +5533 } +5534 +5535 // Issue stream ID credit due to ACKs of outgoing finish/resets and incoming finish/resets +5536 // on stopped streams. Incoming finishes/resets on open streams are not handled here as they +5537 // are only freed, and hence only issue credit, once the application has been notified +5538 // during a read on the stream. +5539 let pending = &mut self.spaces[SpaceId::Data].pending; +5540 self.streams.queue_max_stream_id(pending); +5541 +5542 if let Some(reason) = close { +5543 self.state.move_to_draining(Some(reason.into())); +5544 self.connection_close_pending = true; +5545 } +5546 +5547 // For Multipath any packet triggers migration. For RFC9000 or QNT (+ Multipath) +5548 // only non-probing packets trigger migration. +5549 let migrate_on_any_packet = +5550 self.is_multipath_negotiated() && !self.n0_nat_traversal.is_negotiated(); +5551 +5552 // Only migrate if this is the largest packet number seen. +5553 let is_largest_received_pn = Some(number) +5554 == self.spaces[SpaceId::Data] +5555 .for_path(path_id) +5556 .largest_received_packet_number; +5557 +5558 // If we receive a non-probing packet on a new local IP that means we had a NAT +5559 // rebinding-like migration. We update our local address but do not otherwise +5560 // validate the new path, we only need to validate the path if the peer migrates per +5561 // RFC9000 §9: https://www.rfc-editor.org/rfc/rfc9000.html#section-9-4 +5562 if (migrate_on_any_packet || !is_probing_packet) +5563 && is_largest_received_pn +5564 && self.local_ip_may_migrate() +5565 && let Some(new_local_ip) = network_path.local_ip +5566 { +5567 let path_data = self.path_data_mut(path_id); +5568 if path_data +5569 .network_path +5570 .local_ip +5571 .is_some_and(|ip| ip != new_local_ip) +5572 { +5573 debug!( +5574 %path_id, +5575 new_4tuple = %network_path, +5576 prev_4tuple = %path_data.network_path, +5577 "local address passive migration" +5578 ); +5579 } +5580 path_data.network_path.local_ip = Some(new_local_ip) +5581 } +5582 +5583 // If the peer migrated to a new address, trigger migration. +5584 if (migrate_on_any_packet || !is_probing_packet) +5585 && is_largest_received_pn +5586 && network_path.remote != self.path_data(path_id).network_path.remote +5587 && self.remote_may_migrate() +5588 { +5589 self.migrate(path_id, now, network_path, migration_observed_addr); +5590 // Break linkability, if possible +5591 self.update_remote_cid(path_id); +5592 self.spin = false; +5593 } +5594 +5595 Ok(()) +5596 } +5597 +5598 /// Opens any paths that have been successfully NAT traversed. +5599 fn open_nat_traversed_paths(&mut self, now: Instant) { +5600 while let Some(network_path) = self +5601 .n0_nat_traversal +5602 .client_side_mut() +5603 .ok() +5604 .and_then(|s| s.pop_pending_path_open()) +5605 { +5606 match self.open_path_ensure(network_path, PathStatus::Backup, now) { +5607 Ok((path_id, already_existed)) => { +5608 debug!( +5609 %path_id, +5610 ?network_path, +5611 new_path = !already_existed, +5612 "Opened NAT traversal path", +5613 ); +5614 } +5615 Err(err) => match err { +5616 PathError::MultipathNotNegotiated +5617 | PathError::ServerSideNotAllowed +5618 | PathError::ValidationFailed +5619 | PathError::InvalidRemoteAddress(_) => { +5620 error!( +5621 ?err, +5622 ?network_path, +5623 "Failed to open path for successful NAT traversal" +5624 ); +5625 } +5626 PathError::MaxPathIdReached | PathError::RemoteCidsExhausted => { +5627 // Temporary error, put back. +5628 self.n0_nat_traversal +5629 .client_side_mut() +5630 .map(|s| s.push_pending_path_open(network_path)) +5631 .ok(); +5632 debug!( +5633 ?err, +5634 ?network_path, +5635 "Blocked opening NAT traversal path, enqueued" +5636 ); +5637 return; +5638 } +5639 }, +5640 } +5641 } +5642 } 5643 -5644 // We need to re-assign the correct remote to this path in qlog -5645 self.qlog.emit_tuple_assigned(path_id, network_path, now); -5646 -5647 self.timers.set( -5648 Timer::PerPath(path_id, PathTimer::PathValidationFailed), -5649 now + 3 * cmp::max(self.pto(SpaceKind::Data, path_id), prev_pto), -5650 self.qlog.with_time(now), -5651 ); -5652 } -5653 -5654 /// Handle a change in the local address, i.e. an active migration -5655 /// -5656 /// In the general (non-multipath) case, paths will perform a RFC9000 migration and be pinged -5657 /// for a liveness check. This is the behaviour of a path assumed to be recoverable, even if -5658 /// this is not the case. -5659 /// -5660 /// Clients in a connection in which multipath has been negotiated should migrate paths to new -5661 /// [`PathId`]s. For paths that are known to be non-recoverable can be migrated to a new -5662 /// [`PathId`] by closing the current path, and opening a new one to the same remote. Treating -5663 /// paths as non recoverable when necessary accelerates connectivity re-establishment, or might -5664 /// allow it altogether. -5665 /// -5666 /// The optional `hint` allows callers to indicate when paths are non-recoverable and should be -5667 /// migrated to new a [`PathId`]. -5668 // NOTE: only clients are allowed to migrate, but generally dealing with RFC9000 migrations is -5669 // lacking <https://github.com/n0-computer/noq/issues/364> -5670 pub fn handle_network_change(&mut self, hint: Option<&dyn NetworkChangeHint>, now: Instant) { -5671 debug!("network changed"); -5672 if self.state.is_drained() { -5673 return; -5674 } -5675 if self.highest_space < SpaceKind::Data { -5676 for path in self.paths.values_mut() { -5677 // Clear the local address for it to be obtained from the socket again. -5678 path.data.network_path.local_ip = None; -5679 } -5680 -5681 self.update_remote_cid(PathId::ZERO); -5682 self.ping(); -5683 -5684 return; -5685 } -5686 -5687 // Paths that can't recover so a new path should be open instead. If multipath is not -5688 // negotiated, this will be empty. -5689 let mut non_recoverable_paths = Vec::default(); -5690 let mut recoverable_paths = Vec::default(); -5691 let mut open_paths = 0; -5692 -5693 let is_multipath_negotiated = self.is_multipath_negotiated(); -5694 let is_client = self.side().is_client(); -5695 let immediate_ack_allowed = self.peer_supports_ack_frequency(); -5696 -5697 for (path_id, path) in self.paths.iter_mut() { -5698 if self.abandoned_paths.contains(path_id) { -5699 continue; -5700 } -5701 open_paths += 1; -5702 -5703 // Read the network path BEFORE clearing local_ip, so the hint can -5704 // check which interface the path was using. -5705 let network_path = path.data.network_path; -5706 -5707 // Clear the local address for it to be obtained from the socket again. This applies to -5708 // all paths, regardless of being considered recoverable or not -5709 path.data.network_path.local_ip = None; -5710 let remote = network_path.remote; -5711 -5712 // Without multipath, the connection tries to recover the single path, whereas with -5713 // multipath, even in a single-path scenario, we attempt to migrate the path to a new -5714 // PathId. -5715 let attempt_to_recover = if is_multipath_negotiated { -5716 // Use the hint to determine if the path can recover. When no hint is -5717 // provided, clients default to non-recoverable (abandon and re-open) -5718 // while servers default to recoverable (attempt in-place recovery). -5719 hint.map(|h| h.is_path_recoverable(*path_id, network_path)) -5720 .unwrap_or(!is_client) -5721 } else { -5722 // In the non multipath case, we try to recover the single active path -5723 true -5724 }; -5725 -5726 if attempt_to_recover { -5727 recoverable_paths.push((*path_id, remote)); -5728 } else { -5729 non_recoverable_paths.push((*path_id, remote, path.data.local_status())) -5730 } -5731 } -5732 -5733 /* NON RECOVERABLE PATHS */ -5734 // This are handled first, so that in case the treatment intended for these fails, we can -5735 // go the recoverable route instead. -5736 -5737 // Decide if we need to close first or open first in the multipath case. -5738 // - Opening first has a higher risk of getting limited by the negotiated MAX_PATH_ID. -5739 // - Closing first risks this being the only open path. -5740 // We prefer closing paths first unless we identify this is the last open path. -5741 let open_first = open_paths == non_recoverable_paths.len(); -5742 -5743 for (path_id, remote, status) in non_recoverable_paths.into_iter() { -5744 let network_path = FourTuple { -5745 remote, -5746 local_ip: None, /* allow the local ip to be discovered */ -5747 }; -5748 -5749 if open_first && let Err(e) = self.open_path(network_path, status, now) { -5750 if self.side().is_client() { -5751 debug!(%e, "Failed to open new path for network change"); -5752 } -5753 // if this fails, let the path try to recover itself -5754 recoverable_paths.push((path_id, remote)); -5755 continue; -5756 } +5644 /// Migrates the 4-tuple of the path. +5645 /// +5646 /// This creates a new [`PathData`] for the migrated path and stores the previous +5647 /// [`PathData`] in [`PathState::prev`]. +5648 fn migrate( +5649 &mut self, +5650 path_id: PathId, +5651 now: Instant, +5652 network_path: FourTuple, +5653 observed_addr: Option<ObservedAddr>, +5654 ) { +5655 trace!( +5656 new_4tuple = %network_path, +5657 prev_4tuple = %self.path_data(path_id).network_path, +5658 %path_id, +5659 "migration initiated", +5660 ); +5661 self.path_generation_counter = self.path_generation_counter.wrapping_add(1); +5662 // TODO(@divma): conditions for path migration in multipath are very specific, check them +5663 // again to prevent path migrations that should actually create a new path +5664 +5665 // Reset rtt/congestion state for new path unless it looks like a NAT rebinding. +5666 // Note that the congestion window will not grow until validation terminates. Helps mitigate +5667 // amplification attacks performed by spoofing source addresses. +5668 let prev_pto = self.pto(SpaceKind::Data, path_id); +5669 let path = self.paths.get_mut(&path_id).expect("known path"); +5670 let mut new_path_data = if network_path.remote.is_ipv4() +5671 && network_path.remote.ip() == path.data.network_path.remote.ip() +5672 { +5673 PathData::from_previous(network_path, &path.data, self.path_generation_counter, now) +5674 } else { +5675 let peer_max_udp_payload_size = +5676 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()) +5677 .unwrap_or(u16::MAX); +5678 PathData::new( +5679 network_path, +5680 self.allow_mtud, +5681 Some(peer_max_udp_payload_size), +5682 self.path_generation_counter, +5683 now, +5684 &self.config, +5685 ) +5686 }; +5687 new_path_data.last_observed_addr_report = path.data.last_observed_addr_report.clone(); +5688 if let Some(report) = observed_addr +5689 && let Some(updated) = new_path_data.update_observed_addr_report(report) +5690 { +5691 tracing::info!("adding observed addr event from migration"); +5692 self.events.push_back(Event::Path(PathEvent::ObservedAddr { +5693 id: path_id, +5694 addr: updated, +5695 })); +5696 } +5697 new_path_data.pending_on_path_challenge = true; +5698 +5699 let mut prev_path_data = mem::replace(&mut path.data, new_path_data); +5700 +5701 // Only store this as previous path if it was validated. For all we know there could +5702 // already be a previous path stored which might have been validated in the past, +5703 // which is more valuable than one that's not yet validated. +5704 // +5705 // With multipath it is possible that there are no remote CIDs for the path ID +5706 // yet. In this case we would never have sent on this path yet and would not be able +5707 // to send a PATH_CHALLENGE either, which is currently a fire-and-forget affair +5708 // anyway. So don't store such a path either. +5709 if !prev_path_data.validated +5710 && let Some(cid) = self.remote_cids.get(&path_id).map(CidQueue::active) +5711 { +5712 prev_path_data.pending_on_path_challenge = true; +5713 // We haven't updated the remote CID yet, this captures the remote CID we were using on +5714 // the previous path. +5715 path.prev = Some((cid, prev_path_data)); +5716 } +5717 +5718 // We need to re-assign the correct remote to this path in qlog +5719 self.qlog.emit_tuple_assigned(path_id, network_path, now); +5720 +5721 self.timers.set( +5722 Timer::PerPath(path_id, PathTimer::PathValidationFailed), +5723 now + 3 * cmp::max(self.pto(SpaceKind::Data, path_id), prev_pto), +5724 self.qlog.with_time(now), +5725 ); +5726 } +5727 +5728 /// Handle a change in the local address, i.e. an active migration +5729 /// +5730 /// In the general (non-multipath) case, paths will perform a RFC9000 migration and be pinged +5731 /// for a liveness check. This is the behaviour of a path assumed to be recoverable, even if +5732 /// this is not the case. +5733 /// +5734 /// Clients in a connection in which multipath has been negotiated should migrate paths to new +5735 /// [`PathId`]s. For paths that are known to be non-recoverable can be migrated to a new +5736 /// [`PathId`] by closing the current path, and opening a new one to the same remote. Treating +5737 /// paths as non recoverable when necessary accelerates connectivity re-establishment, or might +5738 /// allow it altogether. +5739 /// +5740 /// The optional `hint` allows callers to indicate when paths are non-recoverable and should be +5741 /// migrated to new a [`PathId`]. +5742 // NOTE: only clients are allowed to migrate, but generally dealing with RFC9000 migrations is +5743 // lacking <https://github.com/n0-computer/noq/issues/364> +5744 pub fn handle_network_change(&mut self, hint: Option<&dyn NetworkChangeHint>, now: Instant) { +5745 debug!("network changed"); +5746 if self.state.is_drained() { +5747 return; +5748 } +5749 if self.highest_space < SpaceKind::Data { +5750 for path in self.paths.values_mut() { +5751 // Clear the local address for it to be obtained from the socket again. +5752 path.data.network_path.local_ip = None; +5753 } +5754 +5755 self.update_remote_cid(PathId::ZERO); +5756 self.ping(); 5757 -5758 if let Err(e) = -5759 self.close_path_inner(now, path_id, PathAbandonReason::UnusableAfterNetworkChange) -5760 { -5761 debug!(%e,"Failed to close unrecoverable path after network change"); -5762 recoverable_paths.push((path_id, remote)); -5763 continue; -5764 } -5765 -5766 if !open_first && let Err(e) = self.open_path(network_path, status, now) { -5767 // Path has already been closed if we got here. Since the path was not recoverable, -5768 // this might be desirable in any case, because other paths exist (!open_first) and -5769 // this was is considered non recoverable -5770 debug!(%e,"Failed to open new path for network change"); -5771 } -5772 } -5773 -5774 /* RECOVERABLE PATHS */ -5775 -5776 for (path_id, remote) in recoverable_paths.into_iter() { -5777 // Schedule a Ping for a liveness check. -5778 if let Some(path_space) = self.spaces[SpaceId::Data].number_spaces.get_mut(&path_id) { -5779 path_space.ping_pending = true; +5758 return; +5759 } +5760 +5761 // Paths that can't recover so a new path should be open instead. If multipath is not +5762 // negotiated, this will be empty. +5763 let mut non_recoverable_paths = Vec::default(); +5764 let mut recoverable_paths = Vec::default(); +5765 let mut open_paths = 0; +5766 +5767 let is_multipath_negotiated = self.is_multipath_negotiated(); +5768 let is_client = self.side().is_client(); +5769 let immediate_ack_allowed = self.peer_supports_ack_frequency(); +5770 +5771 for (path_id, path) in self.paths.iter_mut() { +5772 if self.abandoned_paths.contains(path_id) { +5773 continue; +5774 } +5775 open_paths += 1; +5776 +5777 // Read the network path BEFORE clearing local_ip, so the hint can +5778 // check which interface the path was using. +5779 let network_path = path.data.network_path; 5780 -5781 if immediate_ack_allowed { -5782 path_space.immediate_ack_pending = true; -5783 } -5784 } +5781 // Clear the local address for it to be obtained from the socket again. This applies to +5782 // all paths, regardless of being considered recoverable or not +5783 path.data.network_path.local_ip = None; +5784 let remote = network_path.remote; 5785 -5786 // Reset PTO backoff so retransmits resume promptly. Congestion controller and -5787 // RTT are intentionally preserved for recoverable paths. We explicitly allow -5788 // this reset also during the handshake, so do not check -5789 // Self::peer_competed_handshake_address_validation. -5790 if let Some(path) = self.paths.get_mut(&path_id) { -5791 path.data.pto_count = 0; -5792 } -5793 self.set_loss_detection_timer(now, path_id); -5794 -5795 let Some((reset_token, retired)) = -5796 self.remote_cids.get_mut(&path_id).and_then(CidQueue::next) -5797 else { -5798 continue; -5799 }; -5800 -5801 // Retire the current remote CID and any CIDs we had to skip. -5802 self.spaces[SpaceId::Data] -5803 .pending -5804 .retire_cids -5805 .extend(retired.map(|seq| (path_id, seq))); +5786 // Without multipath, the connection tries to recover the single path, whereas with +5787 // multipath, even in a single-path scenario, we attempt to migrate the path to a new +5788 // PathId. +5789 let attempt_to_recover = if is_multipath_negotiated { +5790 // Use the hint to determine if the path can recover. When no hint is +5791 // provided, clients default to non-recoverable (abandon and re-open) +5792 // while servers default to recoverable (attempt in-place recovery). +5793 hint.map(|h| h.is_path_recoverable(*path_id, network_path)) +5794 .unwrap_or(!is_client) +5795 } else { +5796 // In the non multipath case, we try to recover the single active path +5797 true +5798 }; +5799 +5800 if attempt_to_recover { +5801 recoverable_paths.push((*path_id, remote)); +5802 } else { +5803 non_recoverable_paths.push((*path_id, remote, path.data.local_status())) +5804 } +5805 } 5806 -5807 debug_assert!(!self.state.is_drained()); // required for endpoint_events, checked above -5808 self.endpoint_events -5809 .push_back(EndpointEventInner::ResetToken(path_id, remote, reset_token)); -5810 } -5811 } -5812 -5813 /// Switch to a previously unused remote connection ID, if possible -5814 fn update_remote_cid(&mut self, path_id: PathId) { -5815 let Some((reset_token, retired)) = self -5816 .remote_cids -5817 .get_mut(&path_id) -5818 .and_then(|cids| cids.next()) -5819 else { -5820 return; -5821 }; +5807 /* NON RECOVERABLE PATHS */ +5808 // This are handled first, so that in case the treatment intended for these fails, we can +5809 // go the recoverable route instead. +5810 +5811 // Decide if we need to close first or open first in the multipath case. +5812 // - Opening first has a higher risk of getting limited by the negotiated MAX_PATH_ID. +5813 // - Closing first risks this being the only open path. +5814 // We prefer closing paths first unless we identify this is the last open path. +5815 let open_first = open_paths == non_recoverable_paths.len(); +5816 +5817 for (path_id, remote, status) in non_recoverable_paths.into_iter() { +5818 let network_path = FourTuple { +5819 remote, +5820 local_ip: None, /* allow the local ip to be discovered */ +5821 }; 5822 -5823 // Retire the current remote CID and any CIDs we had to skip. -5824 self.spaces[SpaceId::Data] -5825 .pending -5826 .retire_cids -5827 .extend(retired.map(|seq| (path_id, seq))); -5828 let remote = self.path_data(path_id).network_path.remote; -5829 self.set_reset_token(path_id, remote, reset_token); -5830 } +5823 if open_first && let Err(e) = self.open_path(network_path, status, now) { +5824 if self.side().is_client() { +5825 debug!(%e, "Failed to open new path for network change"); +5826 } +5827 // if this fails, let the path try to recover itself +5828 recoverable_paths.push((path_id, remote)); +5829 continue; +5830 } 5831 -5832 /// Sends this reset token to the endpoint -5833 /// -5834 /// The endpoint needs to know the reset tokens issued by the peer, so that if the peer -5835 /// sends a reset token it knows to route it to this connection. See RFC 9000 section -5836 /// 10.3. Stateless Reset. -5837 /// -5838 /// Reset tokens are different for each path, the endpoint identifies paths by peer -5839 /// socket address however, not by path ID. -5840 fn set_reset_token(&mut self, path_id: PathId, remote: SocketAddr, reset_token: ResetToken) { -5841 debug_assert!(!self.state.is_drained()); // required for endpoint events, set_reset_token is never called for drained connections -5842 self.endpoint_events -5843 .push_back(EndpointEventInner::ResetToken(path_id, remote, reset_token)); -5844 -5845 // During the handshake the server sends a reset token in the transport -5846 // parameters. When we are the client and we receive the reset token during the -5847 // handshake we want this to affect our peer transport parameters. -5848 // TODO(flub): Pretty sure this is pointless, the entire params is overwritten -5849 // shortly after this was called. And then the params don't have this anymore. -5850 if path_id == PathId::ZERO { -5851 self.peer_params.stateless_reset_token = Some(reset_token); -5852 } -5853 } +5832 if let Err(e) = +5833 self.close_path_inner(now, path_id, PathAbandonReason::UnusableAfterNetworkChange) +5834 { +5835 debug!(%e,"Failed to close unrecoverable path after network change"); +5836 recoverable_paths.push((path_id, remote)); +5837 continue; +5838 } +5839 +5840 if !open_first && let Err(e) = self.open_path(network_path, status, now) { +5841 // Path has already been closed if we got here. Since the path was not recoverable, +5842 // this might be desirable in any case, because other paths exist (!open_first) and +5843 // this was is considered non recoverable +5844 debug!(%e,"Failed to open new path for network change"); +5845 } +5846 } +5847 +5848 /* RECOVERABLE PATHS */ +5849 +5850 for (path_id, remote) in recoverable_paths.into_iter() { +5851 // Schedule a Ping for a liveness check. +5852 if let Some(path_space) = self.spaces[SpaceId::Data].number_spaces.get_mut(&path_id) { +5853 path_space.ping_pending = true; 5854 -5855 /// Issue an initial set of connection IDs to the peer upon connection -5856 fn issue_first_cids(&mut self, now: Instant) { -5857 if self -5858 .local_cid_state -5859 .get(&PathId::ZERO) -5860 .expect("PathId::ZERO exists when the connection is created") -5861 .cid_len() -5862 == 0 -5863 { -5864 return; -5865 } -5866 -5867 // Subtract 1 to account for the CID we supplied while handshaking -5868 let mut n = self.peer_params.issue_cids_limit() - 1; -5869 if let ConnectionSide::Server { server_config } = &self.side -5870 && server_config.has_preferred_address() -5871 { -5872 // We also sent a CID in the transport parameters -5873 n -= 1; -5874 } -5875 debug_assert!(!self.state.is_drained()); // requirement for endpoint_events -5876 self.endpoint_events -5877 .push_back(EndpointEventInner::NeedIdentifiers(PathId::ZERO, now, n)); -5878 } -5879 -5880 /// Issues an initial set of CIDs for paths that have not yet had any CIDs issued -5881 /// -5882 /// Later CIDs are issued when CIDs expire or are retired by the peer. -5883 fn issue_first_path_cids(&mut self, now: Instant) { -5884 if let Some(max_path_id) = self.max_path_id() { -5885 let mut path_id = self.max_path_id_with_cids.next(); -5886 while path_id <= max_path_id { -5887 self.endpoint_events -5888 .push_back(EndpointEventInner::NeedIdentifiers( -5889 path_id, -5890 now, -5891 self.peer_params.issue_cids_limit(), -5892 )); -5893 path_id = path_id.next(); -5894 } -5895 self.max_path_id_with_cids = max_path_id; -5896 } -5897 } -5898 -5899 /// Populates a packet with frames -5900 /// -5901 /// This tries to fit as many frames as possible into the packet. -5902 /// -5903 /// *path_exclusive_only* means to only build frames which can only be sent on this -5904 /// *path. This is used in multipath for backup paths while there is still an active -5905 /// *path. -5906 fn populate_packet<'a, 'b>( -5907 &mut self, -5908 now: Instant, -5909 space_id: SpaceId, -5910 path_id: PathId, -5911 scheduling_info: &PathSchedulingInfo, -5912 builder: &mut PacketBuilder<'a, 'b>, -5913 ) { -5914 let is_multipath_negotiated = self.is_multipath_negotiated(); -5915 let space_has_keys = self.crypto_state.has_keys(space_id.encryption_level()); -5916 let is_0rtt = space_id == SpaceId::Data && !space_has_keys; -5917 let stats = &mut self.path_stats.for_path(path_id).frame_tx; -5918 let space = &mut self.spaces[space_id]; -5919 let path = &mut self.paths.get_mut(&path_id).expect("known path").data; -5920 space -5921 .for_path(path_id) -5922 .pending_acks -5923 .maybe_ack_non_eliciting(); -5924 -5925 // HANDSHAKE_DONE -5926 if !is_0rtt -5927 && !scheduling_info.is_abandoned -5928 && scheduling_info.may_send_data -5929 && mem::replace(&mut space.pending.handshake_done, false) -5930 { -5931 builder.write_frame(frame::HandshakeDone, stats); -5932 } -5933 -5934 // PING -5935 if !scheduling_info.is_abandoned -5936 && mem::replace(&mut space.for_path(path_id).ping_pending, false) -5937 { -5938 builder.write_frame(frame::Ping, stats); +5855 if immediate_ack_allowed { +5856 path_space.immediate_ack_pending = true; +5857 } +5858 } +5859 +5860 // Reset PTO backoff so retransmits resume promptly. Congestion controller and +5861 // RTT are intentionally preserved for recoverable paths. We explicitly allow +5862 // this reset also during the handshake, so do not check +5863 // Self::peer_competed_handshake_address_validation. +5864 if let Some(path) = self.paths.get_mut(&path_id) { +5865 path.data.pto_count = 0; +5866 } +5867 self.set_loss_detection_timer(now, path_id); +5868 +5869 let Some((reset_token, retired)) = +5870 self.remote_cids.get_mut(&path_id).and_then(CidQueue::next) +5871 else { +5872 continue; +5873 }; +5874 +5875 // Retire the current remote CID and any CIDs we had to skip. +5876 self.spaces[SpaceId::Data] +5877 .pending +5878 .retire_cids +5879 .extend(retired.map(|seq| (path_id, seq))); +5880 +5881 debug_assert!(!self.state.is_drained()); // required for endpoint_events, checked above +5882 self.endpoint_events +5883 .push_back(EndpointEventInner::ResetToken(path_id, remote, reset_token)); +5884 } +5885 } +5886 +5887 /// Switch to a previously unused remote connection ID, if possible +5888 fn update_remote_cid(&mut self, path_id: PathId) { +5889 let Some((reset_token, retired)) = self +5890 .remote_cids +5891 .get_mut(&path_id) +5892 .and_then(|cids| cids.next()) +5893 else { +5894 return; +5895 }; +5896 +5897 // Retire the current remote CID and any CIDs we had to skip. +5898 self.spaces[SpaceId::Data] +5899 .pending +5900 .retire_cids +5901 .extend(retired.map(|seq| (path_id, seq))); +5902 let remote = self.path_data(path_id).network_path.remote; +5903 self.set_reset_token(path_id, remote, reset_token); +5904 } +5905 +5906 /// Sends this reset token to the endpoint +5907 /// +5908 /// The endpoint needs to know the reset tokens issued by the peer, so that if the peer +5909 /// sends a reset token it knows to route it to this connection. See RFC 9000 section +5910 /// 10.3. Stateless Reset. +5911 /// +5912 /// Reset tokens are different for each path, the endpoint identifies paths by peer +5913 /// socket address however, not by path ID. +5914 fn set_reset_token(&mut self, path_id: PathId, remote: SocketAddr, reset_token: ResetToken) { +5915 debug_assert!(!self.state.is_drained()); // required for endpoint events, set_reset_token is never called for drained connections +5916 self.endpoint_events +5917 .push_back(EndpointEventInner::ResetToken(path_id, remote, reset_token)); +5918 +5919 // During the handshake the server sends a reset token in the transport +5920 // parameters. When we are the client and we receive the reset token during the +5921 // handshake we want this to affect our peer transport parameters. +5922 // TODO(flub): Pretty sure this is pointless, the entire params is overwritten +5923 // shortly after this was called. And then the params don't have this anymore. +5924 if path_id == PathId::ZERO { +5925 self.peer_params.stateless_reset_token = Some(reset_token); +5926 } +5927 } +5928 +5929 /// Issue an initial set of connection IDs to the peer upon connection +5930 fn issue_first_cids(&mut self, now: Instant) { +5931 if self +5932 .local_cid_state +5933 .get(&PathId::ZERO) +5934 .expect("PathId::ZERO exists when the connection is created") +5935 .cid_len() +5936 == 0 +5937 { +5938 return; 5939 } 5940 -5941 // IMMEDIATE_ACK -5942 if !scheduling_info.is_abandoned -5943 && mem::replace(&mut space.for_path(path_id).immediate_ack_pending, false) -5944 { -5945 debug_assert_eq!( -5946 space_id, -5947 SpaceId::Data, -5948 "immediate acks must be sent in the data space" -5949 ); -5950 builder.write_frame(frame::ImmediateAck, stats); -5951 } -5952 -5953 // ACK -5954 if !scheduling_info.is_abandoned && scheduling_info.may_send_data { -5955 for path_id in space -5956 .number_spaces -5957 .iter_mut() -5958 .filter(|(_, pns)| pns.pending_acks.can_send()) -5959 .map(|(&path_id, _)| path_id) -5960 .collect::<Vec<_>>() -5961 { -5962 Self::populate_acks( -5963 now, -5964 self.receiving_ecn, -5965 path_id, -5966 space_id, -5967 space, -5968 is_multipath_negotiated, -5969 builder, -5970 stats, -5971 space_has_keys, -5972 ); -5973 } -5974 } -5975 -5976 // ACK_FREQUENCY -5977 if !scheduling_info.is_abandoned -5978 && scheduling_info.may_send_data -5979 && mem::replace(&mut space.pending.ack_frequency, false) -5980 { -5981 let sequence_number = self.ack_frequency.next_sequence_number(); -5982 -5983 // Safe to unwrap because this is always provided when ACK frequency is enabled -5984 let config = self.config.ack_frequency_config.as_ref().unwrap(); -5985 -5986 // Ensure the delay is within bounds to avoid a PROTOCOL_VIOLATION error -5987 let max_ack_delay = self.ack_frequency.candidate_max_ack_delay( -5988 path.rtt.get(), -5989 config, -5990 &self.peer_params, -5991 ); -5992 -5993 let frame = frame::AckFrequency { -5994 sequence: sequence_number, -5995 ack_eliciting_threshold: config.ack_eliciting_threshold, -5996 request_max_ack_delay: max_ack_delay.as_micros().try_into().unwrap_or(VarInt::MAX), -5997 reordering_threshold: config.reordering_threshold, -5998 }; -5999 builder.write_frame(frame, stats); -6000 -6001 self.ack_frequency -6002 .ack_frequency_sent(path_id, builder.packet_number, max_ack_delay); -6003 } -6004 -6005 // PATH_CHALLENGE -6006 if !scheduling_info.is_abandoned -6007 && space_id == SpaceId::Data -6008 && path.pending_on_path_challenge -6009 && !self.state.is_closed() -6010 && builder.frame_space_remaining() > frame::PathChallenge::SIZE_BOUND -6011 // we don't want to send new challenges if we are already closing -6012 { -6013 path.pending_on_path_challenge = false; +5941 // Subtract 1 to account for the CID we supplied while handshaking +5942 let mut n = self.peer_params.issue_cids_limit() - 1; +5943 if let ConnectionSide::Server { server_config } = &self.side +5944 && server_config.has_preferred_address() +5945 { +5946 // We also sent a CID in the transport parameters +5947 n -= 1; +5948 } +5949 debug_assert!(!self.state.is_drained()); // requirement for endpoint_events +5950 self.endpoint_events +5951 .push_back(EndpointEventInner::NeedIdentifiers(PathId::ZERO, now, n)); +5952 } +5953 +5954 /// Issues an initial set of CIDs for paths that have not yet had any CIDs issued +5955 /// +5956 /// Later CIDs are issued when CIDs expire or are retired by the peer. +5957 fn issue_first_path_cids(&mut self, now: Instant) { +5958 if let Some(max_path_id) = self.max_path_id() { +5959 let mut path_id = self.max_path_id_with_cids.next(); +5960 while path_id <= max_path_id { +5961 self.endpoint_events +5962 .push_back(EndpointEventInner::NeedIdentifiers( +5963 path_id, +5964 now, +5965 self.peer_params.issue_cids_limit(), +5966 )); +5967 path_id = path_id.next(); +5968 } +5969 self.max_path_id_with_cids = max_path_id; +5970 } +5971 } +5972 +5973 /// Populates a packet with frames +5974 /// +5975 /// This tries to fit as many frames as possible into the packet. +5976 /// +5977 /// *path_exclusive_only* means to only build frames which can only be sent on this +5978 /// *path. This is used in multipath for backup paths while there is still an active +5979 /// *path. +5980 fn populate_packet<'a, 'b>( +5981 &mut self, +5982 now: Instant, +5983 space_id: SpaceId, +5984 path_id: PathId, +5985 scheduling_info: &PathSchedulingInfo, +5986 builder: &mut PacketBuilder<'a, 'b>, +5987 ) { +5988 let is_multipath_negotiated = self.is_multipath_negotiated(); +5989 let space_has_keys = self.crypto_state.has_keys(space_id.encryption_level()); +5990 let is_0rtt = space_id == SpaceId::Data && !space_has_keys; +5991 let stats = &mut self.path_stats.for_path(path_id).frame_tx; +5992 let space = &mut self.spaces[space_id]; +5993 let path = &mut self.paths.get_mut(&path_id).expect("known path").data; +5994 space +5995 .for_path(path_id) +5996 .pending_acks +5997 .maybe_ack_non_eliciting(); +5998 +5999 // HANDSHAKE_DONE +6000 if !is_0rtt +6001 && !scheduling_info.is_abandoned +6002 && scheduling_info.may_send_data +6003 && mem::replace(&mut space.pending.handshake_done, false) +6004 { +6005 builder.write_frame(frame::HandshakeDone, stats); +6006 } +6007 +6008 // PING +6009 if !scheduling_info.is_abandoned +6010 && mem::replace(&mut space.for_path(path_id).ping_pending, false) +6011 { +6012 builder.write_frame(frame::Ping, stats); +6013 } 6014 -6015 let token = self.rng.random(); -6016 path.record_path_challenge_sent(now, token, path.network_path); -6017 // Generate a new challenge every time we send a new PATH_CHALLENGE -6018 let challenge = frame::PathChallenge(token); -6019 builder.write_frame(challenge, stats); -6020 builder.require_padding(); -6021 let pto = self.ack_frequency.max_ack_delay_for_pto() + path.rtt.pto_base(); -6022 match path.open_status { -6023 paths::OpenStatus::Sent | paths::OpenStatus::Informed => {} -6024 paths::OpenStatus::Pending => { -6025 path.open_status = paths::OpenStatus::Sent; -6026 self.timers.set( -6027 Timer::PerPath(path_id, PathTimer::AbandonFromValidation), -6028 now + 3 * pto, -6029 self.qlog.with_time(now), -6030 ); -6031 } -6032 // The path open status was informed before, we just want to revalidate again. -6033 // For that, we want to make sure we set the PathOpenFailed timer again. -6034 paths::OpenStatus::Revalidating => { -6035 path.open_status = paths::OpenStatus::Informed; -6036 self.timers.set( -6037 Timer::PerPath(path_id, PathTimer::AbandonFromValidation), -6038 now + 3 * pto, -6039 self.qlog.with_time(now), -6040 ); -6041 } -6042 } -6043 -6044 self.timers.set( -6045 Timer::PerPath(path_id, PathTimer::PathChallengeLost), -6046 now + pto, -6047 self.qlog.with_time(now), -6048 ); +6015 // IMMEDIATE_ACK +6016 if !scheduling_info.is_abandoned +6017 && mem::replace(&mut space.for_path(path_id).immediate_ack_pending, false) +6018 { +6019 debug_assert_eq!( +6020 space_id, +6021 SpaceId::Data, +6022 "immediate acks must be sent in the data space" +6023 ); +6024 builder.write_frame(frame::ImmediateAck, stats); +6025 } +6026 +6027 // ACK +6028 if !scheduling_info.is_abandoned && scheduling_info.may_send_data { +6029 for path_id in space +6030 .number_spaces +6031 .iter_mut() +6032 .filter(|(_, pns)| pns.pending_acks.can_send()) +6033 .map(|(&path_id, _)| path_id) +6034 .collect::<Vec<_>>() +6035 { +6036 Self::populate_acks( +6037 now, +6038 self.receiving_ecn, +6039 path_id, +6040 space_id, +6041 space, +6042 is_multipath_negotiated, +6043 builder, +6044 stats, +6045 space_has_keys, +6046 ); +6047 } +6048 } 6049 -6050 if is_multipath_negotiated && !path.validated && path.pending_on_path_challenge { -6051 // queue informing the path status along with the challenge -6052 space.pending.path_status.insert(path_id); -6053 } -6054 -6055 // Always include an OBSERVED_ADDR frame with a PATH_CHALLENGE, regardless -6056 // of whether one has already been sent on this path. -6057 if space_id == SpaceId::Data -6058 && self -6059 .config -6060 .address_discovery_role -6061 .should_report(&self.peer_params.address_discovery_role) -6062 { -6063 let frame = frame::ObservedAddr::new( -6064 path.network_path.remote, -6065 self.next_observed_addr_seq_no, -6066 ); -6067 if builder.frame_space_remaining() > frame.size() { -6068 builder.write_frame(frame, stats); -6069 -6070 self.next_observed_addr_seq_no = -6071 self.next_observed_addr_seq_no.saturating_add(1u8); -6072 path.observed_addr_sent = true; -6073 -6074 space.pending.observed_addr = false; -6075 } -6076 } +6050 // ACK_FREQUENCY +6051 if !scheduling_info.is_abandoned +6052 && scheduling_info.may_send_data +6053 && mem::replace(&mut space.pending.ack_frequency, false) +6054 { +6055 let sequence_number = self.ack_frequency.next_sequence_number(); +6056 +6057 // Safe to unwrap because this is always provided when ACK frequency is enabled +6058 let config = self.config.ack_frequency_config.as_ref().unwrap(); +6059 +6060 // Ensure the delay is within bounds to avoid a PROTOCOL_VIOLATION error +6061 let max_ack_delay = self.ack_frequency.candidate_max_ack_delay( +6062 path.rtt.get(), +6063 config, +6064 &self.peer_params, +6065 ); +6066 +6067 let frame = frame::AckFrequency { +6068 sequence: sequence_number, +6069 ack_eliciting_threshold: config.ack_eliciting_threshold, +6070 request_max_ack_delay: max_ack_delay.as_micros().try_into().unwrap_or(VarInt::MAX), +6071 reordering_threshold: config.reordering_threshold, +6072 }; +6073 builder.write_frame(frame, stats); +6074 +6075 self.ack_frequency +6076 .ack_frequency_sent(path_id, builder.packet_number, max_ack_delay); 6077 } 6078 -6079 // PATH_RESPONSE +6079 // PATH_CHALLENGE 6080 if !scheduling_info.is_abandoned 6081 && space_id == SpaceId::Data -6082 && builder.frame_space_remaining() > frame::PathResponse::SIZE_BOUND -6083 && let Some(token) = path.path_responses.pop_on_path(path.network_path) -6084 { -6085 let response = frame::PathResponse(token); -6086 builder.write_frame(response, stats); -6087 builder.require_padding(); +6082 && path.pending_on_path_challenge +6083 && !self.state.is_closed() +6084 && builder.frame_space_remaining() > frame::PathChallenge::SIZE_BOUND +6085 // we don't want to send new challenges if we are already closing +6086 { +6087 path.pending_on_path_challenge = false; 6088 -6089 // NOTE: this is technically not required but might be useful to ride the -6090 // request/response nature of path challenges to refresh an observation -6091 // Since PATH_RESPONSE is a probing frame, this is allowed by the spec. -6092 if space_id == SpaceId::Data -6093 && self -6094 .config -6095 .address_discovery_role -6096 .should_report(&self.peer_params.address_discovery_role) -6097 { -6098 let frame = frame::ObservedAddr::new( -6099 path.network_path.remote, -6100 self.next_observed_addr_seq_no, -6101 ); -6102 if builder.frame_space_remaining() > frame.size() { -6103 builder.write_frame(frame, stats); -6104 -6105 self.next_observed_addr_seq_no = -6106 self.next_observed_addr_seq_no.saturating_add(1u8); -6107 path.observed_addr_sent = true; -6108 -6109 space.pending.observed_addr = false; -6110 } -6111 } -6112 } +6089 let token = self.rng.random(); +6090 path.record_path_challenge_sent(now, token, path.network_path); +6091 // Generate a new challenge every time we send a new PATH_CHALLENGE +6092 let challenge = frame::PathChallenge(token); +6093 builder.write_frame(challenge, stats); +6094 builder.require_padding(); +6095 let pto = self.ack_frequency.max_ack_delay_for_pto() + path.rtt.pto_base(); +6096 match path.open_status { +6097 paths::OpenStatus::Sent | paths::OpenStatus::Informed => {} +6098 paths::OpenStatus::Pending => { +6099 path.open_status = paths::OpenStatus::Sent; +6100 self.timers.set( +6101 Timer::PerPath(path_id, PathTimer::AbandonFromValidation), +6102 now + 3 * pto, +6103 self.qlog.with_time(now), +6104 ); +6105 } +6106 } +6107 +6108 self.timers.set( +6109 Timer::PerPath(path_id, PathTimer::PathChallengeLost), +6110 now + pto, +6111 self.qlog.with_time(now), +6112 ); 6113 -6114 // REACH_OUT -6115 if !scheduling_info.is_abandoned -6116 && scheduling_info.may_send_data -6117 && let Some((round, addresses)) = space.pending.reach_out.as_mut() -6118 { -6119 while let Some(local_addr) = addresses.iter().next().copied() { -6120 let local_addr = addresses.take(&local_addr).expect("found from iter"); -6121 let reach_out = frame::ReachOut::new(*round, local_addr); -6122 if builder.frame_space_remaining() > reach_out.size() { -6123 builder.write_frame(reach_out, stats); -6124 } else { -6125 addresses.insert(local_addr); -6126 break; -6127 } -6128 } -6129 if addresses.is_empty() { -6130 space.pending.reach_out = None; -6131 } -6132 } +6114 if is_multipath_negotiated && !path.validated && path.pending_on_path_challenge { +6115 // queue informing the path status along with the challenge +6116 space.pending.path_status.insert(path_id); +6117 } +6118 +6119 // Always include an OBSERVED_ADDR frame with a PATH_CHALLENGE, regardless +6120 // of whether one has already been sent on this path. +6121 if space_id == SpaceId::Data +6122 && self +6123 .config +6124 .address_discovery_role +6125 .should_report(&self.peer_params.address_discovery_role) +6126 { +6127 let frame = frame::ObservedAddr::new( +6128 path.network_path.remote, +6129 self.next_observed_addr_seq_no, +6130 ); +6131 if builder.frame_space_remaining() > frame.size() { +6132 builder.write_frame(frame, stats); 6133 -6134 // PATH_ABANDON -6135 if space_id == SpaceId::Data -6136 && scheduling_info.is_abandoned -6137 && scheduling_info.may_self_abandon -6138 && frame::PathAbandon::SIZE_BOUND <= builder.frame_space_remaining() -6139 && let Some(error_code) = space.pending.path_abandon.remove(&path_id) -6140 { -6141 let frame = frame::PathAbandon { -6142 path_id, -6143 error_code, -6144 }; -6145 builder.write_frame(frame, stats); -6146 -6147 // Consider remotely issued CIDs as retired now that we have sent this frame at -6148 // least once. -6149 self.remote_cids.remove(&path_id); -6150 } -6151 while space_id == SpaceId::Data -6152 && scheduling_info.may_send_data -6153 && frame::PathAbandon::SIZE_BOUND <= builder.frame_space_remaining() -6154 && let Some((abandoned_path_id, error_code)) = space.pending.path_abandon.pop_first() -6155 { -6156 let frame = frame::PathAbandon { -6157 path_id: abandoned_path_id, -6158 error_code, -6159 }; -6160 builder.write_frame(frame, stats); -6161 -6162 // Consider remotely issued CIDs as retired now that we have sent this frame at -6163 // least once. -6164 self.remote_cids.remove(&abandoned_path_id); -6165 } -6166 -6167 // OBSERVED_ADDR -6168 if !scheduling_info.is_abandoned -6169 && scheduling_info.may_send_data -6170 && space_id == SpaceId::Data -6171 && self -6172 .config -6173 .address_discovery_role -6174 .should_report(&self.peer_params.address_discovery_role) -6175 && (!path.observed_addr_sent || space.pending.observed_addr) -6176 { -6177 let frame = -6178 frame::ObservedAddr::new(path.network_path.remote, self.next_observed_addr_seq_no); -6179 if builder.frame_space_remaining() > frame.size() { -6180 builder.write_frame(frame, stats); -6181 -6182 self.next_observed_addr_seq_no = self.next_observed_addr_seq_no.saturating_add(1u8); -6183 path.observed_addr_sent = true; -6184 -6185 space.pending.observed_addr = false; -6186 } +6134 self.next_observed_addr_seq_no = +6135 self.next_observed_addr_seq_no.saturating_add(1u8); +6136 path.observed_addr_sent = true; +6137 +6138 space.pending.observed_addr = false; +6139 } +6140 } +6141 } +6142 +6143 // PATH_RESPONSE +6144 if !scheduling_info.is_abandoned +6145 && space_id == SpaceId::Data +6146 && builder.frame_space_remaining() > frame::PathResponse::SIZE_BOUND +6147 && let Some(token) = path.path_responses.pop_on_path(path.network_path) +6148 { +6149 let response = frame::PathResponse(token); +6150 builder.write_frame(response, stats); +6151 builder.require_padding(); +6152 +6153 // NOTE: this is technically not required but might be useful to ride the +6154 // request/response nature of path challenges to refresh an observation +6155 // Since PATH_RESPONSE is a probing frame, this is allowed by the spec. +6156 if space_id == SpaceId::Data +6157 && self +6158 .config +6159 .address_discovery_role +6160 .should_report(&self.peer_params.address_discovery_role) +6161 { +6162 let frame = frame::ObservedAddr::new( +6163 path.network_path.remote, +6164 self.next_observed_addr_seq_no, +6165 ); +6166 if builder.frame_space_remaining() > frame.size() { +6167 builder.write_frame(frame, stats); +6168 +6169 self.next_observed_addr_seq_no = +6170 self.next_observed_addr_seq_no.saturating_add(1u8); +6171 path.observed_addr_sent = true; +6172 +6173 space.pending.observed_addr = false; +6174 } +6175 } +6176 } +6177 +6178 // REACH_OUT +6179 while !scheduling_info.is_abandoned +6180 && scheduling_info.may_send_data +6181 && let Some(reach_out) = space +6182 .pending +6183 .reach_out +6184 .pop_if(|frame| builder.frame_space_remaining() >= frame.size()) +6185 { +6186 builder.write_frame(reach_out, stats); 6187 } 6188 -6189 // CRYPTO -6190 while !is_0rtt -6191 && !scheduling_info.is_abandoned -6192 && scheduling_info.may_send_data -6193 && builder.frame_space_remaining() > frame::Crypto::SIZE_BOUND -6194 { -6195 let Some(mut frame) = space.pending.crypto.pop_front() else { -6196 break; -6197 }; -6198 -6199 // Calculate the maximum amount of crypto data we can store in the buffer. -6200 // Since the offset is known, we can reserve the exact size required to encode it. -6201 // For length we reserve 2bytes which allows to encode up to 2^14, -6202 // which is more than what fits into normally sized QUIC frames. -6203 let max_crypto_data_size = builder.frame_space_remaining() -6204 - 1 // Frame Type -6205 - VarInt::size(unsafe { VarInt::from_u64_unchecked(frame.offset) }) -6206 - 2; // Maximum encoded length for frame size, given we send less than 2^14 bytes -6207 -6208 let len = frame -6209 .data -6210 .len() -6211 .min(2usize.pow(14) - 1) -6212 .min(max_crypto_data_size); -6213 -6214 let data = frame.data.split_to(len); -6215 let offset = frame.offset; -6216 let truncated = frame::Crypto { offset, data }; -6217 builder.write_frame(truncated, stats); -6218 -6219 if !frame.data.is_empty() { -6220 frame.offset += len as u64; -6221 space.pending.crypto.push_front(frame); -6222 } -6223 } -6224 -6225 // PATH_STATUS_AVAILABLE & PATH_STATUS_BACKUP -6226 while space_id == SpaceId::Data -6227 && !scheduling_info.is_abandoned -6228 && scheduling_info.may_send_data -6229 && frame::PathStatusAvailable::SIZE_BOUND <= builder.frame_space_remaining() -6230 { -6231 let Some(path_id) = space.pending.path_status.pop_first() else { -6232 break; -6233 }; -6234 let Some(path) = self.paths.get(&path_id).map(|path_state| &path_state.data) else { -6235 trace!(%path_id, "discarding queued path status for unknown path"); -6236 continue; -6237 }; -6238 -6239 let seq = path.status.seq(); -6240 match path.local_status() { -6241 PathStatus::Available => { -6242 let frame = frame::PathStatusAvailable { -6243 path_id, -6244 status_seq_no: seq, -6245 }; -6246 builder.write_frame(frame, stats); -6247 } -6248 PathStatus::Backup => { -6249 let frame = frame::PathStatusBackup { -6250 path_id, -6251 status_seq_no: seq, -6252 }; -6253 builder.write_frame(frame, stats); -6254 } -6255 } -6256 } -6257 -6258 // MAX_PATH_ID -6259 if space_id == SpaceId::Data -6260 && !scheduling_info.is_abandoned -6261 && scheduling_info.may_send_data -6262 && space.pending.max_path_id -6263 && frame::MaxPathId::SIZE_BOUND <= builder.frame_space_remaining() -6264 { -6265 let frame = frame::MaxPathId(self.local_max_path_id); -6266 builder.write_frame(frame, stats); -6267 space.pending.max_path_id = false; -6268 } -6269 -6270 // PATHS_BLOCKED -6271 if space_id == SpaceId::Data -6272 && !scheduling_info.is_abandoned -6273 && scheduling_info.may_send_data -6274 && space.pending.paths_blocked -6275 && frame::PathsBlocked::SIZE_BOUND <= builder.frame_space_remaining() -6276 { -6277 let frame = frame::PathsBlocked(self.remote_max_path_id); -6278 builder.write_frame(frame, stats); -6279 space.pending.paths_blocked = false; -6280 } -6281 -6282 // PATH_CIDS_BLOCKED -6283 while space_id == SpaceId::Data -6284 && !scheduling_info.is_abandoned -6285 && scheduling_info.may_send_data -6286 && frame::PathCidsBlocked::SIZE_BOUND <= builder.frame_space_remaining() -6287 { -6288 let Some(path_id) = space.pending.path_cids_blocked.pop_first() else { -6289 break; -6290 }; -6291 let next_seq = match self.remote_cids.get(&path_id) { -6292 Some(cid_queue) => VarInt(cid_queue.active_seq() + 1), -6293 None => VarInt(0), -6294 }; -6295 let frame = frame::PathCidsBlocked { path_id, next_seq }; -6296 builder.write_frame(frame, stats); -6297 } -6298 -6299 // RESET_STREAM, STOP_SENDING, MAX_DATA, MAX_STREAM_DATA, MAX_STREAMS -6300 if space_id == SpaceId::Data -6301 && !scheduling_info.is_abandoned -6302 && scheduling_info.may_send_data -6303 { -6304 self.streams -6305 .write_control_frames(builder, &mut space.pending, stats); -6306 } -6307 -6308 // NEW_CONNECTION_ID -6309 let cid_len = self -6310 .local_cid_state -6311 .values() -6312 .map(|cid_state| cid_state.cid_len()) -6313 .max() -6314 .expect("some local CID state must exist"); -6315 let new_cid_size_bound = -6316 frame::NewConnectionId::size_bound(is_multipath_negotiated, cid_len); -6317 while !scheduling_info.is_abandoned -6318 && scheduling_info.may_send_data -6319 && builder.frame_space_remaining() > new_cid_size_bound -6320 { -6321 let Some(issued) = space.pending.new_cids.pop() else { -6322 break; -6323 }; -6324 let retire_prior_to = self -6325 .local_cid_state -6326 .get(&issued.path_id) -6327 .map(|cid_state| cid_state.retire_prior_to()) -6328 .unwrap_or_else(|| panic!("missing local CID state for path={}", issued.path_id)); -6329 -6330 let cid_path_id = match is_multipath_negotiated { -6331 true => Some(issued.path_id), -6332 false => { -6333 debug_assert_eq!(issued.path_id, PathId::ZERO); -6334 None -6335 } -6336 }; -6337 let frame = frame::NewConnectionId { -6338 path_id: cid_path_id, -6339 sequence: issued.sequence, -6340 retire_prior_to, -6341 id: issued.id, -6342 reset_token: issued.reset_token, -6343 }; -6344 builder.write_frame(frame, stats); -6345 } -6346 -6347 // RETIRE_CONNECTION_ID -6348 let retire_cid_bound = frame::RetireConnectionId::size_bound(is_multipath_negotiated); -6349 while !scheduling_info.is_abandoned -6350 && scheduling_info.may_send_data -6351 && builder.frame_space_remaining() > retire_cid_bound -6352 { -6353 let (path_id, sequence) = match space.pending.retire_cids.pop() { -6354 Some((PathId::ZERO, seq)) if !is_multipath_negotiated => (None, seq), -6355 Some((path_id, seq)) => (Some(path_id), seq), -6356 None => break, -6357 }; -6358 let frame = frame::RetireConnectionId { path_id, sequence }; -6359 builder.write_frame(frame, stats); -6360 } -6361 -6362 // DATAGRAM -6363 let mut sent_datagrams = false; -6364 while !scheduling_info.is_abandoned -6365 && scheduling_info.may_send_data -6366 && builder.frame_space_remaining() > Datagram::SIZE_BOUND -6367 && space_id == SpaceId::Data -6368 { -6369 match self.datagrams.write(builder, stats) { -6370 true => { -6371 sent_datagrams = true; -6372 } -6373 false => break, -6374 } -6375 } -6376 if self.datagrams.send_blocked && sent_datagrams { -6377 self.events.push_back(Event::DatagramsUnblocked); -6378 self.datagrams.send_blocked = false; -6379 } -6380 -6381 let path = &mut self.paths.get_mut(&path_id).expect("known path").data; -6382 -6383 // NEW_TOKEN -6384 if !scheduling_info.is_abandoned && scheduling_info.may_send_data { -6385 while let Some(network_path) = space.pending.new_tokens.pop() { -6386 debug_assert_eq!(space_id, SpaceId::Data); -6387 let ConnectionSide::Server { server_config } = &self.side else { -6388 panic!("NEW_TOKEN frames should not be enqueued by clients"); -6389 }; -6390 -6391 if !network_path.is_probably_same_path(&path.network_path) { -6392 // NEW_TOKEN frames contain tokens bound to a client's IP address, and are only -6393 // useful if used from the same IP address. Thus, we abandon enqueued NEW_TOKEN -6394 // frames upon an path change. Instead, when the new path becomes validated, -6395 // NEW_TOKEN frames may be enqueued for the new path instead. -6396 continue; -6397 } -6398 -6399 let token = Token::new( -6400 TokenPayload::Validation { -6401 ip: network_path.remote.ip(), -6402 issued: server_config.time_source.now(), -6403 }, -6404 &mut self.rng, -6405 ); -6406 let new_token = NewToken { -6407 token: token.encode(&*server_config.token_key).into(), -6408 }; -6409 -6410 if builder.frame_space_remaining() < new_token.size() { -6411 space.pending.new_tokens.push(network_path); -6412 break; -6413 } -6414 -6415 builder.write_frame(new_token, stats); -6416 builder.retransmits_mut().new_tokens.push(network_path); -6417 } -6418 } -6419 -6420 // ADD_ADDRESS -6421 while space_id == SpaceId::Data -6422 && !scheduling_info.is_abandoned -6423 && scheduling_info.may_send_data -6424 && frame::AddAddress::SIZE_BOUND <= builder.frame_space_remaining() -6425 { -6426 if let Some(added_address) = space.pending.add_address.pop_last() { -6427 builder.write_frame(added_address, stats); -6428 } else { -6429 break; -6430 } -6431 } -6432 -6433 // REMOVE_ADDRESS -6434 while space_id == SpaceId::Data -6435 && !scheduling_info.is_abandoned -6436 && scheduling_info.may_send_data -6437 && frame::RemoveAddress::SIZE_BOUND <= builder.frame_space_remaining() -6438 { -6439 if let Some(removed_address) = space.pending.remove_address.pop_last() { -6440 builder.write_frame(removed_address, stats); -6441 } else { -6442 break; -6443 } -6444 } -6445 -6446 // STREAM -6447 if !scheduling_info.is_abandoned -6448 && scheduling_info.may_send_data -6449 && space_id == SpaceId::Data -6450 { -6451 self.streams -6452 .write_stream_frames(builder, self.config.send_fairness, stats); -6453 } -6454 } -6455 -6456 /// Write pending ACKs into a buffer -6457 fn populate_acks<'a, 'b>( -6458 now: Instant, -6459 receiving_ecn: bool, -6460 path_id: PathId, -6461 space_id: SpaceId, -6462 space: &mut PacketSpace, -6463 is_multipath_negotiated: bool, -6464 builder: &mut PacketBuilder<'a, 'b>, -6465 stats: &mut FrameStats, -6466 space_has_keys: bool, -6467 ) { -6468 // 0-RTT packets must never carry acks (which would have to be of handshake packets) -6469 debug_assert!(space_has_keys, "tried to send ACK in 0-RTT"); -6470 -6471 debug_assert!( -6472 is_multipath_negotiated || path_id == PathId::ZERO, -6473 "Only PathId::ZERO allowed without multipath (have {path_id:?})" -6474 ); -6475 if is_multipath_negotiated { -6476 debug_assert!( -6477 space_id == SpaceId::Data || path_id == PathId::ZERO, -6478 "path acks must be sent in 1RTT space (have {space_id:?})" -6479 ); -6480 } -6481 -6482 let pns = space.for_path(path_id); -6483 let ranges = pns.pending_acks.ranges(); -6484 debug_assert!(!ranges.is_empty(), "can not send empty ACK range"); -6485 let ecn = if receiving_ecn { -6486 Some(&pns.ecn_counters) -6487 } else { -6488 None -6489 }; -6490 -6491 let delay_micros = pns.pending_acks.ack_delay(now).as_micros() as u64; -6492 // TODO: This should come from `TransportConfig` if that gets configurable. -6493 let ack_delay_exp = TransportParameters::default().ack_delay_exponent; -6494 let delay = delay_micros >> ack_delay_exp.into_inner(); -6495 -6496 if is_multipath_negotiated && space_id == SpaceId::Data { -6497 if !ranges.is_empty() { -6498 let frame = frame::PathAck::encoder(path_id, delay, ranges, ecn); -6499 builder.write_frame(frame, stats); -6500 } -6501 } else { -6502 builder.write_frame(frame::Ack::encoder(delay, ranges, ecn), stats); +6189 // PATH_ABANDON +6190 if space_id == SpaceId::Data +6191 && scheduling_info.is_abandoned +6192 && scheduling_info.may_self_abandon +6193 && frame::PathAbandon::SIZE_BOUND <= builder.frame_space_remaining() +6194 && let Some(error_code) = space.pending.path_abandon.remove(&path_id) +6195 { +6196 let frame = frame::PathAbandon { +6197 path_id, +6198 error_code, +6199 }; +6200 builder.write_frame(frame, stats); +6201 +6202 // Consider remotely issued CIDs as retired now that we have sent this frame at +6203 // least once. +6204 self.remote_cids.remove(&path_id); +6205 } +6206 while space_id == SpaceId::Data +6207 && scheduling_info.may_send_data +6208 && frame::PathAbandon::SIZE_BOUND <= builder.frame_space_remaining() +6209 && let Some((abandoned_path_id, error_code)) = space.pending.path_abandon.pop_first() +6210 { +6211 let frame = frame::PathAbandon { +6212 path_id: abandoned_path_id, +6213 error_code, +6214 }; +6215 builder.write_frame(frame, stats); +6216 +6217 // Consider remotely issued CIDs as retired now that we have sent this frame at +6218 // least once. +6219 self.remote_cids.remove(&abandoned_path_id); +6220 } +6221 +6222 // OBSERVED_ADDR +6223 if !scheduling_info.is_abandoned +6224 && scheduling_info.may_send_data +6225 && space_id == SpaceId::Data +6226 && self +6227 .config +6228 .address_discovery_role +6229 .should_report(&self.peer_params.address_discovery_role) +6230 && (!path.observed_addr_sent || space.pending.observed_addr) +6231 { +6232 let frame = +6233 frame::ObservedAddr::new(path.network_path.remote, self.next_observed_addr_seq_no); +6234 if builder.frame_space_remaining() > frame.size() { +6235 builder.write_frame(frame, stats); +6236 +6237 self.next_observed_addr_seq_no = self.next_observed_addr_seq_no.saturating_add(1u8); +6238 path.observed_addr_sent = true; +6239 +6240 space.pending.observed_addr = false; +6241 } +6242 } +6243 +6244 // CRYPTO +6245 while !is_0rtt +6246 && !scheduling_info.is_abandoned +6247 && scheduling_info.may_send_data +6248 && builder.frame_space_remaining() > frame::Crypto::SIZE_BOUND +6249 { +6250 let Some(mut frame) = space.pending.crypto.pop_front() else { +6251 break; +6252 }; +6253 +6254 // Calculate the maximum amount of crypto data we can store in the buffer. +6255 // Since the offset is known, we can reserve the exact size required to encode it. +6256 // For length we reserve 2bytes which allows to encode up to 2^14, +6257 // which is more than what fits into normally sized QUIC frames. +6258 let max_crypto_data_size = builder.frame_space_remaining() +6259 - 1 // Frame Type +6260 - VarInt::size(unsafe { VarInt::from_u64_unchecked(frame.offset) }) +6261 - 2; // Maximum encoded length for frame size, given we send less than 2^14 bytes +6262 +6263 let len = frame +6264 .data +6265 .len() +6266 .min(2usize.pow(14) - 1) +6267 .min(max_crypto_data_size); +6268 +6269 let data = frame.data.split_to(len); +6270 let offset = frame.offset; +6271 let truncated = frame::Crypto { offset, data }; +6272 builder.write_frame(truncated, stats); +6273 +6274 if !frame.data.is_empty() { +6275 frame.offset += len as u64; +6276 space.pending.crypto.push_front(frame); +6277 } +6278 } +6279 +6280 // PATH_STATUS_AVAILABLE & PATH_STATUS_BACKUP +6281 while space_id == SpaceId::Data +6282 && !scheduling_info.is_abandoned +6283 && scheduling_info.may_send_data +6284 && frame::PathStatusAvailable::SIZE_BOUND <= builder.frame_space_remaining() +6285 { +6286 let Some(path_id) = space.pending.path_status.pop_first() else { +6287 break; +6288 }; +6289 let Some(path) = self.paths.get(&path_id).map(|path_state| &path_state.data) else { +6290 trace!(%path_id, "discarding queued path status for unknown path"); +6291 continue; +6292 }; +6293 +6294 let seq = path.status.seq(); +6295 match path.local_status() { +6296 PathStatus::Available => { +6297 let frame = frame::PathStatusAvailable { +6298 path_id, +6299 status_seq_no: seq, +6300 }; +6301 builder.write_frame(frame, stats); +6302 } +6303 PathStatus::Backup => { +6304 let frame = frame::PathStatusBackup { +6305 path_id, +6306 status_seq_no: seq, +6307 }; +6308 builder.write_frame(frame, stats); +6309 } +6310 } +6311 } +6312 +6313 // MAX_PATH_ID +6314 if space_id == SpaceId::Data +6315 && !scheduling_info.is_abandoned +6316 && scheduling_info.may_send_data +6317 && space.pending.max_path_id +6318 && frame::MaxPathId::SIZE_BOUND <= builder.frame_space_remaining() +6319 { +6320 let frame = frame::MaxPathId(self.local_max_path_id); +6321 builder.write_frame(frame, stats); +6322 space.pending.max_path_id = false; +6323 } +6324 +6325 // PATHS_BLOCKED +6326 if space_id == SpaceId::Data +6327 && !scheduling_info.is_abandoned +6328 && scheduling_info.may_send_data +6329 && space.pending.paths_blocked +6330 && frame::PathsBlocked::SIZE_BOUND <= builder.frame_space_remaining() +6331 { +6332 let frame = frame::PathsBlocked(self.remote_max_path_id); +6333 builder.write_frame(frame, stats); +6334 space.pending.paths_blocked = false; +6335 } +6336 +6337 // PATH_CIDS_BLOCKED +6338 while space_id == SpaceId::Data +6339 && !scheduling_info.is_abandoned +6340 && scheduling_info.may_send_data +6341 && frame::PathCidsBlocked::SIZE_BOUND <= builder.frame_space_remaining() +6342 { +6343 let Some(path_id) = space.pending.path_cids_blocked.pop_first() else { +6344 break; +6345 }; +6346 let next_seq = match self.remote_cids.get(&path_id) { +6347 Some(cid_queue) => VarInt(cid_queue.active_seq() + 1), +6348 None => VarInt(0), +6349 }; +6350 let frame = frame::PathCidsBlocked { path_id, next_seq }; +6351 builder.write_frame(frame, stats); +6352 } +6353 +6354 // RESET_STREAM, STOP_SENDING, MAX_DATA, MAX_STREAM_DATA, MAX_STREAMS +6355 if space_id == SpaceId::Data +6356 && !scheduling_info.is_abandoned +6357 && scheduling_info.may_send_data +6358 { +6359 self.streams +6360 .write_control_frames(builder, &mut space.pending, stats); +6361 } +6362 +6363 // NEW_CONNECTION_ID +6364 let cid_len = self +6365 .local_cid_state +6366 .values() +6367 .map(|cid_state| cid_state.cid_len()) +6368 .max() +6369 .expect("some local CID state must exist"); +6370 let new_cid_size_bound = +6371 frame::NewConnectionId::size_bound(is_multipath_negotiated, cid_len); +6372 while !scheduling_info.is_abandoned +6373 && scheduling_info.may_send_data +6374 && builder.frame_space_remaining() > new_cid_size_bound +6375 { +6376 let Some(issued) = space.pending.new_cids.pop() else { +6377 break; +6378 }; +6379 // Path was discarded after this CID was queued, drop. +6380 let Some(cid_state) = self.local_cid_state.get(&issued.path_id) else { +6381 debug!( +6382 path = %issued.path_id, seq = issued.sequence, +6383 "dropping queued NEW_CONNECTION_ID for discarded path", +6384 ); +6385 continue; +6386 }; +6387 let retire_prior_to = cid_state.retire_prior_to(); +6388 +6389 let cid_path_id = match is_multipath_negotiated { +6390 true => Some(issued.path_id), +6391 false => { +6392 debug_assert_eq!(issued.path_id, PathId::ZERO); +6393 None +6394 } +6395 }; +6396 let frame = frame::NewConnectionId { +6397 path_id: cid_path_id, +6398 sequence: issued.sequence, +6399 retire_prior_to, +6400 id: issued.id, +6401 reset_token: issued.reset_token, +6402 }; +6403 builder.write_frame(frame, stats); +6404 } +6405 +6406 // RETIRE_CONNECTION_ID +6407 let retire_cid_bound = frame::RetireConnectionId::size_bound(is_multipath_negotiated); +6408 while !scheduling_info.is_abandoned +6409 && scheduling_info.may_send_data +6410 && builder.frame_space_remaining() > retire_cid_bound +6411 { +6412 let (path_id, sequence) = match space.pending.retire_cids.pop() { +6413 Some((PathId::ZERO, seq)) if !is_multipath_negotiated => (None, seq), +6414 Some((path_id, seq)) => (Some(path_id), seq), +6415 None => break, +6416 }; +6417 let frame = frame::RetireConnectionId { path_id, sequence }; +6418 builder.write_frame(frame, stats); +6419 } +6420 +6421 // DATAGRAM +6422 let mut sent_datagrams = false; +6423 while !scheduling_info.is_abandoned +6424 && scheduling_info.may_send_data +6425 && builder.frame_space_remaining() > Datagram::SIZE_BOUND +6426 && space_id == SpaceId::Data +6427 { +6428 match self.datagrams.write(builder, stats) { +6429 true => { +6430 sent_datagrams = true; +6431 } +6432 false => break, +6433 } +6434 } +6435 if self.datagrams.send_blocked && sent_datagrams { +6436 self.events.push_back(Event::DatagramsUnblocked); +6437 self.datagrams.send_blocked = false; +6438 } +6439 +6440 let path = &mut self.paths.get_mut(&path_id).expect("known path").data; +6441 +6442 // NEW_TOKEN +6443 if !scheduling_info.is_abandoned && scheduling_info.may_send_data { +6444 while let Some(network_path) = space.pending.new_tokens.pop() { +6445 debug_assert_eq!(space_id, SpaceId::Data); +6446 let ConnectionSide::Server { server_config } = &self.side else { +6447 panic!("NEW_TOKEN frames should not be enqueued by clients"); +6448 }; +6449 +6450 if !network_path.is_probably_same_path(&path.network_path) { +6451 // NEW_TOKEN frames contain tokens bound to a client's IP address, and are only +6452 // useful if used from the same IP address. Thus, we abandon enqueued NEW_TOKEN +6453 // frames upon an path change. Instead, when the new path becomes validated, +6454 // NEW_TOKEN frames may be enqueued for the new path instead. +6455 continue; +6456 } +6457 +6458 let token = Token::new( +6459 TokenPayload::Validation { +6460 ip: network_path.remote.ip(), +6461 issued: server_config.time_source.now(), +6462 }, +6463 &mut self.rng, +6464 ); +6465 let new_token = NewToken { +6466 token: token.encode(&*server_config.token_key).into(), +6467 }; +6468 +6469 if builder.frame_space_remaining() < new_token.size() { +6470 space.pending.new_tokens.push(network_path); +6471 break; +6472 } +6473 +6474 builder.write_frame(new_token, stats); +6475 builder.retransmits_mut().new_tokens.push(network_path); +6476 } +6477 } +6478 +6479 // ADD_ADDRESS +6480 while space_id == SpaceId::Data +6481 && !scheduling_info.is_abandoned +6482 && scheduling_info.may_send_data +6483 && frame::AddAddress::SIZE_BOUND <= builder.frame_space_remaining() +6484 { +6485 if let Some(added_address) = space.pending.add_address.pop_last() { +6486 builder.write_frame(added_address, stats); +6487 } else { +6488 break; +6489 } +6490 } +6491 +6492 // REMOVE_ADDRESS +6493 while space_id == SpaceId::Data +6494 && !scheduling_info.is_abandoned +6495 && scheduling_info.may_send_data +6496 && frame::RemoveAddress::SIZE_BOUND <= builder.frame_space_remaining() +6497 { +6498 if let Some(removed_address) = space.pending.remove_address.pop_last() { +6499 builder.write_frame(removed_address, stats); +6500 } else { +6501 break; +6502 } 6503 } -6504 } -6505 -6506 fn close_common(&mut self) { -6507 trace!("connection closed"); -6508 self.timers.reset(); -6509 } -6510 -6511 fn set_close_timer(&mut self, now: Instant) { -6512 // QUIC-MULTIPATH § 2.6 Connection Closure: draining for 3*PTO using the max PTO of -6513 // all paths. -6514 let pto_max = self.max_pto_for_space(self.highest_space); -6515 self.timers.set( -6516 Timer::Conn(ConnTimer::Close), -6517 now + 3 * pto_max, -6518 self.qlog.with_time(now), -6519 ); -6520 } -6521 -6522 /// Handle transport parameters received from the peer -6523 /// -6524 /// *remote_cid* and *local_cid* are the source and destination CIDs respectively of the -6525 /// *packet into which the transport parameters arrived. -6526 fn handle_peer_params( -6527 &mut self, -6528 params: TransportParameters, -6529 local_cid: ConnectionId, -6530 remote_cid: ConnectionId, -6531 now: Instant, -6532 ) -> Result<(), TransportError> { -6533 if Some(self.original_remote_cid) != params.initial_src_cid -6534 || (self.side.is_client() -6535 && (Some(self.initial_dst_cid) != params.original_dst_cid -6536 || self.retry_src_cid != params.retry_src_cid)) -6537 { -6538 return Err(TransportError::TRANSPORT_PARAMETER_ERROR( -6539 "CID authentication failure", -6540 )); -6541 } -6542 if params.initial_max_path_id.is_some() && (local_cid.is_empty() || remote_cid.is_empty()) { -6543 return Err(TransportError::PROTOCOL_VIOLATION( -6544 "multipath must not use zero-length CIDs", -6545 )); -6546 } -6547 -6548 self.set_peer_params(params); -6549 self.qlog.emit_peer_transport_params_received(self, now); -6550 -6551 Ok(()) -6552 } -6553 -6554 fn set_peer_params(&mut self, params: TransportParameters) { -6555 self.streams.set_params(&params); -6556 self.idle_timeout = -6557 negotiate_max_idle_timeout(self.config.max_idle_timeout, Some(params.max_idle_timeout)); -6558 trace!("negotiated max idle timeout {:?}", self.idle_timeout); -6559 -6560 if let Some(ref info) = params.preferred_address { -6561 // During the handshake PathId::ZERO exists. -6562 self.remote_cids.get_mut(&PathId::ZERO).expect("not yet abandoned").insert(frame::NewConnectionId { -6563 path_id: None, -6564 sequence: 1, -6565 id: info.connection_id, -6566 reset_token: info.stateless_reset_token, -6567 retire_prior_to: 0, -6568 }) -6569 .expect( -6570 "preferred address CID is the first received, and hence is guaranteed to be legal", -6571 ); -6572 let remote = self.path_data(PathId::ZERO).network_path.remote; -6573 self.set_reset_token(PathId::ZERO, remote, info.stateless_reset_token); -6574 } -6575 self.ack_frequency.peer_max_ack_delay = get_max_ack_delay(&params); -6576 -6577 let mut multipath_enabled = None; -6578 if let (Some(local_max_path_id), Some(remote_max_path_id)) = ( -6579 self.config.get_initial_max_path_id(), -6580 params.initial_max_path_id, -6581 ) { -6582 // multipath is enabled, register the local and remote maximums -6583 self.local_max_path_id = local_max_path_id; -6584 self.remote_max_path_id = remote_max_path_id; -6585 let initial_max_path_id = local_max_path_id.min(remote_max_path_id); -6586 debug!(%initial_max_path_id, "multipath negotiated"); -6587 multipath_enabled = Some(initial_max_path_id); -6588 } -6589 -6590 if let Some((max_locally_allowed_remote_addresses, max_remotely_allowed_remote_addresses)) = -6591 self.config -6592 .max_remote_nat_traversal_addresses -6593 .zip(params.max_remote_nat_traversal_addresses) -6594 { -6595 if let Some(max_initial_paths) = -6596 multipath_enabled.map(|path_id| path_id.saturating_add(1u8)) -6597 { -6598 let max_local_addresses = max_remotely_allowed_remote_addresses.get(); -6599 let max_remote_addresses = max_locally_allowed_remote_addresses.get(); -6600 self.n0_nat_traversal = n0_nat_traversal::State::new( -6601 max_remote_addresses, -6602 max_local_addresses, -6603 self.side(), -6604 ); -6605 debug!( -6606 %max_remote_addresses, %max_local_addresses, -6607 "n0's nat traversal negotiated" -6608 ); +6504 +6505 // STREAM +6506 if !scheduling_info.is_abandoned +6507 && scheduling_info.may_send_data +6508 && space_id == SpaceId::Data +6509 { +6510 self.streams +6511 .write_stream_frames(builder, self.config.send_fairness, stats); +6512 } +6513 } +6514 +6515 /// Write pending ACKs into a buffer +6516 fn populate_acks<'a, 'b>( +6517 now: Instant, +6518 receiving_ecn: bool, +6519 path_id: PathId, +6520 space_id: SpaceId, +6521 space: &mut PacketSpace, +6522 is_multipath_negotiated: bool, +6523 builder: &mut PacketBuilder<'a, 'b>, +6524 stats: &mut FrameStats, +6525 space_has_keys: bool, +6526 ) { +6527 // 0-RTT packets must never carry acks (which would have to be of handshake packets) +6528 debug_assert!(space_has_keys, "tried to send ACK in 0-RTT"); +6529 +6530 debug_assert!( +6531 is_multipath_negotiated || path_id == PathId::ZERO, +6532 "Only PathId::ZERO allowed without multipath (have {path_id:?})" +6533 ); +6534 if is_multipath_negotiated { +6535 debug_assert!( +6536 space_id == SpaceId::Data || path_id == PathId::ZERO, +6537 "path acks must be sent in 1RTT space (have {space_id:?})" +6538 ); +6539 } +6540 +6541 let pns = space.for_path(path_id); +6542 let ranges = pns.pending_acks.ranges(); +6543 debug_assert!(!ranges.is_empty(), "can not send empty ACK range"); +6544 let ecn = if receiving_ecn { +6545 Some(&pns.ecn_counters) +6546 } else { +6547 None +6548 }; +6549 +6550 let delay_micros = pns.pending_acks.ack_delay(now).as_micros() as u64; +6551 // TODO: This should come from `TransportConfig` if that gets configurable. +6552 let ack_delay_exp = TransportParameters::default().ack_delay_exponent; +6553 let delay = delay_micros >> ack_delay_exp.into_inner(); +6554 +6555 if is_multipath_negotiated && space_id == SpaceId::Data { +6556 if !ranges.is_empty() { +6557 let frame = frame::PathAck::encoder(path_id, delay, ranges, ecn); +6558 builder.write_frame(frame, stats); +6559 } +6560 } else { +6561 builder.write_frame(frame::Ack::encoder(delay, ranges, ecn), stats); +6562 } +6563 } +6564 +6565 fn close_common(&mut self) { +6566 trace!("connection closed"); +6567 self.timers.reset(); +6568 } +6569 +6570 fn set_close_timer(&mut self, now: Instant) { +6571 // QUIC-MULTIPATH § 2.6 Connection Closure: draining for 3*PTO using the max PTO of +6572 // all paths. +6573 let pto_max = self.max_pto_for_space(self.highest_space); +6574 self.timers.set( +6575 Timer::Conn(ConnTimer::Close), +6576 now + 3 * pto_max, +6577 self.qlog.with_time(now), +6578 ); +6579 } +6580 +6581 /// Handle transport parameters received from the peer +6582 /// +6583 /// *remote_cid* and *local_cid* are the source and destination CIDs respectively of the +6584 /// *packet into which the transport parameters arrived. +6585 fn handle_peer_params( +6586 &mut self, +6587 params: TransportParameters, +6588 local_cid: ConnectionId, +6589 remote_cid: ConnectionId, +6590 now: Instant, +6591 ) -> Result<(), TransportError> { +6592 if Some(self.original_remote_cid) != params.initial_src_cid +6593 || (self.side.is_client() +6594 && (Some(self.initial_dst_cid) != params.original_dst_cid +6595 || self.retry_src_cid != params.retry_src_cid)) +6596 { +6597 return Err(TransportError::TRANSPORT_PARAMETER_ERROR( +6598 "CID authentication failure", +6599 )); +6600 } +6601 if params.initial_max_path_id.is_some() && (local_cid.is_empty() || remote_cid.is_empty()) { +6602 return Err(TransportError::PROTOCOL_VIOLATION( +6603 "multipath must not use zero-length CIDs", +6604 )); +6605 } +6606 +6607 self.set_peer_params(params); +6608 self.qlog.emit_peer_transport_params_received(self, now); 6609 -6610 match self.side() { -6611 Side::Client => { -6612 if max_initial_paths.as_u32() < max_remote_addresses as u32 + 1 { -6613 // in this case the client might try to open `max_remote_addresses` new -6614 // paths, but the current multipath configuration will not allow it -6615 debug!(%max_initial_paths, %max_remote_addresses, "local client configuration might cause nat traversal issues") -6616 } else if max_local_addresses as u64 -6617 > params.active_connection_id_limit.into_inner() -6618 { -6619 // the server allows us to send at most `params.active_connection_id_limit` -6620 // but they might need at least `max_local_addresses` to effectively send -6621 // `PATH_CHALLENGE` frames to each advertised local address -6622 debug!(%max_local_addresses, remote_cid_limit=%params.active_connection_id_limit.into_inner(), "remote server configuration might cause nat traversal issues") -6623 } -6624 } -6625 Side::Server => { -6626 if (max_initial_paths.as_u32() as u64) < crate::LOCAL_CID_COUNT { -6627 debug!(%max_initial_paths, local_cid_limit=%crate::LOCAL_CID_COUNT, "local server configuration might cause nat traversal issues") -6628 } -6629 } -6630 } -6631 } else { -6632 debug!("n0 nat traversal enabled for both endpoints, but multipath is missing") -6633 } -6634 } +6610 Ok(()) +6611 } +6612 +6613 fn set_peer_params(&mut self, params: TransportParameters) { +6614 self.streams.set_params(&params); +6615 self.idle_timeout = +6616 negotiate_max_idle_timeout(self.config.max_idle_timeout, Some(params.max_idle_timeout)); +6617 trace!("negotiated max idle timeout {:?}", self.idle_timeout); +6618 +6619 if let Some(ref info) = params.preferred_address { +6620 // During the handshake PathId::ZERO exists. +6621 self.remote_cids.get_mut(&PathId::ZERO).expect("not yet abandoned").insert(frame::NewConnectionId { +6622 path_id: None, +6623 sequence: 1, +6624 id: info.connection_id, +6625 reset_token: info.stateless_reset_token, +6626 retire_prior_to: 0, +6627 }) +6628 .expect( +6629 "preferred address CID is the first received, and hence is guaranteed to be legal", +6630 ); +6631 let remote = self.path_data(PathId::ZERO).network_path.remote; +6632 self.set_reset_token(PathId::ZERO, remote, info.stateless_reset_token); +6633 } +6634 self.ack_frequency.peer_max_ack_delay = get_max_ack_delay(&params); 6635 -6636 self.peer_params = params; -6637 let peer_max_udp_payload_size = -6638 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()).unwrap_or(u16::MAX); -6639 self.path_data_mut(PathId::ZERO) -6640 .mtud -6641 .on_peer_max_udp_payload_size_received(peer_max_udp_payload_size); -6642 } -6643 -6644 /// Decrypts a packet, returning the packet number on success -6645 fn decrypt_packet( -6646 &mut self, -6647 now: Instant, -6648 path_id: PathId, -6649 packet: &mut Packet, -6650 ) -> Result<Option<u64>, Option<TransportError>> { -6651 let result = self -6652 .crypto_state -6653 .decrypt_packet_body(packet, path_id, &self.spaces)?; -6654 -6655 let Some(result) = result else { -6656 return Ok(None); -6657 }; -6658 -6659 if result.outgoing_key_update_acked -6660 && let Some(prev) = self.crypto_state.prev_crypto.as_mut() -6661 { -6662 prev.end_packet = Some((result.packet_number, now)); -6663 self.set_key_discard_timer(now, packet.header.space()); -6664 } -6665 -6666 if result.incoming_key_update { -6667 trace!("key update authenticated"); -6668 self.crypto_state -6669 .update_keys(Some((result.packet_number, now)), true); -6670 self.set_key_discard_timer(now, packet.header.space()); -6671 } -6672 -6673 Ok(Some(result.packet_number)) -6674 } -6675 -6676 fn peer_supports_ack_frequency(&self) -> bool { -6677 self.peer_params.min_ack_delay.is_some() -6678 } -6679 -6680 /// Send an IMMEDIATE_ACK frame to the remote endpoint -6681 /// -6682 /// According to the spec, this will result in an error if the remote endpoint does not support -6683 /// the Acknowledgement Frequency extension -6684 pub(crate) fn immediate_ack(&mut self, path_id: PathId) { -6685 debug_assert_eq!( -6686 self.highest_space, -6687 SpaceKind::Data, -6688 "immediate ack must be written in the data space" -6689 ); -6690 self.spaces[SpaceId::Data] -6691 .for_path(path_id) -6692 .immediate_ack_pending = true; -6693 } +6636 let mut multipath_enabled = None; +6637 if let (Some(local_max_path_id), Some(remote_max_path_id)) = ( +6638 self.config.get_initial_max_path_id(), +6639 params.initial_max_path_id, +6640 ) { +6641 // multipath is enabled, register the local and remote maximums +6642 self.local_max_path_id = local_max_path_id; +6643 self.remote_max_path_id = remote_max_path_id; +6644 let initial_max_path_id = local_max_path_id.min(remote_max_path_id); +6645 debug!(%initial_max_path_id, "multipath negotiated"); +6646 multipath_enabled = Some(initial_max_path_id); +6647 } +6648 +6649 if let Some((max_locally_allowed_remote_addresses, max_remotely_allowed_remote_addresses)) = +6650 self.config +6651 .max_remote_nat_traversal_addresses +6652 .zip(params.max_remote_nat_traversal_addresses) +6653 { +6654 if let Some(max_initial_paths) = +6655 multipath_enabled.map(|path_id| path_id.saturating_add(1u8)) +6656 { +6657 let max_local_addresses = max_remotely_allowed_remote_addresses.get(); +6658 let max_remote_addresses = max_locally_allowed_remote_addresses.get(); +6659 self.n0_nat_traversal = n0_nat_traversal::State::new( +6660 max_remote_addresses, +6661 max_local_addresses, +6662 self.side(), +6663 ); +6664 debug!( +6665 %max_remote_addresses, %max_local_addresses, +6666 "n0's nat traversal negotiated" +6667 ); +6668 +6669 match self.side() { +6670 Side::Client => { +6671 if max_initial_paths.as_u32() < max_remote_addresses as u32 + 1 { +6672 // in this case the client might try to open `max_remote_addresses` new +6673 // paths, but the current multipath configuration will not allow it +6674 debug!(%max_initial_paths, %max_remote_addresses, "local client configuration might cause nat traversal issues") +6675 } else if max_local_addresses as u64 +6676 > params.active_connection_id_limit.into_inner() +6677 { +6678 // the server allows us to send at most `params.active_connection_id_limit` +6679 // but they might need at least `max_local_addresses` to effectively send +6680 // `PATH_CHALLENGE` frames to each advertised local address +6681 debug!(%max_local_addresses, remote_cid_limit=%params.active_connection_id_limit.into_inner(), "remote server configuration might cause nat traversal issues") +6682 } +6683 } +6684 Side::Server => { +6685 if (max_initial_paths.as_u32() as u64) < crate::LOCAL_CID_COUNT { +6686 debug!(%max_initial_paths, local_cid_limit=%crate::LOCAL_CID_COUNT, "local server configuration might cause nat traversal issues") +6687 } +6688 } +6689 } +6690 } else { +6691 debug!("n0 nat traversal enabled for both endpoints, but multipath is missing") +6692 } +6693 } 6694 -6695 /// Decodes a packet, returning its decrypted payload, so it can be inspected in tests -6696 #[cfg(test)] -6697 pub(crate) fn decode_packet(&self, event: &ConnectionEvent) -> Option<Vec<u8>> { -6698 let ConnectionEventInner::Datagram(DatagramConnectionEvent { -6699 path_id, -6700 first_decode, -6701 remaining, -6702 .. -6703 }) = &event.0 -6704 else { -6705 return None; -6706 }; -6707 -6708 if remaining.is_some() { -6709 panic!("Packets should never be coalesced in tests"); -6710 } -6711 -6712 let decrypted_header = self -6713 .crypto_state -6714 .unprotect_header(first_decode.clone(), self.peer_params.stateless_reset_token)?; -6715 -6716 let mut packet = decrypted_header.packet?; -6717 self.crypto_state -6718 .decrypt_packet_body(&mut packet, *path_id, &self.spaces) -6719 .ok()?; -6720 -6721 Some(packet.payload.to_vec()) -6722 } -6723 -6724 /// The number of bytes of packets containing retransmittable frames that have not been -6725 /// acknowledged or declared lost. -6726 #[cfg(test)] -6727 pub(crate) fn bytes_in_flight(&self) -> u64 { -6728 // TODO(@divma): consider including for multipath? -6729 self.path_data(PathId::ZERO).in_flight.bytes -6730 } +6695 self.peer_params = params; +6696 let peer_max_udp_payload_size = +6697 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()).unwrap_or(u16::MAX); +6698 self.path_data_mut(PathId::ZERO) +6699 .mtud +6700 .on_peer_max_udp_payload_size_received(peer_max_udp_payload_size); +6701 } +6702 +6703 /// Decrypts a packet, returning the packet number on success +6704 fn decrypt_packet( +6705 &mut self, +6706 now: Instant, +6707 path_id: PathId, +6708 packet: &mut Packet, +6709 ) -> Result<Option<u64>, Option<TransportError>> { +6710 let result = self +6711 .crypto_state +6712 .decrypt_packet_body(packet, path_id, &self.spaces)?; +6713 +6714 let Some(result) = result else { +6715 return Ok(None); +6716 }; +6717 +6718 if result.outgoing_key_update_acked +6719 && let Some(prev) = self.crypto_state.prev_crypto.as_mut() +6720 { +6721 prev.end_packet = Some((result.packet_number, now)); +6722 self.set_key_discard_timer(now, packet.header.space()); +6723 } +6724 +6725 if result.incoming_key_update { +6726 trace!("key update authenticated"); +6727 self.crypto_state +6728 .update_keys(Some((result.packet_number, now)), true); +6729 self.set_key_discard_timer(now, packet.header.space()); +6730 } 6731 -6732 /// Number of bytes worth of non-ack-only packets that may be sent -6733 #[cfg(test)] -6734 pub(crate) fn congestion_window(&self) -> u64 { -6735 let path = self.path_data(PathId::ZERO); -6736 path.congestion -6737 .window() -6738 .saturating_sub(path.in_flight.bytes) -6739 } -6740 -6741 /// Whether no timers but keepalive, idle, rtt, pushnewcid, and key discard are running -6742 #[cfg(test)] -6743 pub(crate) fn is_idle(&self) -> bool { -6744 let current_timers = self.timers.values(); -6745 current_timers -6746 .into_iter() -6747 .filter(|(timer, _)| { -6748 !matches!( -6749 timer, -6750 Timer::Conn(ConnTimer::KeepAlive) -6751 | Timer::PerPath(_, PathTimer::PathKeepAlive) -6752 | Timer::Conn(ConnTimer::PushNewCid) -6753 | Timer::Conn(ConnTimer::KeyDiscard) -6754 ) -6755 }) -6756 .min_by_key(|(_, time)| *time) -6757 .is_none_or(|(timer, _)| { -6758 matches!( -6759 timer, -6760 Timer::Conn(ConnTimer::Idle) | Timer::PerPath(_, PathTimer::PathIdle) -6761 ) -6762 }) -6763 } -6764 -6765 /// Whether explicit congestion notification is in use on outgoing packets. -6766 #[cfg(test)] -6767 pub(crate) fn using_ecn(&self) -> bool { -6768 self.path_data(PathId::ZERO).sending_ecn -6769 } +6732 Ok(Some(result.packet_number)) +6733 } +6734 +6735 fn peer_supports_ack_frequency(&self) -> bool { +6736 self.peer_params.min_ack_delay.is_some() +6737 } +6738 +6739 /// Send an IMMEDIATE_ACK frame to the remote endpoint +6740 /// +6741 /// According to the spec, this will result in an error if the remote endpoint does not support +6742 /// the Acknowledgement Frequency extension +6743 pub(crate) fn immediate_ack(&mut self, path_id: PathId) { +6744 debug_assert_eq!( +6745 self.highest_space, +6746 SpaceKind::Data, +6747 "immediate ack must be written in the data space" +6748 ); +6749 self.spaces[SpaceId::Data] +6750 .for_path(path_id) +6751 .immediate_ack_pending = true; +6752 } +6753 +6754 /// Decodes a packet, returning its decrypted payload, so it can be inspected in tests +6755 #[cfg(test)] +6756 pub(crate) fn decode_packet(&self, event: &ConnectionEvent) -> Option<Vec<u8>> { +6757 let ConnectionEventInner::Datagram(DatagramConnectionEvent { +6758 path_id, +6759 first_decode, +6760 remaining, +6761 .. +6762 }) = &event.0 +6763 else { +6764 return None; +6765 }; +6766 +6767 if remaining.is_some() { +6768 panic!("Packets should never be coalesced in tests"); +6769 } 6770 -6771 /// The number of received bytes in the current path -6772 #[cfg(test)] -6773 pub(crate) fn total_recvd(&self) -> u64 { -6774 self.path_data(PathId::ZERO).total_recvd -6775 } -6776 -6777 #[cfg(test)] -6778 pub(crate) fn active_local_cid_seq(&self) -> (u64, u64) { -6779 self.local_cid_state -6780 .get(&PathId::ZERO) -6781 .unwrap() -6782 .active_seq() -6783 } -6784 -6785 #[cfg(test)] -6786 #[track_caller] -6787 pub(crate) fn active_local_path_cid_seq(&self, path_id: u32) -> (u64, u64) { -6788 self.local_cid_state -6789 .get(&PathId(path_id)) -6790 .unwrap() -6791 .active_seq() -6792 } -6793 -6794 /// Instruct the peer to replace previously issued CIDs by sending a NEW_CONNECTION_ID frame -6795 /// with updated `retire_prior_to` field set to `v` -6796 #[cfg(test)] -6797 pub(crate) fn rotate_local_cid(&mut self, v: u64, now: Instant) { -6798 let n = self -6799 .local_cid_state -6800 .get_mut(&PathId::ZERO) -6801 .unwrap() -6802 .assign_retire_seq(v); -6803 debug_assert!(!self.state.is_drained()); // requirement for endpoint_events -6804 self.endpoint_events -6805 .push_back(EndpointEventInner::NeedIdentifiers(PathId::ZERO, now, n)); -6806 } -6807 -6808 /// Check the current active remote CID sequence for `PathId::ZERO` -6809 #[cfg(test)] -6810 pub(crate) fn active_remote_cid_seq(&self) -> u64 { -6811 self.remote_cids.get(&PathId::ZERO).unwrap().active_seq() -6812 } -6813 -6814 /// Returns the detected maximum udp payload size for the current path -6815 #[cfg(test)] -6816 pub(crate) fn path_mtu(&self, path_id: PathId) -> u16 { -6817 self.path_data(path_id).current_mtu() -6818 } -6819 -6820 /// Triggers path validation on all paths -6821 #[cfg(test)] -6822 pub(crate) fn trigger_path_validation(&mut self) { -6823 for path in self.paths.values_mut() { -6824 path.data.pending_on_path_challenge = true; -6825 } -6826 } -6827 -6828 /// Simulates a protocol violation error for test purposes. -6829 #[cfg(test)] -6830 pub fn simulate_protocol_violation(&mut self, now: Instant) { -6831 if !self.state.is_closed() { -6832 self.state -6833 .move_to_closed(TransportError::PROTOCOL_VIOLATION("simulated violation")); -6834 self.close_common(); -6835 if !self.state.is_drained() { -6836 self.set_close_timer(now); -6837 } -6838 self.connection_close_pending = true; -6839 } -6840 } -6841 -6842 /// Whether we have on-path 1-RTT data to send. -6843 /// -6844 /// This checks for frames that can only be sent in the data space (1-RTT): -6845 /// - Pending PATH_CHALLENGE frames on the active and previous path if just migrated. -6846 /// - Pending PATH_RESPONSE frames. -6847 /// - Pending data to send in STREAM frames. -6848 /// - Pending DATAGRAM frames to send. -6849 /// -6850 /// See also [`PacketSpace::can_send`] which keeps track of all other frame types that -6851 /// may need to be sent. -6852 fn can_send_1rtt(&self, path_id: PathId, max_size: usize) -> SendableFrames { -6853 let space_specific = self.paths.get(&path_id).is_some_and(|path| { -6854 path.data.pending_on_path_challenge || !path.data.path_responses.is_empty() -6855 }); -6856 -6857 // Stream control frames are checked in PacketSpace::can_send, only check data here. -6858 let other = self.streams.can_send_stream_data() -6859 || self -6860 .datagrams -6861 .outgoing -6862 .front() -6863 .is_some_and(|x| x.size(true) <= max_size); -6864 -6865 // All `false` fields are set in PacketSpace::can_send. -6866 SendableFrames { -6867 acks: false, -6868 close: false, -6869 space_specific, -6870 other, -6871 } -6872 } -6873 -6874 /// Terminate the connection instantly, without sending a close packet -6875 fn kill(&mut self, reason: ConnectionError) { -6876 self.close_common(); -6877 self.state.move_to_drained(Some(reason)); -6878 // move_to_drained checks that we were never in drained before, so we -6879 // never sent a `Drained` event before (it's illegal to send more events after drained). -6880 self.endpoint_events.push_back(EndpointEventInner::Drained); -6881 } -6882 -6883 /// Storage size required for the largest packet that can be transmitted on all currently -6884 /// available paths -6885 /// -6886 /// Buffers passed to [`Connection::poll_transmit`] should be at least this large. -6887 /// -6888 /// When multipath is enabled, this value is the minimum MTU across all available paths. -6889 pub fn current_mtu(&self) -> u16 { -6890 self.paths -6891 .iter() -6892 .filter(|&(path_id, _path_state)| !self.abandoned_paths.contains(path_id)) -6893 .map(|(_path_id, path_state)| path_state.data.current_mtu()) -6894 .min() -6895 .unwrap_or(INITIAL_MTU) -6896 } -6897 -6898 /// Size of non-frame data for a 1-RTT packet -6899 /// -6900 /// Quantifies space consumed by the QUIC header and AEAD tag. All other bytes in a packet are -6901 /// frames. Changes if the length of the remote connection ID changes, which is expected to be -6902 /// rare. If `pn` is specified, may additionally change unpredictably due to variations in -6903 /// latency and packet loss. -6904 fn predict_1rtt_overhead(&mut self, pn: u64, path: PathId) -> usize { -6905 let pn_len = PacketNumber::new( -6906 pn, -6907 self.spaces[SpaceId::Data] -6908 .for_path(path) -6909 .largest_acked_packet_pn -6910 .unwrap_or(0), -6911 ) -6912 .len(); -6913 -6914 // 1 byte for flags -6915 1 + self -6916 .remote_cids -6917 .get(&path) -6918 .map(|cids| cids.active().len()) -6919 .unwrap_or(20) // Max CID len in QUIC v1 -6920 + pn_len -6921 + self.tag_len_1rtt() -6922 } +6771 let decrypted_header = self +6772 .crypto_state +6773 .unprotect_header(first_decode.clone(), self.peer_params.stateless_reset_token)?; +6774 +6775 let mut packet = decrypted_header.packet?; +6776 self.crypto_state +6777 .decrypt_packet_body(&mut packet, *path_id, &self.spaces) +6778 .ok()?; +6779 +6780 Some(packet.payload.to_vec()) +6781 } +6782 +6783 /// The number of bytes of packets containing retransmittable frames that have not been +6784 /// acknowledged or declared lost. +6785 #[cfg(test)] +6786 pub(crate) fn bytes_in_flight(&self) -> u64 { +6787 // TODO(@divma): consider including for multipath? +6788 self.path_data(PathId::ZERO).in_flight.bytes +6789 } +6790 +6791 /// Number of bytes worth of non-ack-only packets that may be sent +6792 #[cfg(test)] +6793 pub(crate) fn congestion_window(&self) -> u64 { +6794 let path = self.path_data(PathId::ZERO); +6795 path.congestion +6796 .window() +6797 .saturating_sub(path.in_flight.bytes) +6798 } +6799 +6800 /// Whether no timers but keepalive, idle, rtt, pushnewcid, and key discard are running +6801 #[cfg(test)] +6802 pub(crate) fn is_idle(&self) -> bool { +6803 let current_timers = self.timers.values(); +6804 current_timers +6805 .into_iter() +6806 .filter(|(timer, _)| { +6807 !matches!( +6808 timer, +6809 Timer::Conn(ConnTimer::KeepAlive) +6810 | Timer::PerPath(_, PathTimer::PathKeepAlive) +6811 | Timer::Conn(ConnTimer::PushNewCid) +6812 | Timer::Conn(ConnTimer::KeyDiscard) +6813 ) +6814 }) +6815 .min_by_key(|(_, time)| *time) +6816 .is_none_or(|(timer, _)| { +6817 matches!( +6818 timer, +6819 Timer::Conn(ConnTimer::Idle) | Timer::PerPath(_, PathTimer::PathIdle) +6820 ) +6821 }) +6822 } +6823 +6824 /// Whether explicit congestion notification is in use on outgoing packets. +6825 #[cfg(test)] +6826 pub(crate) fn using_ecn(&self) -> bool { +6827 self.path_data(PathId::ZERO).sending_ecn +6828 } +6829 +6830 /// The number of received bytes in the current path +6831 #[cfg(test)] +6832 pub(crate) fn total_recvd(&self) -> u64 { +6833 self.path_data(PathId::ZERO).total_recvd +6834 } +6835 +6836 #[cfg(test)] +6837 pub(crate) fn active_local_cid_seq(&self) -> (u64, u64) { +6838 self.local_cid_state +6839 .get(&PathId::ZERO) +6840 .unwrap() +6841 .active_seq() +6842 } +6843 +6844 #[cfg(test)] +6845 #[track_caller] +6846 pub(crate) fn active_local_path_cid_seq(&self, path_id: u32) -> (u64, u64) { +6847 self.local_cid_state +6848 .get(&PathId(path_id)) +6849 .unwrap() +6850 .active_seq() +6851 } +6852 +6853 /// Instruct the peer to replace previously issued CIDs by sending a NEW_CONNECTION_ID frame +6854 /// with updated `retire_prior_to` field set to `v` +6855 #[cfg(test)] +6856 pub(crate) fn rotate_local_cid(&mut self, v: u64, now: Instant) { +6857 let n = self +6858 .local_cid_state +6859 .get_mut(&PathId::ZERO) +6860 .unwrap() +6861 .assign_retire_seq(v); +6862 debug_assert!(!self.state.is_drained()); // requirement for endpoint_events +6863 self.endpoint_events +6864 .push_back(EndpointEventInner::NeedIdentifiers(PathId::ZERO, now, n)); +6865 } +6866 +6867 /// Check the current active remote CID sequence for `PathId::ZERO` +6868 #[cfg(test)] +6869 pub(crate) fn active_remote_cid_seq(&self) -> u64 { +6870 self.remote_cids.get(&PathId::ZERO).unwrap().active_seq() +6871 } +6872 +6873 /// Returns the detected maximum udp payload size for the current path +6874 #[cfg(test)] +6875 pub(crate) fn path_mtu(&self, path_id: PathId) -> u16 { +6876 self.path_data(path_id).current_mtu() +6877 } +6878 +6879 /// Triggers path validation on all paths +6880 #[cfg(test)] +6881 pub(crate) fn trigger_path_validation(&mut self) { +6882 for path in self.paths.values_mut() { +6883 path.data.pending_on_path_challenge = true; +6884 } +6885 } +6886 +6887 /// Simulates a protocol violation error for test purposes. +6888 #[cfg(test)] +6889 pub fn simulate_protocol_violation(&mut self, now: Instant) { +6890 if !self.state.is_closed() { +6891 self.state +6892 .move_to_closed(TransportError::PROTOCOL_VIOLATION("simulated violation")); +6893 self.close_common(); +6894 if !self.state.is_drained() { +6895 self.set_close_timer(now); +6896 } +6897 self.connection_close_pending = true; +6898 } +6899 } +6900 +6901 /// Whether we have on-path 1-RTT data to send. +6902 /// +6903 /// This checks for frames that can only be sent in the data space (1-RTT): +6904 /// - Pending PATH_CHALLENGE frames on the active and previous path if just migrated. +6905 /// - Pending PATH_RESPONSE frames. +6906 /// - Pending data to send in STREAM frames. +6907 /// - Pending DATAGRAM frames to send. +6908 /// +6909 /// See also [`PacketSpace::can_send`] which keeps track of all other frame types that +6910 /// may need to be sent. +6911 fn can_send_1rtt(&self, path_id: PathId, max_size: usize) -> SendableFrames { +6912 let space_specific = self.paths.get(&path_id).is_some_and(|path| { +6913 path.data.pending_on_path_challenge || !path.data.path_responses.is_empty() +6914 }); +6915 +6916 // Stream control frames are checked in PacketSpace::can_send, only check data here. +6917 let other = self.streams.can_send_stream_data() +6918 || self +6919 .datagrams +6920 .outgoing +6921 .front() +6922 .is_some_and(|x| x.size(true) <= max_size); 6923 -6924 fn predict_1rtt_overhead_no_pn(&self) -> usize { -6925 let pn_len = 4; -6926 -6927 let cid_len = self -6928 .remote_cids -6929 .values() -6930 .map(|cids| cids.active().len()) -6931 .max() -6932 .unwrap_or(20); // Max CID len in QUIC v1 -6933 -6934 // 1 byte for flags -6935 1 + cid_len + pn_len + self.tag_len_1rtt() -6936 } -6937 -6938 fn tag_len_1rtt(&self) -> usize { -6939 // encryption_keys for Data space returns 1-RTT keys if available, otherwise 0-RTT keys -6940 let packet_crypto = self -6941 .crypto_state -6942 .encryption_keys(SpaceKind::Data, self.side.side()) -6943 .map(|(_header, packet, _level)| packet); -6944 // If neither Data nor 0-RTT keys are available, make a reasonable tag length guess. As of -6945 // this writing, all QUIC cipher suites use 16-byte tags. We could return `None` instead, -6946 // but that would needlessly prevent sending datagrams during 0-RTT. -6947 packet_crypto.map_or(16, |x| x.tag_len()) -6948 } -6949 -6950 /// Mark the path as validated, and enqueue NEW_TOKEN frames to be sent as appropriate -6951 fn on_path_validated(&mut self, path_id: PathId) { -6952 self.path_data_mut(path_id).validated = true; -6953 let ConnectionSide::Server { server_config } = &self.side else { -6954 return; -6955 }; -6956 let network_path = self.path_data(path_id).network_path; -6957 let new_tokens = &mut self.spaces[SpaceId::Data as usize].pending.new_tokens; -6958 new_tokens.clear(); -6959 for _ in 0..server_config.validation_token.sent { -6960 new_tokens.push(network_path); -6961 } -6962 } -6963 -6964 /// Handle new path status information: PATH_STATUS_AVAILABLE, PATH_STATUS_BACKUP -6965 fn on_path_status(&mut self, path_id: PathId, status: PathStatus, status_seq_no: VarInt) { -6966 if let Some(path) = self.paths.get_mut(&path_id) { -6967 path.data.status.remote_update(status, status_seq_no); -6968 } else { -6969 debug!("PATH_STATUS_AVAILABLE received unknown path {:?}", path_id); -6970 } -6971 self.events.push_back( -6972 PathEvent::RemoteStatus { -6973 id: path_id, -6974 status, -6975 } -6976 .into(), -6977 ); -6978 } -6979 -6980 /// Returns the maximum [`PathId`] to be used for sending in this connection. -6981 /// -6982 /// This is calculated as minimum between the local and remote's maximums when multipath is -6983 /// enabled, or `None` when disabled. -6984 /// -6985 /// For data that's received, we should use [`Self::local_max_path_id`] instead. -6986 /// The reasoning is that the remote might already have updated to its own newer -6987 /// [`Self::max_path_id`] after sending out a `MAX_PATH_ID` frame, but it got re-ordered. -6988 fn max_path_id(&self) -> Option<PathId> { -6989 if self.is_multipath_negotiated() { -6990 Some(self.remote_max_path_id.min(self.local_max_path_id)) -6991 } else { -6992 None -6993 } -6994 } -6995 -6996 /// Returns whether this connection has a socket that supports IPv6. -6997 /// -6998 /// TODO(matheus23): This is related to noq endpoint state's `ipv6` bool. We should move that info -6999 /// here instead of trying to hack around not knowing it exactly. -7000 fn is_ipv6(&self) -> bool { -7001 self.paths -7002 .values() -7003 .any(|p| p.data.network_path.remote.is_ipv6()) -7004 } -7005 -7006 /// Add addresses the local endpoint considers are reachable for nat traversal. -7007 pub fn add_nat_traversal_address( -7008 &mut self, -7009 address: SocketAddr, -7010 ) -> Result<(), n0_nat_traversal::Error> { -7011 if let Some(added) = self.n0_nat_traversal.add_local_address(address)? { -7012 self.spaces[SpaceId::Data].pending.add_address.insert(added); -7013 }; -7014 Ok(()) -7015 } -7016 -7017 /// Removes an address the endpoing no longer considers reachable for nat traversal -7018 /// -7019 /// Addresses not present in the set will be silently ignored. -7020 pub fn remove_nat_traversal_address( -7021 &mut self, -7022 address: SocketAddr, -7023 ) -> Result<(), n0_nat_traversal::Error> { -7024 if let Some(removed) = self.n0_nat_traversal.remove_local_address(address)? { -7025 self.spaces[SpaceId::Data] -7026 .pending -7027 .remove_address -7028 .insert(removed); +6924 // All `false` fields are set in PacketSpace::can_send. +6925 SendableFrames { +6926 acks: false, +6927 close: false, +6928 space_specific, +6929 other, +6930 } +6931 } +6932 +6933 /// Terminate the connection instantly, without sending a close packet +6934 fn kill(&mut self, reason: ConnectionError) { +6935 self.close_common(); +6936 self.state.move_to_drained(Some(reason)); +6937 // move_to_drained checks that we were never in drained before, so we +6938 // never sent a `Drained` event before (it's illegal to send more events after drained). +6939 self.endpoint_events.push_back(EndpointEventInner::Drained); +6940 } +6941 +6942 /// Storage size required for the largest packet that can be transmitted on all currently +6943 /// available paths +6944 /// +6945 /// Buffers passed to [`Connection::poll_transmit`] should be at least this large. +6946 /// +6947 /// When multipath is enabled, this value is the minimum MTU across all available paths. +6948 pub fn current_mtu(&self) -> u16 { +6949 self.paths +6950 .iter() +6951 .filter(|&(path_id, _path_state)| !self.abandoned_paths.contains(path_id)) +6952 .map(|(_path_id, path_state)| path_state.data.current_mtu()) +6953 .min() +6954 .unwrap_or(INITIAL_MTU) +6955 } +6956 +6957 /// Size of non-frame data for a 1-RTT packet +6958 /// +6959 /// Quantifies space consumed by the QUIC header and AEAD tag. All other bytes in a packet are +6960 /// frames. Changes if the length of the remote connection ID changes, which is expected to be +6961 /// rare. If `pn` is specified, may additionally change unpredictably due to variations in +6962 /// latency and packet loss. +6963 fn predict_1rtt_overhead(&mut self, pn: u64, path: PathId) -> usize { +6964 let pn_len = PacketNumber::new( +6965 pn, +6966 self.spaces[SpaceId::Data] +6967 .for_path(path) +6968 .largest_acked_packet_pn +6969 .unwrap_or(0), +6970 ) +6971 .len(); +6972 +6973 // 1 byte for flags +6974 1 + self +6975 .remote_cids +6976 .get(&path) +6977 .map(|cids| cids.active().len()) +6978 .unwrap_or(20) // Max CID len in QUIC v1 +6979 + pn_len +6980 + self.tag_len_1rtt() +6981 } +6982 +6983 fn predict_1rtt_overhead_no_pn(&self) -> usize { +6984 let pn_len = 4; +6985 +6986 let cid_len = self +6987 .remote_cids +6988 .values() +6989 .map(|cids| cids.active().len()) +6990 .max() +6991 .unwrap_or(20); // Max CID len in QUIC v1 +6992 +6993 // 1 byte for flags +6994 1 + cid_len + pn_len + self.tag_len_1rtt() +6995 } +6996 +6997 fn tag_len_1rtt(&self) -> usize { +6998 // encryption_keys for Data space returns 1-RTT keys if available, otherwise 0-RTT keys +6999 let packet_crypto = self +7000 .crypto_state +7001 .encryption_keys(SpaceKind::Data, self.side.side()) +7002 .map(|(_header, packet, _level)| packet); +7003 // If neither Data nor 0-RTT keys are available, make a reasonable tag length guess. As of +7004 // this writing, all QUIC cipher suites use 16-byte tags. We could return `None` instead, +7005 // but that would needlessly prevent sending datagrams during 0-RTT. +7006 packet_crypto.map_or(16, |x| x.tag_len()) +7007 } +7008 +7009 /// Mark the path as validated, and enqueue NEW_TOKEN frames to be sent as appropriate +7010 fn on_path_validated(&mut self, path_id: PathId) { +7011 self.path_data_mut(path_id).validated = true; +7012 let ConnectionSide::Server { server_config } = &self.side else { +7013 return; +7014 }; +7015 let network_path = self.path_data(path_id).network_path; +7016 let new_tokens = &mut self.spaces[SpaceId::Data as usize].pending.new_tokens; +7017 new_tokens.clear(); +7018 for _ in 0..server_config.validation_token.sent { +7019 new_tokens.push(network_path); +7020 } +7021 } +7022 +7023 /// Handle new path status information: PATH_STATUS_AVAILABLE, PATH_STATUS_BACKUP +7024 fn on_path_status(&mut self, path_id: PathId, status: PathStatus, status_seq_no: VarInt) { +7025 if let Some(path) = self.paths.get_mut(&path_id) { +7026 path.data.status.remote_update(status, status_seq_no); +7027 } else { +7028 debug!("PATH_STATUS_AVAILABLE received unknown path {:?}", path_id); 7029 } -7030 Ok(()) -7031 } -7032 -7033 /// Get the current local nat traversal addresses -7034 pub fn get_local_nat_traversal_addresses( -7035 &self, -7036 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { -7037 self.n0_nat_traversal.get_local_nat_traversal_addresses() -7038 } -7039 -7040 /// Get the currently advertised nat traversal addresses by the server -7041 pub fn get_remote_nat_traversal_addresses( -7042 &self, -7043 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { -7044 Ok(self -7045 .n0_nat_traversal -7046 .client_side()? -7047 .get_remote_nat_traversal_addresses()) -7048 } -7049 -7050 /// Attempts to open a path for nat traversal. -7051 /// -7052 /// On success returns the [`PathId`] and remote address of the path. -7053 fn open_nat_traversal_path( -7054 &mut self, -7055 now: Instant, -7056 ip_port: (IpAddr, u16), -7057 ) -> Result<Option<(PathId, SocketAddr)>, PathError> { -7058 let remote = ip_port.into(); -7059 // TODO(matheus23): Probe the correct 4-tuple, instead of only a remote address? -7060 // By specifying None for `local_ip`, we do two things: 1. open_path_ensure won't -7061 // generate two paths to the same remote and 2. we let the OS choose which -7062 // interface to use for sending on that path. -7063 let network_path = FourTuple { -7064 remote, -7065 local_ip: None, -7066 }; -7067 match self.open_path_ensure(network_path, PathStatus::Backup, now) { -7068 Ok((path_id, path_was_known)) => { -7069 if path_was_known { -7070 trace!(%path_id, %remote, "nat traversal: path existed for remote, revalidating"); -7071 if let Some(path) = self.paths.get_mut(&path_id) { -7072 use paths::OpenStatus::*; -7073 -7074 path.data.pending_on_path_challenge = true; -7075 path.data.open_status = match path.data.open_status { -7076 // If we just opened the path and have never sent a `PATH_CHALLENGE` yet, -7077 // then we need to keep it at pending, to ensure that -7078 // 1. The PathOpenFailed timer for stopping the PathChallengeLost retries will be set. -7079 // 2. When validation eventually succeeds, then we inform the application layer about this path opening. -7080 Pending => Pending, -7081 // If we had already sent a path challenge in the past, but it hasn't been validated yet (and also not -7082 // failed via the PathOpenFailed timer yet), then we need to go back to pending, to ensure we properly -7083 // re-arm the `PathOpenFailed` timer again. -7084 Sent => Pending, -7085 // If we're already revalidating this path, but haven't sent a `PATH_CHALLENGE` yet, then we just keep -7086 // that state. -7087 Revalidating => Revalidating, -7088 // If we've informed the application layer about the path opening in the past, but we now re-send -7089 // PATH_CHALLENGEs for validation, then using this we ensure: -7090 // 1. The PathOpenFailed timer for stopping the PathChallengeLost retries will be set. -7091 // 2. When validation eventually succeeds, we *don't* inform the application layer about the path -7092 // opening again. -7093 Informed => Revalidating, -7094 } -7095 } -7096 } -7097 Ok(Some((path_id, remote))) -7098 } -7099 Err(e) => { -7100 debug!(%remote, %e, "nat traversal: failed to probe remote"); -7101 Err(e) -7102 } -7103 } -7104 } -7105 -7106 /// Initiates a new nat traversal round -7107 /// -7108 /// A nat traversal round involves advertising the client's local addresses in -7109 /// `REACH_OUT` frames, and initiating probing of the known remote addresses. When a new -7110 /// round is initiated, the previous one is cancelled. -7111 /// -7112 /// For all probes that succeed, if any, a new path will be opened on the successful -7113 /// 4-tuple. +7030 self.events.push_back( +7031 PathEvent::RemoteStatus { +7032 id: path_id, +7033 status, +7034 } +7035 .into(), +7036 ); +7037 } +7038 +7039 /// Returns the maximum [`PathId`] to be used for sending in this connection. +7040 /// +7041 /// This is calculated as minimum between the local and remote's maximums when multipath is +7042 /// enabled, or `None` when disabled. +7043 /// +7044 /// For data that's received, we should use [`Self::local_max_path_id`] instead. +7045 /// The reasoning is that the remote might already have updated to its own newer +7046 /// [`Self::max_path_id`] after sending out a `MAX_PATH_ID` frame, but it got re-ordered. +7047 fn max_path_id(&self) -> Option<PathId> { +7048 if self.is_multipath_negotiated() { +7049 Some(self.remote_max_path_id.min(self.local_max_path_id)) +7050 } else { +7051 None +7052 } +7053 } +7054 +7055 /// Returns whether this connection has a socket that supports IPv6. +7056 /// +7057 /// TODO(matheus23): This is related to noq endpoint state's `ipv6` bool. We should move that info +7058 /// here instead of trying to hack around not knowing it exactly. +7059 fn is_ipv6(&self) -> bool { +7060 self.paths +7061 .values() +7062 .any(|p| p.data.network_path.remote.is_ipv6()) +7063 } +7064 +7065 /// Add addresses the local endpoint considers are reachable for nat traversal. +7066 pub fn add_nat_traversal_address( +7067 &mut self, +7068 address: SocketAddr, +7069 ) -> Result<(), n0_nat_traversal::Error> { +7070 if let Some(added) = self.n0_nat_traversal.add_local_address(address)? { +7071 self.spaces[SpaceId::Data].pending.add_address.insert(added); +7072 }; +7073 Ok(()) +7074 } +7075 +7076 /// Removes an address the endpoing no longer considers reachable for nat traversal +7077 /// +7078 /// Addresses not present in the set will be silently ignored. +7079 pub fn remove_nat_traversal_address( +7080 &mut self, +7081 address: SocketAddr, +7082 ) -> Result<(), n0_nat_traversal::Error> { +7083 if let Some(removed) = self.n0_nat_traversal.remove_local_address(address)? { +7084 self.spaces[SpaceId::Data] +7085 .pending +7086 .remove_address +7087 .insert(removed); +7088 } +7089 Ok(()) +7090 } +7091 +7092 /// Get the current local nat traversal addresses +7093 pub fn get_local_nat_traversal_addresses( +7094 &self, +7095 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { +7096 self.n0_nat_traversal.get_local_nat_traversal_addresses() +7097 } +7098 +7099 /// Get the currently advertised nat traversal addresses by the server +7100 pub fn get_remote_nat_traversal_addresses( +7101 &self, +7102 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { +7103 Ok(self +7104 .n0_nat_traversal +7105 .client_side()? +7106 .get_remote_nat_traversal_addresses()) +7107 } +7108 +7109 /// Initiates a new nat traversal round +7110 /// +7111 /// A nat traversal round involves advertising the client's local addresses in +7112 /// `REACH_OUT` frames, and initiating probing of the known remote addresses. When a new +7113 /// round is initiated, the previous one is cancelled. 7114 /// -7115 /// Returns the server addresses that are now being probed. If addresses fail due to -7116 /// spurious errors, these might succeed later and not be returned in this set. -7117 pub fn initiate_nat_traversal_round( -7118 &mut self, -7119 now: Instant, -7120 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { -7121 if self.state.is_closed() { -7122 return Err(n0_nat_traversal::Error::Closed); -7123 } -7124 -7125 let ipv6 = self.is_ipv6(); -7126 let client_state = self.n0_nat_traversal.client_side_mut()?; -7127 let n0_nat_traversal::NatTraversalRound { -7128 new_round, -7129 reach_out_at, -7130 addresses_to_probe, -7131 prev_round_path_ids, -7132 } = client_state.initiate_nat_traversal_round(ipv6)?; -7133 -7134 trace!(%new_round, reach_out=reach_out_at.len(), to_probe=addresses_to_probe.len(), -7135 "initiating nat traversal round"); -7136 -7137 self.spaces[SpaceId::Data].pending.reach_out = Some((new_round, reach_out_at)); -7138 -7139 for path_id in prev_round_path_ids { -7140 let Some(path) = self.path(path_id) else { -7141 continue; -7142 }; -7143 let ip = path.network_path.remote.ip(); -7144 let port = path.network_path.remote.port(); +7115 /// For all probes that succeed, if any, a new path will be opened on the successful +7116 /// 4-tuple. +7117 /// +7118 /// Returns the server addresses that are now being probed. If addresses fail due to +7119 /// spurious errors, these might succeed later and not be returned in this set. +7120 pub fn initiate_nat_traversal_round( +7121 &mut self, +7122 now: Instant, +7123 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { +7124 if self.state.is_closed() { +7125 return Err(n0_nat_traversal::Error::Closed); +7126 } +7127 +7128 let ipv6 = self.is_ipv6(); +7129 let client_state = self.n0_nat_traversal.client_side_mut()?; +7130 let (mut reach_out_frames, probed_addrs) = +7131 client_state.initiate_nat_traversal_round(ipv6)?; +7132 if !probed_addrs.is_empty() { +7133 let delay = RttEstimator::new(self.config.initial_rtt).pto_base() * 2 / 3; +7134 self.timers.set( +7135 Timer::Conn(ConnTimer::NatTraversalProbeRetry), +7136 now + delay, +7137 self.qlog.with_time(now), +7138 ); +7139 } +7140 +7141 self.spaces[SpaceId::Data] +7142 .pending +7143 .reach_out +7144 .append(&mut reach_out_frames); 7145 -7146 // We only close paths that aren't validated (thus are working) that we opened -7147 // in a previous round. -7148 // And we only close paths that we don't want to probe anyways. -7149 if !addresses_to_probe -7150 .iter() -7151 .any(|(_, probe)| *probe == (ip, port)) -7152 && !path.validated -7153 && !self.abandoned_paths.contains(&path_id) -7154 { -7155 trace!(%path_id, "closing path from previous round"); -7156 let _ = -7157 self.close_path_inner(now, path_id, PathAbandonReason::NatTraversalRoundEnded); -7158 } -7159 } -7160 -7161 let mut err = None; -7162 -7163 let mut path_ids = Vec::with_capacity(addresses_to_probe.len()); -7164 let mut probed_addresses = Vec::with_capacity(addresses_to_probe.len()); -7165 -7166 for (id, address) in addresses_to_probe { -7167 match self.open_nat_traversal_path(now, address) { -7168 Ok(None) => {} -7169 Ok(Some((path_id, remote))) => { -7170 path_ids.push(path_id); -7171 probed_addresses.push(remote); -7172 } -7173 Err(e) => { -7174 self.n0_nat_traversal -7175 .client_side_mut() -7176 .expect("validated") -7177 .report_in_continuation(id, e); -7178 err.get_or_insert(e); -7179 } -7180 } -7181 } +7146 Ok(probed_addrs) +7147 } +7148 +7149 /// Whether the handshake is considered **confirmed**. +7150 /// +7151 /// <https://www.rfc-editor.org/rfc/rfc9001#section-4.1.2> defines a handshake to be +7152 /// confirmed when you know the peer successfully received and successfully processed +7153 /// your TLS Finished message. +7154 /// +7155 /// Implementation-wise this is the point at which the handshake crypto keys are +7156 /// discarded. So we can use this to know if the handshake is confirmed. +7157 fn is_handshake_confirmed(&self) -> bool { +7158 !self.is_handshaking() && !self.crypto_state.has_keys(EncryptionLevel::Handshake) +7159 } +7160} +7161 +7162impl fmt::Debug for Connection { +7163 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +7164 f.debug_struct("Connection") +7165 .field("handshake_cid", &self.handshake_cid) +7166 .finish() +7167 } +7168} +7169 +7170/// Hints when the caller identifies a network change. +7171pub trait NetworkChangeHint: std::fmt::Debug + 'static { +7172 /// Inform the connection if a path may recover after a network change. +7173 /// +7174 /// After network changes, paths may not be recoverable. In this case, waiting for the path to +7175 /// become idle may take longer than what is desirable. If [`Self::is_path_recoverable`] +7176 /// returns `false`, a multipath-enabled, client-side connection will establish a new path to +7177 /// the same remote, closing the current one, instead of migrating the path. +7178 /// +7179 /// Paths that are deemed recoverable will simply be sent a PING for a liveness check. +7180 fn is_path_recoverable(&self, path_id: PathId, network_path: FourTuple) -> bool; +7181} 7182 -7183 if let Some(err) = err { -7184 // We failed to probe any addresses, bail out -7185 if probed_addresses.is_empty() { -7186 return Err(n0_nat_traversal::Error::Multipath(err)); -7187 } -7188 } -7189 -7190 self.n0_nat_traversal -7191 .client_side_mut() -7192 .expect("connection side validated") -7193 .set_round_path_ids(path_ids); -7194 -7195 Ok(probed_addresses) -7196 } -7197 -7198 /// Attempts to continue a nat traversal round by trying to open paths for pending client probes. -7199 /// -7200 /// If there was nothing to do, it returns `None`. Otherwise it returns whether the path was -7201 /// successfully open. -7202 fn continue_nat_traversal_round(&mut self, now: Instant) -> Option<bool> { -7203 let ipv6 = self.is_ipv6(); -7204 let client_state = self.n0_nat_traversal.client_side_mut().ok()?; -7205 let (id, address) = client_state.continue_nat_traversal_round(ipv6)?; -7206 let open_result = self.open_nat_traversal_path(now, address); -7207 let client_state = self.n0_nat_traversal.client_side_mut().expect("validated"); -7208 match open_result { -7209 Ok(None) => Some(true), -7210 Ok(Some((path_id, _remote))) => { -7211 client_state.add_round_path_id(path_id); -7212 Some(true) -7213 } -7214 Err(e) => { -7215 client_state.report_in_continuation(id, e); -7216 Some(false) -7217 } -7218 } -7219 } -7220 -7221 /// Whether the handshake is considered **confirmed**. -7222 /// -7223 /// <https://www.rfc-editor.org/rfc/rfc9001#section-4.1.2> defines a handshake to be -7224 /// confirmed when you know the peer successfully received and successfully processed -7225 /// your TLS Finished message. -7226 /// -7227 /// Implementation-wise this is the point at which the handshake crypto keys are -7228 /// discarded. So we can use this to know if the handshake is confirmed. -7229 fn is_handshake_confirmed(&self) -> bool { -7230 !self.is_handshaking() && !self.crypto_state.has_keys(EncryptionLevel::Handshake) -7231 } -7232} -7233 -7234impl fmt::Debug for Connection { -7235 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -7236 f.debug_struct("Connection") -7237 .field("handshake_cid", &self.handshake_cid) -7238 .finish() -7239 } -7240} -7241 -7242/// Hints when the caller identifies a network change. -7243pub trait NetworkChangeHint: std::fmt::Debug + 'static { -7244 /// Inform the connection if a path may recover after a network change. -7245 /// -7246 /// After network changes, paths may not be recoverable. In this case, waiting for the path to -7247 /// become idle may take longer than what is desirable. If [`Self::is_path_recoverable`] -7248 /// returns `false`, a multipath-enabled, client-side connection will establish a new path to -7249 /// the same remote, closing the current one, instead of migrating the path. +7183/// Return value for [`Connection::poll_transmit_path_space`]. +7184#[derive(Debug)] +7185enum PollPathSpaceStatus { +7186 /// Nothing to send in the space, nothing was written into the [`TransmitBuf`]. +7187 NothingToSend { +7188 /// If true there was data to send but congestion control did not allow so. +7189 congestion_blocked: bool, +7190 }, +7191 /// One or more packets have been written into the [`TransmitBuf`]. +7192 WrotePacket { +7193 /// The highest packet number. +7194 last_packet_number: u64, +7195 /// Whether to pad an already started datagram in the next packet. +7196 /// +7197 /// When packets in Initial, 0-RTT or Handshake packet do not fill the entire +7198 /// datagram they may decide to coalesce with the next packet from a higher +7199 /// encryption level on the same path. But the earlier packet may require specific +7200 /// size requirements for the datagram they are sent in. +7201 /// +7202 /// If a space did not complete the datagram, they use this to request the correct +7203 /// padding in the final packet of the datagram so that the final datagram will have +7204 /// the correct size. +7205 /// +7206 /// If a space did fill an entire datagram, it leaves this to the default of +7207 /// [`PadDatagram::No`]. +7208 pad_datagram: PadDatagram, +7209 }, +7210 /// Send the contents of the transmit immediately. +7211 /// +7212 /// Packets were written and the GSO batch must end now, regardless from whether higher +7213 /// spaces still have frames to write. This is used when the last datagram written would +7214 /// require too much padding to continue a GSO batch, which would waste space on the +7215 /// wire. +7216 Send { +7217 /// The highest packet number written into the transmit. +7218 last_packet_number: u64, +7219 }, +7220} +7221 +7222/// Information used to decide what frames to schedule into which packets. +7223/// +7224/// Primarily used by [`Connection::poll_transmit_on_path`] and the functions that help +7225/// building packets for it: [`Connection::poll_transmit_path_space`] and +7226/// [`Connection::populate_packet`]. +7227#[derive(Debug, Copy, Clone)] +7228struct PathSchedulingInfo { +7229 /// Whether the path is abandoned. +7230 /// +7231 /// Note that a path that is abandoned but still has CIDs can still send a packet. After +7232 /// sending that packet the CIDs issued by the remote have to be considered retired as +7233 /// well. +7234 is_abandoned: bool, +7235 /// Whether the path may send [`SpaceKind::Data`] frames. +7236 /// +7237 /// Some paths should only send frames from [`SendableFrames::space_specific`]. All other +7238 /// frames are essentially frames that can be sent on any [`SpaceKind::Data`] space. For +7239 /// those we want to respect packet scheduling rules however. +7240 /// +7241 /// Roughly speaking data frames are only sent on spaces that have CIDs, are not +7242 /// abandoned and have no *better* spaces. However see to comments where this is +7243 /// populated for the exact packet scheduling implementation. +7244 /// +7245 /// This essentially marks this paths as the best validated space ID. Except during +7246 /// the handshake in which case it does not need to be validated. Several paths could be +7247 /// equally good and all have this set to `true`, in that case packet scheduling can +7248 /// choose which path to use. Currently it chooses the lowest path that is not +7249 /// congestion blocked. 7250 /// -7251 /// Paths that are deemed recoverable will simply be sent a PING for a liveness check. -7252 fn is_path_recoverable(&self, path_id: PathId, network_path: FourTuple) -> bool; -7253} -7254 -7255/// Return value for [`Connection::poll_transmit_path_space`]. -7256#[derive(Debug)] -7257enum PollPathSpaceStatus { -7258 /// Nothing to send in the space, nothing was written into the [`TransmitBuf`]. -7259 NothingToSend { -7260 /// If true there was data to send but congestion control did not allow so. -7261 congestion_blocked: bool, -7262 }, -7263 /// One or more packets have been written into the [`TransmitBuf`]. -7264 WrotePacket { -7265 /// The highest packet number. -7266 last_packet_number: u64, -7267 /// Whether to pad an already started datagram in the next packet. -7268 /// -7269 /// When packets in Initial, 0-RTT or Handshake packet do not fill the entire -7270 /// datagram they may decide to coalesce with the next packet from a higher -7271 /// encryption level on the same path. But the earlier packet may require specific -7272 /// size requirements for the datagram they are sent in. -7273 /// -7274 /// If a space did not complete the datagram, they use this to request the correct -7275 /// padding in the final packet of the datagram so that the final datagram will have -7276 /// the correct size. -7277 /// -7278 /// If a space did fill an entire datagram, it leaves this to the default of -7279 /// [`PadDatagram::No`]. -7280 pad_datagram: PadDatagram, -7281 }, -7282 /// Send the contents of the transmit immediately. -7283 /// -7284 /// Packets were written and the GSO batch must end now, regardless from whether higher -7285 /// spaces still have frames to write. This is used when the last datagram written would -7286 /// require too much padding to continue a GSO batch, which would waste space on the -7287 /// wire. -7288 Send { -7289 /// The highest packet number written into the transmit. -7290 last_packet_number: u64, -7291 }, -7292} -7293 -7294/// Information used to decide what frames to schedule into which packets. -7295/// -7296/// Primarily used by [`Connection::poll_transmit_on_path`] and the functions that help -7297/// building packets for it: [`Connection::poll_transmit_path_space`] and -7298/// [`Connection::populate_packet`]. -7299#[derive(Debug, Copy, Clone)] -7300struct PathSchedulingInfo { -7301 /// Whether the path is abandoned. -7302 /// -7303 /// Note that a path that is abandoned but still has CIDs can still send a packet. After -7304 /// sending that packet the CIDs issued by the remote have to be considered retired as -7305 /// well. -7306 is_abandoned: bool, -7307 /// Whether the path may send [`SpaceKind::Data`] frames. -7308 /// -7309 /// Some paths should only send frames from [`SendableFrames::space_specific`]. All other -7310 /// frames are essentially frames that can be sent on any [`SpaceKind::Data`] space. For -7311 /// those we want to respect packet scheduling rules however. -7312 /// -7313 /// Roughly speaking data frames are only sent on spaces that have CIDs, are not -7314 /// abandoned and have no *better* spaces. However see to comments where this is -7315 /// populated for the exact packet scheduling implementation. -7316 /// -7317 /// This essentially marks this paths as the best validated space ID. Except during -7318 /// the handshake in which case it does not need to be validated. Several paths could be -7319 /// equally good and all have this set to `true`, in that case packet scheduling can -7320 /// choose which path to use. Currently it chooses the lowest path that is not -7321 /// congestion blocked. -7322 /// -7323 /// Note that once in the closed or draining states this will never be true. -7324 may_send_data: bool, -7325 /// Whether the path may send a CONNECTION_CLOSE frame. -7326 /// -7327 /// This essentially marks this path as the best validated space ID with a fallback -7328 /// to unvalidated spaces if there are no validated spaces. Like for -7329 /// [`Self::may_send_data`] other paths could be equally good. -7330 may_send_close: bool, -7331 may_self_abandon: bool, -7332} -7333 -7334#[derive(Debug, Copy, Clone, PartialEq, Eq)] -7335enum PathBlocked { -7336 No, -7337 AntiAmplification, -7338 Congestion, -7339 Pacing, -7340} -7341 -7342/// Fields of `Connection` specific to it being client-side or server-side -7343enum ConnectionSide { -7344 Client { -7345 /// Sent in every outgoing Initial packet. Always empty after Initial keys are discarded -7346 token: Bytes, -7347 token_store: Arc<dyn TokenStore>, -7348 server_name: String, -7349 }, -7350 Server { -7351 server_config: Arc<ServerConfig>, -7352 }, -7353} -7354 -7355impl ConnectionSide { -7356 fn is_client(&self) -> bool { -7357 self.side().is_client() -7358 } -7359 -7360 fn is_server(&self) -> bool { -7361 self.side().is_server() -7362 } -7363 -7364 fn side(&self) -> Side { -7365 match *self { -7366 Self::Client { .. } => Side::Client, -7367 Self::Server { .. } => Side::Server, -7368 } -7369 } -7370} -7371 -7372impl From<SideArgs> for ConnectionSide { -7373 fn from(side: SideArgs) -> Self { -7374 match side { -7375 SideArgs::Client { -7376 token_store, -7377 server_name, -7378 } => Self::Client { -7379 token: token_store.take(&server_name).unwrap_or_default(), -7380 token_store, -7381 server_name, -7382 }, -7383 SideArgs::Server { -7384 server_config, -7385 pref_addr_cid: _, -7386 path_validated: _, -7387 } => Self::Server { server_config }, -7388 } -7389 } -7390} -7391 -7392/// Parameters to `Connection::new` specific to it being client-side or server-side -7393pub(crate) enum SideArgs { -7394 Client { -7395 token_store: Arc<dyn TokenStore>, -7396 server_name: String, -7397 }, -7398 Server { -7399 server_config: Arc<ServerConfig>, -7400 pref_addr_cid: Option<ConnectionId>, -7401 path_validated: bool, -7402 }, -7403} -7404 -7405impl SideArgs { -7406 pub(crate) fn pref_addr_cid(&self) -> Option<ConnectionId> { -7407 match *self { -7408 Self::Client { .. } => None, -7409 Self::Server { pref_addr_cid, .. } => pref_addr_cid, -7410 } -7411 } -7412 -7413 pub(crate) fn path_validated(&self) -> bool { -7414 match *self { -7415 Self::Client { .. } => true, -7416 Self::Server { path_validated, .. } => path_validated, -7417 } -7418 } -7419 -7420 pub(crate) fn side(&self) -> Side { -7421 match *self { -7422 Self::Client { .. } => Side::Client, -7423 Self::Server { .. } => Side::Server, -7424 } -7425 } -7426} -7427 -7428/// Reasons why a connection might be lost -7429#[derive(Debug, Error, Clone, PartialEq, Eq)] -7430pub enum ConnectionError { -7431 /// The peer doesn't implement any supported version -7432 #[error("peer doesn't implement any supported version")] -7433 VersionMismatch, -7434 /// The peer violated the QUIC specification as understood by this implementation -7435 #[error(transparent)] -7436 TransportError(#[from] TransportError), -7437 /// The peer's QUIC stack aborted the connection automatically -7438 #[error("aborted by peer: {0}")] -7439 ConnectionClosed(frame::ConnectionClose), -7440 /// The peer closed the connection -7441 #[error("closed by peer: {0}")] -7442 ApplicationClosed(frame::ApplicationClose), -7443 /// The peer is unable to continue processing this connection, usually due to having restarted -7444 #[error("reset by peer")] -7445 Reset, -7446 /// Communication with the peer has lapsed for longer than the negotiated idle timeout -7447 /// -7448 /// If neither side is sending keep-alives, a connection will time out after a long enough idle -7449 /// period even if the peer is still reachable. See also [`TransportConfig::max_idle_timeout()`] -7450 /// and [`TransportConfig::keep_alive_interval()`]. -7451 #[error("timed out")] -7452 TimedOut, -7453 /// The local application closed the connection -7454 #[error("closed")] -7455 LocallyClosed, -7456 /// The connection could not be created because not enough of the CID space is available -7457 /// -7458 /// Try using longer connection IDs. -7459 #[error("CIDs exhausted")] -7460 CidsExhausted, +7251 /// Note that once in the closed or draining states this will never be true. +7252 may_send_data: bool, +7253 /// Whether the path may send a CONNECTION_CLOSE frame. +7254 /// +7255 /// This essentially marks this path as the best validated space ID with a fallback +7256 /// to unvalidated spaces if there are no validated spaces. Like for +7257 /// [`Self::may_send_data`] other paths could be equally good. +7258 may_send_close: bool, +7259 may_self_abandon: bool, +7260} +7261 +7262#[derive(Debug, Copy, Clone, PartialEq, Eq)] +7263enum PathBlocked { +7264 No, +7265 AntiAmplification, +7266 Congestion, +7267 Pacing, +7268} +7269 +7270/// Fields of `Connection` specific to it being client-side or server-side +7271enum ConnectionSide { +7272 Client { +7273 /// Sent in every outgoing Initial packet. Always empty after Initial keys are discarded +7274 token: Bytes, +7275 token_store: Arc<dyn TokenStore>, +7276 server_name: String, +7277 }, +7278 Server { +7279 server_config: Arc<ServerConfig>, +7280 }, +7281} +7282 +7283impl ConnectionSide { +7284 fn is_client(&self) -> bool { +7285 self.side().is_client() +7286 } +7287 +7288 fn is_server(&self) -> bool { +7289 self.side().is_server() +7290 } +7291 +7292 fn side(&self) -> Side { +7293 match *self { +7294 Self::Client { .. } => Side::Client, +7295 Self::Server { .. } => Side::Server, +7296 } +7297 } +7298} +7299 +7300impl From<SideArgs> for ConnectionSide { +7301 fn from(side: SideArgs) -> Self { +7302 match side { +7303 SideArgs::Client { +7304 token_store, +7305 server_name, +7306 } => Self::Client { +7307 token: token_store.take(&server_name).unwrap_or_default(), +7308 token_store, +7309 server_name, +7310 }, +7311 SideArgs::Server { +7312 server_config, +7313 pref_addr_cid: _, +7314 path_validated: _, +7315 } => Self::Server { server_config }, +7316 } +7317 } +7318} +7319 +7320/// Parameters to `Connection::new` specific to it being client-side or server-side +7321pub(crate) enum SideArgs { +7322 Client { +7323 token_store: Arc<dyn TokenStore>, +7324 server_name: String, +7325 }, +7326 Server { +7327 server_config: Arc<ServerConfig>, +7328 pref_addr_cid: Option<ConnectionId>, +7329 path_validated: bool, +7330 }, +7331} +7332 +7333impl SideArgs { +7334 pub(crate) fn pref_addr_cid(&self) -> Option<ConnectionId> { +7335 match *self { +7336 Self::Client { .. } => None, +7337 Self::Server { pref_addr_cid, .. } => pref_addr_cid, +7338 } +7339 } +7340 +7341 pub(crate) fn path_validated(&self) -> bool { +7342 match *self { +7343 Self::Client { .. } => true, +7344 Self::Server { path_validated, .. } => path_validated, +7345 } +7346 } +7347 +7348 pub(crate) fn side(&self) -> Side { +7349 match *self { +7350 Self::Client { .. } => Side::Client, +7351 Self::Server { .. } => Side::Server, +7352 } +7353 } +7354} +7355 +7356/// Reasons why a connection might be lost +7357#[derive(Debug, Error, Clone, PartialEq, Eq)] +7358pub enum ConnectionError { +7359 /// The peer doesn't implement any supported version +7360 #[error("peer doesn't implement any supported version")] +7361 VersionMismatch, +7362 /// The peer violated the QUIC specification as understood by this implementation +7363 #[error(transparent)] +7364 TransportError(#[from] TransportError), +7365 /// The peer's QUIC stack aborted the connection automatically +7366 #[error("aborted by peer: {0}")] +7367 ConnectionClosed(frame::ConnectionClose), +7368 /// The peer closed the connection +7369 #[error("closed by peer: {0}")] +7370 ApplicationClosed(frame::ApplicationClose), +7371 /// The peer is unable to continue processing this connection, usually due to having restarted +7372 #[error("reset by peer")] +7373 Reset, +7374 /// Communication with the peer has lapsed for longer than the negotiated idle timeout +7375 /// +7376 /// If neither side is sending keep-alives, a connection will time out after a long enough idle +7377 /// period even if the peer is still reachable. See also [`TransportConfig::max_idle_timeout()`] +7378 /// and [`TransportConfig::keep_alive_interval()`]. +7379 #[error("timed out")] +7380 TimedOut, +7381 /// The local application closed the connection +7382 #[error("closed")] +7383 LocallyClosed, +7384 /// The connection could not be created because not enough of the CID space is available +7385 /// +7386 /// Try using longer connection IDs. +7387 #[error("CIDs exhausted")] +7388 CidsExhausted, +7389} +7390 +7391impl From<Close> for ConnectionError { +7392 fn from(x: Close) -> Self { +7393 match x { +7394 Close::Connection(reason) => Self::ConnectionClosed(reason), +7395 Close::Application(reason) => Self::ApplicationClosed(reason), +7396 } +7397 } +7398} +7399 +7400// For compatibility with API consumers +7401impl From<ConnectionError> for io::Error { +7402 fn from(x: ConnectionError) -> Self { +7403 use ConnectionError::*; +7404 let kind = match x { +7405 TimedOut => io::ErrorKind::TimedOut, +7406 Reset => io::ErrorKind::ConnectionReset, +7407 ApplicationClosed(_) | ConnectionClosed(_) => io::ErrorKind::ConnectionAborted, +7408 TransportError(_) | VersionMismatch | LocallyClosed | CidsExhausted => { +7409 io::ErrorKind::Other +7410 } +7411 }; +7412 Self::new(kind, x) +7413 } +7414} +7415 +7416/// Errors that might trigger a path being closed +7417// TODO(@divma): maybe needs to be reworked based on what we want to do with the public API +7418#[derive(Debug, Error, PartialEq, Eq, Clone, Copy)] +7419pub enum PathError { +7420 /// The extension was not negotiated with the peer +7421 #[error("multipath extension not negotiated")] +7422 MultipathNotNegotiated, +7423 /// Paths can only be opened client-side +7424 #[error("the server side may not open a path")] +7425 ServerSideNotAllowed, +7426 /// Current limits do not allow us to open more paths +7427 #[error("maximum number of concurrent paths reached")] +7428 MaxPathIdReached, +7429 /// No remote CIDs available to open a new path +7430 #[error("remoted CIDs exhausted")] +7431 RemoteCidsExhausted, +7432 /// Path could not be validated and will be abandoned +7433 #[error("path validation failed")] +7434 ValidationFailed, +7435 /// The remote address for the path is not supported by the endpoint +7436 #[error("invalid remote address")] +7437 InvalidRemoteAddress(SocketAddr), +7438} +7439 +7440/// Errors triggered when abandoning a path +7441#[derive(Debug, Error, Clone, Eq, PartialEq)] +7442pub enum ClosePathError { +7443 /// Multipath is not negotiated +7444 #[error("Multipath extension not negotiated")] +7445 MultipathNotNegotiated, +7446 /// The path is already closed or was never opened +7447 #[error("closed path")] +7448 ClosedPath, +7449 /// Cannot close the last remaining open path via the local API. +7450 /// +7451 /// Use [`Connection::close`] to end the connection instead. +7452 #[error("last open path")] +7453 LastOpenPath, +7454} +7455 +7456/// Error when the multipath extension was not negotiated, but attempted to be used. +7457#[derive(Debug, Error, Clone, Copy)] +7458#[error("Multipath extension not negotiated")] +7459pub struct MultipathNotNegotiated { +7460 _private: (), 7461} 7462 -7463impl From<Close> for ConnectionError { -7464 fn from(x: Close) -> Self { -7465 match x { -7466 Close::Connection(reason) => Self::ConnectionClosed(reason), -7467 Close::Application(reason) => Self::ApplicationClosed(reason), -7468 } -7469 } -7470} -7471 -7472// For compatibility with API consumers -7473impl From<ConnectionError> for io::Error { -7474 fn from(x: ConnectionError) -> Self { -7475 use ConnectionError::*; -7476 let kind = match x { -7477 TimedOut => io::ErrorKind::TimedOut, -7478 Reset => io::ErrorKind::ConnectionReset, -7479 ApplicationClosed(_) | ConnectionClosed(_) => io::ErrorKind::ConnectionAborted, -7480 TransportError(_) | VersionMismatch | LocallyClosed | CidsExhausted => { -7481 io::ErrorKind::Other -7482 } -7483 }; -7484 Self::new(kind, x) -7485 } -7486} -7487 -7488/// Errors that might trigger a path being closed -7489// TODO(@divma): maybe needs to be reworked based on what we want to do with the public API -7490#[derive(Debug, Error, PartialEq, Eq, Clone, Copy)] -7491pub enum PathError { -7492 /// The extension was not negotiated with the peer -7493 #[error("multipath extension not negotiated")] -7494 MultipathNotNegotiated, -7495 /// Paths can only be opened client-side -7496 #[error("the server side may not open a path")] -7497 ServerSideNotAllowed, -7498 /// Current limits do not allow us to open more paths -7499 #[error("maximum number of concurrent paths reached")] -7500 MaxPathIdReached, -7501 /// No remote CIDs available to open a new path -7502 #[error("remoted CIDs exhausted")] -7503 RemoteCidsExhausted, -7504 /// Path could not be validated and will be abandoned -7505 #[error("path validation failed")] -7506 ValidationFailed, -7507 /// The remote address for the path is not supported by the endpoint -7508 #[error("invalid remote address")] -7509 InvalidRemoteAddress(SocketAddr), -7510} +7463/// Events of interest to the application +7464#[derive(Debug)] +7465pub enum Event { +7466 /// The connection's handshake data is ready +7467 HandshakeDataReady, +7468 /// The connection was successfully established +7469 Connected, +7470 /// The TLS handshake was confirmed +7471 HandshakeConfirmed, +7472 /// The connection was lost +7473 /// +7474 /// Emitted when the connection is closed due to an error, a timeout, or the peer closing it. +7475 /// This is **not** emitted when the local application closes the connection via +7476 /// [`Connection::close()`](crate::Connection::close). In that case, pending operations will +7477 /// fail with [`ConnectionError::LocallyClosed`]. +7478 ConnectionLost { +7479 /// Reason that the connection was closed +7480 reason: ConnectionError, +7481 }, +7482 /// Stream events +7483 Stream(StreamEvent), +7484 /// One or more application datagrams have been received +7485 DatagramReceived, +7486 /// One or more application datagrams have been sent after blocking +7487 DatagramsUnblocked, +7488 /// (Multi)Path events +7489 Path(PathEvent), +7490 /// n0's nat traversal events +7491 NatTraversal(n0_nat_traversal::Event), +7492} +7493 +7494impl From<PathEvent> for Event { +7495 fn from(source: PathEvent) -> Self { +7496 Self::Path(source) +7497 } +7498} +7499 +7500fn get_max_ack_delay(params: &TransportParameters) -> Duration { +7501 Duration::from_micros(params.max_ack_delay.0 * 1000) +7502} +7503 +7504/// Prevents overflow and improves behavior in extreme circumstances. +7505const MAX_BACKOFF_EXPONENT: u32 = 16; +7506 +7507/// The max interval between successive tail-loss probes. +7508/// +7509/// This is the "normal" value we use. +7510const MAX_PTO_INTERVAL: Duration = Duration::from_secs(2); 7511 -7512/// Errors triggered when abandoning a path -7513#[derive(Debug, Error, Clone, Eq, PartialEq)] -7514pub enum ClosePathError { -7515 /// Multipath is not negotiated -7516 #[error("Multipath extension not negotiated")] -7517 MultipathNotNegotiated, -7518 /// The path is already closed or was never opened -7519 #[error("closed path")] -7520 ClosedPath, -7521 /// Cannot close the last remaining open path via the local API. -7522 /// -7523 /// Use [`Connection::close`] to end the connection instead. -7524 #[error("last open path")] -7525 LastOpenPath, -7526} +7512/// The idle time, below which we use the shorter [`MAX_PTO_FAST_INTERVAL`]. +7513const MIN_IDLE_FOR_FAST_PTO: Duration = Duration::from_secs(25); +7514 +7515/// The max interval between successive tail-loss probes with short idle times. +7516/// +7517/// If the path or connection idle time is less than [`MIN_IDLE_FOR_FAST_PTO`] then we use +7518/// this value to ensure we have plenty of retransmits before we reach the idle time. +7519const MAX_PTO_FAST_INTERVAL: Duration = Duration::from_secs(1); +7520 +7521/// The RTT threshold above which we cap the PTO interval to 1.5 * smoothed_rtt +7522/// +7523/// This is RTT time above which 1.5 * RTT > [`MAX_PTO_INTERVAL`], for these links we want +7524/// to extend the interval between tail-loss probes to not fill the entire pipe with them. +7525const SLOW_RTT_THRESHOLD: Duration = +7526 Duration::from_millis((MAX_PTO_INTERVAL.as_millis() as u64 * 2) / 3); 7527 -7528/// Error when the multipath extension was not negotiated, but attempted to be used. -7529#[derive(Debug, Error, Clone, Copy)] -7530#[error("Multipath extension not negotiated")] -7531pub struct MultipathNotNegotiated { -7532 _private: (), -7533} -7534 -7535/// Events of interest to the application -7536#[derive(Debug)] -7537pub enum Event { -7538 /// The connection's handshake data is ready -7539 HandshakeDataReady, -7540 /// The connection was successfully established -7541 Connected, -7542 /// The TLS handshake was confirmed -7543 HandshakeConfirmed, -7544 /// The connection was lost -7545 /// -7546 /// Emitted when the connection is closed due to an error, a timeout, or the peer closing it. -7547 /// This is **not** emitted when the local application closes the connection via -7548 /// [`Connection::close()`](crate::Connection::close). In that case, pending operations will -7549 /// fail with [`ConnectionError::LocallyClosed`]. -7550 ConnectionLost { -7551 /// Reason that the connection was closed -7552 reason: ConnectionError, -7553 }, -7554 /// Stream events -7555 Stream(StreamEvent), -7556 /// One or more application datagrams have been received -7557 DatagramReceived, -7558 /// One or more application datagrams have been sent after blocking -7559 DatagramsUnblocked, -7560 /// (Multi)Path events -7561 Path(PathEvent), -7562 /// n0's nat traversal events -7563 NatTraversal(n0_nat_traversal::Event), -7564} +7528/// Minimal remaining size to allow packet coalescing, excluding cryptographic tag +7529/// +7530/// This must be at least as large as the header for a well-formed empty packet to be coalesced, +7531/// plus some space for frames. We only care about handshake headers because short header packets +7532/// necessarily have smaller headers, and initial packets are only ever the first packet in a +7533/// datagram (because we coalesce in ascending packet space order and the only reason to split a +7534/// packet is when packet space changes). +7535const MIN_PACKET_SPACE: usize = MAX_HANDSHAKE_OR_0RTT_HEADER_SIZE + 32; +7536 +7537/// Largest amount of space that could be occupied by a Handshake or 0-RTT packet's header +7538/// +7539/// Excludes packet-type-specific fields such as packet number or Initial token +7540// https://www.rfc-editor.org/rfc/rfc9000.html#name-0-rtt: flags + version + dcid len + dcid + +7541// scid len + scid + length + pn +7542const MAX_HANDSHAKE_OR_0RTT_HEADER_SIZE: usize = +7543 1 + 4 + 1 + MAX_CID_SIZE + 1 + MAX_CID_SIZE + VarInt::from_u32(u16::MAX as u32).size() + 4; +7544 +7545#[derive(Default)] +7546struct SentFrames { +7547 retransmits: ThinRetransmits, +7548 /// The packet number of the largest acknowledged packet for each path +7549 largest_acked: FxHashMap<PathId, u64>, +7550 stream_frames: StreamMetaVec, +7551 /// Whether the packet contains non-retransmittable frames (like datagrams) +7552 non_retransmits: bool, +7553 /// If the datagram containing these frames should be padded to the min MTU +7554 requires_padding: bool, +7555} +7556 +7557impl SentFrames { +7558 /// Returns whether the packet contains only ACKs +7559 fn is_ack_only(&self, streams: &StreamsState) -> bool { +7560 !self.largest_acked.is_empty() +7561 && !self.non_retransmits +7562 && self.stream_frames.is_empty() +7563 && self.retransmits.is_empty(streams) +7564 } 7565 -7566impl From<PathEvent> for Event { -7567 fn from(source: PathEvent) -> Self { -7568 Self::Path(source) -7569 } -7570} -7571 -7572fn get_max_ack_delay(params: &TransportParameters) -> Duration { -7573 Duration::from_micros(params.max_ack_delay.0 * 1000) -7574} -7575 -7576/// Prevents overflow and improves behavior in extreme circumstances. -7577const MAX_BACKOFF_EXPONENT: u32 = 16; -7578 -7579/// The max interval between successive tail-loss probes. -7580/// -7581/// This is the "normal" value we use. -7582const MAX_PTO_INTERVAL: Duration = Duration::from_secs(2); -7583 -7584/// The idle time, below which we use the shorter [`MAX_PTO_FAST_INTERVAL`]. -7585const MIN_IDLE_FOR_FAST_PTO: Duration = Duration::from_secs(25); -7586 -7587/// The max interval between successive tail-loss probes with short idle times. -7588/// -7589/// If the path or connection idle time is less than [`MIN_IDLE_FOR_FAST_PTO`] then we use -7590/// this value to ensure we have plenty of retransmits before we reach the idle time. -7591const MAX_PTO_FAST_INTERVAL: Duration = Duration::from_secs(1); -7592 -7593/// The RTT threshold above which we cap the PTO interval to 1.5 * smoothed_rtt -7594/// -7595/// This is RTT time above which 1.5 * RTT > [`MAX_PTO_INTERVAL`], for these links we want -7596/// to extend the interval between tail-loss probes to not fill the entire pipe with them. -7597const SLOW_RTT_THRESHOLD: Duration = -7598 Duration::from_millis((MAX_PTO_INTERVAL.as_millis() as u64 * 2) / 3); -7599 -7600/// Minimal remaining size to allow packet coalescing, excluding cryptographic tag -7601/// -7602/// This must be at least as large as the header for a well-formed empty packet to be coalesced, -7603/// plus some space for frames. We only care about handshake headers because short header packets -7604/// necessarily have smaller headers, and initial packets are only ever the first packet in a -7605/// datagram (because we coalesce in ascending packet space order and the only reason to split a -7606/// packet is when packet space changes). -7607const MIN_PACKET_SPACE: usize = MAX_HANDSHAKE_OR_0RTT_HEADER_SIZE + 32; -7608 -7609/// Largest amount of space that could be occupied by a Handshake or 0-RTT packet's header -7610/// -7611/// Excludes packet-type-specific fields such as packet number or Initial token -7612// https://www.rfc-editor.org/rfc/rfc9000.html#name-0-rtt: flags + version + dcid len + dcid + -7613// scid len + scid + length + pn -7614const MAX_HANDSHAKE_OR_0RTT_HEADER_SIZE: usize = -7615 1 + 4 + 1 + MAX_CID_SIZE + 1 + MAX_CID_SIZE + VarInt::from_u32(u16::MAX as u32).size() + 4; -7616 -7617#[derive(Default)] -7618struct SentFrames { -7619 retransmits: ThinRetransmits, -7620 /// The packet number of the largest acknowledged packet for each path -7621 largest_acked: FxHashMap<PathId, u64>, -7622 stream_frames: StreamMetaVec, -7623 /// Whether the packet contains non-retransmittable frames (like datagrams) -7624 non_retransmits: bool, -7625 /// If the datagram containing these frames should be padded to the min MTU -7626 requires_padding: bool, -7627} -7628 -7629impl SentFrames { -7630 /// Returns whether the packet contains only ACKs -7631 fn is_ack_only(&self, streams: &StreamsState) -> bool { -7632 !self.largest_acked.is_empty() -7633 && !self.non_retransmits -7634 && self.stream_frames.is_empty() -7635 && self.retransmits.is_empty(streams) -7636 } -7637 -7638 fn retransmits_mut(&mut self) -> &mut Retransmits { -7639 self.retransmits.get_or_create() +7566 fn retransmits_mut(&mut self) -> &mut Retransmits { +7567 self.retransmits.get_or_create() +7568 } +7569 +7570 fn record_sent_frame(&mut self, frame: frame::EncodableFrame<'_>) { +7571 use frame::EncodableFrame::*; +7572 match frame { +7573 PathAck(path_ack_encoder) => { +7574 if let Some(max) = path_ack_encoder.ranges.max() { +7575 self.largest_acked.insert(path_ack_encoder.path_id, max); +7576 } +7577 } +7578 Ack(ack_encoder) => { +7579 if let Some(max) = ack_encoder.ranges.max() { +7580 self.largest_acked.insert(PathId::ZERO, max); +7581 } +7582 } +7583 Close(_) => { /* non retransmittable, but after this we don't really care */ } +7584 PathResponse(_) => self.non_retransmits = true, +7585 HandshakeDone(_) => self.retransmits_mut().handshake_done = true, +7586 ReachOut(frame) => self.retransmits_mut().reach_out.push(frame), +7587 ObservedAddr(_) => self.retransmits_mut().observed_addr = true, +7588 Ping(_) => self.non_retransmits = true, +7589 ImmediateAck(_) => self.non_retransmits = true, +7590 AckFrequency(_) => self.retransmits_mut().ack_frequency = true, +7591 PathChallenge(_) => self.non_retransmits = true, +7592 Crypto(crypto) => self.retransmits_mut().crypto.push_back(crypto), +7593 PathAbandon(path_abandon) => { +7594 self.retransmits_mut() +7595 .path_abandon +7596 .entry(path_abandon.path_id) +7597 .or_insert(path_abandon.error_code); +7598 } +7599 PathStatusAvailable(frame::PathStatusAvailable { path_id, .. }) +7600 | PathStatusBackup(frame::PathStatusBackup { path_id, .. }) => { +7601 self.retransmits_mut().path_status.insert(path_id); +7602 } +7603 MaxPathId(_) => self.retransmits_mut().max_path_id = true, +7604 PathsBlocked(_) => self.retransmits_mut().paths_blocked = true, +7605 PathCidsBlocked(path_cids_blocked) => { +7606 self.retransmits_mut() +7607 .path_cids_blocked +7608 .insert(path_cids_blocked.path_id); +7609 } +7610 ResetStream(reset) => self +7611 .retransmits_mut() +7612 .reset_stream +7613 .push((reset.id, reset.error_code)), +7614 StopSending(stop_sending) => self.retransmits_mut().stop_sending.push(stop_sending), +7615 NewConnectionId(new_cid) => self.retransmits_mut().new_cids.push(new_cid.issued()), +7616 RetireConnectionId(retire_cid) => self +7617 .retransmits_mut() +7618 .retire_cids +7619 .push((retire_cid.path_id.unwrap_or_default(), retire_cid.sequence)), +7620 Datagram(_) => self.non_retransmits = true, +7621 NewToken(_) => {} +7622 AddAddress(add_address) => { +7623 self.retransmits_mut().add_address.insert(add_address); +7624 } +7625 RemoveAddress(remove_address) => { +7626 self.retransmits_mut().remove_address.insert(remove_address); +7627 } +7628 StreamMeta(stream_meta_encoder) => self.stream_frames.push(stream_meta_encoder.meta), +7629 MaxData(_) => self.retransmits_mut().max_data = true, +7630 MaxStreamData(max) => { +7631 self.retransmits_mut().max_stream_data.insert(max.id); +7632 } +7633 MaxStreams(max_streams) => { +7634 self.retransmits_mut().max_stream_id[max_streams.dir as usize] = true +7635 } +7636 StreamsBlocked(streams_blocked) => { +7637 self.retransmits_mut().streams_blocked[streams_blocked.dir as usize] = true +7638 } +7639 } 7640 } -7641 -7642 fn record_sent_frame(&mut self, frame: frame::EncodableFrame<'_>) { -7643 use frame::EncodableFrame::*; -7644 match frame { -7645 PathAck(path_ack_encoder) => { -7646 if let Some(max) = path_ack_encoder.ranges.max() { -7647 self.largest_acked.insert(path_ack_encoder.path_id, max); -7648 } -7649 } -7650 Ack(ack_encoder) => { -7651 if let Some(max) = ack_encoder.ranges.max() { -7652 self.largest_acked.insert(PathId::ZERO, max); -7653 } -7654 } -7655 Close(_) => { /* non retransmittable, but after this we don't really care */ } -7656 PathResponse(_) => self.non_retransmits = true, -7657 HandshakeDone(_) => self.retransmits_mut().handshake_done = true, -7658 ReachOut(frame::ReachOut { round, ip, port }) => { -7659 let (recorded_round, reach_outs) = self -7660 .retransmits_mut() -7661 .reach_out -7662 .get_or_insert_with(|| (round, FxHashSet::default())); -7663 // Only record reach outs for the current round or a newer than the recorded one. -7664 if *recorded_round == round { -7665 // Same round, simply append. -7666 reach_outs.insert((ip, port)); -7667 } else if *recorded_round < round { -7668 // New round. -7669 *recorded_round = round; -7670 reach_outs.drain(); -7671 reach_outs.insert((ip, port)); -7672 } else { -7673 // ignore old reach out that was sent -7674 } -7675 } -7676 -7677 ObservedAddr(_) => self.retransmits_mut().observed_addr = true, -7678 Ping(_) => self.non_retransmits = true, -7679 ImmediateAck(_) => self.non_retransmits = true, -7680 AckFrequency(_) => self.retransmits_mut().ack_frequency = true, -7681 PathChallenge(_) => self.non_retransmits = true, -7682 Crypto(crypto) => self.retransmits_mut().crypto.push_back(crypto), -7683 PathAbandon(path_abandon) => { -7684 self.retransmits_mut() -7685 .path_abandon -7686 .entry(path_abandon.path_id) -7687 .or_insert(path_abandon.error_code); -7688 } -7689 PathStatusAvailable(frame::PathStatusAvailable { path_id, .. }) -7690 | PathStatusBackup(frame::PathStatusBackup { path_id, .. }) => { -7691 self.retransmits_mut().path_status.insert(path_id); -7692 } -7693 MaxPathId(_) => self.retransmits_mut().max_path_id = true, -7694 PathsBlocked(_) => self.retransmits_mut().paths_blocked = true, -7695 PathCidsBlocked(path_cids_blocked) => { -7696 self.retransmits_mut() -7697 .path_cids_blocked -7698 .insert(path_cids_blocked.path_id); -7699 } -7700 ResetStream(reset) => self -7701 .retransmits_mut() -7702 .reset_stream -7703 .push((reset.id, reset.error_code)), -7704 StopSending(stop_sending) => self.retransmits_mut().stop_sending.push(stop_sending), -7705 NewConnectionId(new_cid) => self.retransmits_mut().new_cids.push(new_cid.issued()), -7706 RetireConnectionId(retire_cid) => self -7707 .retransmits_mut() -7708 .retire_cids -7709 .push((retire_cid.path_id.unwrap_or_default(), retire_cid.sequence)), -7710 Datagram(_) => self.non_retransmits = true, -7711 NewToken(_) => {} -7712 AddAddress(add_address) => { -7713 self.retransmits_mut().add_address.insert(add_address); -7714 } -7715 RemoveAddress(remove_address) => { -7716 self.retransmits_mut().remove_address.insert(remove_address); -7717 } -7718 StreamMeta(stream_meta_encoder) => self.stream_frames.push(stream_meta_encoder.meta), -7719 MaxData(_) => self.retransmits_mut().max_data = true, -7720 MaxStreamData(max) => { -7721 self.retransmits_mut().max_stream_data.insert(max.id); -7722 } -7723 MaxStreams(max_streams) => { -7724 self.retransmits_mut().max_stream_id[max_streams.dir as usize] = true -7725 } -7726 StreamsBlocked(streams_blocked) => { -7727 self.retransmits_mut().streams_blocked[streams_blocked.dir as usize] = true -7728 } -7729 } -7730 } -7731} -7732 -7733/// Compute the negotiated idle timeout based on local and remote max_idle_timeout transport parameters. -7734/// -7735/// 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.> -7736/// -7737/// 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.> -7738/// -7739/// Returns the negotiated idle timeout as a `Duration`, or `None` when both endpoints have opted out of idle timeout. -7740fn negotiate_max_idle_timeout(x: Option<VarInt>, y: Option<VarInt>) -> Option<Duration> { -7741 match (x, y) { -7742 (Some(VarInt(0)) | None, Some(VarInt(0)) | None) => None, -7743 (Some(VarInt(0)) | None, Some(y)) => Some(Duration::from_millis(y.0)), -7744 (Some(x), Some(VarInt(0)) | None) => Some(Duration::from_millis(x.0)), -7745 (Some(x), Some(y)) => Some(Duration::from_millis(cmp::min(x, y).0)), -7746 } -7747} -7748 -7749#[cfg(test)] -7750mod tests { -7751 use super::*; -7752 -7753 #[test] -7754 fn negotiate_max_idle_timeout_commutative() { -7755 let test_params = [ -7756 (None, None, None), -7757 (None, Some(VarInt(0)), None), -7758 (None, Some(VarInt(2)), Some(Duration::from_millis(2))), -7759 (Some(VarInt(0)), Some(VarInt(0)), None), -7760 ( -7761 Some(VarInt(2)), -7762 Some(VarInt(0)), -7763 Some(Duration::from_millis(2)), -7764 ), -7765 ( -7766 Some(VarInt(1)), -7767 Some(VarInt(4)), -7768 Some(Duration::from_millis(1)), -7769 ), -7770 ]; -7771 -7772 for (left, right, result) in test_params { -7773 assert_eq!(negotiate_max_idle_timeout(left, right), result); -7774 assert_eq!(negotiate_max_idle_timeout(right, left), result); -7775 } -7776 } -7777} \ No newline at end of file +7641} +7642 +7643/// Compute the negotiated idle timeout based on local and remote max_idle_timeout transport parameters. +7644/// +7645/// 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.> +7646/// +7647/// 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.> +7648/// +7649/// Returns the negotiated idle timeout as a `Duration`, or `None` when both endpoints have opted out of idle timeout. +7650fn negotiate_max_idle_timeout(x: Option<VarInt>, y: Option<VarInt>) -> Option<Duration> { +7651 match (x, y) { +7652 (Some(VarInt(0)) | None, Some(VarInt(0)) | None) => None, +7653 (Some(VarInt(0)) | None, Some(y)) => Some(Duration::from_millis(y.0)), +7654 (Some(x), Some(VarInt(0)) | None) => Some(Duration::from_millis(x.0)), +7655 (Some(x), Some(y)) => Some(Duration::from_millis(cmp::min(x, y).0)), +7656 } +7657} +7658 +7659#[cfg(test)] +7660mod tests { +7661 use super::*; +7662 +7663 #[test] +7664 fn negotiate_max_idle_timeout_commutative() { +7665 let test_params = [ +7666 (None, None, None), +7667 (None, Some(VarInt(0)), None), +7668 (None, Some(VarInt(2)), Some(Duration::from_millis(2))), +7669 (Some(VarInt(0)), Some(VarInt(0)), None), +7670 ( +7671 Some(VarInt(2)), +7672 Some(VarInt(0)), +7673 Some(Duration::from_millis(2)), +7674 ), +7675 ( +7676 Some(VarInt(1)), +7677 Some(VarInt(4)), +7678 Some(Duration::from_millis(1)), +7679 ), +7680 ]; +7681 +7682 for (left, right, result) in test_params { +7683 assert_eq!(negotiate_max_idle_timeout(left, right), result); +7684 assert_eq!(negotiate_max_idle_timeout(right, left), result); +7685 } +7686 } +7687} \ No newline at end of file diff --git a/pr/617/docs/src/noq_proto/connection/paths.rs.html b/pr/617/docs/src/noq_proto/connection/paths.rs.html index 2886ad45e..0d9375e7d 100644 --- a/pr/617/docs/src/noq_proto/connection/paths.rs.html +++ b/pr/617/docs/src/noq_proto/connection/paths.rs.html @@ -535,622 +535,625 @@ 535 536 let prev_status = std::mem::replace(&mut self.open_status, OpenStatus::Informed); 537 OnPathResponseReceived::OnPath { -538 was_open: matches!( -539 prev_status, -540 OpenStatus::Informed | OpenStatus::Revalidating -541 ), -542 } -543 } -544 // Response to an on-path PathChallenge that does not validate this path. -545 Some(info) => { -546 // This is a valid path response, but this validates a 4-tuple we no longer -547 // have in use. Keep only sent challenges for the current path. -548 self.on_path_challenges_unconfirmed -549 .retain(|_token, i| i.network_path == self.network_path); -550 -551 // If there are no challenges for the current path, schedule one -552 if !self.on_path_challenges_unconfirmed.is_empty() { -553 self.pending_on_path_challenge = true; -554 } -555 OnPathResponseReceived::Ignored { -556 sent_on: info.network_path, -557 current_path: self.network_path, -558 } -559 } -560 None => { -561 // Response to an unknown PathChallenge. Does not indicate failure. -562 OnPathResponseReceived::Unknown -563 } -564 } -565 } -566 -567 /// Removes all on-path challenges we remember and cancels sending new on-path challenges. -568 pub(super) fn reset_on_path_challenges(&mut self) { -569 self.on_path_challenges_unconfirmed.clear(); -570 self.pending_on_path_challenge = false; -571 } -572 -573 #[cfg(feature = "qlog")] -574 pub(super) fn qlog_recovery_metrics( -575 &mut self, -576 path_id: PathId, -577 ) -> Option<RecoveryMetricsUpdated> { -578 let controller_metrics = self.congestion.metrics(); -579 -580 let metrics = RecoveryMetrics { -581 min_rtt: Some(self.rtt.min), -582 smoothed_rtt: Some(self.rtt.get()), -583 latest_rtt: Some(self.rtt.latest), -584 rtt_variance: Some(self.rtt.var), -585 pto_count: Some(self.pto_count), -586 bytes_in_flight: Some(self.in_flight.bytes), -587 packets_in_flight: Some(self.in_flight.ack_eliciting), -588 -589 congestion_window: Some(controller_metrics.congestion_window), -590 ssthresh: controller_metrics.ssthresh, -591 pacing_rate: controller_metrics.pacing_rate, -592 }; -593 -594 let event = metrics.to_qlog_event(path_id, &self.recovery_metrics); -595 self.recovery_metrics = metrics; -596 event -597 } -598 -599 /// Return how long we need to wait before sending `bytes_to_send` -600 /// -601 /// See [`Pacer::delay`]. -602 pub(super) fn pacing_delay(&mut self, bytes_to_send: u64, now: Instant) -> Option<Duration> { -603 let smoothed_rtt = self.rtt.get(); -604 let metrics = self.congestion.metrics(); -605 self.pacing.delay( -606 smoothed_rtt, -607 bytes_to_send, -608 self.current_mtu(), -609 metrics.congestion_window, -610 now, -611 metrics.send_quantum, -612 metrics.pacing_rate, -613 ) -614 } -615 -616 /// Updates the last observed address report received on this path. -617 /// -618 /// If the address was updated, it's returned to be informed to the application. -619 #[must_use = "updated observed address must be reported to the application"] -620 pub(super) fn update_observed_addr_report( -621 &mut self, -622 observed: ObservedAddr, -623 ) -> Option<SocketAddr> { -624 match self.last_observed_addr_report.as_mut() { -625 Some(prev) => { -626 if prev.seq_no >= observed.seq_no { -627 // frames that do not increase the sequence number on this path are ignored -628 None -629 } else if prev.ip == observed.ip && prev.port == observed.port { -630 // keep track of the last seq_no but do not report the address as updated -631 prev.seq_no = observed.seq_no; -632 None -633 } else { -634 let addr = observed.socket_addr(); -635 self.last_observed_addr_report = Some(observed); -636 Some(addr) -637 } -638 } -639 None => { -640 let addr = observed.socket_addr(); -641 self.last_observed_addr_report = Some(observed); -642 Some(addr) -643 } -644 } -645 } -646 -647 pub(crate) fn remote_status(&self) -> Option<PathStatus> { -648 self.status.remote_status.map(|(_seq, status)| status) -649 } -650 -651 pub(crate) fn local_status(&self) -> PathStatus { -652 self.status.local_status -653 } -654 -655 /// Tag uniquely identifying a path in a connection. -656 /// -657 /// When a migration happens on the same [`PathId`] we still detect a change in the -658 /// 4-tuple and generate a new [`PathData`] for it. Each such generation has a unique -659 /// value to keep track of which 4-tuple a packet belonged to. -660 pub(super) fn generation(&self) -> u64 { -661 self.generation -662 } -663} -664 -665pub(super) enum OnPathResponseReceived { -666 /// This response validates the path on its current remote address. -667 OnPath { was_open: bool }, -668 /// The received token is unknown. -669 Unknown, -670 /// The response is valid but it's not usable for path validation. -671 Ignored { -672 sent_on: FourTuple, -673 current_path: FourTuple, -674 }, -675} -676 -677#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] -678pub(super) enum OpenStatus { -679 /// A first packet has not been sent using this [`PathId`]. -680 #[default] -681 Pending, -682 /// The first packet has been sent using this [`PathId`]. However, it is not yet deemed good -683 /// enough to be reported to the application. -684 Sent, -685 /// The application has been informed of this path. -686 Informed, -687 /// The path was [`Self::Informed`] before, but we want to trigger path validation again. -688 /// -689 /// This is used to ensure we properly stop trying to re-send path challenges eventually, without -690 /// having to switch to [`Self::Pending`] when re-validating, as that would trigger another -691 /// application-level event about the path opening once validation succeeds. -692 Revalidating, -693} -694 -695/// Congestion metrics as described in [`recovery_metrics_updated`]. -696/// -697/// [`recovery_metrics_updated`]: https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-quic-events.html#name-recovery_metrics_updated -698#[cfg(feature = "qlog")] -699#[derive(Default, Clone, PartialEq, Debug)] -700#[non_exhaustive] -701struct RecoveryMetrics { -702 pub min_rtt: Option<Duration>, -703 pub smoothed_rtt: Option<Duration>, -704 pub latest_rtt: Option<Duration>, -705 pub rtt_variance: Option<Duration>, -706 pub pto_count: Option<u32>, -707 pub bytes_in_flight: Option<u64>, -708 pub packets_in_flight: Option<u64>, -709 pub congestion_window: Option<u64>, -710 pub ssthresh: Option<u64>, -711 pub pacing_rate: Option<u64>, -712} -713 -714#[cfg(feature = "qlog")] -715impl RecoveryMetrics { -716 /// Retain only values that have been updated since the last snapshot. -717 fn retain_updated(&self, previous: &Self) -> Self { -718 macro_rules! keep_if_changed { -719 ($name:ident) => { -720 if previous.$name == self.$name { -721 None -722 } else { -723 self.$name -724 } -725 }; -726 } -727 -728 Self { -729 min_rtt: keep_if_changed!(min_rtt), -730 smoothed_rtt: keep_if_changed!(smoothed_rtt), -731 latest_rtt: keep_if_changed!(latest_rtt), -732 rtt_variance: keep_if_changed!(rtt_variance), -733 pto_count: keep_if_changed!(pto_count), -734 bytes_in_flight: keep_if_changed!(bytes_in_flight), -735 packets_in_flight: keep_if_changed!(packets_in_flight), -736 congestion_window: keep_if_changed!(congestion_window), -737 ssthresh: keep_if_changed!(ssthresh), -738 pacing_rate: keep_if_changed!(pacing_rate), +538 was_open: matches!(prev_status, OpenStatus::Informed), +539 } +540 } +541 // Response to an on-path PathChallenge that does not validate this path. +542 Some(info) => { +543 // This is a valid path response, but this validates a 4-tuple we no longer +544 // have in use. Keep only sent challenges for the current path. +545 self.on_path_challenges_unconfirmed +546 .retain(|_token, i| i.network_path == self.network_path); +547 +548 // If there are no challenges for the current path, schedule one +549 if !self.on_path_challenges_unconfirmed.is_empty() { +550 self.pending_on_path_challenge = true; +551 } +552 OnPathResponseReceived::Ignored { +553 sent_on: info.network_path, +554 current_path: self.network_path, +555 } +556 } +557 None => { +558 // Response to an unknown PathChallenge. Does not indicate failure. +559 OnPathResponseReceived::Unknown +560 } +561 } +562 } +563 +564 /// Removes all on-path challenges we remember and cancels sending new on-path challenges. +565 pub(super) fn reset_on_path_challenges(&mut self) { +566 self.on_path_challenges_unconfirmed.clear(); +567 self.pending_on_path_challenge = false; +568 } +569 +570 #[cfg(feature = "qlog")] +571 pub(super) fn qlog_recovery_metrics( +572 &mut self, +573 path_id: PathId, +574 ) -> Option<RecoveryMetricsUpdated> { +575 let controller_metrics = self.congestion.metrics(); +576 +577 let metrics = RecoveryMetrics { +578 min_rtt: Some(self.rtt.min), +579 smoothed_rtt: Some(self.rtt.get()), +580 latest_rtt: Some(self.rtt.latest), +581 rtt_variance: Some(self.rtt.var), +582 pto_count: Some(self.pto_count), +583 bytes_in_flight: Some(self.in_flight.bytes), +584 packets_in_flight: Some(self.in_flight.ack_eliciting), +585 +586 congestion_window: Some(controller_metrics.congestion_window), +587 ssthresh: controller_metrics.ssthresh, +588 pacing_rate: controller_metrics.pacing_rate, +589 }; +590 +591 let event = metrics.to_qlog_event(path_id, &self.recovery_metrics); +592 self.recovery_metrics = metrics; +593 event +594 } +595 +596 /// Return how long we need to wait before sending `bytes_to_send` +597 /// +598 /// See [`Pacer::delay`]. +599 pub(super) fn pacing_delay(&mut self, bytes_to_send: u64, now: Instant) -> Option<Duration> { +600 let smoothed_rtt = self.rtt.get(); +601 let metrics = self.congestion.metrics(); +602 self.pacing.delay( +603 smoothed_rtt, +604 bytes_to_send, +605 self.current_mtu(), +606 metrics.congestion_window, +607 now, +608 metrics.send_quantum, +609 metrics.pacing_rate, +610 ) +611 } +612 +613 /// Updates the last observed address report received on this path. +614 /// +615 /// If the address was updated, it's returned to be informed to the application. +616 #[must_use = "updated observed address must be reported to the application"] +617 pub(super) fn update_observed_addr_report( +618 &mut self, +619 observed: ObservedAddr, +620 ) -> Option<SocketAddr> { +621 match self.last_observed_addr_report.as_mut() { +622 Some(prev) => { +623 if prev.seq_no >= observed.seq_no { +624 // frames that do not increase the sequence number on this path are ignored +625 None +626 } else if prev.ip == observed.ip && prev.port == observed.port { +627 // keep track of the last seq_no but do not report the address as updated +628 prev.seq_no = observed.seq_no; +629 None +630 } else { +631 let addr = observed.socket_addr(); +632 self.last_observed_addr_report = Some(observed); +633 Some(addr) +634 } +635 } +636 None => { +637 let addr = observed.socket_addr(); +638 self.last_observed_addr_report = Some(observed); +639 Some(addr) +640 } +641 } +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 -742 /// Emit a `MetricsUpdated` event containing only updated values -743 fn to_qlog_event(&self, path_id: PathId, previous: &Self) -> Option<RecoveryMetricsUpdated> { -744 let updated = self.retain_updated(previous); -745 -746 if updated == Self::default() { -747 return None; -748 } -749 -750 Some(RecoveryMetricsUpdated { -751 min_rtt: updated.min_rtt.map(|rtt| rtt.as_secs_f32()), -752 smoothed_rtt: updated.smoothed_rtt.map(|rtt| rtt.as_secs_f32()), -753 latest_rtt: updated.latest_rtt.map(|rtt| rtt.as_secs_f32()), -754 rtt_variance: updated.rtt_variance.map(|rtt| rtt.as_secs_f32()), -755 pto_count: updated -756 .pto_count -757 .map(|count| count.try_into().unwrap_or(u16::MAX)), -758 bytes_in_flight: updated.bytes_in_flight, -759 packets_in_flight: updated.packets_in_flight, -760 congestion_window: updated.congestion_window, -761 ssthresh: updated.ssthresh, -762 pacing_rate: updated.pacing_rate, -763 path_id: Some(path_id.as_u32() as u64), -764 }) -765 } -766} -767 -768/// RTT estimation for a particular network path -769#[derive(Copy, Clone, Debug)] -770pub struct RttEstimator { -771 /// The most recent RTT measurement made when receiving an ack for a previously unacked packet -772 latest: Duration, -773 /// The smoothed RTT of the connection, computed as described in RFC6298 -774 smoothed: Option<Duration>, -775 /// The RTT variance, computed as described in RFC6298 -776 var: Duration, -777 /// The minimum RTT seen in the connection, ignoring ack delay. -778 min: Duration, -779} -780 -781impl RttEstimator { -782 pub(super) fn new(initial_rtt: Duration) -> Self { -783 Self { -784 latest: initial_rtt, -785 smoothed: None, -786 var: initial_rtt / 2, -787 min: initial_rtt, -788 } -789 } -790 -791 /// Resets the estimator using a new initial_rtt value. -792 /// -793 /// This only resets the initial_rtt **if** no samples have been recorded yet. If there -794 /// are any recorded samples the initial estimate can not be adjusted after the fact. -795 /// -796 /// This is useful when you receive a PATH_RESPONSE in the first packet received on a -797 /// new path. In this case you can use the delay of the PATH_CHALLENGE-PATH_RESPONSE as -798 /// the initial RTT to get a better expected estimation. -799 /// -800 /// A PATH_CHALLENGE-PATH_RESPONSE pair later in the connection should not be used -801 /// explicitly as an estimation since PATH_CHALLENGE is an ACK-eliciting packet itself -802 /// already. -803 pub(crate) fn reset_initial_rtt(&mut self, initial_rtt: Duration) { -804 if self.smoothed.is_none() { -805 self.latest = initial_rtt; -806 self.var = initial_rtt / 2; -807 self.min = initial_rtt; -808 } -809 } -810 -811 /// The current best RTT estimation. -812 pub fn get(&self) -> Duration { -813 self.smoothed.unwrap_or(self.latest) -814 } -815 -816 /// Conservative estimate of RTT -817 /// -818 /// Takes the maximum of smoothed and latest RTT, as recommended -819 /// in 6.1.2 of the recovery spec (draft 29). -820 pub fn conservative(&self) -> Duration { -821 self.get().max(self.latest) -822 } -823 -824 /// Minimum RTT registered so far for this estimator. -825 pub fn min(&self) -> Duration { -826 self.min -827 } -828 -829 /// PTO computed as described in RFC9002#6.2.1. -830 pub(crate) fn pto_base(&self) -> Duration { -831 self.get() + cmp::max(4 * self.var, TIMER_GRANULARITY) -832 } -833 -834 /// Records an RTT sample. -835 pub(crate) fn update(&mut self, ack_delay: Duration, rtt: Duration) { -836 self.latest = rtt; -837 // https://www.rfc-editor.org/rfc/rfc9002.html#section-5.2-3: -838 // min_rtt does not adjust for ack_delay to avoid underestimating. -839 self.min = cmp::min(self.min, self.latest); -840 // Based on RFC6298. -841 if let Some(smoothed) = self.smoothed { -842 let adjusted_rtt = if self.min + ack_delay <= self.latest { -843 self.latest - ack_delay -844 } else { -845 self.latest -846 }; -847 let var_sample = smoothed.abs_diff(adjusted_rtt); -848 self.var = (3 * self.var + var_sample) / 4; -849 self.smoothed = Some((7 * smoothed + adjusted_rtt) / 8); -850 } else { -851 self.smoothed = Some(self.latest); -852 self.var = self.latest / 2; -853 self.min = self.latest; -854 } -855 } -856} -857 -858#[derive(Default, Debug)] -859pub(crate) struct PathResponses { -860 pending: Vec<PathResponse>, -861} -862 -863impl PathResponses { -864 pub(crate) fn push(&mut self, packet: u64, token: u64, network_path: FourTuple) { -865 /// Arbitrary permissive limit to prevent abuse -866 const MAX_PATH_RESPONSES: usize = 16; -867 let response = PathResponse { -868 packet, -869 token, -870 network_path, -871 }; -872 let existing = self -873 .pending -874 .iter_mut() -875 .find(|x| x.network_path.remote == network_path.remote); -876 if let Some(existing) = existing { -877 // Update a queued response -878 if existing.packet <= packet { -879 *existing = response; -880 } -881 return; -882 } -883 if self.pending.len() < MAX_PATH_RESPONSES { -884 self.pending.push(response); -885 } else { -886 // We don't expect to ever hit this with well-behaved peers, so we don't bother dropping -887 // older challenges. -888 trace!("ignoring excessive PATH_CHALLENGE"); -889 } -890 } -891 -892 pub(crate) fn pop_off_path(&mut self, network_path: FourTuple) -> Option<(u64, FourTuple)> { -893 let response = *self.pending.last()?; -894 // We use an exact comparison here, because once we've received for the first time, -895 // we really should either already have a local_ip, or we will never get one -896 // (because our OS doesn't support it). -897 if response.network_path == network_path { -898 // We don't bother searching further because we expect that the on-path response will -899 // get drained in the immediate future by a call to `pop_on_path` -900 return None; -901 } -902 self.pending.pop(); -903 Some((response.token, response.network_path)) -904 } -905 -906 pub(crate) fn pop_on_path(&mut self, network_path: FourTuple) -> Option<u64> { -907 let response = *self.pending.last()?; -908 // Using an exact comparison. See explanation in `pop_off_path`. -909 if response.network_path != network_path { -910 // We don't bother searching further because we expect that the off-path response will -911 // get drained in the immediate future by a call to `pop_off_path` -912 return None; -913 } -914 self.pending.pop(); -915 Some(response.token) -916 } -917 -918 pub(crate) fn is_empty(&self) -> bool { -919 self.pending.is_empty() -920 } -921} -922 -923#[derive(Copy, Clone, Debug)] -924struct PathResponse { -925 /// The packet number the corresponding PATH_CHALLENGE was received in -926 packet: u64, -927 /// The token of the PATH_CHALLENGE -928 token: u64, -929 /// The path the corresponding PATH_CHALLENGE was received from -930 network_path: FourTuple, -931} -932 -933/// Summary statistics of packets that have been sent on a particular path, but which have not yet -934/// been acked or deemed lost -935#[derive(Debug)] -936pub(super) struct InFlight { -937 /// Sum of the sizes of all sent packets considered "in flight" by congestion control -938 /// -939 /// The size does not include IP or UDP overhead. Packets only containing ACK frames do not -940 /// count towards this to ensure congestion control does not impede congestion feedback. -941 pub(super) bytes: u64, -942 /// Number of packets in flight containing frames other than ACK and PADDING -943 /// -944 /// This can be 0 even when bytes is not 0 because PADDING frames cause a packet to be -945 /// considered "in flight" by congestion control. However, if this is nonzero, bytes will always -946 /// also be nonzero. -947 pub(super) ack_eliciting: u64, -948} -949 -950impl InFlight { -951 fn new() -> Self { -952 Self { -953 bytes: 0, -954 ack_eliciting: 0, -955 } -956 } -957 -958 fn insert(&mut self, packet: &SentPacket) { -959 self.bytes += u64::from(packet.size); -960 self.ack_eliciting += u64::from(packet.ack_eliciting); -961 } -962 -963 /// Update counters to account for a packet becoming acknowledged, lost, or abandoned -964 fn remove(&mut self, packet: &SentPacket) { -965 self.bytes -= u64::from(packet.size); -966 self.ack_eliciting -= u64::from(packet.ack_eliciting); -967 } -968} -969 -970/// State for QUIC-MULTIPATH PATH_STATUS_AVAILABLE and PATH_STATUS_BACKUP frames -971#[derive(Debug, Clone, Default)] -972pub(super) struct PathStatusState { -973 /// The local status -974 local_status: PathStatus, -975 /// Local sequence number, for both PATH_STATUS_AVAILABLE and PATH_STATUS_BACKUP -976 /// -977 /// This is the number of the *next* path status frame to be sent. -978 local_seq: VarInt, -979 /// The status set by the remote -980 remote_status: Option<(VarInt, PathStatus)>, -981} -982 -983impl PathStatusState { -984 /// To be called on received PATH_STATUS_AVAILABLE/PATH_STATUS_BACKUP frames -985 pub(super) fn remote_update(&mut self, status: PathStatus, seq: VarInt) { -986 if self.remote_status.is_some_and(|(curr, _)| curr >= seq) { -987 return trace!(%seq, "ignoring path status update"); -988 } -989 -990 let prev = self.remote_status.replace((seq, status)).map(|(_, s)| s); -991 if prev != Some(status) { -992 debug!(?status, ?seq, "remote changed path status"); -993 } -994 } -995 -996 /// Updates the local status -997 /// -998 /// If the local status changed, the previous value is returned -999 pub(super) fn local_update(&mut self, status: PathStatus) -> Option<PathStatus> { -1000 if self.local_status == status { -1001 return None; -1002 } -1003 -1004 self.local_seq = self.local_seq.saturating_add(1u8); -1005 Some(std::mem::replace(&mut self.local_status, status)) -1006 } -1007 -1008 pub(crate) fn seq(&self) -> VarInt { -1009 self.local_seq -1010 } -1011} -1012 -1013/// The QUIC-MULTIPATH path status -1014/// -1015/// See section "3.3 Path Status Management": -1016/// <https://quicwg.org/multipath/draft-ietf-quic-multipath.html#name-path-status-management> -1017#[cfg_attr(test, derive(test_strategy::Arbitrary))] -1018#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] -1019pub enum PathStatus { -1020 /// Paths marked with as available will be used when scheduling packets -1021 /// -1022 /// If multiple paths are available, packets will be scheduled on whichever has -1023 /// capacity. -1024 #[default] -1025 Available, -1026 /// Paths marked as backup will only be used if there are no available paths -1027 /// -1028 /// If the max_idle_timeout is specified the path will be kept alive so that it does not -1029 /// expire. -1030 Backup, -1031} -1032 -1033/// Application events about paths -1034#[derive(Debug, Clone, PartialEq, Eq)] -1035pub enum PathEvent { -1036 /// A new path has been opened -1037 Opened { -1038 /// Which path is now open -1039 id: PathId, -1040 }, -1041 /// A path was abandoned and is no longer usable. -1042 /// -1043 /// This event will always be followed by [`Self::Discarded`] after some time. -1044 Abandoned { -1045 /// With path was abandoned. -1046 id: PathId, -1047 /// Reason why this path was abandoned. -1048 reason: PathAbandonReason, -1049 }, -1050 /// A path was discarded and all remaining state for it has been removed. -1051 /// -1052 /// This event is the last event for a path, and is always emitted after [`Self::Abandoned`]. -1053 Discarded { -1054 /// Which path had its state dropped -1055 id: PathId, -1056 /// The final path stats, they are no longer available via [`Connection::stats`] -1057 /// -1058 /// [`Connection::stats`]: super::Connection::stats -1059 path_stats: Box<PathStats>, -1060 }, -1061 /// The remote changed the status of the path -1062 /// -1063 /// The local status is not changed because of this event. It is up to the application -1064 /// to update the local status, which is used for packet scheduling, when the remote -1065 /// changes the status. -1066 RemoteStatus { -1067 /// Path which has changed status -1068 id: PathId, -1069 /// The new status set by the remote -1070 status: PathStatus, -1071 }, -1072 /// Received an observation of our external address from the peer. -1073 ObservedAddr { -1074 /// Path over which the observed address was reported, [`PathId::ZERO`] when multipath is -1075 /// not negotiated -1076 id: PathId, -1077 /// The address observed by the remote over this path -1078 addr: SocketAddr, -1079 }, -1080} -1081 -1082/// Reason for why a path was abandoned. -1083#[derive(Debug, Clone, Eq, PartialEq)] -1084pub enum PathAbandonReason { -1085 /// The path was closed locally by the application. -1086 ApplicationClosed { -1087 /// The error code to be sent with the abandon frame. -1088 error_code: VarInt, -1089 }, -1090 /// We didn't receive a path response in time after opening this path. -1091 ValidationFailed, -1092 /// We didn't receive any data from the remote within the path's idle timeout. -1093 TimedOut, -1094 /// The path became unusable after a local network change. -1095 UnusableAfterNetworkChange, -1096 /// The path was opened in a NAT traversal round which was terminated. -1097 NatTraversalRoundEnded, -1098 /// The remote closed the path. -1099 RemoteAbandoned { -1100 /// The error that was sent with the abandon frame. -1101 error_code: VarInt, -1102 }, -1103} -1104 -1105impl PathAbandonReason { -1106 /// Whether this abandon was initiated by the remote peer. -1107 pub(crate) fn is_remote(&self) -> bool { -1108 matches!(self, Self::RemoteAbandoned { .. }) -1109 } -1110 -1111 /// Returns the error code to send with a PATH_ABANDON frame. -1112 pub(crate) fn error_code(&self) -> TransportErrorCode { -1113 match self { -1114 Self::ApplicationClosed { error_code } => (*error_code).into(), -1115 Self::NatTraversalRoundEnded => TransportErrorCode::APPLICATION_ABANDON_PATH, -1116 Self::ValidationFailed | Self::TimedOut | Self::UnusableAfterNetworkChange => { -1117 TransportErrorCode::PATH_UNSTABLE_OR_POOR -1118 } -1119 Self::RemoteAbandoned { error_code } => (*error_code).into(), -1120 } -1121 } -1122} -1123 -1124/// Error from setting path status -1125#[derive(Debug, Error, Clone, PartialEq, Eq)] -1126pub enum SetPathStatusError { -1127 /// Error indicating that a path has not been opened or has already been abandoned -1128 #[error("closed path")] -1129 ClosedPath, -1130 /// Error indicating that this operation requires multipath to be negotiated whereas it hasn't been -1131 #[error("multipath not negotiated")] -1132 MultipathNotNegotiated, -1133} -1134 -1135/// Error indicating that a path has not been opened or has already been abandoned -1136#[derive(Debug, Default, Error, Clone, PartialEq, Eq)] -1137#[error("closed path")] -1138pub struct ClosedPath { -1139 pub(super) _private: (), -1140} -1141 -1142#[cfg(test)] -1143mod tests { -1144 use super::*; -1145 -1146 #[test] -1147 fn test_path_id_saturating_add() { -1148 // add within range behaves normally -1149 let large: PathId = u16::MAX.into(); -1150 let next = u32::from(u16::MAX) + 1; -1151 assert_eq!(large.saturating_add(1u8), PathId::from(next)); -1152 -1153 // outside range saturates -1154 assert_eq!(PathId::MAX.saturating_add(1u8), PathId::MAX) -1155 } -1156} \ No newline at end of file +740 +741 Some(RecoveryMetricsUpdated { +742 min_rtt: updated.min_rtt.map(|rtt| rtt.as_secs_f32()), +743 smoothed_rtt: updated.smoothed_rtt.map(|rtt| rtt.as_secs_f32()), +744 latest_rtt: updated.latest_rtt.map(|rtt| rtt.as_secs_f32()), +745 rtt_variance: updated.rtt_variance.map(|rtt| rtt.as_secs_f32()), +746 pto_count: updated +747 .pto_count +748 .map(|count| count.try_into().unwrap_or(u16::MAX)), +749 bytes_in_flight: updated.bytes_in_flight, +750 packets_in_flight: updated.packets_in_flight, +751 congestion_window: updated.congestion_window, +752 ssthresh: updated.ssthresh, +753 pacing_rate: updated.pacing_rate, +754 path_id: Some(path_id.as_u32() as u64), +755 }) +756 } +757} +758 +759/// RTT estimation for a particular network path +760#[derive(Copy, Clone, Debug)] +761pub struct RttEstimator { +762 /// The most recent RTT measurement made when receiving an ack for a previously unacked packet +763 latest: Duration, +764 /// The smoothed RTT of the connection, computed as described in RFC6298 +765 smoothed: Option<Duration>, +766 /// The RTT variance, computed as described in RFC6298 +767 var: Duration, +768 /// The minimum RTT seen in the connection, ignoring ack delay. +769 min: Duration, +770} +771 +772impl RttEstimator { +773 pub(super) fn new(initial_rtt: Duration) -> Self { +774 Self { +775 latest: initial_rtt, +776 smoothed: None, +777 var: initial_rtt / 2, +778 min: initial_rtt, +779 } +780 } +781 +782 /// Resets the estimator using a new initial_rtt value. +783 /// +784 /// This only resets the initial_rtt **if** no samples have been recorded yet. If there +785 /// are any recorded samples the initial estimate can not be adjusted after the fact. +786 /// +787 /// This is useful when you receive a PATH_RESPONSE in the first packet received on a +788 /// new path. In this case you can use the delay of the PATH_CHALLENGE-PATH_RESPONSE as +789 /// the initial RTT to get a better expected estimation. +790 /// +791 /// A PATH_CHALLENGE-PATH_RESPONSE pair later in the connection should not be used +792 /// explicitly as an estimation since PATH_CHALLENGE is an ACK-eliciting packet itself +793 /// already. +794 pub(crate) fn reset_initial_rtt(&mut self, initial_rtt: Duration) { +795 if self.smoothed.is_none() { +796 self.latest = initial_rtt; +797 self.var = initial_rtt / 2; +798 self.min = initial_rtt; +799 } +800 } +801 +802 /// The current best RTT estimation. +803 pub fn get(&self) -> Duration { +804 self.smoothed.unwrap_or(self.latest) +805 } +806 +807 /// Conservative estimate of RTT +808 /// +809 /// Takes the maximum of smoothed and latest RTT, as recommended +810 /// in 6.1.2 of the recovery spec (draft 29). +811 pub fn conservative(&self) -> Duration { +812 self.get().max(self.latest) +813 } +814 +815 /// Minimum RTT registered so far for this estimator. +816 pub fn min(&self) -> Duration { +817 self.min +818 } +819 +820 /// PTO computed as described in RFC9002#6.2.1. +821 pub(crate) fn pto_base(&self) -> Duration { +822 self.get() + cmp::max(4 * self.var, TIMER_GRANULARITY) +823 } +824 +825 /// Records an RTT sample. +826 pub(crate) fn update(&mut self, ack_delay: Duration, rtt: Duration) { +827 self.latest = rtt; +828 // https://www.rfc-editor.org/rfc/rfc9002.html#section-5.2-3: +829 // min_rtt does not adjust for ack_delay to avoid underestimating. +830 self.min = cmp::min(self.min, self.latest); +831 // Based on RFC6298. +832 if let Some(smoothed) = self.smoothed { +833 let adjusted_rtt = if self.min + ack_delay <= self.latest { +834 self.latest - ack_delay +835 } else { +836 self.latest +837 }; +838 let var_sample = smoothed.abs_diff(adjusted_rtt); +839 self.var = (3 * self.var + var_sample) / 4; +840 self.smoothed = Some((7 * smoothed + adjusted_rtt) / 8); +841 } else { +842 self.smoothed = Some(self.latest); +843 self.var = self.latest / 2; +844 self.min = self.latest; +845 } +846 } +847} +848 +849#[derive(Default, Debug)] +850pub(crate) struct PathResponses { +851 pending: Vec<PathResponse>, +852} +853 +854impl PathResponses { +855 pub(crate) fn push(&mut self, packet: u64, token: u64, network_path: FourTuple) { +856 /// An arbitrary permissive limit to prevent abuse. +857 /// +858 /// If we've negotiated the n0 NAT Traversal extension, and one user might have a lot +859 /// of addresses, e.g. because of having lots of interfaces (we've seen >25 interfaces +860 /// on Macs with docker and other things), then we need to be able to process at least +861 /// as many PATH_CHALLENGE frames as there are interfaces. +862 /// On top of that, there are retries, which make it possible that we need to process +863 /// even more. +864 /// +865 /// Considering that there can be up to 2 `PathData`s per active `PathId`, and +866 /// reasonable default values for maximum concurrent multipath paths are ~8 and each +867 /// `PathResponse` struct takes up 72 bytes at the moment this, means an attacker can +868 /// cause us to keep `32 * 2 * 8 * 72 = ~37KB` of data around. +869 const MAX_PATH_RESPONSES: usize = 32; +870 let response = PathResponse { +871 packet, +872 token, +873 network_path, +874 }; +875 let existing = self +876 .pending +877 .iter_mut() +878 .find(|x| x.network_path.remote == network_path.remote); +879 if let Some(existing) = existing { +880 // Update a queued response +881 if existing.packet <= packet { +882 *existing = response; +883 } +884 return; +885 } +886 if self.pending.len() < MAX_PATH_RESPONSES { +887 self.pending.push(response); +888 } else { +889 // We don't expect to ever hit this with well-behaved peers, so we don't bother dropping +890 // older challenges. +891 trace!("ignoring excessive PATH_CHALLENGE"); +892 } +893 } +894 +895 pub(crate) fn pop_off_path(&mut self, network_path: FourTuple) -> Option<(u64, FourTuple)> { +896 let response = *self.pending.last()?; +897 // We use an exact comparison here, because once we've received for the first time, +898 // we really should either already have a local_ip, or we will never get one +899 // (because our OS doesn't support it). +900 if response.network_path == network_path { +901 // We don't bother searching further because we expect that the on-path response will +902 // get drained in the immediate future by a call to `pop_on_path` +903 return None; +904 } +905 self.pending.pop(); +906 Some((response.token, response.network_path)) +907 } +908 +909 pub(crate) fn pop_on_path(&mut self, network_path: FourTuple) -> Option<u64> { +910 let response = *self.pending.last()?; +911 // Using an exact comparison. See explanation in `pop_off_path`. +912 if response.network_path != network_path { +913 // We don't bother searching further because we expect that the off-path response will +914 // get drained in the immediate future by a call to `pop_off_path` +915 return None; +916 } +917 self.pending.pop(); +918 Some(response.token) +919 } +920 +921 pub(crate) fn is_empty(&self) -> bool { +922 self.pending.is_empty() +923 } +924} +925 +926#[derive(Copy, Clone, Debug)] +927struct PathResponse { +928 /// The packet number the corresponding PATH_CHALLENGE was received in +929 packet: u64, +930 /// The token of the PATH_CHALLENGE +931 token: u64, +932 /// The path the corresponding PATH_CHALLENGE was received from +933 network_path: FourTuple, +934} +935 +936/// Summary statistics of packets that have been sent on a particular path, but which have not yet +937/// been acked or deemed lost +938#[derive(Debug)] +939pub(super) struct InFlight { +940 /// Sum of the sizes of all sent packets considered "in flight" by congestion control +941 /// +942 /// The size does not include IP or UDP overhead. Packets only containing ACK frames do not +943 /// count towards this to ensure congestion control does not impede congestion feedback. +944 pub(super) bytes: u64, +945 /// Number of packets in flight containing frames other than ACK and PADDING +946 /// +947 /// This can be 0 even when bytes is not 0 because PADDING frames cause a packet to be +948 /// considered "in flight" by congestion control. However, if this is nonzero, bytes will always +949 /// also be nonzero. +950 pub(super) ack_eliciting: u64, +951} +952 +953impl InFlight { +954 fn new() -> Self { +955 Self { +956 bytes: 0, +957 ack_eliciting: 0, +958 } +959 } +960 +961 fn insert(&mut self, packet: &SentPacket) { +962 self.bytes += u64::from(packet.size); +963 self.ack_eliciting += u64::from(packet.ack_eliciting); +964 } +965 +966 /// Update counters to account for a packet becoming acknowledged, lost, or abandoned +967 fn remove(&mut self, packet: &SentPacket) { +968 self.bytes -= u64::from(packet.size); +969 self.ack_eliciting -= u64::from(packet.ack_eliciting); +970 } +971} +972 +973/// State for QUIC-MULTIPATH PATH_STATUS_AVAILABLE and PATH_STATUS_BACKUP frames +974#[derive(Debug, Clone, Default)] +975pub(super) struct PathStatusState { +976 /// The local status +977 local_status: PathStatus, +978 /// Local sequence number, for both PATH_STATUS_AVAILABLE and PATH_STATUS_BACKUP +979 /// +980 /// This is the number of the *next* path status frame to be sent. +981 local_seq: VarInt, +982 /// The status set by the remote +983 remote_status: Option<(VarInt, PathStatus)>, +984} +985 +986impl PathStatusState { +987 /// To be called on received PATH_STATUS_AVAILABLE/PATH_STATUS_BACKUP frames +988 pub(super) fn remote_update(&mut self, status: PathStatus, seq: VarInt) { +989 if self.remote_status.is_some_and(|(curr, _)| curr >= seq) { +990 return trace!(%seq, "ignoring path status update"); +991 } +992 +993 let prev = self.remote_status.replace((seq, status)).map(|(_, s)| s); +994 if prev != Some(status) { +995 debug!(?status, ?seq, "remote changed path status"); +996 } +997 } +998 +999 /// Updates the local status +1000 /// +1001 /// If the local status changed, the previous value is returned +1002 pub(super) fn local_update(&mut self, status: PathStatus) -> Option<PathStatus> { +1003 if self.local_status == status { +1004 return None; +1005 } +1006 +1007 self.local_seq = self.local_seq.saturating_add(1u8); +1008 Some(std::mem::replace(&mut self.local_status, status)) +1009 } +1010 +1011 pub(crate) fn seq(&self) -> VarInt { +1012 self.local_seq +1013 } +1014} +1015 +1016/// The QUIC-MULTIPATH path status +1017/// +1018/// See section "3.3 Path Status Management": +1019/// <https://quicwg.org/multipath/draft-ietf-quic-multipath.html#name-path-status-management> +1020#[cfg_attr(test, derive(test_strategy::Arbitrary))] +1021#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] +1022pub enum PathStatus { +1023 /// Paths marked with as available will be used when scheduling packets +1024 /// +1025 /// If multiple paths are available, packets will be scheduled on whichever has +1026 /// capacity. +1027 #[default] +1028 Available, +1029 /// Paths marked as backup will only be used if there are no available paths +1030 /// +1031 /// If the max_idle_timeout is specified the path will be kept alive so that it does not +1032 /// expire. +1033 Backup, +1034} +1035 +1036/// Application events about paths +1037#[derive(Debug, Clone, PartialEq, Eq)] +1038pub enum PathEvent { +1039 /// A new path has been opened +1040 Opened { +1041 /// Which path is now open +1042 id: PathId, +1043 }, +1044 /// A path was abandoned and is no longer usable. +1045 /// +1046 /// This event will always be followed by [`Self::Discarded`] after some time. +1047 Abandoned { +1048 /// With path was abandoned. +1049 id: PathId, +1050 /// Reason why this path was abandoned. +1051 reason: PathAbandonReason, +1052 }, +1053 /// A path was discarded and all remaining state for it has been removed. +1054 /// +1055 /// This event is the last event for a path, and is always emitted after [`Self::Abandoned`]. +1056 Discarded { +1057 /// Which path had its state dropped +1058 id: PathId, +1059 /// The final path stats, they are no longer available via [`Connection::stats`] +1060 /// +1061 /// [`Connection::stats`]: super::Connection::stats +1062 path_stats: Box<PathStats>, +1063 }, +1064 /// The remote changed the status of the path +1065 /// +1066 /// The local status is not changed because of this event. It is up to the application +1067 /// to update the local status, which is used for packet scheduling, when the remote +1068 /// changes the status. +1069 RemoteStatus { +1070 /// Path which has changed status +1071 id: PathId, +1072 /// The new status set by the remote +1073 status: PathStatus, +1074 }, +1075 /// Received an observation of our external address from the peer. +1076 ObservedAddr { +1077 /// Path over which the observed address was reported, [`PathId::ZERO`] when multipath is +1078 /// not negotiated +1079 id: PathId, +1080 /// The address observed by the remote over this path +1081 addr: SocketAddr, +1082 }, +1083} +1084 +1085/// Reason for why a path was abandoned. +1086#[derive(Debug, Clone, Eq, PartialEq)] +1087pub enum PathAbandonReason { +1088 /// The path was closed locally by the application. +1089 ApplicationClosed { +1090 /// The error code to be sent with the abandon frame. +1091 error_code: VarInt, +1092 }, +1093 /// We didn't receive a path response in time after opening this path. +1094 ValidationFailed, +1095 /// We didn't receive any data from the remote within the path's idle timeout. +1096 TimedOut, +1097 /// The path became unusable after a local network change. +1098 UnusableAfterNetworkChange, +1099 /// The path was opened in a NAT traversal round which was terminated. +1100 NatTraversalRoundEnded, +1101 /// The remote closed the path. +1102 RemoteAbandoned { +1103 /// The error that was sent with the abandon frame. +1104 error_code: VarInt, +1105 }, +1106} +1107 +1108impl PathAbandonReason { +1109 /// Whether this abandon was initiated by the remote peer. +1110 pub(crate) fn is_remote(&self) -> bool { +1111 matches!(self, Self::RemoteAbandoned { .. }) +1112 } +1113 +1114 /// Returns the error code to send with a PATH_ABANDON frame. +1115 pub(crate) fn error_code(&self) -> TransportErrorCode { +1116 match self { +1117 Self::ApplicationClosed { error_code } => (*error_code).into(), +1118 Self::NatTraversalRoundEnded => TransportErrorCode::APPLICATION_ABANDON_PATH, +1119 Self::ValidationFailed | Self::TimedOut | Self::UnusableAfterNetworkChange => { +1120 TransportErrorCode::PATH_UNSTABLE_OR_POOR +1121 } +1122 Self::RemoteAbandoned { error_code } => (*error_code).into(), +1123 } +1124 } +1125} +1126 +1127/// Error from setting path status +1128#[derive(Debug, Error, Clone, PartialEq, Eq)] +1129pub enum SetPathStatusError { +1130 /// Error indicating that a path has not been opened or has already been abandoned +1131 #[error("closed path")] +1132 ClosedPath, +1133 /// Error indicating that this operation requires multipath to be negotiated whereas it hasn't been +1134 #[error("multipath not negotiated")] +1135 MultipathNotNegotiated, +1136} +1137 +1138/// Error indicating that a path has not been opened or has already been abandoned +1139#[derive(Debug, Default, Error, Clone, PartialEq, Eq)] +1140#[error("closed path")] +1141pub struct ClosedPath { +1142 pub(super) _private: (), +1143} +1144 +1145#[cfg(test)] +1146mod tests { +1147 use super::*; +1148 +1149 #[test] +1150 fn test_path_id_saturating_add() { +1151 // add within range behaves normally +1152 let large: PathId = u16::MAX.into(); +1153 let next = u32::from(u16::MAX) + 1; +1154 assert_eq!(large.saturating_add(1u8), PathId::from(next)); +1155 +1156 // outside range saturates +1157 assert_eq!(PathId::MAX.saturating_add(1u8), PathId::MAX) +1158 } +1159} \ No newline at end of file diff --git a/pr/617/docs/src/noq_proto/connection/spaces.rs.html b/pr/617/docs/src/noq_proto/connection/spaces.rs.html index 5d65aedc1..3f3a3fa55 100644 --- a/pr/617/docs/src/noq_proto/connection/spaces.rs.html +++ b/pr/617/docs/src/noq_proto/connection/spaces.rs.html @@ -2,1543 +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 /// Probes are sent similarly to normal packets when an expected ACK has not arrived. We never -89 /// deem a packet lost until we receive an ACK that should have included it, but if a trailing -90 /// run of packets (or their ACKs) are lost, this might not happen in a timely fashion. We send -91 /// probe packets to force an ACK, and exempt them from congestion control to prevent a deadlock -92 /// when the congestion window is filled with lost tail packets. -93 /// -94 /// We prefer to send new data, to make the most efficient use of bandwidth. If there's no data -95 /// waiting to be sent, then we retransmit in-flight data to reduce odds of loss. If there's no -96 /// in-flight data either, we're probably a client guarding against a handshake -97 /// anti-amplification deadlock and we just make something up. -98 pub(super) fn queue_tail_loss_probe( -99 &mut self, -100 path_id: PathId, -101 request_immediate_ack: bool, -102 streams: &StreamsState, -103 ) { -104 if request_immediate_ack { -105 // The probe should be ACKed without delay (should only be used in the Data space and -106 // when the peer supports the acknowledgement frequency extension) -107 self.for_path(path_id).immediate_ack_pending = true; -108 } -109 -110 // We prefer to send new data to make most efficient use of bandwidth. -111 if !self.pending.is_empty(streams) { -112 // There's real data to send here, no need to make something up -113 return; -114 } -115 -116 // Retransmit data from the oldest in-flight data from any path -117 for packet in self -118 .number_spaces -119 .values_mut() -120 .flat_map(|s| s.sent_packets.values_mut()) -121 { -122 if !packet.retransmits.is_empty(streams) { -123 // Remove retransmitted data from the old packet so we don't end up retransmitting -124 // it *again* even if the copy we're sending now gets acknowledged. -125 self.pending |= mem::take(&mut packet.retransmits); -126 return; -127 } -128 } -129 -130 // Nothing new to send and nothing to retransmit, so fall back on a ping. This should only -131 // happen in rare cases during the handshake when the server becomes blocked by -132 // anti-amplification. -133 if !self.for_path(path_id).immediate_ack_pending { -134 self.for_path(path_id).ping_pending = true; -135 } -136 } -137 -138 /// Whether there is anything to send in this space -139 /// -140 /// For the data space [`Connection::can_send_1rtt`] also needs to be consulted. Prefer -141 /// to use [`Connection::space_can_send`] which handles this. -142 /// -143 /// [`Connection::can_send_1rtt`]: super::Connection::can_send_1rtt -144 /// [`Connection::space_can_send`]: super::Connection::space_can_send -145 pub(super) fn can_send(&self, path_id: PathId, streams: &StreamsState) -> SendableFrames { -146 let acks = self -147 .number_spaces -148 .values() -149 .any(|pns| pns.pending_acks.can_send()); -150 let space_specific = self -151 .number_spaces -152 .get(&path_id) -153 .is_some_and(|s| s.ping_pending || s.immediate_ack_pending); -154 let other = !self.pending.is_empty(streams); -155 SendableFrames { -156 acks, -157 close: false, -158 space_specific, -159 other, -160 } -161 } -162} -163 -164impl Index<SpaceId> for [PacketSpace; 3] { -165 type Output = PacketSpace; -166 fn index(&self, space: SpaceId) -> &PacketSpace { -167 &self.as_ref()[space as usize] -168 } -169} -170 -171impl IndexMut<SpaceId> for [PacketSpace; 3] { -172 fn index_mut(&mut self, space: SpaceId) -> &mut PacketSpace { -173 &mut self.as_mut()[space as usize] -174 } -175} -176 -177/// The three QUIC packet number space kinds -178/// -179/// Unlike [`SpaceId`], this always has exactly three variants — it represents the -180/// encryption level / space kind, not a specific packet number space identity. -181#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -182pub(crate) enum SpaceKind { -183 /// Initial packets (client and server). -184 Initial = 0, -185 /// Handshake packets. -186 Handshake = 1, -187 /// Data (1-RTT and 0-RTT) -188 Data = 2, -189} -190 -191impl SpaceKind { -192 /// Returns the encryption level for this space kind. -193 pub(crate) fn encryption_level(self) -> super::EncryptionLevel { -194 match self { -195 Self::Initial => super::EncryptionLevel::Initial, -196 Self::Handshake => super::EncryptionLevel::Handshake, -197 Self::Data => super::EncryptionLevel::OneRtt, -198 } -199 } -200} -201 -202impl Index<SpaceKind> for [PacketSpace; 3] { -203 type Output = PacketSpace; -204 fn index(&self, space: SpaceKind) -> &PacketSpace { -205 &self.as_ref()[space as usize] -206 } -207} -208 -209impl IndexMut<SpaceKind> for [PacketSpace; 3] { -210 fn index_mut(&mut self, space: SpaceKind) -> &mut PacketSpace { -211 &mut self.as_mut()[space as usize] -212 } -213} -214 -215/// The state of a single packet number space. -216/// -217/// In RFC9000 there are 3 packet number spaces: Initial, Handshake and Data. In QUIC -218/// Multipath there are multiple packet number spaces for Data, each identified by a -219/// [`PathId`]. -220/// -221/// This contains the state for a packet number space which is not specific to the 4-tuple -222/// this space is currently using. The 4-tuple specific state, like congestion controller, -223/// pacing, ECN, MTU etc, is stored in [`PathData`]. -224/// -225/// Note that the `Initial`, `Handshake` and `Data(PathId(0))` space all share the same -226/// [`PathData`]. -227/// -228/// You should access this via [`PacketSpace::for_path`]. -229/// -230/// [`PathData`]: super::paths::PathData -231pub(super) struct PacketNumberSpace { -232 /// Highest received packet number, if any -233 pub(super) largest_received_packet_number: Option<u64>, -234 /// The packet number of the next packet that will be sent, if any. In the Data space, the -235 /// packet number stored here is sometimes skipped by [`PacketNumberFilter`] logic. -236 pub(super) next_packet_number: u64, -237 /// The largest packet number the remote peer acknowledged in an ACK frame. -238 pub(super) largest_acked_packet_pn: Option<u64>, -239 pub(super) largest_acked_packet_send_time: Instant, -240 /// The highest-numbered ACK-eliciting packet we've sent -241 pub(super) largest_ack_eliciting_sent: u64, -242 /// Number of packets in `sent_packets` with numbers above `largest_ack_eliciting_sent` -243 pub(super) unacked_non_ack_eliciting_tail: u64, -244 /// Transmitted but not acked -245 // We use a BTreeMap here so we can efficiently query by range on ACK and for loss detection -246 pub(super) sent_packets: SortedIndexBuffer<SentPacket>, -247 /// Packets that were deemed lost -248 // Older packets are regularly removed in `Connection::drain_lost_packets`. -249 pub(super) lost_packets: SortedIndexBuffer<LostPacket>, -250 /// Number of explicit congestion notification codepoints seen on incoming packets -251 pub(super) ecn_counters: frame::EcnCounts, -252 /// Recent ECN counters sent by the peer in ACK frames -253 /// -254 /// Updated (and inspected) whenever we receive an ACK with a new highest acked packet -255 /// number. Stored per-space to simplify verification, which would otherwise have difficulty -256 /// distinguishing between ECN bleaching and counts having been updated by a near-simultaneous -257 /// ACK already processed in another space. -258 pub(super) ecn_feedback: frame::EcnCounts, -259 /// A PING frame needs to be sent on this path -260 pub(super) ping_pending: bool, -261 /// An IMMEDIATE_ACK (draft-ietf-quic-ack-frequency) frame needs to be sent on this path -262 pub(super) immediate_ack_pending: bool, -263 /// Packet deduplicator -264 pub(super) dedup: Dedup, -265 /// Packet numbers to acknowledge -266 pub(super) pending_acks: PendingAcks, -267 -268 // -269 // Loss Detection -270 // -271 /// The time the most recently sent retransmittable packet was sent. -272 pub(super) time_of_last_ack_eliciting_packet: Option<Instant>, -273 /// Earliest time when we might declare a packet lost. -274 /// -275 /// The time at which the earliest sent packet in this space will be considered lost -276 /// based on exceeding the reordering window in time. Only set for packets numbered -277 /// prior to a packet that has been acknowledged. -278 pub(super) loss_time: Option<Instant>, -279 /// Number of tail loss probes to send -280 pub(super) loss_probes: u32, -281 -282 /// Packet numbers to skip, only used in the data package space. -283 pn_filter: Option<PacketNumberFilter>, -284} -285 -286impl PacketNumberSpace { -287 pub(super) fn new(now: Instant, space: SpaceId, rng: &mut (impl CryptoRng + ?Sized)) -> Self { -288 let pn_filter = match space { -289 SpaceId::Initial | SpaceId::Handshake => None, -290 SpaceId::Data => Some(PacketNumberFilter::new(rng)), -291 }; -292 Self { -293 largest_received_packet_number: None, -294 next_packet_number: 0, -295 largest_acked_packet_pn: None, -296 largest_acked_packet_send_time: now, -297 largest_ack_eliciting_sent: 0, -298 unacked_non_ack_eliciting_tail: 0, -299 sent_packets: SortedIndexBuffer::new(), -300 lost_packets: SortedIndexBuffer::new(), -301 ecn_counters: frame::EcnCounts::ZERO, -302 ecn_feedback: frame::EcnCounts::ZERO, -303 ping_pending: false, -304 immediate_ack_pending: false, -305 dedup: Default::default(), -306 pending_acks: PendingAcks::new(), -307 time_of_last_ack_eliciting_packet: None, -308 loss_time: None, -309 loss_probes: 0, -310 pn_filter, -311 } -312 } -313 -314 #[cfg(test)] -315 fn new_deterministic(now: Instant, space: SpaceId) -> Self { -316 let pn_filter = match space { -317 SpaceId::Initial | SpaceId::Handshake => None, -318 SpaceId::Data => Some(PacketNumberFilter::disabled()), -319 }; -320 Self { -321 largest_received_packet_number: None, -322 next_packet_number: 0, -323 largest_acked_packet_pn: None, -324 largest_acked_packet_send_time: now, -325 largest_ack_eliciting_sent: 0, -326 unacked_non_ack_eliciting_tail: 0, -327 sent_packets: SortedIndexBuffer::new(), -328 lost_packets: SortedIndexBuffer::new(), -329 ecn_counters: frame::EcnCounts::ZERO, -330 ecn_feedback: frame::EcnCounts::ZERO, -331 ping_pending: false, -332 immediate_ack_pending: false, -333 dedup: Default::default(), -334 pending_acks: PendingAcks::new(), -335 time_of_last_ack_eliciting_packet: None, -336 loss_time: None, -337 loss_probes: 0, -338 pn_filter, -339 } -340 } -341 -342 /// Get the next outgoing packet number in this space -343 /// -344 /// In the Data space, the connection's [`PacketNumberFilter`] must be used rather than calling -345 /// this directly. -346 pub(super) fn get_tx_number(&mut self, rng: &mut (impl CryptoRng + ?Sized)) -> u64 { -347 // TODO: Handle packet number overflow gracefully -348 assert!(self.next_packet_number < 2u64.pow(62)); -349 let mut pn = self.next_packet_number; -350 self.next_packet_number += 1; -351 -352 // Skip this number if the filter says so, only enabled in the data space -353 if let Some(ref mut filter) = self.pn_filter -354 && filter.skip_pn(pn, rng) -355 { -356 pn = self.next_packet_number; -357 self.next_packet_number += 1; -358 } -359 pn -360 } -361 -362 pub(super) fn peek_tx_number(&mut self) -> u64 { -363 let pn = self.next_packet_number; -364 if let Some(ref filter) = self.pn_filter -365 && pn == filter.next_skipped_packet_number -366 { -367 return pn + 1; -368 } -369 pn -370 } -371 -372 /// Checks whether a skipped packet number was ACKed. -373 pub(super) fn check_ack(&self, range: std::ops::Range<u64>) -> Result<(), TransportError> { -374 if let Some(ref filter) = self.pn_filter -375 && filter -376 .prev_skipped_packet_number -377 .is_some_and(|pn| range.contains(&pn)) -378 { -379 return Err(TransportError::PROTOCOL_VIOLATION("unsent packet acked")); -380 } -381 Ok(()) -382 } -383 -384 /// Verifies sanity of an ECN block and returns whether congestion was encountered. -385 pub(super) fn detect_ecn( -386 &mut self, -387 newly_acked: u64, -388 ecn: frame::EcnCounts, -389 ) -> Result<bool, &'static str> { -390 let ect0_increase = ecn -391 .ect0 -392 .checked_sub(self.ecn_feedback.ect0) -393 .ok_or("peer ECT(0) count regression")?; -394 let ect1_increase = ecn -395 .ect1 -396 .checked_sub(self.ecn_feedback.ect1) -397 .ok_or("peer ECT(1) count regression")?; -398 let ce_increase = ecn -399 .ce -400 .checked_sub(self.ecn_feedback.ce) -401 .ok_or("peer CE count regression")?; -402 let total_increase = ect0_increase + ect1_increase + ce_increase; -403 if total_increase < newly_acked { -404 return Err("ECN bleaching"); -405 } -406 if (ect0_increase + ce_increase) < newly_acked || ect1_increase != 0 { -407 return Err("ECN corruption"); -408 } -409 // If total_increase > newly_acked (which happens when ACKs are lost), this is required by -410 // the draft so that long-term drift does not occur. If =, then the only question is whether -411 // to count CE packets as CE or ECT0. Recording them as CE is more consistent and keeps the -412 // congestion check obvious. -413 self.ecn_feedback = ecn; -414 Ok(ce_increase != 0) -415 } -416 -417 /// Stop tracking sent packet `number`, and return what we knew about it -418 pub(super) fn take(&mut self, number: u64) -> Option<SentPacket> { -419 let packet = self.sent_packets.remove(number)?; -420 if !packet.ack_eliciting && number > self.largest_ack_eliciting_sent { -421 self.unacked_non_ack_eliciting_tail = -422 self.unacked_non_ack_eliciting_tail.checked_sub(1).unwrap(); -423 } -424 Some(packet) -425 } -426 -427 /// May return a packet that should be forgotten -428 pub(super) fn sent(&mut self, number: u64, packet: SentPacket) -> Option<SentPacket> { -429 // Retain state for at most this many non-ACK-eliciting packets sent after the most recently -430 // sent ACK-eliciting packet. We're never guaranteed to receive an ACK for those, and we -431 // can't judge them as lost without an ACK, so to limit memory in applications which receive -432 // packets but don't send ACK-eliciting data for long periods use we must eventually start -433 // forgetting about them, although it might also be reasonable to just kill the connection -434 // due to weird peer behavior. -435 const MAX_UNACKED_NON_ACK_ELICTING_TAIL: u64 = 1_000; -436 -437 let mut forgotten = None; -438 if packet.ack_eliciting { -439 self.unacked_non_ack_eliciting_tail = 0; -440 self.largest_ack_eliciting_sent = number; -441 } else if self.unacked_non_ack_eliciting_tail > MAX_UNACKED_NON_ACK_ELICTING_TAIL { -442 let oldest_after_ack_eliciting = self -443 .sent_packets -444 .keys_range(( -445 Bound::Excluded(self.largest_ack_eliciting_sent), -446 Bound::Unbounded, -447 )) -448 .next() -449 .unwrap(); -450 // Per https://www.rfc-editor.org/rfc/rfc9000.html#name-frames-and-frame-types, -451 // non-ACK-eliciting packets must only contain PADDING, ACK, and CONNECTION_CLOSE -452 // frames, which require no special handling on ACK or loss beyond removal from -453 // in-flight counters if padded. -454 let packet = self -455 .sent_packets -456 .remove(oldest_after_ack_eliciting) -457 .unwrap(); -458 debug_assert!(!packet.ack_eliciting); -459 forgotten = Some(packet); -460 } else { -461 self.unacked_non_ack_eliciting_tail += 1; -462 } -463 -464 self.sent_packets.insert(number, packet); -465 forgotten -466 } -467 -468 /// Whether any congestion-controlled packets in this space are not yet acknowledged or lost -469 pub(super) fn has_in_flight(&self) -> bool { -470 // The number of non-congestion-controlled (i.e. size == 0) packets in flight at a time -471 // should be small, since otherwise congestion control wouldn't be effective. Therefore, -472 // this shouldn't need to visit many packets before finishing one way or another. -473 self.sent_packets.values().any(|x| x.size != 0) -474 } -475} -476 -477/// Represents one or more packets subject to retransmission -478#[derive(Debug, Clone)] -479pub(super) struct SentPacket { -480 /// [`PathData::generation`](super::PathData::generation) of the path on which this packet was sent -481 pub(super) path_generation: u64, -482 /// The time the packet was sent. -483 pub(super) time_sent: Instant, -484 /// The number of bytes sent in the packet, not including UDP or IP overhead, but including QUIC -485 /// framing overhead. Zero if this packet is not counted towards congestion control, i.e. not an -486 /// "in flight" packet. -487 pub(super) size: u16, -488 /// Whether an acknowledgement is expected directly in response to this packet. -489 pub(super) ack_eliciting: bool, -490 /// The largest packet number acknowledged by this packet -491 pub(super) largest_acked: FxHashMap<PathId, u64>, -492 /// Data which needs to be retransmitted in case the packet is lost. -493 /// The data is boxed to minimize `SentPacket` size for the typical case of -494 /// packets only containing ACKs and STREAM frames. -495 pub(super) retransmits: ThinRetransmits, -496 /// Metadata for stream frames in a packet -497 /// -498 /// The actual application data is stored with the stream state. -499 pub(super) stream_frames: frame::StreamMetaVec, -500} -501 -502/// Represents one or more packets that are deemed lost. -503#[derive(Debug)] -504pub(super) struct LostPacket { -505 /// The time the packet was sent. -506 pub(super) time_sent: Instant, -507} -508 -509/// Retransmittable data queue -510#[allow(unreachable_pub)] // fuzzing only -511#[derive(Debug, Default, Clone)] -512pub struct Retransmits { -513 pub(super) max_data: bool, -514 pub(super) max_stream_id: [bool; 2], -515 pub(super) streams_blocked: [bool; 2], -516 pub(super) reset_stream: Vec<(StreamId, VarInt)>, -517 pub(super) stop_sending: Vec<frame::StopSending>, -518 pub(super) max_stream_data: FxHashSet<StreamId>, -519 pub(super) crypto: VecDeque<frame::Crypto>, -520 pub(super) new_cids: PendingNewCids, -521 pub(super) retire_cids: Vec<(PathId, u64)>, -522 pub(super) ack_frequency: bool, -523 pub(super) handshake_done: bool, -524 pub(super) observed_addr: bool, -525 /// Whether we should inform the peer we will allow higher [`PathId`]s. -526 pub(super) max_path_id: bool, -527 /// Whether we should inform the peer that their max [`PathId`] is blocking our attempt to open -528 /// new paths. -529 // TODO(@divma): we need logic to prevent sending this more than once after being ack-d once -530 pub(super) paths_blocked: bool, -531 /// For each enqueued NEW_TOKEN frame, a copy of the path's remote address -532 /// -533 /// There are 2 reasons this is unusual: -534 /// -535 /// - If the path changes, NEW_TOKEN frames bound for the old path are not retransmitted on the -536 /// new path. That is why this field stores the remote address: so that ones for old paths -537 /// can be filtered out. -538 /// - If a token is lost, a new randomly generated token is re-transmitted, rather than the -539 /// original. This is so that if both transmissions are received, the client won't risk -540 /// sending the same token twice. That is why this field does _not_ store any actual token. -541 /// -542 /// It is true that a QUIC endpoint will only want to effectively have NEW_TOKEN frames -543 /// enqueued for its current path at a given point in time. Based on that, we could conceivably -544 /// change this from a vector to an `Option<(FourTuple, usize)>` or just a `usize` or -545 /// something. However, due to the architecture of noq, it is considerably simpler to not do -546 /// that; consider what such a change would mean for implementing `BitOrAssign` on Self. -547 pub(super) new_tokens: Vec<FourTuple>, -548 /// Paths which need to be abandoned -549 pub(super) path_abandon: BTreeMap<PathId, TransportErrorCode>, -550 /// If a [`frame::PathStatusAvailable`] and [`frame::PathStatusBackup`] need to be sent for a path -551 pub(super) path_status: BTreeSet<PathId>, -552 /// If a PATH_CIDS_BLOCKED frame needs to be sent for a path -553 pub(super) path_cids_blocked: BTreeSet<PathId>, -554 -555 // Nat traversal data -556 /// Addresses to report in `ADD_ADDRESS` frames -557 pub(super) add_address: BTreeSet<AddAddress>, -558 /// Address IDs to remove in `REMOVE_ADDRESS` frames -559 pub(super) remove_address: BTreeSet<RemoveAddress>, -560 /// Round and local addresses to advertise in `REACH_OUT` frames -561 pub(super) reach_out: Option<(VarInt, FxHashSet<(IpAddr, u16)>)>, -562} -563 -564impl Retransmits { -565 pub(super) fn is_empty(&self, streams: &StreamsState) -> bool { -566 let Self { -567 max_data, -568 max_stream_id, -569 streams_blocked, -570 reset_stream, -571 stop_sending, -572 max_stream_data, -573 crypto, -574 new_cids, -575 retire_cids, -576 ack_frequency, -577 handshake_done, -578 observed_addr, -579 max_path_id, -580 paths_blocked, -581 new_tokens, -582 path_abandon, -583 path_status, -584 path_cids_blocked, -585 add_address, -586 remove_address, -587 reach_out, -588 } = &self; -589 !max_data -590 && !max_stream_id.iter().any(|x| *x) -591 && !streams_blocked.iter().any(|x| *x) -592 && reset_stream.is_empty() -593 && stop_sending.is_empty() -594 && max_stream_data -595 .iter() -596 .all(|&id| !streams.can_send_flow_control(id)) -597 && crypto.is_empty() -598 && new_cids.is_empty() -599 && retire_cids.is_empty() -600 && !ack_frequency -601 && !handshake_done -602 && !observed_addr -603 && !max_path_id -604 && !paths_blocked -605 && new_tokens.is_empty() -606 && path_abandon.is_empty() -607 && path_status.is_empty() -608 && path_cids_blocked.is_empty() -609 && add_address.is_empty() -610 && remove_address.is_empty() -611 && reach_out.is_none() -612 } -613} -614 -615impl ::std::ops::BitOrAssign for Retransmits { -616 fn bitor_assign(&mut self, rhs: Self) { -617 let Self { -618 max_data, -619 max_stream_id, -620 streams_blocked, -621 reset_stream, -622 stop_sending, -623 max_stream_data, -624 crypto, -625 new_cids, -626 retire_cids, -627 ack_frequency, -628 handshake_done, -629 observed_addr, -630 max_path_id, -631 paths_blocked, -632 new_tokens, -633 mut path_abandon, -634 mut path_status, -635 mut path_cids_blocked, -636 add_address, -637 remove_address, -638 reach_out, -639 } = rhs; -640 -641 // We reduce in-stream head-of-line blocking by queueing retransmits before other data for -642 // STREAM and CRYPTO frames. -643 self.max_data |= max_data; -644 for dir in Dir::iter() { -645 self.max_stream_id[dir as usize] |= max_stream_id[dir as usize]; -646 self.streams_blocked[dir as usize] |= streams_blocked[dir as usize]; -647 } -648 self.reset_stream.extend_from_slice(&reset_stream); -649 self.stop_sending.extend_from_slice(&stop_sending); -650 self.max_stream_data.extend(&max_stream_data); -651 for crypto in crypto.into_iter().rev() { -652 self.crypto.push_front(crypto); -653 } -654 self.new_cids.extend(&new_cids); -655 self.retire_cids.extend(retire_cids); -656 self.ack_frequency |= ack_frequency; -657 self.handshake_done |= handshake_done; -658 self.observed_addr |= observed_addr; -659 self.max_path_id |= max_path_id; -660 self.paths_blocked |= paths_blocked; -661 self.new_tokens.extend_from_slice(&new_tokens); -662 self.path_abandon.append(&mut path_abandon); -663 self.path_status.append(&mut path_status); -664 self.path_cids_blocked.append(&mut path_cids_blocked); -665 self.add_address.extend(add_address.iter().copied()); -666 self.remove_address.extend(remove_address.iter().copied()); -667 if let Some((rhs_round, rhs_addrs)) = reach_out { -668 match self.reach_out.as_mut() { -669 // Use RHS if there is no recorded round. -670 None => self.reach_out = Some((rhs_round, rhs_addrs)), -671 // Use RHS if newer. -672 Some((lhs_round, _lhs_addrs)) if rhs_round > *lhs_round => { -673 self.reach_out = Some((rhs_round, rhs_addrs)); -674 } -675 // If both rounds are the same, merge them. -676 Some((lhs_round, lhs_addrs)) if rhs_round == *lhs_round => { -677 lhs_addrs.extend(rhs_addrs); -678 } -679 // LHS round is newer, ignore RHS -680 Some(_) => {} -681 } -682 } -683 } -684} -685 -686impl ::std::ops::BitOrAssign<ThinRetransmits> for Retransmits { -687 fn bitor_assign(&mut self, rhs: ThinRetransmits) { -688 let ThinRetransmits { retransmits } = rhs; -689 if let Some(retransmits) = retransmits { -690 self.bitor_assign(*retransmits) -691 } -692 } -693} -694 -695impl ::std::iter::FromIterator<Self> for Retransmits { -696 fn from_iter<T>(iter: T) -> Self -697 where -698 T: IntoIterator<Item = Self>, -699 { -700 let mut result = Self::default(); -701 for packet in iter { -702 result |= packet; -703 } -704 result -705 } -706} -707 -708/// The queue of new CIDs to be transmitted to the peer. -709/// -710/// This queue is always sorted, so that popping off the last item is always the lowest -711/// sequence number of the lowest path ID. Which is the CID you want to be issued next. -712/// -713/// This is but a newtype over a `Vec` to enforce the sorted invariant. -714#[derive(Clone, Debug, Default)] -715pub(super) struct PendingNewCids { -716 /// The CIDs themselves. -717 cids: Vec<IssuedCid>, -718 /// Whether [`Self::cids`] is sorted or not. -719 sorted: bool, -720} +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 +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 IndexMut<SpaceId> for [PacketSpace; 3] { +171 fn index_mut(&mut self, space: SpaceId) -> &mut PacketSpace { +172 &mut self.as_mut()[space as usize] +173 } +174} +175 +176/// The three QUIC packet number space kinds +177/// +178/// Unlike [`SpaceId`], this always has exactly three variants — it represents the +179/// encryption level / space kind, not a specific packet number space identity. +180#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +181pub(crate) enum SpaceKind { +182 /// Initial packets (client and server). +183 Initial = 0, +184 /// Handshake packets. +185 Handshake = 1, +186 /// Data (1-RTT and 0-RTT) +187 Data = 2, +188} +189 +190impl SpaceKind { +191 /// Returns the encryption level for this space kind. +192 pub(crate) fn encryption_level(self) -> super::EncryptionLevel { +193 match self { +194 Self::Initial => super::EncryptionLevel::Initial, +195 Self::Handshake => super::EncryptionLevel::Handshake, +196 Self::Data => super::EncryptionLevel::OneRtt, +197 } +198 } +199} +200 +201impl Index<SpaceKind> for [PacketSpace; 3] { +202 type Output = PacketSpace; +203 fn index(&self, space: SpaceKind) -> &PacketSpace { +204 &self.as_ref()[space as usize] +205 } +206} +207 +208impl IndexMut<SpaceKind> for [PacketSpace; 3] { +209 fn index_mut(&mut self, space: SpaceKind) -> &mut PacketSpace { +210 &mut self.as_mut()[space as usize] +211 } +212} +213 +214/// The state of a single packet number space. +215/// +216/// In RFC9000 there are 3 packet number spaces: Initial, Handshake and Data. In QUIC +217/// Multipath there are multiple packet number spaces for Data, each identified by a +218/// [`PathId`]. +219/// +220/// This contains the state for a packet number space which is not specific to the 4-tuple +221/// this space is currently using. The 4-tuple specific state, like congestion controller, +222/// pacing, ECN, MTU etc, is stored in [`PathData`]. +223/// +224/// Note that the `Initial`, `Handshake` and `Data(PathId(0))` space all share the same +225/// [`PathData`]. +226/// +227/// You should access this via [`PacketSpace::for_path`]. +228/// +229/// [`PathData`]: super::paths::PathData +230pub(super) struct PacketNumberSpace { +231 /// Highest received packet number, if any +232 pub(super) largest_received_packet_number: Option<u64>, +233 /// The packet number of the next packet that will be sent, if any. In the Data space, the +234 /// packet number stored here is sometimes skipped by [`PacketNumberFilter`] logic. +235 pub(super) next_packet_number: u64, +236 /// The largest packet number the remote peer acknowledged in an ACK frame. +237 pub(super) largest_acked_packet_pn: Option<u64>, +238 pub(super) largest_acked_packet_send_time: Instant, +239 /// The highest-numbered ACK-eliciting packet we've sent +240 pub(super) largest_ack_eliciting_sent: u64, +241 /// Number of packets in `sent_packets` with numbers above `largest_ack_eliciting_sent` +242 pub(super) unacked_non_ack_eliciting_tail: u64, +243 /// Transmitted but not acked +244 // We use a BTreeMap here so we can efficiently query by range on ACK and for loss detection +245 pub(super) sent_packets: SortedIndexBuffer<SentPacket>, +246 /// Packets that were deemed lost +247 // Older packets are regularly removed in `Connection::drain_lost_packets`. +248 pub(super) lost_packets: SortedIndexBuffer<LostPacket>, +249 /// Number of explicit congestion notification codepoints seen on incoming packets +250 pub(super) ecn_counters: frame::EcnCounts, +251 /// Recent ECN counters sent by the peer in ACK frames +252 /// +253 /// Updated (and inspected) whenever we receive an ACK with a new highest acked packet +254 /// number. Stored per-space to simplify verification, which would otherwise have difficulty +255 /// distinguishing between ECN bleaching and counts having been updated by a near-simultaneous +256 /// ACK already processed in another space. +257 pub(super) ecn_feedback: frame::EcnCounts, +258 /// A PING frame needs to be sent on this path +259 pub(super) ping_pending: bool, +260 /// An IMMEDIATE_ACK (draft-ietf-quic-ack-frequency) frame needs to be sent on this path +261 pub(super) immediate_ack_pending: bool, +262 /// Packet deduplicator +263 pub(super) dedup: Dedup, +264 /// Packet numbers to acknowledge +265 pub(super) pending_acks: PendingAcks, +266 +267 // +268 // Loss Detection +269 // +270 /// The time the most recently sent retransmittable packet was sent. +271 pub(super) time_of_last_ack_eliciting_packet: Option<Instant>, +272 /// Earliest time when we might declare a packet lost. +273 /// +274 /// The time at which the earliest sent packet in this space will be considered lost +275 /// based on exceeding the reordering window in time. Only set for packets numbered +276 /// prior to a packet that has been acknowledged. +277 pub(super) loss_time: Option<Instant>, +278 /// Number of tail loss probes to send +279 pub(super) loss_probes: u32, +280 +281 /// Packet numbers to skip, only used in the data package space. +282 pn_filter: Option<PacketNumberFilter>, +283} +284 +285impl PacketNumberSpace { +286 pub(super) fn new(now: Instant, space: SpaceId, rng: &mut (impl CryptoRng + ?Sized)) -> Self { +287 let pn_filter = match space { +288 SpaceId::Initial | SpaceId::Handshake => None, +289 SpaceId::Data => Some(PacketNumberFilter::new(rng)), +290 }; +291 Self { +292 largest_received_packet_number: None, +293 next_packet_number: 0, +294 largest_acked_packet_pn: None, +295 largest_acked_packet_send_time: now, +296 largest_ack_eliciting_sent: 0, +297 unacked_non_ack_eliciting_tail: 0, +298 sent_packets: SortedIndexBuffer::new(), +299 lost_packets: SortedIndexBuffer::new(), +300 ecn_counters: frame::EcnCounts::ZERO, +301 ecn_feedback: frame::EcnCounts::ZERO, +302 ping_pending: false, +303 immediate_ack_pending: false, +304 dedup: Default::default(), +305 pending_acks: PendingAcks::new(), +306 time_of_last_ack_eliciting_packet: None, +307 loss_time: None, +308 loss_probes: 0, +309 pn_filter, +310 } +311 } +312 +313 #[cfg(test)] +314 fn new_deterministic(now: Instant, space: SpaceId) -> Self { +315 let pn_filter = match space { +316 SpaceId::Initial | SpaceId::Handshake => None, +317 SpaceId::Data => Some(PacketNumberFilter::disabled()), +318 }; +319 Self { +320 largest_received_packet_number: None, +321 next_packet_number: 0, +322 largest_acked_packet_pn: None, +323 largest_acked_packet_send_time: now, +324 largest_ack_eliciting_sent: 0, +325 unacked_non_ack_eliciting_tail: 0, +326 sent_packets: SortedIndexBuffer::new(), +327 lost_packets: SortedIndexBuffer::new(), +328 ecn_counters: frame::EcnCounts::ZERO, +329 ecn_feedback: frame::EcnCounts::ZERO, +330 ping_pending: false, +331 immediate_ack_pending: false, +332 dedup: Default::default(), +333 pending_acks: PendingAcks::new(), +334 time_of_last_ack_eliciting_packet: None, +335 loss_time: None, +336 loss_probes: 0, +337 pn_filter, +338 } +339 } +340 +341 /// Get the next outgoing packet number in this space +342 /// +343 /// In the Data space, the connection's [`PacketNumberFilter`] must be used rather than calling +344 /// this directly. +345 pub(super) fn get_tx_number(&mut self, rng: &mut (impl CryptoRng + ?Sized)) -> u64 { +346 // TODO: Handle packet number overflow gracefully +347 assert!(self.next_packet_number < 2u64.pow(62)); +348 let mut pn = self.next_packet_number; +349 self.next_packet_number += 1; +350 +351 // Skip this number if the filter says so, only enabled in the data space +352 if let Some(ref mut filter) = self.pn_filter +353 && filter.skip_pn(pn, rng) +354 { +355 pn = self.next_packet_number; +356 self.next_packet_number += 1; +357 } +358 pn +359 } +360 +361 pub(super) fn peek_tx_number(&mut self) -> u64 { +362 let pn = self.next_packet_number; +363 if let Some(ref filter) = self.pn_filter +364 && pn == filter.next_skipped_packet_number +365 { +366 return pn + 1; +367 } +368 pn +369 } +370 +371 /// Checks whether a skipped packet number was ACKed. +372 pub(super) fn check_ack(&self, range: std::ops::Range<u64>) -> Result<(), TransportError> { +373 if let Some(ref filter) = self.pn_filter +374 && filter +375 .prev_skipped_packet_number +376 .is_some_and(|pn| range.contains(&pn)) +377 { +378 return Err(TransportError::PROTOCOL_VIOLATION("unsent packet acked")); +379 } +380 Ok(()) +381 } +382 +383 /// Verifies sanity of an ECN block and returns whether congestion was encountered. +384 pub(super) fn detect_ecn( +385 &mut self, +386 newly_acked: u64, +387 ecn: frame::EcnCounts, +388 ) -> Result<bool, &'static str> { +389 let ect0_increase = ecn +390 .ect0 +391 .checked_sub(self.ecn_feedback.ect0) +392 .ok_or("peer ECT(0) count regression")?; +393 let ect1_increase = ecn +394 .ect1 +395 .checked_sub(self.ecn_feedback.ect1) +396 .ok_or("peer ECT(1) count regression")?; +397 let ce_increase = ecn +398 .ce +399 .checked_sub(self.ecn_feedback.ce) +400 .ok_or("peer CE count regression")?; +401 let total_increase = ect0_increase + ect1_increase + ce_increase; +402 if total_increase < newly_acked { +403 return Err("ECN bleaching"); +404 } +405 if (ect0_increase + ce_increase) < newly_acked || ect1_increase != 0 { +406 return Err("ECN corruption"); +407 } +408 // If total_increase > newly_acked (which happens when ACKs are lost), this is required by +409 // the draft so that long-term drift does not occur. If =, then the only question is whether +410 // to count CE packets as CE or ECT0. Recording them as CE is more consistent and keeps the +411 // congestion check obvious. +412 self.ecn_feedback = ecn; +413 Ok(ce_increase != 0) +414 } +415 +416 /// Stop tracking sent packet `number`, and return what we knew about it +417 pub(super) fn take(&mut self, number: u64) -> Option<SentPacket> { +418 let packet = self.sent_packets.remove(number)?; +419 if !packet.ack_eliciting && number > self.largest_ack_eliciting_sent { +420 self.unacked_non_ack_eliciting_tail = +421 self.unacked_non_ack_eliciting_tail.checked_sub(1).unwrap(); +422 } +423 Some(packet) +424 } +425 +426 /// May return a packet that should be forgotten +427 pub(super) fn sent(&mut self, number: u64, packet: SentPacket) -> Option<SentPacket> { +428 // Retain state for at most this many non-ACK-eliciting packets sent after the most recently +429 // sent ACK-eliciting packet. We're never guaranteed to receive an ACK for those, and we +430 // can't judge them as lost without an ACK, so to limit memory in applications which receive +431 // packets but don't send ACK-eliciting data for long periods use we must eventually start +432 // forgetting about them, although it might also be reasonable to just kill the connection +433 // due to weird peer behavior. +434 const MAX_UNACKED_NON_ACK_ELICTING_TAIL: u64 = 1_000; +435 +436 let mut forgotten = None; +437 if packet.ack_eliciting { +438 self.unacked_non_ack_eliciting_tail = 0; +439 self.largest_ack_eliciting_sent = number; +440 } else if self.unacked_non_ack_eliciting_tail > MAX_UNACKED_NON_ACK_ELICTING_TAIL { +441 let oldest_after_ack_eliciting = self +442 .sent_packets +443 .keys_range(( +444 Bound::Excluded(self.largest_ack_eliciting_sent), +445 Bound::Unbounded, +446 )) +447 .next() +448 .unwrap(); +449 // Per https://www.rfc-editor.org/rfc/rfc9000.html#name-frames-and-frame-types, +450 // non-ACK-eliciting packets must only contain PADDING, ACK, and CONNECTION_CLOSE +451 // frames, which require no special handling on ACK or loss beyond removal from +452 // in-flight counters if padded. +453 let packet = self +454 .sent_packets +455 .remove(oldest_after_ack_eliciting) +456 .unwrap(); +457 debug_assert!(!packet.ack_eliciting); +458 forgotten = Some(packet); +459 } else { +460 self.unacked_non_ack_eliciting_tail += 1; +461 } +462 +463 self.sent_packets.insert(number, packet); +464 forgotten +465 } +466 +467 /// Whether any congestion-controlled packets in this space are not yet acknowledged or lost +468 pub(super) fn has_in_flight(&self) -> bool { +469 // The number of non-congestion-controlled (i.e. size == 0) packets in flight at a time +470 // should be small, since otherwise congestion control wouldn't be effective. Therefore, +471 // this shouldn't need to visit many packets before finishing one way or another. +472 self.sent_packets.values().any(|x| x.size != 0) +473 } +474} +475 +476/// Represents one or more packets subject to retransmission +477#[derive(Debug, Clone)] +478pub(super) struct SentPacket { +479 /// [`PathData::generation`](super::PathData::generation) of the path on which this packet was sent +480 pub(super) path_generation: u64, +481 /// The time the packet was sent. +482 pub(super) time_sent: Instant, +483 /// The number of bytes sent in the packet, not including UDP or IP overhead, but including QUIC +484 /// framing overhead. Zero if this packet is not counted towards congestion control, i.e. not an +485 /// "in flight" packet. +486 pub(super) size: u16, +487 /// Whether an acknowledgement is expected directly in response to this packet. +488 pub(super) ack_eliciting: bool, +489 /// The largest packet number acknowledged by this packet +490 pub(super) largest_acked: FxHashMap<PathId, u64>, +491 /// Data which needs to be retransmitted in case the packet is lost. +492 /// The data is boxed to minimize `SentPacket` size for the typical case of +493 /// packets only containing ACKs and STREAM frames. +494 pub(super) retransmits: ThinRetransmits, +495 /// Metadata for stream frames in a packet +496 /// +497 /// The actual application data is stored with the stream state. +498 pub(super) stream_frames: frame::StreamMetaVec, +499} +500 +501/// Represents one or more packets that are deemed lost. +502#[derive(Debug)] +503pub(super) struct LostPacket { +504 /// The time the packet was sent. +505 pub(super) time_sent: Instant, +506} +507 +508/// Retransmittable data queue +509#[allow(unreachable_pub)] // fuzzing only +510#[derive(Debug, Default, Clone)] +511pub struct Retransmits { +512 pub(super) max_data: bool, +513 pub(super) max_stream_id: [bool; 2], +514 pub(super) streams_blocked: [bool; 2], +515 pub(super) reset_stream: Vec<(StreamId, VarInt)>, +516 pub(super) stop_sending: Vec<frame::StopSending>, +517 pub(super) max_stream_data: FxHashSet<StreamId>, +518 pub(super) crypto: VecDeque<frame::Crypto>, +519 pub(super) new_cids: PendingNewCids, +520 pub(super) retire_cids: Vec<(PathId, u64)>, +521 pub(super) ack_frequency: bool, +522 pub(super) handshake_done: bool, +523 pub(super) observed_addr: bool, +524 /// Whether we should inform the peer we will allow higher [`PathId`]s. +525 pub(super) max_path_id: bool, +526 /// Whether we should inform the peer that their max [`PathId`] is blocking our attempt to open +527 /// new paths. +528 // TODO(@divma): we need logic to prevent sending this more than once after being ack-d once +529 pub(super) paths_blocked: bool, +530 /// For each enqueued NEW_TOKEN frame, a copy of the path's remote address +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 -722impl PendingNewCids { -723 /// Inserts an issued CID into the queue. -724 pub(super) fn push(&mut self, cid: IssuedCid) { -725 self.cids.push(cid); -726 self.sorted = false; -727 } -728 -729 /// Pops the next issued CID to transmit from the queue. -730 pub(super) fn pop(&mut self) -> Option<IssuedCid> { -731 if !std::mem::replace(&mut self.sorted, true) { -732 self.cids -733 .sort_by_key(|cid| cmp::Reverse((cid.path_id, cid.sequence))); -734 } -735 self.cids.pop() +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 retain<F>(&mut self, f: F) +732 where +733 F: FnMut(&IssuedCid) -> bool, +734 { +735 self.cids.retain(f); 736 } -737 -738 pub(super) fn is_empty(&self) -> bool { -739 self.cids.is_empty() -740 } -741 -742 pub(super) fn extend(&mut self, other: &Self) { -743 self.cids.extend(&other.cids); -744 self.sorted = false; -745 } -746 -747 pub(super) fn retain<F>(&mut self, f: F) -748 where -749 F: FnMut(&IssuedCid) -> bool, -750 { -751 self.cids.retain(f); -752 } -753} -754 -755/// A variant of `Retransmits` which only allocates storage when required -756#[derive(Debug, Default, Clone)] -757pub(super) struct ThinRetransmits { -758 retransmits: Option<Box<Retransmits>>, -759} -760 -761impl ThinRetransmits { -762 /// Returns `true` if no retransmits are necessary -763 pub(super) fn is_empty(&self, streams: &StreamsState) -> bool { -764 match &self.retransmits { -765 Some(retransmits) => retransmits.is_empty(streams), -766 None => true, -767 } -768 } -769 -770 /// Returns a reference to the retransmits stored in this box -771 pub(super) fn get(&self) -> Option<&Retransmits> { -772 self.retransmits.as_deref() -773 } -774 -775 /// Returns a mutable reference to the retransmits stored in this box -776 pub(super) fn get_mut(&mut self) -> Option<&mut Retransmits> { -777 self.retransmits.as_deref_mut() -778 } -779 -780 /// Returns a mutable reference to the stored retransmits -781 /// -782 /// This function will allocate a backing storage if required. -783 pub(super) fn get_or_create(&mut self) -> &mut Retransmits { -784 if self.retransmits.is_none() { -785 self.retransmits = Some(Box::default()); -786 } -787 self.retransmits.as_deref_mut().unwrap() +737} +738 +739/// Logically a Vec of REACH_OUT frames queued for transmit. +740/// +741/// This keeps track of the highest round ID and automatically drops frames with a lower +742/// round ID. +743/// +744/// The API is directly modelled on [`Vec`]. +745#[derive(Debug, Default, Clone)] +746pub(crate) struct PendingReachOutFrames { +747 /// The round ID of the REACH_OUT frames currently pending. +748 round: VarInt, +749 /// The REACH_OUT frames, always all having the same round ID. +750 frames: Vec<frame::ReachOut>, +751} +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 -791/// RFC4303-style sliding window packet number deduplicator. -792/// -793/// A contiguous bitfield, where each bit corresponds to a packet number and the rightmost bit is -794/// always set. A set bit represents a packet that has been successfully authenticated. Bits left of -795/// the window are assumed to be set. -796/// -797/// ```text -798/// ...xxxxxxxxx 1 0 -799/// ^ ^ ^ -800/// window highest next -801/// ``` -802#[derive(Debug, Default)] -803pub(super) struct Dedup { -804 window: Window, -805 /// Lowest packet number higher than all yet authenticated. -806 next: u64, -807} -808 -809/// Inner bitfield type. -810/// -811/// Because QUIC never reuses packet numbers, this only needs to be large enough to deal with -812/// packets that are reordered but still delivered in a timely manner. -813type Window = u128; -814 -815/// Number of packets tracked by `Dedup`. -816const WINDOW_SIZE: u64 = 1 + mem::size_of::<Window>() as u64 * 8; -817 -818impl Dedup { -819 /// Construct an empty window positioned at the start. -820 #[cfg(test)] -821 pub(super) fn new() -> Self { -822 Self { window: 0, next: 0 } -823 } -824 -825 /// Highest packet number authenticated. -826 fn highest(&self) -> u64 { -827 self.next - 1 -828 } -829 -830 /// Record a newly authenticated packet number. -831 /// -832 /// Returns whether the packet might be a duplicate. -833 pub(super) fn insert(&mut self, packet: u64) -> bool { -834 if let Some(diff) = packet.checked_sub(self.next) { -835 // Right of window -836 self.window = ((self.window << 1) | 1) -837 .checked_shl(cmp::min(diff, u64::from(u32::MAX)) as u32) -838 .unwrap_or(0); -839 self.next = packet + 1; -840 false -841 } else if self.highest() - packet < WINDOW_SIZE { -842 // Within window -843 if let Some(bit) = (self.highest() - packet).checked_sub(1) { -844 // < highest -845 let mask = 1 << bit; -846 let duplicate = self.window & mask != 0; -847 self.window |= mask; -848 duplicate -849 } else { -850 // == highest -851 true -852 } -853 } else { -854 // Left of window -855 true -856 } -857 } -858 -859 /// Returns the packet number of the smallest packet missing between the provided interval -860 /// -861 /// If there are no missing packets, returns `None` -862 fn smallest_missing_in_interval(&self, lower_bound: u64, upper_bound: u64) -> Option<u64> { -863 debug_assert!(lower_bound <= upper_bound); -864 debug_assert!(upper_bound <= self.highest()); -865 const BITFIELD_SIZE: u64 = (mem::size_of::<Window>() * 8) as u64; -866 -867 // Since we already know the packets at the boundaries have been received, we only need to -868 // check those in between them (this removes the necessity of extra logic to deal with the -869 // highest packet, which is stored outside the bitfield) -870 let lower_bound = lower_bound + 1; -871 let upper_bound = upper_bound.saturating_sub(1); -872 -873 // Note: the offsets are counted from the right -874 // The highest packet is not included in the bitfield, so we subtract 1 to account for that -875 let start_offset = (self.highest() - upper_bound).max(1) - 1; -876 if start_offset >= BITFIELD_SIZE { -877 // The start offset is outside of the window. All packets outside of the window are -878 // considered to be received. -879 return None; -880 } -881 -882 let end_offset_exclusive = self.highest().saturating_sub(lower_bound); -883 -884 // The range is clamped at the edge of the window, because any earlier packets are -885 // considered to be received -886 let range_len = end_offset_exclusive -887 .saturating_sub(start_offset) -888 .min(BITFIELD_SIZE); -889 if range_len == 0 { -890 return None; -891 } -892 -893 // Ensure the shift is within bounds (we already know start_offset < BITFIELD_SIZE, -894 // because of the early return) -895 let mask = if range_len == BITFIELD_SIZE { -896 u128::MAX -897 } else { -898 ((1u128 << range_len) - 1) << start_offset -899 }; -900 let gaps = !self.window & mask; -901 -902 let smallest_missing_offset = 128 - gaps.leading_zeros() as u64; -903 let smallest_missing_packet = self.highest() - smallest_missing_offset; -904 -905 if smallest_missing_packet <= upper_bound { -906 Some(smallest_missing_packet) -907 } else { -908 None -909 } -910 } -911 -912 /// Returns true if there are any missing packets between the provided interval -913 /// -914 /// The provided packet numbers must have been received before calling this function -915 fn missing_in_interval(&self, lower_bound: u64, upper_bound: u64) -> bool { -916 self.smallest_missing_in_interval(lower_bound, upper_bound) -917 .is_some() -918 } -919} -920 -921/// Indicates which data is available for sending. -922/// -923/// This applies to a particular space ID that was queried and all refers to on-path data. -924#[derive(Clone, Copy, PartialEq, Eq, Debug)] -925pub(super) struct SendableFrames { -926 /// Whether there are ACK frames to send, these are not ack-eliciting. -927 pub(super) acks: bool, -928 /// Whether there is a CONNECTION_CLOSE to send, this is not ack-eliciting. -929 pub(super) close: bool, -930 /// Whether there are any frames that must be sent on this specific space. -931 /// -932 /// A space here in the sense of a QUIC Multipath packet number space: `Initial`, -933 /// `Handshake` and all `Data(PathId)` spaces. -934 /// -935 /// These are ack-eliciting. Some frames are scheduled per path, e.g. PING, -936 /// IMMEDIATE_ACK, PATH_CHALLENGE or PATH_RESPONSE. -937 pub(super) space_specific: bool, -938 /// Whether there are any other frames to send, these are ack-eliciting. -939 pub(super) other: bool, -940} -941 -942impl SendableFrames { -943 /// Returns that no data is available for sending -944 pub(super) fn empty() -> Self { -945 Self { -946 acks: false, -947 close: false, -948 space_specific: false, -949 other: false, -950 } -951 } +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 /// 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 /// Whether an ack-eliciting packet will be sent. -954 pub(super) fn is_ack_eliciting(&self) -> bool { -955 let Self { -956 acks: _, -957 close, -958 space_specific, -959 other, -960 } = *self; -961 if close { -962 // No ack-eliciting frames are included with a CONNECTION_CLOSE, only acks. -963 return false; -964 } -965 space_specific || other -966 } -967 -968 /// Whether no data is sendable. -969 pub(super) fn is_empty(&self) -> bool { -970 let Self { -971 acks, -972 close, -973 space_specific, -974 other, -975 } = *self; -976 !acks && !close && !space_specific && !other -977 } -978} -979 -980impl ::std::ops::BitOrAssign for SendableFrames { -981 fn bitor_assign(&mut self, rhs: Self) { -982 let Self { -983 acks, -984 close, -985 space_specific, -986 other, -987 } = rhs; -988 -989 self.acks |= acks; -990 self.close |= close; -991 self.space_specific |= space_specific; -992 self.other |= other; -993 } -994} -995 -996#[derive(Debug)] -997pub(super) struct PendingAcks { -998 /// Whether we should send an ACK immediately, even if that means sending an ACK-only packet -999 /// -1000 /// When `immediate_ack_required` is false, the normal behavior is to send ACK frames only when -1001 /// there is other data to send, or when the `MaxAckDelay` timer expires. -1002 immediate_ack_required: bool, -1003 /// The number of ack-eliciting packets received since the last ACK frame was sent -1004 /// -1005 /// Once the count _exceeds_ `ack_eliciting_threshold`, an immediate ACK is required -1006 ack_eliciting_since_last_ack_sent: u64, -1007 non_ack_eliciting_since_last_ack_sent: u64, -1008 ack_eliciting_threshold: u64, -1009 /// The reordering threshold, controlling how we respond to out-of-order ack-eliciting packets -1010 /// -1011 /// Different values enable different behavior: -1012 /// -1013 /// * `0`: no special action is taken -1014 /// * `1`: an ACK is immediately sent if it is out-of-order according to RFC 9000 -1015 /// * `>1`: an ACK is immediately sent if it is out-of-order according to the ACK frequency draft -1016 reordering_threshold: u64, -1017 /// The earliest ack-eliciting packet since the last ACK was sent, used to calculate the moment -1018 /// upon which `max_ack_delay` elapses -1019 earliest_ack_eliciting_since_last_ack_sent: Option<Instant>, -1020 /// Packet number ranges for which to still send acknowledgements. -1021 /// -1022 /// These are packet number ranges of ack-eliciting packets the peer has sent and which -1023 /// need to be acknowledged. Packet numbers are only removed from here once the peer has -1024 /// acknowledged the ACKs for them. -1025 ranges: ArrayRangeSet, -1026 /// The largest packet number received and the time it was received -1027 /// -1028 /// Used to calculate ACK delay in [`PendingAcks::ack_delay`]. -1029 largest_packet: Option<(u64, Instant)>, -1030 /// The ack-eliciting packet we have received with the largest packet number -1031 largest_ack_eliciting_packet: Option<u64>, -1032 /// The largest acknowledged packet number sent in an ACK frame -1033 largest_acked: Option<u64>, -1034} -1035 -1036impl PendingAcks { -1037 fn new() -> Self { -1038 Self { -1039 immediate_ack_required: false, -1040 ack_eliciting_since_last_ack_sent: 0, -1041 non_ack_eliciting_since_last_ack_sent: 0, -1042 ack_eliciting_threshold: 1, -1043 reordering_threshold: 1, -1044 earliest_ack_eliciting_since_last_ack_sent: None, -1045 ranges: Default::default(), -1046 largest_packet: Default::default(), -1047 largest_ack_eliciting_packet: Default::default(), -1048 largest_acked: Default::default(), -1049 } -1050 } -1051 -1052 pub(super) fn set_ack_frequency_params(&mut self, frame: &frame::AckFrequency) { -1053 self.ack_eliciting_threshold = frame.ack_eliciting_threshold.into_inner(); -1054 self.reordering_threshold = frame.reordering_threshold.into_inner(); -1055 } -1056 -1057 pub(super) fn set_immediate_ack_required(&mut self) { -1058 self.immediate_ack_required = true; -1059 } -1060 -1061 pub(super) fn on_max_ack_delay_timeout(&mut self) { -1062 self.immediate_ack_required = self.ack_eliciting_since_last_ack_sent > 0; -1063 } -1064 -1065 pub(super) fn max_ack_delay_timeout(&self, max_ack_delay: Duration) -> Option<Instant> { -1066 self.earliest_ack_eliciting_since_last_ack_sent -1067 .map(|earliest_unacked| earliest_unacked + max_ack_delay) -1068 } -1069 -1070 /// Whether any ACK frames SHOULD be sent -1071 /// -1072 /// This is used in the top-level [`Connection::space_can_send`], so determines if a -1073 /// packet will be built. It is often possible to construct new ACK ranges to send -1074 /// before this returns `true`. This results in more ACK frames being sent, and -1075 /// processing those at the receiver costs CPU for very little improvements. -1076 /// -1077 /// [`Connection::space_can_send`]: super::Connection::space_can_send -1078 pub(super) fn can_send(&self) -> bool { -1079 self.immediate_ack_required && !self.ranges.is_empty() -1080 } -1081 -1082 /// Returns the delay since the packet with the largest packet number was received -1083 pub(super) fn ack_delay(&self, now: Instant) -> Duration { -1084 self.largest_packet -1085 .map_or_else(Duration::default, |(_, received)| now - received) -1086 } -1087 -1088 /// Handle receipt of a new packet -1089 /// -1090 /// Returns true if the max ack delay timer should be armed -1091 pub(super) fn packet_received( -1092 &mut self, -1093 now: Instant, -1094 packet_number: u64, -1095 ack_eliciting: bool, -1096 dedup: &Dedup, -1097 ) -> bool { -1098 if !ack_eliciting { -1099 self.non_ack_eliciting_since_last_ack_sent += 1; -1100 return false; -1101 } +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 +1047#[derive(Debug)] +1048pub(super) struct PendingAcks { +1049 /// Whether we should send an ACK immediately, even if that means sending an ACK-only packet +1050 /// +1051 /// When `immediate_ack_required` is false, the normal behavior is to send ACK frames only when +1052 /// there is other data to send, or when the `MaxAckDelay` timer expires. +1053 immediate_ack_required: bool, +1054 /// The number of ack-eliciting packets received since the last ACK frame was sent +1055 /// +1056 /// Once the count _exceeds_ `ack_eliciting_threshold`, an immediate ACK is required +1057 ack_eliciting_since_last_ack_sent: u64, +1058 non_ack_eliciting_since_last_ack_sent: u64, +1059 ack_eliciting_threshold: u64, +1060 /// The reordering threshold, controlling how we respond to out-of-order ack-eliciting packets +1061 /// +1062 /// Different values enable different behavior: +1063 /// +1064 /// * `0`: no special action is taken +1065 /// * `1`: an ACK is immediately sent if it is out-of-order according to RFC 9000 +1066 /// * `>1`: an ACK is immediately sent if it is out-of-order according to the ACK frequency draft +1067 reordering_threshold: u64, +1068 /// The earliest ack-eliciting packet since the last ACK was sent, used to calculate the moment +1069 /// upon which `max_ack_delay` elapses +1070 earliest_ack_eliciting_since_last_ack_sent: Option<Instant>, +1071 /// Packet number ranges for which to still send acknowledgements. +1072 /// +1073 /// These are packet number ranges of ack-eliciting packets the peer has sent and which +1074 /// need to be acknowledged. Packet numbers are only removed from here once the peer has +1075 /// acknowledged the ACKs for them. +1076 ranges: ArrayRangeSet, +1077 /// The largest packet number received and the time it was received +1078 /// +1079 /// Used to calculate ACK delay in [`PendingAcks::ack_delay`]. +1080 largest_packet: Option<(u64, Instant)>, +1081 /// The ack-eliciting packet we have received with the largest packet number +1082 largest_ack_eliciting_packet: Option<u64>, +1083 /// The largest acknowledged packet number sent in an ACK frame +1084 largest_acked: Option<u64>, +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 let prev_largest_ack_eliciting = self.largest_ack_eliciting_packet.unwrap_or(0); -1104 -1105 // Track largest ack-eliciting packet -1106 self.largest_ack_eliciting_packet = self -1107 .largest_ack_eliciting_packet -1108 .map(|pn| pn.max(packet_number)) -1109 .or(Some(packet_number)); -1110 -1111 // Handle ack_eliciting_threshold -1112 self.ack_eliciting_since_last_ack_sent += 1; -1113 self.immediate_ack_required |= -1114 self.ack_eliciting_since_last_ack_sent > self.ack_eliciting_threshold; +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 // Handle out-of-order packets -1117 self.immediate_ack_required |= -1118 self.is_out_of_order(packet_number, prev_largest_ack_eliciting, dedup); -1119 -1120 // Arm max_ack_delay timer if necessary -1121 if self.earliest_ack_eliciting_since_last_ack_sent.is_none() && !self.can_send() { -1122 self.earliest_ack_eliciting_since_last_ack_sent = Some(now); -1123 return true; -1124 } -1125 -1126 false -1127 } -1128 -1129 fn is_out_of_order( -1130 &self, -1131 packet_number: u64, -1132 prev_largest_ack_eliciting: u64, -1133 dedup: &Dedup, -1134 ) -> bool { -1135 match self.reordering_threshold { -1136 0 => false, -1137 1 => { -1138 // From https://www.rfc-editor.org/rfc/rfc9000#section-13.2.1-7 -1139 packet_number < prev_largest_ack_eliciting -1140 || dedup.missing_in_interval(prev_largest_ack_eliciting, packet_number) -1141 } -1142 _ => { -1143 // From acknowledgement frequency draft, section 6.1: send an ACK immediately if -1144 // doing so would cause the sender to detect a new packet loss -1145 let Some((largest_acked, largest_unacked)) = -1146 self.largest_acked.zip(self.largest_ack_eliciting_packet) -1147 else { -1148 return false; -1149 }; -1150 if self.reordering_threshold > largest_acked { -1151 return false; -1152 } -1153 // The largest packet number that could be declared lost without a new ACK being -1154 // sent -1155 let largest_reported = largest_acked - self.reordering_threshold + 1; -1156 let Some(smallest_missing_unreported) = -1157 dedup.smallest_missing_in_interval(largest_reported, largest_unacked) -1158 else { -1159 return false; -1160 }; -1161 largest_unacked - smallest_missing_unreported >= self.reordering_threshold -1162 } -1163 } -1164 } -1165 -1166 /// Should be called whenever ACKs have been sent -1167 /// -1168 /// This will suppress sending further ACKs until additional ACK eliciting frames arrive -1169 pub(super) fn acks_sent(&mut self) { -1170 // It is possible (though unlikely) that the ACKs we just sent do not cover all the -1171 // ACK-eliciting packets we have received (e.g. if there is not enough room in the packet to -1172 // fit all the ranges). To keep things simple, however, we assume they do. If there are -1173 // indeed some ACKs that weren't covered, the packets might be ACKed later anyway, because -1174 // they are still contained in `self.ranges`. If we somehow fail to send the ACKs at a later -1175 // moment, the peer will assume the packets got lost and will retransmit their frames in a -1176 // new packet, which is suboptimal, because we already received them. Our assumption here is -1177 // that simplicity results in code that is more performant, even in the presence of -1178 // occasional redundant retransmits. -1179 self.immediate_ack_required = false; -1180 self.ack_eliciting_since_last_ack_sent = 0; -1181 self.non_ack_eliciting_since_last_ack_sent = 0; -1182 self.earliest_ack_eliciting_since_last_ack_sent = None; -1183 self.largest_acked = self.largest_ack_eliciting_packet; -1184 } -1185 -1186 /// Insert one packet that needs to be acknowledged -1187 pub(super) fn insert_one(&mut self, packet: u64, now: Instant) { -1188 self.ranges.insert_one(packet); -1189 -1190 if self.largest_packet.is_none_or(|(pn, _)| packet > pn) { -1191 self.largest_packet = Some((packet, now)); -1192 } -1193 -1194 if self.ranges.len() > MAX_ACK_BLOCKS { -1195 self.ranges.pop_min(); -1196 } -1197 } -1198 -1199 /// Remove ACKs of packets numbered at or below `max` from the set of pending ACKs -1200 pub(super) fn subtract_below(&mut self, max: u64) { -1201 self.ranges.remove(0..(max + 1)); -1202 } -1203 -1204 /// Returns the set of currently pending ACK ranges -1205 pub(super) fn ranges(&self) -> &ArrayRangeSet { -1206 &self.ranges -1207 } -1208 -1209 /// Queue an ACK if a significant number of non-ACK-eliciting packets have not yet been -1210 /// acknowledged -1211 /// -1212 /// Should be called immediately before a non-probing packet is composed, when we've already -1213 /// committed to sending a packet regardless. -1214 pub(super) fn maybe_ack_non_eliciting(&mut self) { -1215 // If we're going to send a packet anyway, and we've received a significant number of -1216 // non-ACK-eliciting packets, then include an ACK to help the peer perform timely loss -1217 // detection even if they're not sending any ACK-eliciting packets themselves. Exact -1218 // threshold chosen somewhat arbitrarily. -1219 const LAZY_ACK_THRESHOLD: u64 = 10; -1220 if self.non_ack_eliciting_since_last_ack_sent > LAZY_ACK_THRESHOLD { -1221 self.immediate_ack_required = true; -1222 } -1223 } -1224} -1225 -1226/// Helper for mitigating [optimistic ACK attacks] -1227/// -1228/// A malicious peer could prompt the local application to begin a large data transfer, and then -1229/// send ACKs without first waiting for data to be received. This could defeat congestion control, -1230/// allowing the connection to consume disproportionate resources. We therefore occasionally skip -1231/// packet numbers, and classify any ACK referencing a skipped packet number as a transport error. -1232/// -1233/// Skipped packet numbers occur only in the application data space (where costly transfers might -1234/// take place) and are distributed exponentially to reflect the reduced likelihood and impact of -1235/// bad behavior from a peer that has been well-behaved for an extended period. -1236/// -1237/// ACKs for packet numbers that have not yet been allocated are also a transport error, but an -1238/// attacker with knowledge of the congestion control algorithm in use could time falsified ACKs to -1239/// arrive after the packets they reference are sent. -1240/// -1241/// [optimistic ACK attacks]: https://www.rfc-editor.org/rfc/rfc9000.html#name-optimistic-ack-attack -1242pub(super) struct PacketNumberFilter { -1243 /// Next outgoing packet number to skip -1244 next_skipped_packet_number: u64, -1245 /// Most recently skipped packet number -1246 prev_skipped_packet_number: Option<u64>, -1247 /// Next packet number to skip is randomly selected from 2^n..2^n+1 -1248 exponent: u32, -1249} -1250 -1251impl PacketNumberFilter { -1252 pub(super) fn new(rng: &mut (impl CryptoRng + ?Sized)) -> Self { -1253 // First skipped PN is in 0..64 -1254 let exponent = 6; -1255 Self { -1256 next_skipped_packet_number: rng.random_range(0..2u64.saturating_pow(exponent)), -1257 prev_skipped_packet_number: None, -1258 exponent, -1259 } -1260 } -1261 -1262 #[cfg(test)] -1263 pub(super) fn disabled() -> Self { -1264 Self { -1265 next_skipped_packet_number: u64::MAX, -1266 prev_skipped_packet_number: None, -1267 exponent: u32::MAX, -1268 } -1269 } -1270 -1271 /// Whether to use the provided packet number (false) or to skip it (true) -1272 pub(super) fn skip_pn(&mut self, n: u64, rng: &mut (impl CryptoRng + ?Sized)) -> bool { -1273 if n != self.next_skipped_packet_number { -1274 return false; -1275 } +1116 pub(super) fn max_ack_delay_timeout(&self, max_ack_delay: Duration) -> Option<Instant> { +1117 self.earliest_ack_eliciting_since_last_ack_sent +1118 .map(|earliest_unacked| earliest_unacked + max_ack_delay) +1119 } +1120 +1121 /// Whether any ACK frames SHOULD be sent +1122 /// +1123 /// This is used in the top-level [`Connection::space_can_send`], so determines if a +1124 /// packet will be built. It is often possible to construct new ACK ranges to send +1125 /// before this returns `true`. This results in more ACK frames being sent, and +1126 /// processing those at the receiver costs CPU for very little improvements. +1127 /// +1128 /// [`Connection::space_can_send`]: super::Connection::space_can_send +1129 pub(super) fn can_send(&self) -> bool { +1130 self.immediate_ack_required && !self.ranges.is_empty() +1131 } +1132 +1133 /// Returns the delay since the packet with the largest packet number was received +1134 pub(super) fn ack_delay(&self, now: Instant) -> Duration { +1135 self.largest_packet +1136 .map_or_else(Duration::default, |(_, received)| now - received) +1137 } +1138 +1139 /// Handle receipt of a new packet +1140 /// +1141 /// Returns true if the max ack delay timer should be armed +1142 pub(super) fn packet_received( +1143 &mut self, +1144 now: Instant, +1145 packet_number: u64, +1146 ack_eliciting: bool, +1147 dedup: &Dedup, +1148 ) -> bool { +1149 if !ack_eliciting { +1150 self.non_ack_eliciting_since_last_ack_sent += 1; +1151 return false; +1152 } +1153 +1154 let prev_largest_ack_eliciting = self.largest_ack_eliciting_packet.unwrap_or(0); +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 /// 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 trace!("skipping pn {n}"); -1278 // Skip this packet number, and choose the next one to skip -1279 self.prev_skipped_packet_number = Some(self.next_skipped_packet_number); -1280 let next_exponent = self.exponent.saturating_add(1); -1281 self.next_skipped_packet_number = rng -1282 .random_range(2u64.saturating_pow(self.exponent)..2u64.saturating_pow(next_exponent)); -1283 self.exponent = next_exponent; -1284 true -1285 } -1286} -1287 -1288/// Ensures we can always fit all our ACKs in a single minimum-MTU packet with room to spare -1289const MAX_ACK_BLOCKS: usize = 64; -1290 -1291#[cfg(test)] -1292mod test { -1293 use rand::Rng; -1294 use rand::seq::SliceRandom; -1295 -1296 use crate::token::ResetToken; -1297 use crate::{ConnectionIdGenerator, RandomConnectionIdGenerator}; -1298 -1299 use super::*; -1300 -1301 #[test] -1302 fn sanity() { -1303 let mut dedup = Dedup::new(); -1304 assert!(!dedup.insert(0)); -1305 assert_eq!(dedup.next, 1); -1306 assert_eq!(dedup.window, 0b1); -1307 assert!(dedup.insert(0)); -1308 assert_eq!(dedup.next, 1); -1309 assert_eq!(dedup.window, 0b1); -1310 assert!(!dedup.insert(1)); -1311 assert_eq!(dedup.next, 2); -1312 assert_eq!(dedup.window, 0b11); -1313 assert!(!dedup.insert(2)); -1314 assert_eq!(dedup.next, 3); -1315 assert_eq!(dedup.window, 0b111); -1316 assert!(!dedup.insert(4)); -1317 assert_eq!(dedup.next, 5); -1318 assert_eq!(dedup.window, 0b11110); -1319 assert!(!dedup.insert(7)); -1320 assert_eq!(dedup.next, 8); -1321 assert_eq!(dedup.window, 0b1111_0100); -1322 assert!(dedup.insert(4)); -1323 assert!(!dedup.insert(3)); -1324 assert_eq!(dedup.next, 8); -1325 assert_eq!(dedup.window, 0b1111_1100); -1326 assert!(!dedup.insert(6)); -1327 assert_eq!(dedup.next, 8); -1328 assert_eq!(dedup.window, 0b1111_1101); -1329 assert!(!dedup.insert(5)); -1330 assert_eq!(dedup.next, 8); -1331 assert_eq!(dedup.window, 0b1111_1111); -1332 } -1333 -1334 #[test] -1335 fn happypath() { -1336 let mut dedup = Dedup::new(); -1337 for i in 0..(2 * WINDOW_SIZE) { -1338 assert!(!dedup.insert(i)); -1339 for j in 0..=i { -1340 assert!(dedup.insert(j)); -1341 } -1342 } -1343 } -1344 -1345 #[test] -1346 fn jump() { -1347 let mut dedup = Dedup::new(); -1348 dedup.insert(2 * WINDOW_SIZE); -1349 assert!(dedup.insert(WINDOW_SIZE)); -1350 assert_eq!(dedup.next, 2 * WINDOW_SIZE + 1); -1351 assert_eq!(dedup.window, 0); -1352 assert!(!dedup.insert(WINDOW_SIZE + 1)); -1353 assert_eq!(dedup.next, 2 * WINDOW_SIZE + 1); -1354 assert_eq!(dedup.window, 1 << (WINDOW_SIZE - 2)); -1355 } -1356 -1357 #[test] -1358 fn dedup_has_missing() { -1359 let mut dedup = Dedup::new(); -1360 -1361 dedup.insert(0); -1362 assert!(!dedup.missing_in_interval(0, 0)); -1363 -1364 dedup.insert(1); -1365 assert!(!dedup.missing_in_interval(0, 1)); -1366 -1367 dedup.insert(3); -1368 assert!(dedup.missing_in_interval(1, 3)); -1369 -1370 dedup.insert(4); -1371 assert!(!dedup.missing_in_interval(3, 4)); -1372 assert!(dedup.missing_in_interval(0, 4)); -1373 -1374 dedup.insert(2); -1375 assert!(!dedup.missing_in_interval(0, 4)); -1376 } -1377 -1378 #[test] -1379 fn dedup_outside_of_window_has_missing() { -1380 let mut dedup = Dedup::new(); -1381 -1382 for i in 0..140 { -1383 dedup.insert(i); -1384 } -1385 -1386 // 0 and 4 are outside of the window -1387 assert!(!dedup.missing_in_interval(0, 4)); -1388 dedup.insert(160); -1389 assert!(!dedup.missing_in_interval(0, 4)); -1390 assert!(!dedup.missing_in_interval(0, 140)); -1391 assert!(dedup.missing_in_interval(0, 160)); -1392 } -1393 -1394 #[test] -1395 fn dedup_smallest_missing() { -1396 let mut dedup = Dedup::new(); -1397 -1398 dedup.insert(0); -1399 assert_eq!(dedup.smallest_missing_in_interval(0, 0), None); -1400 -1401 dedup.insert(1); -1402 assert_eq!(dedup.smallest_missing_in_interval(0, 1), None); -1403 -1404 dedup.insert(5); -1405 dedup.insert(7); -1406 assert_eq!(dedup.smallest_missing_in_interval(0, 7), Some(2)); -1407 assert_eq!(dedup.smallest_missing_in_interval(5, 7), Some(6)); -1408 -1409 dedup.insert(2); -1410 assert_eq!(dedup.smallest_missing_in_interval(1, 7), Some(3)); +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 use super::*; +1351 +1352 #[test] +1353 fn sanity() { +1354 let mut dedup = Dedup::new(); +1355 assert!(!dedup.insert(0)); +1356 assert_eq!(dedup.next, 1); +1357 assert_eq!(dedup.window, 0b1); +1358 assert!(dedup.insert(0)); +1359 assert_eq!(dedup.next, 1); +1360 assert_eq!(dedup.window, 0b1); +1361 assert!(!dedup.insert(1)); +1362 assert_eq!(dedup.next, 2); +1363 assert_eq!(dedup.window, 0b11); +1364 assert!(!dedup.insert(2)); +1365 assert_eq!(dedup.next, 3); +1366 assert_eq!(dedup.window, 0b111); +1367 assert!(!dedup.insert(4)); +1368 assert_eq!(dedup.next, 5); +1369 assert_eq!(dedup.window, 0b11110); +1370 assert!(!dedup.insert(7)); +1371 assert_eq!(dedup.next, 8); +1372 assert_eq!(dedup.window, 0b1111_0100); +1373 assert!(dedup.insert(4)); +1374 assert!(!dedup.insert(3)); +1375 assert_eq!(dedup.next, 8); +1376 assert_eq!(dedup.window, 0b1111_1100); +1377 assert!(!dedup.insert(6)); +1378 assert_eq!(dedup.next, 8); +1379 assert_eq!(dedup.window, 0b1111_1101); +1380 assert!(!dedup.insert(5)); +1381 assert_eq!(dedup.next, 8); +1382 assert_eq!(dedup.window, 0b1111_1111); +1383 } +1384 +1385 #[test] +1386 fn happypath() { +1387 let mut dedup = Dedup::new(); +1388 for i in 0..(2 * WINDOW_SIZE) { +1389 assert!(!dedup.insert(i)); +1390 for j in 0..=i { +1391 assert!(dedup.insert(j)); +1392 } +1393 } +1394 } +1395 +1396 #[test] +1397 fn jump() { +1398 let mut dedup = Dedup::new(); +1399 dedup.insert(2 * WINDOW_SIZE); +1400 assert!(dedup.insert(WINDOW_SIZE)); +1401 assert_eq!(dedup.next, 2 * WINDOW_SIZE + 1); +1402 assert_eq!(dedup.window, 0); +1403 assert!(!dedup.insert(WINDOW_SIZE + 1)); +1404 assert_eq!(dedup.next, 2 * WINDOW_SIZE + 1); +1405 assert_eq!(dedup.window, 1 << (WINDOW_SIZE - 2)); +1406 } +1407 +1408 #[test] +1409 fn dedup_has_missing() { +1410 let mut dedup = Dedup::new(); 1411 -1412 dedup.insert(170); -1413 dedup.insert(172); -1414 dedup.insert(300); -1415 assert_eq!(dedup.smallest_missing_in_interval(170, 172), None); -1416 -1417 dedup.insert(500); -1418 assert_eq!(dedup.smallest_missing_in_interval(0, 500), Some(372)); -1419 assert_eq!(dedup.smallest_missing_in_interval(0, 373), Some(372)); -1420 assert_eq!(dedup.smallest_missing_in_interval(0, 372), None); -1421 } -1422 -1423 #[test] -1424 fn pending_acks_first_packet_is_not_considered_reordered() { -1425 let mut acks = PendingAcks::new(); -1426 let mut dedup = Dedup::new(); -1427 dedup.insert(0); -1428 acks.packet_received(Instant::now(), 0, true, &dedup); -1429 assert!(!acks.immediate_ack_required); -1430 } -1431 -1432 #[test] -1433 fn pending_acks_after_immediate_ack_set() { -1434 let mut acks = PendingAcks::new(); -1435 let mut dedup = Dedup::new(); +1412 dedup.insert(0); +1413 assert!(!dedup.missing_in_interval(0, 0)); +1414 +1415 dedup.insert(1); +1416 assert!(!dedup.missing_in_interval(0, 1)); +1417 +1418 dedup.insert(3); +1419 assert!(dedup.missing_in_interval(1, 3)); +1420 +1421 dedup.insert(4); +1422 assert!(!dedup.missing_in_interval(3, 4)); +1423 assert!(dedup.missing_in_interval(0, 4)); +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 // Receive ack-eliciting packet -1438 dedup.insert(0); -1439 let now = Instant::now(); -1440 acks.insert_one(0, now); -1441 acks.packet_received(now, 0, true, &dedup); -1442 -1443 // Sanity check -1444 assert!(!acks.ranges.is_empty()); -1445 assert!(!acks.can_send()); -1446 -1447 // Can send ACK after max_ack_delay exceeded -1448 acks.set_immediate_ack_required(); -1449 assert!(acks.can_send()); -1450 } +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 #[test] -1453 fn pending_acks_ack_delay() { -1454 let mut acks = PendingAcks::new(); -1455 let mut dedup = Dedup::new(); -1456 -1457 let t1 = Instant::now(); -1458 let t2 = t1 + Duration::from_millis(2); -1459 let t3 = t2 + Duration::from_millis(5); -1460 assert_eq!(acks.ack_delay(t1), Duration::from_millis(0)); -1461 assert_eq!(acks.ack_delay(t2), Duration::from_millis(0)); -1462 assert_eq!(acks.ack_delay(t3), Duration::from_millis(0)); -1463 -1464 // In-order packet -1465 dedup.insert(0); -1466 acks.insert_one(0, t1); -1467 acks.packet_received(t1, 0, true, &dedup); -1468 assert_eq!(acks.ack_delay(t1), Duration::from_millis(0)); -1469 assert_eq!(acks.ack_delay(t2), Duration::from_millis(2)); -1470 assert_eq!(acks.ack_delay(t3), Duration::from_millis(7)); -1471 -1472 // Out of order (higher than expected) -1473 dedup.insert(3); -1474 acks.insert_one(3, t2); -1475 acks.packet_received(t2, 3, true, &dedup); -1476 assert_eq!(acks.ack_delay(t2), Duration::from_millis(0)); -1477 assert_eq!(acks.ack_delay(t3), Duration::from_millis(5)); -1478 -1479 // Out of order (lower than expected, so previous instant is kept) -1480 dedup.insert(2); -1481 acks.insert_one(2, t3); -1482 acks.packet_received(t3, 2, true, &dedup); -1483 assert_eq!(acks.ack_delay(t3), Duration::from_millis(5)); -1484 } -1485 -1486 #[test] -1487 fn sent_packet_size() { -1488 // The tracking state of sent packets should be minimal, and not grow -1489 // over time. -1490 assert!(std::mem::size_of::<SentPacket>() <= 128); -1491 } -1492 -1493 #[test] -1494 fn pending_new_cids() { -1495 #[cfg(all(feature = "aws-lc-rs", not(feature = "ring")))] -1496 use aws_lc_rs::hmac; -1497 #[cfg(feature = "ring")] -1498 use ring::hmac; -1499 -1500 let mut cid_generator = RandomConnectionIdGenerator::new(8); -1501 let mut reset_key = [0; 64]; -1502 rand::rng().fill_bytes(&mut reset_key); -1503 let hmac = hmac::Key::new(hmac::HMAC_SHA256, &reset_key); -1504 -1505 let cid_a = cid_generator.generate_cid(); -1506 let a = IssuedCid { -1507 path_id: PathId::ZERO, -1508 sequence: 1, -1509 id: cid_a, -1510 reset_token: ResetToken::new(&hmac, cid_a), -1511 }; -1512 let cid_b = cid_generator.generate_cid(); -1513 let b = IssuedCid { -1514 path_id: PathId::ZERO, -1515 sequence: 2, -1516 id: cid_b, -1517 reset_token: ResetToken::new(&hmac, cid_b), -1518 }; -1519 let cid_c = cid_generator.generate_cid(); -1520 let c = IssuedCid { -1521 path_id: PathId(1), -1522 sequence: 1, -1523 id: cid_c, -1524 reset_token: ResetToken::new(&hmac, cid_c), -1525 }; -1526 -1527 let mut pending_cids = PendingNewCids::default(); -1528 -1529 for _ in 0..9 { -1530 // Push CIDs in a random order -1531 let mut input = vec![a, b, c]; -1532 input.shuffle(&mut rand::rng()); -1533 for cid in input { -1534 pending_cids.push(cid); -1535 } +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 dedup.insert(500); +1469 assert_eq!(dedup.smallest_missing_in_interval(0, 500), Some(372)); +1470 assert_eq!(dedup.smallest_missing_in_interval(0, 373), Some(372)); +1471 assert_eq!(dedup.smallest_missing_in_interval(0, 372), None); +1472 } +1473 +1474 #[test] +1475 fn pending_acks_first_packet_is_not_considered_reordered() { +1476 let mut acks = PendingAcks::new(); +1477 let mut dedup = Dedup::new(); +1478 dedup.insert(0); +1479 acks.packet_received(Instant::now(), 0, true, &dedup); +1480 assert!(!acks.immediate_ack_required); +1481 } +1482 +1483 #[test] +1484 fn pending_acks_after_immediate_ack_set() { +1485 let mut acks = PendingAcks::new(); +1486 let mut dedup = Dedup::new(); +1487 +1488 // Receive ack-eliciting packet +1489 dedup.insert(0); +1490 let now = Instant::now(); +1491 acks.insert_one(0, now); +1492 acks.packet_received(now, 0, true, &dedup); +1493 +1494 // Sanity check +1495 assert!(!acks.ranges.is_empty()); +1496 assert!(!acks.can_send()); +1497 +1498 // Can send ACK after max_ack_delay exceeded +1499 acks.set_immediate_ack_required(); +1500 assert!(acks.can_send()); +1501 } +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 // Pop order is always the same -1538 assert_eq!(pending_cids.pop().map(|i| i.id), Some(a.id)); -1539 assert_eq!(pending_cids.pop().map(|i| i.id), Some(b.id)); -1540 assert_eq!(pending_cids.pop().map(|i| i.id), Some(c.id)); -1541 assert!(pending_cids.pop().is_none()); -1542 } -1543 } -1544} \ No newline at end of file +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/617/docs/src/noq_proto/frame.rs.html b/pr/617/docs/src/noq_proto/frame.rs.html index c8853decc..3a12df1b4 100644 --- a/pr/617/docs/src/noq_proto/frame.rs.html +++ b/pr/617/docs/src/noq_proto/frame.rs.html @@ -2370,511 +2370,498 @@ 2370 } 2371} 2372 -2373/// Conjunction of the information contained in the reach out frames -2374/// ([`FrameType::ReachOutAtIpv4`], [`FrameType::ReachOutAtIpv6`]) -2375#[derive(Debug, PartialEq, Eq, Clone, derive_more::Display)] -2376#[display("REACH_OUT round: {round} local_addr: {}", self.socket_addr())] -2377#[cfg_attr(test, derive(Arbitrary))] -2378pub(crate) struct ReachOut { -2379 /// The sequence number of the NAT Traversal attempts -2380 pub(crate) round: VarInt, -2381 /// Address to use -2382 pub(crate) ip: IpAddr, -2383 /// Port to use with this address -2384 pub(crate) port: u16, -2385} -2386 -2387// TODO(@divma): remove -2388#[allow(dead_code)] -2389impl ReachOut { -2390 /// Smallest number of bytes this type of frame is guaranteed to fit within -2391 pub(crate) const SIZE_BOUND: usize = Self { -2392 round: VarInt::MAX, -2393 ip: IpAddr::V6(std::net::Ipv6Addr::LOCALHOST), -2394 port: u16::MAX, -2395 } -2396 .size(); +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 pub(crate) const fn new(round: VarInt, (ip, port): (IpAddr, u16)) -> Self { -2399 Self { round, ip, port } -2400 } -2401 -2402 /// Get the [`FrameType`] for this frame -2403 pub(crate) const fn get_type(&self) -> FrameType { -2404 if self.ip.is_ipv6() { -2405 FrameType::ReachOutAtIpv6 -2406 } else { -2407 FrameType::ReachOutAtIpv4 -2408 } -2409 } -2410 -2411 /// Compute the number of bytes needed to encode the frame -2412 pub(crate) const fn size(&self) -> usize { -2413 let type_size = self.get_type().size(); -2414 let round_bytes = self.round.size(); -2415 let ip_bytes = if self.ip.is_ipv6() { 16 } else { 4 }; -2416 let port_bytes = 2; -2417 type_size + round_bytes + ip_bytes + port_bytes -2418 } -2419 -2420 /// Read the frame contents from the buffer -2421 /// -2422 /// Should only be called when the frame type has been identified as -2423 /// [`FrameType::ReachOutAtIpv4`] or [`FrameType::ReachOutAtIpv6`]. -2424 pub(crate) fn read<R: Buf>(bytes: &mut R, is_ipv6: bool) -> coding::Result<Self> { -2425 let round = bytes.get()?; -2426 let ip = if is_ipv6 { -2427 IpAddr::V6(bytes.get()?) -2428 } else { -2429 IpAddr::V4(bytes.get()?) -2430 }; -2431 let port = bytes.get()?; -2432 Ok(Self { round, ip, port }) -2433 } -2434 -2435 /// Give the [`SocketAddr`] encoded in the frame -2436 pub(crate) fn socket_addr(&self) -> SocketAddr { -2437 (self.ip, self.port).into() -2438 } -2439} -2440 -2441impl Encodable for ReachOut { -2442 fn encode<W: BufMut>(&self, buf: &mut W) { -2443 buf.write(self.get_type()); -2444 buf.write(self.round); -2445 match self.ip { -2446 IpAddr::V4(ipv4_addr) => { -2447 buf.write(ipv4_addr); -2448 } -2449 IpAddr::V6(ipv6_addr) => { -2450 buf.write(ipv6_addr); -2451 } -2452 } -2453 buf.write::<u16>(self.port); -2454 } -2455} -2456 -2457/// Frame signaling an address is no longer being advertised -2458#[derive(Debug, PartialEq, Eq, Copy, Clone, PartialOrd, Ord, derive_more::Display)] -2459#[cfg_attr(test, derive(Arbitrary))] -2460#[display("REMOVE_ADDRESS seq_no: {seq_no}")] -2461pub(crate) struct RemoveAddress { -2462 /// The sequence number of the address advertisement to be removed -2463 pub(crate) seq_no: VarInt, -2464} +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 +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// TODO(@divma): remove -2467#[allow(dead_code)] -2468impl RemoveAddress { -2469 /// [`FrameType`] of this frame -2470 pub(crate) const TYPE: FrameType = FrameType::RemoveAddress; -2471 -2472 /// Smallest number of bytes this type of frame is guaranteed to fit within -2473 pub(crate) const SIZE_BOUND: usize = Self::new(VarInt::MAX).size(); -2474 -2475 pub(crate) const fn new(seq_no: VarInt) -> Self { -2476 Self { seq_no } -2477 } -2478 -2479 /// Compute the number of bytes needed to encode the frame -2480 pub(crate) const fn size(&self) -> usize { -2481 let type_size = Self::TYPE.size(); -2482 let seq_no_bytes = self.seq_no.size(); -2483 type_size + seq_no_bytes -2484 } -2485 -2486 /// Read the frame contents from the buffer -2487 /// -2488 /// Should only be called when the frame type has been identified as -2489 /// [`FrameType::RemoveAddress`]. -2490 pub(crate) fn read<R: Buf>(bytes: &mut R) -> coding::Result<Self> { -2491 Ok(Self { -2492 seq_no: bytes.get()?, -2493 }) -2494 } -2495 -2496 const fn get_type(&self) -> FrameType { -2497 FrameType::RemoveAddress -2498 } -2499} -2500 -2501impl Encodable for RemoveAddress { -2502 fn encode<W: BufMut>(&self, buf: &mut W) { -2503 buf.write(Self::TYPE); -2504 buf.write(self.seq_no); -2505 } -2506} -2507 -2508/// Helper struct for display implementations. -2509// NOTE: Due to lifetimes in fmt::Arguments it's not possible to make this a simple function that -2510// avoids allocations. -2511struct DisplayOption<T: Display> { -2512 field_name: &'static str, -2513 op: Option<T>, -2514} -2515 -2516impl<T: Display> DisplayOption<T> { -2517 fn new(field_name: &'static str, op: Option<T>) -> Self { -2518 Self { field_name, op } -2519 } -2520} -2521 -2522impl<T: Display> Display for DisplayOption<T> { -2523 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -2524 if let Some(x) = self.op.as_ref() { -2525 write!(f, "{}: {x}", self.field_name) -2526 } else { -2527 fmt::Result::Ok(()) -2528 } -2529 } -2530} +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 +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#[cfg(test)] -2533mod test { -2534 use super::*; -2535 use crate::coding::Encodable; -2536 use assert_matches::assert_matches; -2537 -2538 #[test] -2539 fn frame_type() { -2540 assert_eq!( -2541 FrameType::try_from(FrameType::Padding.to_u64()), -2542 Ok(FrameType::Padding), -2543 ); -2544 -2545 assert_eq!( -2546 FrameType::try_from(FrameType::Datagram(DatagramInfo(0x30)).to_u64()), -2547 Ok(FrameType::Datagram(DatagramInfo(0x30))), -2548 ); -2549 -2550 assert_eq!( -2551 FrameType::try_from(FrameType::Stream(StreamInfo(0x08)).to_u64()), -2552 Ok(FrameType::Stream(StreamInfo(0x08))), -2553 ); -2554 } -2555 -2556 #[track_caller] -2557 fn frames(buf: Vec<u8>) -> Vec<Frame> { -2558 Iter::new(Bytes::from(buf)) -2559 .unwrap() -2560 .collect::<Result<Vec<_>, _>>() -2561 .unwrap() -2562 } -2563 -2564 #[test] -2565 fn ack_coding() { -2566 const PACKETS: &[u64] = &[1, 2, 3, 5, 10, 11, 14]; -2567 let mut ranges = ArrayRangeSet::new(); -2568 for &packet in PACKETS { -2569 ranges.insert(packet..packet + 1); -2570 } -2571 let mut buf = Vec::new(); -2572 const ECN: EcnCounts = EcnCounts { -2573 ect0: 42, -2574 ect1: 24, -2575 ce: 12, -2576 }; -2577 Ack::encoder(42, &ranges, Some(&ECN)).encode(&mut buf); -2578 let frames = frames(buf); -2579 assert_eq!(frames.len(), 1); -2580 match frames[0] { -2581 Frame::Ack(ref ack) => { -2582 let mut packets = ack.iter().flatten().collect::<Vec<_>>(); -2583 packets.sort_unstable(); -2584 assert_eq!(&packets[..], PACKETS); -2585 assert_eq!(ack.ecn, Some(ECN)); -2586 } -2587 ref x => panic!("incorrect frame {x:?}"), -2588 } -2589 } -2590 -2591 #[test] -2592 #[allow(clippy::range_plus_one)] -2593 fn path_ack_coding_with_ecn() { -2594 const PACKETS: &[u64] = &[1, 2, 3, 5, 10, 11, 14]; -2595 let mut ranges = ArrayRangeSet::new(); -2596 for &packet in PACKETS { -2597 ranges.insert(packet..packet + 1); -2598 } -2599 let mut buf = Vec::new(); -2600 const ECN: EcnCounts = EcnCounts { -2601 ect0: 42, -2602 ect1: 24, -2603 ce: 12, -2604 }; -2605 const PATH_ID: PathId = PathId::MAX; -2606 PathAck::encoder(PATH_ID, 42, &ranges, Some(&ECN)).encode(&mut buf); -2607 let frames = frames(buf); -2608 assert_eq!(frames.len(), 1); -2609 match frames[0] { -2610 Frame::PathAck(ref ack) => { -2611 assert_eq!(ack.path_id, PATH_ID); -2612 let mut packets = ack.ranges.iter().flatten().collect::<Vec<_>>(); -2613 packets.sort_unstable(); -2614 assert_eq!(&packets[..], PACKETS); -2615 assert_eq!(ack.ecn, Some(ECN)); -2616 } -2617 ref x => panic!("incorrect frame {x:?}"), -2618 } -2619 } -2620 -2621 #[test] -2622 #[allow(clippy::range_plus_one)] -2623 fn path_ack_coding_no_ecn() { -2624 const PACKETS: &[u64] = &[1, 2, 3, 5, 10, 11, 14]; -2625 let mut ranges = ArrayRangeSet::new(); -2626 for &packet in PACKETS { -2627 ranges.insert(packet..packet + 1); -2628 } -2629 let mut buf = Vec::new(); -2630 const PATH_ID: PathId = PathId::MAX; -2631 PathAck::encoder(PATH_ID, 42, &ranges, None).encode(&mut buf); -2632 let frames = frames(buf); -2633 assert_eq!(frames.len(), 1); -2634 match frames[0] { -2635 Frame::PathAck(ref ack) => { -2636 assert_eq!(ack.path_id, PATH_ID); -2637 let mut packets = ack.ranges.iter().flatten().collect::<Vec<_>>(); -2638 packets.sort_unstable(); -2639 assert_eq!(&packets[..], PACKETS); -2640 assert_eq!(ack.ecn, None); -2641 } -2642 ref x => panic!("incorrect frame {x:?}"), -2643 } -2644 } -2645 -2646 #[test] -2647 fn ack_frequency_coding() { -2648 let mut buf = Vec::new(); -2649 let original = AckFrequency { -2650 sequence: VarInt(42), -2651 ack_eliciting_threshold: VarInt(20), -2652 request_max_ack_delay: VarInt(50_000), -2653 reordering_threshold: VarInt(1), -2654 }; -2655 original.encode(&mut buf); -2656 let frames = frames(buf); -2657 assert_eq!(frames.len(), 1); -2658 match &frames[0] { -2659 Frame::AckFrequency(decoded) => assert_eq!(decoded, &original), -2660 x => panic!("incorrect frame {x:?}"), -2661 } -2662 } -2663 -2664 #[test] -2665 fn immediate_ack_coding() { -2666 let mut buf = Vec::new(); -2667 FrameType::ImmediateAck.encode(&mut buf); -2668 let frames = frames(buf); -2669 assert_eq!(frames.len(), 1); -2670 assert_matches!(&frames[0], Frame::ImmediateAck); -2671 } -2672 -2673 /// Test that encoding and decoding [`ObservedAddr`] produces the same result. -2674 #[test] -2675 fn test_observed_addr_roundrip() { -2676 let observed_addr = ObservedAddr { -2677 seq_no: VarInt(42), -2678 ip: std::net::Ipv4Addr::LOCALHOST.into(), -2679 port: 4242, -2680 }; -2681 let mut buf = Vec::with_capacity(observed_addr.size()); -2682 observed_addr.encode(&mut buf); -2683 -2684 assert_eq!( -2685 observed_addr.size(), -2686 buf.len(), -2687 "expected written bytes and actual size differ" -2688 ); -2689 -2690 let mut decoded = frames(buf); -2691 assert_eq!(decoded.len(), 1); -2692 match decoded.pop().expect("non empty") { -2693 Frame::ObservedAddr(decoded) => assert_eq!(decoded, observed_addr), -2694 x => panic!("incorrect frame {x:?}"), -2695 } -2696 } -2697 -2698 #[test] -2699 fn test_path_abandon_roundtrip() { -2700 let abandon = PathAbandon { -2701 path_id: PathId(42), -2702 error_code: TransportErrorCode::NO_ERROR, -2703 }; -2704 let mut buf = Vec::new(); -2705 abandon.encode(&mut buf); -2706 -2707 let mut decoded = frames(buf); -2708 assert_eq!(decoded.len(), 1); -2709 match decoded.pop().expect("non empty") { -2710 Frame::PathAbandon(decoded) => assert_eq!(decoded, abandon), -2711 x => panic!("incorrect frame {x:?}"), -2712 } -2713 } -2714 -2715 #[test] -2716 fn test_path_status_available_roundtrip() { -2717 let path_status_available = PathStatusAvailable { -2718 path_id: PathId(42), -2719 status_seq_no: VarInt(73), -2720 }; -2721 let mut buf = Vec::new(); -2722 path_status_available.encode(&mut buf); -2723 -2724 let mut decoded = frames(buf); -2725 assert_eq!(decoded.len(), 1); -2726 match decoded.pop().expect("non empty") { -2727 Frame::PathStatusAvailable(decoded) => assert_eq!(decoded, path_status_available), -2728 x => panic!("incorrect frame {x:?}"), -2729 } -2730 } -2731 -2732 #[test] -2733 fn test_path_status_backup_roundtrip() { -2734 let path_status_backup = PathStatusBackup { -2735 path_id: PathId(42), -2736 status_seq_no: VarInt(73), -2737 }; -2738 let mut buf = Vec::new(); -2739 path_status_backup.encode(&mut buf); -2740 -2741 let mut decoded = frames(buf); -2742 assert_eq!(decoded.len(), 1); -2743 match decoded.pop().expect("non empty") { -2744 Frame::PathStatusBackup(decoded) => assert_eq!(decoded, path_status_backup), -2745 x => panic!("incorrect frame {x:?}"), -2746 } -2747 } -2748 -2749 #[test] -2750 fn test_path_new_connection_id_roundtrip() { -2751 let cid = NewConnectionId { -2752 path_id: Some(PathId(22)), -2753 sequence: 31, -2754 retire_prior_to: 13, -2755 id: ConnectionId::new(&[0xAB; 8]), -2756 reset_token: ResetToken::from([0xCD; crate::RESET_TOKEN_SIZE]), -2757 }; -2758 let mut buf = Vec::new(); -2759 cid.encode(&mut buf); -2760 -2761 let mut decoded = frames(buf); -2762 assert_eq!(decoded.len(), 1); -2763 match decoded.pop().expect("non empty") { -2764 Frame::NewConnectionId(decoded) => assert_eq!(decoded, cid), -2765 x => panic!("incorrect frame {x:?}"), -2766 } -2767 } -2768 -2769 #[test] -2770 fn test_path_retire_connection_id_roundtrip() { -2771 let retire_cid = RetireConnectionId { -2772 path_id: Some(PathId(22)), -2773 sequence: 31, -2774 }; +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::with_capacity(observed_addr.size()); +2669 observed_addr.encode(&mut buf); +2670 +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 retire_cid.encode(&mut buf); -2777 -2778 let mut decoded = frames(buf); -2779 assert_eq!(decoded.len(), 1); -2780 match decoded.pop().expect("non empty") { -2781 Frame::RetireConnectionId(decoded) => assert_eq!(decoded, retire_cid), -2782 x => panic!("incorrect frame {x:?}"), -2783 } -2784 } -2785 -2786 #[test] -2787 fn test_paths_blocked_path_cids_blocked_roundtrip() { -2788 let mut buf = Vec::new(); -2789 -2790 let frame0 = PathsBlocked(PathId(22)); -2791 frame0.encode(&mut buf); -2792 let frame1 = PathCidsBlocked { -2793 path_id: PathId(23), -2794 next_seq: VarInt(32), -2795 }; -2796 frame1.encode(&mut buf); -2797 -2798 let mut decoded = frames(buf); -2799 assert_eq!(decoded.len(), 2); -2800 match decoded.pop().expect("non empty") { -2801 Frame::PathCidsBlocked(decoded) => assert_eq!(decoded, frame1), -2802 x => panic!("incorrect frame {x:?}"), -2803 } -2804 match decoded.pop().expect("non empty") { -2805 Frame::PathsBlocked(decoded) => assert_eq!(decoded, frame0), -2806 x => panic!("incorrect frame {x:?}"), -2807 } -2808 } -2809 -2810 /// Test that encoding and decoding [`AddAddress`] produces the same result -2811 #[test] -2812 fn test_add_address_roundrip() { -2813 let add_address = AddAddress { -2814 seq_no: VarInt(42), -2815 ip: std::net::Ipv4Addr::LOCALHOST.into(), -2816 port: 4242, -2817 }; -2818 let mut buf = Vec::with_capacity(add_address.size()); -2819 add_address.encode(&mut buf); -2820 -2821 assert_eq!( -2822 add_address.size(), -2823 buf.len(), -2824 "expected written bytes and actual size differ" -2825 ); -2826 -2827 let mut decoded = frames(buf); -2828 assert_eq!(decoded.len(), 1); -2829 match decoded.pop().expect("non empty") { -2830 Frame::AddAddress(decoded) => assert_eq!(decoded, add_address), -2831 x => panic!("incorrect frame {x:?}"), -2832 } -2833 } -2834 -2835 /// Test that encoding and decoding [`AddAddress`] produces the same result -2836 #[test] -2837 fn test_reach_out_roundrip() { -2838 let reach_out = ReachOut { -2839 round: VarInt(42), -2840 ip: std::net::Ipv6Addr::LOCALHOST.into(), -2841 port: 4242, -2842 }; -2843 let mut buf = Vec::with_capacity(reach_out.size()); -2844 reach_out.encode(&mut buf); -2845 -2846 assert_eq!( -2847 reach_out.size(), -2848 buf.len(), -2849 "expected written bytes and actual size differ" -2850 ); -2851 -2852 let mut decoded = frames(buf); -2853 assert_eq!(decoded.len(), 1); -2854 match decoded.pop().expect("non empty") { -2855 Frame::ReachOut(decoded) => assert_eq!(decoded, reach_out), -2856 x => panic!("incorrect frame {x:?}"), -2857 } -2858 } +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 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 /// Test that encoding and decoding [`RemoveAddress`] produces the same result -2861 #[test] -2862 fn test_remove_address_roundrip() { -2863 let remove_addr = RemoveAddress::new(VarInt(10)); -2864 let mut buf = Vec::with_capacity(remove_addr.size()); -2865 remove_addr.encode(&mut buf); -2866 -2867 assert_eq!( -2868 remove_addr.size(), -2869 buf.len(), -2870 "expected written bytes and actual size differ" -2871 ); -2872 -2873 let mut decoded = frames(buf); -2874 assert_eq!(decoded.len(), 1); -2875 match decoded.pop().expect("non empty") { -2876 Frame::RemoveAddress(decoded) => assert_eq!(decoded, remove_addr), -2877 x => panic!("incorrect frame {x:?}"), -2878 } -2879 } -2880} \ No newline at end of file +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/617/docs/src/noq_proto/lib.rs.html b/pr/617/docs/src/noq_proto/lib.rs.html index 686d69c0f..241cc7b18 100644 --- a/pr/617/docs/src/noq_proto/lib.rs.html +++ b/pr/617/docs/src/noq_proto/lib.rs.html @@ -56,422 +56,420 @@ 56pub 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/617/docs/src/noq_proto/n0_nat_traversal.rs.html b/pr/617/docs/src/noq_proto/n0_nat_traversal.rs.html index 6d8bc22fd..3bfed167d 100644 --- a/pr/617/docs/src/noq_proto/n0_nat_traversal.rs.html +++ b/pr/617/docs/src/noq_proto/n0_nat_traversal.rs.html @@ -2,697 +2,916 @@ 2 3use std::{ 4 collections::hash_map::Entry, -5 net::{IpAddr, SocketAddr}, -6}; -7 -8use rustc_hash::{FxHashMap, FxHashSet}; -9use tracing::{debug, trace}; -10 -11use crate::{ -12 FourTuple, PathId, Side, VarInt, -13 frame::{AddAddress, ReachOut, RemoveAddress}, -14}; -15 -16type IpPort = (IpAddr, u16); +5 fmt::Display, +6 net::{IpAddr, SocketAddr}, +7}; +8 +9use rustc_hash::{FxHashMap, FxHashSet}; +10use tracing::{debug, trace}; +11 +12use crate::{ +13 FourTuple, Side, VarInt, +14 connection::spaces::PendingReachOutFrames, +15 frame::{AddAddress, ReachOut, RemoveAddress}, +16}; 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`]. +18/// An IP & port. +19/// +20/// Invariant: This value should always be in the ip family that the local +21/// socket operates in. +22/// E.g. if the local socket is ipv4, then all `IpPort`s should only have +23/// IPv4 addresses, and if the socket supports ipv6, then all `IpPort`s +24/// should be IPv6 addresses or IPv6-mapped IPv4 addresses. +25/// +26/// See also [`map_to_local_socket_family`], which powers this conversion. +27type IpPort = (IpAddr, u16); +28 +29/// An IP & port in canonical form. +30/// +31/// Avoids using ipv6-mapped ipv4 addresses. +32/// This is the primary type used to send ip addresses around remotely +33/// and the primary type used to canonicalize received addresses. +34#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +35pub(crate) struct CanonicalIpPort { +36 canonical_ip: IpAddr, +37 port: u16, +38} +39 +40impl CanonicalIpPort { +41 pub(crate) fn ip(&self) -> IpAddr { +42 self.canonical_ip +43 } +44 +45 pub(crate) fn port(&self) -> u16 { +46 self.port +47 } +48 +49 /// Converts this into a local-socket-family-mapped IP & port. 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} +51 /// Instead of using ipv4 and ipv6 addresses, this tries to match `ipv6`, which +52 /// should indicate whether the local socket supports ipv6 or not. +53 /// +54 /// If ipv6 is supported, all ipv4 addresses are mapped using ipv6-mapped ipv4 +55 /// addresses. +56 /// If ipv6 is not supported, then this returns `None` for ipv6 addresses. +57 /// +58 /// See also [`map_to_local_socket_family`]. +59 pub(crate) fn as_local_socket_family(&self, ipv6: bool) -> Option<IpPort> { +60 Some(( +61 map_to_local_socket_family(self.canonical_ip, ipv6)?, +62 self.port, +63 )) +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 -75impl State { -76 pub(crate) fn new(max_remote_addresses: u8, max_local_addresses: u8, side: Side) -> Self { -77 match side { -78 Side::Client => Self::ClientSide(ClientState::new( -79 max_remote_addresses.into(), -80 max_local_addresses.into(), -81 )), -82 Side::Server => Self::ServerSide(ServerState::new( -83 max_remote_addresses.into(), -84 max_local_addresses.into(), -85 )), -86 } -87 } -88 -89 pub(crate) fn is_negotiated(&self) -> bool { -90 match self { -91 Self::NotNegotiated => false, -92 Self::ClientSide(_) | Self::ServerSide(_) => true, -93 } -94 } +66 /// Returns this address as-is with the canonical IP used in a `SocketAddr`. +67 pub(crate) fn as_canonical_addr(&self) -> SocketAddr { +68 SocketAddr::new(self.canonical_ip, self.port) +69 } +70} +71 +72impl Display for CanonicalIpPort { +73 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +74 self.as_canonical_addr().fmt(f) +75 } +76} +77 +78impl From<SocketAddr> for CanonicalIpPort { +79 fn from(addr: SocketAddr) -> Self { +80 Self { +81 canonical_ip: addr.ip().to_canonical(), +82 port: addr.port(), +83 } +84 } +85} +86 +87impl From<IpPort> for CanonicalIpPort { +88 fn from((ip, port): IpPort) -> Self { +89 Self { +90 canonical_ip: ip.to_canonical(), +91 port, +92 } +93 } +94} 95 -96 pub(crate) fn client_side(&self) -> Result<&ClientState, Error> { -97 match self { -98 Self::NotNegotiated => Err(Error::ExtensionNotNegotiated), -99 Self::ClientSide(client_side) => Ok(client_side), -100 Self::ServerSide(_) => Err(Error::WrongConnectionSide), -101 } -102 } -103 -104 pub(crate) fn client_side_mut(&mut self) -> Result<&mut ClientState, Error> { -105 match self { -106 Self::NotNegotiated => Err(Error::ExtensionNotNegotiated), -107 Self::ClientSide(client_side) => Ok(client_side), -108 Self::ServerSide(_) => Err(Error::WrongConnectionSide), -109 } -110 } -111 -112 pub(crate) fn server_side_mut(&mut self) -> Result<&mut ServerState, Error> { -113 match self { -114 Self::NotNegotiated => Err(Error::ExtensionNotNegotiated), -115 Self::ClientSide(_) => Err(Error::WrongConnectionSide), -116 Self::ServerSide(server_side) => Ok(server_side), -117 } -118 } -119 -120 /// Adds a local address to use for nat traversal. -121 /// -122 /// When this endpoint is the server within the connection, these addresses will be sent to the -123 /// client in add address frames. For clients, these addresses will be sent in reach out frames -124 /// when nat traversal attempts are initiated. -125 /// -126 /// If a frame should be sent, it is returned. -127 pub(crate) fn add_local_address( -128 &mut self, -129 address: SocketAddr, -130 ) -> Result<Option<AddAddress>, Error> { -131 let ip_port = IpPort::from((address.ip(), address.port())); -132 match self { -133 Self::NotNegotiated => Err(Error::ExtensionNotNegotiated), -134 Self::ClientSide(client_state) => { -135 client_state.add_local_address(ip_port)?; -136 Ok(None) -137 } -138 Self::ServerSide(server_state) => server_state.add_local_address(ip_port), -139 } -140 } -141 -142 /// Removes a local address from the advertised set for nat traversal. -143 /// -144 /// When this endpoint is the server, removed addresses must be reported with remove address -145 /// frames. Clients will simply stop reporting these addresses in reach out frames. -146 /// -147 /// If a frame should be sent, it is returned. -148 pub(crate) fn remove_local_address( -149 &mut self, -150 address: SocketAddr, -151 ) -> Result<Option<RemoveAddress>, Error> { -152 let address = IpPort::from((address.ip(), address.port())); -153 match self { -154 Self::NotNegotiated => Err(Error::ExtensionNotNegotiated), -155 Self::ClientSide(client_state) => { -156 client_state.remove_local_address(&address); -157 Ok(None) -158 } -159 Self::ServerSide(server_state) => Ok(server_state.remove_local_address(&address)), -160 } -161 } -162 -163 pub(crate) fn get_local_nat_traversal_addresses(&self) -> Result<Vec<SocketAddr>, Error> { -164 match self { -165 Self::NotNegotiated => Err(Error::ExtensionNotNegotiated), -166 Self::ClientSide(client_state) => Ok(client_state -167 .local_addresses -168 .iter() -169 .copied() -170 .map(Into::into) -171 .collect()), -172 Self::ServerSide(server_state) => Ok(server_state -173 .local_addresses -174 .keys() -175 .copied() -176 .map(Into::into) -177 .collect()), -178 } -179 } -180} +96/// Errors that the nat traversal state might encounter. +97#[derive(Debug, thiserror::Error)] +98pub enum Error { +99 /// An endpoint (local or remote) tried to add too many addresses to their advertised set +100 #[error("Tried to add too many addresses to their advertised set")] +101 TooManyAddresses, +102 /// The operation is not allowed for this endpoint's connection side +103 #[error("Not allowed for this endpoint's connection side")] +104 WrongConnectionSide, +105 /// The extension was not negotiated +106 #[error("n0's nat traversal was not negotiated")] +107 ExtensionNotNegotiated, +108 /// Not enough addresses to complete the operation +109 #[error("Not enough addresses")] +110 NotEnoughAddresses, +111 /// Nat traversal attempt failed due to a multipath error +112 #[error("Failed to establish paths {0}")] +113 Multipath(super::PathError), +114 /// Attempted to initiate NAT traversal on a closed, or closing connection. +115 #[error("The connection is already closed")] +116 Closed, +117} +118 +119/// Event emitted when the client receives ADD_ADDRESS or REMOVE_ADDRESS frames. +120#[derive(Debug, Clone)] +121pub enum Event { +122 /// An ADD_ADDRESS frame was received. +123 AddressAdded(SocketAddr), +124 /// A REMOVE_ADDRESS frame was received. +125 AddressRemoved(SocketAddr), +126} +127 +128/// State kept for n0's nat traversal +129#[derive(Debug, Default)] +130pub(crate) enum State { +131 #[default] +132 NotNegotiated, +133 ClientSide(ClientState), +134 ServerSide(ServerState), +135} +136 +137impl State { +138 pub(crate) fn new(max_remote_addresses: u8, max_local_addresses: u8, side: Side) -> Self { +139 match side { +140 Side::Client => Self::ClientSide(ClientState::new( +141 max_remote_addresses.into(), +142 max_local_addresses.into(), +143 )), +144 Side::Server => Self::ServerSide(ServerState::new( +145 max_remote_addresses.into(), +146 max_local_addresses.into(), +147 )), +148 } +149 } +150 +151 pub(crate) fn is_negotiated(&self) -> bool { +152 match self { +153 Self::NotNegotiated => false, +154 Self::ClientSide(_) | Self::ServerSide(_) => true, +155 } +156 } +157 +158 pub(crate) fn client_side(&self) -> Result<&ClientState, Error> { +159 match self { +160 Self::NotNegotiated => Err(Error::ExtensionNotNegotiated), +161 Self::ClientSide(client_side) => Ok(client_side), +162 Self::ServerSide(_) => Err(Error::WrongConnectionSide), +163 } +164 } +165 +166 pub(crate) fn client_side_mut(&mut self) -> Result<&mut ClientState, Error> { +167 match self { +168 Self::NotNegotiated => Err(Error::ExtensionNotNegotiated), +169 Self::ClientSide(client_side) => Ok(client_side), +170 Self::ServerSide(_) => Err(Error::WrongConnectionSide), +171 } +172 } +173 +174 pub(crate) fn server_side_mut(&mut self) -> Result<&mut ServerState, Error> { +175 match self { +176 Self::NotNegotiated => Err(Error::ExtensionNotNegotiated), +177 Self::ClientSide(_) => Err(Error::WrongConnectionSide), +178 Self::ServerSide(server_side) => Ok(server_side), +179 } +180 } 181 -182#[derive(Debug)] -183pub(crate) struct ClientState { -184 /// Max number of remote addresses we allow -185 /// -186 /// This is set by the local endpoint. -187 max_remote_addresses: usize, -188 /// Max number of local addresses allowed -189 /// -190 /// This is set by the remote endpoint. -191 max_local_addresses: usize, -192 /// Candidate addresses the remote server reports as potentially reachable, to use for nat -193 /// traversal attempts. -194 /// -195 /// These are indexed by their advertised Id. For each address, whether the address should be -196 /// reported in nat traversal continuations is kept. -197 remote_addresses: FxHashMap<VarInt, (IpPort, bool)>, -198 /// Candidate addresses the local client reports as potentially reachable, to use for nat -199 /// traversal attempts. -200 local_addresses: FxHashSet<IpPort>, -201 /// Current nat traversal round. -202 round: VarInt, -203 /// [`PathId`]s used to probe remotes assigned to this round. -204 round_path_ids: Vec<PathId>, -205} -206 -207impl ClientState { -208 fn new(max_remote_addresses: usize, max_local_addresses: usize) -> Self { -209 Self { -210 max_remote_addresses, -211 max_local_addresses, -212 remote_addresses: Default::default(), -213 local_addresses: Default::default(), -214 round: Default::default(), -215 round_path_ids: Default::default(), -216 } -217 } -218 -219 fn add_local_address(&mut self, address: IpPort) -> Result<(), Error> { -220 if self.local_addresses.len() < self.max_local_addresses { -221 self.local_addresses.insert(address); -222 Ok(()) -223 } else if self.local_addresses.contains(&address) { -224 // at capacity, but the address is known, no issues here -225 Ok(()) -226 } else { -227 // at capacity and the address is new -228 Err(Error::TooManyAddresses) -229 } -230 } -231 -232 fn remove_local_address(&mut self, address: &IpPort) { -233 self.local_addresses.remove(address); -234 } -235 -236 /// Initiates a new nat traversal round. -237 /// -238 /// A nat traversal round involves advertising the client's local addresses in `REACH_OUT` -239 /// frames, and initiating probing of the known remote addresses. When a new round is -240 /// initiated, the previous one is cancelled, and paths that have not been opened should be -241 /// closed. -242 /// -243 /// `ipv6` indicates if the connection runs on a socket that supports IPv6. If so, then all -244 /// addresses returned in [`NatTraversalRound`] will be IPv6 addresses (and IPv4-mapped IPv6 -245 /// addresses if necessary). Otherwise they're all IPv4 addresses. -246 /// See also [`map_to_local_socket_family`]. -247 pub(crate) fn initiate_nat_traversal_round( -248 &mut self, -249 ipv6: bool, -250 ) -> Result<NatTraversalRound, Error> { -251 if self.local_addresses.is_empty() { -252 return Err(Error::NotEnoughAddresses); -253 } -254 -255 let prev_round_path_ids = std::mem::take(&mut self.round_path_ids); -256 self.round = self.round.saturating_add(1u8); -257 let mut addresses_to_probe = Vec::with_capacity(self.remote_addresses.len()); -258 for (id, ((ip, port), report_in_continuation)) in self.remote_addresses.iter_mut() { -259 *report_in_continuation = false; -260 -261 if let Some(ip) = map_to_local_socket_family(*ip, ipv6) { -262 addresses_to_probe.push((*id, (ip, *port))); -263 } else { -264 trace!(?ip, "not using IPv6 nat candidate for IPv4 socket"); -265 } -266 } -267 -268 Ok(NatTraversalRound { -269 new_round: self.round, -270 reach_out_at: self.local_addresses.iter().copied().collect(), -271 addresses_to_probe, -272 prev_round_path_ids, -273 }) -274 } -275 -276 /// Mark a remote address to be reported back in a nat traversal continuation if the error is -277 /// considered spurious from a nat traversal point of view. -278 /// -279 /// Ids not present are silently ignored. -280 pub(crate) fn report_in_continuation(&mut self, id: VarInt, e: crate::PathError) { -281 match e { -282 crate::PathError::MaxPathIdReached | crate::PathError::RemoteCidsExhausted => { -283 if let Some((_address, report_in_continuation)) = self.remote_addresses.get_mut(&id) -284 { -285 *report_in_continuation = true; -286 } -287 } -288 _ => {} -289 } -290 } -291 -292 /// Returns an address that needs to be probed, if any. +182 /// Adds a local address to use for nat traversal. +183 /// +184 /// When this endpoint is the server within the connection, these addresses will be sent to the +185 /// client in add address frames. For clients, these addresses will be sent in reach out frames +186 /// when nat traversal attempts are initiated. +187 /// +188 /// If a frame should be sent, it is returned. +189 pub(crate) fn add_local_address( +190 &mut self, +191 address: SocketAddr, +192 ) -> Result<Option<AddAddress>, Error> { +193 match self { +194 Self::NotNegotiated => Err(Error::ExtensionNotNegotiated), +195 Self::ClientSide(client_state) => { +196 client_state.add_local_address(address)?; +197 Ok(None) +198 } +199 Self::ServerSide(server_state) => server_state.add_local_address(address), +200 } +201 } +202 +203 /// Removes a local address from the advertised set for nat traversal. +204 /// +205 /// When this endpoint is the server, removed addresses must be reported with remove address +206 /// frames. Clients will simply stop reporting these addresses in reach out frames. +207 /// +208 /// If a frame should be sent, it is returned. +209 pub(crate) fn remove_local_address( +210 &mut self, +211 address: SocketAddr, +212 ) -> Result<Option<RemoveAddress>, Error> { +213 let address = IpPort::from((address.ip(), address.port())); +214 match self { +215 Self::NotNegotiated => Err(Error::ExtensionNotNegotiated), +216 Self::ClientSide(client_state) => { +217 client_state.remove_local_address(&address); +218 Ok(None) +219 } +220 Self::ServerSide(server_state) => Ok(server_state.remove_local_address(&address)), +221 } +222 } +223 +224 pub(crate) fn get_local_nat_traversal_addresses(&self) -> Result<Vec<SocketAddr>, Error> { +225 match self { +226 Self::NotNegotiated => Err(Error::ExtensionNotNegotiated), +227 Self::ClientSide(client_state) => Ok(client_state +228 .local_addresses +229 .iter() +230 .map(CanonicalIpPort::as_canonical_addr) +231 .collect()), +232 Self::ServerSide(server_state) => Ok(server_state +233 .local_addresses +234 .keys() +235 .map(CanonicalIpPort::as_canonical_addr) +236 .collect()), +237 } +238 } +239 +240 /// Returns the next ready probe's address. +241 /// +242 /// If this is actually sent you must call [`Self::mark_probe_sent`]. +243 pub(crate) fn next_probe_addr(&self) -> Option<IpPort> { +244 match self { +245 Self::NotNegotiated => None, +246 Self::ClientSide(state) => state.next_probe_addr(), +247 Self::ServerSide(state) => state.next_probe_addr(), +248 } +249 } +250 +251 /// Marks a probe as sent to the address with the challenge. +252 pub(crate) fn mark_probe_sent(&mut self, remote: IpPort, challenge: u64) { +253 match self { +254 Self::NotNegotiated => (), +255 Self::ClientSide(state) => state.mark_probe_sent(remote, challenge), +256 Self::ServerSide(state) => state.mark_probe_sent(remote, challenge), +257 } +258 } +259 +260 /// Re-queues probes that have not yet succeeded or reached 0 remaining retries. +261 /// +262 /// Returns whether any probes are now queued to send. In this case the +263 /// `NatTraversalProbeRetry` timer needs to be reset. +264 pub(crate) fn queue_retries(&mut self, ipv6: bool) -> bool { +265 match self { +266 Self::NotNegotiated => false, +267 Self::ClientSide(state) => state.queue_retries(ipv6), +268 Self::ServerSide(state) => state.queue_retries(), +269 } +270 } +271 +272 /// Marks a remote as successful if the response matches a sent probe. +273 /// +274 /// Returns the open network path if it was a response to one of the NAT traversal +275 /// probes. Note that the NAT probes are not padded to 1200 bytes so only the address is +276 /// validated, but not the entire path. +277 pub(crate) fn handle_path_response(&mut self, src: FourTuple, challenge: u64) -> bool { +278 match self { +279 Self::NotNegotiated => false, +280 Self::ClientSide(state) => state.handle_path_response(src, challenge), +281 Self::ServerSide(state) => state.handle_path_response(src, challenge), +282 } +283 } +284} +285 +286#[derive(Debug)] +287pub(crate) struct ClientState { +288 /// Max number of remote addresses we allow +289 /// +290 /// This is set by the local endpoint. +291 max_remote_addresses: usize, +292 /// Max number of local addresses allowed 293 /// -294 /// The address will not be returned twice unless marked as such again with -295 /// [`Self::report_in_continuation`]. -296 /// -297 /// `ipv6` indicates if the connection runs on a socket that supports IPv6. If so, then all -298 /// addresses returned in [`NatTraversalRound`] will be IPv6 addresses (and IPv4-mapped IPv6 -299 /// addresses if necessary). Otherwise they're all IPv4 addresses. -300 /// See also [`map_to_local_socket_family`]. -301 pub(crate) fn continue_nat_traversal_round(&mut self, ipv6: bool) -> Option<(VarInt, IpPort)> { -302 // this being random depends on iteration not returning always on the same order -303 let (id, (address, report_in_continuation)) = self -304 .remote_addresses -305 .iter_mut() -306 .filter(|(_id, (_addr, report))| *report) -307 .filter_map(|(id, ((ip, port), report))| { -308 // only continue with addresses we can send on our local socket -309 let Some(ip) = map_to_local_socket_family(*ip, ipv6) else { -310 trace!(?ip, "not using IPv6 nat candidate for IPv4 socket"); -311 return None; -312 }; -313 Some((*id, ((ip, *port), report))) -314 }) -315 .next()?; -316 *report_in_continuation = false; -317 Some((id, address)) -318 } -319 -320 /// Add a [`PathId`] as part of the current attempts to create paths based on the server's -321 /// advertised addresses. -322 pub(crate) fn set_round_path_ids(&mut self, path_ids: Vec<PathId>) { -323 self.round_path_ids = path_ids; -324 } -325 -326 /// Add a [`PathId`] as part of the current attempts to create paths based on the server's -327 /// advertised addresses. -328 pub(crate) fn add_round_path_id(&mut self, path_id: PathId) { -329 self.round_path_ids.push(path_id); -330 } -331 -332 /// Adds an address to the remote set -333 /// -334 /// On success returns the address if it was new to the set. It will error when the set has no -335 /// capacity for the address. -336 pub(crate) fn add_remote_address( -337 &mut self, -338 add_addr: AddAddress, -339 ) -> Result<Option<SocketAddr>, Error> { -340 let AddAddress { seq_no, ip, port } = add_addr; -341 let address = (ip, port); -342 let allow_new = self.remote_addresses.len() < self.max_remote_addresses; -343 match self.remote_addresses.entry(seq_no) { -344 Entry::Occupied(mut occupied_entry) => { -345 let is_update = occupied_entry.get().0 != address; -346 if is_update { -347 occupied_entry.insert((address, false)); -348 } -349 // The value might be different. This should not happen, but we assume that the new -350 // address is more recent than the previous, and thus worth updating -351 Ok(is_update.then_some(address.into())) -352 } -353 Entry::Vacant(vacant_entry) if allow_new => { -354 vacant_entry.insert((address, false)); -355 Ok(Some(address.into())) -356 } -357 _ => Err(Error::TooManyAddresses), -358 } -359 } -360 -361 /// Removes an address from the remote set -362 /// -363 /// Returns whether the address was present. -364 pub(crate) fn remove_remote_address( -365 &mut self, -366 remove_addr: RemoveAddress, -367 ) -> Option<SocketAddr> { -368 self.remote_addresses -369 .remove(&remove_addr.seq_no) -370 .map(|(address, _report_in_continuation)| address.into()) -371 } -372 -373 /// Checks that a received remote address is valid -374 /// -375 /// An address is valid as long as it does not change the value of a known address id. -376 pub(crate) fn check_remote_address(&self, add_addr: &AddAddress) -> bool { -377 match self.remote_addresses.get(&add_addr.seq_no) { -378 None => true, -379 Some((existing, _)) => existing == &add_addr.ip_port(), -380 } -381 } -382 -383 pub(crate) fn get_remote_nat_traversal_addresses(&self) -> Vec<SocketAddr> { -384 self.remote_addresses -385 .values() -386 .map(|(address, _report_in_continuation)| (*address).into()) -387 .collect() -388 } -389} -390 -391/// Maximum number of times we send a NAT probe to the same remote address in a round. -392/// -393/// This is a trade-off between several factors: -394/// - Probe packets could be lost. This allows recovery. -395/// - We may need two probes to reach the NAT firewall to get through. -396/// - We may be sending probes to innocent bystanders on the internet. -397/// - A round never "finishes": probing of remotes only stops when: -398/// 1. A new round is started. -399/// 2. A probe was successful. -400/// 3. This number of attempts is exhausted. -401/// -402/// Currently probes are retried after 2/3rd of the configured initial RTT. At a -403/// fixed interval, so without exponential backoff. For the default initial RTT of 333ms -404/// this is 222ms. So 10 attempts covers 2220ms. -405// TODO(flub): I would like to improve this sometime so that we cover about 2s but with only -406// about 5-6 probes. The three initial probes should be faster, later probes should start -407// to slow down. Unfortunately we only have one timer for the entire round currently, we -408// would need to have a timer per remote. Because REACH_OUT frames can appear in the -409// middle of a round. -410pub(crate) const MAX_NAT_PROBE_ATTEMPTS: u8 = 10; -411 -412/// State of an off-path NAT traversal probe to a remote address. -413#[derive(Debug)] -414enum ProbeState { -415 /// The remote still needs to be probed in this round. -416 /// -417 /// The remaining number of retries are stored in the `u8`. -418 Active(u8), -419 /// We received a probe response for this remote. -420 Succeeded, -421} -422 -423#[derive(Debug)] -424pub(crate) struct ServerState { -425 /// Max number of remote addresses we allow. -426 /// -427 /// This is set by the local endpoint. -428 max_remote_addresses: usize, -429 /// Max number of local addresses allowed. -430 /// -431 /// This is set by the remote endpoint. -432 max_local_addresses: usize, -433 /// Candidate addresses the server reports as potentially reachable, to use for nat -434 /// traversal attempts. -435 local_addresses: FxHashMap<IpPort, VarInt>, -436 /// The next id to use for local addresses sent to the client. -437 next_local_addr_id: VarInt, -438 /// Current nat traversal round -439 /// -440 /// Servers keep track of the client's most recent round and cancel probing related to previous -441 /// rounds. -442 round: VarInt, -443 /// The remote addresses participating in this round. -444 /// -445 /// The set is cleared when a new round starts. -446 remotes: FxHashMap<IpPort, ProbeState>, -447 /// The data of PATH_CHALLENGE frames sent in probes. -448 /// -449 /// These are cleared when a new round starts, so any late-arriving PATH_RESPONSEs will -450 /// have no effect. -451 sent_challenges: FxHashMap<u64, IpPort>, -452 /// Queued probes to be sent in the next [`poll_transmit`] call. -453 /// -454 /// At the beginning of a round this is populated from REACH_OUT frames and at every -455 /// retry this is populated from [`Self::remotes`]. -456 /// -457 /// [`poll_transmit`]: crate::connection::Connection::poll_transmit -458 pending_probes: FxHashSet<IpPort>, -459} -460 -461impl ServerState { -462 fn new(max_remote_addresses: usize, max_local_addresses: usize) -> Self { -463 Self { -464 max_remote_addresses, -465 max_local_addresses, -466 local_addresses: Default::default(), -467 next_local_addr_id: Default::default(), -468 round: Default::default(), -469 remotes: Default::default(), -470 sent_challenges: Default::default(), -471 pending_probes: Default::default(), -472 } -473 } -474 -475 fn add_local_address(&mut self, address: IpPort) -> Result<Option<AddAddress>, Error> { -476 let allow_new = self.local_addresses.len() < self.max_local_addresses; -477 match self.local_addresses.entry(address) { -478 Entry::Occupied(_) => Ok(None), -479 Entry::Vacant(vacant_entry) if allow_new => { -480 let id = self.next_local_addr_id; -481 self.next_local_addr_id = self.next_local_addr_id.saturating_add(1u8); -482 vacant_entry.insert(id); -483 Ok(Some(AddAddress::new(address, id))) -484 } -485 _ => Err(Error::TooManyAddresses), -486 } -487 } -488 -489 fn remove_local_address(&mut self, address: &IpPort) -> Option<RemoveAddress> { -490 self.local_addresses.remove(address).map(RemoveAddress::new) -491 } -492 -493 /// Returns the current NAT traversal round number. -494 pub(crate) fn current_round(&self) -> VarInt { -495 self.round -496 } -497 -498 /// Handles a received REACH_OUT frame. -499 /// -500 /// This might ignore the reach out frame if it belongs to an older round or if the -501 /// frame contains an IPv6 address while the local socket is IPv4-only. -502 /// -503 /// If a new round was started, the `NatTraversalProbeRetry` timer needs to be reset. -504 pub(crate) fn handle_reach_out( -505 &mut self, -506 reach_out: ReachOut, -507 ipv6: bool, -508 ) -> Result<(), Error> { -509 let ReachOut { round, ip, port } = reach_out; -510 -511 if round < self.round { -512 trace!(current_round=%self.round, "ignoring REACH_OUT for previous round"); -513 return Ok(()); -514 } -515 let Some(ip) = map_to_local_socket_family(ip, ipv6) else { -516 trace!("Ignoring IPv6 REACH_OUT frame due to not supporting IPv6 locally"); -517 return Ok(()); -518 }; -519 -520 if round > self.round { -521 self.round = round; -522 self.remotes.clear(); -523 self.sent_challenges.clear(); -524 self.pending_probes.clear(); -525 } else if self.remotes.contains_key(&(ip, port)) { -526 // Retransmitted frame. -527 return Ok(()); -528 } else if self.remotes.len() >= self.max_remote_addresses { -529 return Err(Error::TooManyAddresses); -530 } -531 self.remotes -532 .entry((ip, port)) -533 .or_insert(ProbeState::Active(MAX_NAT_PROBE_ATTEMPTS - 1)); -534 self.pending_probes.insert((ip, port)); -535 Ok(()) -536 } -537 -538 /// Re-queues probes that have not yet succeeded or reached [`MAX_NAT_PROBE_ATTEMPTS`]. -539 /// -540 /// Returns whether any probes are now queued to send. In this case the -541 /// `NatTraversalProbeRetry` timer needs to be reset. -542 pub(crate) fn queue_retries(&mut self) -> bool { -543 self.remotes -544 .iter_mut() -545 .for_each(|(remote, state)| match state { -546 ProbeState::Active(remaining) if *remaining > 0 => { -547 *remaining -= 1; -548 self.pending_probes.insert(*remote); -549 } -550 ProbeState::Active(_) | ProbeState::Succeeded => (), -551 }); -552 !self.pending_probes.is_empty() -553 } -554 -555 /// Returns the next ready probe's address. -556 /// -557 /// If this is actually sent you must call [`Self::mark_probe_sent`]. -558 pub(crate) fn next_probe_addr(&self) -> Option<SocketAddr> { -559 self.pending_probes.iter().next().map(|addr| (*addr).into()) -560 } -561 -562 /// Marks a probe as sent to the address with the challenge. -563 pub(crate) fn mark_probe_sent(&mut self, remote: IpPort, challenge: u64) { -564 self.pending_probes.remove(&remote); -565 self.sent_challenges.insert(challenge, remote); -566 } -567 -568 /// Marks a remote as successful if the response matches a sent probe. -569 /// -570 /// Returns `true` if it was a response to one of the NAT traversal probes. -571 pub(crate) fn handle_path_response(&mut self, src: FourTuple, challenge: u64) -> bool { -572 if let Entry::Occupied(entry) = self.sent_challenges.entry(challenge) { -573 let remote = (src.remote().ip(), src.remote().port()); -574 if *entry.get() == remote { -575 entry.remove(); -576 self.remotes.insert(remote, ProbeState::Succeeded); -577 return true; +294 /// This is set by the remote endpoint. +295 max_local_addresses: usize, +296 /// Candidate addresses the remote endpoint advertises. +297 /// +298 /// These are addresses on which the server is potentially reachable, to use for NAT +299 /// traversal attempts. +300 /// +301 /// They are indexed by their ADD_ADDRESS sequence id and stored in **canonical +302 /// form**. Not in the socket-native form as usual. This because we need to store them +303 /// so we have the correct sequence IDs. +304 remote_addresses: FxHashMap<VarInt, (CanonicalIpPort, ProbeState)>, +305 /// Candidate addresses for the local endpoint. +306 /// +307 /// These are addresses on which we are potentially reachable, to use for NAT traversal +308 /// attempts. +309 /// +310 /// They are stored in **canonical form**, not in socket-native form as usual. We may +311 /// nave a reflexive address that is IPv6 even if our local socket can only handle IPv4. +312 local_addresses: FxHashSet<CanonicalIpPort>, +313 /// Current nat traversal round. +314 round: VarInt, +315 /// The data of PATH_CHALLENGE frames sent in probes. +316 /// +317 /// These are cleared when a new round starts, so any late-arriving PATH_RESPONSEs will +318 /// have no effect. +319 /// +320 /// They are stored in the usual socket-native form. +321 sent_challenges: FxHashMap<u64, IpPort>, +322 /// Queued probes to be sent in the next [`poll_transmit`] call. +323 /// +324 /// [`poll_transmit`]: crate::connection::Connection::poll_transmit +325 /// +326 /// They are stored in the usual socket-native form. Probes to address families not +327 /// addressable by the family are never inserted. +328 pending_probes: FxHashSet<IpPort>, +329 /// Network paths that were successfully probed but not yet opened. +330 /// +331 /// When we do not have enough CIDs or free path IDs we might not have been able to open +332 /// a new path. This allows us to try re-open the path when we get new CIDs or a new +333 /// MAX_PATH_ID. +334 // TODO(flub): perhaps there should be a time-limit on these? +335 paths_to_be_opened: Vec<FourTuple>, +336} +337 +338impl ClientState { +339 fn new(max_remote_addresses: usize, max_local_addresses: usize) -> Self { +340 Self { +341 max_remote_addresses, +342 max_local_addresses, +343 remote_addresses: Default::default(), +344 local_addresses: Default::default(), +345 round: Default::default(), +346 sent_challenges: Default::default(), +347 pending_probes: Default::default(), +348 paths_to_be_opened: Default::default(), +349 } +350 } +351 +352 fn add_local_address(&mut self, address: SocketAddr) -> Result<(), Error> { +353 let address = CanonicalIpPort::from(address); +354 if self.local_addresses.len() < self.max_local_addresses { +355 self.local_addresses.insert(address); +356 Ok(()) +357 } else if self.local_addresses.contains(&address) { +358 // at capacity, but the address is known, no issues here +359 Ok(()) +360 } else { +361 // at capacity and the address is new +362 Err(Error::TooManyAddresses) +363 } +364 } +365 +366 fn remove_local_address(&mut self, address: &IpPort) { +367 let address = CanonicalIpPort::from(*address); +368 self.local_addresses.remove(&address); +369 } +370 +371 /// Initiates a new nat traversal round. +372 /// +373 /// A nat traversal round involves advertising the client's local addresses in +374 /// `REACH_OUT` frames, and initiating probing of the known remote addresses. When a new +375 /// round is initiated, the previous one is cancelled. +376 /// +377 /// `ipv6` indicates if the connection runs on a socket that supports IPv6. If so, then +378 /// all addresses returned [`PendingReachOutFrames`] will be IPv6 addresses (and +379 /// IPv4-mapped IPv6 addresses if necessary). Otherwise they're all IPv4 addresses. See +380 /// also [`map_to_local_socket_family`]. +381 /// +382 /// # Returns +383 /// +384 /// The REACH_OUT frames that need to be sent to the peer and the probed addresses. The +385 /// probed addresses are only informational, the pending probes are stored in +386 /// [`Self::pending_probes`]. +387 /// +388 /// If the probed addresses are non-empty the `NatTraversalProbeRetry` timer needs to be +389 /// set. +390 pub(crate) fn initiate_nat_traversal_round( +391 &mut self, +392 ipv6: bool, +393 ) -> Result<(PendingReachOutFrames, Vec<SocketAddr>), Error> { +394 if self.local_addresses.is_empty() { +395 return Err(Error::NotEnoughAddresses); +396 } +397 +398 self.round = self.round.saturating_add(1u8); +399 self.sent_challenges.clear(); +400 self.pending_probes.clear(); +401 +402 // Enqueue the NAT probes to known remote addresses. +403 self.remote_addresses +404 .values_mut() +405 .for_each(|(ip_port, state)| { +406 if let Some(ip_port) = ip_port.as_local_socket_family(ipv6) { +407 self.pending_probes.insert(ip_port); +408 *state = ProbeState::Active(MAX_NAT_PROBE_ATTEMPTS - 1); +409 } else { +410 trace!(%ip_port, "not using IPv6 NAT candidate for IPv4 socket"); +411 *state = ProbeState::Active(0); +412 } +413 }); +414 let probed_addrs: Vec<SocketAddr> = self +415 .pending_probes +416 .iter() +417 .copied() +418 .map(Into::into) +419 .collect(); +420 +421 // Build the REACH_OUT frames. +422 let reach_out_frames: PendingReachOutFrames = self +423 .local_addresses +424 .iter() +425 .map(|ip_port| ReachOut { +426 round: self.round, +427 ip: ip_port.ip(), +428 port: ip_port.port(), +429 }) +430 .collect(); +431 +432 trace!( +433 round = %self.round, +434 reach_out = %reach_out_frames.len(), +435 to_probe = %self.pending_probes.len(), +436 "initiating NAT traversal round", +437 ); +438 Ok((reach_out_frames, probed_addrs)) +439 } +440 +441 /// Re-queues probes that have not yet succeeded or reached 0 remaining retries. +442 /// +443 /// Returns whether any probes are now queued to send. In this case the +444 /// `NatTraversalProbeRetry` timer needs to be reset. +445 /// +446 /// `ipv6` as for [`Self::initiate_nat_traversal_round`]. +447 pub(crate) fn queue_retries(&mut self, ipv6: bool) -> bool { +448 self.remote_addresses +449 .values_mut() +450 .for_each(|(ip_port, state)| match state { +451 ProbeState::Active(remaining) if *remaining > 0 => { +452 *remaining -= 1; +453 if let Some(ip_port) = ip_port.as_local_socket_family(ipv6) { +454 self.pending_probes.insert(ip_port); +455 } else { +456 trace!(%ip_port, "skipping IPv6 NAT candidate for IPv4 socket"); +457 *remaining = 0; +458 } +459 } +460 ProbeState::Active(_) | ProbeState::Succeeded => {} +461 }); +462 !self.pending_probes.is_empty() +463 } +464 +465 /// Returns the next ready probe's address. +466 /// +467 /// If this is actually sent you must call [`Self::mark_probe_sent`]. +468 fn next_probe_addr(&self) -> Option<IpPort> { +469 self.pending_probes.iter().next().copied() +470 } +471 +472 /// Marks a probe as sent to the address with the challenge. +473 fn mark_probe_sent(&mut self, remote: IpPort, challenge: u64) { +474 self.pending_probes.remove(&remote); +475 self.sent_challenges.insert(challenge, remote); +476 } +477 +478 /// Adds an address to the remote set. +479 /// +480 /// On success returns the address if it was new to the set. It will error when the set +481 /// has no capacity for the address. +482 /// +483 /// If this is called while a round is in progress this will effectively add the address +484 /// to the current round. There is no guarantee however that the current round is still +485 /// in progress however, if the last [`Self::queue_retries`] call returned `false` the +486 /// round has stopped. +487 // TODO(flub): probably should add an event to signal that the round is finished, so +488 // that the application knows to start a new round. +489 pub(crate) fn add_remote_address( +490 &mut self, +491 add_addr: AddAddress, +492 ) -> Result<Option<SocketAddr>, Error> { +493 let AddAddress { seq_no, ip, port } = add_addr; +494 let address = CanonicalIpPort::from((ip, port)); +495 let allow_new = self.remote_addresses.len() < self.max_remote_addresses; +496 match self.remote_addresses.entry(seq_no) { +497 Entry::Occupied(mut occupied_entry) => { +498 let is_update = occupied_entry.get().0 != address; +499 if is_update { +500 occupied_entry.insert((address, ProbeState::Active(MAX_NAT_PROBE_ATTEMPTS))); +501 } +502 // The value might be different. This should not happen, but we assume that the new +503 // address is more recent than the previous, and thus worth updating +504 Ok(is_update.then_some(address.as_canonical_addr())) +505 } +506 Entry::Vacant(vacant_entry) if allow_new => { +507 vacant_entry.insert((address, ProbeState::Active(MAX_NAT_PROBE_ATTEMPTS))); +508 Ok(Some(address.as_canonical_addr())) +509 } +510 _ => Err(Error::TooManyAddresses), +511 } +512 } +513 +514 /// Removes an address from the remote set. +515 /// +516 /// Returns whether the address was present. +517 pub(crate) fn remove_remote_address( +518 &mut self, +519 remove_addr: RemoveAddress, +520 ) -> Option<SocketAddr> { +521 self.remote_addresses +522 .remove(&remove_addr.seq_no) +523 .map(|(address, _)| address.as_canonical_addr()) +524 } +525 +526 /// Checks that a received remote address is valid. +527 /// +528 /// An address is valid as long as it does not change the value of a known address id. +529 pub(crate) fn check_remote_address(&self, add_addr: &AddAddress) -> bool { +530 match self.remote_addresses.get(&add_addr.seq_no) { +531 None => true, +532 Some((existing, _)) => *existing == CanonicalIpPort::from(add_addr.ip_port()), +533 } +534 } +535 +536 pub(crate) fn get_remote_nat_traversal_addresses(&self) -> Vec<SocketAddr> { +537 self.remote_addresses +538 .values() +539 .map(|(address, _)| (*address).as_canonical_addr()) +540 .collect() +541 } +542 +543 /// Marks a remote as successful if the response matches a sent probe. +544 /// +545 /// Returns `true` if it was a response to one of the NAT traversal probes. In that case +546 /// [`Self::pop_pending_path_open`] should be called to open the next path. +547 fn handle_path_response(&mut self, network_path: FourTuple, challenge: u64) -> bool { +548 if let Entry::Occupied(entry) = self.sent_challenges.entry(challenge) { +549 let remote = (network_path.remote().ip(), network_path.remote().port()); +550 if *entry.get() == remote { +551 entry.remove(); +552 +553 // self.remote_addresses is stored in canonical form. +554 let remote = CanonicalIpPort::from(remote); +555 // TODO: linear search is sad. +556 if let Some(seq) = self +557 .remote_addresses +558 .iter() +559 .filter_map( +560 |(seq, (addr, _))| { +561 if *addr == remote { Some(*seq) } else { None } +562 }, +563 ) +564 .next() +565 { +566 trace!( +567 ?network_path, +568 challenge = %display(format_args!("0x{challenge:x}")), +569 "Received valid NAT traversal probe response", +570 ); +571 self.remote_addresses +572 .insert(seq, (remote, ProbeState::Succeeded)); +573 self.paths_to_be_opened.push(network_path); +574 return true; +575 } else { +576 debug!("inconsistent remote addrs and seq"); +577 } 578 } else { 579 debug!( -580 ?challenge, -581 ?src.remote, -582 "PATH_RESPONSE matched a NAT traversal probe but mismatching addr", -583 ) -584 } -585 } -586 false -587 } -588} +580 ?network_path.remote, +581 expected_remote = ?entry.get(), +582 challenge = %display(format_args!("0x{challenge:x}")), +583 "PATH_RESPONSE matched a NAT traversal probe but mismatching addr", +584 ) +585 } +586 } +587 false +588 } 589 -590/// Returns the given address as canonicalized IP address. -591/// -592/// This checks that the address family is supported by our local socket. -593/// If it is supported, then the address is mapped to the respective IP address. -594/// If the given address is an IPv6 address, but our local socket doesn't support -595/// IPv6, then this returns `None`. -596pub(crate) fn map_to_local_socket_family(address: IpAddr, ipv6: bool) -> Option<IpAddr> { -597 let ip = match address { -598 IpAddr::V4(addr) if ipv6 => IpAddr::V6(addr.to_ipv6_mapped()), -599 IpAddr::V4(_) => address, -600 IpAddr::V6(_) if ipv6 => address, -601 IpAddr::V6(addr) => IpAddr::V4(addr.to_ipv4_mapped()?), -602 }; -603 Some(ip) -604} -605 -606#[cfg(test)] -607mod tests { -608 use super::*; -609 -610 #[test] -611 fn test_basic_server_state() { -612 let mut state = ServerState::new(2, 2); -613 -614 state -615 .handle_reach_out( -616 ReachOut { -617 round: 1u32.into(), -618 ip: std::net::Ipv4Addr::LOCALHOST.into(), -619 port: 1, -620 }, -621 true, -622 ) -623 .unwrap(); -624 -625 state -626 .handle_reach_out( -627 ReachOut { -628 round: 1u32.into(), -629 ip: "1.1.1.1".parse().unwrap(), //std::net::Ipv4Addr::LOCALHOST.into(), -630 port: 2, -631 }, -632 true, -633 ) -634 .unwrap(); -635 -636 dbg!(&state); -637 assert_eq!(state.pending_probes.len(), 2); -638 -639 // Helper: send next ready probe -640 let mut challenge = 0; -641 let mut send_probe = |state: &mut ServerState| { -642 let remote = state.next_probe_addr().unwrap(); -643 challenge += 1; -644 state.mark_probe_sent((remote.ip(), remote.port()), challenge); -645 }; -646 -647 send_probe(&mut state); -648 send_probe(&mut state); -649 -650 // After sending both probes, no ready probes remain but they're still tracked. -651 assert!(state.next_probe_addr().is_none()); -652 -653 // After queuing retries, probes become available again -654 assert!(state.queue_retries()); -655 send_probe(&mut state); -656 send_probe(&mut state); -657 -658 // After 2 attempts each, retries still available (max is 10) -659 assert!(state.queue_retries()); -660 send_probe(&mut state); -661 send_probe(&mut state); -662 -663 // Exhaust remaining attempts -664 for _ in 3..MAX_NAT_PROBE_ATTEMPTS { -665 assert!(state.queue_retries()); -666 send_probe(&mut state); -667 send_probe(&mut state); -668 } -669 -670 // After max attempts, probes are removed -671 assert!(!state.queue_retries()); -672 assert!(state.next_probe_addr().is_none()); -673 } -674 -675 #[test] -676 fn test_map_to_local_socket() { -677 assert_eq!( -678 map_to_local_socket_family("1.1.1.1".parse().unwrap(), false), -679 Some("1.1.1.1".parse().unwrap()) -680 ); -681 assert_eq!( -682 map_to_local_socket_family("1.1.1.1".parse().unwrap(), true), -683 Some("::ffff:1.1.1.1".parse().unwrap()) -684 ); -685 assert_eq!( -686 map_to_local_socket_family("::1".parse().unwrap(), true), -687 Some("::1".parse().unwrap()) -688 ); -689 assert_eq!( -690 map_to_local_socket_family("::1".parse().unwrap(), false), -691 None -692 ); -693 assert_eq!( -694 map_to_local_socket_family("::ffff:1.1.1.1".parse().unwrap(), false), -695 Some("1.1.1.1".parse().unwrap()) -696 ) -697 } -698} \ No newline at end of file +590 /// Returns a path that was NAT traversed and needs to be opened. +591 pub(crate) fn pop_pending_path_open(&mut self) -> Option<FourTuple> { +592 self.paths_to_be_opened.pop() +593 } +594 +595 /// Put back a path that needs to be opened, e.g. for a temporary failure. +596 pub(crate) fn push_pending_path_open(&mut self, network_path: FourTuple) { +597 self.paths_to_be_opened.push(network_path) +598 } +599} +600 +601/// Maximum number of times we send a NAT probe to the same remote address in a round. +602/// +603/// This is a trade-off between several factors: +604/// - Probe packets could be lost. This allows recovery. +605/// - We may need two probes to reach the NAT firewall to get through. +606/// - We may be sending probes to innocent bystanders on the internet. +607/// - A round never "finishes": probing of remotes only stops when: +608/// 1. A new round is started. +609/// 2. A probe was successful. +610/// 3. This number of attempts is exhausted. +611/// +612/// Currently probes are retried after 2/3rd of the configured initial RTT. At a +613/// fixed interval, so without exponential backoff. For the default initial RTT of 333ms +614/// this is 222ms. So 10 attempts covers 2220ms. +615// TODO(flub): I would like to improve this sometime so that we cover about 2s but with only +616// about 5-6 probes. The three initial probes should be faster, later probes should start +617// to slow down. Unfortunately we only have one timer for the entire round currently, we +618// would need to have a timer per remote. Because REACH_OUT frames can appear in the +619// middle of a round. +620pub(crate) const MAX_NAT_PROBE_ATTEMPTS: u8 = 10; +621 +622/// State of an off-path NAT traversal probe to a remote address. +623#[derive(Debug)] +624enum ProbeState { +625 /// The remote still needs to be probed in this round. +626 /// +627 /// The remaining number of retries are stored in the `u8`. +628 Active(u8), +629 /// We received a probe response for this remote. +630 Succeeded, +631} +632 +633#[derive(Debug)] +634pub(crate) struct ServerState { +635 /// Max number of remote addresses we allow. +636 /// +637 /// This is set by the local endpoint. +638 max_remote_addresses: usize, +639 /// Max number of local addresses allowed. +640 /// +641 /// This is set by the remote endpoint. +642 max_local_addresses: usize, +643 /// Candidate addresses the server reports as potentially reachable, to use for nat +644 /// traversal attempts. +645 /// +646 /// They are stored in **canonical form**, not in socket-native form as usual. We may +647 /// nave a reflexive address that is IPv6 even if our local socket can only handle IPv4. +648 local_addresses: FxHashMap<CanonicalIpPort, VarInt>, +649 /// The next id to use for local addresses sent to the client. +650 next_local_addr_id: VarInt, +651 /// Current nat traversal round +652 /// +653 /// Servers keep track of the client's most recent round and cancel probing related to previous +654 /// rounds. +655 round: VarInt, +656 /// The remote addresses participating in this round. +657 /// +658 /// The set is cleared when a new round starts. +659 /// +660 /// These are stored in the usual local-socket native form. +661 remotes: FxHashMap<IpPort, ProbeState>, +662 /// The data of PATH_CHALLENGE frames sent in probes. +663 /// +664 /// These are cleared when a new round starts, so any late-arriving PATH_RESPONSEs will +665 /// have no effect. +666 sent_challenges: FxHashMap<u64, IpPort>, +667 /// Queued probes to be sent in the next [`poll_transmit`] call. +668 /// +669 /// At the beginning of a round this is populated from REACH_OUT frames and at every +670 /// retry this is populated from [`Self::remotes`]. +671 /// +672 /// [`poll_transmit`]: crate::connection::Connection::poll_transmit +673 pending_probes: FxHashSet<IpPort>, +674} +675 +676impl ServerState { +677 fn new(max_remote_addresses: usize, max_local_addresses: usize) -> Self { +678 Self { +679 max_remote_addresses, +680 max_local_addresses, +681 local_addresses: Default::default(), +682 next_local_addr_id: Default::default(), +683 round: Default::default(), +684 remotes: Default::default(), +685 sent_challenges: Default::default(), +686 pending_probes: Default::default(), +687 } +688 } +689 +690 fn add_local_address(&mut self, address: SocketAddr) -> Result<Option<AddAddress>, Error> { +691 let address = CanonicalIpPort::from(address); +692 let allow_new = self.local_addresses.len() < self.max_local_addresses; +693 match self.local_addresses.entry(address) { +694 Entry::Occupied(_) => Ok(None), +695 Entry::Vacant(vacant_entry) if allow_new => { +696 let id = self.next_local_addr_id; +697 self.next_local_addr_id = self.next_local_addr_id.saturating_add(1u8); +698 vacant_entry.insert(id); +699 Ok(Some(AddAddress::new((address.ip(), address.port()), id))) +700 } +701 _ => Err(Error::TooManyAddresses), +702 } +703 } +704 +705 fn remove_local_address(&mut self, address: &IpPort) -> Option<RemoveAddress> { +706 let address = CanonicalIpPort::from(*address); +707 self.local_addresses +708 .remove(&address) +709 .map(RemoveAddress::new) +710 } +711 +712 /// Returns the current NAT traversal round number. +713 pub(crate) fn current_round(&self) -> VarInt { +714 self.round +715 } +716 +717 /// Handles a received REACH_OUT frame. +718 /// +719 /// This might ignore the reach out frame if it belongs to an older round or if the +720 /// frame contains an IPv6 address while the local socket is IPv4-only. +721 /// +722 /// If a new round was started, the `NatTraversalProbeRetry` timer needs to be reset. +723 pub(crate) fn handle_reach_out( +724 &mut self, +725 reach_out: ReachOut, +726 ipv6: bool, +727 ) -> Result<(), Error> { +728 let ReachOut { round, ip, port } = reach_out; +729 +730 if round < self.round { +731 trace!(current_round=%self.round, "ignoring REACH_OUT for previous round"); +732 return Ok(()); +733 } +734 let Some(ip) = map_to_local_socket_family(ip, ipv6) else { +735 trace!("Ignoring IPv6 REACH_OUT frame due to not supporting IPv6 locally"); +736 return Ok(()); +737 }; +738 +739 if round > self.round { +740 self.round = round; +741 self.remotes.clear(); +742 self.sent_challenges.clear(); +743 self.pending_probes.clear(); +744 } else if self.remotes.contains_key(&(ip, port)) { +745 // Retransmitted frame. +746 return Ok(()); +747 } else if self.remotes.len() >= self.max_remote_addresses { +748 return Err(Error::TooManyAddresses); +749 } +750 self.remotes +751 .entry((ip, port)) +752 .or_insert(ProbeState::Active(MAX_NAT_PROBE_ATTEMPTS - 1)); +753 self.pending_probes.insert((ip, port)); +754 Ok(()) +755 } +756 +757 /// Re-queues probes that have not yet succeeded or reached [`MAX_NAT_PROBE_ATTEMPTS`]. +758 /// +759 /// Returns whether any probes are now queued to send. In this case the +760 /// `NatTraversalProbeRetry` timer needs to be reset. +761 pub(crate) fn queue_retries(&mut self) -> bool { +762 self.remotes +763 .iter_mut() +764 .for_each(|(remote, state)| match state { +765 ProbeState::Active(remaining) if *remaining > 0 => { +766 *remaining -= 1; +767 self.pending_probes.insert(*remote); +768 } +769 ProbeState::Active(_) | ProbeState::Succeeded => (), +770 }); +771 !self.pending_probes.is_empty() +772 } +773 +774 /// Returns the next ready probe's address. +775 /// +776 /// If this is actually sent you must call [`Self::mark_probe_sent`]. +777 fn next_probe_addr(&self) -> Option<IpPort> { +778 self.pending_probes.iter().next().cloned() +779 } +780 +781 /// Marks a probe as sent to the address with the challenge. +782 fn mark_probe_sent(&mut self, remote: IpPort, challenge: u64) { +783 self.pending_probes.remove(&remote); +784 self.sent_challenges.insert(challenge, remote); +785 } +786 +787 /// Marks a remote as successful if the response matches a sent probe. +788 /// +789 /// Returns `true` if it was a response to one of the NAT traversal probes. +790 fn handle_path_response(&mut self, src: FourTuple, challenge: u64) -> bool { +791 if let Entry::Occupied(entry) = self.sent_challenges.entry(challenge) { +792 let remote = (src.remote().ip(), src.remote().port()); +793 if *entry.get() == remote { +794 entry.remove(); +795 self.remotes.insert(remote, ProbeState::Succeeded); +796 return true; +797 } else { +798 debug!( +799 ?challenge, +800 ?src.remote, +801 "PATH_RESPONSE matched a NAT traversal probe but mismatching addr", +802 ) +803 } +804 } +805 false +806 } +807} +808 +809/// Returns the given address as canonicalized IP address. +810/// +811/// This checks that the address family is supported by our local socket. +812/// If it is supported, then the address is mapped to the respective IP address. +813/// If the given address is an IPv6 address, but our local socket doesn't support +814/// IPv6, then this returns `None`. +815pub(crate) fn map_to_local_socket_family(address: IpAddr, ipv6: bool) -> Option<IpAddr> { +816 let ip = match address { +817 IpAddr::V4(addr) if ipv6 => IpAddr::V6(addr.to_ipv6_mapped()), +818 IpAddr::V4(_) => address, +819 IpAddr::V6(_) if ipv6 => address, +820 IpAddr::V6(addr) => IpAddr::V4(addr.to_ipv4_mapped()?), +821 }; +822 Some(ip) +823} +824 +825#[cfg(test)] +826mod tests { +827 use super::*; +828 +829 #[test] +830 fn test_basic_server_state() { +831 let mut state = ServerState::new(2, 2); +832 +833 state +834 .handle_reach_out( +835 ReachOut { +836 round: 1u32.into(), +837 ip: std::net::Ipv4Addr::LOCALHOST.into(), +838 port: 1, +839 }, +840 true, +841 ) +842 .unwrap(); +843 +844 state +845 .handle_reach_out( +846 ReachOut { +847 round: 1u32.into(), +848 ip: "1.1.1.1".parse().unwrap(), //std::net::Ipv4Addr::LOCALHOST.into(), +849 port: 2, +850 }, +851 true, +852 ) +853 .unwrap(); +854 +855 dbg!(&state); +856 assert_eq!(state.pending_probes.len(), 2); +857 +858 // Helper: send next ready probe +859 let mut challenge = 0; +860 let mut send_probe = |state: &mut ServerState| { +861 let remote = state.next_probe_addr().unwrap(); +862 challenge += 1; +863 state.mark_probe_sent(remote, challenge); +864 }; +865 +866 send_probe(&mut state); +867 send_probe(&mut state); +868 +869 // After sending both probes, no ready probes remain but they're still tracked. +870 assert!(state.next_probe_addr().is_none()); +871 +872 // After queuing retries, probes become available again +873 assert!(state.queue_retries()); +874 send_probe(&mut state); +875 send_probe(&mut state); +876 +877 // After 2 attempts each, retries still available (max is 10) +878 assert!(state.queue_retries()); +879 send_probe(&mut state); +880 send_probe(&mut state); +881 +882 // Exhaust remaining attempts +883 for _ in 3..MAX_NAT_PROBE_ATTEMPTS { +884 assert!(state.queue_retries()); +885 send_probe(&mut state); +886 send_probe(&mut state); +887 } +888 +889 // After max attempts, probes are removed +890 assert!(!state.queue_retries()); +891 assert!(state.next_probe_addr().is_none()); +892 } +893 +894 #[test] +895 fn test_map_to_local_socket() { +896 assert_eq!( +897 map_to_local_socket_family("1.1.1.1".parse().unwrap(), false), +898 Some("1.1.1.1".parse().unwrap()) +899 ); +900 assert_eq!( +901 map_to_local_socket_family("1.1.1.1".parse().unwrap(), true), +902 Some("::ffff:1.1.1.1".parse().unwrap()) +903 ); +904 assert_eq!( +905 map_to_local_socket_family("::1".parse().unwrap(), true), +906 Some("::1".parse().unwrap()) +907 ); +908 assert_eq!( +909 map_to_local_socket_family("::1".parse().unwrap(), false), +910 None +911 ); +912 assert_eq!( +913 map_to_local_socket_family("::ffff:1.1.1.1".parse().unwrap(), false), +914 Some("1.1.1.1".parse().unwrap()) +915 ) +916 } +917} \ No newline at end of file diff --git a/pr/617/docs/src/perf/server.rs.html b/pr/617/docs/src/perf/server.rs.html index ef6348b3f..ea8fd4c49 100644 --- a/pr/617/docs/src/perf/server.rs.html +++ b/pr/617/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_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