diff options
Diffstat (limited to 'chromium/net/third_party/quiche')
355 files changed, 13102 insertions, 5415 deletions
diff --git a/chromium/net/third_party/quiche/BUILD.gn b/chromium/net/third_party/quiche/BUILD.gn index 41d2dfff1d9..eb544f93d1c 100644 --- a/chromium/net/third_party/quiche/BUILD.gn +++ b/chromium/net/third_party/quiche/BUILD.gn @@ -31,17 +31,22 @@ source_set("quiche") { sources = [ "overrides/quiche_platform_impl/quic_mutex_impl.cc", "overrides/quiche_platform_impl/quic_mutex_impl.h", + "overrides/quiche_platform_impl/quiche_bug_tracker_impl.h", "overrides/quiche_platform_impl/quiche_export_impl.h", "overrides/quiche_platform_impl/quiche_logging_impl.h", + "overrides/quiche_platform_impl/quiche_thread_local_impl.h", "overrides/quiche_platform_impl/quiche_time_utils_impl.cc", "overrides/quiche_platform_impl/quiche_time_utils_impl.h", + "src/common/platform/api/quiche_bug_tracker.h", "src/common/platform/api/quiche_export.h", "src/common/platform/api/quiche_flag_utils.h", "src/common/platform/api/quiche_flags.h", "src/common/platform/api/quiche_logging.h", "src/common/platform/api/quiche_prefetch.h", + "src/common/platform/api/quiche_thread_local.h", "src/common/platform/api/quiche_time_utils.h", "src/common/platform/default/quiche_platform_impl/quiche_prefetch_impl.h", + "src/common/print_elements.h", "src/common/quiche_circular_deque.h", "src/common/quiche_data_reader.cc", "src/common/quiche_data_reader.h", @@ -140,13 +145,10 @@ source_set("quiche") { "src/http2/http2_structures.cc", "src/http2/http2_structures.h", "src/http2/platform/api/http2_bug_tracker.h", - "src/http2/platform/api/http2_containers.h", - "src/http2/platform/api/http2_estimate_memory_usage.h", "src/http2/platform/api/http2_flag_utils.h", "src/http2/platform/api/http2_flags.h", "src/http2/platform/api/http2_logging.h", "src/http2/platform/api/http2_macros.h", - "src/http2/platform/api/http2_string_utils.h", "src/quic/core/congestion_control/bandwidth_sampler.cc", "src/quic/core/congestion_control/bandwidth_sampler.h", "src/quic/core/congestion_control/bbr2_drain.cc", @@ -433,6 +435,8 @@ source_set("quiche") { "src/quic/core/quic_config.h", "src/quic/core/quic_connection.cc", "src/quic/core/quic_connection.h", + "src/quic/core/quic_connection_context.cc", + "src/quic/core/quic_connection_context.h", "src/quic/core/quic_connection_id.cc", "src/quic/core/quic_connection_id.h", "src/quic/core/quic_connection_id_manager.cc", @@ -463,6 +467,7 @@ source_set("quiche") { "src/quic/core/quic_datagram_queue.h", "src/quic/core/quic_error_codes.cc", "src/quic/core/quic_error_codes.h", + "src/quic/core/quic_flags_list.h", "src/quic/core/quic_flow_controller.cc", "src/quic/core/quic_flow_controller.h", "src/quic/core/quic_framer.cc", @@ -552,11 +557,8 @@ source_set("quiche") { "src/quic/platform/api/quic_client_stats.h", "src/quic/platform/api/quic_containers.h", "src/quic/platform/api/quic_error_code_wrappers.h", - "src/quic/platform/api/quic_estimate_memory_usage.h", "src/quic/platform/api/quic_export.h", "src/quic/platform/api/quic_exported_stats.h", - "src/quic/platform/api/quic_file_utils.cc", - "src/quic/platform/api/quic_file_utils.h", "src/quic/platform/api/quic_flag_utils.h", "src/quic/platform/api/quic_flags.h", "src/quic/platform/api/quic_hostname_utils.cc", @@ -566,7 +568,6 @@ source_set("quiche") { "src/quic/platform/api/quic_ip_address.h", "src/quic/platform/api/quic_ip_address_family.h", "src/quic/platform/api/quic_logging.h", - "src/quic/platform/api/quic_map_util.h", "src/quic/platform/api/quic_mem_slice.h", "src/quic/platform/api/quic_mem_slice_span.h", "src/quic/platform/api/quic_mem_slice_storage.h", @@ -633,8 +634,6 @@ source_set("quiche") { "src/spdy/core/spdy_simple_arena.cc", "src/spdy/core/spdy_simple_arena.h", "src/spdy/core/zero_copy_output_buffer.h", - "src/spdy/platform/api/spdy_containers.h", - "src/spdy/platform/api/spdy_estimate_memory_usage.h", ] deps = [ "//net:net_deps" ] @@ -859,6 +858,7 @@ source_set("quiche_test_tools_core") { testonly = true sources = [ "src/common/platform/api/quiche_test.h", + "src/common/platform/api/quiche_test_helpers.h", "src/common/test_tools/quiche_test_utils.cc", "src/common/test_tools/quiche_test_utils.h", ] @@ -869,6 +869,17 @@ source_set("quiche_test_tools_core") { ] } +source_set("quiche_file_utils") { + sources = [ + "src/common/platform/api/quiche_file_utils.cc", + "src/common/platform/api/quiche_file_utils.h", + "src/common/platform/default/quiche_platform_impl/quiche_file_utils_impl.cc", + "src/common/platform/default/quiche_platform_impl/quiche_file_utils_impl.h", + ] + configs += [ ":quiche_config" ] + deps = [ "//base" ] +} + source_set("quic_test_tools_core") { testonly = true sources = [ @@ -1005,6 +1016,7 @@ source_set("quic_test_tools_core") { ] deps = [ + ":quiche_file_utils", "//base", "//crypto:test_support", "//net", @@ -1100,6 +1112,7 @@ source_set("simple_quic_tools_core") { "src/quic/tools/simple_ticket_crypter.h", ] deps = [ + ":quiche_file_utils", "//base", "//base/third_party/dynamic_annotations", "//net", @@ -1136,6 +1149,7 @@ if (!is_ios) { executable("quic_packet_printer") { sources = [ "src/quic/tools/quic_packet_printer_bin.cc" ] deps = [ + ":quiche_file_utils", "//base", "//build/win:default_exe_manifest", "//net", @@ -1185,6 +1199,8 @@ if (!is_ios) { source_set("quiche_tests") { testonly = true sources = [ + # TODO(bnc): Include in tests after test data files are added to QUICHE. + # "src/common/platform/api/quiche_file_utils_test.cc", "src/common/platform/api/quiche_time_utils_test.cc", "src/common/quiche_circular_deque_test.cc", "src/common/quiche_data_writer_test.cc", @@ -1312,6 +1328,7 @@ source_set("quiche_tests") { "src/quic/core/frames/quic_frames_test.cc", "src/quic/core/http/http_decoder_test.cc", "src/quic/core/http/http_encoder_test.cc", + "src/quic/core/http/http_frames_test.cc", "src/quic/core/http/quic_client_promised_info_test.cc", "src/quic/core/http/quic_client_push_promise_index_test.cc", "src/quic/core/http/quic_header_list_test.cc", @@ -1351,6 +1368,7 @@ source_set("quiche_tests") { "src/quic/core/quic_chaos_protector_test.cc", "src/quic/core/quic_coalesced_packet_test.cc", "src/quic/core/quic_config_test.cc", + "src/quic/core/quic_connection_context_test.cc", "src/quic/core/quic_connection_id_manager_test.cc", "src/quic/core/quic_connection_id_test.cc", "src/quic/core/quic_connection_test.cc", @@ -1400,9 +1418,9 @@ source_set("quiche_tests") { "src/quic/core/quic_versions_test.cc", "src/quic/core/quic_write_blocked_list_test.cc", "src/quic/core/tls_chlo_extractor_test.cc", + "src/quic/core/tls_client_handshaker_test.cc", "src/quic/core/uber_quic_stream_id_manager_test.cc", "src/quic/core/uber_received_packet_manager_test.cc", - "src/quic/platform/api/quic_containers_test.cc", "src/quic/platform/api/quic_hostname_utils_test.cc", "src/quic/platform/api/quic_ip_address_test.cc", "src/quic/platform/api/quic_mem_slice_span_test.cc", @@ -1455,6 +1473,7 @@ source_set("quiche_tests") { ] deps = [ + ":quiche_file_utils", "//net", "//net:quic_test_tools", "//net:quiche_test_tools", diff --git a/chromium/net/third_party/quiche/overrides/quiche_platform_impl/quiche_bug_tracker_impl.h b/chromium/net/third_party/quiche/overrides/quiche_platform_impl/quiche_bug_tracker_impl.h new file mode 100644 index 00000000000..5ae25a09c77 --- /dev/null +++ b/chromium/net/third_party/quiche/overrides/quiche_platform_impl/quiche_bug_tracker_impl.h @@ -0,0 +1,20 @@ +// Copyright (c) 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#ifndef NET_THIRD_PARTY_QUICHE_OVERRIDES_QUICHE_PLATFORM_IMPL_QUICHE_BUG_TRACKER_IMPL_H_ +#define NET_THIRD_PARTY_QUICHE_OVERRIDES_QUICHE_PLATFORM_IMPL_QUICHE_BUG_TRACKER_IMPL_H_ + +#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h" + +#define QUICHE_BUG_IMPL(bug_id) QUIC_LOG(DFATAL) +#define QUICHE_BUG_IF_IMPL(bug_id, condition) QUIC_LOG_IF(DFATAL, condition) +#define QUICHE_PEER_BUG_IMPL(bug_id) QUIC_LOG(ERROR) +#define QUICHE_PEER_BUG_IF_IMPL(bug_id, condition) QUIC_LOG_IF(ERROR, condition) + +#define QUICHE_BUG_V2_IMPL(bug_id) QUIC_LOG(DFATAL) +#define QUICHE_BUG_IF_V2_IMPL(bug_id, condition) QUIC_LOG_IF(DFATAL, condition) +#define QUICHE_PEER_BUG_V2_IMPL(bug_id) QUIC_LOG(ERROR) +#define QUICHE_PEER_BUG_IF_V2_IMPL(bug_id, condition) \ + QUIC_LOG_IF(ERROR, condition) + +#endif // NET_THIRD_PARTY_QUICHE_OVERRIDES_QUICHE_PLATFORM_IMPL_QUICHE_BUG_TRACKER_IMPL_H_ diff --git a/chromium/net/third_party/quiche/overrides/quiche_platform_impl/quiche_logging_impl.h b/chromium/net/third_party/quiche/overrides/quiche_platform_impl/quiche_logging_impl.h index 8ff5e27f8ee..326ed930701 100644 --- a/chromium/net/third_party/quiche/overrides/quiche_platform_impl/quiche_logging_impl.h +++ b/chromium/net/third_party/quiche/overrides/quiche_platform_impl/quiche_logging_impl.h @@ -5,6 +5,8 @@ #ifndef NET_THIRD_PARTY_QUICHE_OVERRIDES_QUICHE_PLATFORM_IMPL_QUICHE_LOGGING_IMPL_H_ #define NET_THIRD_PARTY_QUICHE_OVERRIDES_QUICHE_PLATFORM_IMPL_QUICHE_LOGGING_IMPL_H_ +#include <vector> + #include "base/check_op.h" #include "base/logging.h" #include "base/notreached.h" diff --git a/chromium/net/third_party/quiche/overrides/quiche_platform_impl/quiche_thread_local_impl.h b/chromium/net/third_party/quiche/overrides/quiche_platform_impl/quiche_thread_local_impl.h new file mode 100644 index 00000000000..8be2972e5b6 --- /dev/null +++ b/chromium/net/third_party/quiche/overrides/quiche_platform_impl/quiche_thread_local_impl.h @@ -0,0 +1,27 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_THIRD_PARTY_QUICHE_OVERRIDES_QUICHE_PLATFORM_IMPL_QUICHE_THREAD_LOCAL_IMPL_H_ +#define NET_THIRD_PARTY_QUICHE_OVERRIDES_QUICHE_PLATFORM_IMPL_QUICHE_THREAD_LOCAL_IMPL_H_ + +#include "base/no_destructor.h" +#include "base/threading/thread_local.h" + +#define DEFINE_QUICHE_THREAD_LOCAL_POINTER_IMPL(name, type) \ + struct QuicheThreadLocalPointer_##name { \ + static ::base::ThreadLocalPointer<type>* Instance() { \ + static ::base::NoDestructor<::base::ThreadLocalPointer<type>> instance; \ + return instance.get(); \ + } \ + static type* Get() { return Instance()->Get(); } \ + static void Set(type* ptr) { Instance()->Set(ptr); } \ + } + +#define GET_QUICHE_THREAD_LOCAL_POINTER_IMPL(name) \ + QuicheThreadLocalPointer_##name::Get() + +#define SET_QUICHE_THREAD_LOCAL_POINTER_IMPL(name, value) \ + QuicheThreadLocalPointer_##name::Set(value) + +#endif // NET_THIRD_PARTY_QUICHE_OVERRIDES_QUICHE_PLATFORM_IMPL_QUICHE_THREAD_LOCAL_IMPL_H_ diff --git a/chromium/net/third_party/quiche/src/CONTRIBUTING.md b/chromium/net/third_party/quiche/src/CONTRIBUTING.md index 07824e04d4f..d8c6e48b7be 100644 --- a/chromium/net/third_party/quiche/src/CONTRIBUTING.md +++ b/chromium/net/third_party/quiche/src/CONTRIBUTING.md @@ -20,12 +20,13 @@ again. The QUICHE repository is currently not set up to accept pull requests directly. If you would like to make a contribution, please follow these steps: -1. Sign the Contributor License Agreement (see above). -2. Create a Gerrit pull request at <https://quiche-review.googlesource.com>. -3. Email a link to your pull request to <quiche-contribution@google.com>. -4. An engineer will review your pull request and merge it internally. +1. Sign the Contributor License Agreement (see above). +2. Create a Gerrit pull request at <https://quiche-review.googlesource.com>, or + a GitHub pull request at <https://github.com/google/quiche/pulls>. +3. Email a link to your pull request to <quiche-contribution@google.com>. +4. An engineer will review your pull request and merge it internally. ## Community Guidelines -This project follows [Google's Open Source Community -Guidelines](https://opensource.google.com/conduct/). +This project follows +[Google's Open Source Community Guidelines](https://opensource.google.com/conduct/). diff --git a/chromium/net/third_party/quiche/src/README.md b/chromium/net/third_party/quiche/src/README.md index 0c441923f54..60f24da2b09 100644 --- a/chromium/net/third_party/quiche/src/README.md +++ b/chromium/net/third_party/quiche/src/README.md @@ -1,8 +1,28 @@ # QUICHE -QUICHE (QUIC, Http/2, Etc) is Google's implementation of QUIC and related -protocols. It powers Chromium as well as Google's QUIC servers and some other -projects. QUICHE is only supported on little-endian platforms. +QUICHE stands for QUIC, Http/2, Etc. It is Google's production-ready +implementation of QUIC, HTTP/2, HTTP/3, and related protocols and tools. It +powers Google's servers, Chromium, Envoy, and other projects. It is actively +developed and maintained. -Code can be viewed in CodeSearch in Quiche and is imported into -[Chromium](https://source.chromium.org/chromium/chromium/src/+/master:net/third_party/quiche/src). +There are two public QUICHE repositories. Either one may be used by embedders, +as they are automatically kept in sync: + +* https://quiche.googlesource.com/quiche +* https://github.com/google/quiche + +To embed QUICHE in your project, platform APIs need to be implemented and build +files need to be created. Note that it is on the QUICHE team's roadmap to +include default implementation for all platform APIs and to open-source build +files. In the meanwhile, take a look at open source embedders like Chromium and +Envoy to get started: + +* Platform implementations in Chromium: + + [quic/platform](https://source.chromium.org/chromium/chromium/src/+/main:net/net/quic/platform/impl/) + + [http2/platform](https://source.chromium.org/chromium/chromium/src/+/main:net/net/http2/platform/impl/) + + [quiche/common/platform](https://source.chromium.org/chromium/chromium/src/+/main:net/quiche/net/quiche/common/platform/impl/) +* [Build file in Chromium](https://source.chromium.org/chromium/chromium/src/+/main:net/third_party/quiche/BUILD.gn) +* [Platform implementations in Envoy](https://github.com/envoyproxy/envoy/tree/master/source/common/quic/platform) +* [Build file in Envoy](https://github.com/envoyproxy/envoy/blob/main/bazel/external/quiche.BUILD) + +QUICHE is only supported on little-endian platforms. diff --git a/chromium/net/third_party/quiche/src/common/platform/api/quiche_bug_tracker.h b/chromium/net/third_party/quiche/src/common/platform/api/quiche_bug_tracker.h index 0e8e456209a..27ac0d20948 100644 --- a/chromium/net/third_party/quiche/src/common/platform/api/quiche_bug_tracker.h +++ b/chromium/net/third_party/quiche/src/common/platform/api/quiche_bug_tracker.h @@ -5,7 +5,7 @@ #ifndef QUICHE_COMMON_PLATFORM_API_QUICHE_BUG_TRACKER_H_ #define QUICHE_COMMON_PLATFORM_API_QUICHE_BUG_TRACKER_H_ -#include "net/quiche/common/platform/impl/quiche_bug_tracker_impl.h" +#include "quiche_platform_impl/quiche_bug_tracker_impl.h" #define QUICHE_BUG QUICHE_BUG_IMPL #define QUICHE_BUG_IF QUICHE_BUG_IF_IMPL diff --git a/chromium/net/third_party/quiche/src/common/platform/api/quiche_file_utils.cc b/chromium/net/third_party/quiche/src/common/platform/api/quiche_file_utils.cc new file mode 100644 index 00000000000..fc0c189382e --- /dev/null +++ b/chromium/net/third_party/quiche/src/common/platform/api/quiche_file_utils.cc @@ -0,0 +1,51 @@ +#include "common/platform/api/quiche_file_utils.h" + +#include "quiche_platform_impl/quiche_file_utils_impl.h" + +namespace quiche { + +std::string JoinPath(absl::string_view a, absl::string_view b) { + return JoinPathImpl(a, b); +} + +absl::optional<std::string> ReadFileContents(absl::string_view file) { + return ReadFileContentsImpl(file); +} + +bool EnumerateDirectory(absl::string_view path, + std::vector<std::string>& directories, + std::vector<std::string>& files) { + return EnumerateDirectoryImpl(path, directories, files); +} + +bool EnumerateDirectoryRecursivelyInner(absl::string_view path, + int recursion_limit, + std::vector<std::string>& files) { + if (recursion_limit < 0) { + return false; + } + + std::vector<std::string> local_files; + std::vector<std::string> directories; + if (!EnumerateDirectory(path, directories, local_files)) { + return false; + } + for (const std::string& directory : directories) { + if (!EnumerateDirectoryRecursivelyInner(JoinPath(path, directory), + recursion_limit - 1, files)) { + return false; + } + } + for (const std::string& file : local_files) { + files.push_back(JoinPath(path, file)); + } + return true; +} + +bool EnumerateDirectoryRecursively(absl::string_view path, + std::vector<std::string>& files) { + constexpr int kRecursionLimit = 20; + return EnumerateDirectoryRecursivelyInner(path, kRecursionLimit, files); +} + +} // namespace quiche diff --git a/chromium/net/third_party/quiche/src/common/platform/api/quiche_file_utils.h b/chromium/net/third_party/quiche/src/common/platform/api/quiche_file_utils.h new file mode 100644 index 00000000000..47723d19e92 --- /dev/null +++ b/chromium/net/third_party/quiche/src/common/platform/api/quiche_file_utils.h @@ -0,0 +1,40 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This header contains basic filesystem functions for use in unit tests and CLI +// tools. Note that those are not 100% suitable for production use, as in, they +// might be prone to race conditions and not always handle non-ASCII filenames +// correctly. +#ifndef QUICHE_COMMON_PLATFORM_API_QUICHE_FILE_UTILS_H_ +#define QUICHE_COMMON_PLATFORM_API_QUICHE_FILE_UTILS_H_ + +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" + +namespace quiche { + +// Join two paths in a platform-specific way. Returns |a| if |b| is empty, and +// vice versa. +std::string JoinPath(absl::string_view a, absl::string_view b); + +// Reads the entire file into the memory. +absl::optional<std::string> ReadFileContents(absl::string_view file); + +// Lists all files and directories in the directory specified by |path|. Returns +// true on success, false on failure. +bool EnumerateDirectory(absl::string_view path, + std::vector<std::string>& directories, + std::vector<std::string>& files); + +// Recursively enumerates all of the files in the directory and all of the +// internal subdirectories. Has a fairly small recursion limit. +bool EnumerateDirectoryRecursively(absl::string_view path, + std::vector<std::string>& files); + +} // namespace quiche + +#endif // QUICHE_COMMON_PLATFORM_API_QUICHE_FILE_UTILS_H_ diff --git a/chromium/net/third_party/quiche/src/common/platform/api/quiche_file_utils_test.cc b/chromium/net/third_party/quiche/src/common/platform/api/quiche_file_utils_test.cc new file mode 100644 index 00000000000..ddf1a4000b1 --- /dev/null +++ b/chromium/net/third_party/quiche/src/common/platform/api/quiche_file_utils_test.cc @@ -0,0 +1,86 @@ +#include "common/platform/api/quiche_file_utils.h" + +#include <vector> + +#include "absl/algorithm/container.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/strip.h" +#include "absl/types/optional.h" +#include "common/platform/api/quiche_test.h" + +namespace quiche { +namespace test { +namespace { + +using testing::UnorderedElementsAre; +using testing::UnorderedElementsAreArray; + +TEST(QuicheFileUtilsTest, ReadFileContents) { + std::string path = absl::StrCat(QuicheGetCommonSourcePath(), + "/platform/api/testdir/testfile"); + absl::optional<std::string> contents = ReadFileContents(path); + ASSERT_TRUE(contents.has_value()); + EXPECT_EQ(*contents, "This is a test file."); +} + +TEST(QuicheFileUtilsTest, ReadFileContentsFileNotFound) { + std::string path = + absl::StrCat(QuicheGetCommonSourcePath(), + "/platform/api/testdir/file-that-does-not-exist"); + absl::optional<std::string> contents = ReadFileContents(path); + EXPECT_FALSE(contents.has_value()); +} + +TEST(QuicheFileUtilsTest, EnumerateDirectory) { + std::string path = + absl::StrCat(QuicheGetCommonSourcePath(), "/platform/api/testdir"); + std::vector<std::string> dirs; + std::vector<std::string> files; + bool success = EnumerateDirectory(path, dirs, files); + EXPECT_TRUE(success); + EXPECT_THAT(files, UnorderedElementsAre("testfile", "README.md")); + EXPECT_THAT(dirs, UnorderedElementsAre("a")); +} + +TEST(QuicheFileUtilsTest, EnumerateDirectoryNoSuchDirectory) { + std::string path = absl::StrCat(QuicheGetCommonSourcePath(), + "/platform/api/testdir/no-such-directory"); + std::vector<std::string> dirs; + std::vector<std::string> files; + bool success = EnumerateDirectory(path, dirs, files); + EXPECT_FALSE(success); +} + +TEST(QuicheFileUtilsTest, EnumerateDirectoryNotADirectory) { + std::string path = absl::StrCat(QuicheGetCommonSourcePath(), + "/platform/api/testdir/testfile"); + std::vector<std::string> dirs; + std::vector<std::string> files; + bool success = EnumerateDirectory(path, dirs, files); + EXPECT_FALSE(success); +} + +TEST(QuicheFileUtilsTest, EnumerateDirectoryRecursively) { + std::vector<std::string> expected_paths = {"a/b/c/d/e", "a/subdir/testfile", + "a/z", "testfile", "README.md"}; + + std::string root_path = + absl::StrCat(QuicheGetCommonSourcePath(), "/platform/api/testdir"); + for (std::string& path : expected_paths) { + // For Windows, use Windows path separators. + if (JoinPath("a", "b") == "a\\b") { + absl::c_replace(path, '/', '\\'); + } + + path = JoinPath(root_path, path); + } + + std::vector<std::string> files; + bool success = EnumerateDirectoryRecursively(root_path, files); + EXPECT_TRUE(success); + EXPECT_THAT(files, UnorderedElementsAreArray(expected_paths)); +} + +} // namespace +} // namespace test +} // namespace quiche diff --git a/chromium/net/third_party/quiche/src/common/platform/api/quiche_test.h b/chromium/net/third_party/quiche/src/common/platform/api/quiche_test.h index 1af25df1ea5..efec6384db3 100644 --- a/chromium/net/third_party/quiche/src/common/platform/api/quiche_test.h +++ b/chromium/net/third_party/quiche/src/common/platform/api/quiche_test.h @@ -12,6 +12,18 @@ using QuicheTest = quiche::test::QuicheTest; template <class T> using QuicheTestWithParam = quiche::test::QuicheTestWithParamImpl<T>; +namespace quiche { +namespace test { + +// Returns the path to quiche/common directory where the test data could be +// located. +inline std::string QuicheGetCommonSourcePath() { + return QuicheGetCommonSourcePathImpl(); +} + +} // namespace test +} // namespace quiche + #define EXPECT_QUICHE_DEBUG_DEATH(condition, message) \ EXPECT_QUICHE_DEBUG_DEATH_IMPL(condition, message) diff --git a/chromium/net/third_party/quiche/src/common/platform/api/quiche_thread_local.h b/chromium/net/third_party/quiche/src/common/platform/api/quiche_thread_local.h new file mode 100644 index 00000000000..c2d58e1dd5e --- /dev/null +++ b/chromium/net/third_party/quiche/src/common/platform/api/quiche_thread_local.h @@ -0,0 +1,27 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_COMMON_PLATFORM_API_QUICHE_THREAD_LOCAL_H_ +#define QUICHE_COMMON_PLATFORM_API_QUICHE_THREAD_LOCAL_H_ + +#include "quiche_platform_impl/quiche_thread_local_impl.h" + +// Define a thread local |type*| with |name|. Conceptually, this is a +// +// static thread_local type* name = nullptr; +// +// It is wrapped in a macro because the thread_local keyword is banned from +// Chromium. +#define DEFINE_QUICHE_THREAD_LOCAL_POINTER(name, type) \ + DEFINE_QUICHE_THREAD_LOCAL_POINTER_IMPL(name, type) + +// Get the value of |name| for the current thread. +#define GET_QUICHE_THREAD_LOCAL_POINTER(name) \ + GET_QUICHE_THREAD_LOCAL_POINTER_IMPL(name) + +// Set the |value| of |name| for the current thread. +#define SET_QUICHE_THREAD_LOCAL_POINTER(name, value) \ + SET_QUICHE_THREAD_LOCAL_POINTER_IMPL(name, value) + +#endif // QUICHE_COMMON_PLATFORM_API_QUICHE_THREAD_LOCAL_H_ diff --git a/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quiche_bug_tracker_impl.h b/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quiche_bug_tracker_impl.h new file mode 100644 index 00000000000..de164aac15e --- /dev/null +++ b/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quiche_bug_tracker_impl.h @@ -0,0 +1,15 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUICHE_BUG_TRACKER_IMPL_H_ +#define QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUICHE_BUG_TRACKER_IMPL_H_ + +#include "common/platform/api/quiche_logging.h" + +#define QUICHE_BUG_IMPL(b) QUICHE_LOG(DFATAL) +#define QUICHE_BUG_IF_IMPL(b, condition) QUICHE_LOG_IF(DFATAL, condition) +#define QUICHE_PEER_BUG_IMPL(b) QUICHE_LOG(DFATAL) +#define QUICHE_PEER_BUG_IF_IMPL(b, condition) QUICHE_LOG_IF(DFATAL, condition) + +#endif // QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUICHE_BUG_TRACKER_IMPL_H_ diff --git a/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quiche_containers_impl.h b/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quiche_containers_impl.h deleted file mode 100644 index 2e18dd7a2f8..00000000000 --- a/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quiche_containers_impl.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUICHE_CONTAINERS_IMPL_H_ -#define QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUICHE_CONTAINERS_IMPL_H_ - -#include <deque> - -namespace quiche { - -// Represents a double-ended queue which may be backed by a list or a flat -// circular buffer. -// -// DOES NOT GUARANTEE POINTER OR ITERATOR STABILITY! -template <typename T> -using QuicheDequeImpl = std::deque<T>; - -} // namespace quiche - -#endif // QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUICHE_CONTAINERS_IMPL_H_ diff --git a/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quiche_estimate_memory_usage_impl.h b/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quiche_estimate_memory_usage_impl.h deleted file mode 100644 index 59087a725eb..00000000000 --- a/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quiche_estimate_memory_usage_impl.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUICHE_ESTIMATE_MEMORY_USAGE_IMPL_H_ -#define QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUICHE_ESTIMATE_MEMORY_USAGE_IMPL_H_ - -#include <cstddef> - -namespace quiche { - -// No-op implementation. -template <class T> -size_t QuicheEstimateMemoryUsageImpl(const T& /*object*/) { - return 0; -} - -} // namespace quiche - -#endif // QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUICHE_ESTIMATE_MEMORY_USAGE_IMPL_H_ diff --git a/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quiche_file_utils_impl.cc b/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quiche_file_utils_impl.cc new file mode 100644 index 00000000000..65965b222d5 --- /dev/null +++ b/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quiche_file_utils_impl.cc @@ -0,0 +1,182 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche_platform_impl/quiche_file_utils_impl.h" + +#if defined(_WIN32) +#include <windows.h> +#else +#include <dirent.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#endif // defined(_WIN32) + +#include <fstream> +#include <ios> +#include <iostream> + +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "absl/strings/strip.h" +#include "absl/types/optional.h" + +namespace quiche { + +#if defined(_WIN32) +std::string JoinPathImpl(absl::string_view a, absl::string_view b) { + if (a.empty()) { + return std::string(b); + } + if (b.empty()) { + return std::string(a); + } + // Win32 actually provides two different APIs for combining paths; one of them + // has issues that could potentially lead to buffer overflow, and another is + // not supported in Windows 7, which is why we're doing it manually. + a = absl::StripSuffix(a, "/"); + a = absl::StripSuffix(a, "\\"); + return absl::StrCat(a, "\\", b); +} +#else +std::string JoinPathImpl(absl::string_view a, absl::string_view b) { + if (a.empty()) { + return std::string(b); + } + if (b.empty()) { + return std::string(a); + } + return absl::StrCat(absl::StripSuffix(a, "/"), "/", b); +} +#endif // defined(_WIN32) + +absl::optional<std::string> ReadFileContentsImpl(absl::string_view file) { + std::ifstream input_file(std::string{file}, std::ios::binary); + if (!input_file || !input_file.is_open()) { + return absl::nullopt; + } + + input_file.seekg(0, std::ios_base::end); + auto file_size = input_file.tellg(); + if (!input_file) { + return absl::nullopt; + } + input_file.seekg(0, std::ios_base::beg); + + std::string output; + output.resize(file_size); + input_file.read(&output[0], file_size); + if (!input_file) { + return absl::nullopt; + } + + return output; +} + +#if defined(_WIN32) + +class ScopedDir { + public: + ScopedDir(HANDLE dir) : dir_(dir) {} + ~ScopedDir() { + if (dir_ != INVALID_HANDLE_VALUE) { + // The API documentation explicitly says that CloseHandle() should not be + // used on directory search handles. + FindClose(dir_); + dir_ = INVALID_HANDLE_VALUE; + } + } + + HANDLE get() { return dir_; } + + private: + HANDLE dir_; +}; + +bool EnumerateDirectoryImpl(absl::string_view path, + std::vector<std::string>& directories, + std::vector<std::string>& files) { + std::string path_owned(path); + + // Explicitly check that the directory we are trying to search is in fact a + // directory. + DWORD attributes = GetFileAttributesA(path_owned.c_str()); + if (attributes == INVALID_FILE_ATTRIBUTES) { + return false; + } + if ((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { + return false; + } + + std::string search_path = JoinPathImpl(path, "*"); + WIN32_FIND_DATAA file_data; + ScopedDir dir(FindFirstFileA(search_path.c_str(), &file_data)); + if (dir.get() == INVALID_HANDLE_VALUE) { + return GetLastError() == ERROR_FILE_NOT_FOUND; + } + do { + std::string filename(file_data.cFileName); + if (filename == "." || filename == "..") { + continue; + } + if ((file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { + directories.push_back(std::move(filename)); + } else { + files.push_back(std::move(filename)); + } + } while (FindNextFileA(dir.get(), &file_data)); + return GetLastError() == ERROR_NO_MORE_FILES; +} + +#else // defined(_WIN32) + +class ScopedDir { + public: + ScopedDir(DIR* dir) : dir_(dir) {} + ~ScopedDir() { + if (dir_ != nullptr) { + closedir(dir_); + dir_ = nullptr; + } + } + + DIR* get() { return dir_; } + + private: + DIR* dir_; +}; + +bool EnumerateDirectoryImpl(absl::string_view path, + std::vector<std::string>& directories, + std::vector<std::string>& files) { + std::string path_owned(path); + ScopedDir dir(opendir(path_owned.c_str())); + if (dir.get() == nullptr) { + return false; + } + + dirent* entry; + while ((entry = readdir(dir.get()))) { + const std::string filename(entry->d_name); + if (filename == "." || filename == "..") { + continue; + } + + const std::string entry_path = JoinPathImpl(path, filename); + struct stat stat_entry; + if (stat(entry_path.c_str(), &stat_entry) != 0) { + return false; + } + if (S_ISREG(stat_entry.st_mode)) { + files.push_back(std::move(filename)); + } else if (S_ISDIR(stat_entry.st_mode)) { + directories.push_back(std::move(filename)); + } + } + return true; +} + +#endif // defined(_WIN32) + +} // namespace quiche diff --git a/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quiche_file_utils_impl.h b/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quiche_file_utils_impl.h new file mode 100644 index 00000000000..ad5ff1a9084 --- /dev/null +++ b/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quiche_file_utils_impl.h @@ -0,0 +1,26 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUICHE_FILE_UTILS_IMPL_H_ +#define QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUICHE_FILE_UTILS_IMPL_H_ + +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" + +namespace quiche { + +std::string JoinPathImpl(absl::string_view a, absl::string_view b); + +absl::optional<std::string> ReadFileContentsImpl(absl::string_view file); + +bool EnumerateDirectoryImpl(absl::string_view path, + std::vector<std::string>& directories, + std::vector<std::string>& files); + +} // namespace quiche + +#endif // QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUICHE_FILE_UTILS_IMPL_H_ diff --git a/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quiche_thread_local_impl.h b/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quiche_thread_local_impl.h new file mode 100644 index 00000000000..5ebea4c5bfb --- /dev/null +++ b/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quiche_thread_local_impl.h @@ -0,0 +1,24 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUICHE_THREAD_LOCAL_IMPL_H_ +#define QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUICHE_THREAD_LOCAL_IMPL_H_ + +#define DEFINE_QUICHE_THREAD_LOCAL_POINTER_IMPL(name, type) \ + struct QuicheThreadLocalPointer_##name { \ + static type** Instance() { \ + static thread_local type* instance = nullptr; \ + return &instance; \ + } \ + static type* Get() { return *Instance(); } \ + static void Set(type* ptr) { *Instance() = ptr; } \ + } + +#define GET_QUICHE_THREAD_LOCAL_POINTER_IMPL(name) \ + QuicheThreadLocalPointer_##name::Get() + +#define SET_QUICHE_THREAD_LOCAL_POINTER_IMPL(name, value) \ + QuicheThreadLocalPointer_##name::Set(value) + +#endif // QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUICHE_THREAD_LOCAL_IMPL_H_ diff --git a/chromium/net/third_party/quiche/src/common/print_elements.h b/chromium/net/third_party/quiche/src/common/print_elements.h new file mode 100644 index 00000000000..f241a4b9509 --- /dev/null +++ b/chromium/net/third_party/quiche/src/common/print_elements.h @@ -0,0 +1,35 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_COMMON_PRINT_ELEMENTS_H_ +#define QUICHE_COMMON_PRINT_ELEMENTS_H_ + +#include <ostream> +#include <sstream> +#include <string> + +#include "common/platform/api/quiche_export.h" + +namespace quiche { + +// Print elements of any iterable container that has cbegin() and cend() methods +// and the elements have operator<<(ostream) override. +template <typename T> +QUICHE_EXPORT_PRIVATE inline std::string PrintElements(const T& container) { + std::stringstream debug_string; + debug_string << "{"; + auto it = container.cbegin(); + debug_string << *it; + ++it; + while (it != container.cend()) { + debug_string << ", " << *it; + ++it; + } + debug_string << "}"; + return debug_string.str(); +} + +} // namespace quiche + +#endif // QUICHE_COMMON_PRINT_ELEMENTS_H_ diff --git a/chromium/net/third_party/quiche/src/http2/adapter/callback_visitor.cc b/chromium/net/third_party/quiche/src/http2/adapter/callback_visitor.cc index eaf4b6169d2..4381749d430 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/callback_visitor.cc +++ b/chromium/net/third_party/quiche/src/http2/adapter/callback_visitor.cc @@ -36,14 +36,63 @@ struct nghttp2_session_callbacks { namespace http2 { namespace adapter { +CallbackVisitor::CallbackVisitor(Perspective perspective, + const nghttp2_session_callbacks& callbacks, + void* user_data) + : perspective_(perspective), + callbacks_(MakeCallbacksPtr(nullptr)), + user_data_(user_data) { + nghttp2_session_callbacks* c; + nghttp2_session_callbacks_new(&c); + *c = callbacks; + callbacks_ = MakeCallbacksPtr(c); +} + +ssize_t CallbackVisitor::OnReadyToSend(absl::string_view serialized) { + if (!callbacks_->send_callback) { + return kSendError; + } + ssize_t result = callbacks_->send_callback( + nullptr, ToUint8Ptr(serialized.data()), serialized.size(), 0, user_data_); + QUICHE_VLOG(1) << "CallbackVisitor::OnReadyToSend returning " << result; + if (result > 0) { + return result; + } else if (result == NGHTTP2_ERR_WOULDBLOCK) { + return kSendBlocked; + } else { + return kSendError; + } +} + void CallbackVisitor::OnConnectionError() { - QUICHE_LOG(FATAL) << "Not implemented"; + QUICHE_LOG(ERROR) << "OnConnectionError not implemented"; } void CallbackVisitor::OnFrameHeader(Http2StreamId stream_id, size_t length, uint8_t type, uint8_t flags) { + QUICHE_VLOG(1) << "CallbackVisitor::OnFrameHeader(stream_id: " << stream_id + << ", len: " << length << ", type: " << int(type) + << ", flags: " << int(flags) << ")"; + if (static_cast<FrameType>(type) == FrameType::CONTINUATION) { + // Treat CONTINUATION as HEADERS + QUICHE_DCHECK_EQ(current_frame_.hd.stream_id, stream_id); + current_frame_.hd.length += length; + current_frame_.hd.flags |= flags; + QUICHE_DLOG_IF(ERROR, length == 0) << "Empty CONTINUATION!"; + // Still need to deliver the CONTINUATION to the begin frame callback. + nghttp2_frame_hd hd; + memset(&hd, 0, sizeof(hd)); + hd.stream_id = stream_id; + hd.length = length; + hd.type = type; + hd.flags = flags; + if (callbacks_->on_begin_frame_callback) { + callbacks_->on_begin_frame_callback(nullptr, &hd, user_data_); + } + return; + } // The general strategy is to clear |current_frame_| at the start of a new // frame, accumulate frame information from the various callback events, then // invoke the on_frame_recv_callback() with the accumulated frame data. @@ -52,7 +101,10 @@ void CallbackVisitor::OnFrameHeader(Http2StreamId stream_id, current_frame_.hd.length = length; current_frame_.hd.type = type; current_frame_.hd.flags = flags; - callbacks_->on_begin_frame_callback(nullptr, ¤t_frame_.hd, user_data_); + if (callbacks_->on_begin_frame_callback) { + callbacks_->on_begin_frame_callback(nullptr, ¤t_frame_.hd, + user_data_); + } } void CallbackVisitor::OnSettingsStart() {} @@ -64,106 +116,147 @@ void CallbackVisitor::OnSetting(Http2Setting setting) { void CallbackVisitor::OnSettingsEnd() { current_frame_.settings.niv = settings_.size(); current_frame_.settings.iv = settings_.data(); - callbacks_->on_frame_recv_callback(nullptr, ¤t_frame_, user_data_); + QUICHE_VLOG(1) << "OnSettingsEnd, received settings of size " + << current_frame_.settings.niv; + if (callbacks_->on_frame_recv_callback) { + callbacks_->on_frame_recv_callback(nullptr, ¤t_frame_, user_data_); + } settings_.clear(); } void CallbackVisitor::OnSettingsAck() { // ACK is part of the flags, which were set in OnFrameHeader(). - callbacks_->on_frame_recv_callback(nullptr, ¤t_frame_, user_data_); + if (callbacks_->on_frame_recv_callback) { + callbacks_->on_frame_recv_callback(nullptr, ¤t_frame_, user_data_); + } } -void CallbackVisitor::OnBeginHeadersForStream(Http2StreamId stream_id) { - auto it = stream_map_.find(stream_id); - if (it == stream_map_.end()) { - auto p = stream_map_.insert({stream_id, absl::make_unique<StreamInfo>()}); - it = p.first; - } +bool CallbackVisitor::OnBeginHeadersForStream(Http2StreamId stream_id) { + auto it = GetStreamInfo(stream_id); if (it->second->received_headers) { // At least one headers frame has already been received. + QUICHE_VLOG(1) + << "Headers already received for stream " << stream_id + << ", these are trailers or headers following a 100 response"; current_frame_.headers.cat = NGHTTP2_HCAT_HEADERS; } else { switch (perspective_) { case Perspective::kClient: + QUICHE_VLOG(1) << "First headers at the client for stream " << stream_id + << "; these are response headers"; current_frame_.headers.cat = NGHTTP2_HCAT_RESPONSE; break; case Perspective::kServer: + QUICHE_VLOG(1) << "First headers at the server for stream " << stream_id + << "; these are request headers"; current_frame_.headers.cat = NGHTTP2_HCAT_REQUEST; break; } } - callbacks_->on_begin_headers_callback(nullptr, ¤t_frame_, user_data_); it->second->received_headers = true; + if (callbacks_->on_begin_headers_callback) { + const int result = callbacks_->on_begin_headers_callback( + nullptr, ¤t_frame_, user_data_); + return result == 0; + } + return true; } -void CallbackVisitor::OnHeaderForStream(Http2StreamId stream_id, - absl::string_view name, - absl::string_view value) { - callbacks_->on_header_callback( - nullptr, ¤t_frame_, ToUint8Ptr(name.data()), name.size(), - ToUint8Ptr(value.data()), value.size(), NGHTTP2_NV_FLAG_NONE, user_data_); +Http2VisitorInterface::OnHeaderResult CallbackVisitor::OnHeaderForStream( + Http2StreamId /*stream_id*/, absl::string_view name, + absl::string_view value) { + if (callbacks_->on_header_callback) { + const int result = callbacks_->on_header_callback( + nullptr, ¤t_frame_, ToUint8Ptr(name.data()), name.size(), + ToUint8Ptr(value.data()), value.size(), NGHTTP2_NV_FLAG_NONE, + user_data_); + if (result == 0) { + return HEADER_OK; + } else if (result == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { + return HEADER_RST_STREAM; + } else { + // Assume NGHTTP2_ERR_CALLBACK_FAILURE. + return HEADER_CONNECTION_ERROR; + } + } + return HEADER_OK; } -void CallbackVisitor::OnEndHeadersForStream(Http2StreamId stream_id) { - callbacks_->on_frame_recv_callback(nullptr, ¤t_frame_, user_data_); +void CallbackVisitor::OnEndHeadersForStream(Http2StreamId /*stream_id*/) { + if (callbacks_->on_frame_recv_callback) { + const int result = callbacks_->on_frame_recv_callback( + nullptr, ¤t_frame_, user_data_); + QUICHE_DCHECK_EQ(0, result); + } } -void CallbackVisitor::OnBeginDataForStream(Http2StreamId stream_id, +void CallbackVisitor::OnBeginDataForStream(Http2StreamId /*stream_id*/, size_t payload_length) { // TODO(b/181586191): Interpret padding, subtract padding from // |remaining_data_|. remaining_data_ = payload_length; - if (remaining_data_ == 0) { + if (remaining_data_ == 0 && callbacks_->on_frame_recv_callback) { callbacks_->on_frame_recv_callback(nullptr, ¤t_frame_, user_data_); } } void CallbackVisitor::OnDataForStream(Http2StreamId stream_id, absl::string_view data) { - callbacks_->on_data_chunk_recv_callback(nullptr, current_frame_.hd.flags, - stream_id, ToUint8Ptr(data.data()), - data.size(), user_data_); + if (callbacks_->on_data_chunk_recv_callback) { + callbacks_->on_data_chunk_recv_callback(nullptr, current_frame_.hd.flags, + stream_id, ToUint8Ptr(data.data()), + data.size(), user_data_); + } remaining_data_ -= data.size(); - if (remaining_data_ == 0) { + if (remaining_data_ == 0 && callbacks_->on_frame_recv_callback) { callbacks_->on_frame_recv_callback(nullptr, ¤t_frame_, user_data_); } } -void CallbackVisitor::OnEndStream(Http2StreamId stream_id) {} +void CallbackVisitor::OnEndStream(Http2StreamId /*stream_id*/) {} -void CallbackVisitor::OnRstStream(Http2StreamId stream_id, +void CallbackVisitor::OnRstStream(Http2StreamId /*stream_id*/, Http2ErrorCode error_code) { current_frame_.rst_stream.error_code = static_cast<uint32_t>(error_code); - callbacks_->on_frame_recv_callback(nullptr, ¤t_frame_, user_data_); + if (callbacks_->on_frame_recv_callback) { + callbacks_->on_frame_recv_callback(nullptr, ¤t_frame_, user_data_); + } } void CallbackVisitor::OnCloseStream(Http2StreamId stream_id, Http2ErrorCode error_code) { - callbacks_->on_stream_close_callback( - nullptr, stream_id, static_cast<uint32_t>(error_code), user_data_); + if (callbacks_->on_stream_close_callback) { + QUICHE_VLOG(1) << "OnCloseStream(stream_id: " << stream_id + << ", error_code: " << int(error_code) << ")"; + callbacks_->on_stream_close_callback( + nullptr, stream_id, static_cast<uint32_t>(error_code), user_data_); + } } -void CallbackVisitor::OnPriorityForStream(Http2StreamId stream_id, +void CallbackVisitor::OnPriorityForStream(Http2StreamId /*stream_id*/, Http2StreamId parent_stream_id, - int weight, - bool exclusive) { + int weight, bool exclusive) { current_frame_.priority.pri_spec.stream_id = parent_stream_id; current_frame_.priority.pri_spec.weight = weight; current_frame_.priority.pri_spec.exclusive = exclusive; - callbacks_->on_frame_recv_callback(nullptr, ¤t_frame_, user_data_); + if (callbacks_->on_frame_recv_callback) { + callbacks_->on_frame_recv_callback(nullptr, ¤t_frame_, user_data_); + } } -void CallbackVisitor::OnPing(Http2PingId ping_id, bool is_ack) { +void CallbackVisitor::OnPing(Http2PingId ping_id, bool /*is_ack*/) { uint64_t network_order_opaque_data = quiche::QuicheEndian::HostToNet64(ping_id); std::memcpy(current_frame_.ping.opaque_data, &network_order_opaque_data, sizeof(network_order_opaque_data)); - callbacks_->on_frame_recv_callback(nullptr, ¤t_frame_, user_data_); + if (callbacks_->on_frame_recv_callback) { + callbacks_->on_frame_recv_callback(nullptr, ¤t_frame_, user_data_); + } } -void CallbackVisitor::OnPushPromiseForStream(Http2StreamId stream_id, - Http2StreamId promised_stream_id) { - QUICHE_LOG(FATAL) << "Not implemented"; +void CallbackVisitor::OnPushPromiseForStream( + Http2StreamId /*stream_id*/, Http2StreamId /*promised_stream_id*/) { + QUICHE_LOG(DFATAL) << "Not implemented"; } void CallbackVisitor::OnGoAway(Http2StreamId last_accepted_stream_id, @@ -173,42 +266,173 @@ void CallbackVisitor::OnGoAway(Http2StreamId last_accepted_stream_id, current_frame_.goaway.error_code = static_cast<uint32_t>(error_code); current_frame_.goaway.opaque_data = ToUint8Ptr(opaque_data.data()); current_frame_.goaway.opaque_data_len = opaque_data.size(); - callbacks_->on_frame_recv_callback(nullptr, ¤t_frame_, user_data_); + if (callbacks_->on_frame_recv_callback) { + callbacks_->on_frame_recv_callback(nullptr, ¤t_frame_, user_data_); + } } -void CallbackVisitor::OnWindowUpdate(Http2StreamId stream_id, +void CallbackVisitor::OnWindowUpdate(Http2StreamId /*stream_id*/, int window_increment) { current_frame_.window_update.window_size_increment = window_increment; - callbacks_->on_frame_recv_callback(nullptr, ¤t_frame_, user_data_); + if (callbacks_->on_frame_recv_callback) { + callbacks_->on_frame_recv_callback(nullptr, ¤t_frame_, user_data_); + } +} + +void CallbackVisitor::PopulateFrame(nghttp2_frame& frame, uint8_t frame_type, + Http2StreamId stream_id, size_t length, + uint8_t flags, uint32_t error_code, + bool sent_headers) { + frame.hd.type = frame_type; + frame.hd.stream_id = stream_id; + frame.hd.length = length; + frame.hd.flags = flags; + const FrameType frame_type_enum = static_cast<FrameType>(frame_type); + if (frame_type_enum == FrameType::HEADERS) { + if (sent_headers) { + frame.headers.cat = NGHTTP2_HCAT_HEADERS; + } else { + switch (perspective_) { + case Perspective::kClient: + QUICHE_VLOG(1) << "First headers sent by the client for stream " + << stream_id << "; these are request headers"; + frame.headers.cat = NGHTTP2_HCAT_REQUEST; + break; + case Perspective::kServer: + QUICHE_VLOG(1) << "First headers sent by the server for stream " + << stream_id << "; these are response headers"; + frame.headers.cat = NGHTTP2_HCAT_RESPONSE; + break; + } + } + } else if (frame_type_enum == FrameType::RST_STREAM) { + frame.rst_stream.error_code = error_code; + } else if (frame_type_enum == FrameType::GOAWAY) { + frame.goaway.error_code = error_code; + } } -void CallbackVisitor::OnReadyToSendDataForStream(Http2StreamId stream_id, - char* destination_buffer, - size_t length, - ssize_t* written, - bool* end_stream) { - QUICHE_LOG(FATAL) << "Not implemented"; +int CallbackVisitor::OnBeforeFrameSent(uint8_t frame_type, + Http2StreamId stream_id, size_t length, + uint8_t flags) { + if (callbacks_->before_frame_send_callback) { + QUICHE_VLOG(1) << "OnBeforeFrameSent(type=" << int(frame_type) + << ", stream_id=" << stream_id << ", length=" << length + << ", flags=" << int(flags) << ")"; + nghttp2_frame frame; + auto it = GetStreamInfo(stream_id); + // The implementation of the before_frame_send_callback doesn't look at the + // error code, so for now it's populated with 0. + PopulateFrame(frame, frame_type, stream_id, length, flags, /*error_code=*/0, + it->second->before_sent_headers); + it->second->before_sent_headers = true; + return callbacks_->before_frame_send_callback(nullptr, &frame, user_data_); + } + return 0; +} + +int CallbackVisitor::OnFrameSent(uint8_t frame_type, Http2StreamId stream_id, + size_t length, uint8_t flags, + uint32_t error_code) { + if (callbacks_->on_frame_send_callback) { + QUICHE_VLOG(1) << "OnFrameSent(type=" << int(frame_type) + << ", stream_id=" << stream_id << ", length=" << length + << ", flags=" << int(flags) << ", error_code=" << error_code + << ")"; + nghttp2_frame frame; + auto it = GetStreamInfo(stream_id); + PopulateFrame(frame, frame_type, stream_id, length, flags, error_code, + it->second->sent_headers); + it->second->sent_headers = true; + return callbacks_->on_frame_send_callback(nullptr, &frame, user_data_); + } + return 0; +} + +void CallbackVisitor::OnReadyToSendDataForStream(Http2StreamId /*stream_id*/, + char* /*destination_buffer*/, + size_t /*length*/, + ssize_t* /*written*/, + bool* /*end_stream*/) { + QUICHE_LOG(DFATAL) << "Not implemented"; +} + +bool CallbackVisitor::OnInvalidFrame(Http2StreamId stream_id, int error_code) { + QUICHE_VLOG(1) << "OnInvalidFrame(" << stream_id << ", " << error_code << ")"; + QUICHE_DCHECK_EQ(stream_id, current_frame_.hd.stream_id); + if (callbacks_->on_invalid_frame_recv_callback) { + return 0 == callbacks_->on_invalid_frame_recv_callback( + nullptr, ¤t_frame_, error_code, user_data_); + } + return true; } void CallbackVisitor::OnReadyToSendMetadataForStream(Http2StreamId stream_id, char* buffer, size_t length, ssize_t* written) { - QUICHE_LOG(FATAL) << "Not implemented"; + if (callbacks_->pack_extension_callback) { + nghttp2_frame frame; + frame.hd.type = kMetadataFrameType; + frame.hd.stream_id = stream_id; + frame.hd.flags = 0; + frame.hd.length = 0; + *written = callbacks_->pack_extension_callback(nullptr, ToUint8Ptr(buffer), + length, &frame, user_data_); + } + QUICHE_VLOG(1) << "OnReadyToSendMetadataForStream(stream_id=" << stream_id + << ", length=" << length << ", written=" << *written << ")"; } void CallbackVisitor::OnBeginMetadataForStream(Http2StreamId stream_id, size_t payload_length) { - QUICHE_LOG(FATAL) << "Not implemented"; + QUICHE_VLOG(1) << "OnBeginMetadataForStream(stream_id=" << stream_id + << ", payload_length=" << payload_length << ")"; } void CallbackVisitor::OnMetadataForStream(Http2StreamId stream_id, absl::string_view metadata) { - QUICHE_LOG(FATAL) << "Not implemented"; + QUICHE_VLOG(1) << "OnMetadataForStream(stream_id=" << stream_id + << ", len=" << metadata.size() << ")"; + if (callbacks_->on_extension_chunk_recv_callback) { + int result = callbacks_->on_extension_chunk_recv_callback( + nullptr, ¤t_frame_.hd, ToUint8Ptr(metadata.data()), + metadata.size(), user_data_); + QUICHE_DCHECK_EQ(0, result); + } +} + +bool CallbackVisitor::OnMetadataEndForStream(Http2StreamId stream_id) { + QUICHE_LOG_IF(DFATAL, current_frame_.hd.flags != kMetadataEndFlag); + QUICHE_VLOG(1) << "OnMetadataEndForStream(stream_id=" << stream_id << ")"; + if (callbacks_->unpack_extension_callback) { + void* payload; + int result = callbacks_->unpack_extension_callback( + nullptr, &payload, ¤t_frame_.hd, user_data_); + if (callbacks_->on_frame_recv_callback) { + current_frame_.ext.payload = payload; + callbacks_->on_frame_recv_callback(nullptr, ¤t_frame_, user_data_); + } + return (result == 0); + } + return true; } -void CallbackVisitor::OnMetadataEndForStream(Http2StreamId stream_id) { - QUICHE_LOG(FATAL) << "Not implemented"; +void CallbackVisitor::OnErrorDebug(absl::string_view message) { + if (callbacks_->error_callback2) { + callbacks_->error_callback2(nullptr, -1, message.data(), message.size(), + user_data_); + } +} + +CallbackVisitor::StreamInfoMap::iterator CallbackVisitor::GetStreamInfo( + Http2StreamId stream_id) { + auto it = stream_map_.find(stream_id); + if (it == stream_map_.end()) { + auto p = stream_map_.insert({stream_id, absl::make_unique<StreamInfo>()}); + it = p.first; + } + return it; } } // namespace adapter diff --git a/chromium/net/third_party/quiche/src/http2/adapter/callback_visitor.h b/chromium/net/third_party/quiche/src/http2/adapter/callback_visitor.h index 94746bf8edb..b5afc83a103 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/callback_visitor.h +++ b/chromium/net/third_party/quiche/src/http2/adapter/callback_visitor.h @@ -8,21 +8,20 @@ #include "http2/adapter/http2_visitor_interface.h" #include "http2/adapter/nghttp2_util.h" #include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h" +#include "common/platform/api/quiche_export.h" namespace http2 { namespace adapter { // This visitor implementation accepts a set of nghttp2 callbacks and a "user // data" pointer, and invokes the callbacks according to HTTP/2 events received. -class CallbackVisitor : public Http2VisitorInterface { +class QUICHE_EXPORT_PRIVATE CallbackVisitor : public Http2VisitorInterface { public: explicit CallbackVisitor(Perspective perspective, - nghttp2_session_callbacks_unique_ptr callbacks, - void* user_data) - : perspective_(perspective), - callbacks_(std::move(callbacks)), - user_data_(user_data) {} + const nghttp2_session_callbacks& callbacks, + void* user_data); + ssize_t OnReadyToSend(absl::string_view serialized) override; void OnConnectionError() override; void OnFrameHeader(Http2StreamId stream_id, size_t length, @@ -32,10 +31,10 @@ class CallbackVisitor : public Http2VisitorInterface { void OnSetting(Http2Setting setting) override; void OnSettingsEnd() override; void OnSettingsAck() override; - void OnBeginHeadersForStream(Http2StreamId stream_id) override; - void OnHeaderForStream(Http2StreamId stream_id, - absl::string_view name, - absl::string_view value) override; + bool OnBeginHeadersForStream(Http2StreamId stream_id) override; + OnHeaderResult OnHeaderForStream(Http2StreamId stream_id, + absl::string_view name, + absl::string_view value) override; void OnEndHeadersForStream(Http2StreamId stream_id) override; void OnBeginDataForStream(Http2StreamId stream_id, size_t payload_length) override; @@ -56,11 +55,16 @@ class CallbackVisitor : public Http2VisitorInterface { Http2ErrorCode error_code, absl::string_view opaque_data) override; void OnWindowUpdate(Http2StreamId stream_id, int window_increment) override; + int OnBeforeFrameSent(uint8_t frame_type, Http2StreamId stream_id, + size_t length, uint8_t flags) override; + int OnFrameSent(uint8_t frame_type, Http2StreamId stream_id, size_t length, + uint8_t flags, uint32_t error_code) override; void OnReadyToSendDataForStream(Http2StreamId stream_id, char* destination_buffer, size_t length, ssize_t* written, bool* end_stream) override; + bool OnInvalidFrame(Http2StreamId stream_id, int error_code) override; void OnReadyToSendMetadataForStream(Http2StreamId stream_id, char* buffer, size_t length, @@ -69,9 +73,25 @@ class CallbackVisitor : public Http2VisitorInterface { size_t payload_length) override; void OnMetadataForStream(Http2StreamId stream_id, absl::string_view metadata) override; - void OnMetadataEndForStream(Http2StreamId stream_id) override; + bool OnMetadataEndForStream(Http2StreamId stream_id) override; + void OnErrorDebug(absl::string_view message) override; private: + struct QUICHE_EXPORT_PRIVATE StreamInfo { + bool before_sent_headers = false; + bool sent_headers = false; + bool received_headers = false; + }; + + using StreamInfoMap = + absl::flat_hash_map<Http2StreamId, std::unique_ptr<StreamInfo>>; + + void PopulateFrame(nghttp2_frame& frame, uint8_t frame_type, + Http2StreamId stream_id, size_t length, uint8_t flags, + uint32_t error_code, bool sent_headers); + // Creates the StreamInfoMap entry if it doesn't exist. + StreamInfoMap::iterator GetStreamInfo(Http2StreamId stream_id); + Perspective perspective_; nghttp2_session_callbacks_unique_ptr callbacks_; void* user_data_; @@ -80,10 +100,7 @@ class CallbackVisitor : public Http2VisitorInterface { std::vector<nghttp2_settings_entry> settings_; size_t remaining_data_ = 0; - struct StreamInfo { - bool received_headers = false; - }; - absl::flat_hash_map<Http2StreamId, std::unique_ptr<StreamInfo>> stream_map_; + StreamInfoMap stream_map_; }; } // namespace adapter diff --git a/chromium/net/third_party/quiche/src/http2/adapter/callback_visitor_test.cc b/chromium/net/third_party/quiche/src/http2/adapter/callback_visitor_test.cc index 4dfcb5fcddb..2cb31ed24f4 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/callback_visitor_test.cc +++ b/chromium/net/third_party/quiche/src/http2/adapter/callback_visitor_test.cc @@ -1,6 +1,7 @@ #include "http2/adapter/callback_visitor.h" #include "http2/adapter/mock_nghttp2_callbacks.h" +#include "http2/adapter/nghttp2_test_utils.h" #include "http2/adapter/test_utils.h" #include "common/platform/api/quiche_test.h" @@ -21,13 +22,14 @@ enum FrameType { PING, GOAWAY, WINDOW_UPDATE, + CONTINUATION, }; // Tests connection-level events. TEST(ClientCallbackVisitorUnitTest, ConnectionFrames) { testing::StrictMock<MockNghttp2Callbacks> callbacks; CallbackVisitor visitor(Perspective::kClient, - MockNghttp2Callbacks::GetCallbacks(), &callbacks); + *MockNghttp2Callbacks::GetCallbacks(), &callbacks); testing::InSequence seq; @@ -73,7 +75,7 @@ TEST(ClientCallbackVisitorUnitTest, ConnectionFrames) { TEST(ClientCallbackVisitorUnitTest, StreamFrames) { testing::StrictMock<MockNghttp2Callbacks> callbacks; CallbackVisitor visitor(Perspective::kClient, - MockNghttp2Callbacks::GetCallbacks(), &callbacks); + *MockNghttp2Callbacks::GetCallbacks(), &callbacks); testing::InSequence seq; @@ -153,12 +155,69 @@ TEST(ClientCallbackVisitorUnitTest, StreamFrames) { EXPECT_CALL(callbacks, OnStreamClose(5, NGHTTP2_REFUSED_STREAM)); visitor.OnCloseStream(5, Http2ErrorCode::REFUSED_STREAM); + + // Metadata events + constexpr size_t kMetadataBufferSize = 256; + char metadata_dest[kMetadataBufferSize]; + ssize_t written = 0; + + const absl::string_view kExampleFrame = "This is supposed to be metadata."; + EXPECT_CALL( + callbacks, + OnPackExtension(_, kMetadataBufferSize, + Field(&nghttp2_frame::hd, + HasFrameHeaderRef(7, kMetadataFrameType, _)))) + .WillOnce(testing::Invoke( + [kExampleFrame](uint8_t* buf, size_t /*len*/, + const nghttp2_frame* /*frame*/) -> ssize_t { + std::memcpy(buf, kExampleFrame.data(), kExampleFrame.size()); + return kExampleFrame.size(); + })); + visitor.OnReadyToSendMetadataForStream(7, metadata_dest, kMetadataBufferSize, + &written); + ASSERT_EQ(written, kExampleFrame.size()); + EXPECT_EQ(absl::string_view(metadata_dest, written), kExampleFrame); +} + +TEST(ClientCallbackVisitorUnitTest, HeadersWithContinuation) { + testing::StrictMock<MockNghttp2Callbacks> callbacks; + CallbackVisitor visitor(Perspective::kClient, + *MockNghttp2Callbacks::GetCallbacks(), &callbacks); + + testing::InSequence seq; + + // HEADERS on stream 1 + EXPECT_CALL(callbacks, OnBeginFrame(HasFrameHeader(1, HEADERS, 0x0))); + visitor.OnFrameHeader(1, 23, HEADERS, 0x0); + + EXPECT_CALL(callbacks, + OnBeginHeaders(IsHeaders(1, _, NGHTTP2_HCAT_RESPONSE))); + visitor.OnBeginHeadersForStream(1); + + EXPECT_CALL(callbacks, OnHeader(_, ":status", "200", _)); + visitor.OnHeaderForStream(1, ":status", "200"); + + EXPECT_CALL(callbacks, OnHeader(_, "server", "my-fake-server", _)); + visitor.OnHeaderForStream(1, "server", "my-fake-server"); + + EXPECT_CALL(callbacks, OnBeginFrame(HasFrameHeader(1, CONTINUATION, 0x4))); + visitor.OnFrameHeader(1, 23, CONTINUATION, 0x4); + + EXPECT_CALL(callbacks, + OnHeader(_, "date", "Tue, 6 Apr 2021 12:54:01 GMT", _)); + visitor.OnHeaderForStream(1, "date", "Tue, 6 Apr 2021 12:54:01 GMT"); + + EXPECT_CALL(callbacks, OnHeader(_, "trailer", "x-server-status", _)); + visitor.OnHeaderForStream(1, "trailer", "x-server-status"); + + EXPECT_CALL(callbacks, OnFrameRecv(IsHeaders(1, _, NGHTTP2_HCAT_RESPONSE))); + visitor.OnEndHeadersForStream(1); } TEST(ServerCallbackVisitorUnitTest, ConnectionFrames) { testing::StrictMock<MockNghttp2Callbacks> callbacks; CallbackVisitor visitor(Perspective::kServer, - MockNghttp2Callbacks::GetCallbacks(), &callbacks); + *MockNghttp2Callbacks::GetCallbacks(), &callbacks); testing::InSequence seq; @@ -196,7 +255,7 @@ TEST(ServerCallbackVisitorUnitTest, ConnectionFrames) { TEST(ServerCallbackVisitorUnitTest, StreamFrames) { testing::StrictMock<MockNghttp2Callbacks> callbacks; CallbackVisitor visitor(Perspective::kServer, - MockNghttp2Callbacks::GetCallbacks(), &callbacks); + *MockNghttp2Callbacks::GetCallbacks(), &callbacks); testing::InSequence seq; @@ -252,6 +311,28 @@ TEST(ServerCallbackVisitorUnitTest, StreamFrames) { EXPECT_CALL(callbacks, OnStreamClose(3, NGHTTP2_INTERNAL_ERROR)); visitor.OnCloseStream(3, Http2ErrorCode::INTERNAL_ERROR); + + // Metadata events + constexpr size_t kMetadataBufferSize = 256; + char metadata_dest[kMetadataBufferSize]; + ssize_t written = 0; + + const absl::string_view kExampleFrame = "This is supposed to be metadata."; + EXPECT_CALL( + callbacks, + OnPackExtension(_, kMetadataBufferSize, + Field(&nghttp2_frame::hd, + HasFrameHeaderRef(5, kMetadataFrameType, _)))) + .WillOnce(testing::Invoke( + [kExampleFrame](uint8_t* buf, size_t /*len*/, + const nghttp2_frame* /*frame*/) -> ssize_t { + std::memcpy(buf, kExampleFrame.data(), kExampleFrame.size()); + return kExampleFrame.size(); + })); + visitor.OnReadyToSendMetadataForStream(5, metadata_dest, kMetadataBufferSize, + &written); + ASSERT_EQ(written, kExampleFrame.size()); + EXPECT_EQ(absl::string_view(metadata_dest, written), kExampleFrame); } } // namespace diff --git a/chromium/net/third_party/quiche/src/http2/adapter/data_source.cc b/chromium/net/third_party/quiche/src/http2/adapter/data_source.cc deleted file mode 100644 index 13f89d28581..00000000000 --- a/chromium/net/third_party/quiche/src/http2/adapter/data_source.cc +++ /dev/null @@ -1,23 +0,0 @@ -#include "http2/adapter/data_source.h" - -namespace http2 { -namespace adapter { - -StringDataSource::StringDataSource(std::string data) - : data_(std::move(data)), remaining_(data_) { - state_ = remaining_.empty() ? DONE : READY; -} - -absl::string_view StringDataSource::NextData() const { - return remaining_; -} - -void StringDataSource::Consume(size_t bytes) { - remaining_.remove_prefix(std::min(bytes, remaining_.size())); - if (remaining_.empty()) { - state_ = DONE; - } -} - -} // namespace adapter -} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/adapter/data_source.h b/chromium/net/third_party/quiche/src/http2/adapter/data_source.h index e170104a597..d86714148c3 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/data_source.h +++ b/chromium/net/third_party/quiche/src/http2/adapter/data_source.h @@ -2,49 +2,46 @@ #define QUICHE_HTTP2_ADAPTER_DATA_SOURCE_H_ #include <string> +#include <utility> #include "absl/strings/string_view.h" +#include "common/platform/api/quiche_export.h" namespace http2 { namespace adapter { -// Represents a HTTP message body. -class DataSource { +// Represents a source of DATA frames for transmission to the peer. +class QUICHE_EXPORT_PRIVATE DataFrameSource { public: - virtual ~DataSource() {} + virtual ~DataFrameSource() {} - enum State { - // The source is not done, but cannot currently provide more data. - NOT_READY, - // The source can provide more data. - READY, - // The source is done. - DONE, - }; + enum : ssize_t { kBlocked = 0, kError = -1 }; - State state() const { return state_; } + // Returns the number of bytes to send in the next DATA frame, and whether + // this frame indicates the end of the data. Returns {kBlocked, false} if + // blocked, {kError, false} on error. + virtual std::pair<ssize_t, bool> SelectPayloadLength(size_t max_length) = 0; - // The next range of data provided by this data source. - virtual absl::string_view NextData() const = 0; + // This method is called with a frame header and a payload length to send. The + // source should send or buffer the entire frame and return true, or return + // false without sending or buffering anything. + virtual bool Send(absl::string_view frame_header, size_t payload_length) = 0; - // Indicates that |bytes| bytes have been consumed by the caller. - virtual void Consume(size_t bytes) = 0; - - protected: - State state_ = NOT_READY; + // If true, the end of this data source indicates the end of the stream. + // Otherwise, this data will be followed by trailers. + virtual bool send_fin() const = 0; }; -// A simple implementation constructible from a string_view or std::string. -class StringDataSource : public DataSource { +// Represents a source of metadata frames for transmission to the peer. +class QUICHE_EXPORT_PRIVATE MetadataSource { public: - explicit StringDataSource(std::string data); - - absl::string_view NextData() const override; - void Consume(size_t bytes) override; + virtual ~MetadataSource() {} - private: - const std::string data_; - absl::string_view remaining_; + // This method is called with a destination buffer and length. It should + // return the number of payload bytes copied to |dest|, or a negative integer + // to indicate an error, as well as a boolean indicating whether the metadata + // has been completely copied. + virtual std::pair<ssize_t, bool> Pack(uint8_t* dest, size_t dest_len) = 0; }; } // namespace adapter diff --git a/chromium/net/third_party/quiche/src/http2/adapter/data_source_test.cc b/chromium/net/third_party/quiche/src/http2/adapter/data_source_test.cc deleted file mode 100644 index c290124b430..00000000000 --- a/chromium/net/third_party/quiche/src/http2/adapter/data_source_test.cc +++ /dev/null @@ -1,40 +0,0 @@ -#include "http2/adapter/data_source.h" - -#include "common/platform/api/quiche_test.h" - -namespace http2 { -namespace adapter { -namespace test { -namespace { - -TEST(StringDataSourceTest, EmptyString) { - StringDataSource source(""); - - EXPECT_EQ(source.state(), DataSource::DONE); - EXPECT_THAT(source.NextData(), testing::IsEmpty()); -} - -TEST(StringDataSourceTest, PartialConsume) { - StringDataSource source("I'm a HTTP message body. Really!"); - - EXPECT_EQ(source.state(), DataSource::READY); - EXPECT_THAT(source.NextData(), testing::Not(testing::IsEmpty())); - source.Consume(6); - EXPECT_EQ(source.state(), DataSource::READY); - EXPECT_THAT(source.NextData(), testing::StartsWith("HTTP")); - - source.Consume(0); - EXPECT_EQ(source.state(), DataSource::READY); - EXPECT_THAT(source.NextData(), testing::StartsWith("HTTP")); - - // Consumes more than the remaining bytes available. - source.Consume(50); - EXPECT_THAT(source.NextData(), testing::IsEmpty()) - << "next data: " << source.NextData(); - EXPECT_EQ(source.state(), DataSource::DONE); -} - -} // namespace -} // namespace test -} // namespace adapter -} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/adapter/http2_adapter.h b/chromium/net/third_party/quiche/src/http2/adapter/http2_adapter.h index ef2ae0a5baf..789be98ae3b 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/http2_adapter.h +++ b/chromium/net/third_party/quiche/src/http2/adapter/http2_adapter.h @@ -4,9 +4,11 @@ #include "absl/strings/string_view.h" #include "absl/types/optional.h" #include "absl/types/span.h" +#include "http2/adapter/data_source.h" #include "http2/adapter/http2_protocol.h" #include "http2/adapter/http2_session.h" #include "http2/adapter/http2_visitor_interface.h" +#include "common/platform/api/quiche_export.h" namespace http2 { namespace adapter { @@ -17,11 +19,13 @@ namespace adapter { // invokes corresponding callbacks on its passed-in Http2VisitorInterface. // Http2Adapter is a base class shared between client-side and server-side // implementations. -class Http2Adapter { +class QUICHE_EXPORT_PRIVATE Http2Adapter { public: Http2Adapter(const Http2Adapter&) = delete; Http2Adapter& operator=(const Http2Adapter&) = delete; + virtual bool IsServerSession() const = 0; + // Processes the incoming |bytes| as HTTP/2 and invokes callbacks on the // |visitor_| as appropriate. virtual ssize_t ProcessBytes(absl::string_view bytes) = 0; @@ -36,17 +40,15 @@ class Http2Adapter { int weight, bool exclusive) = 0; - // Submits a PING on the connection. Note that nghttp2 automatically submits - // PING acks upon receiving non-ack PINGs from the peer, so callers only use - // this method to originate PINGs. See nghttp2_option_set_no_auto_ping_ack(). + // Submits a PING on the connection. virtual void SubmitPing(Http2PingId ping_id) = 0; + // Starts a graceful shutdown. A no-op for clients. + virtual void SubmitShutdownNotice() = 0; + // Submits a GOAWAY on the connection. Note that |last_accepted_stream_id| - // refers to stream IDs initiated by the peer. For client-side, this last - // stream ID must be even (or 0); for server-side, this last stream ID must be - // odd (or 0). To submit a GOAWAY with |last_accepted_stream_id| with the - // maximum stream ID, signaling imminent connection termination, call - // SubmitShutdownNotice() instead (though this is only possible server-side). + // refers to stream IDs initiated by the peer. For a server sending this + // frame, this last stream ID must be odd (or 0). virtual void SubmitGoAway(Http2StreamId last_accepted_stream_id, Http2ErrorCode error_code, absl::string_view opaque_data) = 0; @@ -56,27 +58,85 @@ class Http2Adapter { virtual void SubmitWindowUpdate(Http2StreamId stream_id, int window_increment) = 0; - // Submits a METADATA frame for the given stream (a |stream_id| of 0 indicates - // connection-level METADATA). If |fin|, the frame will also have the - // END_METADATA flag set. - virtual void SubmitMetadata(Http2StreamId stream_id, bool fin) = 0; + // Submits a RST_STREAM for the given |stream_id| and |error_code|. + virtual void SubmitRst(Http2StreamId stream_id, + Http2ErrorCode error_code) = 0; + + // Submits a sequence of METADATA frames for the given stream. A |stream_id| + // of 0 indicates connection-level METADATA. + virtual void SubmitMetadata(Http2StreamId stream_id, + std::unique_ptr<MetadataSource> source) = 0; + + // Invokes the visitor's OnReadyToSend() method for serialized frame data. + // Returns 0 on success. + virtual int Send() = 0; + + // Returns the connection-level flow control window advertised by the peer. + virtual int GetSendWindowSize() const = 0; - // Returns serialized bytes for writing to the wire. - // Writes should be submitted to Http2Adapter first, so that Http2Adapter - // has data to serialize and return in this method. - virtual std::string GetBytesToWrite(absl::optional<size_t> max_bytes) = 0; + // Returns the stream-level flow control window advertised by the peer. + virtual int GetStreamSendWindowSize(Http2StreamId stream_id) const = 0; - // Returns the connection-level flow control window for the peer. - virtual int GetPeerConnectionWindow() const = 0; + // Returns the current upper bound on the flow control receive window for this + // stream. This value does not account for data received from the peer. + virtual int GetStreamReceiveWindowLimit(Http2StreamId stream_id) const = 0; + + // Returns the amount of data a peer could send on a given stream. This is + // the outstanding stream receive window. + virtual int GetStreamReceiveWindowSize(Http2StreamId stream_id) const = 0; + + // Returns the total amount of data a peer could send on the connection. This + // is the outstanding connection receive window. + virtual int GetReceiveWindowSize() const = 0; + + // Returns the size of the HPACK encoder's dynamic table, including the + // per-entry overhead from the specification. + virtual int GetHpackEncoderDynamicTableSize() const = 0; + + // Returns the size of the HPACK decoder's dynamic table, including the + // per-entry overhead from the specification. + virtual int GetHpackDecoderDynamicTableSize() const = 0; + + // Gets the highest stream ID value seen in a frame received by this endpoint. + // This method is only guaranteed to work for server endpoints. + virtual Http2StreamId GetHighestReceivedStreamId() const = 0; // Marks the given amount of data as consumed for the given stream, which - // enables the nghttp2 layer to trigger WINDOW_UPDATEs as appropriate. + // enables the implementation layer to send WINDOW_UPDATEs as appropriate. virtual void MarkDataConsumedForStream(Http2StreamId stream_id, size_t num_bytes) = 0; - // Submits a RST_STREAM for the given stream. - virtual void SubmitRst(Http2StreamId stream_id, - Http2ErrorCode error_code) = 0; + // Returns the assigned stream ID if the operation succeeds. Otherwise, + // returns a negative integer indicating an error code. |data_source| may be + // nullptr if the request does not have a body. + virtual int32_t SubmitRequest(absl::Span<const Header> headers, + std::unique_ptr<DataFrameSource> data_source, + void* user_data) = 0; + + // Returns 0 on success. |data_source| may be nullptr if the response does not + // have a body. + virtual int SubmitResponse(Http2StreamId stream_id, + absl::Span<const Header> headers, + std::unique_ptr<DataFrameSource> data_source) = 0; + + // Queues trailers to be sent after any outstanding data on the stream with ID + // |stream_id|. Returns 0 on success. + virtual int SubmitTrailer(Http2StreamId stream_id, + absl::Span<const Header> trailers) = 0; + + // Sets a user data pointer for the given stream. Can be called after + // SubmitRequest/SubmitResponse, or after receiving any frame for a given + // stream. + virtual void SetStreamUserData(Http2StreamId stream_id, void* user_data) = 0; + + // Returns nullptr if the stream does not exist, or if stream user data has + // not been set. + virtual void* GetStreamUserData(Http2StreamId stream_id) = 0; + + // Resumes a stream that was previously blocked (for example, due to + // DataFrameSource::SelectPayloadLength() returning kBlocked). Returns true if + // the stream was successfully resumed. + virtual bool ResumeStream(Http2StreamId stream_id) = 0; protected: // Subclasses should expose a public factory method for constructing and diff --git a/chromium/net/third_party/quiche/src/http2/adapter/http2_protocol.cc b/chromium/net/third_party/quiche/src/http2/adapter/http2_protocol.cc index 8d59aeedda0..dc0a57acac5 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/http2_protocol.cc +++ b/chromium/net/third_party/quiche/src/http2/adapter/http2_protocol.cc @@ -12,6 +12,10 @@ const char kHttp2AuthorityPseudoHeader[] = ":authority"; const char kHttp2PathPseudoHeader[] = ":path"; const char kHttp2StatusPseudoHeader[] = ":status"; +const uint8_t kMetadataFrameType = 0x4d; +const uint8_t kMetadataEndFlag = 0x04; +const uint16_t kMetadataExtensionId = 0x4d44; + std::pair<absl::string_view, bool> GetStringView(const HeaderRep& rep) { if (absl::holds_alternative<absl::string_view>(rep)) { return std::make_pair(absl::get<absl::string_view>(rep), true); diff --git a/chromium/net/third_party/quiche/src/http2/adapter/http2_protocol.h b/chromium/net/third_party/quiche/src/http2/adapter/http2_protocol.h index 1e1dd39a21a..9f72fbe9d05 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/http2_protocol.h +++ b/chromium/net/third_party/quiche/src/http2/adapter/http2_protocol.h @@ -5,10 +5,10 @@ #include <string> #include <utility> -#include "base/integral_types.h" #include "absl/base/attributes.h" #include "absl/strings/string_view.h" #include "absl/types/variant.h" +#include "common/platform/api/quiche_export.h" namespace http2 { namespace adapter { @@ -34,7 +34,7 @@ std::pair<absl::string_view, bool> GetStringView(const HeaderRep& rep); using Header = std::pair<HeaderRep, HeaderRep>; // Represents an HTTP/2 SETTINGS key-value parameter. -struct Http2Setting { +struct QUICHE_EXPORT_PRIVATE Http2Setting { Http2SettingsId id; uint32_t value; }; @@ -50,17 +50,40 @@ const Http2StreamId kConnectionStreamId = 0; // 7540 Section 6.5.2 (SETTINGS_MAX_FRAME_SIZE). const int kDefaultFramePayloadSizeLimit = 16 * 1024; -// The default value for the initial stream flow control window size, according -// to RFC 7540 Section 6.9.2. -const int kDefaultInitialStreamWindowSize = 64 * 1024 - 1; +// The default value for the initial stream and connection flow control window +// size, according to RFC 7540 Section 6.9.2. +const int kInitialFlowControlWindowSize = 64 * 1024 - 1; // The pseudo-header fields as specified in RFC 7540 Section 8.1.2.3 (request) // and Section 8.1.2.4 (response). -ABSL_CONST_INIT extern const char kHttp2MethodPseudoHeader[]; -ABSL_CONST_INIT extern const char kHttp2SchemePseudoHeader[]; -ABSL_CONST_INIT extern const char kHttp2AuthorityPseudoHeader[]; -ABSL_CONST_INIT extern const char kHttp2PathPseudoHeader[]; -ABSL_CONST_INIT extern const char kHttp2StatusPseudoHeader[]; +ABSL_CONST_INIT QUICHE_EXPORT_PRIVATE extern const char + kHttp2MethodPseudoHeader[]; +ABSL_CONST_INIT QUICHE_EXPORT_PRIVATE extern const char + kHttp2SchemePseudoHeader[]; +ABSL_CONST_INIT QUICHE_EXPORT_PRIVATE extern const char + kHttp2AuthorityPseudoHeader[]; +ABSL_CONST_INIT QUICHE_EXPORT_PRIVATE extern const char + kHttp2PathPseudoHeader[]; +ABSL_CONST_INIT QUICHE_EXPORT_PRIVATE extern const char + kHttp2StatusPseudoHeader[]; + +ABSL_CONST_INIT QUICHE_EXPORT_PRIVATE extern const uint8_t kMetadataFrameType; +ABSL_CONST_INIT QUICHE_EXPORT_PRIVATE extern const uint8_t kMetadataEndFlag; +ABSL_CONST_INIT QUICHE_EXPORT_PRIVATE extern const uint16_t + kMetadataExtensionId; + +enum class FrameType : uint8_t { + DATA = 0x0, + HEADERS, + PRIORITY, + RST_STREAM, + SETTINGS, + PUSH_PROMISE, + PING, + GOAWAY, + WINDOW_UPDATE, + CONTINUATION, +}; // HTTP/2 error codes as specified in RFC 7540 Section 7. enum class Http2ErrorCode { @@ -94,7 +117,8 @@ enum Http2KnownSettingsId : Http2SettingsId { INITIAL_WINDOW_SIZE = 0x4, MAX_FRAME_SIZE = 0x5, MAX_HEADER_LIST_SIZE = 0x6, - MAX_SETTING = MAX_HEADER_LIST_SIZE + ENABLE_CONNECT_PROTOCOL = 0x8, // See RFC 8441 + MAX_SETTING = ENABLE_CONNECT_PROTOCOL }; // Returns a human-readable string representation of the given SETTINGS |id| for diff --git a/chromium/net/third_party/quiche/src/http2/adapter/http2_session.h b/chromium/net/third_party/quiche/src/http2/adapter/http2_session.h index 0a6321c0ccc..3b57d8f1aba 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/http2_session.h +++ b/chromium/net/third_party/quiche/src/http2/adapter/http2_session.h @@ -5,14 +5,15 @@ #include "absl/strings/string_view.h" #include "http2/adapter/http2_protocol.h" +#include "common/platform/api/quiche_export.h" namespace http2 { namespace adapter { -struct Http2SessionCallbacks {}; +struct QUICHE_EXPORT_PRIVATE Http2SessionCallbacks {}; // A class to represent the state of a single HTTP/2 connection. -class Http2Session { +class QUICHE_EXPORT_PRIVATE Http2Session { public: Http2Session() = default; virtual ~Http2Session() {} diff --git a/chromium/net/third_party/quiche/src/http2/adapter/http2_util.h b/chromium/net/third_party/quiche/src/http2/adapter/http2_util.h index 3ace28b2ce9..88e9a49e6e4 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/http2_util.h +++ b/chromium/net/third_party/quiche/src/http2/adapter/http2_util.h @@ -2,13 +2,16 @@ #define QUICHE_HTTP2_ADAPTER_HTTP2_UTIL_H_ #include "http2/adapter/http2_protocol.h" +#include "common/platform/api/quiche_export.h" #include "spdy/core/spdy_protocol.h" namespace http2 { namespace adapter { -spdy::SpdyErrorCode TranslateErrorCode(Http2ErrorCode code); -Http2ErrorCode TranslateErrorCode(spdy::SpdyErrorCode code); +QUICHE_EXPORT_PRIVATE spdy::SpdyErrorCode TranslateErrorCode( + Http2ErrorCode code); +QUICHE_EXPORT_PRIVATE Http2ErrorCode +TranslateErrorCode(spdy::SpdyErrorCode code); } // namespace adapter } // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/adapter/http2_visitor_interface.h b/chromium/net/third_party/quiche/src/http2/adapter/http2_visitor_interface.h index 12729e75651..292021a23f4 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/http2_visitor_interface.h +++ b/chromium/net/third_party/quiche/src/http2/adapter/http2_visitor_interface.h @@ -5,6 +5,7 @@ #include "absl/strings/string_view.h" #include "http2/adapter/http2_protocol.h" +#include "common/platform/api/quiche_export.h" namespace http2 { namespace adapter { @@ -44,20 +45,24 @@ namespace adapter { // - OnCloseStream() // // More details are at RFC 7540 (go/http2spec). -class Http2VisitorInterface { +class QUICHE_EXPORT_PRIVATE Http2VisitorInterface { public: Http2VisitorInterface(const Http2VisitorInterface&) = delete; Http2VisitorInterface& operator=(const Http2VisitorInterface&) = delete; virtual ~Http2VisitorInterface() = default; + static const ssize_t kSendBlocked = 0; + static const ssize_t kSendError = -1; + // Called when there are serialized frames to send. Should return how many + // bytes were actually sent. May return kSendBlocked or kSendError. + virtual ssize_t OnReadyToSend(absl::string_view serialized) = 0; + // Called when a connection-level processing error has been encountered. virtual void OnConnectionError() = 0; // Called when the header for a frame is received. - virtual void OnFrameHeader(Http2StreamId stream_id, - size_t length, - uint8_t type, - uint8_t flags) {} + virtual void OnFrameHeader(Http2StreamId /*stream_id*/, size_t /*length*/, + uint8_t /*type*/, uint8_t /*flags*/) {} // Called when a non-ack SETTINGS frame is received. virtual void OnSettingsStart() = 0; @@ -72,15 +77,28 @@ class Http2VisitorInterface { virtual void OnSettingsAck() = 0; // Called when the connection receives the header block for a HEADERS frame on - // a stream but has not yet parsed individual headers. - virtual void OnBeginHeadersForStream(Http2StreamId stream_id) = 0; + // a stream but has not yet parsed individual headers. Returns false if a + // fatal error has occurred. + virtual bool OnBeginHeadersForStream(Http2StreamId stream_id) = 0; // Called when the connection receives the header |key| and |value| for a // stream. The HTTP/2 pseudo-headers defined in RFC 7540 Sections 8.1.2.3 and // 8.1.2.4 are also conveyed in this callback. This method is called after - // OnBeginHeadersForStream(). - virtual void OnHeaderForStream(Http2StreamId stream_id, absl::string_view key, - absl::string_view value) = 0; + // OnBeginHeadersForStream(). May return HEADER_RST_STREAM to indicate the + // header block should be rejected. This will cause the library to queue a + // RST_STREAM frame, which will have a default error code of INTERNAL_ERROR. + // The visitor implementation may choose to queue a RST_STREAM with a + // different error code instead, which should be done before returning + // HEADER_RST_STREAM. Returning HEADER_CONNECTION_ERROR will lead to a + // non-recoverable error on the connection. + enum OnHeaderResult { + HEADER_OK, + HEADER_CONNECTION_ERROR, + HEADER_RST_STREAM, + }; + virtual OnHeaderResult OnHeaderForStream(Http2StreamId stream_id, + absl::string_view key, + absl::string_view value) = 0; // Called when the connection has received the complete header block for a // logical HEADERS frame on a stream (which may contain CONTINUATION frames, @@ -133,6 +151,18 @@ class Http2VisitorInterface { virtual void OnWindowUpdate(Http2StreamId stream_id, int window_increment) = 0; + // Called immediately before a frame of the given type is sent. Should return + // 0 on success. + virtual int OnBeforeFrameSent(uint8_t frame_type, Http2StreamId stream_id, + size_t length, uint8_t flags) = 0; + + // Called immediately after a frame of the given type is sent. Should return 0 + // on success. |error_code| is only populated for RST_STREAM and GOAWAY frame + // types. + virtual int OnFrameSent(uint8_t frame_type, Http2StreamId stream_id, + size_t length, uint8_t flags, + uint32_t error_code) = 0; + // Called when the connection is ready to send data for a stream. The // implementation should write at most |length| bytes of the data payload to // the |destination_buffer| and set |end_stream| to true IFF there will be no @@ -144,6 +174,12 @@ class Http2VisitorInterface { ssize_t* written, bool* end_stream) = 0; + // Called when the connection receives an invalid frame. |error_code| is a + // negative integer error code generated by the library. A return value of + // false will result in the connection entering an error state, with no + // further frame processing possible. + virtual bool OnInvalidFrame(Http2StreamId stream_id, int error_code) = 0; + // Called when the connection is ready to write metadata for |stream_id| to // the wire. The implementation should write at most |length| bytes of the // serialized metadata payload to the |buffer| and set |written| to the number @@ -155,6 +191,7 @@ class Http2VisitorInterface { // Called when the connection receives the beginning of a METADATA frame // (which may itself be the middle of a logical metadata block). The metadata // payload will be provided via subsequent calls to OnMetadataForStream(). + // TODO(birenroy): Consider removing this unnecessary method. virtual void OnBeginMetadataForStream(Http2StreamId stream_id, size_t payload_length) = 0; @@ -165,7 +202,11 @@ class Http2VisitorInterface { // Called when the connection has finished receiving a logical metadata block // for a stream. Note that there may be multiple metadata blocks for a stream. - virtual void OnMetadataEndForStream(Http2StreamId stream_id) = 0; + // Returns false if there was an error unpacking the metadata payload. + virtual bool OnMetadataEndForStream(Http2StreamId stream_id) = 0; + + // Invoked with an error message from the application. + virtual void OnErrorDebug(absl::string_view message) = 0; protected: Http2VisitorInterface() = default; diff --git a/chromium/net/third_party/quiche/src/http2/adapter/mock_http2_visitor.h b/chromium/net/third_party/quiche/src/http2/adapter/mock_http2_visitor.h index 171d40f443f..3daa8053962 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/mock_http2_visitor.h +++ b/chromium/net/third_party/quiche/src/http2/adapter/mock_http2_visitor.h @@ -2,6 +2,7 @@ #define QUICHE_HTTP2_ADAPTER_MOCK_HTTP2_VISITOR_INTERFACE_H_ #include "http2/adapter/http2_visitor_interface.h" +#include "common/platform/api/quiche_export.h" #include "common/platform/api/quiche_test.h" namespace http2 { @@ -9,10 +10,20 @@ namespace adapter { namespace test { // A mock visitor class, for use in tests. -class MockHttp2Visitor : public Http2VisitorInterface { +class QUICHE_NO_EXPORT MockHttp2Visitor : public Http2VisitorInterface { public: - MockHttp2Visitor() = default; - + MockHttp2Visitor() { + ON_CALL(*this, OnBeginHeadersForStream) + .WillByDefault(testing::Return(true)); + ON_CALL(*this, OnHeaderForStream).WillByDefault(testing::Return(HEADER_OK)); + ON_CALL(*this, OnInvalidFrame).WillByDefault(testing::Return(true)); + ON_CALL(*this, OnMetadataEndForStream).WillByDefault(testing::Return(true)); + } + + MOCK_METHOD(ssize_t, + OnReadyToSend, + (absl::string_view serialized), + (override)); MOCK_METHOD(void, OnConnectionError, (), (override)); MOCK_METHOD( void, @@ -23,15 +34,11 @@ class MockHttp2Visitor : public Http2VisitorInterface { MOCK_METHOD(void, OnSetting, (Http2Setting setting), (override)); MOCK_METHOD(void, OnSettingsEnd, (), (override)); MOCK_METHOD(void, OnSettingsAck, (), (override)); - MOCK_METHOD(void, - OnBeginHeadersForStream, - (Http2StreamId stream_id), + MOCK_METHOD(bool, OnBeginHeadersForStream, (Http2StreamId stream_id), (override)); - MOCK_METHOD(void, - OnHeaderForStream, - (Http2StreamId stream_id, - absl::string_view key, + MOCK_METHOD(OnHeaderResult, OnHeaderForStream, + (Http2StreamId stream_id, absl::string_view key, absl::string_view value), (override)); @@ -89,6 +96,19 @@ class MockHttp2Visitor : public Http2VisitorInterface { (Http2StreamId stream_id, int window_increment), (override)); + MOCK_METHOD(int, OnBeforeFrameSent, + (uint8_t frame_type, Http2StreamId stream_id, size_t length, + uint8_t flags), + (override)); + + MOCK_METHOD(int, OnFrameSent, + (uint8_t frame_type, Http2StreamId stream_id, size_t length, + uint8_t flags, uint32_t error_code), + (override)); + + MOCK_METHOD(bool, OnInvalidFrame, (Http2StreamId stream_id, int error_code), + (override)); + MOCK_METHOD(void, OnReadyToSendDataForStream, (Http2StreamId stream_id, @@ -114,10 +134,10 @@ class MockHttp2Visitor : public Http2VisitorInterface { (Http2StreamId stream_id, absl::string_view metadata), (override)); - MOCK_METHOD(void, - OnMetadataEndForStream, - (Http2StreamId stream_id), + MOCK_METHOD(bool, OnMetadataEndForStream, (Http2StreamId stream_id), (override)); + + MOCK_METHOD(void, OnErrorDebug, (absl::string_view message), (override)); }; } // namespace test diff --git a/chromium/net/third_party/quiche/src/http2/adapter/mock_nghttp2_callbacks.cc b/chromium/net/third_party/quiche/src/http2/adapter/mock_nghttp2_callbacks.cc index 4699a371add..4243347e758 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/mock_nghttp2_callbacks.cc +++ b/chromium/net/third_party/quiche/src/http2/adapter/mock_nghttp2_callbacks.cc @@ -109,12 +109,19 @@ nghttp2_session_callbacks_unique_ptr MockNghttp2Callbacks::GetCallbacks() { nghttp2_session_callbacks_set_error_callback2( callbacks, - [](nghttp2_session* session, int lib_error_code, const char* msg, + [](nghttp2_session* /*session*/, int lib_error_code, const char* msg, size_t len, void* user_data) -> int { return static_cast<MockNghttp2Callbacks*>(user_data)->OnErrorCallback2( lib_error_code, msg, len); }); + nghttp2_session_callbacks_set_pack_extension_callback( + callbacks, + [](nghttp2_session*, uint8_t* buf, size_t len, const nghttp2_frame* frame, + void* user_data) -> ssize_t { + return static_cast<MockNghttp2Callbacks*>(user_data)->OnPackExtension( + buf, len, frame); + }); return MakeCallbacksPtr(callbacks); } diff --git a/chromium/net/third_party/quiche/src/http2/adapter/mock_nghttp2_callbacks.h b/chromium/net/third_party/quiche/src/http2/adapter/mock_nghttp2_callbacks.h index e2794575c8d..08d15be7d7d 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/mock_nghttp2_callbacks.h +++ b/chromium/net/third_party/quiche/src/http2/adapter/mock_nghttp2_callbacks.h @@ -4,6 +4,7 @@ #include "absl/strings/string_view.h" #include "http2/adapter/nghttp2_util.h" #include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h" +#include "common/platform/api/quiche_export.h" #include "common/platform/api/quiche_test.h" namespace http2 { @@ -12,7 +13,7 @@ namespace test { // This class provides a set of mock nghttp2 callbacks for use in unit test // expectations. -class MockNghttp2Callbacks { +class QUICHE_NO_EXPORT MockNghttp2Callbacks { public: MockNghttp2Callbacks() = default; @@ -71,6 +72,9 @@ class MockNghttp2Callbacks { OnErrorCallback2, (int lib_error_code, const char* msg, size_t len), ()); + + MOCK_METHOD(ssize_t, OnPackExtension, + (uint8_t * buf, size_t len, const nghttp2_frame* frame), ()); }; } // namespace test diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_adapter.cc b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_adapter.cc index 878c040ef72..9b444a3243d 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_adapter.cc +++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_adapter.cc @@ -4,6 +4,7 @@ #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "http2/adapter/nghttp2_callbacks.h" +#include "http2/adapter/nghttp2_data_provider.h" #include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h" #include "common/platform/api/quiche_logging.h" #include "common/quiche_endian.h" @@ -13,20 +14,26 @@ namespace adapter { /* static */ std::unique_ptr<NgHttp2Adapter> NgHttp2Adapter::CreateClientAdapter( - Http2VisitorInterface& visitor) { - auto adapter = new NgHttp2Adapter(visitor, Perspective::kClient); + Http2VisitorInterface& visitor, const nghttp2_option* options) { + auto adapter = new NgHttp2Adapter(visitor, Perspective::kClient, options); adapter->Initialize(); return absl::WrapUnique(adapter); } /* static */ std::unique_ptr<NgHttp2Adapter> NgHttp2Adapter::CreateServerAdapter( - Http2VisitorInterface& visitor) { - auto adapter = new NgHttp2Adapter(visitor, Perspective::kServer); + Http2VisitorInterface& visitor, const nghttp2_option* options) { + auto adapter = new NgHttp2Adapter(visitor, Perspective::kServer, options); adapter->Initialize(); return absl::WrapUnique(adapter); } +bool NgHttp2Adapter::IsServerSession() const { + int result = nghttp2_session_check_server_session(session_->raw_ptr()); + QUICHE_DCHECK_EQ(perspective_ == Perspective::kServer, result > 0); + return result > 0; +} + ssize_t NgHttp2Adapter::ProcessBytes(absl::string_view bytes) { const ssize_t processed_bytes = session_->ProcessBytes(bytes); if (processed_bytes < 0) { @@ -64,6 +71,10 @@ void NgHttp2Adapter::SubmitPing(Http2PingId ping_id) { nghttp2_submit_ping(session_->raw_ptr(), NGHTTP2_FLAG_NONE, opaque_data); } +void NgHttp2Adapter::SubmitShutdownNotice() { + nghttp2_submit_shutdown_notice(session_->raw_ptr()); +} + void NgHttp2Adapter::SubmitGoAway(Http2StreamId last_accepted_stream_id, Http2ErrorCode error_code, absl::string_view opaque_data) { @@ -79,32 +90,55 @@ void NgHttp2Adapter::SubmitWindowUpdate(Http2StreamId stream_id, stream_id, window_increment); } -void NgHttp2Adapter::SubmitMetadata(Http2StreamId stream_id, - bool end_metadata) { +void NgHttp2Adapter::SubmitMetadata( + Http2StreamId /*stream_id*/, std::unique_ptr<MetadataSource> /*source*/) { QUICHE_LOG(DFATAL) << "Not implemented"; } -std::string NgHttp2Adapter::GetBytesToWrite(absl::optional<size_t> max_bytes) { - ssize_t num_bytes = 0; - std::string result; - do { - const uint8_t* data = nullptr; - num_bytes = nghttp2_session_mem_send(session_->raw_ptr(), &data); - if (num_bytes > 0) { - absl::StrAppend( - &result, - absl::string_view(reinterpret_cast<const char*>(data), num_bytes)); - } else if (num_bytes < 0) { - visitor_.OnConnectionError(); - } - } while (num_bytes > 0); +int NgHttp2Adapter::Send() { + const int result = nghttp2_session_send(session_->raw_ptr()); + if (result != 0) { + QUICHE_VLOG(1) << "nghttp2_session_send returned " << result; + visitor_.OnConnectionError(); + } return result; } -int NgHttp2Adapter::GetPeerConnectionWindow() const { +int NgHttp2Adapter::GetSendWindowSize() const { return session_->GetRemoteWindowSize(); } +int NgHttp2Adapter::GetStreamSendWindowSize(Http2StreamId stream_id) const { + return nghttp2_session_get_stream_remote_window_size(session_->raw_ptr(), + stream_id); +} + +int NgHttp2Adapter::GetStreamReceiveWindowLimit(Http2StreamId stream_id) const { + return nghttp2_session_get_stream_effective_local_window_size( + session_->raw_ptr(), stream_id); +} + +int NgHttp2Adapter::GetStreamReceiveWindowSize(Http2StreamId stream_id) const { + return nghttp2_session_get_stream_local_window_size(session_->raw_ptr(), + stream_id); +} + +int NgHttp2Adapter::GetReceiveWindowSize() const { + return nghttp2_session_get_local_window_size(session_->raw_ptr()); +} + +int NgHttp2Adapter::GetHpackEncoderDynamicTableSize() const { + return nghttp2_session_get_hd_deflate_dynamic_table_size(session_->raw_ptr()); +} + +int NgHttp2Adapter::GetHpackDecoderDynamicTableSize() const { + return nghttp2_session_get_hd_inflate_dynamic_table_size(session_->raw_ptr()); +} + +Http2StreamId NgHttp2Adapter::GetHighestReceivedStreamId() const { + return nghttp2_session_get_last_proc_stream_id(session_->raw_ptr()); +} + void NgHttp2Adapter::MarkDataConsumedForStream(Http2StreamId stream_id, size_t num_bytes) { int rc = session_->Consume(stream_id, num_bytes); @@ -125,24 +159,96 @@ void NgHttp2Adapter::SubmitRst(Http2StreamId stream_id, } } +int32_t NgHttp2Adapter::SubmitRequest( + absl::Span<const Header> headers, + std::unique_ptr<DataFrameSource> data_source, void* stream_user_data) { + auto nvs = GetNghttp2Nvs(headers); + std::unique_ptr<nghttp2_data_provider> provider = + MakeDataProvider(data_source.get()); + + int32_t stream_id = + nghttp2_submit_request(session_->raw_ptr(), nullptr, nvs.data(), + nvs.size(), provider.get(), stream_user_data); + // TODO(birenroy): clean up data source on stream close + sources_.emplace(stream_id, std::move(data_source)); + QUICHE_VLOG(1) << "Submitted request with " << nvs.size() + << " request headers and user data " << stream_user_data + << "; resulted in stream " << stream_id; + return stream_id; +} + +int NgHttp2Adapter::SubmitResponse( + Http2StreamId stream_id, absl::Span<const Header> headers, + std::unique_ptr<DataFrameSource> data_source) { + auto nvs = GetNghttp2Nvs(headers); + std::unique_ptr<nghttp2_data_provider> provider = + MakeDataProvider(data_source.get()); + + // TODO(birenroy): clean up data source on stream close + sources_.emplace(stream_id, std::move(data_source)); + + int result = nghttp2_submit_response(session_->raw_ptr(), stream_id, + nvs.data(), nvs.size(), provider.get()); + QUICHE_VLOG(1) << "Submitted response with " << nvs.size() + << " response headers; result = " << result; + return result; +} + +int NgHttp2Adapter::SubmitTrailer(Http2StreamId stream_id, + absl::Span<const Header> trailers) { + auto nvs = GetNghttp2Nvs(trailers); + int result = nghttp2_submit_trailer(session_->raw_ptr(), stream_id, + nvs.data(), nvs.size()); + QUICHE_VLOG(1) << "Submitted trailers with " << nvs.size() + << " response trailers; result = " << result; + return result; +} + +void NgHttp2Adapter::SetStreamUserData(Http2StreamId stream_id, + void* stream_user_data) { + nghttp2_session_set_stream_user_data(session_->raw_ptr(), stream_id, + stream_user_data); +} + +void* NgHttp2Adapter::GetStreamUserData(Http2StreamId stream_id) { + return nghttp2_session_get_stream_user_data(session_->raw_ptr(), stream_id); +} + +bool NgHttp2Adapter::ResumeStream(Http2StreamId stream_id) { + return 0 == nghttp2_session_resume_data(session_->raw_ptr(), stream_id); +} + NgHttp2Adapter::NgHttp2Adapter(Http2VisitorInterface& visitor, - Perspective perspective) - : Http2Adapter(visitor), visitor_(visitor), perspective_(perspective) {} + Perspective perspective, + const nghttp2_option* options) + : Http2Adapter(visitor), + visitor_(visitor), + options_(options), + perspective_(perspective) {} NgHttp2Adapter::~NgHttp2Adapter() {} void NgHttp2Adapter::Initialize() { - nghttp2_option* options; - nghttp2_option_new(&options); - // Set some common options for compatibility. - nghttp2_option_set_no_closed_streams(options, 1); - nghttp2_option_set_no_auto_window_update(options, 1); - nghttp2_option_set_max_send_header_block_length(options, 0x2000000); - nghttp2_option_set_max_outbound_ack(options, 10000); - - session_ = - absl::make_unique<NgHttp2Session>(perspective_, callbacks::Create(), - options, static_cast<void*>(&visitor_)); + nghttp2_option* owned_options = nullptr; + if (options_ == nullptr) { + nghttp2_option_new(&owned_options); + // Set some common options for compatibility. + nghttp2_option_set_no_closed_streams(owned_options, 1); + nghttp2_option_set_no_auto_window_update(owned_options, 1); + nghttp2_option_set_max_send_header_block_length(owned_options, 0x2000000); + nghttp2_option_set_max_outbound_ack(owned_options, 10000); + nghttp2_option_set_user_recv_extension_type(owned_options, + kMetadataFrameType); + options_ = owned_options; + } + + session_ = absl::make_unique<NgHttp2Session>(perspective_, + callbacks::Create(), options_, + static_cast<void*>(&visitor_)); + if (owned_options != nullptr) { + nghttp2_option_del(owned_options); + } + options_ = nullptr; } } // namespace adapter diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_adapter.h b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_adapter.h index 13c2ffcc3f4..151f9a7ae5a 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_adapter.h +++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_adapter.h @@ -1,35 +1,34 @@ #ifndef QUICHE_HTTP2_ADAPTER_NGHTTP2_ADAPTER_H_ #define QUICHE_HTTP2_ADAPTER_NGHTTP2_ADAPTER_H_ +#include "absl/container/flat_hash_map.h" #include "http2/adapter/http2_adapter.h" #include "http2/adapter/http2_protocol.h" #include "http2/adapter/nghttp2_session.h" #include "http2/adapter/nghttp2_util.h" +#include "common/platform/api/quiche_export.h" namespace http2 { namespace adapter { -class NgHttp2Adapter : public Http2Adapter { +class QUICHE_EXPORT_PRIVATE NgHttp2Adapter : public Http2Adapter { public: ~NgHttp2Adapter() override; - // Creates an adapter that functions as a client. + // Creates an adapter that functions as a client. Does not take ownership of + // |options|. static std::unique_ptr<NgHttp2Adapter> CreateClientAdapter( - Http2VisitorInterface& visitor); + Http2VisitorInterface& visitor, const nghttp2_option* options = nullptr); - // Creates an adapter that functions as a server. + // Creates an adapter that functions as a server. Does not take ownership of + // |options|. static std::unique_ptr<NgHttp2Adapter> CreateServerAdapter( - Http2VisitorInterface& visitor); + Http2VisitorInterface& visitor, const nghttp2_option* options = nullptr); - // Processes the incoming |bytes| as HTTP/2 and invokes callbacks on the - // |visitor_| as appropriate. - ssize_t ProcessBytes(absl::string_view bytes) override; + bool IsServerSession() const override; - // Submits the |settings| to be written to the peer, e.g., as part of the - // HTTP/2 connection preface. + ssize_t ProcessBytes(absl::string_view bytes) override; void SubmitSettings(absl::Span<const Http2Setting> settings) override; - - // Submits a PRIORITY frame for the given stream. void SubmitPriorityForStream(Http2StreamId stream_id, Http2StreamId parent_stream_id, int weight, @@ -40,47 +39,58 @@ class NgHttp2Adapter : public Http2Adapter { // this method to originate PINGs. See nghttp2_option_set_no_auto_ping_ack(). void SubmitPing(Http2PingId ping_id) override; - // Submits a GOAWAY on the connection. Note that |last_accepted_stream_id| - // refers to stream IDs initiated by the peer. For client-side, this last - // stream ID must be even (or 0); for server-side, this last stream ID must be - // odd (or 0). - // TODO(birenroy): Add a graceful shutdown behavior to the API. + void SubmitShutdownNotice() override; void SubmitGoAway(Http2StreamId last_accepted_stream_id, Http2ErrorCode error_code, absl::string_view opaque_data) override; - // Submits a WINDOW_UPDATE for the given stream (a |stream_id| of 0 indicates - // a connection-level WINDOW_UPDATE). void SubmitWindowUpdate(Http2StreamId stream_id, int window_increment) override; - // Submits a METADATA frame for the given stream (a |stream_id| of 0 indicates - // connection-level METADATA). If |end_metadata|, the frame will also have the - // END_METADATA flag set. - void SubmitMetadata(Http2StreamId stream_id, bool end_metadata) override; + void SubmitRst(Http2StreamId stream_id, Http2ErrorCode error_code) override; + + void SubmitMetadata(Http2StreamId stream_id, + std::unique_ptr<MetadataSource> source) override; + + int Send() override; + + int GetSendWindowSize() const override; + int GetStreamSendWindowSize(Http2StreamId stream_id) const override; - // Returns serialized bytes for writing to the wire. Writes should be - // submitted to Nghttp2Adapter first, so that Nghttp2Adapter has data to - // serialize and return in this method. - std::string GetBytesToWrite(absl::optional<size_t> max_bytes) override; + int GetStreamReceiveWindowLimit(Http2StreamId stream_id) const override; + int GetStreamReceiveWindowSize(Http2StreamId stream_id) const override; + int GetReceiveWindowSize() const override; - // Returns the connection-level flow control window for the peer. - int GetPeerConnectionWindow() const override; + int GetHpackEncoderDynamicTableSize() const override; + int GetHpackDecoderDynamicTableSize() const override; + + Http2StreamId GetHighestReceivedStreamId() const override; - // Marks the given amount of data as consumed for the given stream, which - // enables the nghttp2 layer to trigger WINDOW_UPDATEs as appropriate. void MarkDataConsumedForStream(Http2StreamId stream_id, size_t num_bytes) override; - // Submits a RST_STREAM with the desired |error_code|. - void SubmitRst(Http2StreamId stream_id, Http2ErrorCode error_code) override; + int32_t SubmitRequest(absl::Span<const Header> headers, + std::unique_ptr<DataFrameSource> data_source, + void* user_data) override; + + int SubmitResponse(Http2StreamId stream_id, absl::Span<const Header> headers, + std::unique_ptr<DataFrameSource> data_source) override; + + int SubmitTrailer(Http2StreamId stream_id, + absl::Span<const Header> trailers) override; + + void SetStreamUserData(Http2StreamId stream_id, void* user_data) override; + void* GetStreamUserData(Http2StreamId stream_id) override; + + bool ResumeStream(Http2StreamId stream_id) override; // TODO(b/181586191): Temporary accessor until equivalent functionality is // available in this adapter class. NgHttp2Session& session() { return *session_; } private: - NgHttp2Adapter(Http2VisitorInterface& visitor, Perspective perspective); + NgHttp2Adapter(Http2VisitorInterface& visitor, Perspective perspective, + const nghttp2_option* options); // Performs any necessary initialization of the underlying HTTP/2 session, // such as preparing initial SETTINGS. @@ -88,7 +98,10 @@ class NgHttp2Adapter : public Http2Adapter { std::unique_ptr<NgHttp2Session> session_; Http2VisitorInterface& visitor_; + const nghttp2_option* options_; Perspective perspective_; + + absl::flat_hash_map<int32_t, std::unique_ptr<DataFrameSource>> sources_; }; } // namespace adapter diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_adapter_test.cc b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_adapter_test.cc index 3aec115dbeb..785e476c94d 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_adapter_test.cc +++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_adapter_test.cc @@ -1,6 +1,7 @@ #include "http2/adapter/nghttp2_adapter.h" #include "http2/adapter/mock_http2_visitor.h" +#include "http2/adapter/nghttp2_test_utils.h" #include "http2/adapter/test_frame_sequence.h" #include "http2/adapter/test_utils.h" #include "common/platform/api/quiche_test.h" @@ -22,21 +23,46 @@ enum FrameType { PING, GOAWAY, WINDOW_UPDATE, + CONTINUATION, }; +// This send callback assumes |source|'s pointer is a TestDataSource, and +// |user_data| is a Http2VisitorInterface. +int TestSendCallback(nghttp2_session*, nghttp2_frame* /*frame*/, + const uint8_t* framehd, size_t length, + nghttp2_data_source* source, void* user_data) { + auto* visitor = static_cast<Http2VisitorInterface*>(user_data); + // Send the frame header via the visitor. + ssize_t result = visitor->OnReadyToSend(ToStringView(framehd, 9)); + if (result == 0) { + return NGHTTP2_ERR_WOULDBLOCK; + } + auto* test_source = static_cast<TestDataSource*>(source->ptr); + absl::string_view payload = test_source->ReadNext(length); + // Send the frame payload via the visitor. + visitor->OnReadyToSend(payload); + return 0; +} + TEST(NgHttp2AdapterTest, ClientConstruction) { testing::StrictMock<MockHttp2Visitor> visitor; auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); ASSERT_NE(nullptr, adapter); EXPECT_TRUE(adapter->session().want_read()); EXPECT_FALSE(adapter->session().want_write()); + EXPECT_FALSE(adapter->IsServerSession()); } TEST(NgHttp2AdapterTest, ClientHandlesFrames) { - testing::StrictMock<MockHttp2Visitor> visitor; + DataSavingVisitor visitor; auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); - std::string serialized = adapter->GetBytesToWrite(absl::nullopt); - EXPECT_THAT(serialized, testing::StrEq(spdy::kHttp2ConnectionHeaderPrefix)); + int result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), + testing::StrEq(spdy::kHttp2ConnectionHeaderPrefix)); + visitor.Clear(); + + EXPECT_EQ(0, adapter->GetHighestReceivedStreamId()); const std::string initial_frames = TestFrameSequence() .ServerPreface() @@ -58,56 +84,95 @@ TEST(NgHttp2AdapterTest, ClientHandlesFrames) { const ssize_t initial_result = adapter->ProcessBytes(initial_frames); EXPECT_EQ(initial_frames.size(), initial_result); - EXPECT_EQ(adapter->GetPeerConnectionWindow(), - kDefaultInitialStreamWindowSize + 1000); + EXPECT_EQ(adapter->GetSendWindowSize(), kInitialFlowControlWindowSize + 1000); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, 8, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(PING, 0, 8, 0x1, 0)); + + result = adapter->Send(); // Some bytes should have been serialized. - serialized = adapter->GetBytesToWrite(absl::nullopt); - EXPECT_THAT(serialized, EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::PING})); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, + spdy::SpdyFrameType::PING})); + visitor.Clear(); const std::vector<const Header> headers1 = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, {":path", "/this/is/request/one"}}); - const auto nvs1 = GetNghttp2Nvs(headers1); const std::vector<const Header> headers2 = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, {":path", "/this/is/request/two"}}); - const auto nvs2 = GetNghttp2Nvs(headers2); const std::vector<const Header> headers3 = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, {":path", "/this/is/request/three"}}); - const auto nvs3 = GetNghttp2Nvs(headers3); + const char* kSentinel1 = "arbitrary pointer 1"; + const char* kSentinel3 = "arbitrary pointer 3"; const int32_t stream_id1 = - nghttp2_submit_request(adapter->session().raw_ptr(), nullptr, nvs1.data(), - nvs1.size(), nullptr, nullptr); + adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1)); ASSERT_GT(stream_id1, 0); QUICHE_LOG(INFO) << "Created stream: " << stream_id1; - const int32_t stream_id2 = - nghttp2_submit_request(adapter->session().raw_ptr(), nullptr, nvs2.data(), - nvs2.size(), nullptr, nullptr); + const int32_t stream_id2 = adapter->SubmitRequest(headers2, nullptr, nullptr); ASSERT_GT(stream_id2, 0); QUICHE_LOG(INFO) << "Created stream: " << stream_id2; const int32_t stream_id3 = - nghttp2_submit_request(adapter->session().raw_ptr(), nullptr, nvs3.data(), - nvs3.size(), nullptr, nullptr); + adapter->SubmitRequest(headers3, nullptr, const_cast<char*>(kSentinel3)); ASSERT_GT(stream_id3, 0); QUICHE_LOG(INFO) << "Created stream: " << stream_id3; - serialized = adapter->GetBytesToWrite(absl::nullopt); - EXPECT_THAT(serialized, EqualsFrames({spdy::SpdyFrameType::HEADERS, - spdy::SpdyFrameType::HEADERS, - spdy::SpdyFrameType::HEADERS})); + const char* kSentinel2 = "arbitrary pointer 2"; + adapter->SetStreamUserData(stream_id2, const_cast<char*>(kSentinel2)); + adapter->SetStreamUserData(stream_id3, nullptr); + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id2, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id2, _, 0x5, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id3, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id3, _, 0x5, 0)); + + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS, + spdy::SpdyFrameType::HEADERS, + spdy::SpdyFrameType::HEADERS})); + visitor.Clear(); + + // All streams are active and have not yet received any data, so the receive + // window should be at the initial value. + EXPECT_EQ(kInitialFlowControlWindowSize, + adapter->GetStreamReceiveWindowSize(stream_id1)); + EXPECT_EQ(kInitialFlowControlWindowSize, + adapter->GetStreamReceiveWindowSize(stream_id2)); + EXPECT_EQ(kInitialFlowControlWindowSize, + adapter->GetStreamReceiveWindowSize(stream_id3)); + + // Upper bound on the flow control receive window should be the initial value. + EXPECT_EQ(kInitialFlowControlWindowSize, + adapter->GetStreamReceiveWindowLimit(stream_id1)); + + // Connection has not yet received any data. + EXPECT_EQ(kInitialFlowControlWindowSize, adapter->GetReceiveWindowSize()); + + EXPECT_EQ(0, adapter->GetHighestReceivedStreamId()); + + EXPECT_EQ(kSentinel1, adapter->GetStreamUserData(stream_id1)); + EXPECT_EQ(kSentinel2, adapter->GetStreamUserData(stream_id2)); + EXPECT_EQ(nullptr, adapter->GetStreamUserData(stream_id3)); + + EXPECT_EQ(0, adapter->GetHpackDecoderDynamicTableSize()); const std::string stream_frames = TestFrameSequence() @@ -140,6 +205,29 @@ TEST(NgHttp2AdapterTest, ClientHandlesFrames) { const ssize_t stream_result = adapter->ProcessBytes(stream_frames); EXPECT_EQ(stream_frames.size(), stream_result); + // First stream has received some data. + EXPECT_GT(kInitialFlowControlWindowSize, + adapter->GetStreamReceiveWindowSize(stream_id1)); + // Second stream was closed. + EXPECT_EQ(-1, adapter->GetStreamReceiveWindowSize(stream_id2)); + // Third stream has not received any data. + EXPECT_EQ(kInitialFlowControlWindowSize, + adapter->GetStreamReceiveWindowSize(stream_id3)); + + // Connection window should be the same as the first stream. + EXPECT_EQ(adapter->GetReceiveWindowSize(), + adapter->GetStreamReceiveWindowSize(stream_id1)); + + // Upper bound on the flow control receive window should still be the initial + // value. + EXPECT_EQ(kInitialFlowControlWindowSize, + adapter->GetStreamReceiveWindowLimit(stream_id1)); + + EXPECT_GT(adapter->GetHpackDecoderDynamicTableSize(), 0); + + // Should be 3, but this method only works for server adapters. + EXPECT_EQ(0, adapter->GetHighestReceivedStreamId()); + // Even though the client recieved a GOAWAY, streams 1 and 5 are still active. EXPECT_TRUE(adapter->session().want_read()); @@ -154,14 +242,801 @@ TEST(NgHttp2AdapterTest, ClientHandlesFrames) { .Data(1, "", true) .RstStream(5, Http2ErrorCode::REFUSED_STREAM) .Serialize()); + + // Should be 5, but this method only works for server adapters. + EXPECT_EQ(0, adapter->GetHighestReceivedStreamId()); + // After receiving END_STREAM for 1 and RST_STREAM for 5, the session no // longer expects reads. EXPECT_FALSE(adapter->session().want_read()); // Client will not have anything else to write. EXPECT_FALSE(adapter->session().want_write()); - serialized = adapter->GetBytesToWrite(absl::nullopt); - EXPECT_THAT(serialized, testing::IsEmpty()); + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), testing::IsEmpty()); +} + +TEST(NgHttp2AdapterTest, ClientHandlesTrailers) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); + + testing::InSequence s; + + const std::vector<const Header> headers1 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + + const char* kSentinel1 = "arbitrary pointer 1"; + const int32_t stream_id1 = + adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1)); + ASSERT_GT(stream_id1, 0); + QUICHE_LOG(INFO) << "Created stream: " << stream_id1; + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); + + int result = adapter->Send(); + EXPECT_EQ(0, result); + absl::string_view data = visitor.data(); + EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); + data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); + EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::HEADERS})); + visitor.Clear(); + + const std::string stream_frames = + TestFrameSequence() + .ServerPreface() + .Headers(1, + {{":status", "200"}, + {"server", "my-fake-server"}, + {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}}, + /*fin=*/false) + .Data(1, "This is the response body.") + .Headers(1, {{"final-status", "A-OK"}}, + /*fin=*/true) + .Serialize(); + + // Server preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "200")); + EXPECT_CALL(visitor, OnHeaderForStream(1, "server", "my-fake-server")); + EXPECT_CALL(visitor, + OnHeaderForStream(1, "date", "Tue, 6 Apr 2021 12:54:01 GMT")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnFrameHeader(1, 26, DATA, 0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 26)); + EXPECT_CALL(visitor, OnDataForStream(1, "This is the response body.")); + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, "final-status", "A-OK")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnEndStream(1)); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR)); + + const ssize_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), stream_result); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + + EXPECT_TRUE(adapter->session().want_write()); + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); +} + +TEST(NgHttp2AdapterTest, ClientHandlesMetadata) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); + + testing::InSequence s; + + const std::vector<const Header> headers1 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + + const char* kSentinel1 = "arbitrary pointer 1"; + const int32_t stream_id1 = + adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1)); + ASSERT_GT(stream_id1, 0); + QUICHE_LOG(INFO) << "Created stream: " << stream_id1; + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); + + int result = adapter->Send(); + EXPECT_EQ(0, result); + absl::string_view data = visitor.data(); + EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); + data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); + EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::HEADERS})); + visitor.Clear(); + + const std::string stream_frames = + TestFrameSequence() + .ServerPreface() + .Metadata(0, "Example connection metadata") + .Headers(1, + {{":status", "200"}, + {"server", "my-fake-server"}, + {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}}, + /*fin=*/false) + .Metadata(1, "Example stream metadata") + .Data(1, "This is the response body.", true) + .Serialize(); + + // Server preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + EXPECT_CALL(visitor, OnFrameHeader(0, _, kMetadataFrameType, 4)); + EXPECT_CALL(visitor, OnBeginMetadataForStream(0, _)); + EXPECT_CALL(visitor, OnMetadataForStream(0, _)); + EXPECT_CALL(visitor, OnMetadataEndForStream(0)); + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "200")); + EXPECT_CALL(visitor, OnHeaderForStream(1, "server", "my-fake-server")); + EXPECT_CALL(visitor, + OnHeaderForStream(1, "date", "Tue, 6 Apr 2021 12:54:01 GMT")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnFrameHeader(1, _, kMetadataFrameType, 4)); + EXPECT_CALL(visitor, OnBeginMetadataForStream(1, _)); + EXPECT_CALL(visitor, OnMetadataForStream(1, _)); + EXPECT_CALL(visitor, OnMetadataEndForStream(1)); + EXPECT_CALL(visitor, OnFrameHeader(1, 26, DATA, 1)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 26)); + EXPECT_CALL(visitor, OnDataForStream(1, "This is the response body.")); + EXPECT_CALL(visitor, OnEndStream(1)); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR)); + + const ssize_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), stream_result); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + + EXPECT_TRUE(adapter->session().want_write()); + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); +} + +TEST(NgHttp2AdapterTest, ClientHandlesInvalidTrailers) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); + + testing::InSequence s; + + const std::vector<const Header> headers1 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + + const char* kSentinel1 = "arbitrary pointer 1"; + const int32_t stream_id1 = + adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1)); + ASSERT_GT(stream_id1, 0); + QUICHE_LOG(INFO) << "Created stream: " << stream_id1; + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); + + int result = adapter->Send(); + EXPECT_EQ(0, result); + absl::string_view data = visitor.data(); + EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); + data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); + EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::HEADERS})); + visitor.Clear(); + + const std::string stream_frames = + TestFrameSequence() + .ServerPreface() + .Headers(1, + {{":status", "200"}, + {"server", "my-fake-server"}, + {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}}, + /*fin=*/false) + .Data(1, "This is the response body.") + .Headers(1, {{":bad-status", "9000"}}, + /*fin=*/true) + .Serialize(); + + // Server preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "200")); + EXPECT_CALL(visitor, OnHeaderForStream(1, "server", "my-fake-server")); + EXPECT_CALL(visitor, + OnHeaderForStream(1, "date", "Tue, 6 Apr 2021 12:54:01 GMT")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnFrameHeader(1, 26, DATA, 0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 26)); + EXPECT_CALL(visitor, OnDataForStream(1, "This is the response body.")); + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL( + visitor, + OnErrorDebug("Invalid HTTP header field was received: frame type: 1, " + "stream: 1, name: [:bad-status], value: [9000]")); + EXPECT_CALL(visitor, OnInvalidFrame(1, -531)); + + // Bad status trailer will cause a PROTOCOL_ERROR. The header is never + // delivered in an OnHeaderForStream callback. + + const ssize_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), stream_result); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, stream_id1, 4, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(RST_STREAM, stream_id1, 4, 0x0, 1)); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR)); + + EXPECT_TRUE(adapter->session().want_write()); + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, + spdy::SpdyFrameType::RST_STREAM})); +} + +TEST(NgHttp2AdapterTest, ClientRstStreamWhileHandlingHeaders) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); + + testing::InSequence s; + + const std::vector<const Header> headers1 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + + const char* kSentinel1 = "arbitrary pointer 1"; + const int32_t stream_id1 = + adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1)); + ASSERT_GT(stream_id1, 0); + QUICHE_LOG(INFO) << "Created stream: " << stream_id1; + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); + + int result = adapter->Send(); + EXPECT_EQ(0, result); + absl::string_view data = visitor.data(); + EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); + data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); + EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::HEADERS})); + visitor.Clear(); + + const std::string stream_frames = + TestFrameSequence() + .ServerPreface() + .Headers(1, + {{":status", "200"}, + {"server", "my-fake-server"}, + {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}}, + /*fin=*/false) + .Data(1, "This is the response body.") + .Serialize(); + + // Server preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "200")); + EXPECT_CALL(visitor, OnHeaderForStream(1, "server", "my-fake-server")); + EXPECT_CALL(visitor, + OnHeaderForStream(1, "date", "Tue, 6 Apr 2021 12:54:01 GMT")) + .WillOnce(testing::DoAll( + testing::InvokeWithoutArgs([&adapter]() { + adapter->SubmitRst(1, Http2ErrorCode::REFUSED_STREAM); + }), + testing::Return(Http2VisitorInterface::HEADER_RST_STREAM))); + + const ssize_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), stream_result); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, stream_id1, 4, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, stream_id1, 4, 0x0, + static_cast<int>(Http2ErrorCode::REFUSED_STREAM))); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::REFUSED_STREAM)); + + EXPECT_TRUE(adapter->session().want_write()); + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, + spdy::SpdyFrameType::RST_STREAM})); +} + +TEST(NgHttp2AdapterTest, ClientConnectionErrorWhileHandlingHeaders) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); + + testing::InSequence s; + + const std::vector<const Header> headers1 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + + const char* kSentinel1 = "arbitrary pointer 1"; + const int32_t stream_id1 = + adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1)); + ASSERT_GT(stream_id1, 0); + QUICHE_LOG(INFO) << "Created stream: " << stream_id1; + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); + + int result = adapter->Send(); + EXPECT_EQ(0, result); + absl::string_view data = visitor.data(); + EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); + data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); + EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::HEADERS})); + visitor.Clear(); + + const std::string stream_frames = + TestFrameSequence() + .ServerPreface() + .Headers(1, + {{":status", "200"}, + {"server", "my-fake-server"}, + {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}}, + /*fin=*/false) + .Data(1, "This is the response body.") + .Serialize(); + + // Server preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "200")); + EXPECT_CALL(visitor, OnHeaderForStream(1, "server", "my-fake-server")); + EXPECT_CALL(visitor, + OnHeaderForStream(1, "date", "Tue, 6 Apr 2021 12:54:01 GMT")) + .WillOnce( + testing::Return(Http2VisitorInterface::HEADER_CONNECTION_ERROR)); + EXPECT_CALL(visitor, OnConnectionError()); + + const ssize_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(-902 /* NGHTTP2_ERR_CALLBACK_FAILURE */, stream_result); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + + EXPECT_TRUE(adapter->session().want_write()); + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); +} + +TEST(NgHttp2AdapterTest, ClientRejectsHeaders) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); + + testing::InSequence s; + + const std::vector<const Header> headers1 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + + const char* kSentinel1 = "arbitrary pointer 1"; + const int32_t stream_id1 = + adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1)); + ASSERT_GT(stream_id1, 0); + QUICHE_LOG(INFO) << "Created stream: " << stream_id1; + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); + + int result = adapter->Send(); + EXPECT_EQ(0, result); + absl::string_view data = visitor.data(); + EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); + data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); + EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::HEADERS})); + visitor.Clear(); + + const std::string stream_frames = + TestFrameSequence() + .ServerPreface() + .Headers(1, + {{":status", "200"}, + {"server", "my-fake-server"}, + {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}}, + /*fin=*/false) + .Data(1, "This is the response body.") + .Serialize(); + + // Server preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)) + .WillOnce(testing::Return(false)); + // Rejecting headers leads to a connection error. + EXPECT_CALL(visitor, OnConnectionError()); + + const ssize_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(NGHTTP2_ERR_CALLBACK_FAILURE, stream_result); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + + EXPECT_TRUE(adapter->session().want_write()); + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); +} + +TEST(NgHttp2AdapterTest, ClientSubmitRequest) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); + int result = adapter->Send(); + EXPECT_EQ(0, result); + // Client preface does not appear to include the mandatory SETTINGS frame. + EXPECT_THAT(visitor.data(), + testing::StrEq(spdy::kHttp2ConnectionHeaderPrefix)); + visitor.Clear(); + + const std::string initial_frames = + TestFrameSequence().ServerPreface().Serialize(); + testing::InSequence s; + + // Server preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + const ssize_t initial_result = adapter->ProcessBytes(initial_frames); + EXPECT_EQ(initial_frames.size(), initial_result); + + EXPECT_TRUE(adapter->session().want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + visitor.Clear(); + + EXPECT_EQ(0, adapter->GetHpackEncoderDynamicTableSize()); + EXPECT_FALSE(adapter->session().want_write()); + const char* kSentinel = ""; + const absl::string_view kBody = "This is an example request body."; + auto body1 = absl::make_unique<TestDataFrameSource>(visitor, true); + body1->AppendPayload(kBody); + body1->EndData(); + int stream_id = + adapter->SubmitRequest(ToHeaders({{":method", "POST"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}), + std::move(body1), const_cast<char*>(kSentinel)); + EXPECT_GT(stream_id, 0); + EXPECT_TRUE(adapter->session().want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0)); + EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, _, 0x1, 0)); + + result = adapter->Send(); + EXPECT_EQ(0, result); + + EXPECT_EQ(kInitialFlowControlWindowSize, + adapter->GetStreamReceiveWindowSize(stream_id)); + EXPECT_EQ(kInitialFlowControlWindowSize, adapter->GetReceiveWindowSize()); + EXPECT_EQ(kInitialFlowControlWindowSize, + adapter->GetStreamReceiveWindowLimit(stream_id)); + + EXPECT_GT(adapter->GetHpackEncoderDynamicTableSize(), 0); + + // Some data was sent, so the remaining send window size should be less than + // the default. + EXPECT_LT(adapter->GetStreamSendWindowSize(stream_id), + kInitialFlowControlWindowSize); + EXPECT_GT(adapter->GetStreamSendWindowSize(stream_id), 0); + // Send window for a nonexistent stream is not available. + EXPECT_EQ(-1, adapter->GetStreamSendWindowSize(stream_id + 2)); + + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS, + spdy::SpdyFrameType::DATA})); + EXPECT_THAT(visitor.data(), testing::HasSubstr(kBody)); + visitor.Clear(); + EXPECT_FALSE(adapter->session().want_write()); + + stream_id = + adapter->SubmitRequest(ToHeaders({{":method", "POST"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}), + nullptr, nullptr); + EXPECT_GT(stream_id, 0); + EXPECT_TRUE(adapter->session().want_write()); + const char* kSentinel2 = "arbitrary pointer 2"; + EXPECT_EQ(nullptr, adapter->GetStreamUserData(stream_id)); + adapter->SetStreamUserData(stream_id, const_cast<char*>(kSentinel2)); + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0)); + + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS})); + + EXPECT_EQ(kSentinel2, adapter->GetStreamUserData(stream_id)); + + // No data was sent (just HEADERS), so the remaining send window size should + // still be the default. + EXPECT_EQ(adapter->GetStreamSendWindowSize(stream_id), + kInitialFlowControlWindowSize); +} + +// This is really a test of the MakeZeroCopyDataFrameSource adapter, but I +// wasn't sure where else to put it. +TEST(NgHttp2AdapterTest, ClientSubmitRequestWithDataProvider) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); + int result = adapter->Send(); + EXPECT_EQ(0, result); + // Client preface does not appear to include the mandatory SETTINGS frame. + EXPECT_THAT(visitor.data(), + testing::StrEq(spdy::kHttp2ConnectionHeaderPrefix)); + visitor.Clear(); + + const std::string initial_frames = + TestFrameSequence().ServerPreface().Serialize(); + testing::InSequence s; + + // Server preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + const ssize_t initial_result = adapter->ProcessBytes(initial_frames); + EXPECT_EQ(initial_frames.size(), initial_result); + + EXPECT_TRUE(adapter->session().want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + visitor.Clear(); + + EXPECT_FALSE(adapter->session().want_write()); + const absl::string_view kBody = "This is an example request body."; + // This test will use TestDataSource as the source of the body payload data. + TestDataSource body1{kBody}; + // The TestDataSource is wrapped in the nghttp2_data_provider data type. + nghttp2_data_provider provider = body1.MakeDataProvider(); + nghttp2_send_data_callback send_callback = &TestSendCallback; + + // This call transforms it back into a DataFrameSource, which is compatible + // with the Http2Adapter API. + std::unique_ptr<DataFrameSource> frame_source = + MakeZeroCopyDataFrameSource(provider, &visitor, std::move(send_callback)); + int stream_id = + adapter->SubmitRequest(ToHeaders({{":method", "POST"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}), + std::move(frame_source), nullptr); + EXPECT_GT(stream_id, 0); + EXPECT_TRUE(adapter->session().want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0)); + EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, _, 0x1, 0)); + + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS, + spdy::SpdyFrameType::DATA})); + EXPECT_THAT(visitor.data(), testing::HasSubstr(kBody)); + EXPECT_FALSE(adapter->session().want_write()); +} + +// This test verifies how nghttp2 behaves when a data source becomes +// read-blocked. +TEST(NgHttp2AdapterTest, ClientSubmitRequestWithDataProviderAndReadBlock) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); + + const absl::string_view kBody = "This is an example request body."; + // This test will use TestDataSource as the source of the body payload data. + TestDataSource body1{kBody}; + body1.set_is_data_available(false); + // The TestDataSource is wrapped in the nghttp2_data_provider data type. + nghttp2_data_provider provider = body1.MakeDataProvider(); + nghttp2_send_data_callback send_callback = &TestSendCallback; + + // This call transforms it back into a DataFrameSource, which is compatible + // with the Http2Adapter API. + std::unique_ptr<DataFrameSource> frame_source = + MakeZeroCopyDataFrameSource(provider, &visitor, std::move(send_callback)); + int stream_id = + adapter->SubmitRequest(ToHeaders({{":method", "POST"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}), + std::move(frame_source), nullptr); + EXPECT_GT(stream_id, 0); + EXPECT_TRUE(adapter->session().want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0)); + + int result = adapter->Send(); + EXPECT_EQ(0, result); + // Client preface does not appear to include the mandatory SETTINGS frame. + absl::string_view serialized = visitor.data(); + EXPECT_THAT(serialized, + testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); + serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); + EXPECT_THAT(serialized, EqualsFrames({spdy::SpdyFrameType::HEADERS})); + visitor.Clear(); + EXPECT_FALSE(adapter->session().want_write()); + + // Resume the deferred stream. + body1.set_is_data_available(true); + EXPECT_TRUE(adapter->ResumeStream(stream_id)); + EXPECT_TRUE(adapter->session().want_write()); + + EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, _, 0x1, 0)); + + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::DATA})); + EXPECT_FALSE(adapter->session().want_write()); + + // Stream data is done, so this stream cannot be resumed. + EXPECT_FALSE(adapter->ResumeStream(stream_id)); + EXPECT_FALSE(adapter->session().want_write()); +} + +// This test verifies how nghttp2 behaves when a data source is read block, then +// ends with an empty DATA frame. +TEST(NgHttp2AdapterTest, ClientSubmitRequestEmptyDataWithFin) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); + + const absl::string_view kEmptyBody = ""; + // This test will use TestDataSource as the source of the body payload data. + TestDataSource body1{kEmptyBody}; + body1.set_is_data_available(false); + // The TestDataSource is wrapped in the nghttp2_data_provider data type. + nghttp2_data_provider provider = body1.MakeDataProvider(); + nghttp2_send_data_callback send_callback = &TestSendCallback; + + // This call transforms it back into a DataFrameSource, which is compatible + // with the Http2Adapter API. + std::unique_ptr<DataFrameSource> frame_source = + MakeZeroCopyDataFrameSource(provider, &visitor, std::move(send_callback)); + int stream_id = + adapter->SubmitRequest(ToHeaders({{":method", "POST"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}), + std::move(frame_source), nullptr); + EXPECT_GT(stream_id, 0); + EXPECT_TRUE(adapter->session().want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0)); + + int result = adapter->Send(); + EXPECT_EQ(0, result); + // Client preface does not appear to include the mandatory SETTINGS frame. + absl::string_view serialized = visitor.data(); + EXPECT_THAT(serialized, + testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); + serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); + EXPECT_THAT(serialized, EqualsFrames({spdy::SpdyFrameType::HEADERS})); + visitor.Clear(); + EXPECT_FALSE(adapter->session().want_write()); + + // Resume the deferred stream. + body1.set_is_data_available(true); + EXPECT_TRUE(adapter->ResumeStream(stream_id)); + EXPECT_TRUE(adapter->session().want_write()); + + EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, 0, 0x1, 0)); + + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::DATA})); + EXPECT_FALSE(adapter->session().want_write()); + + // Stream data is done, so this stream cannot be resumed. + EXPECT_FALSE(adapter->ResumeStream(stream_id)); + EXPECT_FALSE(adapter->session().want_write()); +} + +// This test verifies how nghttp2 behaves when a connection becomes +// write-blocked. +TEST(NgHttp2AdapterTest, ClientSubmitRequestWithDataProviderAndWriteBlock) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); + + const absl::string_view kBody = "This is an example request body."; + // This test will use TestDataSource as the source of the body payload data. + TestDataSource body1{kBody}; + // The TestDataSource is wrapped in the nghttp2_data_provider data type. + nghttp2_data_provider provider = body1.MakeDataProvider(); + nghttp2_send_data_callback send_callback = &TestSendCallback; + + // This call transforms it back into a DataFrameSource, which is compatible + // with the Http2Adapter API. + std::unique_ptr<DataFrameSource> frame_source = + MakeZeroCopyDataFrameSource(provider, &visitor, std::move(send_callback)); + int stream_id = + adapter->SubmitRequest(ToHeaders({{":method", "POST"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}), + std::move(frame_source), nullptr); + EXPECT_GT(stream_id, 0); + EXPECT_TRUE(adapter->session().want_write()); + + visitor.set_is_write_blocked(true); + int result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), testing::IsEmpty()); + EXPECT_TRUE(adapter->session().want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0)); + EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, _, 0x1, 0)); + + visitor.set_is_write_blocked(false); + result = adapter->Send(); + EXPECT_EQ(0, result); + + // Client preface does not appear to include the mandatory SETTINGS frame. + absl::string_view serialized = visitor.data(); + EXPECT_THAT(serialized, + testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); + serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); + EXPECT_THAT(serialized, EqualsFrames({spdy::SpdyFrameType::HEADERS, + spdy::SpdyFrameType::DATA})); + EXPECT_FALSE(adapter->session().want_write()); } TEST(NgHttp2AdapterTest, ServerConstruction) { @@ -170,12 +1045,16 @@ TEST(NgHttp2AdapterTest, ServerConstruction) { ASSERT_NE(nullptr, adapter); EXPECT_TRUE(adapter->session().want_read()); EXPECT_FALSE(adapter->session().want_write()); + EXPECT_TRUE(adapter->IsServerSession()); } TEST(NgHttp2AdapterTest, ServerHandlesFrames) { - testing::StrictMock<MockHttp2Visitor> visitor; + DataSavingVisitor visitor; auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + EXPECT_EQ(0, adapter->GetHighestReceivedStreamId()); + EXPECT_EQ(0, adapter->GetHpackDecoderDynamicTableSize()); + const std::string frames = TestFrameSequence() .ClientPreface() .Ping(42) @@ -199,6 +1078,8 @@ TEST(NgHttp2AdapterTest, ServerHandlesFrames) { .Serialize(); testing::InSequence s; + const char* kSentinel1 = "arbitrary pointer 1"; + // Client preface (empty SETTINGS) EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); EXPECT_CALL(visitor, OnSettingsStart()); @@ -214,7 +1095,10 @@ TEST(NgHttp2AdapterTest, ServerHandlesFrames) { EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); - EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)) + .WillOnce(testing::InvokeWithoutArgs([&adapter, kSentinel1]() { + adapter->SetStreamUserData(1, const_cast<char*>(kSentinel1)); + })); EXPECT_CALL(visitor, OnFrameHeader(1, 4, WINDOW_UPDATE, 0)); EXPECT_CALL(visitor, OnWindowUpdate(1, 2000)); EXPECT_CALL(visitor, OnFrameHeader(1, 25, DATA, 0)); @@ -237,16 +1121,506 @@ TEST(NgHttp2AdapterTest, ServerHandlesFrames) { const ssize_t result = adapter->ProcessBytes(frames); EXPECT_EQ(frames.size(), result); - EXPECT_EQ(adapter->GetPeerConnectionWindow(), - kDefaultInitialStreamWindowSize + 1000); + EXPECT_EQ(kSentinel1, adapter->GetStreamUserData(1)); + + EXPECT_GT(kInitialFlowControlWindowSize, + adapter->GetStreamReceiveWindowSize(1)); + EXPECT_EQ(adapter->GetStreamReceiveWindowSize(1), + adapter->GetReceiveWindowSize()); + // Upper bound should still be the original value. + EXPECT_EQ(kInitialFlowControlWindowSize, + adapter->GetStreamReceiveWindowLimit(1)); + + EXPECT_GT(adapter->GetHpackDecoderDynamicTableSize(), 0); + + // Because stream 3 has already been closed, it's not possible to set user + // data. + const char* kSentinel3 = "another arbitrary pointer"; + adapter->SetStreamUserData(3, const_cast<char*>(kSentinel3)); + EXPECT_EQ(nullptr, adapter->GetStreamUserData(3)); + + EXPECT_EQ(3, adapter->GetHighestReceivedStreamId()); + + EXPECT_EQ(adapter->GetSendWindowSize(), kInitialFlowControlWindowSize + 1000); EXPECT_TRUE(adapter->session().want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, 8, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(PING, 0, 8, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, 8, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(PING, 0, 8, 0x1, 0)); + + int send_result = adapter->Send(); // Some bytes should have been serialized. - std::string serialized = adapter->GetBytesToWrite(absl::nullopt); + EXPECT_EQ(0, send_result); // SETTINGS ack, two PING acks. - EXPECT_THAT(serialized, EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::PING, - spdy::SpdyFrameType::PING})); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, + spdy::SpdyFrameType::PING, + spdy::SpdyFrameType::PING})); +} + +TEST(NgHttp2AdapterTest, ServerErrorWhileHandlingHeaders) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + + const std::string frames = TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":method", "POST"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}, + {"accept", "some bogus value!"}}, + /*fin=*/false) + .WindowUpdate(1, 2000) + .Data(1, "This is the request body.") + .WindowUpdate(0, 2000) + .Serialize(); + testing::InSequence s; + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "POST")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); + EXPECT_CALL(visitor, OnHeaderForStream(1, "accept", "some bogus value!")) + .WillOnce(testing::Return(Http2VisitorInterface::HEADER_RST_STREAM)); + EXPECT_CALL(visitor, OnFrameHeader(1, 4, WINDOW_UPDATE, 0)); + EXPECT_CALL(visitor, OnWindowUpdate(1, 2000)); + // DATA frame is not delivered to the visitor. + EXPECT_CALL(visitor, OnFrameHeader(0, 4, WINDOW_UPDATE, 0)); + EXPECT_CALL(visitor, OnWindowUpdate(0, 2000)); + + const ssize_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(frames.size(), result); + + EXPECT_TRUE(adapter->session().want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, 4, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 1, 4, 0x0, + static_cast<int>(Http2ErrorCode::INTERNAL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::INTERNAL_ERROR)); + + int send_result = adapter->Send(); + // Some bytes should have been serialized. + EXPECT_EQ(0, send_result); + // SETTINGS ack + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, + spdy::SpdyFrameType::RST_STREAM})); +} + +TEST(NgHttp2AdapterTest, ServerSubmitResponse) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + EXPECT_FALSE(adapter->session().want_write()); + + const std::string frames = TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}, + /*fin=*/true) + .Serialize(); + testing::InSequence s; + + const char* kSentinel1 = "arbitrary pointer 1"; + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + // Stream 1 + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)) + .WillOnce(testing::InvokeWithoutArgs([&adapter, kSentinel1]() { + adapter->SetStreamUserData(1, const_cast<char*>(kSentinel1)); + })); + EXPECT_CALL(visitor, OnEndStream(1)); + + const ssize_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(frames.size(), result); + + EXPECT_EQ(1, adapter->GetHighestReceivedStreamId()); + + // Server will want to send a SETTINGS ack. + EXPECT_TRUE(adapter->session().want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + + int send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + visitor.Clear(); + + EXPECT_EQ(0, adapter->GetHpackEncoderDynamicTableSize()); + + EXPECT_FALSE(adapter->session().want_write()); + const absl::string_view kBody = "This is an example response body."; + // A data fin is not sent so that the stream remains open, and the flow + // control state can be verified. + auto body1 = absl::make_unique<TestDataFrameSource>(visitor, false); + body1->AppendPayload(kBody); + int submit_result = adapter->SubmitResponse( + 1, + ToHeaders({{":status", "404"}, + {"x-comment", "I have no idea what you're talking about."}}), + std::move(body1)); + EXPECT_EQ(submit_result, 0); + EXPECT_TRUE(adapter->session().want_write()); + + // Stream user data should have been set successfully after receiving headers. + EXPECT_EQ(kSentinel1, adapter->GetStreamUserData(1)); + adapter->SetStreamUserData(1, nullptr); + EXPECT_EQ(nullptr, adapter->GetStreamUserData(1)); + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0)); + EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0)); + + send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS, + spdy::SpdyFrameType::DATA})); + EXPECT_THAT(visitor.data(), testing::HasSubstr(kBody)); + EXPECT_FALSE(adapter->session().want_write()); + + // Some data was sent, so the remaining send window size should be less than + // the default. + EXPECT_LT(adapter->GetStreamSendWindowSize(1), kInitialFlowControlWindowSize); + EXPECT_GT(adapter->GetStreamSendWindowSize(1), 0); + // Send window for a nonexistent stream is not available. + EXPECT_EQ(adapter->GetStreamSendWindowSize(3), -1); + + EXPECT_GT(adapter->GetHpackEncoderDynamicTableSize(), 0); +} + +// Should also test: client attempts shutdown, server attempts shutdown after an +// explicit GOAWAY. +TEST(NgHttp2AdapterTest, ServerSendsShutdown) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + + const std::string frames = TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":method", "POST"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}, + /*fin=*/false) + .Serialize(); + testing::InSequence s; + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "POST")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + + const ssize_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(frames.size(), result); + + adapter->SubmitShutdownNotice(); + + EXPECT_TRUE(adapter->session().want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(GOAWAY, 0, _, 0x0, 0)); + + int send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, + spdy::SpdyFrameType::GOAWAY})); +} + +TEST(NgHttp2AdapterTest, ServerSendsTrailers) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + EXPECT_FALSE(adapter->session().want_write()); + + const std::string frames = TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}, + /*fin=*/true) + .Serialize(); + testing::InSequence s; + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + // Stream 1 + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnEndStream(1)); + + const ssize_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(frames.size(), result); + + // Server will want to send a SETTINGS ack. + EXPECT_TRUE(adapter->session().want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + + int send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + visitor.Clear(); + + EXPECT_FALSE(adapter->session().want_write()); + const absl::string_view kBody = "This is an example response body."; + + // The body source must indicate that the end of the body is not the end of + // the stream. + auto body1 = absl::make_unique<TestDataFrameSource>(visitor, false); + body1->AppendPayload(kBody); + body1->EndData(); + int submit_result = adapter->SubmitResponse( + 1, ToHeaders({{":status", "200"}, {"x-comment", "Sure, sounds good."}}), + std::move(body1)); + EXPECT_EQ(submit_result, 0); + EXPECT_TRUE(adapter->session().want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0)); + EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0)); + + send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS, + spdy::SpdyFrameType::DATA})); + EXPECT_THAT(visitor.data(), testing::HasSubstr(kBody)); + visitor.Clear(); + EXPECT_FALSE(adapter->session().want_write()); + + // The body source has been exhausted by the call to Send() above. + int trailer_result = adapter->SubmitTrailer( + 1, ToHeaders({{"final-status", "a-ok"}, + {"x-comment", "trailers sure are cool"}})); + ASSERT_EQ(trailer_result, 0); + EXPECT_TRUE(adapter->session().want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x5, 0)); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR)); + + send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS})); +} + +TEST(NgHttp2AdapterTest, ClientSendsContinuation) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + EXPECT_FALSE(adapter->session().want_write()); + + const std::string frames = TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}, + /*fin=*/true, + /*add_continuation=*/true) + .Serialize(); + testing::InSequence s; + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + // Stream 1 + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 1)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); + EXPECT_CALL(visitor, OnFrameHeader(1, _, CONTINUATION, 4)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnEndStream(1)); + + const size_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(frames.size(), result); +} + +TEST(NgHttp2AdapterTest, ClientSendsMetadataWithContinuation) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + EXPECT_FALSE(adapter->session().want_write()); + + const std::string frames = + TestFrameSequence() + .ClientPreface() + .Metadata(0, "Example connection metadata in multiple frames", true) + .Headers(1, + {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}, + /*fin=*/false, + /*add_continuation=*/true) + .Metadata(1, + "Some stream metadata that's also sent in multiple frames", + true) + .Serialize(); + testing::InSequence s; + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + // Metadata on stream 0 + EXPECT_CALL(visitor, OnFrameHeader(0, _, kMetadataFrameType, 0)); + EXPECT_CALL(visitor, OnBeginMetadataForStream(0, _)); + EXPECT_CALL(visitor, OnMetadataForStream(0, _)); + EXPECT_CALL(visitor, OnFrameHeader(0, _, kMetadataFrameType, 4)); + EXPECT_CALL(visitor, OnBeginMetadataForStream(0, _)); + EXPECT_CALL(visitor, OnMetadataForStream(0, _)); + EXPECT_CALL(visitor, OnMetadataEndForStream(0)); + + // Stream 1 + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 0)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); + EXPECT_CALL(visitor, OnFrameHeader(1, _, CONTINUATION, 4)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + // Metadata on stream 1 + EXPECT_CALL(visitor, OnFrameHeader(1, _, kMetadataFrameType, 0)); + EXPECT_CALL(visitor, OnBeginMetadataForStream(1, _)); + EXPECT_CALL(visitor, OnMetadataForStream(1, _)); + EXPECT_CALL(visitor, OnFrameHeader(1, _, kMetadataFrameType, 4)); + EXPECT_CALL(visitor, OnBeginMetadataForStream(1, _)); + EXPECT_CALL(visitor, OnMetadataForStream(1, _)); + EXPECT_CALL(visitor, OnMetadataEndForStream(1)); + + const size_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(frames.size(), result); + EXPECT_EQ(TestFrameSequence::MetadataBlockForPayload( + "Example connection metadata in multiple frames"), + absl::StrJoin(visitor.GetMetadata(0), "")); + EXPECT_EQ(TestFrameSequence::MetadataBlockForPayload( + "Some stream metadata that's also sent in multiple frames"), + absl::StrJoin(visitor.GetMetadata(1), "")); +} + +TEST(NgHttp2AdapterTest, ServerSendsInvalidTrailers) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + EXPECT_FALSE(adapter->session().want_write()); + + const std::string frames = TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}, + /*fin=*/true) + .Serialize(); + testing::InSequence s; + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + // Stream 1 + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnEndStream(1)); + + const ssize_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(frames.size(), result); + + const absl::string_view kBody = "This is an example response body."; + + // The body source must indicate that the end of the body is not the end of + // the stream. + auto body1 = absl::make_unique<TestDataFrameSource>(visitor, false); + body1->AppendPayload(kBody); + body1->EndData(); + int submit_result = adapter->SubmitResponse( + 1, ToHeaders({{":status", "200"}, {"x-comment", "Sure, sounds good."}}), + std::move(body1)); + EXPECT_EQ(submit_result, 0); + EXPECT_TRUE(adapter->session().want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0)); + EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0)); + + int send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, + spdy::SpdyFrameType::HEADERS, + spdy::SpdyFrameType::DATA})); + EXPECT_THAT(visitor.data(), testing::HasSubstr(kBody)); + visitor.Clear(); + EXPECT_FALSE(adapter->session().want_write()); + + // The body source has been exhausted by the call to Send() above. + int trailer_result = + adapter->SubmitTrailer(1, ToHeaders({{":final-status", "a-ok"}})); + ASSERT_EQ(trailer_result, 0); + EXPECT_TRUE(adapter->session().want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x5, 0)); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR)); + + send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS})); } } // namespace diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_callbacks.cc b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_callbacks.cc index 8774873c95f..4a2221ea2ee 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_callbacks.cc +++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_callbacks.cc @@ -6,6 +6,7 @@ #include "absl/strings/string_view.h" #include "http2/adapter/http2_protocol.h" #include "http2/adapter/http2_visitor_interface.h" +#include "http2/adapter/nghttp2_data_provider.h" #include "http2/adapter/nghttp2_util.h" #include "third_party/nghttp2/nghttp2.h" #include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h" @@ -16,14 +17,33 @@ namespace http2 { namespace adapter { namespace callbacks { +ssize_t OnReadyToSend(nghttp2_session* /* session */, const uint8_t* data, + size_t length, int flags, void* user_data) { + QUICHE_CHECK_NE(user_data, nullptr); + auto* visitor = static_cast<Http2VisitorInterface*>(user_data); + const ssize_t result = visitor->OnReadyToSend(ToStringView(data, length)); + QUICHE_VLOG(1) << "OnReadyToSend(length=" << length << ", flags=" << flags + << ") returning " << result; + if (result > 0) { + return result; + } else if (result == Http2VisitorInterface::kSendBlocked) { + return -504; // NGHTTP2_ERR_WOULDBLOCK + } else { + return -902; // NGHTTP2_ERR_CALLBACK_FAILURE + } +} + int OnBeginFrame(nghttp2_session* /* session */, const nghttp2_frame_hd* header, void* user_data) { + QUICHE_CHECK_NE(user_data, nullptr); auto* visitor = static_cast<Http2VisitorInterface*>(user_data); visitor->OnFrameHeader(header->stream_id, header->length, header->type, header->flags); if (header->type == NGHTTP2_DATA) { visitor->OnBeginDataForStream(header->stream_id, header->length); + } else if (header->type == kMetadataFrameType) { + visitor->OnBeginMetadataForStream(header->stream_id, header->length); } return 0; } @@ -31,10 +51,9 @@ int OnBeginFrame(nghttp2_session* /* session */, int OnFrameReceived(nghttp2_session* /* session */, const nghttp2_frame* frame, void* user_data) { + QUICHE_CHECK_NE(user_data, nullptr); auto* visitor = static_cast<Http2VisitorInterface*>(user_data); const Http2StreamId stream_id = frame->hd.stream_id; - QUICHE_VLOG(2) << "Frame " << static_cast<int>(frame->hd.type) - << " for stream " << stream_id; switch (frame->hd.type) { // The beginning of the DATA frame is handled in OnBeginFrame(), and the // beginning of the header block is handled in client/server-specific @@ -71,7 +90,7 @@ int OnFrameReceived(nghttp2_session* /* session */, visitor->OnSettingsAck(); } else { visitor->OnSettingsStart(); - for (int i = 0; i < frame->settings.niv; ++i) { + for (size_t i = 0; i < frame->settings.niv; ++i) { nghttp2_settings_entry entry = frame->settings.iv[i]; // The nghttp2_settings_entry uses int32_t for the ID; we must cast. visitor->OnSetting(Http2Setting{ @@ -129,29 +148,67 @@ int OnFrameReceived(nghttp2_session* /* session */, int OnBeginHeaders(nghttp2_session* /* session */, const nghttp2_frame* frame, void* user_data) { + QUICHE_CHECK_NE(user_data, nullptr); auto* visitor = static_cast<Http2VisitorInterface*>(user_data); - visitor->OnBeginHeadersForStream(frame->hd.stream_id); - return 0; + const bool result = visitor->OnBeginHeadersForStream(frame->hd.stream_id); + return result ? 0 : NGHTTP2_ERR_CALLBACK_FAILURE; } -int OnHeader(nghttp2_session* /* session */, - const nghttp2_frame* frame, - nghttp2_rcbuf* name, - nghttp2_rcbuf* value, - uint8_t flags, +int OnHeader(nghttp2_session* /* session */, const nghttp2_frame* frame, + nghttp2_rcbuf* name, nghttp2_rcbuf* value, uint8_t /*flags*/, void* user_data) { + QUICHE_CHECK_NE(user_data, nullptr); auto* visitor = static_cast<Http2VisitorInterface*>(user_data); - visitor->OnHeaderForStream(frame->hd.stream_id, ToStringView(name), - ToStringView(value)); - return 0; + const Http2VisitorInterface::OnHeaderResult result = + visitor->OnHeaderForStream(frame->hd.stream_id, ToStringView(name), + ToStringView(value)); + switch (result) { + case Http2VisitorInterface::HEADER_OK: + return 0; + case Http2VisitorInterface::HEADER_CONNECTION_ERROR: + return NGHTTP2_ERR_CALLBACK_FAILURE; + case Http2VisitorInterface::HEADER_RST_STREAM: + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } } -int OnDataChunk(nghttp2_session* /* session */, - uint8_t flags, - Http2StreamId stream_id, - const uint8_t* data, - size_t len, +int OnBeforeFrameSent(nghttp2_session* /* session */, + const nghttp2_frame* frame, void* user_data) { + QUICHE_CHECK_NE(user_data, nullptr); + LogBeforeSend(*frame); + auto* visitor = static_cast<Http2VisitorInterface*>(user_data); + return visitor->OnBeforeFrameSent(frame->hd.type, frame->hd.stream_id, + frame->hd.length, frame->hd.flags); +} + +int OnFrameSent(nghttp2_session* /* session */, const nghttp2_frame* frame, void* user_data) { + QUICHE_CHECK_NE(user_data, nullptr); + auto* visitor = static_cast<Http2VisitorInterface*>(user_data); + uint32_t error_code = 0; + if (frame->hd.type == NGHTTP2_RST_STREAM) { + error_code = frame->rst_stream.error_code; + } else if (frame->hd.type == NGHTTP2_GOAWAY) { + error_code = frame->goaway.error_code; + } + return visitor->OnFrameSent(frame->hd.type, frame->hd.stream_id, + frame->hd.length, frame->hd.flags, error_code); +} + +int OnInvalidFrameReceived(nghttp2_session* /* session */, + const nghttp2_frame* frame, int lib_error_code, + void* user_data) { + QUICHE_CHECK_NE(user_data, nullptr); + auto* visitor = static_cast<Http2VisitorInterface*>(user_data); + const bool result = + visitor->OnInvalidFrame(frame->hd.stream_id, lib_error_code); + return result ? 0 : NGHTTP2_ERR_CALLBACK_FAILURE; +} + +int OnDataChunk(nghttp2_session* /* session */, uint8_t /*flags*/, + Http2StreamId stream_id, const uint8_t* data, size_t len, + void* user_data) { + QUICHE_CHECK_NE(user_data, nullptr); auto* visitor = static_cast<Http2VisitorInterface*>(user_data); visitor->OnDataForStream( stream_id, absl::string_view(reinterpret_cast<const char*>(data), len)); @@ -162,34 +219,63 @@ int OnStreamClosed(nghttp2_session* /* session */, Http2StreamId stream_id, uint32_t error_code, void* user_data) { + QUICHE_CHECK_NE(user_data, nullptr); auto* visitor = static_cast<Http2VisitorInterface*>(user_data); visitor->OnCloseStream(stream_id, ToHttp2ErrorCode(error_code)); return 0; } -ssize_t OnReadyToReadDataForStream(nghttp2_session* /* session */, - Http2StreamId stream_id, - uint8_t* dest_buffer, - size_t max_length, - uint32_t* data_flags, - nghttp2_data_source* source, - void* user_data) { - auto* visitor = static_cast<Http2VisitorInterface*>(source->ptr); - ssize_t bytes_to_send = 0; - bool end_stream = false; - visitor->OnReadyToSendDataForStream(stream_id, - reinterpret_cast<char*>(dest_buffer), - max_length, &bytes_to_send, &end_stream); - if (bytes_to_send >= 0 && end_stream) { - *data_flags |= NGHTTP2_DATA_FLAG_EOF; +int OnExtensionChunkReceived(nghttp2_session* /*session*/, + const nghttp2_frame_hd* hd, const uint8_t* data, + size_t len, void* user_data) { + QUICHE_CHECK_NE(user_data, nullptr); + auto* visitor = static_cast<Http2VisitorInterface*>(user_data); + if (hd->type != kMetadataFrameType) { + QUICHE_LOG(ERROR) << "Unexpected frame type: " + << static_cast<int>(hd->type); + return NGHTTP2_ERR_CANCEL; + } + visitor->OnMetadataForStream(hd->stream_id, ToStringView(data, len)); + return 0; +} + +int OnUnpackExtensionCallback(nghttp2_session* /*session*/, void** /*payload*/, + const nghttp2_frame_hd* hd, void* user_data) { + QUICHE_CHECK_NE(user_data, nullptr); + auto* visitor = static_cast<Http2VisitorInterface*>(user_data); + if (hd->flags == kMetadataEndFlag) { + const bool result = visitor->OnMetadataEndForStream(hd->stream_id); + if (!result) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } } - return bytes_to_send; + return 0; +} + +ssize_t OnPackExtensionCallback(nghttp2_session* /*session*/, uint8_t* buf, + size_t len, const nghttp2_frame* frame, + void* user_data) { + QUICHE_CHECK_NE(user_data, nullptr); + auto* visitor = static_cast<Http2VisitorInterface*>(user_data); + ssize_t written = 0; + visitor->OnReadyToSendMetadataForStream( + frame->hd.stream_id, reinterpret_cast<char*>(buf), len, &written); + return written; +} + +int OnError(nghttp2_session* /*session*/, int /*lib_error_code*/, + const char* msg, size_t len, void* user_data) { + QUICHE_CHECK_NE(user_data, nullptr); + auto* visitor = static_cast<Http2VisitorInterface*>(user_data); + visitor->OnErrorDebug(absl::string_view(msg, len)); + return 0; } nghttp2_session_callbacks_unique_ptr Create() { nghttp2_session_callbacks* callbacks; nghttp2_session_callbacks_new(&callbacks); + nghttp2_session_callbacks_set_send_callback(callbacks, &OnReadyToSend); nghttp2_session_callbacks_set_on_begin_frame_callback(callbacks, &OnBeginFrame); nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, @@ -201,6 +287,21 @@ nghttp2_session_callbacks_unique_ptr Create() { &OnDataChunk); nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, &OnStreamClosed); + nghttp2_session_callbacks_set_before_frame_send_callback(callbacks, + &OnBeforeFrameSent); + nghttp2_session_callbacks_set_on_frame_send_callback(callbacks, &OnFrameSent); + nghttp2_session_callbacks_set_on_invalid_frame_recv_callback( + callbacks, &OnInvalidFrameReceived); + nghttp2_session_callbacks_set_error_callback2(callbacks, &OnError); + // on_frame_not_send_callback <- just ignored + nghttp2_session_callbacks_set_send_data_callback( + callbacks, &DataFrameSourceSendCallback); + nghttp2_session_callbacks_set_pack_extension_callback( + callbacks, &OnPackExtensionCallback); + nghttp2_session_callbacks_set_unpack_extension_callback( + callbacks, &OnUnpackExtensionCallback); + nghttp2_session_callbacks_set_on_extension_chunk_recv_callback( + callbacks, &OnExtensionChunkReceived); return MakeCallbacksPtr(callbacks); } diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_callbacks.h b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_callbacks.h index 5fbaee041c5..696b6847509 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_callbacks.h +++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_callbacks.h @@ -13,6 +13,13 @@ namespace callbacks { // beginning of its lifetime. It is expected that |user_data| holds an // Http2VisitorInterface. +// Callback once the library is ready to send serialized frames. +ssize_t OnReadyToSend(nghttp2_session* session, + const uint8_t* data, + size_t length, + int flags, + void* user_data); + // Callback once a frame header has been received. int OnBeginFrame(nghttp2_session* session, const nghttp2_frame_hd* header, void* user_data); @@ -31,7 +38,19 @@ int OnHeader(nghttp2_session* session, const nghttp2_frame* frame, nghttp2_rcbuf* name, nghttp2_rcbuf* value, uint8_t flags, void* user_data); -// Callback once a chunk of data (from a DATA frame payload) has been received. +// Invoked immediately before sending a frame. +int OnBeforeFrameSent(nghttp2_session* session, const nghttp2_frame* frame, + void* user_data); + +// Invoked immediately after a frame is sent. +int OnFrameSent(nghttp2_session* session, const nghttp2_frame* frame, + void* user_data); + +// Invoked when an invalid frame is received. +int OnInvalidFrameReceived(nghttp2_session* session, const nghttp2_frame* frame, + int lib_error_code, void* user_data); + +// Invoked when a chunk of data (from a DATA frame payload) has been received. int OnDataChunk(nghttp2_session* session, uint8_t flags, Http2StreamId stream_id, const uint8_t* data, size_t len, void* user_data); @@ -40,13 +59,25 @@ int OnDataChunk(nghttp2_session* session, uint8_t flags, int OnStreamClosed(nghttp2_session* session, Http2StreamId stream_id, uint32_t error_code, void* user_data); -// Callback once nghttp2 is ready to read data from |source| into |dest_buffer|. -ssize_t OnReadyToReadDataForStream(nghttp2_session* session, - Http2StreamId stream_id, - uint8_t* dest_buffer, size_t max_length, - uint32_t* data_flags, - nghttp2_data_source* source, - void* user_data); +// Invoked when nghttp2 has a chunk of extension frame data to pass to the +// application. +int OnExtensionChunkReceived(nghttp2_session* session, + const nghttp2_frame_hd* hd, const uint8_t* data, + size_t len, void* user_data); + +// Invoked when nghttp2 wants the application to unpack an extension payload. +int OnUnpackExtensionCallback(nghttp2_session* session, void** payload, + const nghttp2_frame_hd* hd, void* user_data); + +// Invoked when nghttp2 is ready to pack an extension payload. Returns the +// number of bytes serialized to |buf|. +ssize_t OnPackExtensionCallback(nghttp2_session* session, uint8_t* buf, + size_t len, const nghttp2_frame* frame, + void* user_data); + +// Invoked when the library has an error message to deliver. +int OnError(nghttp2_session* session, int lib_error_code, const char* msg, + size_t len, void* user_data); nghttp2_session_callbacks_unique_ptr Create(); diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_data_provider.cc b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_data_provider.cc new file mode 100644 index 00000000000..200aa67ae29 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_data_provider.cc @@ -0,0 +1,63 @@ +#include "http2/adapter/nghttp2_data_provider.h" + +#include "http2/adapter/http2_visitor_interface.h" +#include "http2/adapter/nghttp2_util.h" + +namespace http2 { +namespace adapter { +namespace callbacks { + +namespace { +const size_t kFrameHeaderSize = 9; +} + +ssize_t DataFrameSourceReadCallback(nghttp2_session* /* session */, + int32_t /* stream_id */, + uint8_t* /* buf */, + size_t length, + uint32_t* data_flags, + nghttp2_data_source* source, + void* /* user_data */) { + *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY; + auto* frame_source = static_cast<DataFrameSource*>(source->ptr); + auto [result_length, done] = frame_source->SelectPayloadLength(length); + if (result_length == 0 && !done) { + return NGHTTP2_ERR_DEFERRED; + } else if (result_length == DataFrameSource::kError) { + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + if (done) { + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + } + if (!frame_source->send_fin()) { + *data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM; + } + return result_length; +} + +int DataFrameSourceSendCallback(nghttp2_session* /* session */, + nghttp2_frame* /* frame */, + const uint8_t* framehd, + size_t length, + nghttp2_data_source* source, + void* /* user_data */) { + auto* frame_source = static_cast<DataFrameSource*>(source->ptr); + frame_source->Send(ToStringView(framehd, kFrameHeaderSize), length); + return 0; +} + +} // namespace callbacks + +std::unique_ptr<nghttp2_data_provider> MakeDataProvider( + DataFrameSource* source) { + if (source == nullptr) { + return nullptr; + } + auto provider = absl::make_unique<nghttp2_data_provider>(); + provider->source.ptr = source; + provider->read_callback = &callbacks::DataFrameSourceReadCallback; + return provider; +} + +} // namespace adapter +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_data_provider.h b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_data_provider.h new file mode 100644 index 00000000000..241bab91cdd --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_data_provider.h @@ -0,0 +1,37 @@ +#ifndef QUICHE_HTTP2_ADAPTER_NGHTTP2_DATA_PROVIDER_H_ +#define QUICHE_HTTP2_ADAPTER_NGHTTP2_DATA_PROVIDER_H_ + +#include "http2/adapter/data_source.h" +#include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h" + +namespace http2 { +namespace adapter { +namespace callbacks { + +// Assumes |source| is a DataFrameSource. +ssize_t DataFrameSourceReadCallback(nghttp2_session* /*session */, + int32_t /* stream_id */, + uint8_t* /* buf */, + size_t length, + uint32_t* data_flags, + nghttp2_data_source* source, + void* /* user_data */); + +int DataFrameSourceSendCallback(nghttp2_session* /* session */, + nghttp2_frame* /* frame */, + const uint8_t* framehd, + size_t length, + nghttp2_data_source* source, + void* /* user_data */); + +} // namespace callbacks + +// Transforms a DataFrameSource into a nghttp2_data_provider. Does not take +// ownership of |source|. Returns nullptr if |source| is nullptr. +std::unique_ptr<nghttp2_data_provider> MakeDataProvider( + DataFrameSource* source); + +} // namespace adapter +} // namespace http2 + +#endif // QUICHE_HTTP2_ADAPTER_NGHTTP2_DATA_PROVIDER_H_ diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_data_provider_test.cc b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_data_provider_test.cc new file mode 100644 index 00000000000..af8d98187f2 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_data_provider_test.cc @@ -0,0 +1,117 @@ +#include "http2/adapter/nghttp2_data_provider.h" + +#include "http2/adapter/test_utils.h" +#include "common/platform/api/quiche_test.h" + +namespace http2 { +namespace adapter { +namespace test { + +const size_t kFrameHeaderSize = 9; + +// Verifies that a nghttp2_data_provider derived from a DataFrameSource works +// correctly with nghttp2-style callbacks when the amount of data read is less +// than what the source provides. +TEST(DataProviderTest, ReadLessThanSourceProvides) { + DataSavingVisitor visitor; + TestDataFrameSource source(visitor, true); + source.AppendPayload("Example payload"); + source.EndData(); + auto provider = MakeDataProvider(&source); + uint32_t data_flags = 0; + const int32_t kStreamId = 1; + const size_t kReadLength = 10; + // Read callback selects a payload length given an upper bound. + ssize_t result = + provider->read_callback(nullptr, kStreamId, nullptr, kReadLength, + &data_flags, &provider->source, nullptr); + ASSERT_EQ(kReadLength, result); + EXPECT_EQ(NGHTTP2_DATA_FLAG_NO_COPY, data_flags); + + const uint8_t framehd[kFrameHeaderSize] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; + // Sends the frame header and some payload bytes. + int send_result = callbacks::DataFrameSourceSendCallback( + nullptr, nullptr, framehd, result, &provider->source, nullptr); + EXPECT_EQ(0, send_result); + // Data accepted by the visitor includes a frame header and kReadLength bytes + // of payload. + EXPECT_EQ(visitor.data().size(), kFrameHeaderSize + kReadLength); +} + +// Verifies that a nghttp2_data_provider derived from a DataFrameSource works +// correctly with nghttp2-style callbacks when the amount of data read is more +// than what the source provides. +TEST(DataProviderTest, ReadMoreThanSourceProvides) { + DataSavingVisitor visitor; + const absl::string_view kPayload = "Example payload"; + TestDataFrameSource source(visitor, true); + source.AppendPayload(kPayload); + source.EndData(); + auto provider = MakeDataProvider(&source); + uint32_t data_flags = 0; + const int32_t kStreamId = 1; + const size_t kReadLength = 30; + // Read callback selects a payload length given an upper bound. + ssize_t result = + provider->read_callback(nullptr, kStreamId, nullptr, kReadLength, + &data_flags, &provider->source, nullptr); + ASSERT_EQ(kPayload.size(), result); + EXPECT_EQ(NGHTTP2_DATA_FLAG_NO_COPY | NGHTTP2_DATA_FLAG_EOF, data_flags); + + const uint8_t framehd[kFrameHeaderSize] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; + // Sends the frame header and some payload bytes. + int send_result = callbacks::DataFrameSourceSendCallback( + nullptr, nullptr, framehd, result, &provider->source, nullptr); + EXPECT_EQ(0, send_result); + // Data accepted by the visitor includes a frame header and the entire + // payload. + EXPECT_EQ(visitor.data().size(), kFrameHeaderSize + kPayload.size()); +} + +// Verifies that a nghttp2_data_provider derived from a DataFrameSource works +// correctly with nghttp2-style callbacks when the source is blocked. +TEST(DataProviderTest, ReadFromBlockedSource) { + DataSavingVisitor visitor; + // Source has no payload, but also no fin, so it's blocked. + TestDataFrameSource source(visitor, false); + auto provider = MakeDataProvider(&source); + uint32_t data_flags = 0; + const int32_t kStreamId = 1; + const size_t kReadLength = 10; + ssize_t result = + provider->read_callback(nullptr, kStreamId, nullptr, kReadLength, + &data_flags, &provider->source, nullptr); + // Read operation is deferred, since the source is blocked. + EXPECT_EQ(NGHTTP2_ERR_DEFERRED, result); +} + +// Verifies that a nghttp2_data_provider derived from a DataFrameSource works +// correctly with nghttp2-style callbacks when the source provides only fin and +// no data. +TEST(DataProviderTest, ReadFromZeroLengthSource) { + DataSavingVisitor visitor; + // Empty payload and fin=true indicates the source is done. + TestDataFrameSource source(visitor, true); + source.EndData(); + auto provider = MakeDataProvider(&source); + uint32_t data_flags = 0; + const int32_t kStreamId = 1; + const size_t kReadLength = 10; + ssize_t result = + provider->read_callback(nullptr, kStreamId, nullptr, kReadLength, + &data_flags, &provider->source, nullptr); + ASSERT_EQ(0, result); + EXPECT_EQ(NGHTTP2_DATA_FLAG_NO_COPY | NGHTTP2_DATA_FLAG_EOF, data_flags); + + const uint8_t framehd[kFrameHeaderSize] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; + int send_result = callbacks::DataFrameSourceSendCallback( + nullptr, nullptr, framehd, result, &provider->source, nullptr); + EXPECT_EQ(0, send_result); + // Data accepted by the visitor includes a frame header with fin and zero + // bytes of payload. + EXPECT_EQ(visitor.data().size(), kFrameHeaderSize); +} + +} // namespace test +} // namespace adapter +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_session.cc b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_session.cc index d434b06e0f5..9868958bc67 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_session.cc +++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_session.cc @@ -4,35 +4,21 @@ namespace http2 { namespace adapter { -namespace { - -void DeleteOptions(nghttp2_option* options) { - if (options) { - nghttp2_option_del(options); - } -} - -} // namespace NgHttp2Session::NgHttp2Session(Perspective perspective, nghttp2_session_callbacks_unique_ptr callbacks, - nghttp2_option* options, - void* userdata) - : session_(MakeSessionPtr(nullptr)), - options_(options, DeleteOptions), - perspective_(perspective) { + const nghttp2_option* options, void* userdata) + : session_(MakeSessionPtr(nullptr)), perspective_(perspective) { nghttp2_session* session; switch (perspective) { case Perspective::kClient: - nghttp2_session_client_new2(&session, callbacks.get(), userdata, - options_.get()); + nghttp2_session_client_new2(&session, callbacks.get(), userdata, options); break; case Perspective::kServer: - nghttp2_session_server_new2(&session, callbacks.get(), userdata, - options_.get()); + nghttp2_session_server_new2(&session, callbacks.get(), userdata, options); break; } - session_.reset(session); + session_ = MakeSessionPtr(session); } NgHttp2Session::~NgHttp2Session() { diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_session.h b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_session.h index d446a07c75b..4339875588c 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_session.h +++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_session.h @@ -4,18 +4,18 @@ #include "http2/adapter/http2_session.h" #include "http2/adapter/nghttp2_util.h" #include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h" +#include "common/platform/api/quiche_export.h" namespace http2 { namespace adapter { // A C++ wrapper around common nghttp2_session operations. -class NgHttp2Session : public Http2Session { +class QUICHE_EXPORT_PRIVATE NgHttp2Session : public Http2Session { public: - // Takes ownership of |options|. + // Does not take ownership of |options|. NgHttp2Session(Perspective perspective, nghttp2_session_callbacks_unique_ptr callbacks, - nghttp2_option* options, - void* userdata); + const nghttp2_option* options, void* userdata); ~NgHttp2Session() override; ssize_t ProcessBytes(absl::string_view bytes) override; @@ -29,10 +29,7 @@ class NgHttp2Session : public Http2Session { nghttp2_session* raw_ptr() const { return session_.get(); } private: - using OptionsDeleter = void (&)(nghttp2_option*); - nghttp2_session_unique_ptr session_; - std::unique_ptr<nghttp2_option, OptionsDeleter> options_; Perspective perspective_; }; diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_session_test.cc b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_session_test.cc index 169d2f20dd0..487843bcc52 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_session_test.cc +++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_session_test.cc @@ -26,47 +26,36 @@ enum FrameType { WINDOW_UPDATE, }; -ssize_t SaveSessionOutput(nghttp2_session* /* session*/, - const uint8_t* data, - size_t length, - int /* flags */, - void* user_data) { - auto visitor = static_cast<DataSavingVisitor*>(user_data); - visitor->Save(ToStringView(data, length)); - return length; -} - class NgHttp2SessionTest : public testing::Test { public: - nghttp2_option* CreateOptions() { - nghttp2_option* options; - nghttp2_option_new(&options); - nghttp2_option_set_no_auto_window_update(options, 1); - return options; + void SetUp() override { + nghttp2_option_new(&options_); + nghttp2_option_set_no_auto_window_update(options_, 1); } + void TearDown() override { nghttp2_option_del(options_); } + nghttp2_session_callbacks_unique_ptr CreateCallbacks() { nghttp2_session_callbacks_unique_ptr callbacks = callbacks::Create(); - nghttp2_session_callbacks_set_send_callback(callbacks.get(), - &SaveSessionOutput); return callbacks; } DataSavingVisitor visitor_; + nghttp2_option* options_ = nullptr; }; TEST_F(NgHttp2SessionTest, ClientConstruction) { - NgHttp2Session session(Perspective::kClient, CreateCallbacks(), - CreateOptions(), &visitor_); + NgHttp2Session session(Perspective::kClient, CreateCallbacks(), options_, + &visitor_); EXPECT_TRUE(session.want_read()); EXPECT_FALSE(session.want_write()); - EXPECT_EQ(session.GetRemoteWindowSize(), kDefaultInitialStreamWindowSize); + EXPECT_EQ(session.GetRemoteWindowSize(), kInitialFlowControlWindowSize); EXPECT_NE(session.raw_ptr(), nullptr); } TEST_F(NgHttp2SessionTest, ClientHandlesFrames) { - NgHttp2Session session(Perspective::kClient, CreateCallbacks(), - CreateOptions(), &visitor_); + NgHttp2Session session(Perspective::kClient, CreateCallbacks(), options_, + &visitor_); ASSERT_EQ(0, nghttp2_session_send(session.raw_ptr())); ASSERT_GT(visitor_.data().size(), 0); @@ -92,7 +81,13 @@ TEST_F(NgHttp2SessionTest, ClientHandlesFrames) { EXPECT_EQ(initial_frames.size(), initial_result); EXPECT_EQ(session.GetRemoteWindowSize(), - kDefaultInitialStreamWindowSize + 1000); + kInitialFlowControlWindowSize + 1000); + + EXPECT_CALL(visitor_, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor_, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + EXPECT_CALL(visitor_, OnBeforeFrameSent(PING, 0, 8, 0x1)); + EXPECT_CALL(visitor_, OnFrameSent(PING, 0, 8, 0x1, 0)); + ASSERT_EQ(0, nghttp2_session_send(session.raw_ptr())); // Some bytes should have been serialized. absl::string_view serialized = visitor_.data(); @@ -139,6 +134,13 @@ TEST_F(NgHttp2SessionTest, ClientHandlesFrames) { ASSERT_GT(stream_id3, 0); QUICHE_LOG(INFO) << "Created stream: " << stream_id3; + EXPECT_CALL(visitor_, OnBeforeFrameSent(HEADERS, 1, _, 0x5)); + EXPECT_CALL(visitor_, OnFrameSent(HEADERS, 1, _, 0x5, 0)); + EXPECT_CALL(visitor_, OnBeforeFrameSent(HEADERS, 3, _, 0x5)); + EXPECT_CALL(visitor_, OnFrameSent(HEADERS, 3, _, 0x5, 0)); + EXPECT_CALL(visitor_, OnBeforeFrameSent(HEADERS, 5, _, 0x5)); + EXPECT_CALL(visitor_, OnFrameSent(HEADERS, 5, _, 0x5, 0)); + ASSERT_EQ(0, nghttp2_session_send(session.raw_ptr())); serialized = visitor_.data(); EXPECT_THAT(serialized, EqualsFrames({spdy::SpdyFrameType::HEADERS, @@ -203,17 +205,17 @@ TEST_F(NgHttp2SessionTest, ClientHandlesFrames) { } TEST_F(NgHttp2SessionTest, ServerConstruction) { - NgHttp2Session session(Perspective::kServer, CreateCallbacks(), - CreateOptions(), &visitor_); + NgHttp2Session session(Perspective::kServer, CreateCallbacks(), options_, + &visitor_); EXPECT_TRUE(session.want_read()); EXPECT_FALSE(session.want_write()); - EXPECT_EQ(session.GetRemoteWindowSize(), kDefaultInitialStreamWindowSize); + EXPECT_EQ(session.GetRemoteWindowSize(), kInitialFlowControlWindowSize); EXPECT_NE(session.raw_ptr(), nullptr); } TEST_F(NgHttp2SessionTest, ServerHandlesFrames) { - NgHttp2Session session(Perspective::kServer, CreateCallbacks(), - CreateOptions(), &visitor_); + NgHttp2Session session(Perspective::kServer, CreateCallbacks(), options_, + &visitor_); const std::string frames = TestFrameSequence() .ClientPreface() @@ -277,7 +279,14 @@ TEST_F(NgHttp2SessionTest, ServerHandlesFrames) { EXPECT_EQ(frames.size(), result); EXPECT_EQ(session.GetRemoteWindowSize(), - kDefaultInitialStreamWindowSize + 1000); + kInitialFlowControlWindowSize + 1000); + + EXPECT_CALL(visitor_, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor_, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + EXPECT_CALL(visitor_, OnBeforeFrameSent(PING, 0, 8, 0x1)); + EXPECT_CALL(visitor_, OnFrameSent(PING, 0, 8, 0x1, 0)); + EXPECT_CALL(visitor_, OnBeforeFrameSent(PING, 0, 8, 0x1)); + EXPECT_CALL(visitor_, OnFrameSent(PING, 0, 8, 0x1, 0)); EXPECT_TRUE(session.want_write()); ASSERT_EQ(0, nghttp2_session_send(session.raw_ptr())); diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_test.cc b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_test.cc new file mode 100644 index 00000000000..0b977dc1815 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_test.cc @@ -0,0 +1,205 @@ +#include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h" + +#include "absl/strings/str_cat.h" +#include "http2/adapter/mock_nghttp2_callbacks.h" +#include "http2/adapter/nghttp2_test_utils.h" +#include "http2/adapter/nghttp2_util.h" +#include "http2/adapter/test_frame_sequence.h" +#include "http2/adapter/test_utils.h" +#include "common/platform/api/quiche_test.h" + +namespace http2 { +namespace adapter { +namespace test { +namespace { + +using testing::_; + +enum FrameType { + DATA, + HEADERS, + PRIORITY, + RST_STREAM, + SETTINGS, + PUSH_PROMISE, + PING, + GOAWAY, + WINDOW_UPDATE, +}; + +nghttp2_option* GetOptions() { + nghttp2_option* options; + nghttp2_option_new(&options); + // Set some common options for compatibility. + nghttp2_option_set_no_closed_streams(options, 1); + nghttp2_option_set_no_auto_window_update(options, 1); + nghttp2_option_set_max_send_header_block_length(options, 0x2000000); + nghttp2_option_set_max_outbound_ack(options, 10000); + return options; +} + +class Nghttp2Test : public testing::Test { + public: + Nghttp2Test() : session_(MakeSessionPtr(nullptr)) {} + + void SetUp() override { InitializeSession(); } + + virtual Perspective GetPerspective() = 0; + + void InitializeSession() { + auto nghttp2_callbacks = MockNghttp2Callbacks::GetCallbacks(); + nghttp2_option* options = GetOptions(); + nghttp2_session* ptr; + if (GetPerspective() == Perspective::kClient) { + nghttp2_session_client_new2(&ptr, nghttp2_callbacks.get(), + &mock_callbacks_, options); + } else { + nghttp2_session_server_new2(&ptr, nghttp2_callbacks.get(), + &mock_callbacks_, options); + } + nghttp2_option_del(options); + + // Sets up the Send() callback to append to |serialized_|. + EXPECT_CALL(mock_callbacks_, Send(_, _, _)) + .WillRepeatedly( + [this](const uint8_t* data, size_t length, int /*flags*/) { + absl::StrAppend(&serialized_, ToStringView(data, length)); + return length; + }); + // Sets up the SendData() callback to fetch and append data from a + // TestDataSource. + EXPECT_CALL(mock_callbacks_, SendData(_, _, _, _)) + .WillRepeatedly([this](nghttp2_frame* /*frame*/, const uint8_t* framehd, + size_t length, nghttp2_data_source* source) { + QUICHE_LOG(INFO) << "Appending frame header and " << length + << " bytes of data"; + auto* s = static_cast<TestDataSource*>(source->ptr); + absl::StrAppend(&serialized_, ToStringView(framehd, 9), + s->ReadNext(length)); + return 0; + }); + session_ = MakeSessionPtr(ptr); + } + + testing::StrictMock<MockNghttp2Callbacks> mock_callbacks_; + nghttp2_session_unique_ptr session_; + std::string serialized_; +}; + +class Nghttp2ClientTest : public Nghttp2Test { + public: + Perspective GetPerspective() override { return Perspective::kClient; } +}; + +// Verifies nghttp2 behavior when acting as a client. +TEST_F(Nghttp2ClientTest, ClientReceivesUnexpectedHeaders) { + const std::string initial_frames = TestFrameSequence() + .ServerPreface() + .Ping(42) + .WindowUpdate(0, 1000) + .Serialize(); + + testing::InSequence seq; + EXPECT_CALL(mock_callbacks_, OnBeginFrame(HasFrameHeader(0, SETTINGS, 0))); + EXPECT_CALL(mock_callbacks_, OnFrameRecv(IsSettings(testing::IsEmpty()))); + EXPECT_CALL(mock_callbacks_, OnBeginFrame(HasFrameHeader(0, PING, 0))); + EXPECT_CALL(mock_callbacks_, OnFrameRecv(IsPing(42))); + EXPECT_CALL(mock_callbacks_, + OnBeginFrame(HasFrameHeader(0, WINDOW_UPDATE, 0))); + EXPECT_CALL(mock_callbacks_, OnFrameRecv(IsWindowUpdate(1000))); + + ssize_t result = nghttp2_session_mem_recv( + session_.get(), ToUint8Ptr(initial_frames.data()), initial_frames.size()); + ASSERT_EQ(result, initial_frames.size()); + + const std::string unexpected_stream_frames = + TestFrameSequence() + .Headers(1, + {{":status", "200"}, + {"server", "my-fake-server"}, + {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}}, + /*fin=*/false) + .Data(1, "This is the response body.") + .RstStream(3, Http2ErrorCode::INTERNAL_ERROR) + .GoAway(5, Http2ErrorCode::ENHANCE_YOUR_CALM, "calm down!!") + .Serialize(); + + EXPECT_CALL(mock_callbacks_, OnBeginFrame(HasFrameHeader(1, HEADERS, _))); + EXPECT_CALL(mock_callbacks_, OnInvalidFrameRecv(IsHeaders(1, _, _), _)); + // No events from the DATA, RST_STREAM or GOAWAY. + + nghttp2_session_mem_recv(session_.get(), + ToUint8Ptr(unexpected_stream_frames.data()), + unexpected_stream_frames.size()); +} + +// Tests the request-sending behavior of nghttp2 when acting as a client. +TEST_F(Nghttp2ClientTest, ClientSendsRequest) { + int result = nghttp2_session_send(session_.get()); + ASSERT_EQ(result, 0); + + EXPECT_THAT(serialized_, testing::StrEq(spdy::kHttp2ConnectionHeaderPrefix)); + serialized_.clear(); + + const std::string initial_frames = + TestFrameSequence().ServerPreface().Serialize(); + testing::InSequence s; + + // Server preface (empty SETTINGS) + EXPECT_CALL(mock_callbacks_, OnBeginFrame(HasFrameHeader(0, SETTINGS, 0))); + EXPECT_CALL(mock_callbacks_, OnFrameRecv(IsSettings(testing::IsEmpty()))); + + ssize_t recv_result = nghttp2_session_mem_recv( + session_.get(), ToUint8Ptr(initial_frames.data()), initial_frames.size()); + EXPECT_EQ(initial_frames.size(), recv_result); + + // Client wants to send a SETTINGS ack. + EXPECT_CALL(mock_callbacks_, BeforeFrameSend(IsSettings(testing::IsEmpty()))); + EXPECT_CALL(mock_callbacks_, OnFrameSend(IsSettings(testing::IsEmpty()))); + EXPECT_TRUE(nghttp2_session_want_write(session_.get())); + result = nghttp2_session_send(session_.get()); + EXPECT_THAT(serialized_, EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + serialized_.clear(); + + EXPECT_FALSE(nghttp2_session_want_write(session_.get())); + + // The following sets up the client request. + std::vector<std::pair<absl::string_view, absl::string_view>> headers = { + {":method", "POST"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}; + std::vector<nghttp2_nv> nvs; + for (const auto& h : headers) { + nvs.push_back({.name = ToUint8Ptr(h.first.data()), + .value = ToUint8Ptr(h.second.data()), + .namelen = h.first.size(), + .valuelen = h.second.size()}); + } + const absl::string_view kBody = "This is an example request body."; + TestDataSource source{kBody}; + nghttp2_data_provider provider = source.MakeDataProvider(); + // After submitting the request, the client will want to write. + int stream_id = + nghttp2_submit_request(session_.get(), nullptr /* pri_spec */, nvs.data(), + nvs.size(), &provider, nullptr /* stream_data */); + EXPECT_GT(stream_id, 0); + EXPECT_TRUE(nghttp2_session_want_write(session_.get())); + + // We expect that the client will want to write HEADERS, then DATA. + EXPECT_CALL(mock_callbacks_, BeforeFrameSend(IsHeaders(stream_id, _, _))); + EXPECT_CALL(mock_callbacks_, OnFrameSend(IsHeaders(stream_id, _, _))); + EXPECT_CALL(mock_callbacks_, OnFrameSend(IsData(stream_id, kBody.size(), _))); + nghttp2_session_send(session_.get()); + EXPECT_THAT(serialized_, EqualsFrames({spdy::SpdyFrameType::HEADERS, + spdy::SpdyFrameType::DATA})); + EXPECT_THAT(serialized_, testing::HasSubstr(kBody)); + + // Once the request is flushed, the client no longer wants to write. + EXPECT_FALSE(nghttp2_session_want_write(session_.get())); +} + +} // namespace +} // namespace test +} // namespace adapter +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_test_utils.cc b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_test_utils.cc new file mode 100644 index 00000000000..d9a04f27772 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_test_utils.cc @@ -0,0 +1,454 @@ +#include "http2/adapter/nghttp2_test_utils.h" + +#include "http2/adapter/nghttp2_util.h" +#include "common/quiche_endian.h" + +namespace http2 { +namespace adapter { +namespace test { + +namespace { + +// Custom gMock matcher, used to implement HasFrameHeader(). +class FrameHeaderMatcher { + public: + FrameHeaderMatcher(int32_t streamid, uint8_t type, + const testing::Matcher<int> flags) + : stream_id_(streamid), type_(type), flags_(flags) {} + + bool Match(const nghttp2_frame_hd& frame, + testing::MatchResultListener* listener) const { + bool matched = true; + if (stream_id_ != frame.stream_id) { + *listener << "; expected stream " << stream_id_ << ", saw " + << frame.stream_id; + matched = false; + } + if (type_ != frame.type) { + *listener << "; expected frame type " << type_ << ", saw " + << static_cast<int>(frame.type); + matched = false; + } + if (!flags_.MatchAndExplain(frame.flags, listener)) { + matched = false; + } + return matched; + } + + void DescribeTo(std::ostream* os) const { + *os << "contains a frame header with stream " << stream_id_ << ", type " + << type_ << ", "; + flags_.DescribeTo(os); + } + + void DescribeNegationTo(std::ostream* os) const { + *os << "does not contain a frame header with stream " << stream_id_ + << ", type " << type_ << ", "; + flags_.DescribeNegationTo(os); + } + + private: + const int32_t stream_id_; + const int type_; + const testing::Matcher<int> flags_; +}; + +class PointerToFrameHeaderMatcher + : public FrameHeaderMatcher, + public testing::MatcherInterface<const nghttp2_frame_hd*> { + public: + PointerToFrameHeaderMatcher(int32_t streamid, uint8_t type, + const testing::Matcher<int> flags) + : FrameHeaderMatcher(streamid, type, flags) {} + + bool MatchAndExplain(const nghttp2_frame_hd* frame, + testing::MatchResultListener* listener) const override { + return FrameHeaderMatcher::Match(*frame, listener); + } + + void DescribeTo(std::ostream* os) const override { + FrameHeaderMatcher::DescribeTo(os); + } + + void DescribeNegationTo(std::ostream* os) const override { + FrameHeaderMatcher::DescribeNegationTo(os); + } +}; + +class ReferenceToFrameHeaderMatcher + : public FrameHeaderMatcher, + public testing::MatcherInterface<const nghttp2_frame_hd&> { + public: + ReferenceToFrameHeaderMatcher(int32_t streamid, uint8_t type, + const testing::Matcher<int> flags) + : FrameHeaderMatcher(streamid, type, flags) {} + + bool MatchAndExplain(const nghttp2_frame_hd& frame, + testing::MatchResultListener* listener) const override { + return FrameHeaderMatcher::Match(frame, listener); + } + + void DescribeTo(std::ostream* os) const override { + FrameHeaderMatcher::DescribeTo(os); + } + + void DescribeNegationTo(std::ostream* os) const override { + FrameHeaderMatcher::DescribeNegationTo(os); + } +}; + +class DataMatcher : public testing::MatcherInterface<const nghttp2_frame*> { + public: + DataMatcher(const testing::Matcher<uint32_t> stream_id, + const testing::Matcher<size_t> length, + const testing::Matcher<int> flags) + : stream_id_(stream_id), length_(length), flags_(flags) {} + + bool MatchAndExplain(const nghttp2_frame* frame, + testing::MatchResultListener* listener) const override { + if (frame->hd.type != NGHTTP2_DATA) { + *listener << "; expected DATA frame, saw frame of type " + << static_cast<int>(frame->hd.type); + return false; + } + bool matched = true; + if (!stream_id_.MatchAndExplain(frame->hd.stream_id, listener)) { + matched = false; + } + if (!length_.MatchAndExplain(frame->hd.length, listener)) { + matched = false; + } + if (!flags_.MatchAndExplain(frame->hd.flags, listener)) { + matched = false; + } + return matched; + } + + void DescribeTo(std::ostream* os) const override { + *os << "contains a DATA frame, "; + stream_id_.DescribeTo(os); + length_.DescribeTo(os); + flags_.DescribeTo(os); + } + + void DescribeNegationTo(std::ostream* os) const override { + *os << "does not contain a DATA frame, "; + stream_id_.DescribeNegationTo(os); + length_.DescribeNegationTo(os); + flags_.DescribeNegationTo(os); + } + + private: + const testing::Matcher<uint32_t> stream_id_; + const testing::Matcher<size_t> length_; + const testing::Matcher<int> flags_; +}; + +class HeadersMatcher : public testing::MatcherInterface<const nghttp2_frame*> { + public: + HeadersMatcher(const testing::Matcher<uint32_t> stream_id, + const testing::Matcher<int> flags, + const testing::Matcher<int> category) + : stream_id_(stream_id), flags_(flags), category_(category) {} + + bool MatchAndExplain(const nghttp2_frame* frame, + testing::MatchResultListener* listener) const override { + if (frame->hd.type != NGHTTP2_HEADERS) { + *listener << "; expected HEADERS frame, saw frame of type " + << static_cast<int>(frame->hd.type); + return false; + } + bool matched = true; + if (!stream_id_.MatchAndExplain(frame->hd.stream_id, listener)) { + matched = false; + } + if (!flags_.MatchAndExplain(frame->hd.flags, listener)) { + matched = false; + } + if (!category_.MatchAndExplain(frame->headers.cat, listener)) { + matched = false; + } + return matched; + } + + void DescribeTo(std::ostream* os) const override { + *os << "contains a HEADERS frame, "; + stream_id_.DescribeTo(os); + flags_.DescribeTo(os); + category_.DescribeTo(os); + } + + void DescribeNegationTo(std::ostream* os) const override { + *os << "does not contain a HEADERS frame, "; + stream_id_.DescribeNegationTo(os); + flags_.DescribeNegationTo(os); + category_.DescribeNegationTo(os); + } + + private: + const testing::Matcher<uint32_t> stream_id_; + const testing::Matcher<int> flags_; + const testing::Matcher<int> category_; +}; + +class RstStreamMatcher + : public testing::MatcherInterface<const nghttp2_frame*> { + public: + RstStreamMatcher(const testing::Matcher<uint32_t> stream_id, + const testing::Matcher<uint32_t> error_code) + : stream_id_(stream_id), error_code_(error_code) {} + + bool MatchAndExplain(const nghttp2_frame* frame, + testing::MatchResultListener* listener) const override { + if (frame->hd.type != NGHTTP2_RST_STREAM) { + *listener << "; expected RST_STREAM frame, saw frame of type " + << static_cast<int>(frame->hd.type); + return false; + } + bool matched = true; + if (!stream_id_.MatchAndExplain(frame->hd.stream_id, listener)) { + matched = false; + } + if (!error_code_.MatchAndExplain(frame->rst_stream.error_code, listener)) { + matched = false; + } + return matched; + } + + void DescribeTo(std::ostream* os) const override { + *os << "contains a RST_STREAM frame, "; + stream_id_.DescribeTo(os); + error_code_.DescribeTo(os); + } + + void DescribeNegationTo(std::ostream* os) const override { + *os << "does not contain a RST_STREAM frame, "; + stream_id_.DescribeNegationTo(os); + error_code_.DescribeNegationTo(os); + } + + private: + const testing::Matcher<uint32_t> stream_id_; + const testing::Matcher<uint32_t> error_code_; +}; + +class SettingsMatcher : public testing::MatcherInterface<const nghttp2_frame*> { + public: + SettingsMatcher(const testing::Matcher<std::vector<Http2Setting>> values) + : values_(values) {} + + bool MatchAndExplain(const nghttp2_frame* frame, + testing::MatchResultListener* listener) const override { + if (frame->hd.type != NGHTTP2_SETTINGS) { + *listener << "; expected SETTINGS frame, saw frame of type " + << static_cast<int>(frame->hd.type); + return false; + } + std::vector<Http2Setting> settings; + settings.reserve(frame->settings.niv); + for (size_t i = 0; i < frame->settings.niv; ++i) { + const auto& p = frame->settings.iv[i]; + settings.push_back({static_cast<uint16_t>(p.settings_id), p.value}); + } + return values_.MatchAndExplain(settings, listener); + } + + void DescribeTo(std::ostream* os) const override { + *os << "contains a SETTINGS frame, "; + values_.DescribeTo(os); + } + + void DescribeNegationTo(std::ostream* os) const override { + *os << "does not contain a SETTINGS frame, "; + values_.DescribeNegationTo(os); + } + + private: + const testing::Matcher<std::vector<Http2Setting>> values_; +}; + +class PingMatcher : public testing::MatcherInterface<const nghttp2_frame*> { + public: + PingMatcher(const testing::Matcher<uint64_t> id, bool is_ack) + : id_(id), is_ack_(is_ack) {} + + bool MatchAndExplain(const nghttp2_frame* frame, + testing::MatchResultListener* listener) const override { + if (frame->hd.type != NGHTTP2_PING) { + *listener << "; expected PING frame, saw frame of type " + << static_cast<int>(frame->hd.type); + return false; + } + bool matched = true; + bool frame_ack = frame->hd.flags & NGHTTP2_FLAG_ACK; + if (is_ack_ != frame_ack) { + *listener << "; expected is_ack=" << is_ack_ << ", saw " << frame_ack; + matched = false; + } + uint64_t data; + std::memcpy(&data, frame->ping.opaque_data, sizeof(data)); + data = quiche::QuicheEndian::HostToNet64(data); + if (!id_.MatchAndExplain(data, listener)) { + matched = false; + } + return matched; + } + + void DescribeTo(std::ostream* os) const override { + *os << "contains a PING frame, "; + id_.DescribeTo(os); + } + + void DescribeNegationTo(std::ostream* os) const override { + *os << "does not contain a PING frame, "; + id_.DescribeNegationTo(os); + } + + private: + const testing::Matcher<uint64_t> id_; + const bool is_ack_; +}; + +class GoAwayMatcher : public testing::MatcherInterface<const nghttp2_frame*> { + public: + GoAwayMatcher(const testing::Matcher<uint32_t> last_stream_id, + const testing::Matcher<uint32_t> error_code, + const testing::Matcher<absl::string_view> opaque_data) + : last_stream_id_(last_stream_id), + error_code_(error_code), + opaque_data_(opaque_data) {} + + bool MatchAndExplain(const nghttp2_frame* frame, + testing::MatchResultListener* listener) const override { + if (frame->hd.type != NGHTTP2_GOAWAY) { + *listener << "; expected GOAWAY frame, saw frame of type " + << static_cast<int>(frame->hd.type); + return false; + } + bool matched = true; + if (!last_stream_id_.MatchAndExplain(frame->goaway.last_stream_id, + listener)) { + matched = false; + } + if (!error_code_.MatchAndExplain(frame->goaway.error_code, listener)) { + matched = false; + } + auto opaque_data = + ToStringView(frame->goaway.opaque_data, frame->goaway.opaque_data_len); + if (!opaque_data_.MatchAndExplain(opaque_data, listener)) { + matched = false; + } + return matched; + } + + void DescribeTo(std::ostream* os) const override { + *os << "contains a GOAWAY frame, "; + last_stream_id_.DescribeTo(os); + error_code_.DescribeTo(os); + opaque_data_.DescribeTo(os); + } + + void DescribeNegationTo(std::ostream* os) const override { + *os << "does not contain a GOAWAY frame, "; + last_stream_id_.DescribeNegationTo(os); + error_code_.DescribeNegationTo(os); + opaque_data_.DescribeNegationTo(os); + } + + private: + const testing::Matcher<uint32_t> last_stream_id_; + const testing::Matcher<uint32_t> error_code_; + const testing::Matcher<absl::string_view> opaque_data_; +}; + +class WindowUpdateMatcher + : public testing::MatcherInterface<const nghttp2_frame*> { + public: + WindowUpdateMatcher(const testing::Matcher<uint32_t> delta) : delta_(delta) {} + + bool MatchAndExplain(const nghttp2_frame* frame, + testing::MatchResultListener* listener) const override { + if (frame->hd.type != NGHTTP2_WINDOW_UPDATE) { + *listener << "; expected WINDOW_UPDATE frame, saw frame of type " + << static_cast<int>(frame->hd.type); + return false; + } + return delta_.MatchAndExplain(frame->window_update.window_size_increment, + listener); + } + + void DescribeTo(std::ostream* os) const override { + *os << "contains a WINDOW_UPDATE frame, "; + delta_.DescribeTo(os); + } + + void DescribeNegationTo(std::ostream* os) const override { + *os << "does not contain a WINDOW_UPDATE frame, "; + delta_.DescribeNegationTo(os); + } + + private: + const testing::Matcher<uint32_t> delta_; +}; + +} // namespace + +testing::Matcher<const nghttp2_frame_hd*> HasFrameHeader( + uint32_t streamid, uint8_t type, const testing::Matcher<int> flags) { + return MakeMatcher(new PointerToFrameHeaderMatcher(streamid, type, flags)); +} + +testing::Matcher<const nghttp2_frame_hd&> HasFrameHeaderRef( + uint32_t streamid, uint8_t type, const testing::Matcher<int> flags) { + return MakeMatcher(new ReferenceToFrameHeaderMatcher(streamid, type, flags)); +} + +testing::Matcher<const nghttp2_frame*> IsData( + const testing::Matcher<uint32_t> stream_id, + const testing::Matcher<size_t> length, const testing::Matcher<int> flags) { + return MakeMatcher(new DataMatcher(stream_id, length, flags)); +} + +testing::Matcher<const nghttp2_frame*> IsHeaders( + const testing::Matcher<uint32_t> stream_id, + const testing::Matcher<int> flags, const testing::Matcher<int> category) { + return MakeMatcher(new HeadersMatcher(stream_id, flags, category)); +} + +testing::Matcher<const nghttp2_frame*> IsRstStream( + const testing::Matcher<uint32_t> stream_id, + const testing::Matcher<uint32_t> error_code) { + return MakeMatcher(new RstStreamMatcher(stream_id, error_code)); +} + +testing::Matcher<const nghttp2_frame*> IsSettings( + const testing::Matcher<std::vector<Http2Setting>> values) { + return MakeMatcher(new SettingsMatcher(values)); +} + +testing::Matcher<const nghttp2_frame*> IsPing( + const testing::Matcher<uint64_t> id) { + return MakeMatcher(new PingMatcher(id, false)); +} + +testing::Matcher<const nghttp2_frame*> IsPingAck( + const testing::Matcher<uint64_t> id) { + return MakeMatcher(new PingMatcher(id, true)); +} + +testing::Matcher<const nghttp2_frame*> IsGoAway( + const testing::Matcher<uint32_t> last_stream_id, + const testing::Matcher<uint32_t> error_code, + const testing::Matcher<absl::string_view> opaque_data) { + return MakeMatcher( + new GoAwayMatcher(last_stream_id, error_code, opaque_data)); +} + +testing::Matcher<const nghttp2_frame*> IsWindowUpdate( + const testing::Matcher<uint32_t> delta) { + return MakeMatcher(new WindowUpdateMatcher(delta)); +} + +} // namespace test +} // namespace adapter +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_test_utils.h b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_test_utils.h new file mode 100644 index 00000000000..9b772ffda63 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_test_utils.h @@ -0,0 +1,99 @@ +#ifndef QUICHE_HTTP2_ADAPTER_NGHTTP2_TEST_UTILS_H_ +#define QUICHE_HTTP2_ADAPTER_NGHTTP2_TEST_UTILS_H_ + +#include <vector> + +#include "absl/strings/string_view.h" +#include "http2/adapter/http2_protocol.h" +#include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h" +#include "common/platform/api/quiche_export.h" +#include "common/platform/api/quiche_test.h" + +namespace http2 { +namespace adapter { +namespace test { + +// A simple class that can easily be adapted to act as a nghttp2_data_source. +class QUICHE_NO_EXPORT TestDataSource { + public: + explicit TestDataSource(absl::string_view data) : data_(std::string(data)) {} + + absl::string_view ReadNext(size_t size) { + const size_t to_send = std::min(size, remaining_.size()); + auto ret = remaining_.substr(0, to_send); + remaining_.remove_prefix(to_send); + return ret; + } + + size_t SelectPayloadLength(size_t max_length) { + return std::min(max_length, remaining_.size()); + } + + nghttp2_data_provider MakeDataProvider() { + return nghttp2_data_provider{ + .source = {.ptr = this}, + .read_callback = [](nghttp2_session*, int32_t, uint8_t*, size_t length, + uint32_t* data_flags, nghttp2_data_source* source, + void*) -> ssize_t { + *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY; + auto* s = static_cast<TestDataSource*>(source->ptr); + if (!s->is_data_available()) { + return NGHTTP2_ERR_DEFERRED; + } + const ssize_t ret = s->SelectPayloadLength(length); + if (ret < static_cast<ssize_t>(length)) { + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + } + return ret; + }}; + } + + bool is_data_available() const { return is_data_available_; } + void set_is_data_available(bool value) { is_data_available_ = value; } + + private: + const std::string data_; + absl::string_view remaining_ = data_; + bool is_data_available_ = true; +}; + +// Matchers for nghttp2 data types. +testing::Matcher<const nghttp2_frame_hd*> HasFrameHeader( + uint32_t streamid, uint8_t type, const testing::Matcher<int> flags); +testing::Matcher<const nghttp2_frame_hd&> HasFrameHeaderRef( + uint32_t streamid, uint8_t type, const testing::Matcher<int> flags); + +testing::Matcher<const nghttp2_frame*> IsData( + const testing::Matcher<uint32_t> stream_id, + const testing::Matcher<size_t> length, const testing::Matcher<int> flags); + +testing::Matcher<const nghttp2_frame*> IsHeaders( + const testing::Matcher<uint32_t> stream_id, + const testing::Matcher<int> flags, const testing::Matcher<int> category); + +testing::Matcher<const nghttp2_frame*> IsRstStream( + const testing::Matcher<uint32_t> stream_id, + const testing::Matcher<uint32_t> error_code); + +testing::Matcher<const nghttp2_frame*> IsSettings( + const testing::Matcher<std::vector<Http2Setting>> values); + +testing::Matcher<const nghttp2_frame*> IsPing( + const testing::Matcher<uint64_t> id); + +testing::Matcher<const nghttp2_frame*> IsPingAck( + const testing::Matcher<uint64_t> id); + +testing::Matcher<const nghttp2_frame*> IsGoAway( + const testing::Matcher<uint32_t> last_stream_id, + const testing::Matcher<uint32_t> error_code, + const testing::Matcher<absl::string_view> opaque_data); + +testing::Matcher<const nghttp2_frame*> IsWindowUpdate( + const testing::Matcher<uint32_t> delta); + +} // namespace test +} // namespace adapter +} // namespace http2 + +#endif // QUICHE_HTTP2_ADAPTER_NGHTTP2_TEST_UTILS_H_ diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_util.cc b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_util.cc index 8d23a55278a..efb811fc35c 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_util.cc +++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_util.cc @@ -2,10 +2,13 @@ #include <cstdint> +#include "absl/strings/str_join.h" #include "absl/strings/string_view.h" +#include "absl/types/span.h" #include "http2/adapter/http2_protocol.h" #include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h" #include "common/platform/api/quiche_logging.h" +#include "common/quiche_endian.h" namespace http2 { namespace adapter { @@ -28,11 +31,11 @@ void DeleteSession(nghttp2_session* session) { nghttp2_session_callbacks_unique_ptr MakeCallbacksPtr( nghttp2_session_callbacks* callbacks) { - return nghttp2_session_callbacks_unique_ptr(callbacks, DeleteCallbacks); + return nghttp2_session_callbacks_unique_ptr(callbacks, &DeleteCallbacks); } nghttp2_session_unique_ptr MakeSessionPtr(nghttp2_session* session) { - return nghttp2_session_unique_ptr(session, DeleteSession); + return nghttp2_session_unique_ptr(session, &DeleteSession); } uint8_t* ToUint8Ptr(char* str) { return reinterpret_cast<uint8_t*>(str); } @@ -56,7 +59,8 @@ absl::string_view ToStringView(const uint8_t* pointer, size_t length) { std::vector<nghttp2_nv> GetNghttp2Nvs(absl::Span<const Header> headers) { const int num_headers = headers.size(); - auto nghttp2_nvs = std::vector<nghttp2_nv>(num_headers); + std::vector<nghttp2_nv> nghttp2_nvs; + nghttp2_nvs.reserve(num_headers); for (int i = 0; i < num_headers; ++i) { nghttp2_nv header; uint8_t flags = NGHTTP2_NV_FLAG_NONE; @@ -85,7 +89,8 @@ std::vector<nghttp2_nv> GetResponseNghttp2Nvs( absl::string_view response_code) { // Allocate enough for all headers and also the :status pseudoheader. const int num_headers = headers.size(); - auto nghttp2_nvs = std::vector<nghttp2_nv>(num_headers + 1); + std::vector<nghttp2_nv> nghttp2_nvs; + nghttp2_nvs.reserve(num_headers + 1); // Add the :status pseudoheader first. nghttp2_nv status; @@ -97,7 +102,7 @@ std::vector<nghttp2_nv> GetResponseNghttp2Nvs( nghttp2_nvs.push_back(std::move(status)); // Add the remaining headers. - for (const auto header_pair : headers) { + for (const auto& header_pair : headers) { nghttp2_nv header; header.name = ToUint8Ptr(header_pair.first.data()); header.namelen = header_pair.first.size(); @@ -117,5 +122,148 @@ Http2ErrorCode ToHttp2ErrorCode(uint32_t wire_error_code) { return static_cast<Http2ErrorCode>(wire_error_code); } +class Nghttp2DataFrameSource : public DataFrameSource { + public: + Nghttp2DataFrameSource(nghttp2_data_provider provider, + nghttp2_send_data_callback send_data, + void* user_data) + : provider_(std::move(provider)), + send_data_(std::move(send_data)), + user_data_(user_data) {} + + std::pair<ssize_t, bool> SelectPayloadLength(size_t max_length) override { + const int32_t stream_id = 0; + uint32_t data_flags = 0; + QUICHE_LOG(INFO) << "Invoking read callback"; + ssize_t result = provider_.read_callback( + nullptr /* session */, stream_id, nullptr /* buf */, max_length, + &data_flags, &provider_.source, nullptr /* user_data */); + if (result == NGHTTP2_ERR_DEFERRED) { + return {kBlocked, false}; + } else if (result < 0) { + return {kError, false}; + } else if ((data_flags & NGHTTP2_DATA_FLAG_NO_COPY) == 0) { + QUICHE_LOG(ERROR) << "Source did not use the zero-copy API!"; + return {kError, false}; + } else { + if (data_flags & NGHTTP2_DATA_FLAG_NO_END_STREAM) { + send_fin_ = false; + } + const bool eof = data_flags & NGHTTP2_DATA_FLAG_EOF; + return {result, eof}; + } + } + + bool Send(absl::string_view frame_header, size_t payload_length) override { + nghttp2_frame frame; + frame.hd.type = 0; + frame.hd.length = payload_length; + frame.hd.flags = 0; + frame.hd.stream_id = 0; + frame.data.padlen = 0; + const int result = send_data_( + nullptr /* session */, &frame, ToUint8Ptr(frame_header.data()), + payload_length, &provider_.source, user_data_); + QUICHE_LOG_IF(ERROR, result < 0 && result != NGHTTP2_ERR_WOULDBLOCK) + << "Unexpected error code from send: " << result; + return result == 0; + } + + bool send_fin() const override { return send_fin_; } + + private: + nghttp2_data_provider provider_; + nghttp2_send_data_callback send_data_; + void* user_data_; + bool send_fin_ = true; +}; + +std::unique_ptr<DataFrameSource> MakeZeroCopyDataFrameSource( + nghttp2_data_provider provider, + void* user_data, + nghttp2_send_data_callback send_data) { + return absl::make_unique<Nghttp2DataFrameSource>( + std::move(provider), std::move(send_data), user_data); +} + +absl::string_view ErrorString(uint32_t error_code) { + return Http2ErrorCodeToString(static_cast<Http2ErrorCode>(error_code)); +} + +size_t PaddingLength(uint8_t flags, size_t padlen) { + return (flags & 0x8 ? 1 : 0) + padlen; +} + +struct NvFormatter { + void operator()(std::string* out, const nghttp2_nv& nv) { + absl::StrAppend(out, ToStringView(nv.name, nv.namelen), ": ", + ToStringView(nv.value, nv.valuelen)); + } +}; + +std::string NvsAsString(nghttp2_nv* nva, size_t nvlen) { + return absl::StrJoin(absl::MakeConstSpan(nva, nvlen), ", ", NvFormatter()); +} + +#define HTTP2_FRAME_SEND_LOG QUICHE_VLOG(1) + +void LogBeforeSend(const nghttp2_frame& frame) { + switch (static_cast<FrameType>(frame.hd.type)) { + case FrameType::DATA: + HTTP2_FRAME_SEND_LOG << "Sending DATA on stream " << frame.hd.stream_id + << " with length " + << frame.hd.length - PaddingLength(frame.hd.flags, + frame.data.padlen) + << " and padding " + << PaddingLength(frame.hd.flags, frame.data.padlen); + break; + case FrameType::HEADERS: + HTTP2_FRAME_SEND_LOG << "Sending HEADERS on stream " << frame.hd.stream_id + << " with headers [" + << NvsAsString(frame.headers.nva, + frame.headers.nvlen) + << "]"; + break; + case FrameType::PRIORITY: + HTTP2_FRAME_SEND_LOG << "Sending PRIORITY"; + break; + case FrameType::RST_STREAM: + HTTP2_FRAME_SEND_LOG << "Sending RST_STREAM on stream " + << frame.hd.stream_id << " with error code " + << ErrorString(frame.rst_stream.error_code); + break; + case FrameType::SETTINGS: + HTTP2_FRAME_SEND_LOG << "Sending SETTINGS with " << frame.settings.niv + << " entries, is_ack: " << (frame.hd.flags & 0x01); + break; + case FrameType::PUSH_PROMISE: + HTTP2_FRAME_SEND_LOG << "Sending PUSH_PROMISE"; + break; + case FrameType::PING: { + Http2PingId ping_id; + std::memcpy(&ping_id, frame.ping.opaque_data, sizeof(Http2PingId)); + HTTP2_FRAME_SEND_LOG << "Sending PING with unique_id " + << quiche::QuicheEndian::NetToHost64(ping_id) + << ", is_ack: " << (frame.hd.flags & 0x01); + break; + } + case FrameType::GOAWAY: + HTTP2_FRAME_SEND_LOG << "Sending GOAWAY with last_stream: " + << frame.goaway.last_stream_id << " and error " + << ErrorString(frame.goaway.error_code); + break; + case FrameType::WINDOW_UPDATE: + HTTP2_FRAME_SEND_LOG << "Sending WINDOW_UPDATE on stream " + << frame.hd.stream_id << " with update delta " + << frame.window_update.window_size_increment; + break; + case FrameType::CONTINUATION: + HTTP2_FRAME_SEND_LOG << "Sending CONTINUATION, which is unexpected"; + break; + } +} + +#undef HTTP2_FRAME_SEND_LOG + } // namespace adapter } // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_util.h b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_util.h index e2c3e7a37c7..78d4702a3c5 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_util.h +++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_util.h @@ -8,6 +8,7 @@ #include "absl/strings/string_view.h" #include "absl/types/span.h" +#include "http2/adapter/data_source.h" #include "http2/adapter/http2_protocol.h" #include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h" #include "spdy/core/spdy_header_block.h" @@ -20,8 +21,8 @@ inline constexpr int kStreamCallbackFailureStatus = NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; inline constexpr int kCancelStatus = NGHTTP2_ERR_CANCEL; -using CallbacksDeleter = void (&)(nghttp2_session_callbacks*); -using SessionDeleter = void (&)(nghttp2_session*); +using CallbacksDeleter = void (*)(nghttp2_session_callbacks*); +using SessionDeleter = void (*)(nghttp2_session*); using nghttp2_session_callbacks_unique_ptr = std::unique_ptr<nghttp2_session_callbacks, CallbacksDeleter>; @@ -55,6 +56,16 @@ std::vector<nghttp2_nv> GetResponseNghttp2Nvs( // based on the RFC 7540 Section 7 suggestion. Http2ErrorCode ToHttp2ErrorCode(uint32_t wire_error_code); +// Transforms a nghttp2_data_provider into a DataFrameSource. Assumes that +// |provider| uses the zero-copy nghttp2_data_source_read_callback API. Unsafe +// otherwise. +std::unique_ptr<DataFrameSource> MakeZeroCopyDataFrameSource( + nghttp2_data_provider provider, + void* user_data, + nghttp2_send_data_callback send_data); + +void LogBeforeSend(const nghttp2_frame& frame); + } // namespace adapter } // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_util_test.cc b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_util_test.cc new file mode 100644 index 00000000000..1ec00ba32ec --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/adapter/nghttp2_util_test.cc @@ -0,0 +1,109 @@ +#include "http2/adapter/nghttp2_util.h" + +#include "http2/adapter/nghttp2_test_utils.h" +#include "http2/adapter/test_utils.h" +#include "common/platform/api/quiche_test.h" + +namespace http2 { +namespace adapter { +namespace test { +namespace { + +// This send callback assumes |source|'s pointer is a TestDataSource, and +// |user_data| is a std::string. +int FakeSendCallback(nghttp2_session*, nghttp2_frame* /*frame*/, + const uint8_t* framehd, size_t length, + nghttp2_data_source* source, void* user_data) { + auto* dest = static_cast<std::string*>(user_data); + // Appends the frame header to the string. + absl::StrAppend(dest, ToStringView(framehd, 9)); + auto* test_source = static_cast<TestDataSource*>(source->ptr); + absl::string_view payload = test_source->ReadNext(length); + // Appends the frame payload to the string. + absl::StrAppend(dest, payload); + return 0; +} + +TEST(MakeZeroCopyDataFrameSource, EmptyPayload) { + std::string result; + + const absl::string_view kEmptyBody = ""; + TestDataSource body1{kEmptyBody}; + // The TestDataSource is wrapped in the nghttp2_data_provider data type. + nghttp2_data_provider provider = body1.MakeDataProvider(); + + // This call transforms it back into a DataFrameSource, which is compatible + // with the Http2Adapter API. + std::unique_ptr<DataFrameSource> frame_source = + MakeZeroCopyDataFrameSource(provider, &result, FakeSendCallback); + auto [length, eof] = frame_source->SelectPayloadLength(100); + EXPECT_EQ(length, 0); + EXPECT_TRUE(eof); + frame_source->Send("ninebytes", 0); + EXPECT_EQ(result, "ninebytes"); +} + +TEST(MakeZeroCopyDataFrameSource, ShortPayload) { + std::string result; + + const absl::string_view kShortBody = + "<html><head><title>Example Page!</title></head>" + "<body><div><span><table><tr><th><blink>Wow!!" + "</blink></th></tr></table></span></div></body>" + "</html>"; + TestDataSource body1{kShortBody}; + // The TestDataSource is wrapped in the nghttp2_data_provider data type. + nghttp2_data_provider provider = body1.MakeDataProvider(); + + // This call transforms it back into a DataFrameSource, which is compatible + // with the Http2Adapter API. + std::unique_ptr<DataFrameSource> frame_source = + MakeZeroCopyDataFrameSource(provider, &result, FakeSendCallback); + auto [length, eof] = frame_source->SelectPayloadLength(200); + EXPECT_EQ(length, kShortBody.size()); + EXPECT_TRUE(eof); + frame_source->Send("ninebytes", length); + EXPECT_EQ(result, absl::StrCat("ninebytes", kShortBody)); +} + +TEST(MakeZeroCopyDataFrameSource, MultiFramePayload) { + std::string result; + + const absl::string_view kShortBody = + "<html><head><title>Example Page!</title></head>" + "<body><div><span><table><tr><th><blink>Wow!!" + "</blink></th></tr></table></span></div></body>" + "</html>"; + TestDataSource body1{kShortBody}; + // The TestDataSource is wrapped in the nghttp2_data_provider data type. + nghttp2_data_provider provider = body1.MakeDataProvider(); + + // This call transforms it back into a DataFrameSource, which is compatible + // with the Http2Adapter API. + std::unique_ptr<DataFrameSource> frame_source = + MakeZeroCopyDataFrameSource(provider, &result, FakeSendCallback); + auto ret = frame_source->SelectPayloadLength(50); + EXPECT_EQ(ret.first, 50); + EXPECT_FALSE(ret.second); + frame_source->Send("ninebyte1", ret.first); + + ret = frame_source->SelectPayloadLength(50); + EXPECT_EQ(ret.first, 50); + EXPECT_FALSE(ret.second); + frame_source->Send("ninebyte2", ret.first); + + ret = frame_source->SelectPayloadLength(50); + EXPECT_EQ(ret.first, 44); + EXPECT_TRUE(ret.second); + frame_source->Send("ninebyte3", ret.first); + + EXPECT_EQ(result, + "ninebyte1<html><head><title>Example Page!</title></head><bo" + "ninebyte2dy><div><span><table><tr><th><blink>Wow!!</blink><" + "ninebyte3/th></tr></table></span></div></body></html>"); +} + +} // namespace +} // namespace test +} // namespace adapter +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_adapter.cc b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_adapter.cc index 9f287facb9f..27f011ec62f 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_adapter.cc +++ b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_adapter.cc @@ -30,6 +30,10 @@ std::unique_ptr<OgHttp2Adapter> OgHttp2Adapter::Create( OgHttp2Adapter::~OgHttp2Adapter() {} +bool OgHttp2Adapter::IsServerSession() const { + return session_->IsServerSession(); +} + ssize_t OgHttp2Adapter::ProcessBytes(absl::string_view bytes) { return session_->ProcessBytes(bytes); } @@ -54,6 +58,10 @@ void OgHttp2Adapter::SubmitPing(Http2PingId ping_id) { session_->EnqueueFrame(absl::make_unique<SpdyPingIR>(ping_id)); } +void OgHttp2Adapter::SubmitShutdownNotice() { + session_->StartGracefulShutdown(); +} + void OgHttp2Adapter::SubmitGoAway(Http2StreamId last_accepted_stream_id, Http2ErrorCode error_code, absl::string_view opaque_data) { @@ -67,18 +75,45 @@ void OgHttp2Adapter::SubmitWindowUpdate(Http2StreamId stream_id, absl::make_unique<SpdyWindowUpdateIR>(stream_id, window_increment)); } -void OgHttp2Adapter::SubmitMetadata(Http2StreamId stream_id, bool fin) { - QUICHE_BUG(oghttp2_submit_metadata) << "Not implemented"; +void OgHttp2Adapter::SubmitMetadata(Http2StreamId stream_id, + std::unique_ptr<MetadataSource> source) { + session_->SubmitMetadata(stream_id, std::move(source)); } -std::string OgHttp2Adapter::GetBytesToWrite(absl::optional<size_t> max_bytes) { - return session_->GetBytesToWrite(max_bytes); -} +int OgHttp2Adapter::Send() { return session_->Send(); } -int OgHttp2Adapter::GetPeerConnectionWindow() const { +int OgHttp2Adapter::GetSendWindowSize() const { return session_->GetRemoteWindowSize(); } +int OgHttp2Adapter::GetStreamSendWindowSize(Http2StreamId stream_id) const { + return session_->GetStreamSendWindowSize(stream_id); +} + +int OgHttp2Adapter::GetStreamReceiveWindowLimit(Http2StreamId stream_id) const { + return session_->GetStreamReceiveWindowLimit(stream_id); +} + +int OgHttp2Adapter::GetStreamReceiveWindowSize(Http2StreamId stream_id) const { + return session_->GetStreamReceiveWindowSize(stream_id); +} + +int OgHttp2Adapter::GetReceiveWindowSize() const { + return session_->GetReceiveWindowSize(); +} + +int OgHttp2Adapter::GetHpackEncoderDynamicTableSize() const { + return session_->GetHpackEncoderDynamicTableSize(); +} + +int OgHttp2Adapter::GetHpackDecoderDynamicTableSize() const { + return session_->GetHpackDecoderDynamicTableSize(); +} + +Http2StreamId OgHttp2Adapter::GetHighestReceivedStreamId() const { + return session_->GetHighestReceivedStreamId(); +} + void OgHttp2Adapter::MarkDataConsumedForStream(Http2StreamId stream_id, size_t num_bytes) { session_->Consume(stream_id, num_bytes); @@ -90,6 +125,36 @@ void OgHttp2Adapter::SubmitRst(Http2StreamId stream_id, stream_id, TranslateErrorCode(error_code))); } +int32_t OgHttp2Adapter::SubmitRequest( + absl::Span<const Header> headers, + std::unique_ptr<DataFrameSource> data_source, void* user_data) { + return session_->SubmitRequest(headers, std::move(data_source), user_data); +} + +int OgHttp2Adapter::SubmitResponse( + Http2StreamId stream_id, absl::Span<const Header> headers, + std::unique_ptr<DataFrameSource> data_source) { + return session_->SubmitResponse(stream_id, headers, std::move(data_source)); +} + +int OgHttp2Adapter::SubmitTrailer(Http2StreamId stream_id, + absl::Span<const Header> trailers) { + return session_->SubmitTrailer(stream_id, trailers); +} + +void OgHttp2Adapter::SetStreamUserData(Http2StreamId stream_id, + void* user_data) { + session_->SetStreamUserData(stream_id, user_data); +} + +void* OgHttp2Adapter::GetStreamUserData(Http2StreamId stream_id) { + return session_->GetStreamUserData(stream_id); +} + +bool OgHttp2Adapter::ResumeStream(Http2StreamId stream_id) { + return session_->ResumeStream(stream_id); +} + const Http2Session& OgHttp2Adapter::session() const { return *session_; } diff --git a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_adapter.h b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_adapter.h index 572ba04e9fc..9c7bad3c575 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_adapter.h +++ b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_adapter.h @@ -6,11 +6,12 @@ #include "http2/adapter/http2_adapter.h" #include "http2/adapter/http2_session.h" #include "http2/adapter/oghttp2_session.h" +#include "common/platform/api/quiche_export.h" namespace http2 { namespace adapter { -class OgHttp2Adapter : public Http2Adapter { +class QUICHE_EXPORT_PRIVATE OgHttp2Adapter : public Http2Adapter { public: using Options = OgHttp2Session::Options; static std::unique_ptr<OgHttp2Adapter> Create(Http2VisitorInterface& visitor, @@ -19,6 +20,7 @@ class OgHttp2Adapter : public Http2Adapter { ~OgHttp2Adapter(); // From Http2Adapter. + bool IsServerSession() const override; ssize_t ProcessBytes(absl::string_view bytes) override; void SubmitSettings(absl::Span<const Http2Setting> settings) override; void SubmitPriorityForStream(Http2StreamId stream_id, @@ -26,17 +28,38 @@ class OgHttp2Adapter : public Http2Adapter { int weight, bool exclusive) override; void SubmitPing(Http2PingId ping_id) override; + void SubmitShutdownNotice() override; void SubmitGoAway(Http2StreamId last_accepted_stream_id, Http2ErrorCode error_code, absl::string_view opaque_data) override; void SubmitWindowUpdate(Http2StreamId stream_id, int window_increment) override; - void SubmitMetadata(Http2StreamId stream_id, bool fin) override; - std::string GetBytesToWrite(absl::optional<size_t> max_bytes) override; - int GetPeerConnectionWindow() const override; + void SubmitMetadata(Http2StreamId stream_id, + std::unique_ptr<MetadataSource> source) override; + int Send() override; + int GetSendWindowSize() const override; + int GetStreamSendWindowSize(Http2StreamId stream_id) const override; + int GetStreamReceiveWindowLimit(Http2StreamId stream_id) const override; + int GetStreamReceiveWindowSize(Http2StreamId stream_id) const override; + int GetReceiveWindowSize() const override; + int GetHpackEncoderDynamicTableSize() const override; + int GetHpackDecoderDynamicTableSize() const override; + Http2StreamId GetHighestReceivedStreamId() const override; void MarkDataConsumedForStream(Http2StreamId stream_id, size_t num_bytes) override; void SubmitRst(Http2StreamId stream_id, Http2ErrorCode error_code) override; + int32_t SubmitRequest(absl::Span<const Header> headers, + std::unique_ptr<DataFrameSource> data_source, + void* user_data) override; + int SubmitResponse(Http2StreamId stream_id, absl::Span<const Header> headers, + std::unique_ptr<DataFrameSource> data_source) override; + + int SubmitTrailer(Http2StreamId stream_id, + absl::Span<const Header> trailers) override; + + void SetStreamUserData(Http2StreamId stream_id, void* user_data) override; + void* GetStreamUserData(Http2StreamId stream_id) override; + bool ResumeStream(Http2StreamId stream_id) override; const Http2Session& session() const; diff --git a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_adapter_test.cc b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_adapter_test.cc index 4c849f6fd6d..03b45df6aa9 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_adapter_test.cc +++ b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_adapter_test.cc @@ -1,6 +1,8 @@ #include "http2/adapter/oghttp2_adapter.h" +#include "absl/strings/str_join.h" #include "http2/adapter/mock_http2_visitor.h" +#include "http2/adapter/oghttp2_util.h" #include "http2/adapter/test_frame_sequence.h" #include "http2/adapter/test_utils.h" #include "common/platform/api/quiche_test.h" @@ -11,6 +13,23 @@ namespace adapter { namespace test { namespace { +using testing::_; + +enum FrameType { + DATA, + HEADERS, + PRIORITY, + RST_STREAM, + SETTINGS, + PUSH_PROMISE, + PING, + GOAWAY, + WINDOW_UPDATE, + CONTINUATION, +}; + +using spdy::SpdyFrameType; + class OgHttp2AdapterTest : public testing::Test { protected: void SetUp() override { @@ -18,10 +37,14 @@ class OgHttp2AdapterTest : public testing::Test { adapter_ = OgHttp2Adapter::Create(http2_visitor_, options); } - testing::StrictMock<MockHttp2Visitor> http2_visitor_; + DataSavingVisitor http2_visitor_; std::unique_ptr<OgHttp2Adapter> adapter_; }; +TEST_F(OgHttp2AdapterTest, IsServerSession) { + EXPECT_TRUE(adapter_->IsServerSession()); +} + TEST_F(OgHttp2AdapterTest, ProcessBytes) { testing::InSequence seq; EXPECT_CALL(http2_visitor_, OnFrameHeader(0, 0, 4, 0)); @@ -33,13 +56,527 @@ TEST_F(OgHttp2AdapterTest, ProcessBytes) { TestFrameSequence().ClientPreface().Ping(17).Serialize()); } +TEST(OgHttp2AdapterClientTest, ClientHandlesTrailers) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + testing::InSequence s; + + const std::vector<const Header> headers1 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + + const char* kSentinel1 = "arbitrary pointer 1"; + const int32_t stream_id1 = + adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1)); + ASSERT_GT(stream_id1, 0); + QUICHE_LOG(INFO) << "Created stream: " << stream_id1; + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); + + int result = adapter->Send(); + EXPECT_EQ(0, result); + absl::string_view data = visitor.data(); + EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); + data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); + EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::SETTINGS, + spdy::SpdyFrameType::HEADERS})); + visitor.Clear(); + + const std::string stream_frames = + TestFrameSequence() + .ServerPreface() + .Headers(1, + {{":status", "200"}, + {"server", "my-fake-server"}, + {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}}, + /*fin=*/false) + .Data(1, "This is the response body.") + .Headers(1, {{"final-status", "A-OK"}}, + /*fin=*/true) + .Serialize(); + + // Server preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "200")); + EXPECT_CALL(visitor, OnHeaderForStream(1, "server", "my-fake-server")); + EXPECT_CALL(visitor, + OnHeaderForStream(1, "date", "Tue, 6 Apr 2021 12:54:01 GMT")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnFrameHeader(1, 26, DATA, 0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 26)); + EXPECT_CALL(visitor, OnDataForStream(1, "This is the response body.")); + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, "final-status", "A-OK")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnEndStream(1)); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR)); + + const size_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), stream_result); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + + EXPECT_TRUE(adapter->session().want_write()); + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); +} + +TEST(OgHttp2AdapterClientTest, ClientHandlesMetadata) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + testing::InSequence s; + + const std::vector<const Header> headers1 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + + const char* kSentinel1 = "arbitrary pointer 1"; + const int32_t stream_id1 = + adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1)); + ASSERT_GT(stream_id1, 0); + QUICHE_LOG(INFO) << "Created stream: " << stream_id1; + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); + + int result = adapter->Send(); + EXPECT_EQ(0, result); + absl::string_view data = visitor.data(); + EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); + data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); + EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::SETTINGS, + spdy::SpdyFrameType::HEADERS})); + visitor.Clear(); + + const std::string stream_frames = + TestFrameSequence() + .ServerPreface() + .Metadata(0, "Example connection metadata") + .Headers(1, + {{":status", "200"}, + {"server", "my-fake-server"}, + {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}}, + /*fin=*/false) + .Metadata(1, "Example stream metadata") + .Data(1, "This is the response body.", true) + .Serialize(); + + // Server preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + EXPECT_CALL(visitor, OnFrameHeader(0, _, kMetadataFrameType, 4)); + EXPECT_CALL(visitor, OnBeginMetadataForStream(0, _)); + EXPECT_CALL(visitor, OnMetadataForStream(0, _)); + EXPECT_CALL(visitor, OnMetadataEndForStream(0)); + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "200")); + EXPECT_CALL(visitor, OnHeaderForStream(1, "server", "my-fake-server")); + EXPECT_CALL(visitor, + OnHeaderForStream(1, "date", "Tue, 6 Apr 2021 12:54:01 GMT")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnFrameHeader(1, _, kMetadataFrameType, 4)); + EXPECT_CALL(visitor, OnBeginMetadataForStream(1, _)); + EXPECT_CALL(visitor, OnMetadataForStream(1, _)); + EXPECT_CALL(visitor, OnMetadataEndForStream(1)); + EXPECT_CALL(visitor, OnFrameHeader(1, 26, DATA, 1)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 26)); + EXPECT_CALL(visitor, OnDataForStream(1, "This is the response body.")); + EXPECT_CALL(visitor, OnEndStream(1)); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR)); + + const size_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), stream_result); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + + EXPECT_TRUE(adapter->session().want_write()); + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); +} + +TEST(OgHttp2AdapterClientTest, ClientRstStreamWhileHandlingHeaders) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + testing::InSequence s; + + const std::vector<const Header> headers1 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + + const char* kSentinel1 = "arbitrary pointer 1"; + const int32_t stream_id1 = + adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1)); + ASSERT_GT(stream_id1, 0); + QUICHE_LOG(INFO) << "Created stream: " << stream_id1; + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); + + int result = adapter->Send(); + EXPECT_EQ(0, result); + absl::string_view data = visitor.data(); + EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); + data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); + EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::SETTINGS, + spdy::SpdyFrameType::HEADERS})); + visitor.Clear(); + + const std::string stream_frames = + TestFrameSequence() + .ServerPreface() + .Headers(1, + {{":status", "200"}, + {"server", "my-fake-server"}, + {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}}, + /*fin=*/false) + .Data(1, "This is the response body.") + .Serialize(); + + // Server preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "200")); + EXPECT_CALL(visitor, OnHeaderForStream(1, "server", "my-fake-server")); + EXPECT_CALL(visitor, + OnHeaderForStream(1, "date", "Tue, 6 Apr 2021 12:54:01 GMT")) + .WillOnce(testing::DoAll( + testing::InvokeWithoutArgs([&adapter]() { + adapter->SubmitRst(1, Http2ErrorCode::REFUSED_STREAM); + }), + testing::Return(Http2VisitorInterface::HEADER_RST_STREAM))); + EXPECT_CALL(visitor, OnFrameHeader(1, _, DATA, 0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, _)); + EXPECT_CALL(visitor, OnDataForStream(1, "This is the response body.")); + + const size_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), stream_result); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, stream_id1, 4, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, stream_id1, 4, 0x0, + static_cast<int>(Http2ErrorCode::REFUSED_STREAM))); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR)); + + EXPECT_TRUE(adapter->session().want_write()); + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, + spdy::SpdyFrameType::RST_STREAM})); +} + +TEST(OgHttp2AdapterClientTest, ClientConnectionErrorWhileHandlingHeaders) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + testing::InSequence s; + + const std::vector<const Header> headers1 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + + const char* kSentinel1 = "arbitrary pointer 1"; + const int32_t stream_id1 = + adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1)); + ASSERT_GT(stream_id1, 0); + QUICHE_LOG(INFO) << "Created stream: " << stream_id1; + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); + + int result = adapter->Send(); + EXPECT_EQ(0, result); + absl::string_view data = visitor.data(); + EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); + data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); + EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::SETTINGS, + spdy::SpdyFrameType::HEADERS})); + visitor.Clear(); + + const std::string stream_frames = + TestFrameSequence() + .ServerPreface() + .Headers(1, + {{":status", "200"}, + {"server", "my-fake-server"}, + {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}}, + /*fin=*/false) + .Data(1, "This is the response body.") + .Serialize(); + + // Server preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "200")); + EXPECT_CALL(visitor, OnHeaderForStream(1, "server", "my-fake-server")); + EXPECT_CALL(visitor, + OnHeaderForStream(1, "date", "Tue, 6 Apr 2021 12:54:01 GMT")) + .WillOnce( + testing::Return(Http2VisitorInterface::HEADER_CONNECTION_ERROR)); + EXPECT_CALL(visitor, OnConnectionError()); + // Note: OgHttp2Adapter continues processing bytes until the input is + // complete. + EXPECT_CALL(visitor, OnFrameHeader(1, _, DATA, 0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, _)); + EXPECT_CALL(visitor, OnDataForStream(1, "This is the response body.")); + + const size_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), stream_result); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + + EXPECT_TRUE(adapter->session().want_write()); + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); +} + +TEST(OgHttp2AdapterClientTest, ClientRejectsHeaders) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + testing::InSequence s; + + const std::vector<const Header> headers1 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + + const char* kSentinel1 = "arbitrary pointer 1"; + const int32_t stream_id1 = + adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1)); + ASSERT_GT(stream_id1, 0); + QUICHE_LOG(INFO) << "Created stream: " << stream_id1; + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); + + int result = adapter->Send(); + EXPECT_EQ(0, result); + absl::string_view data = visitor.data(); + EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); + data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); + EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::SETTINGS, + spdy::SpdyFrameType::HEADERS})); + visitor.Clear(); + + const std::string stream_frames = + TestFrameSequence() + .ServerPreface() + .Headers(1, + {{":status", "200"}, + {"server", "my-fake-server"}, + {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}}, + /*fin=*/false) + .Data(1, "This is the response body.") + .Serialize(); + + // Server preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)) + .WillOnce(testing::Return(false)); + // Rejecting headers leads to a connection error. + EXPECT_CALL(visitor, OnConnectionError()); + // Note: OgHttp2Adapter continues processing bytes until the input is + // complete. + EXPECT_CALL(visitor, OnFrameHeader(1, _, DATA, 0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, _)); + EXPECT_CALL(visitor, OnDataForStream(1, "This is the response body.")); + + const size_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_result, stream_frames.size()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + + EXPECT_TRUE(adapter->session().want_write()); + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); +} + +// TODO(birenroy): Validate headers and re-enable this test. The library should +// invoke OnErrorDebug() with an error message for the invalid header. The +// library should also invoke OnInvalidFrame() for the invalid HEADERS frame. +TEST(OgHttp2AdapterClientTest, DISABLED_ClientHandlesInvalidTrailers) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + testing::InSequence s; + + const std::vector<const Header> headers1 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + + const char* kSentinel1 = "arbitrary pointer 1"; + const int32_t stream_id1 = + adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1)); + ASSERT_GT(stream_id1, 0); + QUICHE_LOG(INFO) << "Created stream: " << stream_id1; + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); + + int result = adapter->Send(); + EXPECT_EQ(0, result); + absl::string_view data = visitor.data(); + EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); + data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); + EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::SETTINGS, + spdy::SpdyFrameType::HEADERS})); + visitor.Clear(); + + const std::string stream_frames = + TestFrameSequence() + .ServerPreface() + .Headers(1, + {{":status", "200"}, + {"server", "my-fake-server"}, + {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}}, + /*fin=*/false) + .Data(1, "This is the response body.") + .Headers(1, {{":bad-status", "9000"}}, + /*fin=*/true) + .Serialize(); + + // Server preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "200")); + EXPECT_CALL(visitor, OnHeaderForStream(1, "server", "my-fake-server")); + EXPECT_CALL(visitor, + OnHeaderForStream(1, "date", "Tue, 6 Apr 2021 12:54:01 GMT")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnFrameHeader(1, 26, DATA, 0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 26)); + EXPECT_CALL(visitor, OnDataForStream(1, "This is the response body.")); + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + + // Bad status trailer will cause a PROTOCOL_ERROR. The header is never + // delivered in an OnHeaderForStream callback. + + const size_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), stream_result); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, stream_id1, 4, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(RST_STREAM, stream_id1, 4, 0x0, 1)); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR)); + + EXPECT_TRUE(adapter->session().want_write()); + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, + spdy::SpdyFrameType::RST_STREAM})); +} + TEST_F(OgHttp2AdapterTest, SubmitMetadata) { - EXPECT_QUICHE_BUG(adapter_->SubmitMetadata(3, true), "Not implemented"); + auto source = absl::make_unique<TestMetadataSource>(ToHeaderBlock(ToHeaders( + {{"query-cost", "is too darn high"}, {"secret-sauce", "hollandaise"}}))); + adapter_->SubmitMetadata(1, std::move(source)); + EXPECT_TRUE(adapter_->session().want_write()); + + EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(http2_visitor_, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(kMetadataFrameType, 1, _, 0x4)); + EXPECT_CALL(http2_visitor_, OnFrameSent(kMetadataFrameType, 1, _, 0x4, 0)); + + int result = adapter_->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT( + http2_visitor_.data(), + EqualsFrames({spdy::SpdyFrameType::SETTINGS, + static_cast<spdy::SpdyFrameType>(kMetadataFrameType)})); + EXPECT_FALSE(adapter_->session().want_write()); +} + +TEST_F(OgHttp2AdapterTest, SubmitConnectionMetadata) { + auto source = absl::make_unique<TestMetadataSource>(ToHeaderBlock(ToHeaders( + {{"query-cost", "is too darn high"}, {"secret-sauce", "hollandaise"}}))); + adapter_->SubmitMetadata(0, std::move(source)); + EXPECT_TRUE(adapter_->session().want_write()); + + EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(http2_visitor_, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(kMetadataFrameType, 0, _, 0x4)); + EXPECT_CALL(http2_visitor_, OnFrameSent(kMetadataFrameType, 0, _, 0x4, 0)); + + int result = adapter_->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT( + http2_visitor_.data(), + EqualsFrames({spdy::SpdyFrameType::SETTINGS, + static_cast<spdy::SpdyFrameType>(kMetadataFrameType)})); + EXPECT_FALSE(adapter_->session().want_write()); } -TEST_F(OgHttp2AdapterTest, GetPeerConnectionWindow) { - const int peer_window = adapter_->GetPeerConnectionWindow(); - EXPECT_GT(peer_window, 0); +TEST_F(OgHttp2AdapterTest, GetSendWindowSize) { + const int peer_window = adapter_->GetSendWindowSize(); + EXPECT_EQ(peer_window, kInitialFlowControlWindowSize); } TEST_F(OgHttp2AdapterTest, MarkDataConsumedForStream) { @@ -62,12 +599,27 @@ TEST_F(OgHttp2AdapterTest, TestSerialize) { adapter_->SubmitWindowUpdate(3, 127); EXPECT_TRUE(adapter_->session().want_write()); + EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(http2_visitor_, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(PRIORITY, 3, _, 0x0)); + EXPECT_CALL(http2_visitor_, OnFrameSent(PRIORITY, 3, _, 0x0, 0)); + EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(RST_STREAM, 3, _, 0x0)); + EXPECT_CALL(http2_visitor_, OnFrameSent(RST_STREAM, 3, _, 0x0, 0x8)); + EXPECT_CALL(http2_visitor_, OnCloseStream(3, Http2ErrorCode::NO_ERROR)); + EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(PING, 0, _, 0x0)); + EXPECT_CALL(http2_visitor_, OnFrameSent(PING, 0, _, 0x0, 0)); + EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(GOAWAY, 0, _, 0x0)); + EXPECT_CALL(http2_visitor_, OnFrameSent(GOAWAY, 0, _, 0x0, 0)); + EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(WINDOW_UPDATE, 3, _, 0x0)); + EXPECT_CALL(http2_visitor_, OnFrameSent(WINDOW_UPDATE, 3, _, 0x0, 0)); + + int result = adapter_->Send(); + EXPECT_EQ(0, result); EXPECT_THAT( - adapter_->GetBytesToWrite(absl::nullopt), - EqualsFrames( - {spdy::SpdyFrameType::SETTINGS, spdy::SpdyFrameType::PRIORITY, - spdy::SpdyFrameType::RST_STREAM, spdy::SpdyFrameType::PING, - spdy::SpdyFrameType::GOAWAY, spdy::SpdyFrameType::WINDOW_UPDATE})); + http2_visitor_.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::PRIORITY, + SpdyFrameType::RST_STREAM, SpdyFrameType::PING, + SpdyFrameType::GOAWAY, SpdyFrameType::WINDOW_UPDATE})); EXPECT_FALSE(adapter_->session().want_write()); } @@ -80,14 +632,271 @@ TEST_F(OgHttp2AdapterTest, TestPartialSerialize) { adapter_->SubmitPing(42); EXPECT_TRUE(adapter_->session().want_write()); - const std::string first_part = adapter_->GetBytesToWrite(10); + http2_visitor_.set_send_limit(20); + EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(http2_visitor_, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + int result = adapter_->Send(); + EXPECT_EQ(0, result); + EXPECT_TRUE(adapter_->session().want_write()); + EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(GOAWAY, 0, _, 0x0)); + EXPECT_CALL(http2_visitor_, OnFrameSent(GOAWAY, 0, _, 0x0, 0)); + result = adapter_->Send(); + EXPECT_EQ(0, result); EXPECT_TRUE(adapter_->session().want_write()); - const std::string second_part = adapter_->GetBytesToWrite(absl::nullopt); + EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(PING, 0, _, 0x0)); + EXPECT_CALL(http2_visitor_, OnFrameSent(PING, 0, _, 0x0, 0)); + result = adapter_->Send(); + EXPECT_EQ(0, result); EXPECT_FALSE(adapter_->session().want_write()); - EXPECT_THAT( - absl::StrCat(first_part, second_part), - EqualsFrames({spdy::SpdyFrameType::SETTINGS, spdy::SpdyFrameType::GOAWAY, - spdy::SpdyFrameType::PING})); + EXPECT_THAT(http2_visitor_.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::GOAWAY, + SpdyFrameType::PING})); +} + +TEST(OgHttp2AdapterServerTest, ClientSendsContinuation) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + EXPECT_FALSE(adapter->session().want_write()); + + const std::string frames = TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}, + /*fin=*/true, + /*add_continuation=*/true) + .Serialize(); + testing::InSequence s; + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + // Stream 1 + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 1)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); + EXPECT_CALL(visitor, OnFrameHeader(1, _, CONTINUATION, 4)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnEndStream(1)); + + const size_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(frames.size(), result); +} + +TEST(OgHttp2AdapterServerTest, ClientSendsMetadataWithContinuation) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + EXPECT_FALSE(adapter->session().want_write()); + + const std::string frames = + TestFrameSequence() + .ClientPreface() + .Metadata(0, "Example connection metadata in multiple frames", true) + .Headers(1, + {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}, + /*fin=*/false, + /*add_continuation=*/true) + .Metadata(1, + "Some stream metadata that's also sent in multiple frames", + true) + .Serialize(); + testing::InSequence s; + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + // Metadata on stream 0 + EXPECT_CALL(visitor, OnFrameHeader(0, _, kMetadataFrameType, 0)); + EXPECT_CALL(visitor, OnBeginMetadataForStream(0, _)); + EXPECT_CALL(visitor, OnMetadataForStream(0, _)); + EXPECT_CALL(visitor, OnFrameHeader(0, _, kMetadataFrameType, 4)); + EXPECT_CALL(visitor, OnBeginMetadataForStream(0, _)); + EXPECT_CALL(visitor, OnMetadataForStream(0, _)); + EXPECT_CALL(visitor, OnMetadataEndForStream(0)); + + // Stream 1 + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 0)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); + EXPECT_CALL(visitor, OnFrameHeader(1, _, CONTINUATION, 4)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + // Metadata on stream 1 + EXPECT_CALL(visitor, OnFrameHeader(1, _, kMetadataFrameType, 0)); + EXPECT_CALL(visitor, OnBeginMetadataForStream(1, _)); + EXPECT_CALL(visitor, OnMetadataForStream(1, _)); + EXPECT_CALL(visitor, OnFrameHeader(1, _, kMetadataFrameType, 4)); + EXPECT_CALL(visitor, OnBeginMetadataForStream(1, _)); + EXPECT_CALL(visitor, OnMetadataForStream(1, _)); + EXPECT_CALL(visitor, OnMetadataEndForStream(1)); + + const size_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(frames.size(), result); + EXPECT_EQ(TestFrameSequence::MetadataBlockForPayload( + "Example connection metadata in multiple frames"), + absl::StrJoin(visitor.GetMetadata(0), "")); + EXPECT_EQ(TestFrameSequence::MetadataBlockForPayload( + "Some stream metadata that's also sent in multiple frames"), + absl::StrJoin(visitor.GetMetadata(1), "")); +} + +TEST(OgHttp2AdapterServerTest, ServerSendsInvalidTrailers) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + EXPECT_FALSE(adapter->session().want_write()); + + const std::string frames = TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}, + /*fin=*/true) + .Serialize(); + testing::InSequence s; + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + // Stream 1 + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnEndStream(1)); + + const size_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(frames.size(), result); + + const absl::string_view kBody = "This is an example response body."; + + // The body source must indicate that the end of the body is not the end of + // the stream. + auto body1 = absl::make_unique<TestDataFrameSource>(visitor, false); + body1->AppendPayload(kBody); + body1->EndData(); + int submit_result = adapter->SubmitResponse( + 1, ToHeaders({{":status", "200"}, {"x-comment", "Sure, sounds good."}}), + std::move(body1)); + EXPECT_EQ(submit_result, 0); + EXPECT_TRUE(adapter->session().want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0)); + EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0)); + + int send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), + EqualsFrames( + {spdy::SpdyFrameType::SETTINGS, spdy::SpdyFrameType::SETTINGS, + spdy::SpdyFrameType::HEADERS, spdy::SpdyFrameType::DATA})); + EXPECT_THAT(visitor.data(), testing::HasSubstr(kBody)); + visitor.Clear(); + EXPECT_FALSE(adapter->session().want_write()); + + // The body source has been exhausted by the call to Send() above. + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR)); + int trailer_result = + adapter->SubmitTrailer(1, ToHeaders({{":final-status", "a-ok"}})); + ASSERT_EQ(trailer_result, 0); + EXPECT_TRUE(adapter->session().want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x5, 0)); + + send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS})); +} + +TEST(OgHttp2AdapterServerTest, ServerErrorWhileHandlingHeaders) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + const std::string frames = TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":method", "POST"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}, + {"accept", "some bogus value!"}}, + /*fin=*/false) + .WindowUpdate(1, 2000) + .Data(1, "This is the request body.") + .WindowUpdate(0, 2000) + .Serialize(); + testing::InSequence s; + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "POST")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); + EXPECT_CALL(visitor, OnHeaderForStream(1, "accept", "some bogus value!")) + .WillOnce(testing::Return(Http2VisitorInterface::HEADER_RST_STREAM)); + EXPECT_CALL(visitor, OnFrameHeader(1, 4, WINDOW_UPDATE, 0)); + EXPECT_CALL(visitor, OnWindowUpdate(1, 2000)); + EXPECT_CALL(visitor, OnFrameHeader(1, _, DATA, 0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, _)); + EXPECT_CALL(visitor, OnDataForStream(1, "This is the request body.")); + EXPECT_CALL(visitor, OnFrameHeader(0, 4, WINDOW_UPDATE, 0)); + EXPECT_CALL(visitor, OnWindowUpdate(0, 2000)); + + const size_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(frames.size(), result); + + EXPECT_TRUE(adapter->session().want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, 4, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 1, 4, 0x0, + static_cast<int>(Http2ErrorCode::INTERNAL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR)); + + int send_result = adapter->Send(); + // Some bytes should have been serialized. + EXPECT_EQ(0, send_result); + // SETTINGS ack + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, + spdy::SpdyFrameType::SETTINGS, + spdy::SpdyFrameType::RST_STREAM})); } } // namespace diff --git a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_session.cc b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_session.cc index 2222fed4b6b..3a32b2e70ea 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_session.cc +++ b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_session.cc @@ -1,29 +1,150 @@ #include "http2/adapter/oghttp2_session.h" +#include <tuple> + #include "absl/strings/escaping.h" +#include "http2/adapter/oghttp2_util.h" namespace http2 { namespace adapter { +namespace { + +const size_t kMaxMetadataFrameSize = 16384; + +// TODO(birenroy): Consider incorporating spdy::FlagsSerializionVisitor here. +class FrameAttributeCollector : public spdy::SpdyFrameVisitor { + public: + FrameAttributeCollector() = default; + void VisitData(const spdy::SpdyDataIR& data) override { + frame_type_ = static_cast<uint8_t>(data.frame_type()); + stream_id_ = data.stream_id(); + length_ = + data.data_len() + (data.padded() ? 1 : 0) + data.padding_payload_len(); + flags_ = (data.fin() ? 0x1 : 0) | (data.padded() ? 0x8 : 0); + } + void VisitHeaders(const spdy::SpdyHeadersIR& headers) override { + frame_type_ = static_cast<uint8_t>(headers.frame_type()); + stream_id_ = headers.stream_id(); + length_ = headers.size() - spdy::kFrameHeaderSize; + flags_ = 0x4 | (headers.fin() ? 0x1 : 0) | (headers.padded() ? 0x8 : 0) | + (headers.has_priority() ? 0x20 : 0); + } + void VisitPriority(const spdy::SpdyPriorityIR& priority) override { + frame_type_ = static_cast<uint8_t>(priority.frame_type()); + frame_type_ = 2; + length_ = 5; + stream_id_ = priority.stream_id(); + } + void VisitRstStream(const spdy::SpdyRstStreamIR& rst_stream) override { + frame_type_ = static_cast<uint8_t>(rst_stream.frame_type()); + frame_type_ = 3; + length_ = 4; + stream_id_ = rst_stream.stream_id(); + error_code_ = rst_stream.error_code(); + } + void VisitSettings(const spdy::SpdySettingsIR& settings) override { + frame_type_ = static_cast<uint8_t>(settings.frame_type()); + frame_type_ = 4; + length_ = 6 * settings.values().size(); + flags_ = (settings.is_ack() ? 0x1 : 0); + } + void VisitPushPromise(const spdy::SpdyPushPromiseIR& push_promise) override { + frame_type_ = static_cast<uint8_t>(push_promise.frame_type()); + frame_type_ = 5; + length_ = push_promise.size() - spdy::kFrameHeaderSize; + stream_id_ = push_promise.stream_id(); + flags_ = (push_promise.padded() ? 0x8 : 0); + } + void VisitPing(const spdy::SpdyPingIR& ping) override { + frame_type_ = static_cast<uint8_t>(ping.frame_type()); + frame_type_ = 6; + length_ = 8; + flags_ = (ping.is_ack() ? 0x1 : 0); + } + void VisitGoAway(const spdy::SpdyGoAwayIR& goaway) override { + frame_type_ = static_cast<uint8_t>(goaway.frame_type()); + frame_type_ = 7; + length_ = goaway.size() - spdy::kFrameHeaderSize; + error_code_ = goaway.error_code(); + } + void VisitWindowUpdate( + const spdy::SpdyWindowUpdateIR& window_update) override { + frame_type_ = static_cast<uint8_t>(window_update.frame_type()); + frame_type_ = 8; + length_ = 4; + stream_id_ = window_update.stream_id(); + } + void VisitContinuation( + const spdy::SpdyContinuationIR& continuation) override { + frame_type_ = static_cast<uint8_t>(continuation.frame_type()); + stream_id_ = continuation.stream_id(); + flags_ = continuation.end_headers() ? 0x4 : 0; + length_ = continuation.size() - spdy::kFrameHeaderSize; + } + void VisitUnknown(const spdy::SpdyUnknownIR& unknown) override { + frame_type_ = static_cast<uint8_t>(unknown.frame_type()); + stream_id_ = unknown.stream_id(); + flags_ = unknown.flags(); + length_ = unknown.size() - spdy::kFrameHeaderSize; + } + void VisitAltSvc(const spdy::SpdyAltSvcIR& /*altsvc*/) override {} + void VisitPriorityUpdate( + const spdy::SpdyPriorityUpdateIR& /*priority_update*/) override {} + void VisitAcceptCh(const spdy::SpdyAcceptChIR& /*accept_ch*/) override {} + + uint32_t stream_id() { return stream_id_; } + uint32_t length() { return length_; } + uint32_t error_code() { return error_code_; } + uint8_t frame_type() { return frame_type_; } + uint8_t flags() { return flags_; } + + private: + uint32_t stream_id_ = 0; + uint32_t length_ = 0; + uint32_t error_code_ = 0; + uint8_t frame_type_ = 0; + uint8_t flags_ = 0; +}; + +} // namespace + void OgHttp2Session::PassthroughHeadersHandler::OnHeaderBlockStart() { - visitor_.OnBeginHeadersForStream(stream_id_); + const bool status = visitor_.OnBeginHeadersForStream(stream_id_); + if (!status) { + result_ = Http2VisitorInterface::HEADER_CONNECTION_ERROR; + } } void OgHttp2Session::PassthroughHeadersHandler::OnHeader( absl::string_view key, absl::string_view value) { - visitor_.OnHeaderForStream(stream_id_, key, value); + if (result_ == Http2VisitorInterface::HEADER_OK) { + result_ = visitor_.OnHeaderForStream(stream_id_, key, value); + } } void OgHttp2Session::PassthroughHeadersHandler::OnHeaderBlockEnd( size_t /* uncompressed_header_bytes */, size_t /* compressed_header_bytes */) { - visitor_.OnEndHeadersForStream(stream_id_); + if (result_ == Http2VisitorInterface::HEADER_OK) { + visitor_.OnEndHeadersForStream(stream_id_); + } else { + session_.OnHeaderStatus(stream_id_, result_); + } } OgHttp2Session::OgHttp2Session(Http2VisitorInterface& visitor, Options options) - : visitor_(visitor), headers_handler_(visitor), options_(options) { + : visitor_(visitor), + headers_handler_(*this, visitor), + connection_window_manager_(kInitialFlowControlWindowSize, + [this](size_t window_update_delta) { + SendWindowUpdate(kConnectionStreamId, + window_update_delta); + }), + options_(options) { decoder_.set_visitor(this); + decoder_.set_extension_visitor(this); if (options_.perspective == Perspective::kServer) { remaining_preface_ = {spdy::kHttp2ConnectionHeaderPrefix, spdy::kHttp2ConnectionHeaderPrefixSize}; @@ -32,6 +153,70 @@ OgHttp2Session::OgHttp2Session(Http2VisitorInterface& visitor, Options options) OgHttp2Session::~OgHttp2Session() {} +void OgHttp2Session::SetStreamUserData(Http2StreamId stream_id, + void* user_data) { + auto it = stream_map_.find(stream_id); + if (it != stream_map_.end()) { + it->second.user_data = user_data; + } +} + +void* OgHttp2Session::GetStreamUserData(Http2StreamId stream_id) { + auto it = stream_map_.find(stream_id); + if (it != stream_map_.end()) { + return it->second.user_data; + } + return nullptr; +} + +bool OgHttp2Session::ResumeStream(Http2StreamId stream_id) { + auto it = stream_map_.find(stream_id); + if (it->second.outbound_body == nullptr || + !write_scheduler_.StreamRegistered(stream_id)) { + return false; + } + write_scheduler_.MarkStreamReady(stream_id, /*add_to_front=*/false); + return true; +} + +int OgHttp2Session::GetStreamSendWindowSize(Http2StreamId stream_id) const { + auto it = stream_map_.find(stream_id); + if (it != stream_map_.end()) { + return it->second.send_window; + } + return -1; +} + +int OgHttp2Session::GetStreamReceiveWindowLimit(Http2StreamId stream_id) const { + auto it = stream_map_.find(stream_id); + if (it != stream_map_.end()) { + return it->second.window_manager.WindowSizeLimit(); + } + return -1; +} + +int OgHttp2Session::GetStreamReceiveWindowSize(Http2StreamId stream_id) const { + auto it = stream_map_.find(stream_id); + if (it != stream_map_.end()) { + return it->second.window_manager.CurrentWindowSize(); + } + return -1; +} + +int OgHttp2Session::GetReceiveWindowSize() const { + return connection_window_manager_.CurrentWindowSize(); +} + +int OgHttp2Session::GetHpackEncoderDynamicTableSize() const { + const spdy::HpackEncoder* encoder = framer_.GetHpackEncoder(); + return encoder == nullptr ? 0 : encoder->GetDynamicTableSize(); +} + +int OgHttp2Session::GetHpackDecoderDynamicTableSize() const { + const spdy::HpackDecoderAdapter* decoder = decoder_.GetHpackDecoder(); + return decoder == nullptr ? 0 : decoder->GetDynamicTableSize(); +} + ssize_t OgHttp2Session::ProcessBytes(absl::string_view bytes) { ssize_t preface_consumed = 0; if (!remaining_preface_.empty()) { @@ -68,27 +253,317 @@ int OgHttp2Session::Consume(Http2StreamId stream_id, size_t num_bytes) { } else { it->second.window_manager.MarkDataFlushed(num_bytes); } + connection_window_manager_.MarkDataFlushed(num_bytes); return 0; // Remove? } +void OgHttp2Session::StartGracefulShutdown() { + if (options_.perspective == Perspective::kServer) { + if (!queued_goaway_) { + EnqueueFrame(absl::make_unique<spdy::SpdyGoAwayIR>( + std::numeric_limits<int32_t>::max(), spdy::ERROR_CODE_NO_ERROR, + "graceful_shutdown")); + } + } else { + QUICHE_LOG(ERROR) << "Graceful shutdown not needed for clients."; + } +} + void OgHttp2Session::EnqueueFrame(std::unique_ptr<spdy::SpdyFrameIR> frame) { + if (frame->frame_type() == spdy::SpdyFrameType::GOAWAY) { + queued_goaway_ = true; + } else if (frame->frame_type() == spdy::SpdyFrameType::RST_STREAM) { + streams_reset_.insert(frame->stream_id()); + auto iter = stream_map_.find(frame->stream_id()); + if (iter != stream_map_.end()) { + iter->second.half_closed_local = true; + } + } frames_.push_back(std::move(frame)); } -std::string OgHttp2Session::GetBytesToWrite(absl::optional<size_t> max_bytes) { - const size_t serialized_max = - max_bytes ? max_bytes.value() : std::numeric_limits<size_t>::max(); - std::string serialized = std::move(serialized_prefix_); - while (serialized.size() < serialized_max && !frames_.empty()) { - spdy::SpdySerializedFrame frame = framer_.SerializeFrame(*frames_.front()); - absl::StrAppend(&serialized, absl::string_view(frame)); - frames_.pop_front(); +int OgHttp2Session::Send() { + MaybeSetupPreface(); + ssize_t result = std::numeric_limits<ssize_t>::max(); + // Flush any serialized prefix. + while (result > 0 && !serialized_prefix_.empty()) { + result = visitor_.OnReadyToSend(serialized_prefix_); + if (result > 0) { + serialized_prefix_.erase(0, result); + } + } + if (!serialized_prefix_.empty()) { + return result < 0 ? result : 0; + } + bool continue_writing = SendQueuedFrames(); + while (continue_writing && !connection_metadata_.empty()) { + continue_writing = SendMetadata(0, connection_metadata_); + } + // Wake streams for writes. + while (continue_writing && write_scheduler_.HasReadyStreams() && + connection_send_window_ > 0) { + const Http2StreamId stream_id = write_scheduler_.PopNextReadyStream(); + // TODO(birenroy): Add a return value to indicate write blockage, so streams + // aren't woken unnecessarily. + continue_writing = WriteForStream(stream_id); + } + if (continue_writing) { + SendQueuedFrames(); + } + return 0; +} + +bool OgHttp2Session::SendQueuedFrames() { + // Serialize and send frames in the queue. + while (!frames_.empty()) { + const auto& frame_ptr = frames_.front(); + FrameAttributeCollector c; + frame_ptr->Visit(&c); + visitor_.OnBeforeFrameSent(c.frame_type(), c.stream_id(), c.length(), + c.flags()); + spdy::SpdySerializedFrame frame = framer_.SerializeFrame(*frame_ptr); + const ssize_t result = visitor_.OnReadyToSend(absl::string_view(frame)); + if (result < 0) { + visitor_.OnConnectionError(); + return false; + } else if (result == 0) { + // Write blocked. + return false; + } else { + visitor_.OnFrameSent(c.frame_type(), c.stream_id(), c.length(), c.flags(), + c.error_code()); + if (static_cast<FrameType>(c.frame_type()) == FrameType::RST_STREAM) { + // If this endpoint is resetting the stream, the stream should be + // closed. This endpoint is already aware of the outbound RST_STREAM and + // its error code, so close with NO_ERROR. + visitor_.OnCloseStream(c.stream_id(), Http2ErrorCode::NO_ERROR); + } + + frames_.pop_front(); + if (static_cast<size_t>(result) < frame.size()) { + // The frame was partially written, so the rest must be buffered. + serialized_prefix_.assign(frame.data() + result, frame.size() - result); + return false; + } + } + } + return true; +} + +bool OgHttp2Session::WriteForStream(Http2StreamId stream_id) { + auto it = stream_map_.find(stream_id); + if (it == stream_map_.end()) { + QUICHE_LOG(ERROR) << "Can't find stream " << stream_id + << " which is ready to write!"; + return true; + } + StreamState& state = it->second; + bool connection_can_write = true; + if (!state.outbound_metadata.empty()) { + connection_can_write = SendMetadata(stream_id, state.outbound_metadata); + } + + if (state.outbound_body == nullptr) { + // No data to send, but there might be trailers. + if (state.trailers != nullptr) { + auto block_ptr = std::move(state.trailers); + if (state.half_closed_local) { + QUICHE_LOG(ERROR) << "Sent fin; can't send trailers."; + } else { + SendTrailers(stream_id, std::move(*block_ptr)); + MaybeCloseWithRstStream(stream_id, state); + } + } + return true; + } + bool source_can_produce = true; + int32_t available_window = std::min( + std::min(connection_send_window_, state.send_window), max_frame_payload_); + while (connection_can_write && available_window > 0 && + state.outbound_body != nullptr) { + ssize_t length; + bool end_data; + std::tie(length, end_data) = + state.outbound_body->SelectPayloadLength(available_window); + if (length == 0 && !end_data) { + source_can_produce = false; + break; + } else if (length == DataFrameSource::kError) { + source_can_produce = false; + visitor_.OnCloseStream(stream_id, Http2ErrorCode::INTERNAL_ERROR); + break; + } + const bool fin = end_data ? state.outbound_body->send_fin() : false; + if (length > 0 || fin) { + spdy::SpdyDataIR data(stream_id); + data.set_fin(fin); + data.SetDataShallow(length); + spdy::SpdySerializedFrame header = + spdy::SpdyFramer::SerializeDataFrameHeaderWithPaddingLengthField( + data); + QUICHE_DCHECK(serialized_prefix_.empty() && frames_.empty()); + const bool success = + state.outbound_body->Send(absl::string_view(header), length); + if (!success) { + connection_can_write = false; + break; + } + visitor_.OnFrameSent(/* DATA */ 0, stream_id, length, fin ? 0x1 : 0x0, 0); + connection_send_window_ -= length; + state.send_window -= length; + available_window = + std::min(std::min(connection_send_window_, state.send_window), + max_frame_payload_); + } + if (end_data) { + bool sent_trailers = false; + if (state.trailers != nullptr) { + auto block_ptr = std::move(state.trailers); + if (fin) { + QUICHE_LOG(ERROR) << "Sent fin; can't send trailers."; + } else { + SendTrailers(stream_id, std::move(*block_ptr)); + sent_trailers = true; + } + } + state.outbound_body = nullptr; + if (fin || sent_trailers) { + MaybeCloseWithRstStream(stream_id, state); + } + } + } + // If the stream still has data to send, it should be marked as ready in the + // write scheduler. + if (source_can_produce && state.send_window > 0 && + state.outbound_body != nullptr) { + write_scheduler_.MarkStreamReady(stream_id, false); + } + // Streams can continue writing as long as the connection is not write-blocked + // and there is additional flow control quota available. + return connection_can_write && available_window > 0; +} + +bool OgHttp2Session::SendMetadata(Http2StreamId stream_id, + OgHttp2Session::MetadataSequence& sequence) { + auto payload_buffer = absl::make_unique<uint8_t[]>(kMaxMetadataFrameSize); + while (!sequence.empty()) { + MetadataSource& source = *sequence.front(); + + ssize_t written; + bool end_metadata; + std::tie(written, end_metadata) = + source.Pack(payload_buffer.get(), kMaxMetadataFrameSize); + if (written < 0) { + // Did not touch the connection, so perhaps writes are still possible. + return true; + } + QUICHE_DCHECK_LE(static_cast<size_t>(written), kMaxMetadataFrameSize); + auto payload = absl::string_view( + reinterpret_cast<const char*>(payload_buffer.get()), written); + EnqueueFrame(absl::make_unique<spdy::SpdyUnknownIR>( + stream_id, kMetadataFrameType, end_metadata ? kMetadataEndFlag : 0u, + std::string(payload))); + if (end_metadata) { + sequence.erase(sequence.begin()); + } + } + return SendQueuedFrames(); +} + +int32_t OgHttp2Session::SubmitRequest( + absl::Span<const Header> headers, + std::unique_ptr<DataFrameSource> data_source, void* user_data) { + // TODO(birenroy): return an error for the incorrect perspective + const Http2StreamId stream_id = next_stream_id_; + next_stream_id_ += 2; + // Convert headers to header block, create headers frame. + auto frame = + absl::make_unique<spdy::SpdyHeadersIR>(stream_id, ToHeaderBlock(headers)); + // Add data source and user data to stream state + auto iter = CreateStream(stream_id); + write_scheduler_.MarkStreamReady(stream_id, false); + if (data_source == nullptr) { + frame->set_fin(true); + iter->second.half_closed_local = true; + } else { + iter->second.outbound_body = std::move(data_source); + } + iter->second.user_data = user_data; + // Enqueue headers frame + EnqueueFrame(std::move(frame)); + return stream_id; +} + +int OgHttp2Session::SubmitResponse( + Http2StreamId stream_id, absl::Span<const Header> headers, + std::unique_ptr<DataFrameSource> data_source) { + // TODO(birenroy): return an error for the incorrect perspective + auto iter = stream_map_.find(stream_id); + if (iter == stream_map_.end()) { + QUICHE_LOG(ERROR) << "Unable to find stream " << stream_id; + return -501; // NGHTTP2_ERR_INVALID_ARGUMENT + } + // Convert headers to header block, create headers frame + auto frame = + absl::make_unique<spdy::SpdyHeadersIR>(stream_id, ToHeaderBlock(headers)); + if (data_source == nullptr) { + frame->set_fin(true); + if (iter->second.half_closed_remote) { + visitor_.OnCloseStream(stream_id, Http2ErrorCode::NO_ERROR); + } + // TODO(birenroy): the server adapter should probably delete stream state + // when calling visitor_.OnCloseStream. + } else { + // Add data source to stream state + iter->second.outbound_body = std::move(data_source); + write_scheduler_.MarkStreamReady(stream_id, false); + } + EnqueueFrame(std::move(frame)); + return 0; +} + +int OgHttp2Session::SubmitTrailer(Http2StreamId stream_id, + absl::Span<const Header> trailers) { + // TODO(birenroy): Reject trailers when acting as a client? + auto iter = stream_map_.find(stream_id); + if (iter == stream_map_.end()) { + QUICHE_LOG(ERROR) << "Unable to find stream " << stream_id; + return -501; // NGHTTP2_ERR_INVALID_ARGUMENT + } + StreamState& state = iter->second; + if (state.half_closed_local) { + QUICHE_LOG(ERROR) << "Stream " << stream_id << " is half closed (local)"; + return -514; // NGHTTP2_ERR_INVALID_STREAM_STATE } - if (serialized.size() > serialized_max) { - serialized_prefix_ = serialized.substr(serialized_max); - serialized.resize(serialized_max); + if (state.trailers != nullptr) { + QUICHE_LOG(ERROR) << "Stream " << stream_id + << " already has trailers queued"; + return -514; // NGHTTP2_ERR_INVALID_STREAM_STATE + } + if (state.outbound_body == nullptr) { + // Enqueue trailers immediately. + SendTrailers(stream_id, ToHeaderBlock(trailers)); + MaybeCloseWithRstStream(stream_id, state); + } else { + QUICHE_LOG_IF(ERROR, state.outbound_body->send_fin()) + << "DataFrameSource will send fin, preventing trailers!"; + // Save trailers so they can be written once data is done. + state.trailers = + absl::make_unique<spdy::SpdyHeaderBlock>(ToHeaderBlock(trailers)); + write_scheduler_.MarkStreamReady(stream_id, false); + } + return 0; +} + +void OgHttp2Session::SubmitMetadata(Http2StreamId stream_id, + std::unique_ptr<MetadataSource> source) { + if (stream_id == 0) { + connection_metadata_.push_back(std::move(source)); + } else { + auto iter = CreateStream(stream_id); + iter->second.outbound_metadata.push_back(std::move(source)); + write_scheduler_.MarkStreamReady(stream_id, false); } - return serialized; } void OgHttp2Session::OnError(http2::Http2DecoderAdapter::SpdyFramerError error, @@ -103,32 +578,47 @@ void OgHttp2Session::OnCommonHeader(spdy::SpdyStreamId stream_id, size_t length, uint8_t type, uint8_t flags) { + highest_received_stream_id_ = std::max(static_cast<Http2StreamId>(stream_id), + highest_received_stream_id_); visitor_.OnFrameHeader(stream_id, length, type, flags); } void OgHttp2Session::OnDataFrameHeader(spdy::SpdyStreamId stream_id, - size_t length, - bool fin) { + size_t length, bool /*fin*/) { visitor_.OnBeginDataForStream(stream_id, length); } void OgHttp2Session::OnStreamFrameData(spdy::SpdyStreamId stream_id, const char* data, size_t len) { + MarkDataBuffered(stream_id, len); visitor_.OnDataForStream(stream_id, absl::string_view(data, len)); } void OgHttp2Session::OnStreamEnd(spdy::SpdyStreamId stream_id) { + auto iter = stream_map_.find(stream_id); + if (iter != stream_map_.end()) { + iter->second.half_closed_remote = true; + } visitor_.OnEndStream(stream_id); + if (iter != stream_map_.end() && iter->second.half_closed_local && + options_.perspective == Perspective::kClient) { + // From the client's perspective, the stream can be closed if it's already + // half_closed_local. + visitor_.OnCloseStream(stream_id, Http2ErrorCode::NO_ERROR); + } } -void OgHttp2Session::OnStreamPadLength(spdy::SpdyStreamId /*stream_id*/, - size_t /*value*/) { - // TODO(181586191): handle padding +void OgHttp2Session::OnStreamPadLength(spdy::SpdyStreamId stream_id, + size_t value) { + MarkDataBuffered(stream_id, 1 + value); + // TODO(181586191): Pass padding to the visitor? } -void OgHttp2Session::OnStreamPadding(spdy::SpdyStreamId stream_id, size_t len) { - // TODO(181586191): handle padding +void OgHttp2Session::OnStreamPadding(spdy::SpdyStreamId /*stream_id*/, size_t + /*len*/) { + // Flow control was accounted for in OnStreamPadLength(). + // TODO(181586191): Pass padding to the visitor? } spdy::SpdyHeadersHandlerInterface* OgHttp2Session::OnHeaderFrameStart( @@ -137,13 +627,21 @@ spdy::SpdyHeadersHandlerInterface* OgHttp2Session::OnHeaderFrameStart( return &headers_handler_; } -void OgHttp2Session::OnHeaderFrameEnd(spdy::SpdyStreamId stream_id) { +void OgHttp2Session::OnHeaderFrameEnd(spdy::SpdyStreamId /*stream_id*/) { headers_handler_.set_stream_id(0); } void OgHttp2Session::OnRstStream(spdy::SpdyStreamId stream_id, spdy::SpdyErrorCode error_code) { + auto iter = stream_map_.find(stream_id); + if (iter != stream_map_.end()) { + iter->second.half_closed_remote = true; + iter->second.outbound_body = nullptr; + write_scheduler_.UnregisterStream(stream_id); + } visitor_.OnRstStream(stream_id, TranslateErrorCode(error_code)); + // TODO(birenroy): Consider bundling "close stream" behavior into a dedicated + // method that also cleans up the stream map. visitor_.OnCloseStream(stream_id, TranslateErrorCode(error_code)); } @@ -153,10 +651,16 @@ void OgHttp2Session::OnSettings() { void OgHttp2Session::OnSetting(spdy::SpdySettingsId id, uint32_t value) { visitor_.OnSetting({id, value}); + if (id == kMetadataExtensionId) { + peer_supports_metadata_ = (value != 0); + } } void OgHttp2Session::OnSettingsEnd() { visitor_.OnSettingsEnd(); + auto settings = absl::make_unique<spdy::SpdySettingsIR>(); + settings->set_is_ack(true); + EnqueueFrame(std::move(settings)); } void OgHttp2Session::OnSettingsAck() { @@ -174,39 +678,46 @@ void OgHttp2Session::OnGoAway(spdy::SpdyStreamId last_accepted_stream_id, ""); } -bool OgHttp2Session::OnGoAwayFrameData(const char* goaway_data, size_t len) { +bool OgHttp2Session::OnGoAwayFrameData(const char* /*goaway_data*/, size_t + /*len*/) { // Opaque data is currently ignored. return true; } void OgHttp2Session::OnHeaders(spdy::SpdyStreamId stream_id, - bool has_priority, - int weight, - spdy::SpdyStreamId parent_stream_id, - bool exclusive, - bool fin, - bool end) {} + bool /*has_priority*/, int /*weight*/, + spdy::SpdyStreamId /*parent_stream_id*/, + bool /*exclusive*/, bool /*fin*/, bool /*end*/) { + if (options_.perspective == Perspective::kServer) { + CreateStream(stream_id); + } +} void OgHttp2Session::OnWindowUpdate(spdy::SpdyStreamId stream_id, int delta_window_size) { if (stream_id == 0) { - peer_window_ += delta_window_size; + connection_send_window_ += delta_window_size; } else { auto it = stream_map_.find(stream_id); if (it == stream_map_.end()) { QUICHE_VLOG(1) << "Stream " << stream_id << " not found!"; } else { + if (it->second.send_window == 0) { + // The stream was blocked on flow control. + write_scheduler_.MarkStreamReady(stream_id, false); + } it->second.send_window += delta_window_size; } } visitor_.OnWindowUpdate(stream_id, delta_window_size); } -void OgHttp2Session::OnPushPromise(spdy::SpdyStreamId stream_id, - spdy::SpdyStreamId promised_stream_id, - bool end) {} +void OgHttp2Session::OnPushPromise(spdy::SpdyStreamId /*stream_id*/, + spdy::SpdyStreamId /*promised_stream_id*/, + bool /*end*/) {} -void OgHttp2Session::OnContinuation(spdy::SpdyStreamId stream_id, bool end) {} +void OgHttp2Session::OnContinuation(spdy::SpdyStreamId /*stream_id*/, bool + /*end*/) {} void OgHttp2Session::OnAltSvc(spdy::SpdyStreamId /*stream_id*/, absl::string_view /*origin*/, @@ -214,18 +725,141 @@ void OgHttp2Session::OnAltSvc(spdy::SpdyStreamId /*stream_id*/, AlternativeServiceVector& /*altsvc_vector*/) { } -void OgHttp2Session::OnPriority(spdy::SpdyStreamId stream_id, - spdy::SpdyStreamId parent_stream_id, - int weight, - bool exclusive) {} +void OgHttp2Session::OnPriority(spdy::SpdyStreamId /*stream_id*/, + spdy::SpdyStreamId /*parent_stream_id*/, + int /*weight*/, bool /*exclusive*/) {} -void OgHttp2Session::OnPriorityUpdate(spdy::SpdyStreamId prioritized_stream_id, - absl::string_view priority_field_value) {} +void OgHttp2Session::OnPriorityUpdate( + spdy::SpdyStreamId /*prioritized_stream_id*/, + absl::string_view /*priority_field_value*/) {} -bool OgHttp2Session::OnUnknownFrame(spdy::SpdyStreamId stream_id, - uint8_t frame_type) { +bool OgHttp2Session::OnUnknownFrame(spdy::SpdyStreamId /*stream_id*/, + uint8_t /*frame_type*/) { return true; } +void OgHttp2Session::OnHeaderStatus( + Http2StreamId stream_id, Http2VisitorInterface::OnHeaderResult result) { + QUICHE_DCHECK_NE(result, Http2VisitorInterface::HEADER_OK); + if (result == Http2VisitorInterface::HEADER_RST_STREAM) { + auto it = streams_reset_.find(stream_id); + if (it == streams_reset_.end()) { + EnqueueFrame(absl::make_unique<spdy::SpdyRstStreamIR>( + stream_id, spdy::ERROR_CODE_INTERNAL_ERROR)); + } + } else if (result == Http2VisitorInterface::HEADER_CONNECTION_ERROR) { + visitor_.OnConnectionError(); + } +} + +bool OgHttp2Session::OnFrameHeader(spdy::SpdyStreamId stream_id, size_t length, + uint8_t type, uint8_t flags) { + if (type == kMetadataFrameType) { + QUICHE_DCHECK_EQ(metadata_length_, 0u); + visitor_.OnBeginMetadataForStream(stream_id, length); + metadata_stream_id_ = stream_id; + metadata_length_ = length; + end_metadata_ = flags & kMetadataEndFlag; + return true; + } else { + QUICHE_DLOG(INFO) << "Unexpected frame type " << static_cast<int>(type) + << " received by the extension visitor."; + return false; + } +} + +void OgHttp2Session::OnFramePayload(const char* data, size_t len) { + if (metadata_length_ > 0) { + QUICHE_DCHECK_LE(len, metadata_length_); + visitor_.OnMetadataForStream(metadata_stream_id_, + absl::string_view(data, len)); + metadata_length_ -= len; + if (metadata_length_ == 0 && end_metadata_) { + visitor_.OnMetadataEndForStream(metadata_stream_id_); + metadata_stream_id_ = 0; + end_metadata_ = false; + } + } else { + QUICHE_DLOG(INFO) << "Unexpected metadata payload for stream " + << metadata_stream_id_; + } +} + +void OgHttp2Session::MaybeSetupPreface() { + if (!queued_preface_) { + if (options_.perspective == Perspective::kClient) { + serialized_prefix_.assign(spdy::kHttp2ConnectionHeaderPrefix, + spdy::kHttp2ConnectionHeaderPrefixSize); + } + // First frame must be a non-ack SETTINGS. + if (frames_.empty() || + frames_.front()->frame_type() != spdy::SpdyFrameType::SETTINGS || + reinterpret_cast<spdy::SpdySettingsIR*>(frames_.front().get()) + ->is_ack()) { + frames_.push_front(absl::make_unique<spdy::SpdySettingsIR>()); + } + queued_preface_ = true; + } +} + +void OgHttp2Session::SendWindowUpdate(Http2StreamId stream_id, + size_t update_delta) { + EnqueueFrame( + absl::make_unique<spdy::SpdyWindowUpdateIR>(stream_id, update_delta)); +} + +void OgHttp2Session::SendTrailers(Http2StreamId stream_id, + spdy::SpdyHeaderBlock trailers) { + auto frame = + absl::make_unique<spdy::SpdyHeadersIR>(stream_id, std::move(trailers)); + frame->set_fin(true); + EnqueueFrame(std::move(frame)); +} + +void OgHttp2Session::MaybeCloseWithRstStream(Http2StreamId stream_id, + StreamState& state) { + state.half_closed_local = true; + if (options_.perspective == Perspective::kServer) { + if (state.half_closed_remote) { + visitor_.OnCloseStream(stream_id, Http2ErrorCode::NO_ERROR); + } else { + // Since the peer has not yet ended the stream, this endpoint should + // send a RST_STREAM NO_ERROR. See RFC 7540 Section 8.1. + EnqueueFrame(absl::make_unique<spdy::SpdyRstStreamIR>( + stream_id, spdy::SpdyErrorCode::ERROR_CODE_NO_ERROR)); + // Enqueuing the RST_STREAM also invokes OnCloseStream. + } + // TODO(birenroy): the server adapter should probably delete stream state + // when calling visitor_.OnCloseStream. + } +} + +void OgHttp2Session::MarkDataBuffered(Http2StreamId stream_id, size_t bytes) { + connection_window_manager_.MarkDataBuffered(bytes); + auto it = stream_map_.find(stream_id); + if (it != stream_map_.end()) { + it->second.window_manager.MarkDataBuffered(bytes); + } +} + +OgHttp2Session::StreamStateMap::iterator OgHttp2Session::CreateStream( + Http2StreamId stream_id) { + WindowManager::WindowUpdateListener listener = + [this, stream_id](size_t window_update_delta) { + SendWindowUpdate(stream_id, window_update_delta); + }; + absl::flat_hash_map<Http2StreamId, StreamState>::iterator iter; + bool inserted; + std::tie(iter, inserted) = stream_map_.try_emplace( + stream_id, + StreamState(stream_receive_window_limit_, std::move(listener))); + if (inserted) { + // Add the stream to the write scheduler. + const WriteScheduler::StreamPrecedenceType precedence(3); + write_scheduler_.RegisterStream(stream_id, precedence); + } + return iter; +} + } // namespace adapter } // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_session.h b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_session.h index b0bc28b7002..77b9da73c38 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_session.h +++ b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_session.h @@ -3,11 +3,14 @@ #include <list> +#include "http2/adapter/data_source.h" #include "http2/adapter/http2_session.h" #include "http2/adapter/http2_util.h" #include "http2/adapter/http2_visitor_interface.h" #include "http2/adapter/window_manager.h" +#include "http2/core/priority_write_scheduler.h" #include "common/platform/api/quiche_bug_tracker.h" +#include "common/platform/api/quiche_export.h" #include "spdy/core/http2_frame_decoder_adapter.h" #include "spdy/core/spdy_framer.h" @@ -15,33 +18,80 @@ namespace http2 { namespace adapter { // This class manages state associated with a single multiplexed HTTP/2 session. -class OgHttp2Session : public Http2Session, - public spdy::SpdyFramerVisitorInterface { +class QUICHE_EXPORT_PRIVATE OgHttp2Session + : public Http2Session, + public spdy::SpdyFramerVisitorInterface, + public spdy::ExtensionVisitorInterface { public: - struct Options { + struct QUICHE_EXPORT_PRIVATE Options { Perspective perspective = Perspective::kClient; }; - OgHttp2Session(Http2VisitorInterface& visitor, Options /*options*/); + OgHttp2Session(Http2VisitorInterface& visitor, Options options); ~OgHttp2Session() override; // Enqueues a frame for transmission to the peer. void EnqueueFrame(std::unique_ptr<spdy::SpdyFrameIR> frame); - // If |want_write()| returns true, this method will return a non-empty string - // containing serialized HTTP/2 frames to write to the peer. - std::string GetBytesToWrite(absl::optional<size_t> max_bytes); + // Starts a graceful shutdown sequence. No-op if a GOAWAY has already been + // sent. + void StartGracefulShutdown(); + + // Invokes the visitor's OnReadyToSend() method for serialized frame data. + int Send(); + + int32_t SubmitRequest(absl::Span<const Header> headers, + std::unique_ptr<DataFrameSource> data_source, + void* user_data); + int SubmitResponse(Http2StreamId stream_id, absl::Span<const Header> headers, + std::unique_ptr<DataFrameSource> data_source); + int SubmitTrailer(Http2StreamId stream_id, absl::Span<const Header> trailers); + void SubmitMetadata(Http2StreamId stream_id, + std::unique_ptr<MetadataSource> source); + + bool IsServerSession() const { + return options_.perspective == Perspective::kServer; + } + Http2StreamId GetHighestReceivedStreamId() const { + return highest_received_stream_id_; + } + void SetStreamUserData(Http2StreamId stream_id, void* user_data); + void* GetStreamUserData(Http2StreamId stream_id); + + // Resumes a stream that was previously blocked. Returns true on success. + bool ResumeStream(Http2StreamId stream_id); + + // Returns the peer's outstanding stream receive window for the given stream. + int GetStreamSendWindowSize(Http2StreamId stream_id) const; + + // Returns the current upper bound on the flow control receive window for this + // stream. + int GetStreamReceiveWindowLimit(Http2StreamId stream_id) const; + + // Returns the outstanding stream receive window, or -1 if the stream does not + // exist. + int GetStreamReceiveWindowSize(Http2StreamId stream_id) const; + + // Returns the outstanding connection receive window. + int GetReceiveWindowSize() const; + + // Returns the size of the HPACK encoder's dynamic table, including the + // per-entry overhead from the specification. + int GetHpackEncoderDynamicTableSize() const; + + // Returns the size of the HPACK decoder's dynamic table, including the + // per-entry overhead from the specification. + int GetHpackDecoderDynamicTableSize() const; // From Http2Session. ssize_t ProcessBytes(absl::string_view bytes) override; int Consume(Http2StreamId stream_id, size_t num_bytes) override; bool want_read() const override { return !received_goaway_; } bool want_write() const override { - return !frames_.empty() || !serialized_prefix_.empty(); - } - int GetRemoteWindowSize() const override { - return peer_window_; + return !frames_.empty() || !serialized_prefix_.empty() || + write_scheduler_.HasReadyStreams() || !connection_metadata_.empty(); } + int GetRemoteWindowSize() const override { return connection_send_window_; } // From SpdyFramerVisitorInterface void OnError(http2::Http2DecoderAdapter::SpdyFramerError error, @@ -72,7 +122,7 @@ class OgHttp2Session : public Http2Session, void OnPing(spdy::SpdyPingId unique_id, bool is_ack) override; void OnGoAway(spdy::SpdyStreamId last_accepted_stream_id, spdy::SpdyErrorCode error_code) override; - bool OnGoAwayFrameData(const char* goaway_data, size_t len); + bool OnGoAwayFrameData(const char* goaway_data, size_t len) override; void OnHeaders(spdy::SpdyStreamId stream_id, bool has_priority, int weight, @@ -86,10 +136,9 @@ class OgHttp2Session : public Http2Session, spdy::SpdyStreamId promised_stream_id, bool end) override; void OnContinuation(spdy::SpdyStreamId stream_id, bool end) override; - void OnAltSvc(spdy::SpdyStreamId /*stream_id*/, - absl::string_view /*origin*/, + void OnAltSvc(spdy::SpdyStreamId /*stream_id*/, absl::string_view /*origin*/, const spdy::SpdyAltSvcWireFormat:: - AlternativeServiceVector& /*altsvc_vector*/); + AlternativeServiceVector& /*altsvc_vector*/) override; void OnPriority(spdy::SpdyStreamId stream_id, spdy::SpdyStreamId parent_stream_id, int weight, @@ -99,40 +148,136 @@ class OgHttp2Session : public Http2Session, bool OnUnknownFrame(spdy::SpdyStreamId stream_id, uint8_t frame_type) override; + // Invoked when header processing encounters an invalid or otherwise + // problematic header. + void OnHeaderStatus(Http2StreamId stream_id, + Http2VisitorInterface::OnHeaderResult result); + + // Returns true if a recognized extension frame is received. + bool OnFrameHeader(spdy::SpdyStreamId stream_id, size_t length, uint8_t type, + uint8_t flags) override; + + // Handles the payload for a recognized extension frame. + void OnFramePayload(const char* data, size_t len) override; + private: - struct StreamState { + using MetadataSequence = std::vector<std::unique_ptr<MetadataSource>>; + struct QUICHE_EXPORT_PRIVATE StreamState { + StreamState(int32_t stream_receive_window, + WindowManager::WindowUpdateListener listener) + : window_manager(stream_receive_window, std::move(listener)) {} + WindowManager window_manager; - int32_t send_window = 65535; + std::unique_ptr<DataFrameSource> outbound_body; + MetadataSequence outbound_metadata; + std::unique_ptr<spdy::SpdyHeaderBlock> trailers; + void* user_data = nullptr; + int32_t send_window = kInitialFlowControlWindowSize; bool half_closed_local = false; bool half_closed_remote = false; }; + using StreamStateMap = absl::flat_hash_map<Http2StreamId, StreamState>; - class PassthroughHeadersHandler : public spdy::SpdyHeadersHandlerInterface { + class QUICHE_EXPORT_PRIVATE PassthroughHeadersHandler + : public spdy::SpdyHeadersHandlerInterface { public: - explicit PassthroughHeadersHandler(Http2VisitorInterface& visitor) - : visitor_(visitor) {} - void set_stream_id(Http2StreamId stream_id) { stream_id_ = stream_id; } + explicit PassthroughHeadersHandler(OgHttp2Session& session, + Http2VisitorInterface& visitor) + : session_(session), visitor_(visitor) {} + void set_stream_id(Http2StreamId stream_id) { + stream_id_ = stream_id; + result_ = Http2VisitorInterface::HEADER_OK; + } void OnHeaderBlockStart() override; void OnHeader(absl::string_view key, absl::string_view value) override; void OnHeaderBlockEnd(size_t /* uncompressed_header_bytes */, size_t /* compressed_header_bytes */) override; private: + OgHttp2Session& session_; Http2VisitorInterface& visitor_; Http2StreamId stream_id_ = 0; + Http2VisitorInterface::OnHeaderResult result_ = + Http2VisitorInterface::HEADER_OK; }; + // Queues the connection preface, if not already done. + void MaybeSetupPreface(); + + void SendWindowUpdate(Http2StreamId stream_id, size_t update_delta); + + // Sends queued frames, returning true if all frames were flushed + // successfully. + bool SendQueuedFrames(); + + // Returns false if the connection is write-blocked (due to flow control or + // some other reason). + bool WriteForStream(Http2StreamId stream_id); + + bool SendMetadata(Http2StreamId stream_id, MetadataSequence& sequence); + + void SendTrailers(Http2StreamId stream_id, spdy::SpdyHeaderBlock trailers); + + // Encapsulates the RST_STREAM NO_ERROR behavior described in RFC 7540 + // Section 8.1. + void MaybeCloseWithRstStream(Http2StreamId stream_id, StreamState& state); + + // Performs flow control accounting for data sent by the peer. + void MarkDataBuffered(Http2StreamId stream_id, size_t bytes); + + // Creates a stream and returns an iterator pointing to it. + StreamStateMap::iterator CreateStream(Http2StreamId stream_id); + + // Receives events when inbound frames are parsed. Http2VisitorInterface& visitor_; + + // Encodes outbound frames. spdy::SpdyFramer framer_{spdy::SpdyFramer::ENABLE_COMPRESSION}; + + // Decodes inbound frames. http2::Http2DecoderAdapter decoder_; - absl::flat_hash_map<Http2StreamId, StreamState> stream_map_; + + // Maintains the state of all streams known to this session. + StreamStateMap stream_map_; + + // Maintains the queue of outbound frames, and any serialized bytes that have + // not yet been consumed. std::list<std::unique_ptr<spdy::SpdyFrameIR>> frames_; - PassthroughHeadersHandler headers_handler_; std::string serialized_prefix_; + + // Maintains the set of streams ready to write data to the peer. + using WriteScheduler = PriorityWriteScheduler<Http2StreamId>; + WriteScheduler write_scheduler_; + + // Delivers header name-value pairs to the visitor. + PassthroughHeadersHandler headers_handler_; + + // Tracks the remaining client connection preface, in the case of a server + // session. absl::string_view remaining_preface_; - int peer_window_ = 65535; + + WindowManager connection_window_manager_; + + absl::flat_hash_set<Http2StreamId> streams_reset_; + + MetadataSequence connection_metadata_; + + Http2StreamId next_stream_id_ = 1; + Http2StreamId highest_received_stream_id_ = 0; + Http2StreamId metadata_stream_id_ = 0; + size_t metadata_length_ = 0; + int connection_send_window_ = kInitialFlowControlWindowSize; + // The initial flow control receive window size for any newly created streams. + int stream_receive_window_limit_ = kInitialFlowControlWindowSize; + int max_frame_payload_ = 16384; Options options_; bool received_goaway_ = false; + bool queued_preface_ = false; + bool peer_supports_metadata_ = false; + bool end_metadata_ = false; + + // Replace this with a stream ID, for multiple GOAWAY support. + bool queued_goaway_ = false; }; } // namespace adapter diff --git a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_session_test.cc b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_session_test.cc index ab155697d5d..3f24ea200ef 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_session_test.cc +++ b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_session_test.cc @@ -2,6 +2,7 @@ #include "http2/adapter/mock_http2_visitor.h" #include "http2/adapter/test_frame_sequence.h" +#include "http2/adapter/test_utils.h" #include "common/platform/api/quiche_test.h" namespace http2 { @@ -9,6 +10,7 @@ namespace adapter { namespace test { namespace { +using spdy::SpdyFrameType; using testing::_; enum FrameType { @@ -31,7 +33,9 @@ TEST(OgHttp2SessionTest, ClientConstruction) { visitor, OgHttp2Session::Options{.perspective = Perspective::kClient}); EXPECT_TRUE(session.want_read()); EXPECT_FALSE(session.want_write()); - EXPECT_EQ(session.GetRemoteWindowSize(), kDefaultInitialStreamWindowSize); + EXPECT_EQ(session.GetRemoteWindowSize(), kInitialFlowControlWindowSize); + EXPECT_FALSE(session.IsServerSession()); + EXPECT_EQ(0, session.GetHighestReceivedStreamId()); } TEST(OgHttp2SessionTest, ClientHandlesFrames) { @@ -56,43 +60,433 @@ TEST(OgHttp2SessionTest, ClientHandlesFrames) { EXPECT_CALL(visitor, OnFrameHeader(0, 4, WINDOW_UPDATE, 0)); EXPECT_CALL(visitor, OnWindowUpdate(0, 1000)); - const ssize_t initial_result = session.ProcessBytes(initial_frames); + const size_t initial_result = session.ProcessBytes(initial_frames); EXPECT_EQ(initial_frames.size(), initial_result); EXPECT_EQ(session.GetRemoteWindowSize(), - kDefaultInitialStreamWindowSize + 1000); + kInitialFlowControlWindowSize + 1000); + EXPECT_EQ(0, session.GetHighestReceivedStreamId()); + + // Connection has not yet received any data. + EXPECT_EQ(kInitialFlowControlWindowSize, session.GetReceiveWindowSize()); + + EXPECT_EQ(0, session.GetHpackDecoderDynamicTableSize()); // Should OgHttp2Session require that streams 1 and 3 have been created? + // Submit a request to ensure the first stream is created. + const char* kSentinel1 = "arbitrary pointer 1"; + auto body1 = absl::make_unique<TestDataFrameSource>(visitor, true); + body1->AppendPayload("This is an example request body."); + body1->EndData(); + int stream_id = + session.SubmitRequest(ToHeaders({{":method", "POST"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}), + std::move(body1), const_cast<char*>(kSentinel1)); + EXPECT_EQ(stream_id, 1); + const std::string stream_frames = TestFrameSequence() - .Headers(1, + .Headers(stream_id, {{":status", "200"}, {"server", "my-fake-server"}, {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}}, /*fin=*/false) - .Data(1, "This is the response body.") + .Data(stream_id, "This is the response body.") .RstStream(3, Http2ErrorCode::INTERNAL_ERROR) .GoAway(5, Http2ErrorCode::ENHANCE_YOUR_CALM, "calm down!!") .Serialize(); - EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); - EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); - EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "200")); - EXPECT_CALL(visitor, OnHeaderForStream(1, "server", "my-fake-server")); + EXPECT_CALL(visitor, OnFrameHeader(stream_id, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(stream_id)); + EXPECT_CALL(visitor, OnHeaderForStream(stream_id, ":status", "200")); EXPECT_CALL(visitor, - OnHeaderForStream(1, "date", "Tue, 6 Apr 2021 12:54:01 GMT")); - EXPECT_CALL(visitor, OnEndHeadersForStream(1)); - EXPECT_CALL(visitor, OnFrameHeader(1, 26, DATA, 0)); - EXPECT_CALL(visitor, OnBeginDataForStream(1, 26)); - EXPECT_CALL(visitor, OnDataForStream(1, "This is the response body.")); + OnHeaderForStream(stream_id, "server", "my-fake-server")); + EXPECT_CALL(visitor, OnHeaderForStream(stream_id, "date", + "Tue, 6 Apr 2021 12:54:01 GMT")); + EXPECT_CALL(visitor, OnEndHeadersForStream(stream_id)); + EXPECT_CALL(visitor, OnFrameHeader(stream_id, 26, DATA, 0)); + EXPECT_CALL(visitor, OnBeginDataForStream(stream_id, 26)); + EXPECT_CALL(visitor, + OnDataForStream(stream_id, "This is the response body.")); EXPECT_CALL(visitor, OnFrameHeader(3, 4, RST_STREAM, 0)); EXPECT_CALL(visitor, OnRstStream(3, Http2ErrorCode::INTERNAL_ERROR)); EXPECT_CALL(visitor, OnCloseStream(3, Http2ErrorCode::INTERNAL_ERROR)); EXPECT_CALL(visitor, OnFrameHeader(0, 19, GOAWAY, 0)); EXPECT_CALL(visitor, OnGoAway(5, Http2ErrorCode::ENHANCE_YOUR_CALM, "")); - const ssize_t stream_result = session.ProcessBytes(stream_frames); + const size_t stream_result = session.ProcessBytes(stream_frames); EXPECT_EQ(stream_frames.size(), stream_result); + EXPECT_EQ(3, session.GetHighestReceivedStreamId()); + + // The first stream is active and has received some data. + EXPECT_GT(kInitialFlowControlWindowSize, + session.GetStreamReceiveWindowSize(stream_id)); + // Connection receive window is equivalent to the first stream's. + EXPECT_EQ(session.GetReceiveWindowSize(), + session.GetStreamReceiveWindowSize(stream_id)); + // Receive window upper bound is still the initial value. + EXPECT_EQ(kInitialFlowControlWindowSize, + session.GetStreamReceiveWindowLimit(stream_id)); + + EXPECT_GT(session.GetHpackDecoderDynamicTableSize(), 0); +} + +// Verifies that a client session enqueues initial SETTINGS if Send() is called +// before any frames are explicitly queued. +TEST(OgHttp2SessionTest, ClientEnqueuesSettingsOnSend) { + DataSavingVisitor visitor; + OgHttp2Session session( + visitor, OgHttp2Session::Options{.perspective = Perspective::kClient}); + EXPECT_FALSE(session.want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + + int result = session.Send(); + EXPECT_EQ(0, result); + absl::string_view serialized = visitor.data(); + EXPECT_THAT(serialized, + testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); + serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); + EXPECT_THAT(serialized, EqualsFrames({SpdyFrameType::SETTINGS})); +} + +// Verifies that a client session enqueues initial SETTINGS before whatever +// frame type is passed to the first invocation of EnqueueFrame(). +TEST(OgHttp2SessionTest, ClientEnqueuesSettingsBeforeOtherFrame) { + DataSavingVisitor visitor; + OgHttp2Session session( + visitor, OgHttp2Session::Options{.perspective = Perspective::kClient}); + EXPECT_FALSE(session.want_write()); + session.EnqueueFrame(absl::make_unique<spdy::SpdyPingIR>(42)); + EXPECT_TRUE(session.want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, 8, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(PING, 0, 8, 0x0, 0)); + + int result = session.Send(); + EXPECT_EQ(0, result); + absl::string_view serialized = visitor.data(); + EXPECT_THAT(serialized, + testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); + serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); + EXPECT_THAT(serialized, + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::PING})); +} + +// Verifies that if the first call to EnqueueFrame() passes a SETTINGS frame, +// the client session will not enqueue an additional SETTINGS frame. +TEST(OgHttp2SessionTest, ClientEnqueuesSettingsOnce) { + DataSavingVisitor visitor; + OgHttp2Session session( + visitor, OgHttp2Session::Options{.perspective = Perspective::kClient}); + EXPECT_FALSE(session.want_write()); + session.EnqueueFrame(absl::make_unique<spdy::SpdySettingsIR>()); + EXPECT_TRUE(session.want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + + int result = session.Send(); + EXPECT_EQ(0, result); + absl::string_view serialized = visitor.data(); + EXPECT_THAT(serialized, + testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); + serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); + EXPECT_THAT(serialized, EqualsFrames({SpdyFrameType::SETTINGS})); +} + +TEST(OgHttp2SessionTest, ClientSubmitRequest) { + DataSavingVisitor visitor; + OgHttp2Session session( + visitor, OgHttp2Session::Options{.perspective = Perspective::kClient}); + + EXPECT_FALSE(session.want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + + // Even though the user has not queued any frames for the session, it should + // still send the connection preface. + int result = session.Send(); + EXPECT_EQ(0, result); + absl::string_view serialized = visitor.data(); + EXPECT_THAT(serialized, + testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); + serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); + // Initial SETTINGS. + EXPECT_THAT(serialized, EqualsFrames({SpdyFrameType::SETTINGS})); + visitor.Clear(); + + const std::string initial_frames = + TestFrameSequence().ServerPreface().Serialize(); + testing::InSequence s; + + // Server preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + const size_t initial_result = session.ProcessBytes(initial_frames); + EXPECT_EQ(initial_frames.size(), initial_result); + + // Session will want to write a SETTINGS ack. + EXPECT_TRUE(session.want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + + result = session.Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); + visitor.Clear(); + + EXPECT_EQ(0, session.GetHpackEncoderDynamicTableSize()); + + const char* kSentinel1 = "arbitrary pointer 1"; + auto body1 = absl::make_unique<TestDataFrameSource>(visitor, true); + body1->AppendPayload("This is an example request body."); + body1->EndData(); + int stream_id = + session.SubmitRequest(ToHeaders({{":method", "POST"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}), + std::move(body1), const_cast<char*>(kSentinel1)); + EXPECT_GT(stream_id, 0); + EXPECT_TRUE(session.want_write()); + EXPECT_EQ(kSentinel1, session.GetStreamUserData(stream_id)); + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0)); + EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, _, 0x1, 0)); + + result = session.Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS, + spdy::SpdyFrameType::DATA})); + visitor.Clear(); + EXPECT_FALSE(session.want_write()); + + // Some data was sent, so the remaining send window size should be less than + // the default. + EXPECT_LT(session.GetStreamSendWindowSize(stream_id), + kInitialFlowControlWindowSize); + EXPECT_GT(session.GetStreamSendWindowSize(stream_id), 0); + // Send window for a nonexistent stream is not available. + EXPECT_EQ(-1, session.GetStreamSendWindowSize(stream_id + 2)); + + EXPECT_GT(session.GetHpackEncoderDynamicTableSize(), 0); + + stream_id = + session.SubmitRequest(ToHeaders({{":method", "POST"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/two"}}), + nullptr, nullptr); + EXPECT_GT(stream_id, 0); + EXPECT_TRUE(session.want_write()); + const char* kSentinel2 = "arbitrary pointer 2"; + EXPECT_EQ(nullptr, session.GetStreamUserData(stream_id)); + session.SetStreamUserData(stream_id, const_cast<char*>(kSentinel2)); + EXPECT_EQ(kSentinel2, session.GetStreamUserData(stream_id)); + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0)); + + result = session.Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS})); + + // No data was sent (just HEADERS), so the remaining send window size should + // still be the default. + EXPECT_EQ(session.GetStreamSendWindowSize(stream_id), + kInitialFlowControlWindowSize); +} + +// This test exercises the case where the client request body source is read +// blocked. +TEST(OgHttp2SessionTest, ClientSubmitRequestWithReadBlock) { + DataSavingVisitor visitor; + OgHttp2Session session( + visitor, OgHttp2Session::Options{.perspective = Perspective::kClient}); + EXPECT_FALSE(session.want_write()); + + const char* kSentinel1 = "arbitrary pointer 1"; + auto body1 = absl::make_unique<TestDataFrameSource>(visitor, true); + TestDataFrameSource* body_ref = body1.get(); + int stream_id = + session.SubmitRequest(ToHeaders({{":method", "POST"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}), + std::move(body1), const_cast<char*>(kSentinel1)); + EXPECT_GT(stream_id, 0); + EXPECT_TRUE(session.want_write()); + EXPECT_EQ(kSentinel1, session.GetStreamUserData(stream_id)); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0)); + + int result = session.Send(); + EXPECT_EQ(0, result); + absl::string_view serialized = visitor.data(); + EXPECT_THAT(serialized, + testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); + serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); + EXPECT_THAT(serialized, + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::HEADERS})); + // No data frame, as body1 was read blocked. + visitor.Clear(); + EXPECT_FALSE(session.want_write()); + + body_ref->AppendPayload("This is an example request body."); + body_ref->EndData(); + EXPECT_TRUE(session.ResumeStream(stream_id)); + EXPECT_TRUE(session.want_write()); + + EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, _, 0x1, 0)); + + result = session.Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::DATA})); + EXPECT_FALSE(session.want_write()); + + // Stream data is done, so this stream cannot be resumed. + EXPECT_FALSE(session.ResumeStream(stream_id)); + EXPECT_FALSE(session.want_write()); +} + +// This test exercises the case where the client request body source is read +// blocked, then ends with an empty DATA frame. +TEST(OgHttp2SessionTest, ClientSubmitRequestEmptyDataWithFin) { + DataSavingVisitor visitor; + OgHttp2Session session( + visitor, OgHttp2Session::Options{.perspective = Perspective::kClient}); + EXPECT_FALSE(session.want_write()); + + const char* kSentinel1 = "arbitrary pointer 1"; + auto body1 = absl::make_unique<TestDataFrameSource>(visitor, true); + TestDataFrameSource* body_ref = body1.get(); + int stream_id = + session.SubmitRequest(ToHeaders({{":method", "POST"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}), + std::move(body1), const_cast<char*>(kSentinel1)); + EXPECT_GT(stream_id, 0); + EXPECT_TRUE(session.want_write()); + EXPECT_EQ(kSentinel1, session.GetStreamUserData(stream_id)); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0)); + + int result = session.Send(); + EXPECT_EQ(0, result); + absl::string_view serialized = visitor.data(); + EXPECT_THAT(serialized, + testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); + serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); + EXPECT_THAT(serialized, + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::HEADERS})); + // No data frame, as body1 was read blocked. + visitor.Clear(); + EXPECT_FALSE(session.want_write()); + + body_ref->EndData(); + EXPECT_TRUE(session.ResumeStream(stream_id)); + EXPECT_TRUE(session.want_write()); + + EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, 0, 0x1, 0)); + + result = session.Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::DATA})); + EXPECT_FALSE(session.want_write()); + + // Stream data is done, so this stream cannot be resumed. + EXPECT_FALSE(session.ResumeStream(stream_id)); + EXPECT_FALSE(session.want_write()); +} + +// This test exercises the case where the connection to the peer is write +// blocked. +TEST(OgHttp2SessionTest, ClientSubmitRequestWithWriteBlock) { + DataSavingVisitor visitor; + OgHttp2Session session( + visitor, OgHttp2Session::Options{.perspective = Perspective::kClient}); + EXPECT_FALSE(session.want_write()); + + const char* kSentinel1 = "arbitrary pointer 1"; + auto body1 = absl::make_unique<TestDataFrameSource>(visitor, true); + body1->AppendPayload("This is an example request body."); + body1->EndData(); + int stream_id = + session.SubmitRequest(ToHeaders({{":method", "POST"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}), + std::move(body1), const_cast<char*>(kSentinel1)); + EXPECT_GT(stream_id, 0); + EXPECT_TRUE(session.want_write()); + EXPECT_EQ(kSentinel1, session.GetStreamUserData(stream_id)); + visitor.set_is_write_blocked(true); + int result = session.Send(); + EXPECT_EQ(0, result); + + EXPECT_THAT(visitor.data(), testing::IsEmpty()); + EXPECT_TRUE(session.want_write()); + visitor.set_is_write_blocked(false); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0)); + EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, _, 0x1, 0)); + + result = session.Send(); + EXPECT_EQ(0, result); + + absl::string_view serialized = visitor.data(); + EXPECT_THAT(serialized, + testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); + serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); + EXPECT_THAT(serialized, + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::HEADERS, + SpdyFrameType::DATA})); + EXPECT_FALSE(session.want_write()); +} + +TEST(OgHttp2SessionTest, ClientStartShutdown) { + DataSavingVisitor visitor; + OgHttp2Session session( + visitor, OgHttp2Session::Options{.perspective = Perspective::kClient}); + + EXPECT_FALSE(session.want_write()); + + // No-op (except for logging) for a client implementation. + session.StartGracefulShutdown(); + EXPECT_FALSE(session.want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + + int result = session.Send(); + EXPECT_EQ(0, result); + + absl::string_view serialized = visitor.data(); + EXPECT_THAT(serialized, + testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); + serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); + EXPECT_THAT(serialized, EqualsFrames({SpdyFrameType::SETTINGS})); } TEST(OgHttp2SessionTest, ServerConstruction) { @@ -101,14 +495,18 @@ TEST(OgHttp2SessionTest, ServerConstruction) { visitor, OgHttp2Session::Options{.perspective = Perspective::kServer}); EXPECT_TRUE(session.want_read()); EXPECT_FALSE(session.want_write()); - EXPECT_EQ(session.GetRemoteWindowSize(), kDefaultInitialStreamWindowSize); + EXPECT_EQ(session.GetRemoteWindowSize(), kInitialFlowControlWindowSize); + EXPECT_TRUE(session.IsServerSession()); + EXPECT_EQ(0, session.GetHighestReceivedStreamId()); } TEST(OgHttp2SessionTest, ServerHandlesFrames) { - testing::StrictMock<MockHttp2Visitor> visitor; + DataSavingVisitor visitor; OgHttp2Session session( visitor, OgHttp2Session::Options{.perspective = Perspective::kServer}); + EXPECT_EQ(0, session.GetHpackDecoderDynamicTableSize()); + const std::string frames = TestFrameSequence() .ClientPreface() .Ping(42) @@ -132,6 +530,8 @@ TEST(OgHttp2SessionTest, ServerHandlesFrames) { .Serialize(); testing::InSequence s; + const char* kSentinel1 = "arbitrary pointer 1"; + // Client preface (empty SETTINGS) EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); EXPECT_CALL(visitor, OnSettingsStart()); @@ -147,7 +547,10 @@ TEST(OgHttp2SessionTest, ServerHandlesFrames) { EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); - EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)) + .WillOnce(testing::InvokeWithoutArgs([&session, kSentinel1]() { + session.SetStreamUserData(1, const_cast<char*>(kSentinel1)); + })); EXPECT_CALL(visitor, OnFrameHeader(1, 4, WINDOW_UPDATE, 0)); EXPECT_CALL(visitor, OnWindowUpdate(1, 2000)); EXPECT_CALL(visitor, OnFrameHeader(1, 25, DATA, 0)); @@ -167,11 +570,410 @@ TEST(OgHttp2SessionTest, ServerHandlesFrames) { EXPECT_CALL(visitor, OnFrameHeader(0, 8, PING, 0)); EXPECT_CALL(visitor, OnPing(47, false)); - const ssize_t result = session.ProcessBytes(frames); + const size_t result = session.ProcessBytes(frames); EXPECT_EQ(frames.size(), result); + EXPECT_EQ(kSentinel1, session.GetStreamUserData(1)); + + // The first stream is active and has received some data. + EXPECT_GT(kInitialFlowControlWindowSize, + session.GetStreamReceiveWindowSize(1)); + // Connection receive window is equivalent to the first stream's. + EXPECT_EQ(session.GetReceiveWindowSize(), + session.GetStreamReceiveWindowSize(1)); + // Receive window upper bound is still the initial value. + EXPECT_EQ(kInitialFlowControlWindowSize, + session.GetStreamReceiveWindowLimit(1)); + + EXPECT_GT(session.GetHpackDecoderDynamicTableSize(), 0); + + // TODO(birenroy): drop stream state when streams are closed. It should no + // longer be possible to set user data. + const char* kSentinel3 = "another arbitrary pointer"; + session.SetStreamUserData(3, const_cast<char*>(kSentinel3)); + EXPECT_EQ(kSentinel3, session.GetStreamUserData(3)); + EXPECT_EQ(session.GetRemoteWindowSize(), - kDefaultInitialStreamWindowSize + 1000); + kInitialFlowControlWindowSize + 1000); + EXPECT_EQ(3, session.GetHighestReceivedStreamId()); + + EXPECT_TRUE(session.want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + + // Some bytes should have been serialized. + int send_result = session.Send(); + EXPECT_EQ(0, send_result); + // Initial SETTINGS, SETTINGS ack. + // TODO(birenroy): automatically queue PING acks. + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, + spdy::SpdyFrameType::SETTINGS})); +} + +// Verifies that a server session enqueues initial SETTINGS before whatever +// frame type is passed to the first invocation of EnqueueFrame(). +TEST(OgHttp2SessionTest, ServerEnqueuesSettingsBeforeOtherFrame) { + DataSavingVisitor visitor; + OgHttp2Session session( + visitor, OgHttp2Session::Options{.perspective = Perspective::kServer}); + EXPECT_FALSE(session.want_write()); + session.EnqueueFrame(absl::make_unique<spdy::SpdyPingIR>(42)); + EXPECT_TRUE(session.want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(PING, 0, _, 0x0, 0)); + + int result = session.Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::PING})); +} + +// Verifies that if the first call to EnqueueFrame() passes a SETTINGS frame, +// the server session will not enqueue an additional SETTINGS frame. +TEST(OgHttp2SessionTest, ServerEnqueuesSettingsOnce) { + DataSavingVisitor visitor; + OgHttp2Session session( + visitor, OgHttp2Session::Options{.perspective = Perspective::kServer}); + EXPECT_FALSE(session.want_write()); + session.EnqueueFrame(absl::make_unique<spdy::SpdySettingsIR>()); + EXPECT_TRUE(session.want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + + int result = session.Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); +} + +TEST(OgHttp2SessionTest, ServerSubmitResponse) { + DataSavingVisitor visitor; + OgHttp2Session session( + visitor, OgHttp2Session::Options{.perspective = Perspective::kServer}); + + EXPECT_FALSE(session.want_write()); + + const std::string frames = TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}, + /*fin=*/true) + .Serialize(); + testing::InSequence s; + + const char* kSentinel1 = "arbitrary pointer 1"; + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + // Stream 1 + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)) + .WillOnce(testing::InvokeWithoutArgs([&session, kSentinel1]() { + session.SetStreamUserData(1, const_cast<char*>(kSentinel1)); + })); + EXPECT_CALL(visitor, OnEndStream(1)); + + const size_t result = session.ProcessBytes(frames); + EXPECT_EQ(frames.size(), result); + + EXPECT_EQ(1, session.GetHighestReceivedStreamId()); + + EXPECT_EQ(0, session.GetHpackEncoderDynamicTableSize()); + + // Server will want to send initial SETTINGS, and a SETTINGS ack. + EXPECT_TRUE(session.want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + + int send_result = session.Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS})); + visitor.Clear(); + + EXPECT_FALSE(session.want_write()); + // A data fin is not sent so that the stream remains open, and the flow + // control state can be verified. + auto body1 = absl::make_unique<TestDataFrameSource>(visitor, false); + body1->AppendPayload("This is an example response body."); + int submit_result = session.SubmitResponse( + 1, + ToHeaders({{":status", "404"}, + {"x-comment", "I have no idea what you're talking about."}}), + std::move(body1)); + EXPECT_EQ(submit_result, 0); + EXPECT_TRUE(session.want_write()); + + // Stream user data should have been set successfully after receiving headers. + EXPECT_EQ(kSentinel1, session.GetStreamUserData(1)); + session.SetStreamUserData(1, nullptr); + EXPECT_EQ(nullptr, session.GetStreamUserData(1)); + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0)); + EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0)); + + send_result = session.Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::HEADERS, SpdyFrameType::DATA})); + EXPECT_FALSE(session.want_write()); + + // Some data was sent, so the remaining send window size should be less than + // the default. + EXPECT_LT(session.GetStreamSendWindowSize(1), kInitialFlowControlWindowSize); + EXPECT_GT(session.GetStreamSendWindowSize(1), 0); + // Send window for a nonexistent stream is not available. + EXPECT_EQ(session.GetStreamSendWindowSize(3), -1); + + EXPECT_GT(session.GetHpackEncoderDynamicTableSize(), 0); +} + +TEST(OgHttp2SessionTest, ServerStartShutdown) { + DataSavingVisitor visitor; + OgHttp2Session session( + visitor, OgHttp2Session::Options{.perspective = Perspective::kServer}); + + EXPECT_FALSE(session.want_write()); + + session.StartGracefulShutdown(); + EXPECT_TRUE(session.want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(GOAWAY, 0, _, 0x0, 0)); + + int result = session.Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::GOAWAY})); +} + +TEST(OgHttp2SessionTest, ServerStartShutdownAfterGoaway) { + DataSavingVisitor visitor; + OgHttp2Session session( + visitor, OgHttp2Session::Options{.perspective = Perspective::kServer}); + + EXPECT_FALSE(session.want_write()); + + auto goaway = absl::make_unique<spdy::SpdyGoAwayIR>( + 1, spdy::ERROR_CODE_NO_ERROR, "and don't come back!"); + session.EnqueueFrame(std::move(goaway)); + EXPECT_TRUE(session.want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(GOAWAY, 0, _, 0x0, 0)); + + int result = session.Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::GOAWAY})); + + // No-op, since a GOAWAY has previously been enqueued. + session.StartGracefulShutdown(); + EXPECT_FALSE(session.want_write()); +} + +// Tests the case where the server queues trailers after the data stream is +// exhausted. +TEST(OgHttp2SessionTest, ServerSendsTrailers) { + DataSavingVisitor visitor; + OgHttp2Session session( + visitor, OgHttp2Session::Options{.perspective = Perspective::kServer}); + + EXPECT_FALSE(session.want_write()); + + const std::string frames = TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}, + /*fin=*/true) + .Serialize(); + testing::InSequence s; + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + // Stream 1 + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnEndStream(1)); + + const size_t result = session.ProcessBytes(frames); + EXPECT_EQ(frames.size(), result); + + // Server will want to send initial SETTINGS, and a SETTINGS ack. + EXPECT_TRUE(session.want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + + int send_result = session.Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS})); + visitor.Clear(); + + EXPECT_FALSE(session.want_write()); + + // The body source must indicate that the end of the body is not the end of + // the stream. + auto body1 = absl::make_unique<TestDataFrameSource>(visitor, false); + body1->AppendPayload("This is an example response body."); + body1->EndData(); + int submit_result = session.SubmitResponse( + 1, ToHeaders({{":status", "200"}, {"x-comment", "Sure, sounds good."}}), + std::move(body1)); + EXPECT_EQ(submit_result, 0); + EXPECT_TRUE(session.want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0)); + EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0)); + + send_result = session.Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::HEADERS, SpdyFrameType::DATA})); + visitor.Clear(); + EXPECT_FALSE(session.want_write()); + + // The body source has been exhausted by the call to Send() above. + // TODO(birenroy): Fix this strange ordering. + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR)); + int trailer_result = session.SubmitTrailer( + 1, ToHeaders({{"final-status", "a-ok"}, + {"x-comment", "trailers sure are cool"}})); + ASSERT_EQ(trailer_result, 0); + EXPECT_TRUE(session.want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x5, 0)); + + send_result = session.Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::HEADERS})); +} + +// Tests the case where the server queues trailers immediately after headers and +// data, and before any writes have taken place. +TEST(OgHttp2SessionTest, ServerQueuesTrailersWithResponse) { + DataSavingVisitor visitor; + OgHttp2Session session( + visitor, OgHttp2Session::Options{.perspective = Perspective::kServer}); + + EXPECT_FALSE(session.want_write()); + + const std::string frames = TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}, + /*fin=*/true) + .Serialize(); + testing::InSequence s; + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + // Stream 1 + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnEndStream(1)); + + const size_t result = session.ProcessBytes(frames); + EXPECT_EQ(frames.size(), result); + + // Server will want to send initial SETTINGS, and a SETTINGS ack. + EXPECT_TRUE(session.want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + + int send_result = session.Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS})); + visitor.Clear(); + + EXPECT_FALSE(session.want_write()); + + // The body source must indicate that the end of the body is not the end of + // the stream. + auto body1 = absl::make_unique<TestDataFrameSource>(visitor, false); + body1->AppendPayload("This is an example response body."); + body1->EndData(); + int submit_result = session.SubmitResponse( + 1, ToHeaders({{":status", "200"}, {"x-comment", "Sure, sounds good."}}), + std::move(body1)); + EXPECT_EQ(submit_result, 0); + EXPECT_TRUE(session.want_write()); + // There has not been a call to Send() yet, so neither headers nor body have + // been written. + int trailer_result = session.SubmitTrailer( + 1, ToHeaders({{"final-status", "a-ok"}, + {"x-comment", "trailers sure are cool"}})); + ASSERT_EQ(trailer_result, 0); + EXPECT_TRUE(session.want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0)); + EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0)); + + // TODO(birenroy): Fix this strange ordering. + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR)); + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x5, 0)); + + send_result = session.Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::HEADERS, SpdyFrameType::DATA, + SpdyFrameType::HEADERS})); } } // namespace test diff --git a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_util.h b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_util.h index e222c96ccfd..3134ff7b004 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_util.h +++ b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_util.h @@ -3,12 +3,14 @@ #include "absl/types/span.h" #include "http2/adapter/http2_protocol.h" +#include "common/platform/api/quiche_export.h" #include "spdy/core/spdy_header_block.h" namespace http2 { namespace adapter { -spdy::SpdyHeaderBlock ToHeaderBlock(absl::Span<const Header> headers); +QUICHE_EXPORT_PRIVATE spdy::SpdyHeaderBlock ToHeaderBlock( + absl::Span<const Header> headers); } // namespace adapter } // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/adapter/recording_http2_visitor.cc b/chromium/net/third_party/quiche/src/http2/adapter/recording_http2_visitor.cc index 78ec423e25e..75f3749e62c 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/recording_http2_visitor.cc +++ b/chromium/net/third_party/quiche/src/http2/adapter/recording_http2_visitor.cc @@ -7,6 +7,11 @@ namespace http2 { namespace adapter { namespace test { +ssize_t RecordingHttp2Visitor::OnReadyToSend(absl::string_view serialized) { + events_.push_back(absl::StrFormat("OnReadyToSend %d", serialized.size())); + return serialized.size(); +} + void RecordingHttp2Visitor::OnConnectionError() { events_.push_back("OnConnectionError"); } @@ -36,15 +41,16 @@ void RecordingHttp2Visitor::OnSettingsAck() { events_.push_back("OnSettingsAck"); } -void RecordingHttp2Visitor::OnBeginHeadersForStream(Http2StreamId stream_id) { +bool RecordingHttp2Visitor::OnBeginHeadersForStream(Http2StreamId stream_id) { events_.push_back(absl::StrFormat("OnBeginHeadersForStream %d", stream_id)); + return true; } -void RecordingHttp2Visitor::OnHeaderForStream(Http2StreamId stream_id, - absl::string_view name, - absl::string_view value) { +Http2VisitorInterface::OnHeaderResult RecordingHttp2Visitor::OnHeaderForStream( + Http2StreamId stream_id, absl::string_view name, absl::string_view value) { events_.push_back( absl::StrFormat("OnHeaderForStream %d %s %s", stream_id, name, value)); + return HEADER_OK; } void RecordingHttp2Visitor::OnEndHeadersForStream(Http2StreamId stream_id) { @@ -112,11 +118,32 @@ void RecordingHttp2Visitor::OnWindowUpdate(Http2StreamId stream_id, absl::StrFormat("OnWindowUpdate %d %d", stream_id, window_increment)); } -void RecordingHttp2Visitor::OnReadyToSendDataForStream(Http2StreamId stream_id, - char* destination_buffer, - size_t length, - ssize_t* written, - bool* end_stream) { +int RecordingHttp2Visitor::OnBeforeFrameSent(uint8_t frame_type, + Http2StreamId stream_id, + size_t length, uint8_t flags) { + events_.push_back(absl::StrFormat("OnBeforeFrameSent %d %d %d %d", frame_type, + stream_id, length, flags)); + return 0; +} + +int RecordingHttp2Visitor::OnFrameSent(uint8_t frame_type, + Http2StreamId stream_id, size_t length, + uint8_t flags, uint32_t error_code) { + events_.push_back(absl::StrFormat("OnFrameSent %d %d %d %d %d", frame_type, + stream_id, length, flags, error_code)); + return 0; +} + +bool RecordingHttp2Visitor::OnInvalidFrame(Http2StreamId stream_id, + int error_code) { + events_.push_back( + absl::StrFormat("OnInvalidFrame %d %d", stream_id, error_code)); + return true; +} + +void RecordingHttp2Visitor::OnReadyToSendDataForStream( + Http2StreamId stream_id, char* /*destination_buffer*/, size_t length, + ssize_t* /*written*/, bool* /*end_stream*/) { // TODO(b/181586191): Revisit this. The visitor is expected to write to the // |destination_buffer| and set the other pointer values appropriately. events_.push_back( @@ -124,10 +151,8 @@ void RecordingHttp2Visitor::OnReadyToSendDataForStream(Http2StreamId stream_id, } void RecordingHttp2Visitor::OnReadyToSendMetadataForStream( - Http2StreamId stream_id, - char* buffer, - size_t length, - ssize_t* written) { + Http2StreamId stream_id, char* /*buffer*/, size_t length, + ssize_t* /*written*/) { // TODO(b/181586191): Revisit this. The visitor is expected to write to the // |buffer| and set *written appropriately. events_.push_back(absl::StrFormat("OnReadyToSendMetadataForStream %d %d", @@ -146,8 +171,13 @@ void RecordingHttp2Visitor::OnMetadataForStream(Http2StreamId stream_id, absl::StrFormat("OnMetadataForStream %d %s", stream_id, metadata)); } -void RecordingHttp2Visitor::OnMetadataEndForStream(Http2StreamId stream_id) { +bool RecordingHttp2Visitor::OnMetadataEndForStream(Http2StreamId stream_id) { events_.push_back(absl::StrFormat("OnMetadataEndForStream %d", stream_id)); + return true; +} + +void RecordingHttp2Visitor::OnErrorDebug(absl::string_view message) { + events_.push_back(absl::StrFormat("OnErrorDebug %s", message)); } } // namespace test diff --git a/chromium/net/third_party/quiche/src/http2/adapter/recording_http2_visitor.h b/chromium/net/third_party/quiche/src/http2/adapter/recording_http2_visitor.h index 452b45185cb..25b92935b35 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/recording_http2_visitor.h +++ b/chromium/net/third_party/quiche/src/http2/adapter/recording_http2_visitor.h @@ -5,6 +5,7 @@ #include <string> #include "http2/adapter/http2_visitor_interface.h" +#include "common/platform/api/quiche_export.h" #include "common/platform/api/quiche_test.h" namespace http2 { @@ -12,12 +13,13 @@ namespace adapter { namespace test { // A visitor implementation that records the sequence of callbacks it receives. -class RecordingHttp2Visitor : public Http2VisitorInterface { +class QUICHE_NO_EXPORT RecordingHttp2Visitor : public Http2VisitorInterface { public: using Event = std::string; using EventSequence = std::list<Event>; // From Http2VisitorInterface + ssize_t OnReadyToSend(absl::string_view serialized) override; void OnConnectionError() override; void OnFrameHeader(Http2StreamId stream_id, size_t length, @@ -27,10 +29,10 @@ class RecordingHttp2Visitor : public Http2VisitorInterface { void OnSetting(Http2Setting setting) override; void OnSettingsEnd() override; void OnSettingsAck() override; - void OnBeginHeadersForStream(Http2StreamId stream_id) override; - void OnHeaderForStream(Http2StreamId stream_id, - absl::string_view name, - absl::string_view value) override; + bool OnBeginHeadersForStream(Http2StreamId stream_id) override; + OnHeaderResult OnHeaderForStream(Http2StreamId stream_id, + absl::string_view name, + absl::string_view value) override; void OnEndHeadersForStream(Http2StreamId stream_id) override; void OnBeginDataForStream(Http2StreamId stream_id, size_t payload_length) override; @@ -51,6 +53,11 @@ class RecordingHttp2Visitor : public Http2VisitorInterface { Http2ErrorCode error_code, absl::string_view opaque_data) override; void OnWindowUpdate(Http2StreamId stream_id, int window_increment) override; + int OnBeforeFrameSent(uint8_t frame_type, Http2StreamId stream_id, + size_t length, uint8_t flags) override; + int OnFrameSent(uint8_t frame_type, Http2StreamId stream_id, size_t length, + uint8_t flags, uint32_t error_code) override; + bool OnInvalidFrame(Http2StreamId stream_id, int error_code) override; void OnReadyToSendDataForStream(Http2StreamId stream_id, char* destination_buffer, size_t length, @@ -64,7 +71,8 @@ class RecordingHttp2Visitor : public Http2VisitorInterface { size_t payload_length) override; void OnMetadataForStream(Http2StreamId stream_id, absl::string_view metadata) override; - void OnMetadataEndForStream(Http2StreamId stream_id) override; + bool OnMetadataEndForStream(Http2StreamId stream_id) override; + void OnErrorDebug(absl::string_view message) override; const EventSequence& GetEventSequence() const { return events_; } void Clear() { events_.clear(); } diff --git a/chromium/net/third_party/quiche/src/http2/adapter/test_frame_sequence.cc b/chromium/net/third_party/quiche/src/http2/adapter/test_frame_sequence.cc index 7be368ea6a5..bb1517d3d73 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/test_frame_sequence.cc +++ b/chromium/net/third_party/quiche/src/http2/adapter/test_frame_sequence.cc @@ -2,6 +2,7 @@ #include "http2/adapter/http2_util.h" #include "http2/adapter/oghttp2_util.h" +#include "spdy/core/hpack/hpack_encoder.h" #include "spdy/core/spdy_framer.h" namespace http2 { @@ -11,8 +12,9 @@ namespace test { std::vector<const Header> ToHeaders( absl::Span<const std::pair<absl::string_view, absl::string_view>> headers) { std::vector<const Header> out; - for (auto [name, value] : headers) { - out.push_back(std::make_pair(HeaderRep(name), HeaderRep(value))); + for (const auto& header : headers) { + out.push_back( + std::make_pair(HeaderRep(header.first), HeaderRep(header.second))); } return out; } @@ -88,24 +90,45 @@ TestFrameSequence& TestFrameSequence::GoAway(Http2StreamId last_good_stream_id, TestFrameSequence& TestFrameSequence::Headers( Http2StreamId stream_id, absl::Span<const std::pair<absl::string_view, absl::string_view>> headers, - bool fin) { - return Headers(stream_id, ToHeaders(headers), fin); + bool fin, bool add_continuation) { + return Headers(stream_id, ToHeaders(headers), fin, add_continuation); } TestFrameSequence& TestFrameSequence::Headers(Http2StreamId stream_id, spdy::Http2HeaderBlock block, - bool fin) { - auto headers = - absl::make_unique<spdy::SpdyHeadersIR>(stream_id, std::move(block)); - headers->set_fin(fin); - frames_.push_back(std::move(headers)); + bool fin, bool add_continuation) { + if (add_continuation) { + // The normal intermediate representations don't allow you to represent a + // nonterminal HEADERS frame explicitly, so we'll need to use + // SpdyUnknownIRs. For simplicity, and in order not to mess up HPACK state, + // the payload will be uncompressed. + std::string encoded_block; + spdy::HpackEncoder encoder; + encoder.DisableCompression(); + encoder.EncodeHeaderSet(block, &encoded_block); + const size_t pos = encoded_block.size() / 2; + const uint8_t flags = fin ? 0x1 : 0x0; + frames_.push_back(absl::make_unique<spdy::SpdyUnknownIR>( + stream_id, static_cast<uint8_t>(spdy::SpdyFrameType::HEADERS), flags, + encoded_block.substr(0, pos))); + + auto continuation = absl::make_unique<spdy::SpdyContinuationIR>(stream_id); + continuation->set_end_headers(true); + continuation->take_encoding(encoded_block.substr(pos)); + frames_.push_back(std::move(continuation)); + } else { + auto headers = + absl::make_unique<spdy::SpdyHeadersIR>(stream_id, std::move(block)); + headers->set_fin(fin); + frames_.push_back(std::move(headers)); + } return *this; } TestFrameSequence& TestFrameSequence::Headers(Http2StreamId stream_id, absl::Span<const Header> headers, - bool fin) { - return Headers(stream_id, ToHeaderBlock(headers), fin); + bool fin, bool add_continuation) { + return Headers(stream_id, ToHeaderBlock(headers), fin, add_continuation); } TestFrameSequence& TestFrameSequence::WindowUpdate(Http2StreamId stream_id, @@ -124,6 +147,25 @@ TestFrameSequence& TestFrameSequence::Priority(Http2StreamId stream_id, return *this; } +TestFrameSequence& TestFrameSequence::Metadata(Http2StreamId stream_id, + absl::string_view payload, + bool multiple_frames) { + const std::string encoded_payload = MetadataBlockForPayload(payload); + if (multiple_frames) { + const size_t pos = encoded_payload.size() / 2; + frames_.push_back(absl::make_unique<spdy::SpdyUnknownIR>( + stream_id, kMetadataFrameType, 0, encoded_payload.substr(0, pos))); + frames_.push_back(absl::make_unique<spdy::SpdyUnknownIR>( + stream_id, kMetadataFrameType, kMetadataEndFlag, + encoded_payload.substr(pos))); + } else { + frames_.push_back(absl::make_unique<spdy::SpdyUnknownIR>( + stream_id, kMetadataFrameType, kMetadataEndFlag, + std::move(encoded_payload))); + } + return *this; +} + std::string TestFrameSequence::Serialize() { std::string result; if (!preface_.empty()) { @@ -137,6 +179,18 @@ std::string TestFrameSequence::Serialize() { return result; } +std::string TestFrameSequence::MetadataBlockForPayload( + absl::string_view payload) { + // Encode the payload using a header block. + spdy::SpdyHeaderBlock block; + block["example-payload"] = payload; + spdy::HpackEncoder encoder; + encoder.DisableCompression(); + std::string encoded_payload; + encoder.EncodeHeaderSet(block, &encoded_payload); + return encoded_payload; +} + } // namespace test } // namespace adapter } // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/adapter/test_frame_sequence.h b/chromium/net/third_party/quiche/src/http2/adapter/test_frame_sequence.h index cc5e8b5ff57..99740d38f9e 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/test_frame_sequence.h +++ b/chromium/net/third_party/quiche/src/http2/adapter/test_frame_sequence.h @@ -6,16 +6,17 @@ #include <vector> #include "http2/adapter/http2_protocol.h" +#include "common/platform/api/quiche_export.h" #include "spdy/core/spdy_protocol.h" namespace http2 { namespace adapter { namespace test { -std::vector<const Header> ToHeaders( +std::vector<const Header> QUICHE_NO_EXPORT ToHeaders( absl::Span<const std::pair<absl::string_view, absl::string_view>> headers); -class TestFrameSequence { +class QUICHE_NO_EXPORT TestFrameSequence { public: TestFrameSequence() = default; @@ -36,21 +37,26 @@ class TestFrameSequence { TestFrameSequence& Headers( Http2StreamId stream_id, absl::Span<const std::pair<absl::string_view, absl::string_view>> headers, - bool fin = false); + bool fin = false, bool add_continuation = false); TestFrameSequence& Headers(Http2StreamId stream_id, - spdy::Http2HeaderBlock block, - bool fin = false); + spdy::Http2HeaderBlock block, bool fin = false, + bool add_continuation = false); TestFrameSequence& Headers(Http2StreamId stream_id, - absl::Span<const Header> headers, - bool fin = false); + absl::Span<const Header> headers, bool fin = false, + bool add_continuation = false); TestFrameSequence& WindowUpdate(Http2StreamId stream_id, int32_t delta); TestFrameSequence& Priority(Http2StreamId stream_id, Http2StreamId parent_stream_id, int weight, bool exclusive); + TestFrameSequence& Metadata(Http2StreamId stream_id, + absl::string_view payload, + bool multiple_frames = false); std::string Serialize(); + static std::string MetadataBlockForPayload(absl::string_view); + private: std::string preface_; std::vector<std::unique_ptr<spdy::SpdyFrameIR>> frames_; diff --git a/chromium/net/third_party/quiche/src/http2/adapter/test_utils.cc b/chromium/net/third_party/quiche/src/http2/adapter/test_utils.cc index 315a28f0717..b8acddbb87f 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/test_utils.cc +++ b/chromium/net/third_party/quiche/src/http2/adapter/test_utils.cc @@ -1,28 +1,123 @@ #include "http2/adapter/test_utils.h" -#include "http2/adapter/nghttp2_util.h" +#include <ostream> + +#include "absl/strings/str_format.h" #include "common/quiche_endian.h" +#include "spdy/core/hpack/hpack_encoder.h" #include "spdy/core/spdy_frame_reader.h" namespace http2 { namespace adapter { namespace test { + +TestDataFrameSource::TestDataFrameSource(Http2VisitorInterface& visitor, + bool has_fin) + : visitor_(visitor), has_fin_(has_fin) {} + +void TestDataFrameSource::AppendPayload(absl::string_view payload) { + QUICHE_CHECK(!end_data_); + if (!payload.empty()) { + payload_fragments_.push_back(std::string(payload)); + current_fragment_ = payload_fragments_.front(); + } +} + +void TestDataFrameSource::EndData() { end_data_ = true; } + +std::pair<ssize_t, bool> TestDataFrameSource::SelectPayloadLength( + size_t max_length) { + // The stream is done if there's no more data, or if |max_length| is at least + // as large as the remaining data. + const bool end_data = end_data_ && (current_fragment_.empty() || + (payload_fragments_.size() == 1 && + max_length >= current_fragment_.size())); + const ssize_t length = std::min(max_length, current_fragment_.size()); + return {length, end_data}; +} + +bool TestDataFrameSource::Send(absl::string_view frame_header, + size_t payload_length) { + QUICHE_LOG_IF(DFATAL, payload_length > current_fragment_.size()) + << "payload_length: " << payload_length + << " current_fragment_size: " << current_fragment_.size(); + const std::string concatenated = + absl::StrCat(frame_header, current_fragment_.substr(0, payload_length)); + const ssize_t result = visitor_.OnReadyToSend(concatenated); + if (result < 0) { + // Write encountered error. + visitor_.OnConnectionError(); + current_fragment_ = {}; + payload_fragments_.clear(); + return false; + } else if (result == 0) { + // Write blocked. + return false; + } else if (static_cast<const size_t>(result) < concatenated.size()) { + // Probably need to handle this better within this test class. + QUICHE_LOG(DFATAL) + << "DATA frame not fully flushed. Connection will be corrupt!"; + visitor_.OnConnectionError(); + current_fragment_ = {}; + payload_fragments_.clear(); + return false; + } + if (payload_length > 0) { + current_fragment_.remove_prefix(payload_length); + } + if (current_fragment_.empty() && !payload_fragments_.empty()) { + payload_fragments_.erase(payload_fragments_.begin()); + if (!payload_fragments_.empty()) { + current_fragment_ = payload_fragments_.front(); + } + } + return true; +} + +std::string EncodeHeaders(const spdy::SpdyHeaderBlock& entries) { + spdy::HpackEncoder encoder; + encoder.DisableCompression(); + std::string result; + QUICHE_CHECK(encoder.EncodeHeaderSet(entries, &result)); + return result; +} + +TestMetadataSource::TestMetadataSource(const spdy::SpdyHeaderBlock& entries) + : encoded_entries_(EncodeHeaders(entries)) { + remaining_ = encoded_entries_; +} + +std::pair<ssize_t, bool> TestMetadataSource::Pack(uint8_t* dest, + size_t dest_len) { + const size_t copied = std::min(dest_len, remaining_.size()); + std::memcpy(dest, remaining_.data(), copied); + remaining_.remove_prefix(copied); + return std::make_pair(copied, remaining_.empty()); +} + namespace { using TypeAndOptionalLength = std::pair<spdy::SpdyFrameType, absl::optional<size_t>>; -std::vector<std::pair<const char*, std::string>> LogFriendly( +std::ostream& operator<<( + std::ostream& os, const std::vector<TypeAndOptionalLength>& types_and_lengths) { - std::vector<std::pair<const char*, std::string>> out; - out.reserve(types_and_lengths.size()); - for (const auto type_and_length : types_and_lengths) { - out.push_back({spdy::FrameTypeToString(type_and_length.first), - type_and_length.second - ? absl::StrCat(type_and_length.second.value()) - : "<unspecified>"}); + for (const auto& type_and_length : types_and_lengths) { + os << "(" << spdy::FrameTypeToString(type_and_length.first) << ", " + << (type_and_length.second ? absl::StrCat(type_and_length.second.value()) + : "<unspecified>") + << ") "; + } + return os; +} + +std::string FrameTypeToString(uint8_t frame_type) { + if (spdy::IsDefinedFrameType(frame_type)) { + return spdy::FrameTypeToString(spdy::ParseFrameType(frame_type)); + } else { + return absl::StrFormat("0x%x", static_cast<int>(frame_type)); } - return out; } // Custom gMock matcher, used to implement EqualsFrames(). @@ -75,16 +170,8 @@ class SpdyControlFrameMatcher return false; } - if (!spdy::IsDefinedFrameType(raw_type)) { - *listener << "; expected type " << FrameTypeToString(expected_type) - << " but raw type " << static_cast<int>(raw_type) - << " is not a defined frame type!"; - return false; - } - - spdy::SpdyFrameType actual_type = spdy::ParseFrameType(raw_type); - if (actual_type != expected_type) { - *listener << "; actual type: " << FrameTypeToString(actual_type) + if (raw_type != static_cast<uint8_t>(expected_type)) { + *listener << "; actual type: " << FrameTypeToString(raw_type) << " but expected type: " << FrameTypeToString(expected_type); return false; } @@ -96,358 +183,18 @@ class SpdyControlFrameMatcher void DescribeTo(std::ostream* os) const override { *os << "Data contains frames of types in sequence " - << LogFriendly(expected_types_and_lengths_); + << expected_types_and_lengths_; } void DescribeNegationTo(std::ostream* os) const override { *os << "Data does not contain frames of types in sequence " - << LogFriendly(expected_types_and_lengths_); + << expected_types_and_lengths_; } private: const std::vector<TypeAndOptionalLength> expected_types_and_lengths_; }; -// Custom gMock matcher, used to implement HasFrameHeader(). -class FrameHeaderMatcher - : public testing::MatcherInterface<const nghttp2_frame_hd*> { - public: - FrameHeaderMatcher(int32_t streamid, - uint8_t type, - const testing::Matcher<int> flags) - : stream_id_(streamid), type_(type), flags_(flags) {} - - bool MatchAndExplain(const nghttp2_frame_hd* frame, - testing::MatchResultListener* listener) const override { - bool matched = true; - if (stream_id_ != frame->stream_id) { - *listener << "; expected stream " << stream_id_ << ", saw " - << frame->stream_id; - matched = false; - } - if (type_ != frame->type) { - *listener << "; expected frame type " << type_ << ", saw " - << static_cast<int>(frame->type); - matched = false; - } - if (!flags_.MatchAndExplain(frame->flags, listener)) { - matched = false; - } - return matched; - } - - void DescribeTo(std::ostream* os) const override { - *os << "contains a frame header with stream " << stream_id_ << ", type " - << type_ << ", "; - flags_.DescribeTo(os); - } - - void DescribeNegationTo(std::ostream* os) const override { - *os << "does not contain a frame header with stream " << stream_id_ - << ", type " << type_ << ", "; - flags_.DescribeNegationTo(os); - } - - private: - const int32_t stream_id_; - const int type_; - const testing::Matcher<int> flags_; -}; - -class DataMatcher : public testing::MatcherInterface<const nghttp2_frame*> { - public: - DataMatcher(const testing::Matcher<uint32_t> stream_id, - const testing::Matcher<size_t> length, - const testing::Matcher<int> flags) - : stream_id_(stream_id), length_(length), flags_(flags) {} - - bool MatchAndExplain(const nghttp2_frame* frame, - testing::MatchResultListener* listener) const override { - if (frame->hd.type != NGHTTP2_DATA) { - *listener << "; expected DATA frame, saw frame of type " - << static_cast<int>(frame->hd.type); - return false; - } - bool matched = true; - if (!stream_id_.MatchAndExplain(frame->hd.stream_id, listener)) { - matched = false; - } - if (!length_.MatchAndExplain(frame->hd.length, listener)) { - matched = false; - } - if (!flags_.MatchAndExplain(frame->hd.flags, listener)) { - matched = false; - } - return matched; - } - - void DescribeTo(std::ostream* os) const override { - *os << "contains a DATA frame, "; - stream_id_.DescribeTo(os); - length_.DescribeTo(os); - flags_.DescribeTo(os); - } - - void DescribeNegationTo(std::ostream* os) const override { - *os << "does not contain a DATA frame, "; - stream_id_.DescribeNegationTo(os); - length_.DescribeNegationTo(os); - flags_.DescribeNegationTo(os); - } - - private: - const testing::Matcher<uint32_t> stream_id_; - const testing::Matcher<size_t> length_; - const testing::Matcher<int> flags_; -}; - -class HeadersMatcher : public testing::MatcherInterface<const nghttp2_frame*> { - public: - HeadersMatcher(const testing::Matcher<uint32_t> stream_id, - const testing::Matcher<int> flags, - const testing::Matcher<int> category) - : stream_id_(stream_id), flags_(flags), category_(category) {} - - bool MatchAndExplain(const nghttp2_frame* frame, - testing::MatchResultListener* listener) const override { - if (frame->hd.type != NGHTTP2_HEADERS) { - *listener << "; expected HEADERS frame, saw frame of type " - << static_cast<int>(frame->hd.type); - return false; - } - bool matched = true; - if (!stream_id_.MatchAndExplain(frame->hd.stream_id, listener)) { - matched = false; - } - if (!flags_.MatchAndExplain(frame->hd.flags, listener)) { - matched = false; - } - if (!category_.MatchAndExplain(frame->headers.cat, listener)) { - matched = false; - } - return matched; - } - - void DescribeTo(std::ostream* os) const override { - *os << "contains a HEADERS frame, "; - stream_id_.DescribeTo(os); - flags_.DescribeTo(os); - category_.DescribeTo(os); - } - - void DescribeNegationTo(std::ostream* os) const override { - *os << "does not contain a HEADERS frame, "; - stream_id_.DescribeNegationTo(os); - flags_.DescribeNegationTo(os); - category_.DescribeNegationTo(os); - } - - private: - const testing::Matcher<uint32_t> stream_id_; - const testing::Matcher<int> flags_; - const testing::Matcher<int> category_; -}; - -class RstStreamMatcher - : public testing::MatcherInterface<const nghttp2_frame*> { - public: - RstStreamMatcher(const testing::Matcher<uint32_t> stream_id, - const testing::Matcher<uint32_t> error_code) - : stream_id_(stream_id), error_code_(error_code) {} - - bool MatchAndExplain(const nghttp2_frame* frame, - testing::MatchResultListener* listener) const override { - if (frame->hd.type != NGHTTP2_RST_STREAM) { - *listener << "; expected RST_STREAM frame, saw frame of type " - << static_cast<int>(frame->hd.type); - return false; - } - bool matched = true; - if (!stream_id_.MatchAndExplain(frame->hd.stream_id, listener)) { - matched = false; - } - if (!error_code_.MatchAndExplain(frame->rst_stream.error_code, listener)) { - matched = false; - } - return matched; - } - - void DescribeTo(std::ostream* os) const override { - *os << "contains a RST_STREAM frame, "; - stream_id_.DescribeTo(os); - error_code_.DescribeTo(os); - } - - void DescribeNegationTo(std::ostream* os) const override { - *os << "does not contain a RST_STREAM frame, "; - stream_id_.DescribeNegationTo(os); - error_code_.DescribeNegationTo(os); - } - - private: - const testing::Matcher<uint32_t> stream_id_; - const testing::Matcher<uint32_t> error_code_; -}; - -class SettingsMatcher : public testing::MatcherInterface<const nghttp2_frame*> { - public: - SettingsMatcher(const testing::Matcher<std::vector<Http2Setting>> values) - : values_(values) {} - - bool MatchAndExplain(const nghttp2_frame* frame, - testing::MatchResultListener* listener) const override { - if (frame->hd.type != NGHTTP2_SETTINGS) { - *listener << "; expected SETTINGS frame, saw frame of type " - << static_cast<int>(frame->hd.type); - return false; - } - std::vector<Http2Setting> settings; - settings.reserve(frame->settings.niv); - for (int i = 0; i < frame->settings.niv; ++i) { - const auto& p = frame->settings.iv[i]; - settings.push_back({static_cast<uint16_t>(p.settings_id), p.value}); - } - return values_.MatchAndExplain(settings, listener); - } - - void DescribeTo(std::ostream* os) const override { - *os << "contains a SETTINGS frame, "; - values_.DescribeTo(os); - } - - void DescribeNegationTo(std::ostream* os) const override { - *os << "does not contain a SETTINGS frame, "; - values_.DescribeNegationTo(os); - } - - private: - const testing::Matcher<std::vector<Http2Setting>> values_; -}; - -class PingMatcher : public testing::MatcherInterface<const nghttp2_frame*> { - public: - PingMatcher(const testing::Matcher<uint64_t> id, bool is_ack) - : id_(id), is_ack_(is_ack) {} - - bool MatchAndExplain(const nghttp2_frame* frame, - testing::MatchResultListener* listener) const override { - if (frame->hd.type != NGHTTP2_PING) { - *listener << "; expected PING frame, saw frame of type " - << static_cast<int>(frame->hd.type); - return false; - } - bool matched = true; - bool frame_ack = frame->hd.flags & NGHTTP2_FLAG_ACK; - if (is_ack_ != frame_ack) { - *listener << "; expected is_ack=" << is_ack_ << ", saw " << frame_ack; - matched = false; - } - uint64_t data; - std::memcpy(&data, frame->ping.opaque_data, sizeof(data)); - data = quiche::QuicheEndian::HostToNet64(data); - if (!id_.MatchAndExplain(data, listener)) { - matched = false; - } - return matched; - } - - void DescribeTo(std::ostream* os) const override { - *os << "contains a PING frame, "; - id_.DescribeTo(os); - } - - void DescribeNegationTo(std::ostream* os) const override { - *os << "does not contain a PING frame, "; - id_.DescribeNegationTo(os); - } - - private: - const testing::Matcher<uint64_t> id_; - const bool is_ack_; -}; - -class GoAwayMatcher : public testing::MatcherInterface<const nghttp2_frame*> { - public: - GoAwayMatcher(const testing::Matcher<uint32_t> last_stream_id, - const testing::Matcher<uint32_t> error_code, - const testing::Matcher<absl::string_view> opaque_data) - : last_stream_id_(last_stream_id), - error_code_(error_code), - opaque_data_(opaque_data) {} - - bool MatchAndExplain(const nghttp2_frame* frame, - testing::MatchResultListener* listener) const override { - if (frame->hd.type != NGHTTP2_GOAWAY) { - *listener << "; expected GOAWAY frame, saw frame of type " - << static_cast<int>(frame->hd.type); - return false; - } - bool matched = true; - if (!last_stream_id_.MatchAndExplain(frame->goaway.last_stream_id, - listener)) { - matched = false; - } - if (!error_code_.MatchAndExplain(frame->goaway.error_code, listener)) { - matched = false; - } - auto opaque_data = - ToStringView(frame->goaway.opaque_data, frame->goaway.opaque_data_len); - if (!opaque_data_.MatchAndExplain(opaque_data, listener)) { - matched = false; - } - return matched; - } - - void DescribeTo(std::ostream* os) const override { - *os << "contains a GOAWAY frame, "; - last_stream_id_.DescribeTo(os); - error_code_.DescribeTo(os); - opaque_data_.DescribeTo(os); - } - - void DescribeNegationTo(std::ostream* os) const override { - *os << "does not contain a GOAWAY frame, "; - last_stream_id_.DescribeNegationTo(os); - error_code_.DescribeNegationTo(os); - opaque_data_.DescribeNegationTo(os); - } - - private: - const testing::Matcher<uint32_t> last_stream_id_; - const testing::Matcher<uint32_t> error_code_; - const testing::Matcher<absl::string_view> opaque_data_; -}; - -class WindowUpdateMatcher - : public testing::MatcherInterface<const nghttp2_frame*> { - public: - WindowUpdateMatcher(const testing::Matcher<uint32_t> delta) : delta_(delta) {} - - bool MatchAndExplain(const nghttp2_frame* frame, - testing::MatchResultListener* listener) const override { - if (frame->hd.type != NGHTTP2_WINDOW_UPDATE) { - *listener << "; expected WINDOW_UPDATE frame, saw frame of type " - << static_cast<int>(frame->hd.type); - return false; - } - return delta_.MatchAndExplain(frame->window_update.window_size_increment, - listener); - } - - void DescribeTo(std::ostream* os) const override { - *os << "contains a WINDOW_UPDATE frame, "; - delta_.DescribeTo(os); - } - - void DescribeNegationTo(std::ostream* os) const override { - *os << "does not contain a WINDOW_UPDATE frame, "; - delta_.DescribeNegationTo(os); - } - - private: - const testing::Matcher<uint32_t> delta_; -}; - } // namespace testing::Matcher<absl::string_view> EqualsFrames( @@ -467,61 +214,6 @@ testing::Matcher<absl::string_view> EqualsFrames( return MakeMatcher(new SpdyControlFrameMatcher(std::move(types_and_lengths))); } -testing::Matcher<const nghttp2_frame_hd*> HasFrameHeader( - uint32_t streamid, - uint8_t type, - const testing::Matcher<int> flags) { - return MakeMatcher(new FrameHeaderMatcher(streamid, type, flags)); -} - -testing::Matcher<const nghttp2_frame*> IsData( - const testing::Matcher<uint32_t> stream_id, - const testing::Matcher<size_t> length, - const testing::Matcher<int> flags) { - return MakeMatcher(new DataMatcher(stream_id, length, flags)); -} - -testing::Matcher<const nghttp2_frame*> IsHeaders( - const testing::Matcher<uint32_t> stream_id, - const testing::Matcher<int> flags, - const testing::Matcher<int> category) { - return MakeMatcher(new HeadersMatcher(stream_id, flags, category)); -} - -testing::Matcher<const nghttp2_frame*> IsRstStream( - const testing::Matcher<uint32_t> stream_id, - const testing::Matcher<uint32_t> error_code) { - return MakeMatcher(new RstStreamMatcher(stream_id, error_code)); -} - -testing::Matcher<const nghttp2_frame*> IsSettings( - const testing::Matcher<std::vector<Http2Setting>> values) { - return MakeMatcher(new SettingsMatcher(values)); -} - -testing::Matcher<const nghttp2_frame*> IsPing( - const testing::Matcher<uint64_t> id) { - return MakeMatcher(new PingMatcher(id, false)); -} - -testing::Matcher<const nghttp2_frame*> IsPingAck( - const testing::Matcher<uint64_t> id) { - return MakeMatcher(new PingMatcher(id, true)); -} - -testing::Matcher<const nghttp2_frame*> IsGoAway( - const testing::Matcher<uint32_t> last_stream_id, - const testing::Matcher<uint32_t> error_code, - const testing::Matcher<absl::string_view> opaque_data) { - return MakeMatcher( - new GoAwayMatcher(last_stream_id, error_code, opaque_data)); -} - -testing::Matcher<const nghttp2_frame*> IsWindowUpdate( - const testing::Matcher<uint32_t> delta) { - return MakeMatcher(new WindowUpdateMatcher(delta)); -} - } // namespace test } // namespace adapter } // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/adapter/test_utils.h b/chromium/net/third_party/quiche/src/http2/adapter/test_utils.h index ef1ae29a650..d92617fa3c9 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/test_utils.h +++ b/chromium/net/third_party/quiche/src/http2/adapter/test_utils.h @@ -4,26 +4,99 @@ #include <string> #include <vector> +#include "absl/container/flat_hash_map.h" #include "absl/strings/string_view.h" +#include "http2/adapter/data_source.h" #include "http2/adapter/http2_protocol.h" #include "http2/adapter/mock_http2_visitor.h" -#include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h" +#include "common/platform/api/quiche_export.h" #include "common/platform/api/quiche_test.h" +#include "spdy/core/spdy_header_block.h" #include "spdy/core/spdy_protocol.h" namespace http2 { namespace adapter { namespace test { -class DataSavingVisitor : public testing::StrictMock<MockHttp2Visitor> { +class QUICHE_NO_EXPORT DataSavingVisitor + : public testing::StrictMock<MockHttp2Visitor> { public: - void Save(absl::string_view data) { absl::StrAppend(&data_, data); } + ssize_t OnReadyToSend(absl::string_view data) override { + if (is_write_blocked_) { + return kSendBlocked; + } + const size_t to_accept = std::min(send_limit_, data.size()); + if (to_accept == 0) { + return kSendBlocked; + } + absl::StrAppend(&data_, data.substr(0, to_accept)); + return to_accept; + } + + void OnMetadataForStream(Http2StreamId stream_id, + absl::string_view metadata) override { + testing::StrictMock<MockHttp2Visitor>::OnMetadataForStream(stream_id, + metadata); + auto result = + metadata_map_.try_emplace(stream_id, std::vector<std::string>()); + result.first->second.push_back(std::string(metadata)); + } + + const std::vector<std::string> GetMetadata(Http2StreamId stream_id) { + auto it = metadata_map_.find(stream_id); + if (it == metadata_map_.end()) { + return {}; + } else { + return it->second; + } + } const std::string& data() { return data_; } void Clear() { data_.clear(); } + void set_send_limit(size_t limit) { send_limit_ = limit; } + + bool is_write_blocked() const { return is_write_blocked_; } + void set_is_write_blocked(bool value) { is_write_blocked_ = value; } + private: std::string data_; + absl::flat_hash_map<Http2StreamId, std::vector<std::string>> metadata_map_; + size_t send_limit_ = std::numeric_limits<size_t>::max(); + bool is_write_blocked_ = false; +}; + +// A test DataFrameSource. Starts out in the empty, blocked state. +class QUICHE_NO_EXPORT TestDataFrameSource : public DataFrameSource { + public: + TestDataFrameSource(Http2VisitorInterface& visitor, bool has_fin); + + void AppendPayload(absl::string_view payload); + void EndData(); + + std::pair<ssize_t, bool> SelectPayloadLength(size_t max_length) override; + bool Send(absl::string_view frame_header, size_t payload_length) override; + bool send_fin() const override { return has_fin_; } + + private: + Http2VisitorInterface& visitor_; + std::vector<std::string> payload_fragments_; + absl::string_view current_fragment_; + // Whether the stream should end with the final frame of data. + const bool has_fin_; + // Whether |payload_fragments_| contains the final segment of data. + bool end_data_ = false; +}; + +class QUICHE_NO_EXPORT TestMetadataSource : public MetadataSource { + public: + explicit TestMetadataSource(const spdy::SpdyHeaderBlock& entries); + + std::pair<ssize_t, bool> Pack(uint8_t* dest, size_t dest_len) override; + + private: + const std::string encoded_entries_; + absl::string_view remaining_; }; // These matchers check whether a string consists entirely of HTTP/2 frames of @@ -41,42 +114,6 @@ testing::Matcher<absl::string_view> EqualsFrames( testing::Matcher<absl::string_view> EqualsFrames( std::vector<spdy::SpdyFrameType> types); -testing::Matcher<const nghttp2_frame_hd*> HasFrameHeader( - uint32_t streamid, - uint8_t type, - const testing::Matcher<int> flags); - -testing::Matcher<const nghttp2_frame*> IsData( - const testing::Matcher<uint32_t> stream_id, - const testing::Matcher<size_t> length, - const testing::Matcher<int> flags); - -testing::Matcher<const nghttp2_frame*> IsHeaders( - const testing::Matcher<uint32_t> stream_id, - const testing::Matcher<int> flags, - const testing::Matcher<int> category); - -testing::Matcher<const nghttp2_frame*> IsRstStream( - const testing::Matcher<uint32_t> stream_id, - const testing::Matcher<uint32_t> error_code); - -testing::Matcher<const nghttp2_frame*> IsSettings( - const testing::Matcher<std::vector<Http2Setting>> values); - -testing::Matcher<const nghttp2_frame*> IsPing( - const testing::Matcher<uint64_t> id); - -testing::Matcher<const nghttp2_frame*> IsPingAck( - const testing::Matcher<uint64_t> id); - -testing::Matcher<const nghttp2_frame*> IsGoAway( - const testing::Matcher<uint32_t> last_stream_id, - const testing::Matcher<uint32_t> error_code, - const testing::Matcher<absl::string_view> opaque_data); - -testing::Matcher<const nghttp2_frame*> IsWindowUpdate( - const testing::Matcher<uint32_t> delta); - } // namespace test } // namespace adapter } // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/adapter/window_manager.h b/chromium/net/third_party/quiche/src/http2/adapter/window_manager.h index 277c24f960e..f15982d466a 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/window_manager.h +++ b/chromium/net/third_party/quiche/src/http2/adapter/window_manager.h @@ -3,6 +3,8 @@ #include <functional> +#include "common/platform/api/quiche_export.h" + namespace http2 { namespace adapter { @@ -12,7 +14,7 @@ class WindowManagerPeer; // This class keeps track of a HTTP/2 flow control window, notifying a listener // when a window update needs to be sent. This class is not thread-safe. -class WindowManager { +class QUICHE_EXPORT_PRIVATE WindowManager { public: // A WindowUpdateListener is invoked when it is time to send a window update. typedef std::function<void(size_t)> WindowUpdateListener; diff --git a/chromium/net/third_party/quiche/src/http2/adapter/window_manager_test.cc b/chromium/net/third_party/quiche/src/http2/adapter/window_manager_test.cc index 5e0645365ec..e706156db69 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/window_manager_test.cc +++ b/chromium/net/third_party/quiche/src/http2/adapter/window_manager_test.cc @@ -90,21 +90,21 @@ TEST_F(WindowManagerTest, AvoidWindowUnderflow) { EXPECT_EQ(wm_.CurrentWindowSize(), wm_.WindowSizeLimit()); // Don't buffer more than the total window! wm_.MarkDataBuffered(wm_.WindowSizeLimit() + 1); - EXPECT_EQ(wm_.CurrentWindowSize(), 0); + EXPECT_EQ(wm_.CurrentWindowSize(), 0u); } // Window manager should GFE_BUG and avoid buffered underflow. TEST_F(WindowManagerTest, AvoidBufferedUnderflow) { - EXPECT_EQ(peer_.buffered(), 0); + EXPECT_EQ(peer_.buffered(), 0u); // Don't flush more than has been buffered! EXPECT_QUICHE_BUG(wm_.MarkDataFlushed(1), "buffered underflow"); - EXPECT_EQ(peer_.buffered(), 0); + EXPECT_EQ(peer_.buffered(), 0u); wm_.MarkDataBuffered(42); - EXPECT_EQ(peer_.buffered(), 42); + EXPECT_EQ(peer_.buffered(), 42u); // Don't flush more than has been buffered! EXPECT_QUICHE_BUG(wm_.MarkDataFlushed(43), "buffered underflow"); - EXPECT_EQ(peer_.buffered(), 0); + EXPECT_EQ(peer_.buffered(), 0u); } // This test verifies that WindowManager notifies its listener when window is diff --git a/chromium/net/third_party/quiche/src/http2/core/http2_priority_write_scheduler.h b/chromium/net/third_party/quiche/src/http2/core/http2_priority_write_scheduler.h index b7046e7023a..4d64d75a961 100644 --- a/chromium/net/third_party/quiche/src/http2/core/http2_priority_write_scheduler.h +++ b/chromium/net/third_party/quiche/src/http2/core/http2_priority_write_scheduler.h @@ -24,7 +24,6 @@ #include "common/platform/api/quiche_logging.h" #include "spdy/core/spdy_intrusive_list.h" #include "spdy/core/spdy_protocol.h" -#include "spdy/platform/api/spdy_string_utils.h" namespace http2 { diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.cc index 0f1336ed29e..37933f19630 100644 --- a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.cc +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.cc @@ -5,7 +5,6 @@ #include "http2/hpack/decoder/hpack_decoder.h" #include "http2/decoder/decode_status.h" -#include "http2/platform/api/http2_estimate_memory_usage.h" #include "http2/platform/api/http2_flag_utils.h" #include "http2/platform/api/http2_flags.h" #include "http2/platform/api/http2_logging.h" @@ -110,10 +109,6 @@ bool HpackDecoder::DetectError() { return error_ != HpackDecodingError::kOk; } -size_t HpackDecoder::EstimateMemoryUsage() const { - return Http2EstimateMemoryUsage(entry_buffer_); -} - void HpackDecoder::ReportError(HpackDecodingError error, std::string detailed_error) { HTTP2_DVLOG(3) << "HpackDecoder::ReportError is new=" diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.h index 01d32a38ac1..92177070acc 100644 --- a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.h +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.h @@ -92,12 +92,13 @@ class QUICHE_EXPORT_PRIVATE HpackDecoder { // detected. bool DetectError(); + size_t GetDynamicTableSize() const { + return decoder_state_.GetDynamicTableSize(); + } + // Error code if an error has occurred, HpackDecodingError::kOk otherwise. HpackDecodingError error() const { return error_; } - // Returns the estimate of dynamically allocated memory in bytes. - size_t EstimateMemoryUsage() const; - std::string detailed_error() const { return detailed_error_; } private: diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h index 14b4184bd5a..5dcdc40bf99 100644 --- a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h @@ -83,6 +83,10 @@ class QUICHE_EXPORT_PRIVATE HpackDecoderState : public HpackWholeEntryListener { // No further callbacks will be made to the listener. HpackDecodingError error() const { return error_; } + size_t GetDynamicTableSize() const { + return decoder_tables_.current_header_table_size(); + } + const HpackDecoderTables& decoder_tables_for_test() const { return decoder_tables_; } diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.cc index 8f2243d7f6b..d03590377e3 100644 --- a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.cc +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.cc @@ -7,7 +7,6 @@ #include <utility> #include "http2/platform/api/http2_bug_tracker.h" -#include "http2/platform/api/http2_estimate_memory_usage.h" #include "http2/platform/api/http2_logging.h" namespace http2 { @@ -232,10 +231,6 @@ void HpackDecoderStringBuffer::OutputDebugStringTo(std::ostream& out) const { out << "}"; } -size_t HpackDecoderStringBuffer::EstimateMemoryUsage() const { - return Http2EstimateMemoryUsage(buffer_); -} - std::ostream& operator<<(std::ostream& out, const HpackDecoderStringBuffer& v) { v.OutputDebugStringTo(out); return out; diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h index 9397330fd96..83f59eda593 100644 --- a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h @@ -67,9 +67,6 @@ class QUICHE_EXPORT_PRIVATE HpackDecoderStringBuffer { Backing backing_for_testing() const { return backing_; } void OutputDebugStringTo(std::ostream& out) const; - // Returns the estimate of dynamically allocated memory in bytes. - size_t EstimateMemoryUsage() const; - private: // Storage for the string being buffered, if buffering is necessary // (e.g. if Huffman encoded, buffer_ is storage for the decoded string). diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer_test.cc index b7a8b39d051..82244efe4eb 100644 --- a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer_test.cc +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer_test.cc @@ -10,7 +10,6 @@ #include "absl/strings/escaping.h" #include "http2/platform/api/http2_logging.h" -#include "http2/platform/api/http2_string_utils.h" #include "http2/platform/api/http2_test_helpers.h" #include "common/platform/api/quiche_test.h" diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.cc index 76d1f31354e..5f3e63e2315 100644 --- a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.cc +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.cc @@ -5,12 +5,11 @@ #include "http2/hpack/decoder/hpack_whole_entry_buffer.h" #include "absl/strings/str_cat.h" -#include "http2/platform/api/http2_estimate_memory_usage.h" #include "http2/platform/api/http2_flag_utils.h" #include "http2/platform/api/http2_flags.h" #include "http2/platform/api/http2_logging.h" #include "http2/platform/api/http2_macros.h" -#include "http2/platform/api/http2_string_utils.h" +#include "common/quiche_text_utils.h" namespace http2 { @@ -35,10 +34,6 @@ void HpackWholeEntryBuffer::BufferStringsIfUnbuffered() { value_.BufferStringIfUnbuffered(); } -size_t HpackWholeEntryBuffer::EstimateMemoryUsage() const { - return Http2EstimateMemoryUsage(name_) + Http2EstimateMemoryUsage(value_); -} - void HpackWholeEntryBuffer::OnIndexedHeader(size_t index) { HTTP2_DVLOG(2) << "HpackWholeEntryBuffer::OnIndexedHeader: index=" << index; listener_->OnIndexedHeader(index); @@ -71,7 +66,8 @@ void HpackWholeEntryBuffer::OnNameStart(bool huffman_encoded, size_t len) { void HpackWholeEntryBuffer::OnNameData(const char* data, size_t len) { HTTP2_DVLOG(2) << "HpackWholeEntryBuffer::OnNameData: len=" << len << " data:\n" - << Http2HexDump(absl::string_view(data, len)); + << quiche::QuicheTextUtils::HexDump( + absl::string_view(data, len)); QUICHE_DCHECK_EQ(maybe_name_index_, 0u); if (!error_detected_ && !name_.OnData(data, len)) { ReportError(HpackDecodingError::kNameHuffmanError, ""); @@ -108,7 +104,8 @@ void HpackWholeEntryBuffer::OnValueStart(bool huffman_encoded, size_t len) { void HpackWholeEntryBuffer::OnValueData(const char* data, size_t len) { HTTP2_DVLOG(2) << "HpackWholeEntryBuffer::OnValueData: len=" << len << " data:\n" - << Http2HexDump(absl::string_view(data, len)); + << quiche::QuicheTextUtils::HexDump( + absl::string_view(data, len)); if (!error_detected_ && !value_.OnData(data, len)) { ReportError(HpackDecodingError::kValueHuffmanError, ""); HTTP2_CODE_COUNT_N(decompress_failure_3, 22, 23); diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.h index eb4edbe53f6..0cce0bafd72 100644 --- a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.h +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.h @@ -63,9 +63,6 @@ class QUICHE_EXPORT_PRIVATE HpackWholeEntryBuffer // no further callbacks will be made to the listener. bool error_detected() const { return error_detected_; } - // Returns the estimate of dynamically allocated memory in bytes. - size_t EstimateMemoryUsage() const; - // Implement the HpackEntryDecoderListener methods. void OnIndexedHeader(size_t index) override; diff --git a/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder_test.cc index e7a346e7719..395706f219c 100644 --- a/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder_test.cc +++ b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder_test.cc @@ -12,7 +12,6 @@ #include "absl/strings/escaping.h" #include "http2/decoder/decode_buffer.h" #include "http2/decoder/decode_status.h" -#include "http2/platform/api/http2_string_utils.h" #include "http2/platform/api/http2_test_helpers.h" #include "http2/tools/random_decoder_test.h" #include "common/platform/api/quiche_test.h" diff --git a/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder_test.cc index 0d321876149..84439c16973 100644 --- a/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder_test.cc +++ b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder_test.cc @@ -6,7 +6,6 @@ #include "absl/base/macros.h" #include "absl/strings/escaping.h" -#include "http2/platform/api/http2_string_utils.h" #include "common/platform/api/quiche_test.h" namespace http2 { diff --git a/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_transcoder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_transcoder_test.cc index bca12c50ebb..3b0047d49d4 100644 --- a/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_transcoder_test.cc +++ b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_transcoder_test.cc @@ -11,9 +11,9 @@ #include "http2/decoder/decode_status.h" #include "http2/hpack/huffman/hpack_huffman_decoder.h" #include "http2/hpack/huffman/hpack_huffman_encoder.h" -#include "http2/platform/api/http2_string_utils.h" #include "http2/tools/random_decoder_test.h" #include "common/platform/api/quiche_test.h" +#include "common/quiche_text_utils.h" using ::testing::AssertionResult; using ::testing::AssertionSuccess; @@ -114,8 +114,8 @@ TEST_F(HpackHuffmanTranscoderTest, RoundTripRandomAsciiNonControlString) { const std::string s = RandomAsciiNonControlString(length); ASSERT_TRUE(TranscodeAndValidateSeveralWays(s)) << "Unable to decode:\n\n" - << Http2HexDump(s) << "\n\noutput_buffer_:\n" - << Http2HexDump(output_buffer_); + << quiche::QuicheTextUtils::HexDump(s) << "\n\noutput_buffer_:\n" + << quiche::QuicheTextUtils::HexDump(output_buffer_); } } @@ -124,8 +124,8 @@ TEST_F(HpackHuffmanTranscoderTest, RoundTripRandomBytes) { const std::string s = RandomBytes(length); ASSERT_TRUE(TranscodeAndValidateSeveralWays(s)) << "Unable to decode:\n\n" - << Http2HexDump(s) << "\n\noutput_buffer_:\n" - << Http2HexDump(output_buffer_); + << quiche::QuicheTextUtils::HexDump(s) << "\n\noutput_buffer_:\n" + << quiche::QuicheTextUtils::HexDump(output_buffer_); } } diff --git a/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder_test.cc index 3821127bd08..b292c4b3767 100644 --- a/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder_test.cc +++ b/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder_test.cc @@ -5,7 +5,6 @@ #include "http2/hpack/tools/hpack_block_builder.h" #include "absl/strings/escaping.h" -#include "http2/platform/api/http2_string_utils.h" #include "common/platform/api/quiche_test.h" namespace http2 { diff --git a/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_example.cc b/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_example.cc index b2783e3b277..64255b1ef2d 100644 --- a/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_example.cc +++ b/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_example.cc @@ -10,7 +10,6 @@ #include "absl/strings/str_cat.h" #include "http2/platform/api/http2_bug_tracker.h" #include "http2/platform/api/http2_logging.h" -#include "http2/platform/api/http2_string_utils.h" namespace http2 { namespace test { diff --git a/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder_test.cc index a67cca6c729..aff6af3730e 100644 --- a/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder_test.cc +++ b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder_test.cc @@ -12,7 +12,6 @@ #include "absl/strings/escaping.h" #include "absl/strings/string_view.h" #include "http2/platform/api/http2_logging.h" -#include "http2/platform/api/http2_string_utils.h" #include "http2/tools/random_decoder_test.h" #include "common/platform/api/quiche_test.h" diff --git a/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder_test.cc index 7f049e8eabf..5daf88b99d7 100644 --- a/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder_test.cc +++ b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder_test.cc @@ -6,7 +6,6 @@ #include "absl/base/macros.h" #include "absl/strings/escaping.h" -#include "http2/platform/api/http2_string_utils.h" #include "common/platform/api/quiche_test.h" namespace http2 { diff --git a/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_round_trip_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_round_trip_test.cc index d8152593547..0c832d9e83a 100644 --- a/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_round_trip_test.cc +++ b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_round_trip_test.cc @@ -18,9 +18,9 @@ #include "absl/strings/string_view.h" #include "http2/hpack/tools/hpack_block_builder.h" #include "http2/platform/api/http2_logging.h" -#include "http2/platform/api/http2_string_utils.h" #include "http2/tools/random_decoder_test.h" #include "common/platform/api/quiche_test.h" +#include "common/quiche_text_utils.h" using ::testing::AssertionFailure; using ::testing::AssertionSuccess; @@ -166,7 +166,7 @@ class HpackVarintRoundTripTest : public RandomDecoderTest { std::string msg = absl::StrCat("value=", value, " (0x", absl::Hex(value), "), prefix_length=", prefix_length, ", expected_bytes=", expected_bytes, "\n", - Http2HexDump(buffer_)); + quiche::QuicheTextUtils::HexDump(buffer_)); if (value == minimum) { HTTP2_LOG(INFO) << "Checking minimum; " << msg; @@ -221,7 +221,8 @@ class HpackVarintRoundTripTest : public RandomDecoderTest { if (expected_bytes < 11) { // Confirm the claim that beyond requires more bytes. Encode(beyond, prefix_length); - EXPECT_EQ(expected_bytes + 1, buffer_.size()) << Http2HexDump(buffer_); + EXPECT_EQ(expected_bytes + 1, buffer_.size()) + << quiche::QuicheTextUtils::HexDump(buffer_); } std::set<uint64_t> values; @@ -285,9 +286,9 @@ TEST_F(HpackVarintRoundTripTest, Encode) { for (uint64_t value : values) { EncodeNoRandom(value, prefix_length); - std::string dump = Http2HexDump(buffer_); + std::string dump = quiche::QuicheTextUtils::HexDump(buffer_); HTTP2_LOG(INFO) << absl::StrFormat("%10llu %0#18x ", value, value) - << Http2HexDump(buffer_).substr(7); + << quiche::QuicheTextUtils::HexDump(buffer_).substr(7); } } } diff --git a/chromium/net/third_party/quiche/src/http2/http2_constants.cc b/chromium/net/third_party/quiche/src/http2/http2_constants.cc index bebe8803652..43d89bcc221 100644 --- a/chromium/net/third_party/quiche/src/http2/http2_constants.cc +++ b/chromium/net/third_party/quiche/src/http2/http2_constants.cc @@ -8,7 +8,6 @@ #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" #include "http2/platform/api/http2_logging.h" -#include "http2/platform/api/http2_string_utils.h" namespace http2 { diff --git a/chromium/net/third_party/quiche/src/http2/http2_structures.cc b/chromium/net/third_party/quiche/src/http2/http2_structures.cc index a96edccdd71..f8a8fadadbf 100644 --- a/chromium/net/third_party/quiche/src/http2/http2_structures.cc +++ b/chromium/net/third_party/quiche/src/http2/http2_structures.cc @@ -9,7 +9,6 @@ #include "absl/strings/escaping.h" #include "absl/strings/str_cat.h" -#include "http2/platform/api/http2_string_utils.h" namespace http2 { diff --git a/chromium/net/third_party/quiche/src/http2/http2_structures_test.cc b/chromium/net/third_party/quiche/src/http2/http2_structures_test.cc index 44712bc13b9..d74b74cc6f3 100644 --- a/chromium/net/third_party/quiche/src/http2/http2_structures_test.cc +++ b/chromium/net/third_party/quiche/src/http2/http2_structures_test.cc @@ -9,7 +9,7 @@ // Note that EXPECT.*DEATH tests are slow (a fork is probably involved). // And in case you're wondering, yes, these are ridiculously thorough tests, -// but believe it or not, I've found stupid bugs this way. +// but believe it or not, I've found silly bugs this way. #include <memory> #include <ostream> diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_containers.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_containers.h deleted file mode 100644 index 7bd3f8eb4a3..00000000000 --- a/chromium/net/third_party/quiche/src/http2/platform/api/http2_containers.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_CONTAINERS_H_ -#define QUICHE_HTTP2_PLATFORM_API_HTTP2_CONTAINERS_H_ - -#include "quiche_platform_impl/quiche_containers_impl.h" - -namespace http2 { - -// Represents a double-ended queue which may be backed by a list or a flat -// circular buffer. -// -// DOES NOT GUARANTEE POINTER OR ITERATOR STABILITY! -template <typename T> -using Http2Deque = quiche::QuicheDequeImpl<T>; - -} // namespace http2 - -#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_CONTAINERS_H_ diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_estimate_memory_usage.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_estimate_memory_usage.h deleted file mode 100644 index 39b3af59528..00000000000 --- a/chromium/net/third_party/quiche/src/http2/platform/api/http2_estimate_memory_usage.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_ESTIMATE_MEMORY_USAGE_H_ -#define QUICHE_HTTP2_PLATFORM_API_HTTP2_ESTIMATE_MEMORY_USAGE_H_ - -#include <cstddef> - -#include "quiche_platform_impl/quiche_estimate_memory_usage_impl.h" - -namespace http2 { - -template <class T> -size_t Http2EstimateMemoryUsage(const T& object) { - return quiche::QuicheEstimateMemoryUsageImpl(object); -} - -} // namespace http2 - -#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_ESTIMATE_MEMORY_USAGE_H_ diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_string_utils.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_string_utils.h deleted file mode 100644 index cf39515ad4c..00000000000 --- a/chromium/net/third_party/quiche/src/http2/platform/api/http2_string_utils.h +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_UTILS_H_ -#define QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_UTILS_H_ - -#include <string> -#include <type_traits> -#include <utility> - -#include "absl/strings/string_view.h" -#include "net/http2/platform/impl/http2_string_utils_impl.h" - -namespace http2 { - -inline std::string Http2HexDump(absl::string_view data) { - return Http2HexDumpImpl(data); -} - -} // namespace http2 - -#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_UTILS_H_ diff --git a/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts.cc b/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts.cc index a053beb10df..47135233ffd 100644 --- a/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts.cc +++ b/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts.cc @@ -126,7 +126,7 @@ void FrameParts::SetAltSvcExpected(absl::string_view origin, opt_altsvc_value_length_ = value.size(); } -bool FrameParts::OnFrameHeader(const Http2FrameHeader& header) { +bool FrameParts::OnFrameHeader(const Http2FrameHeader& /*header*/) { ADD_FAILURE() << "OnFrameHeader: " << *this; return true; } diff --git a/chromium/net/third_party/quiche/src/http2/test_tools/http2_random.cc b/chromium/net/third_party/quiche/src/http2/test_tools/http2_random.cc index 8f9014efd17..71c08b78faf 100644 --- a/chromium/net/third_party/quiche/src/http2/test_tools/http2_random.cc +++ b/chromium/net/third_party/quiche/src/http2/test_tools/http2_random.cc @@ -2,7 +2,6 @@ #include "absl/strings/escaping.h" #include "http2/platform/api/http2_logging.h" -#include "http2/platform/api/http2_string_utils.h" #include "third_party/boringssl/src/include/openssl/chacha.h" #include "third_party/boringssl/src/include/openssl/rand.h" diff --git a/chromium/net/third_party/quiche/src/quic/core/batch_writer/quic_batch_writer_base.cc b/chromium/net/third_party/quiche/src/quic/core/batch_writer/quic_batch_writer_base.cc index 173c8d30488..9cdff7e2b6c 100644 --- a/chromium/net/third_party/quiche/src/quic/core/batch_writer/quic_batch_writer_base.cc +++ b/chromium/net/third_party/quiche/src/quic/core/batch_writer/quic_batch_writer_base.cc @@ -24,20 +24,10 @@ WriteResult QuicBatchWriterBase::WritePacket( const WriteResult result = InternalWritePacket(buffer, buf_len, self_address, peer_address, options); - if (GetQuicReloadableFlag(quic_batch_writer_fix_write_blocked)) { - if (IsWriteBlockedStatus(result.status)) { - if (result.status == WRITE_STATUS_BLOCKED_DATA_BUFFERED) { - QUIC_CODE_COUNT(quic_batch_writer_fix_write_blocked_data_buffered); - } else { - QUIC_CODE_COUNT(quic_batch_writer_fix_write_blocked_data_not_buffered); - } - write_blocked_ = true; - } - } else { - if (result.status == WRITE_STATUS_BLOCKED) { - write_blocked_ = true; - } + if (IsWriteBlockedStatus(result.status)) { + write_blocked_ = true; } + return result; } diff --git a/chromium/net/third_party/quiche/src/quic/core/batch_writer/quic_batch_writer_test.h b/chromium/net/third_party/quiche/src/quic/core/batch_writer/quic_batch_writer_test.h index 820100adabd..e14040fb39c 100644 --- a/chromium/net/third_party/quiche/src/quic/core/batch_writer/quic_batch_writer_test.h +++ b/chromium/net/third_party/quiche/src/quic/core/batch_writer/quic_batch_writer_test.h @@ -12,6 +12,7 @@ #include <iostream> #include <utility> +#include "absl/base/optimization.h" #include "quic/core/batch_writer/quic_batch_writer_base.h" #include "quic/core/quic_udp_socket.h" #include "quic/platform/api/quic_test.h" @@ -259,8 +260,9 @@ class QUIC_EXPORT_PRIVATE QuicUdpBatchWriterIOTest QuicSocketAddress self_address_; QuicSocketAddress peer_address_; - char packet_buffer_[1500]; - char control_buffer_[kDefaultUdpPacketControlBufferSize]; + ABSL_CACHELINE_ALIGNED char packet_buffer_[1500]; + ABSL_CACHELINE_ALIGNED char + control_buffer_[kDefaultUdpPacketControlBufferSize]; int address_family_; const size_t data_size_; const size_t packet_size_; diff --git a/chromium/net/third_party/quiche/src/quic/core/batch_writer/quic_gso_batch_writer_test.cc b/chromium/net/third_party/quiche/src/quic/core/batch_writer/quic_gso_batch_writer_test.cc index 8a1b93fc723..4aab1982f55 100644 --- a/chromium/net/third_party/quiche/src/quic/core/batch_writer/quic_gso_batch_writer_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/batch_writer/quic_gso_batch_writer_test.cc @@ -322,11 +322,9 @@ TEST_F(QuicGsoBatchWriterTest, WriteBlockDataBuffered) { })); ASSERT_EQ(WriteResult(WRITE_STATUS_BLOCKED_DATA_BUFFERED, EWOULDBLOCK), WritePacket(&writer, 50)); - if (GetQuicReloadableFlag(quic_batch_writer_fix_write_blocked)) { - EXPECT_TRUE(writer.IsWriteBlocked()); - } else { - EXPECT_FALSE(writer.IsWriteBlocked()); - } + + EXPECT_TRUE(writer.IsWriteBlocked()); + ASSERT_EQ(250u, writer.batch_buffer().SizeInUse()); ASSERT_EQ(3u, writer.buffered_writes().size()); } diff --git a/chromium/net/third_party/quiche/src/quic/core/congestion_control/bandwidth_sampler.h b/chromium/net/third_party/quiche/src/quic/core/congestion_control/bandwidth_sampler.h index 6738f49ab05..2d5924b06e1 100644 --- a/chromium/net/third_party/quiche/src/quic/core/congestion_control/bandwidth_sampler.h +++ b/chromium/net/third_party/quiche/src/quic/core/congestion_control/bandwidth_sampler.h @@ -47,6 +47,7 @@ struct QUIC_EXPORT_PRIVATE SendTimeState { bytes_in_flight(bytes_in_flight) {} SendTimeState(const SendTimeState& other) = default; + SendTimeState& operator=(const SendTimeState& other) = default; friend QUIC_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os, const SendTimeState& s); diff --git a/chromium/net/third_party/quiche/src/quic/core/congestion_control/bbr2_misc.cc b/chromium/net/third_party/quiche/src/quic/core/congestion_control/bbr2_misc.cc index 8d4a49350b5..6eb9b2ec940 100644 --- a/chromium/net/third_party/quiche/src/quic/core/congestion_control/bbr2_misc.cc +++ b/chromium/net/third_party/quiche/src/quic/core/congestion_control/bbr2_misc.cc @@ -264,9 +264,7 @@ void Bbr2NetworkModel::AdaptLowerBounds( // sample_max_bandwidth will be Zero() if the loss is triggered by a timer // expiring. Ideally we'd use the most recent bandwidth sample, // but bandwidth_latest is safer than Zero(). - if (GetQuicReloadableFlag(quic_bbr2_fix_bw_lo_mode2) && - !congestion_event.sample_max_bandwidth.IsZero()) { - QUIC_RELOADABLE_FLAG_COUNT_N(quic_bbr2_fix_bw_lo_mode2, 1, 2); + if (!congestion_event.sample_max_bandwidth.IsZero()) { // bandwidth_latest_ is the max bandwidth for the round, but to allow // fast, conservation style response to loss, use the last sample. last_bandwidth = congestion_event.sample_max_bandwidth; @@ -286,9 +284,7 @@ void Bbr2NetworkModel::AdaptLowerBounds( } // If it's the end of the round, ensure bandwidth_lo doesn't decrease more // than beta. - if (GetQuicReloadableFlag(quic_bbr2_fix_bw_lo_mode) && - congestion_event.end_of_round_trip) { - QUIC_RELOADABLE_FLAG_COUNT_N(quic_bbr2_fix_bw_lo_mode, 2, 2); + if (congestion_event.end_of_round_trip) { bandwidth_lo_ = std::max(bandwidth_lo_, prior_bandwidth_lo_ * (1.0 - Params().beta)); prior_bandwidth_lo_ = QuicBandwidth::Zero(); diff --git a/chromium/net/third_party/quiche/src/quic/core/congestion_control/bbr2_misc.h b/chromium/net/third_party/quiche/src/quic/core/congestion_control/bbr2_misc.h index b7007f55705..d28036df543 100644 --- a/chromium/net/third_party/quiche/src/quic/core/congestion_control/bbr2_misc.h +++ b/chromium/net/third_party/quiche/src/quic/core/congestion_control/bbr2_misc.h @@ -176,10 +176,6 @@ struct QUIC_EXPORT_PRIVATE Bbr2Params { // Can be disabled by connection option 'B2RP'. bool avoid_unnecessary_probe_rtt = true; - // Can be disabled by connection option 'B2CL'. - bool avoid_too_low_probe_bw_cwnd = - GetQuicReloadableFlag(quic_bbr2_avoid_too_low_probe_bw_cwnd); - // Can be enabled by connection option 'B2LO'. bool ignore_inflight_lo = false; diff --git a/chromium/net/third_party/quiche/src/quic/core/congestion_control/bbr2_probe_bw.cc b/chromium/net/third_party/quiche/src/quic/core/congestion_control/bbr2_probe_bw.cc index af0f00fee80..ce9443f7d1d 100644 --- a/chromium/net/third_party/quiche/src/quic/core/congestion_control/bbr2_probe_bw.cc +++ b/chromium/net/third_party/quiche/src/quic/core/congestion_control/bbr2_probe_bw.cc @@ -80,36 +80,12 @@ Bbr2Mode Bbr2ProbeBwMode::OnCongestionEvent( } Limits<QuicByteCount> Bbr2ProbeBwMode::GetCwndLimits() const { - if (!GetQuicReloadableFlag(quic_bbr2_avoid_too_low_probe_bw_cwnd)) { - if (cycle_.phase == CyclePhase::PROBE_CRUISE) { - return NoGreaterThan( - std::min(model_->inflight_lo(), model_->inflight_hi_with_headroom())); - } - + if (cycle_.phase == CyclePhase::PROBE_CRUISE) { return NoGreaterThan( - std::min(model_->inflight_lo(), model_->inflight_hi())); - } - - QUIC_RELOADABLE_FLAG_COUNT(quic_bbr2_avoid_too_low_probe_bw_cwnd); - - QuicByteCount upper_limit = - std::min(model_->inflight_lo(), cycle_.phase == CyclePhase::PROBE_CRUISE - ? model_->inflight_hi_with_headroom() - : model_->inflight_hi()); - - if (Params().avoid_too_low_probe_bw_cwnd) { - // Ensure upper_limit is at least BDP + AckHeight. - QuicByteCount bdp_with_ack_height = - model_->BDP(model_->MaxBandwidth()) + model_->MaxAckHeight(); - if (upper_limit < bdp_with_ack_height) { - QUIC_DVLOG(3) << sender_ << " Rasing upper_limit from " << upper_limit - << " to " << bdp_with_ack_height; - QUIC_CODE_COUNT(quic_bbr2_avoid_too_low_probe_bw_cwnd_in_effect); - upper_limit = bdp_with_ack_height; - } + std::min(model_->inflight_lo(), model_->inflight_hi_with_headroom())); } - return NoGreaterThan(upper_limit); + return NoGreaterThan(std::min(model_->inflight_lo(), model_->inflight_hi())); } bool Bbr2ProbeBwMode::IsProbingForBandwidth() const { @@ -500,9 +476,7 @@ void Bbr2ProbeBwMode::EnterProbeDown(bool probed_too_high, cycle_.rounds_in_phase = 0; cycle_.phase_start_time = now; ++sender_->connection_stats_->bbr_num_cycles; - if (GetQuicReloadableFlag(quic_bbr2_fix_bw_lo_mode2) && - Params().bw_lo_mode_ != Bbr2Params::QuicBandwidthLoMode::DEFAULT) { - QUIC_RELOADABLE_FLAG_COUNT_N(quic_bbr2_fix_bw_lo_mode2, 2, 2); + if (Params().bw_lo_mode_ != Bbr2Params::QuicBandwidthLoMode::DEFAULT) { // Clear bandwidth lo if it was set in PROBE_UP, because losses in PROBE_UP // should not permanently change bandwidth_lo. // It's possible for bandwidth_lo to be set during REFILL, but if that was diff --git a/chromium/net/third_party/quiche/src/quic/core/congestion_control/bbr2_sender.cc b/chromium/net/third_party/quiche/src/quic/core/congestion_control/bbr2_sender.cc index b69a7952afa..09b920e5759 100644 --- a/chromium/net/third_party/quiche/src/quic/core/congestion_control/bbr2_sender.cc +++ b/chromium/net/third_party/quiche/src/quic/core/congestion_control/bbr2_sender.cc @@ -16,6 +16,7 @@ #include "quic/platform/api/quic_flag_utils.h" #include "quic/platform/api/quic_flags.h" #include "quic/platform/api/quic_logging.h" +#include "common/print_elements.h" namespace quic { @@ -101,10 +102,6 @@ void Bbr2Sender::SetFromConfig(const QuicConfig& config, if (config.HasClientRequestedIndependentOption(kB2RP, perspective)) { params_.avoid_unnecessary_probe_rtt = false; } - if (GetQuicReloadableFlag(quic_bbr2_avoid_too_low_probe_bw_cwnd) && - config.HasClientRequestedIndependentOption(kB2CL, perspective)) { - params_.avoid_too_low_probe_bw_cwnd = false; - } if (config.HasClientRequestedIndependentOption(k1RTT, perspective)) { params_.startup_full_bw_rounds = 1; } @@ -287,7 +284,8 @@ void Bbr2Sender::OnCongestionEvent(bool /*rtt_updated*/, } QUIC_DVLOG(3) - << this << " END CongestionEvent(acked:" << acked_packets + << this + << " END CongestionEvent(acked:" << quiche::PrintElements(acked_packets) << ", lost:" << lost_packets.size() << ") " << ", Mode:" << mode_ << ", RttCount:" << model_.RoundTripCount() << ", BytesInFlight:" << congestion_event.bytes_in_flight diff --git a/chromium/net/third_party/quiche/src/quic/core/congestion_control/bbr2_simulator_test.cc b/chromium/net/third_party/quiche/src/quic/core/congestion_control/bbr2_simulator_test.cc index 99acf2c3e21..ae0c8842c8e 100644 --- a/chromium/net/third_party/quiche/src/quic/core/congestion_control/bbr2_simulator_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/congestion_control/bbr2_simulator_test.cc @@ -477,7 +477,11 @@ TEST_F(Bbr2DefaultTopologyTest, SimpleTransfer2RTTAggregationBytes) { EXPECT_APPROX_EQ(params.BottleneckBandwidth(), sender_->ExportDebugState().bandwidth_hi, 0.01f); - EXPECT_LE(sender_loss_rate_in_packets(), 0.05); + if (GetQuicReloadableFlag(quic_fix_pacing_sender_bursts)) { + EXPECT_EQ(sender_loss_rate_in_packets(), 0); + } else { + EXPECT_LE(sender_loss_rate_in_packets(), 0.05); + } // The margin here is high, because both link level aggregation and ack // decimation can greatly increase smoothed rtt. EXPECT_GE(params.RTT() * 5, rtt_stats()->smoothed_rtt()); @@ -558,6 +562,9 @@ TEST_F(Bbr2DefaultTopologyTest, QUIC_SLOW_TEST(BandwidthIncrease)) { [this]() { return sender_endpoint_.bytes_to_transfer() == 0; }, QuicTime::Delta::FromSeconds(50)); EXPECT_TRUE(simulator_result); + // Ensure the full bandwidth is discovered. + EXPECT_APPROX_EQ(params.test_link.bandwidth, + sender_->ExportDebugState().bandwidth_hi, 0.02f); } // Test the number of losses incurred by the startup phase in a situation when @@ -1075,7 +1082,6 @@ TEST_F(Bbr2DefaultTopologyTest, ProbeBwAfterQuiescencePostponeMinRttTimestamp) { min_rtt_timestamp_after_idle); } -// Regression test for http://go/switchtobbr2midconnection. TEST_F(Bbr2DefaultTopologyTest, SwitchToBbr2MidConnection) { QuicTime now = QuicTime::Zero(); BbrSender old_sender(sender_connection()->clock()->Now(), diff --git a/chromium/net/third_party/quiche/src/quic/core/congestion_control/pacing_sender.cc b/chromium/net/third_party/quiche/src/quic/core/congestion_control/pacing_sender.cc index a43601eae0d..265a422817d 100644 --- a/chromium/net/third_party/quiche/src/quic/core/congestion_control/pacing_sender.cc +++ b/chromium/net/third_party/quiche/src/quic/core/congestion_control/pacing_sender.cc @@ -102,6 +102,12 @@ void PacingSender::OnPacketSent( // is about 10ms of queueing. lumpy_tokens_ = 1u; } + if (GetQuicReloadableFlag(quic_fix_pacing_sender_bursts) && + (bytes_in_flight + bytes) >= sender_->GetCongestionWindow()) { + QUIC_RELOADABLE_FLAG_COUNT(quic_fix_pacing_sender_bursts); + // Don't add lumpy_tokens if the congestion controller is CWND limited. + lumpy_tokens_ = 1u; + } } --lumpy_tokens_; if (pacing_limited_) { diff --git a/chromium/net/third_party/quiche/src/quic/core/congestion_control/pacing_sender_test.cc b/chromium/net/third_party/quiche/src/quic/core/congestion_control/pacing_sender_test.cc index b7513576cca..ec3119d2407 100644 --- a/chromium/net/third_party/quiche/src/quic/core/congestion_control/pacing_sender_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/congestion_control/pacing_sender_test.cc @@ -7,6 +7,7 @@ #include <memory> #include <utility> +#include "quic/core/quic_constants.h" #include "quic/core/quic_packets.h" #include "quic/platform/api/quic_flag_utils.h" #include "quic/platform/api/quic_flags.h" @@ -75,42 +76,41 @@ class PacingSenderTest : public QuicTest { } void CheckPacketIsSentImmediately(HasRetransmittableData retransmittable_data, - QuicByteCount bytes_in_flight, + QuicByteCount prior_in_flight, bool in_recovery, - bool cwnd_limited, QuicPacketCount cwnd) { // In order for the packet to be sendable, the underlying sender must // permit it to be sent immediately. for (int i = 0; i < 2; ++i) { - EXPECT_CALL(*mock_sender_, CanSend(bytes_in_flight)) + EXPECT_CALL(*mock_sender_, CanSend(prior_in_flight)) .WillOnce(Return(true)); // Verify that the packet can be sent immediately. EXPECT_EQ(zero_time_, - pacing_sender_->TimeUntilSend(clock_.Now(), bytes_in_flight)); + pacing_sender_->TimeUntilSend(clock_.Now(), prior_in_flight)); } // Actually send the packet. - if (bytes_in_flight == 0) { + if (prior_in_flight == 0) { EXPECT_CALL(*mock_sender_, InRecovery()).WillOnce(Return(in_recovery)); } EXPECT_CALL(*mock_sender_, - OnPacketSent(clock_.Now(), bytes_in_flight, packet_number_, + OnPacketSent(clock_.Now(), prior_in_flight, packet_number_, kMaxOutgoingPacketSize, retransmittable_data)); EXPECT_CALL(*mock_sender_, GetCongestionWindow()) - .Times(AtMost(1)) .WillRepeatedly(Return(cwnd * kDefaultTCPMSS)); EXPECT_CALL(*mock_sender_, - CanSend(bytes_in_flight + kMaxOutgoingPacketSize)) + CanSend(prior_in_flight + kMaxOutgoingPacketSize)) .Times(AtMost(1)) - .WillRepeatedly(Return(!cwnd_limited)); - pacing_sender_->OnPacketSent(clock_.Now(), bytes_in_flight, + .WillRepeatedly(Return((prior_in_flight + kMaxOutgoingPacketSize) < + (cwnd * kDefaultTCPMSS))); + pacing_sender_->OnPacketSent(clock_.Now(), prior_in_flight, packet_number_++, kMaxOutgoingPacketSize, retransmittable_data); } void CheckPacketIsSentImmediately() { CheckPacketIsSentImmediately(HAS_RETRANSMITTABLE_DATA, kBytesInFlight, - false, false, 10); + false, 10); } void CheckPacketIsDelayed(QuicTime::Delta delay) { @@ -242,7 +242,7 @@ TEST_F(PacingSenderTest, InitialBurst) { // Next time TimeUntilSend is called with no bytes in flight, pacing should // allow a packet to be sent, and when it's sent, the tokens are refilled. - CheckPacketIsSentImmediately(HAS_RETRANSMITTABLE_DATA, 0, false, false, 10); + CheckPacketIsSentImmediately(HAS_RETRANSMITTABLE_DATA, 0, false, 10); for (int i = 0; i < kInitialBurstPackets - 1; ++i) { CheckPacketIsSentImmediately(); } @@ -276,7 +276,7 @@ TEST_F(PacingSenderTest, InitialBurstNoRttMeasurement) { // Next time TimeUntilSend is called with no bytes in flight, the tokens // should be refilled and there should be no delay. - CheckPacketIsSentImmediately(HAS_RETRANSMITTABLE_DATA, 0, false, false, 10); + CheckPacketIsSentImmediately(HAS_RETRANSMITTABLE_DATA, 0, false, 10); // Send 10 packets, and verify that they are not paced. for (int i = 0; i < kInitialBurstPackets - 1; ++i) { CheckPacketIsSentImmediately(); @@ -314,7 +314,7 @@ TEST_F(PacingSenderTest, FastSending) { // Next time TimeUntilSend is called with no bytes in flight, the tokens // should be refilled and there should be no delay. - CheckPacketIsSentImmediately(HAS_RETRANSMITTABLE_DATA, 0, false, false, 10); + CheckPacketIsSentImmediately(HAS_RETRANSMITTABLE_DATA, 0, false, 10); for (int i = 0; i < kInitialBurstPackets - 1; ++i) { CheckPacketIsSentImmediately(); } @@ -348,10 +348,12 @@ TEST_F(PacingSenderTest, NoBurstEnteringRecovery) { // One packet is sent immediately, because of 1ms pacing granularity. CheckPacketIsSentImmediately(); // Ensure packets are immediately paced. - EXPECT_CALL(*mock_sender_, CanSend(kDefaultTCPMSS)).WillOnce(Return(true)); + EXPECT_CALL(*mock_sender_, CanSend(kMaxOutgoingPacketSize)) + .WillOnce(Return(true)); // Verify the next packet is paced and delayed 2ms due to granularity. - EXPECT_EQ(QuicTime::Delta::FromMilliseconds(2), - pacing_sender_->TimeUntilSend(clock_.Now(), kDefaultTCPMSS)); + EXPECT_EQ( + QuicTime::Delta::FromMilliseconds(2), + pacing_sender_->TimeUntilSend(clock_.Now(), kMaxOutgoingPacketSize)); CheckPacketIsDelayed(QuicTime::Delta::FromMilliseconds(2)); } @@ -364,7 +366,7 @@ TEST_F(PacingSenderTest, NoBurstInRecovery) { UpdateRtt(); // Ensure only one packet is sent immediately and the rest are paced. - CheckPacketIsSentImmediately(HAS_RETRANSMITTABLE_DATA, 0, true, false, 10); + CheckPacketIsSentImmediately(HAS_RETRANSMITTABLE_DATA, 0, true, 10); CheckPacketIsSentImmediately(); CheckPacketIsDelayed(QuicTime::Delta::FromMilliseconds(2)); } @@ -385,8 +387,11 @@ TEST_F(PacingSenderTest, CwndLimited) { // Wake up on time. clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(2)); // After sending packet 3, cwnd is limited. - CheckPacketIsSentImmediately(HAS_RETRANSMITTABLE_DATA, kBytesInFlight, false, - true, 10); + // This test is slightly odd because bytes_in_flight is calculated using + // kMaxOutgoingPacketSize and CWND is calculated using kDefaultTCPMSS, + // which is 8 bytes larger, so 3 packets can be sent for a CWND of 2. + CheckPacketIsSentImmediately(HAS_RETRANSMITTABLE_DATA, + 2 * kMaxOutgoingPacketSize, false, 2); clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(100)); // Verify pacing sender stops making up for lost time after sending packet 3. @@ -438,20 +443,23 @@ TEST_F(PacingSenderTest, LumpyPacingWithInitialBurstToken) { clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(3)); CheckPacketIsSentImmediately(); // After sending packet 21, cwnd is limited. - CheckPacketIsSentImmediately(HAS_RETRANSMITTABLE_DATA, kBytesInFlight, false, - true, 10); + // This test is slightly odd because bytes_in_flight is calculated using + // kMaxOutgoingPacketSize and CWND is calculated using kDefaultTCPMSS, + // which is 8 bytes larger, so 21 packets can be sent for a CWND of 20. + CheckPacketIsSentImmediately(HAS_RETRANSMITTABLE_DATA, + 20 * kMaxOutgoingPacketSize, false, 20); clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(100)); // Suppose cwnd size is 5, so that lumpy size becomes 2. CheckPacketIsSentImmediately(HAS_RETRANSMITTABLE_DATA, kBytesInFlight, false, - false, 5); + 5); CheckPacketIsSentImmediately(); // Packet 24 will be delayed 2ms. CheckPacketIsDelayed(QuicTime::Delta::FromMilliseconds(2)); } TEST_F(PacingSenderTest, NoLumpyPacingForLowBandwidthFlows) { - // Set lumpy size to be 3, and cwnd faction to 0.5 + // Set lumpy size to be 3, and cwnd fraction to 0.5 SetQuicFlag(FLAGS_quic_lumpy_pacing_size, 3); SetQuicFlag(FLAGS_quic_lumpy_pacing_cwnd_fraction, 0.5f); @@ -476,6 +484,43 @@ TEST_F(PacingSenderTest, NoLumpyPacingForLowBandwidthFlows) { } } +// Regression test for b/184471302 to ensure that ACKs received back-to-back +// don't cause bursts in sending. +TEST_F(PacingSenderTest, NoBurstsForLumpyPacingWithAckAggregation) { + // Configure pacing rate of 1 packet per millisecond. + QuicTime::Delta inter_packet_delay = QuicTime::Delta::FromMilliseconds(1); + InitPacingRate(kInitialBurstPackets, + QuicBandwidth::FromBytesAndTimeDelta(kMaxOutgoingPacketSize, + inter_packet_delay)); + UpdateRtt(); + + // Send kInitialBurstPackets packets, and verify that they are not paced. + for (int i = 0; i < kInitialBurstPackets; ++i) { + CheckPacketIsSentImmediately(); + } + // The last packet of the burst causes the sender to be CWND limited. + CheckPacketIsSentImmediately(HAS_RETRANSMITTABLE_DATA, + 10 * kMaxOutgoingPacketSize, false, 10); + + if (GetQuicReloadableFlag(quic_fix_pacing_sender_bursts)) { + // The last sent packet made the connection CWND limited, so no lumpy tokens + // should be available. + EXPECT_EQ(0u, pacing_sender_->lumpy_tokens()); + CheckPacketIsSentImmediately(HAS_RETRANSMITTABLE_DATA, + 10 * kMaxOutgoingPacketSize, false, 10); + EXPECT_EQ(0u, pacing_sender_->lumpy_tokens()); + CheckPacketIsDelayed(2 * inter_packet_delay); + } else { + EXPECT_EQ(1u, pacing_sender_->lumpy_tokens()); + // Repeatedly send single packets to make the sender CWND limited and + // observe that there's no pacing without the fix. + for (int i = 0; i < 10; ++i) { + CheckPacketIsSentImmediately(HAS_RETRANSMITTABLE_DATA, + 10 * kMaxOutgoingPacketSize, false, 10); + } + } +} + TEST_F(PacingSenderTest, IdealNextPacketSendTimeWithLumpyPacing) { // Set lumpy size to be 3, and cwnd faction to 0.5 SetQuicFlag(FLAGS_quic_lumpy_pacing_size, 3); diff --git a/chromium/net/third_party/quiche/src/quic/core/congestion_control/rtt_stats.cc b/chromium/net/third_party/quiche/src/quic/core/congestion_control/rtt_stats.cc index 2be6fa44b2a..e167dec9cf1 100644 --- a/chromium/net/third_party/quiche/src/quic/core/congestion_control/rtt_stats.cc +++ b/chromium/net/third_party/quiche/src/quic/core/congestion_control/rtt_stats.cc @@ -29,8 +29,7 @@ RttStats::RttStats() mean_deviation_(QuicTime::Delta::Zero()), calculate_standard_deviation_(false), initial_rtt_(QuicTime::Delta::FromMilliseconds(kInitialRttMs)), - last_update_time_(QuicTime::Zero()), - ignore_max_ack_delay_(false) {} + last_update_time_(QuicTime::Zero()) {} void RttStats::ExpireSmoothedMetrics() { mean_deviation_ = std::max( @@ -63,17 +62,19 @@ void RttStats::UpdateRtt(QuicTime::Delta send_delta, QuicTime::Delta rtt_sample(send_delta); previous_srtt_ = smoothed_rtt_; - - if (ignore_max_ack_delay_) { - ack_delay = QuicTime::Delta::Zero(); - } // Correct for ack_delay if information received from the peer results in a // an RTT sample at least as large as min_rtt. Otherwise, only use the // send_delta. + // TODO(fayang): consider to ignore rtt_sample if rtt_sample < ack_delay and + // ack_delay is relatively large. if (rtt_sample > ack_delay) { if (rtt_sample - min_rtt_ >= ack_delay) { rtt_sample = rtt_sample - ack_delay; + } else { + QUIC_CODE_COUNT(quic_ack_delay_makes_rtt_sample_smaller_than_min_rtt); } + } else { + QUIC_CODE_COUNT(quic_ack_delay_greater_than_rtt_sample); } latest_rtt_ = rtt_sample; if (calculate_standard_deviation_) { @@ -138,7 +139,6 @@ void RttStats::CloneFrom(const RttStats& stats) { calculate_standard_deviation_ = stats.calculate_standard_deviation_; initial_rtt_ = stats.initial_rtt_; last_update_time_ = stats.last_update_time_; - ignore_max_ack_delay_ = stats.ignore_max_ack_delay_; } } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/congestion_control/rtt_stats.h b/chromium/net/third_party/quiche/src/quic/core/congestion_control/rtt_stats.h index 0047b0f0448..cb591dbf2e1 100644 --- a/chromium/net/third_party/quiche/src/quic/core/congestion_control/rtt_stats.h +++ b/chromium/net/third_party/quiche/src/quic/core/congestion_control/rtt_stats.h @@ -101,12 +101,6 @@ class QUIC_EXPORT_PRIVATE RttStats { QuicTime last_update_time() const { return last_update_time_; } - bool ignore_max_ack_delay() const { return ignore_max_ack_delay_; } - - void set_ignore_max_ack_delay(bool ignore_max_ack_delay) { - ignore_max_ack_delay_ = ignore_max_ack_delay; - } - void EnableStandardDeviationCalculation() { calculate_standard_deviation_ = true; } @@ -130,8 +124,6 @@ class QUIC_EXPORT_PRIVATE RttStats { bool calculate_standard_deviation_; QuicTime::Delta initial_rtt_; QuicTime last_update_time_; - // Whether to ignore the peer's max ack delay. - bool ignore_max_ack_delay_; }; } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/congestion_control/rtt_stats_test.cc b/chromium/net/third_party/quiche/src/quic/core/congestion_control/rtt_stats_test.cc index bdf54c611fc..1df509f0f7c 100644 --- a/chromium/net/third_party/quiche/src/quic/core/congestion_control/rtt_stats_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/congestion_control/rtt_stats_test.cc @@ -56,34 +56,6 @@ TEST_F(RttStatsTest, SmoothedRtt) { rtt_stats_.smoothed_rtt()); } -TEST_F(RttStatsTest, SmoothedRttIgnoreAckDelay) { - rtt_stats_.set_ignore_max_ack_delay(true); - // Verify that ack_delay is ignored in the first measurement. - rtt_stats_.UpdateRtt(QuicTime::Delta::FromMilliseconds(300), - QuicTime::Delta::FromMilliseconds(100), - QuicTime::Zero()); - EXPECT_EQ(QuicTime::Delta::FromMilliseconds(300), rtt_stats_.latest_rtt()); - EXPECT_EQ(QuicTime::Delta::FromMilliseconds(300), rtt_stats_.smoothed_rtt()); - // Verify that a plausible ack delay increases the max ack delay. - rtt_stats_.UpdateRtt(QuicTime::Delta::FromMilliseconds(300), - QuicTime::Delta::FromMilliseconds(100), - QuicTime::Zero()); - EXPECT_EQ(QuicTime::Delta::FromMilliseconds(300), rtt_stats_.latest_rtt()); - EXPECT_EQ(QuicTime::Delta::FromMilliseconds(300), rtt_stats_.smoothed_rtt()); - // Verify that Smoothed RTT includes max ack delay if it's reasonable. - rtt_stats_.UpdateRtt(QuicTime::Delta::FromMilliseconds(300), - QuicTime::Delta::FromMilliseconds(50), QuicTime::Zero()); - EXPECT_EQ(QuicTime::Delta::FromMilliseconds(300), rtt_stats_.latest_rtt()); - EXPECT_EQ(QuicTime::Delta::FromMilliseconds(300), rtt_stats_.smoothed_rtt()); - // Verify that large erroneous ack_delay does not change Smoothed RTT. - rtt_stats_.UpdateRtt(QuicTime::Delta::FromMilliseconds(200), - QuicTime::Delta::FromMilliseconds(300), - QuicTime::Zero()); - EXPECT_EQ(QuicTime::Delta::FromMilliseconds(200), rtt_stats_.latest_rtt()); - EXPECT_EQ(QuicTime::Delta::FromMicroseconds(287500), - rtt_stats_.smoothed_rtt()); -} - // Ensure that the potential rounding artifacts in EWMA calculation do not cause // the SRTT to drift too far from the exact value. TEST_F(RttStatsTest, SmoothedRttStability) { diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_handshake_message.cc b/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_handshake_message.cc index 6dcb989b9e8..b591becf4f9 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_handshake_message.cc +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_handshake_message.cc @@ -16,7 +16,6 @@ #include "quic/core/crypto/crypto_utils.h" #include "quic/core/quic_socket_address_coder.h" #include "quic/core/quic_utils.h" -#include "quic/platform/api/quic_map_util.h" #include "common/quiche_endian.h" namespace quic { @@ -171,7 +170,7 @@ bool CryptoHandshakeMessage::GetStringPiece(QuicTag tag, } bool CryptoHandshakeMessage::HasStringPiece(QuicTag tag) const { - return QuicContainsKey(tag_value_map_, tag); + return tag_value_map_.find(tag) != tag_value_map_.end(); } QuicErrorCode CryptoHandshakeMessage::GetNthValue24( diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_protocol.h b/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_protocol.h index af62865b653..d594f487549 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_protocol.h +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_protocol.h @@ -28,7 +28,7 @@ using ServerConfigID = std::string; // The following tags have been deprecated and should not be reused: // "1CON", "BBQ4", "NCON", "RCID", "SREJ", "TBKP", "TB10", "SCLS", "SMHL", -// "QNZR", "B2HI", "H2PR", "FIFO", "LIFO", "RRWS", "QNSP" +// "QNZR", "B2HI", "H2PR", "FIFO", "LIFO", "RRWS", "QNSP", "B2CL" // clang-format off const QuicTag kCHLO = TAG('C', 'H', 'L', 'O'); // Client hello @@ -133,9 +133,6 @@ const QuicTag kB2NE = TAG('B', '2', 'N', 'E'); // For BBRv2, always exit // threshold. const QuicTag kB2RP = TAG('B', '2', 'R', 'P'); // For BBRv2, run PROBE_RTT on // the regular schedule -const QuicTag kB2CL = TAG('B', '2', 'C', 'L'); // For BBRv2, allow PROBE_BW - // cwnd to be below BDP + ack - // height. const QuicTag kB2LO = TAG('B', '2', 'L', 'O'); // Ignore inflight_lo in BBR2 const QuicTag kB2HR = TAG('B', '2', 'H', 'R'); // 15% inflight_hi headroom. const QuicTag kB2SL = TAG('B', '2', 'S', 'L'); // When exiting STARTUP due to @@ -344,6 +341,8 @@ const QuicTag kMTUL = TAG('M', 'T', 'U', 'L'); // Low-target MTU discovery. const QuicTag kNSLC = TAG('N', 'S', 'L', 'C'); // Always send connection close // for idle timeout. const QuicTag kCHSP = TAG('C', 'H', 'S', 'P'); // Chaos protection. +const QuicTag kBPTE = TAG('B', 'P', 'T', 'E'); // BoringSSL Permutes + // TLS Extensions. // Proof types (i.e. certificate types) // NOTE: although it would be silly to do so, specifying both kX509 and kX59R diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_server_test.cc b/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_server_test.cc index 7fadbbdcef7..753bd603f07 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_server_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_server_test.cc @@ -59,21 +59,19 @@ const char kOldConfigId[] = "old-config-id"; struct TestParams { friend std::ostream& operator<<(std::ostream& os, const TestParams& p) { os << " versions: " - << ParsedQuicVersionVectorToString(p.supported_versions) - << " } allow_sni_without_dots: " << p.allow_sni_without_dots; + << ParsedQuicVersionVectorToString(p.supported_versions) << " }"; return os; } // Versions supported by client and server. ParsedQuicVersionVector supported_versions; - bool allow_sni_without_dots; }; // Used by ::testing::PrintToStringParamName(). std::string PrintToString(const TestParams& p) { std::string rv = ParsedQuicVersionVectorToString(p.supported_versions); std::replace(rv.begin(), rv.end(), ',', '_'); - return absl::StrCat(rv, "_allow_sni_without_dots_", p.allow_sni_without_dots); + return rv; } // Constructs various test permutations. @@ -83,9 +81,7 @@ std::vector<TestParams> GetTestParams() { // Start with all versions, remove highest on each iteration. ParsedQuicVersionVector supported_versions = AllSupportedVersions(); while (!supported_versions.empty()) { - for (bool allow_sni_without_dots : {false, true}) { - params.push_back({supported_versions, allow_sni_without_dots}); - } + params.push_back({supported_versions}); supported_versions.erase(supported_versions.begin()); } @@ -109,8 +105,6 @@ class CryptoServerTest : public QuicTestWithParam<TestParams> { signed_config_(new QuicSignedServerConfig), chlo_packet_size_(kDefaultMaxPacketSize) { supported_versions_ = GetParam().supported_versions; - SetQuicReloadableFlag(quic_and_tls_allow_sni_without_dots, - GetParam().allow_sni_without_dots); config_.set_enable_serving_sct(true); client_version_ = supported_versions_.front(); @@ -387,9 +381,6 @@ TEST_P(CryptoServerTest, BadSNI) { "127.0.0.1", "ffee::1", }; - if (!GetParam().allow_sni_without_dots) { - badSNIs.push_back("foo"); - } // clang-format on for (const std::string& bad_sni : badSNIs) { @@ -402,12 +393,11 @@ TEST_P(CryptoServerTest, BadSNI) { CheckRejectReasons(kRejectReasons, ABSL_ARRAYSIZE(kRejectReasons)); } - if (GetParam().allow_sni_without_dots) { - CryptoHandshakeMessage msg = crypto_test_utils::CreateCHLO( - {{"PDMD", "X509"}, {"SNI", "foo"}, {"VER\0", client_version_string_}}, - kClientHelloMinimumSize); - ShouldSucceed(msg); - } + // Check that SNIs without dots are allowed + CryptoHandshakeMessage msg = crypto_test_utils::CreateCHLO( + {{"PDMD", "X509"}, {"SNI", "foo"}, {"VER\0", client_version_string_}}, + kClientHelloMinimumSize); + ShouldSucceed(msg); } TEST_P(CryptoServerTest, DefaultCert) { diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_utils.cc b/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_utils.cc index 67527d45826..9c12d8cf309 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_utils.cc +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_utils.cc @@ -713,35 +713,15 @@ const char* CryptoUtils::HandshakeFailureReasonToString( return "INVALID_HANDSHAKE_FAILURE_REASON"; } +#undef RETURN_STRING_LITERAL // undef for jumbo builds + // static std::string CryptoUtils::EarlyDataReasonToString( ssl_early_data_reason_t reason) { -#if BORINGSSL_API_VERSION >= 12 const char* reason_string = SSL_early_data_reason_string(reason); if (reason_string != nullptr) { return std::string("ssl_early_data_") + reason_string; } -#else - // TODO(davidben): Remove this logic once - // https://boringssl-review.googlesource.com/c/boringssl/+/43724 has landed in - // downstream repositories. - switch (reason) { - RETURN_STRING_LITERAL(ssl_early_data_unknown); - RETURN_STRING_LITERAL(ssl_early_data_disabled); - RETURN_STRING_LITERAL(ssl_early_data_accepted); - RETURN_STRING_LITERAL(ssl_early_data_protocol_version); - RETURN_STRING_LITERAL(ssl_early_data_peer_declined); - RETURN_STRING_LITERAL(ssl_early_data_no_session_offered); - RETURN_STRING_LITERAL(ssl_early_data_session_not_resumed); - RETURN_STRING_LITERAL(ssl_early_data_unsupported_for_session); - RETURN_STRING_LITERAL(ssl_early_data_hello_retry_request); - RETURN_STRING_LITERAL(ssl_early_data_alpn_mismatch); - RETURN_STRING_LITERAL(ssl_early_data_channel_id); - RETURN_STRING_LITERAL(ssl_early_data_token_binding); - RETURN_STRING_LITERAL(ssl_early_data_ticket_age_skew); - RETURN_STRING_LITERAL(ssl_early_data_quic_parameter_mismatch); - } -#endif QUIC_BUG_IF(quic_bug_12871_3, reason < 0 || reason > ssl_early_data_reason_max_value) << "Unknown ssl_early_data_reason_t " << reason; @@ -761,8 +741,6 @@ std::string CryptoUtils::HashHandshakeMessage( return output; } -#undef RETURN_STRING_LITERAL // undef for jumbo builds - // static bool CryptoUtils::GetSSLCapabilities(const SSL* ssl, bssl::UniquePtr<uint8_t>* capabilities, diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/key_exchange.cc b/chromium/net/third_party/quiche/src/quic/core/crypto/key_exchange.cc index c4e66c80e73..c97d5ddb512 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/key_exchange.cc +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/key_exchange.cc @@ -31,10 +31,8 @@ std::unique_ptr<SynchronousKeyExchange> CreateLocalSynchronousKeyExchange( switch (type) { case kC255: return Curve25519KeyExchange::New(rand); - break; case kP256: return P256KeyExchange::New(); - break; default: QUIC_BUG(quic_bug_10712_2) << "Unknown key exchange method: " << QuicTagToString(type); diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/proof_source.h b/chromium/net/third_party/quiche/src/quic/core/crypto/proof_source.h index 31cbfd66114..b0a24468a23 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/proof_source.h +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/proof_source.h @@ -166,6 +166,15 @@ class QUIC_EXPORT_PRIVATE ProofSource { absl::string_view in, std::unique_ptr<SignatureCallback> callback) = 0; + // Return the list of TLS signature algorithms that is acceptable by the + // ComputeTlsSignature method. If the entire BoringSSL's default list of + // supported signature algorithms are acceptable, return an empty list. + // + // If returns a non-empty list, ComputeTlsSignature will only be called with a + // algorithm in the list. + virtual absl::InlinedVector<uint16_t, 8> SupportedTlsSignatureAlgorithms() + const = 0; + class QUIC_EXPORT_PRIVATE DecryptCallback { public: DecryptCallback() = default; @@ -197,7 +206,12 @@ class QUIC_EXPORT_PRIVATE ProofSource { // returns the encrypted ticket. The resulting value must not be larger than // MaxOverhead bytes larger than |in|. If encryption fails, this method // returns an empty vector. - virtual std::vector<uint8_t> Encrypt(absl::string_view in) = 0; + // + // If |encryption_key| is nonempty, this method should use it for minting + // TLS resumption tickets. If it is empty, this method may use an + // internally cached encryption key, if available. + virtual std::vector<uint8_t> Encrypt(absl::string_view in, + absl::string_view encryption_key) = 0; // Decrypt takes an encrypted ticket |in|, decrypts it, and calls // |callback->Run| with the decrypted ticket, which must not be larger than @@ -231,13 +245,15 @@ class QUIC_EXPORT_PRIVATE ProofSourceHandleCallback { // |chain| the certificate chain in leaf-first order. // |handshake_hints| (optional) handshake hints that can be used by // SSL_set_handshake_hints. + // |ticket_encryption_key| (optional) encryption key to be used for minting + // TLS resumption tickets. // // When called asynchronously(is_sync=false), this method will be responsible // to continue the handshake from where it left off. - virtual void OnSelectCertificateDone(bool ok, - bool is_sync, - const ProofSource::Chain* chain, - absl::string_view handshake_hints) = 0; + virtual void OnSelectCertificateDone( + bool ok, bool is_sync, const ProofSource::Chain* chain, + absl::string_view handshake_hints, + absl::string_view ticket_encryption_key) = 0; // Called when a ProofSourceHandle::ComputeSignature operation completes. virtual void OnComputeSignatureDone( @@ -245,6 +261,10 @@ class QUIC_EXPORT_PRIVATE ProofSourceHandleCallback { bool is_sync, std::string signature, std::unique_ptr<ProofSource::Details> details) = 0; + + // Return true iff ProofSourceHandle::ComputeSignature won't be called later. + // The handle can use this function to release resources promptly. + virtual bool WillNotCallComputeSignature() const = 0; }; // ProofSourceHandle is an interface by which a TlsServerHandshaker can obtain @@ -264,13 +284,17 @@ class QUIC_EXPORT_PRIVATE ProofSourceHandle { public: virtual ~ProofSourceHandle() = default; - // Cancel the pending operation, if any. - // Once called, any completion method on |callback()| won't be invoked. - virtual void CancelPendingOperation() = 0; + // Close the handle. Cancel the pending operation, if any. + // Once called, any completion method on |callback()| won't be invoked, and + // future SelectCertificate and ComputeSignature calls should return failure. + virtual void CloseHandle() = 0; // Starts a select certificate operation. If the operation is not cancelled // when it completes, callback()->OnSelectCertificateDone will be invoked. // + // server_address and client_address should be normalized by the caller before + // sending down to this function. + // // If the operation is handled synchronously: // - QUIC_SUCCESS or QUIC_FAILURE will be returned. // - callback()->OnSelectCertificateDone should be invoked before the function @@ -289,7 +313,8 @@ class QUIC_EXPORT_PRIVATE ProofSourceHandle { const std::string& alpn, absl::optional<std::string> alps, const std::vector<uint8_t>& quic_transport_params, - const absl::optional<std::vector<uint8_t>>& early_data_context) = 0; + const absl::optional<std::vector<uint8_t>>& early_data_context, + const QuicSSLConfig& ssl_config) = 0; // Starts a compute signature operation. If the operation is not cancelled // when it completes, callback()->OnComputeSignatureDone will be invoked. diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/proof_source_x509.cc b/chromium/net/third_party/quiche/src/quic/core/crypto/proof_source_x509.cc index d61df716c3a..c7acd0056bc 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/proof_source_x509.cc +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/proof_source_x509.cc @@ -80,6 +80,13 @@ void ProofSourceX509::ComputeTlsSignature( callback->Run(/*ok=*/!signature.empty(), signature, nullptr); } +absl::InlinedVector<uint16_t, 8> +ProofSourceX509::SupportedTlsSignatureAlgorithms() const { + // Let ComputeTlsSignature() report an error if a bad signature algorithm is + // requested. + return {}; +} + ProofSource::TicketCrypter* ProofSourceX509::GetTicketCrypter() { return nullptr; } diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/proof_source_x509.h b/chromium/net/third_party/quiche/src/quic/core/crypto/proof_source_x509.h index 83de1de204e..9ac676939a3 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/proof_source_x509.h +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/proof_source_x509.h @@ -41,11 +41,11 @@ class QUIC_EXPORT_PRIVATE ProofSourceX509 : public ProofSource { const std::string& hostname) override; void ComputeTlsSignature( const QuicSocketAddress& server_address, - const QuicSocketAddress& client_address, - const std::string& hostname, - uint16_t signature_algorithm, - absl::string_view in, + const QuicSocketAddress& client_address, const std::string& hostname, + uint16_t signature_algorithm, absl::string_view in, std::unique_ptr<SignatureCallback> callback) override; + absl::InlinedVector<uint16_t, 8> SupportedTlsSignatureAlgorithms() + const override; TicketCrypter* GetTicketCrypter() override; // Adds a certificate chain to the verifier. Returns false if the chain is diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/quic_crypto_client_config.cc b/chromium/net/third_party/quiche/src/quic/core/crypto/quic_crypto_client_config.cc index 3f94e7cda60..99f92793c0e 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/quic_crypto_client_config.cc +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/quic_crypto_client_config.cc @@ -33,7 +33,6 @@ #include "quic/platform/api/quic_client_stats.h" #include "quic/platform/api/quic_hostname_utils.h" #include "quic/platform/api/quic_logging.h" -#include "quic/platform/api/quic_map_util.h" namespace quic { @@ -848,22 +847,23 @@ bool QuicCryptoClientConfig::PopulateFromCanonicalConfig( QuicServerId suffix_server_id(canonical_suffixes_[i], server_id.port(), server_id.privacy_mode_enabled()); - if (!QuicContainsKey(canonical_server_map_, suffix_server_id)) { + auto it = canonical_server_map_.lower_bound(suffix_server_id); + if (it == canonical_server_map_.end() || it->first != suffix_server_id) { // This is the first host we've seen which matches the suffix, so make it - // canonical. - canonical_server_map_[suffix_server_id] = server_id; + // canonical. Use |it| as position hint for faster insertion. + canonical_server_map_.insert( + it, std::make_pair(std::move(suffix_server_id), std::move(server_id))); return false; } - const QuicServerId& canonical_server_id = - canonical_server_map_[suffix_server_id]; + const QuicServerId& canonical_server_id = it->second; CachedState* canonical_state = cached_states_[canonical_server_id].get(); if (!canonical_state->proof_valid()) { return false; } // Update canonical version to point at the "most recent" entry. - canonical_server_map_[suffix_server_id] = server_id; + it->second = server_id; server_state->InitializeFrom(*canonical_state); return true; diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/quic_crypto_client_config.h b/chromium/net/third_party/quiche/src/quic/core/crypto/quic_crypto_client_config.h index bb0626094db..847fc96ebe6 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/quic_crypto_client_config.h +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/quic_crypto_client_config.h @@ -362,6 +362,14 @@ class QUIC_EXPORT_PRIVATE QuicCryptoClientConfig : public QuicCryptoConfig { // handshake message. const std::string& user_agent_id() const { return user_agent_id_; } + void set_tls_signature_algorithms(std::string signature_algorithms) { + tls_signature_algorithms_ = std::move(signature_algorithms); + } + + const absl::optional<std::string>& tls_signature_algorithms() const { + return tls_signature_algorithms_; + } + // Saves the |alpn| that will be passed in QUIC's CHLO message. void set_alpn(const std::string& alpn) { alpn_ = alpn; } @@ -435,6 +443,10 @@ class QUIC_EXPORT_PRIVATE QuicCryptoClientConfig : public QuicCryptoConfig { // incorporating |pre_shared_key_| into the key schedule. std::string pre_shared_key_; + // If set, configure the client to use the specified signature algorithms, via + // SSL_set1_sigalgs_list. TLS only. + absl::optional<std::string> tls_signature_algorithms_; + // In QUIC, technically, client hello should be fully padded. // However, fully padding on slow network connection (e.g. 50kbps) can add // 150ms latency to one roundtrip. Therefore, you can disable padding of diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/quic_crypto_client_config_test.cc b/chromium/net/third_party/quiche/src/quic/core/crypto/quic_crypto_client_config_test.cc index 6dd09677fb9..5bcf01cd074 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/quic_crypto_client_config_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/quic_crypto_client_config_test.cc @@ -179,6 +179,7 @@ TEST_F(QuicCryptoClientConfigTest, InchoateChloSecureWithSCIDNoEXPY) { QuicWallTime expiry = QuicWallTime::FromUNIXSeconds(2); state.SetServerConfig(scfg.GetSerialized().AsStringPiece(), now, expiry, &details); + EXPECT_FALSE(state.IsEmpty()); QuicCryptoClientConfig config(crypto_test_utils::ProofVerifierForTesting()); QuicReferenceCountedPointer<QuicCryptoNegotiatedParameters> params( @@ -205,6 +206,7 @@ TEST_F(QuicCryptoClientConfigTest, InchoateChloSecureWithSCID) { state.SetServerConfig(scfg.GetSerialized().AsStringPiece(), QuicWallTime::FromUNIXSeconds(1), QuicWallTime::FromUNIXSeconds(0), &details); + EXPECT_FALSE(state.IsEmpty()); QuicCryptoClientConfig config(crypto_test_utils::ProofVerifierForTesting()); QuicReferenceCountedPointer<QuicCryptoNegotiatedParameters> params( @@ -503,5 +505,46 @@ TEST_F(QuicCryptoClientConfigTest, ServerNonceinSHLO) { EXPECT_EQ("server hello missing server nonce", error_details); } +// Test that PopulateFromCanonicalConfig() handles the case of multiple entries +// in |canonical_server_map_|. +TEST_F(QuicCryptoClientConfigTest, MultipleCanonicalEntries) { + QuicCryptoClientConfig config(crypto_test_utils::ProofVerifierForTesting()); + config.AddCanonicalSuffix(".google.com"); + QuicServerId canonical_server_id1("www.google.com", 443, false); + QuicCryptoClientConfig::CachedState* state1 = + config.LookupOrCreate(canonical_server_id1); + + CryptoHandshakeMessage scfg; + scfg.set_tag(kSCFG); + scfg.SetStringPiece(kSCID, "12345678"); + std::string details; + QuicWallTime now = QuicWallTime::FromUNIXSeconds(1); + QuicWallTime expiry = QuicWallTime::FromUNIXSeconds(2); + state1->SetServerConfig(scfg.GetSerialized().AsStringPiece(), now, expiry, + &details); + state1->set_source_address_token("TOKEN"); + state1->SetProofValid(); + EXPECT_FALSE(state1->IsEmpty()); + + // This will have the same |suffix_server_id| as |canonical_server_id1|, + // therefore |*state2| will be initialized from |*state1|. + QuicServerId canonical_server_id2("mail.google.com", 443, false); + QuicCryptoClientConfig::CachedState* state2 = + config.LookupOrCreate(canonical_server_id2); + EXPECT_FALSE(state2->IsEmpty()); + const CryptoHandshakeMessage* const scfg2 = state2->GetServerConfig(); + ASSERT_TRUE(scfg2); + EXPECT_EQ(kSCFG, scfg2->tag()); + + // With a different |suffix_server_id|, this will return an empty CachedState. + config.AddCanonicalSuffix(".example.com"); + QuicServerId canonical_server_id3("www.example.com", 443, false); + QuicCryptoClientConfig::CachedState* state3 = + config.LookupOrCreate(canonical_server_id3); + EXPECT_TRUE(state3->IsEmpty()); + const CryptoHandshakeMessage* const scfg3 = state3->GetServerConfig(); + EXPECT_FALSE(scfg3); +} + } // namespace test } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/quic_crypto_server_config.h b/chromium/net/third_party/quiche/src/quic/core/crypto/quic_crypto_server_config.h index 4f3ad0819e4..15ce0f276d7 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/quic_crypto_server_config.h +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/quic_crypto_server_config.h @@ -507,9 +507,6 @@ class QUIC_EXPORT_PRIVATE QuicCryptoServerConfig { // one-to-one, with the tags in |kexs| from the parent class. std::vector<std::unique_ptr<AsynchronousKeyExchange>> key_exchanges; - // tag_value_map contains the raw key/value pairs for the config. - QuicTagValueMap tag_value_map; - // channel_id_enabled is true if the config in |serialized| specifies that // ChannelIDs are supported. bool channel_id_enabled; diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/quic_random.cc b/chromium/net/third_party/quiche/src/quic/core/crypto/quic_random.cc index 56657b507f9..d3190c626e7 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/quic_random.cc +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/quic_random.cc @@ -19,17 +19,22 @@ namespace { // xoshiro256++ 1.0 based on code in the public domain from // <http://prng.di.unimi.it/xoshiro256plusplus.c>. +inline uint64_t Xoshiro256InitializeRngStateMember() { + uint64_t result; + RAND_bytes(reinterpret_cast<uint8_t*>(&result), sizeof(result)); + return result; +} + inline uint64_t Xoshiro256PlusPlusRotLeft(uint64_t x, int k) { return (x << k) | (x >> (64 - k)); } uint64_t Xoshiro256PlusPlus() { - static thread_local uint64_t rng_state[4]; - static thread_local bool rng_state_initialized = false; - if (QUIC_PREDICT_FALSE(!rng_state_initialized)) { - RAND_bytes(reinterpret_cast<uint8_t*>(&rng_state), sizeof(rng_state)); - rng_state_initialized = true; - } + static thread_local uint64_t rng_state[4] = { + Xoshiro256InitializeRngStateMember(), + Xoshiro256InitializeRngStateMember(), + Xoshiro256InitializeRngStateMember(), + Xoshiro256InitializeRngStateMember()}; const uint64_t result = Xoshiro256PlusPlusRotLeft(rng_state[0] + rng_state[3], 23) + rng_state[0]; const uint64_t t = rng_state[1] << 17; diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/tls_client_connection.cc b/chromium/net/third_party/quiche/src/quic/core/crypto/tls_client_connection.cc index c6a45c4b834..dd53ee7037c 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/tls_client_connection.cc +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/tls_client_connection.cc @@ -6,8 +6,12 @@ namespace quic { -TlsClientConnection::TlsClientConnection(SSL_CTX* ssl_ctx, Delegate* delegate) - : TlsConnection(ssl_ctx, delegate->ConnectionDelegate()), +TlsClientConnection::TlsClientConnection(SSL_CTX* ssl_ctx, + Delegate* delegate, + QuicSSLConfig ssl_config) + : TlsConnection(ssl_ctx, + delegate->ConnectionDelegate(), + std::move(ssl_config)), delegate_(delegate) {} // static @@ -24,6 +28,8 @@ bssl::UniquePtr<SSL_CTX> TlsClientConnection::CreateSslCtx( ssl_ctx.get(), SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL); SSL_CTX_sess_set_new_cb(ssl_ctx.get(), NewSessionCallback); + // TODO(wub): Always enable early data on the SSL_CTX, but allow it to be + // overridden on the SSL object, via QuicSSLConfig. SSL_CTX_set_early_data_enabled(ssl_ctx.get(), enable_early_data); return ssl_ctx; } diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/tls_client_connection.h b/chromium/net/third_party/quiche/src/quic/core/crypto/tls_client_connection.h index c9471536b67..ce4b94804a6 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/tls_client_connection.h +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/tls_client_connection.h @@ -30,7 +30,9 @@ class QUIC_EXPORT_PRIVATE TlsClientConnection : public TlsConnection { friend class TlsClientConnection; }; - TlsClientConnection(SSL_CTX* ssl_ctx, Delegate* delegate); + TlsClientConnection(SSL_CTX* ssl_ctx, + Delegate* delegate, + QuicSSLConfig ssl_config); // Creates and configures an SSL_CTX that is appropriate for clients to use. static bssl::UniquePtr<SSL_CTX> CreateSslCtx(bool enable_early_data); diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/tls_connection.cc b/chromium/net/third_party/quiche/src/quic/core/crypto/tls_connection.cc index 381de7eeafb..7a66e2f8f8d 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/tls_connection.cc +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/tls_connection.cc @@ -5,6 +5,7 @@ #include "quic/core/crypto/tls_connection.h" #include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/ssl.h" #include "quic/platform/api/quic_bug_tracker.h" namespace quic { @@ -88,12 +89,32 @@ enum ssl_encryption_level_t TlsConnection::BoringEncryptionLevel( } TlsConnection::TlsConnection(SSL_CTX* ssl_ctx, - TlsConnection::Delegate* delegate) - : delegate_(delegate), ssl_(SSL_new(ssl_ctx)) { + TlsConnection::Delegate* delegate, + QuicSSLConfig ssl_config) + : delegate_(delegate), + ssl_(SSL_new(ssl_ctx)), + ssl_config_(std::move(ssl_config)) { SSL_set_ex_data( ssl(), SslIndexSingleton::GetInstance()->ssl_ex_data_index_connection(), this); + if (ssl_config_.early_data_enabled.has_value()) { + const int early_data_enabled = *ssl_config_.early_data_enabled ? 1 : 0; + SSL_set_early_data_enabled(ssl(), early_data_enabled); + } + if (ssl_config_.signing_algorithm_prefs.has_value()) { + SSL_set_signing_algorithm_prefs( + ssl(), ssl_config_.signing_algorithm_prefs->data(), + ssl_config_.signing_algorithm_prefs->size()); + } } + +void TlsConnection::EnableInfoCallback() { + SSL_set_info_callback( + ssl(), +[](const SSL* ssl, int type, int value) { + ConnectionFromSsl(ssl)->delegate_->InfoCallback(type, value); + }); +} + // static bssl::UniquePtr<SSL_CTX> TlsConnection::CreateSslCtx(int cert_verify_mode) { CRYPTO_library_init(); diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/tls_connection.h b/chromium/net/third_party/quiche/src/quic/core/crypto/tls_connection.h index 28b5684ad4a..f59eaa13150 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/tls_connection.h +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/tls_connection.h @@ -75,12 +75,21 @@ class QUIC_EXPORT_PRIVATE TlsConnection { // level |level|. virtual void SendAlert(EncryptionLevel level, uint8_t desc) = 0; + // Informational callback from BoringSSL. This callback is disabled by + // default, but can be enabled by TlsConnection::EnableInfoCallback. + // + // See |SSL_CTX_set_info_callback| for the meaning of |type| and |value|. + virtual void InfoCallback(int type, int value) = 0; + friend class TlsConnection; }; TlsConnection(const TlsConnection&) = delete; TlsConnection& operator=(const TlsConnection&) = delete; + // Configure the SSL such that delegate_->InfoCallback will be called. + void EnableInfoCallback(); + // Functions to convert between BoringSSL's enum ssl_encryption_level_t and // QUIC's EncryptionLevel. static EncryptionLevel QuicEncryptionLevel(enum ssl_encryption_level_t level); @@ -89,10 +98,12 @@ class QUIC_EXPORT_PRIVATE TlsConnection { SSL* ssl() const { return ssl_.get(); } + const QuicSSLConfig& ssl_config() const { return ssl_config_; } + protected: - // TlsConnection does not take ownership of any of its arguments; they must + // TlsConnection does not take ownership of |ssl_ctx| or |delegate|; they must // outlive the TlsConnection object. - TlsConnection(SSL_CTX* ssl_ctx, Delegate* delegate); + TlsConnection(SSL_CTX* ssl_ctx, Delegate* delegate, QuicSSLConfig ssl_config); // Creates an SSL_CTX and configures it with the options that are appropriate // for both client and server. The caller is responsible for ownership of the @@ -141,6 +152,7 @@ class QUIC_EXPORT_PRIVATE TlsConnection { Delegate* delegate_; bssl::UniquePtr<SSL> ssl_; + const QuicSSLConfig ssl_config_; }; } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/tls_server_connection.cc b/chromium/net/third_party/quiche/src/quic/core/crypto/tls_server_connection.cc index 6e9901b995e..2042c15aaf2 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/tls_server_connection.cc +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/tls_server_connection.cc @@ -12,8 +12,12 @@ namespace quic { -TlsServerConnection::TlsServerConnection(SSL_CTX* ssl_ctx, Delegate* delegate) - : TlsConnection(ssl_ctx, delegate->ConnectionDelegate()), +TlsServerConnection::TlsServerConnection(SSL_CTX* ssl_ctx, + Delegate* delegate, + QuicSSLConfig ssl_config) + : TlsConnection(ssl_ctx, + delegate->ConnectionDelegate(), + std::move(ssl_config)), delegate_(delegate) {} // static diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/tls_server_connection.h b/chromium/net/third_party/quiche/src/quic/core/crypto/tls_server_connection.h index 774bb440f64..6c775b86cd7 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/tls_server_connection.h +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/tls_server_connection.h @@ -120,7 +120,9 @@ class QUIC_EXPORT_PRIVATE TlsServerConnection : public TlsConnection { friend class TlsServerConnection; }; - TlsServerConnection(SSL_CTX* ssl_ctx, Delegate* delegate); + TlsServerConnection(SSL_CTX* ssl_ctx, + Delegate* delegate, + QuicSSLConfig ssl_config); // Creates and configures an SSL_CTX that is appropriate for servers to use. static bssl::UniquePtr<SSL_CTX> CreateSslCtx(ProofSource* proof_source); diff --git a/chromium/net/third_party/quiche/src/quic/core/frames/quic_frame.cc b/chromium/net/third_party/quiche/src/quic/core/frames/quic_frame.cc index ad10d601c31..c31b45e322e 100644 --- a/chromium/net/third_party/quiche/src/quic/core/frames/quic_frame.cc +++ b/chromium/net/third_party/quiche/src/quic/core/frames/quic_frame.cc @@ -391,11 +391,9 @@ QuicFrame CopyQuicFrame(QuicBufferAllocator* allocator, copy.message_frame->data = frame.message_frame->data; copy.message_frame->message_length = frame.message_frame->message_length; for (const auto& slice : frame.message_frame->message_data) { - QuicUniqueBufferPtr buffer = - MakeUniqueBuffer(allocator, slice.length()); - memcpy(buffer.get(), slice.data(), slice.length()); + QuicBuffer buffer = QuicBuffer::Copy(allocator, slice.AsStringView()); copy.message_frame->message_data.push_back( - QuicMemSlice(std::move(buffer), slice.length())); + QuicMemSlice(std::move(buffer))); } break; case NEW_TOKEN_FRAME: diff --git a/chromium/net/third_party/quiche/src/quic/core/frames/quic_frame.h b/chromium/net/third_party/quiche/src/quic/core/frames/quic_frame.h index 1efe1e662d7..9ee45689117 100644 --- a/chromium/net/third_party/quiche/src/quic/core/frames/quic_frame.h +++ b/chromium/net/third_party/quiche/src/quic/core/frames/quic_frame.h @@ -9,6 +9,7 @@ #include <type_traits> #include <vector> +#include "absl/container/inlined_vector.h" #include "quic/core/frames/quic_ack_frame.h" #include "quic/core/frames/quic_ack_frequency_frame.h" #include "quic/core/frames/quic_blocked_frame.h" @@ -133,7 +134,7 @@ static_assert(offsetof(QuicStreamFrame, type) == offsetof(QuicFrame, type), // A inline size of 1 is chosen to optimize the typical use case of // 1-stream-frame in QuicTransmissionInfo.retransmittable_frames. -using QuicFrames = QuicInlinedVector<QuicFrame, 1>; +using QuicFrames = absl::InlinedVector<QuicFrame, 1>; // Deletes all the sub-frames contained in |frames|. QUIC_EXPORT_PRIVATE void DeleteFrames(QuicFrames* frames); diff --git a/chromium/net/third_party/quiche/src/quic/core/frames/quic_frames_test.cc b/chromium/net/third_party/quiche/src/quic/core/frames/quic_frames_test.cc index c11be9eb016..038924e2165 100644 --- a/chromium/net/third_party/quiche/src/quic/core/frames/quic_frames_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/frames/quic_frames_test.cc @@ -543,10 +543,8 @@ TEST_F(QuicFramesTest, RemoveSmallestInterval) { TEST_F(QuicFramesTest, CopyQuicFrames) { QuicFrames frames; - SimpleBufferAllocator allocator; - QuicMemSliceStorage storage(nullptr, 0, nullptr, 0); QuicMessageFrame* message_frame = - new QuicMessageFrame(1, MakeSpan(&allocator, "message", &storage)); + new QuicMessageFrame(1, MemSliceFromString("message")); // Construct a frame list. for (uint8_t i = 0; i < NUM_FRAME_TYPES; ++i) { switch (i) { @@ -626,7 +624,7 @@ TEST_F(QuicFramesTest, CopyQuicFrames) { } } - QuicFrames copy = CopyQuicFrames(&allocator, frames); + QuicFrames copy = CopyQuicFrames(SimpleBufferAllocator::Get(), frames); ASSERT_EQ(NUM_FRAME_TYPES, copy.size()); for (uint8_t i = 0; i < NUM_FRAME_TYPES; ++i) { EXPECT_EQ(i, copy[i].type); diff --git a/chromium/net/third_party/quiche/src/quic/core/frames/quic_message_frame.cc b/chromium/net/third_party/quiche/src/quic/core/frames/quic_message_frame.cc index 054303ac1ee..1ac0de67035 100644 --- a/chromium/net/third_party/quiche/src/quic/core/frames/quic_message_frame.cc +++ b/chromium/net/third_party/quiche/src/quic/core/frames/quic_message_frame.cc @@ -14,13 +14,18 @@ QuicMessageFrame::QuicMessageFrame(QuicMessageId message_id) : message_id(message_id), data(nullptr), message_length(0) {} QuicMessageFrame::QuicMessageFrame(QuicMessageId message_id, - QuicMemSliceSpan span) + absl::Span<QuicMemSlice> span) : message_id(message_id), data(nullptr), message_length(0) { - span.ConsumeAll([&](QuicMemSlice slice) { + for (QuicMemSlice& slice : span) { + if (slice.empty()) { + continue; + } message_length += slice.length(); message_data.push_back(std::move(slice)); - }); + } } +QuicMessageFrame::QuicMessageFrame(QuicMessageId message_id, QuicMemSlice slice) + : QuicMessageFrame(message_id, absl::MakeSpan(&slice, 1)) {} QuicMessageFrame::QuicMessageFrame(const char* data, QuicPacketLength length) : message_id(0), data(data), message_length(length) {} diff --git a/chromium/net/third_party/quiche/src/quic/core/frames/quic_message_frame.h b/chromium/net/third_party/quiche/src/quic/core/frames/quic_message_frame.h index ef1e070deb5..1d090c902e1 100644 --- a/chromium/net/third_party/quiche/src/quic/core/frames/quic_message_frame.h +++ b/chromium/net/third_party/quiche/src/quic/core/frames/quic_message_frame.h @@ -5,20 +5,22 @@ #ifndef QUICHE_QUIC_CORE_FRAMES_QUIC_MESSAGE_FRAME_H_ #define QUICHE_QUIC_CORE_FRAMES_QUIC_MESSAGE_FRAME_H_ +#include "absl/container/inlined_vector.h" +#include "absl/types/span.h" #include "quic/core/quic_types.h" #include "quic/platform/api/quic_containers.h" #include "quic/platform/api/quic_export.h" #include "quic/platform/api/quic_mem_slice.h" -#include "quic/platform/api/quic_mem_slice_span.h" namespace quic { -using QuicMessageData = QuicInlinedVector<QuicMemSlice, 1>; +using QuicMessageData = absl::InlinedVector<QuicMemSlice, 1>; struct QUIC_EXPORT_PRIVATE QuicMessageFrame { QuicMessageFrame() = default; explicit QuicMessageFrame(QuicMessageId message_id); - QuicMessageFrame(QuicMessageId message_id, QuicMemSliceSpan span); + QuicMessageFrame(QuicMessageId message_id, absl::Span<QuicMemSlice> span); + QuicMessageFrame(QuicMessageId message_id, QuicMemSlice slice); QuicMessageFrame(const char* data, QuicPacketLength length); QuicMessageFrame(const QuicMessageFrame& other) = delete; diff --git a/chromium/net/third_party/quiche/src/quic/core/frames/quic_new_connection_id_frame.h b/chromium/net/third_party/quiche/src/quic/core/frames/quic_new_connection_id_frame.h index 9c3d2053d34..235d2af792b 100644 --- a/chromium/net/third_party/quiche/src/quic/core/frames/quic_new_connection_id_frame.h +++ b/chromium/net/third_party/quiche/src/quic/core/frames/quic_new_connection_id_frame.h @@ -32,7 +32,7 @@ struct QUIC_EXPORT_PRIVATE QuicNewConnectionIdFrame { QuicConnectionId connection_id = EmptyQuicConnectionId(); QuicConnectionIdSequenceNumber sequence_number = 0; StatelessResetToken stateless_reset_token; - uint64_t retire_prior_to; + uint64_t retire_prior_to = 0; }; } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/frames/quic_path_challenge_frame.h b/chromium/net/third_party/quiche/src/quic/core/frames/quic_path_challenge_frame.h index 7c27a4258b3..6966775a30d 100644 --- a/chromium/net/third_party/quiche/src/quic/core/frames/quic_path_challenge_frame.h +++ b/chromium/net/third_party/quiche/src/quic/core/frames/quic_path_challenge_frame.h @@ -27,7 +27,7 @@ struct QUIC_EXPORT_PRIVATE QuicPathChallengeFrame { // and non-zero when sent. QuicControlFrameId control_frame_id = kInvalidControlFrameId; - QuicPathFrameBuffer data_buffer; + QuicPathFrameBuffer data_buffer{}; }; } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/frames/quic_path_response_frame.h b/chromium/net/third_party/quiche/src/quic/core/frames/quic_path_response_frame.h index 6b43bda16ae..a4a75be208b 100644 --- a/chromium/net/third_party/quiche/src/quic/core/frames/quic_path_response_frame.h +++ b/chromium/net/third_party/quiche/src/quic/core/frames/quic_path_response_frame.h @@ -27,7 +27,7 @@ struct QUIC_EXPORT_PRIVATE QuicPathResponseFrame { // and non-zero when sent. QuicControlFrameId control_frame_id = kInvalidControlFrameId; - QuicPathFrameBuffer data_buffer; + QuicPathFrameBuffer data_buffer{}; }; } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/frames/quic_streams_blocked_frame.h b/chromium/net/third_party/quiche/src/quic/core/frames/quic_streams_blocked_frame.h index 94676fb00da..2d738901776 100644 --- a/chromium/net/third_party/quiche/src/quic/core/frames/quic_streams_blocked_frame.h +++ b/chromium/net/third_party/quiche/src/quic/core/frames/quic_streams_blocked_frame.h @@ -35,7 +35,7 @@ struct QUIC_EXPORT_PRIVATE QuicStreamsBlockedFrame QuicControlFrameId control_frame_id = kInvalidControlFrameId; // The number of streams that the sender wishes to exceed - QuicStreamCount stream_count; + QuicStreamCount stream_count = 0; // Whether uni- or bi-directional streams bool unidirectional = false; diff --git a/chromium/net/third_party/quiche/src/quic/core/frames/quic_window_update_frame.h b/chromium/net/third_party/quiche/src/quic/core/frames/quic_window_update_frame.h index e4f2a86eac7..372a4ea598e 100644 --- a/chromium/net/third_party/quiche/src/quic/core/frames/quic_window_update_frame.h +++ b/chromium/net/third_party/quiche/src/quic/core/frames/quic_window_update_frame.h @@ -31,11 +31,11 @@ struct QUIC_EXPORT_PRIVATE QuicWindowUpdateFrame { // The stream this frame applies to. 0 is a special case meaning the overall // connection rather than a specific stream. - QuicStreamId stream_id; + QuicStreamId stream_id = 0; // Maximum data allowed in the stream or connection. The receiver of this // frame must not send data which would exceedes this restriction. - QuicByteCount max_data; + QuicByteCount max_data = 0; }; } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/http/end_to_end_test.cc b/chromium/net/third_party/quiche/src/quic/core/http/end_to_end_test.cc index 8efba8869bf..e64d8e62581 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/end_to_end_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/http/end_to_end_test.cc @@ -363,7 +363,6 @@ class EndToEndTest : public QuicTestWithParam<TestParams> { bool Initialize() { if (enable_web_transport_) { - SetQuicReloadableFlag(quic_h3_datagram, true); memory_cache_backend_.set_enable_webtransport(true); } @@ -1883,11 +1882,6 @@ TEST_P(EndToEndTest, RetransmissionAfterZeroRTTRejectBeforeOneRtt) { ON_CALL(visitor, OnZeroRttRejected(_)).WillByDefault(Invoke([this]() { EXPECT_FALSE(GetClientSession()->IsEncryptionEstablished()); - if (!GetQuicReloadableFlag(quic_donot_write_mid_packet_processing)) { - // Trigger an OnCanWrite() to make sure no unencrypted data will be - // written. - GetClientSession()->OnCanWrite(); - } })); // The 0-RTT handshake should fail. @@ -3882,7 +3876,7 @@ TEST_P(EndToEndTest, BadPacketHeaderFlags) { SendSynchronousFooRequestAndCheckResponse(); // Packet with invalid public flags. - char packet[] = { + uint8_t packet[] = { // invalid public flags 0xFF, // connection_id @@ -3905,7 +3899,7 @@ TEST_P(EndToEndTest, BadPacketHeaderFlags) { 0x00, }; client_writer_->WritePacket( - &packet[0], sizeof(packet), + reinterpret_cast<const char*>(packet), sizeof(packet), client_->client()->network_helper()->GetLatestClientAddress().host(), server_address_, nullptr); @@ -4739,20 +4733,15 @@ TEST_P(EndToEndTest, SendMessages) { ASSERT_LT(0, client_session->GetCurrentLargestMessagePayload()); std::string message_string(kMaxOutgoingPacketSize, 'a'); - absl::string_view message_buffer(message_string); QuicRandom* random = QuicConnectionPeer::GetHelper(client_connection)->GetRandomGenerator(); - QuicMemSliceStorage storage(nullptr, 0, nullptr, 0); { QuicConnection::ScopedPacketFlusher flusher(client_session->connection()); // Verify the largest message gets successfully sent. EXPECT_EQ(MessageResult(MESSAGE_STATUS_SUCCESS, 1), - client_session->SendMessage(MakeSpan( - client_connection->helper()->GetStreamSendBufferAllocator(), - absl::string_view( - message_buffer.data(), - client_session->GetCurrentLargestMessagePayload()), - &storage))); + client_session->SendMessage(MemSliceFromString(absl::string_view( + message_string.data(), + client_session->GetCurrentLargestMessagePayload())))); // Send more messages with size (0, largest_payload] until connection is // write blocked. const int kTestMaxNumberOfMessages = 100; @@ -4761,9 +4750,8 @@ TEST_P(EndToEndTest, SendMessages) { random->RandUint64() % client_session->GetGuaranteedLargestMessagePayload() + 1; - MessageResult result = client_session->SendMessage(MakeSpan( - client_connection->helper()->GetStreamSendBufferAllocator(), - absl::string_view(message_buffer.data(), message_length), &storage)); + MessageResult result = client_session->SendMessage(MemSliceFromString( + absl::string_view(message_string.data(), message_length))); if (result.status == MESSAGE_STATUS_BLOCKED) { // Connection is write blocked. break; @@ -4775,12 +4763,9 @@ TEST_P(EndToEndTest, SendMessages) { client_->WaitForDelayedAcks(); EXPECT_EQ(MESSAGE_STATUS_TOO_LARGE, client_session - ->SendMessage(MakeSpan( - client_connection->helper()->GetStreamSendBufferAllocator(), - absl::string_view( - message_buffer.data(), - client_session->GetCurrentLargestMessagePayload() + 1), - &storage)) + ->SendMessage(MemSliceFromString(absl::string_view( + message_string.data(), + client_session->GetCurrentLargestMessagePayload() + 1))) .status); EXPECT_THAT(client_->connection_error(), IsQuicNoError()); } @@ -5698,6 +5683,18 @@ TEST_P(EndToEndTest, ChaosProtectionWithMultiPacketChlo) { SendSynchronousFooRequestAndCheckResponse(); } +TEST_P(EndToEndTest, PermuteTlsExtensions) { + if (!version_.UsesTls()) { + ASSERT_TRUE(Initialize()); + return; + } + // Enable TLS extension permutation and perform an HTTP request. + client_config_.SetClientConnectionOptions(QuicTagVector{kBPTE}); + ASSERT_TRUE(Initialize()); + EXPECT_TRUE(GetClientSession()->permutes_tls_extensions()); + SendSynchronousFooRequestAndCheckResponse(); +} + TEST_P(EndToEndTest, KeyUpdateInitiatedByClient) { if (!version_.UsesTls()) { // Key Update is only supported in TLS handshake. @@ -6043,6 +6040,7 @@ TEST_P(EndToEndTest, WebTransportSessionSetup) { WebTransportHttp3* web_transport = CreateWebTransportSession("/echo", /*wait_for_server_response=*/true); + ASSERT_NE(web_transport, nullptr); server_thread_->Pause(); QuicSpdySession* server_session = GetServerSession(); @@ -6064,6 +6062,7 @@ TEST_P(EndToEndTest, WebTransportSessionWithLoss) { WebTransportHttp3* web_transport = CreateWebTransportSession("/echo", /*wait_for_server_response=*/true); + ASSERT_NE(web_transport, nullptr); server_thread_->Pause(); QuicSpdySession* server_session = GetServerSession(); diff --git a/chromium/net/third_party/quiche/src/quic/core/http/http_decoder.cc b/chromium/net/third_party/quiche/src/quic/core/http/http_decoder.cc index 6d9290a12c1..de86955739b 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/http_decoder.cc +++ b/chromium/net/third_party/quiche/src/quic/core/http/http_decoder.cc @@ -33,20 +33,9 @@ HttpDecoder::HttpDecoder(Visitor* visitor, Options options) remaining_frame_length_(0), current_type_field_length_(0), remaining_type_field_length_(0), - current_push_id_length_(0), - remaining_push_id_length_(0), error_(QUIC_NO_ERROR), - error_detail_(""), - ignore_old_priority_update_( - GetQuicReloadableFlag(quic_ignore_old_priority_update_frame)), - error_on_http3_push_(GetQuicReloadableFlag(quic_error_on_http3_push)) { + error_detail_("") { QUICHE_DCHECK(visitor_); - if (ignore_old_priority_update_) { - QUIC_RELOADABLE_FLAG_COUNT(quic_ignore_old_priority_update_frame); - } - if (error_on_http3_push_) { - QUIC_RELOADABLE_FLAG_COUNT(quic_error_on_http3_push); - } } HttpDecoder::~HttpDecoder() {} @@ -182,17 +171,15 @@ bool HttpDecoder::ReadFrameType(QuicDataReader* reader) { return false; } - if (error_on_http3_push_) { - if (current_frame_type_ == - static_cast<uint64_t>(HttpFrameType::CANCEL_PUSH)) { - RaiseError(QUIC_HTTP_FRAME_ERROR, "CANCEL_PUSH frame received."); - return false; - } - if (current_frame_type_ == - static_cast<uint64_t>(HttpFrameType::PUSH_PROMISE)) { - RaiseError(QUIC_HTTP_FRAME_ERROR, "PUSH_PROMISE frame received."); - return false; - } + if (current_frame_type_ == + static_cast<uint64_t>(HttpFrameType::CANCEL_PUSH)) { + RaiseError(QUIC_HTTP_FRAME_ERROR, "CANCEL_PUSH frame received."); + return false; + } + if (current_frame_type_ == + static_cast<uint64_t>(HttpFrameType::PUSH_PROMISE)) { + RaiseError(QUIC_HTTP_FRAME_ERROR, "PUSH_PROMISE frame received."); + return false; } state_ = STATE_READING_FRAME_LENGTH; @@ -241,6 +228,10 @@ bool HttpDecoder::ReadFrameLength(QuicDataReader* reader) { } if (current_frame_length_ > MaxFrameLength(current_frame_type_)) { + // MaxFrameLength() returns numeric_limits::max() + // if IsFrameBuffered() is false. + QUICHE_DCHECK(IsFrameBuffered()); + RaiseError(QUIC_HTTP_FRAME_TOO_LARGE, "Frame is too large."); return false; } @@ -261,41 +252,18 @@ bool HttpDecoder::ReadFrameLength(QuicDataReader* reader) { visitor_->OnHeadersFrameStart(header_length, current_frame_length_); break; case static_cast<uint64_t>(HttpFrameType::CANCEL_PUSH): - if (error_on_http3_push_) { - QUICHE_NOTREACHED(); - break; - } + QUICHE_NOTREACHED(); break; case static_cast<uint64_t>(HttpFrameType::SETTINGS): continue_processing = visitor_->OnSettingsFrameStart(header_length); break; case static_cast<uint64_t>(HttpFrameType::PUSH_PROMISE): - if (error_on_http3_push_) { - QUICHE_NOTREACHED(); - break; - } - // This edge case needs to be handled here, because ReadFramePayload() - // does not get called if |current_frame_length_| is zero. - if (current_frame_length_ == 0) { - RaiseError(QUIC_HTTP_FRAME_ERROR, - "PUSH_PROMISE frame with empty payload."); - return false; - } - continue_processing = visitor_->OnPushPromiseFrameStart(header_length); + QUICHE_NOTREACHED(); break; case static_cast<uint64_t>(HttpFrameType::GOAWAY): break; case static_cast<uint64_t>(HttpFrameType::MAX_PUSH_ID): break; - case static_cast<uint64_t>(HttpFrameType::PRIORITY_UPDATE): - if (ignore_old_priority_update_) { - continue_processing = visitor_->OnUnknownFrameStart( - current_frame_type_, header_length, current_frame_length_); - } else { - continue_processing = - visitor_->OnPriorityUpdateFrameStart(header_length); - } - break; case static_cast<uint64_t>(HttpFrameType::PRIORITY_UPDATE_REQUEST_STREAM): continue_processing = visitor_->OnPriorityUpdateFrameStart(header_length); break; @@ -314,6 +282,24 @@ bool HttpDecoder::ReadFrameLength(QuicDataReader* reader) { return continue_processing; } +bool HttpDecoder::IsFrameBuffered() { + switch (current_frame_type_) { + case static_cast<uint64_t>(HttpFrameType::SETTINGS): + return true; + case static_cast<uint64_t>(HttpFrameType::GOAWAY): + return true; + case static_cast<uint64_t>(HttpFrameType::MAX_PUSH_ID): + return true; + case static_cast<uint64_t>(HttpFrameType::PRIORITY_UPDATE_REQUEST_STREAM): + return true; + case static_cast<uint64_t>(HttpFrameType::ACCEPT_CH): + return true; + } + + // Other defined frame types as well as unknown frames are not buffered. + return false; +} + bool HttpDecoder::ReadFramePayload(QuicDataReader* reader) { QUICHE_DCHECK_NE(0u, reader->BytesRemaining()); QUICHE_DCHECK_NE(0u, remaining_frame_length_); @@ -344,11 +330,7 @@ bool HttpDecoder::ReadFramePayload(QuicDataReader* reader) { break; } case static_cast<uint64_t>(HttpFrameType::CANCEL_PUSH): { - if (error_on_http3_push_) { - QUICHE_NOTREACHED(); - } else { - continue_processing = BufferOrParsePayload(reader); - } + QUICHE_NOTREACHED(); break; } case static_cast<uint64_t>(HttpFrameType::SETTINGS): { @@ -356,72 +338,7 @@ bool HttpDecoder::ReadFramePayload(QuicDataReader* reader) { break; } case static_cast<uint64_t>(HttpFrameType::PUSH_PROMISE): { - if (error_on_http3_push_) { - QUICHE_NOTREACHED(); - break; - } - PushId push_id; - if (current_frame_length_ == remaining_frame_length_) { - // A new Push Promise frame just arrived. - QUICHE_DCHECK_EQ(0u, current_push_id_length_); - current_push_id_length_ = reader->PeekVarInt62Length(); - if (current_push_id_length_ > remaining_frame_length_) { - RaiseError(QUIC_HTTP_FRAME_ERROR, - "Unable to read PUSH_PROMISE push_id."); - return false; - } - if (current_push_id_length_ > reader->BytesRemaining()) { - // Not all bytes of push id is present yet, buffer push id. - QUICHE_DCHECK_EQ(0u, remaining_push_id_length_); - remaining_push_id_length_ = current_push_id_length_; - BufferPushId(reader); - break; - } - bool success = reader->ReadVarInt62(&push_id); - QUICHE_DCHECK(success); - remaining_frame_length_ -= current_push_id_length_; - if (!visitor_->OnPushPromiseFramePushId( - push_id, current_push_id_length_, - current_frame_length_ - current_push_id_length_)) { - continue_processing = false; - current_push_id_length_ = 0; - break; - } - current_push_id_length_ = 0; - } else if (remaining_push_id_length_ > 0) { - // Waiting for more bytes on push id. - BufferPushId(reader); - if (remaining_push_id_length_ != 0) { - break; - } - QuicDataReader push_id_reader(push_id_buffer_.data(), - current_push_id_length_); - - bool success = push_id_reader.ReadVarInt62(&push_id); - QUICHE_DCHECK(success); - if (!visitor_->OnPushPromiseFramePushId( - push_id, current_push_id_length_, - current_frame_length_ - current_push_id_length_)) { - continue_processing = false; - current_push_id_length_ = 0; - break; - } - current_push_id_length_ = 0; - } - - // Read Push Promise headers. - QUICHE_DCHECK_LT(remaining_frame_length_, current_frame_length_); - QuicByteCount bytes_to_read = std::min<QuicByteCount>( - remaining_frame_length_, reader->BytesRemaining()); - if (bytes_to_read == 0) { - break; - } - absl::string_view payload; - bool success = reader->ReadStringPiece(&payload, bytes_to_read); - QUICHE_DCHECK(success); - QUICHE_DCHECK(!payload.empty()); - continue_processing = visitor_->OnPushPromiseFramePayload(payload); - remaining_frame_length_ -= payload.length(); + QUICHE_NOTREACHED(); break; } case static_cast<uint64_t>(HttpFrameType::GOAWAY): { @@ -432,14 +349,6 @@ bool HttpDecoder::ReadFramePayload(QuicDataReader* reader) { continue_processing = BufferOrParsePayload(reader); break; } - case static_cast<uint64_t>(HttpFrameType::PRIORITY_UPDATE): { - if (ignore_old_priority_update_) { - continue_processing = HandleUnknownFramePayload(reader); - } else { - continue_processing = BufferOrParsePayload(reader); - } - break; - } case static_cast<uint64_t>(HttpFrameType::PRIORITY_UPDATE_REQUEST_STREAM): { continue_processing = BufferOrParsePayload(reader); break; @@ -454,6 +363,17 @@ bool HttpDecoder::ReadFramePayload(QuicDataReader* reader) { } } + if (IsFrameBuffered()) { + if (state_ != STATE_READING_FRAME_PAYLOAD) { + // BufferOrParsePayload() has advanced |state_|. + // TODO(bnc): Simplify state transitions. + QUICHE_DCHECK_EQ(STATE_READING_FRAME_TYPE, state_); + QUICHE_DCHECK_EQ(0u, remaining_frame_length_); + } + } else { + QUICHE_DCHECK(state_ == STATE_READING_FRAME_PAYLOAD); + } + // BufferOrParsePayload() may have advanced |state_|. if (state_ == STATE_READING_FRAME_PAYLOAD && remaining_frame_length_ == 0) { state_ = STATE_FINISH_PARSING; @@ -477,13 +397,7 @@ bool HttpDecoder::FinishParsing(QuicDataReader* reader) { break; } case static_cast<uint64_t>(HttpFrameType::CANCEL_PUSH): { - if (error_on_http3_push_) { - QUICHE_NOTREACHED(); - } else { - // If frame payload is not empty, FinishParsing() is skipped. - QUICHE_DCHECK_EQ(0u, current_frame_length_); - continue_processing = BufferOrParsePayload(reader); - } + QUICHE_NOTREACHED(); break; } case static_cast<uint64_t>(HttpFrameType::SETTINGS): { @@ -493,11 +407,7 @@ bool HttpDecoder::FinishParsing(QuicDataReader* reader) { break; } case static_cast<uint64_t>(HttpFrameType::PUSH_PROMISE): { - if (error_on_http3_push_) { - QUICHE_NOTREACHED(); - } else { - continue_processing = visitor_->OnPushPromiseFrameEnd(); - } + QUICHE_NOTREACHED(); break; } case static_cast<uint64_t>(HttpFrameType::GOAWAY): { @@ -512,16 +422,6 @@ bool HttpDecoder::FinishParsing(QuicDataReader* reader) { continue_processing = BufferOrParsePayload(reader); break; } - case static_cast<uint64_t>(HttpFrameType::PRIORITY_UPDATE): { - if (ignore_old_priority_update_) { - continue_processing = visitor_->OnUnknownFrameEnd(); - } else { - // If frame payload is not empty, FinishParsing() is skipped. - QUICHE_DCHECK_EQ(0u, current_frame_length_); - continue_processing = BufferOrParsePayload(reader); - } - break; - } case static_cast<uint64_t>(HttpFrameType::PRIORITY_UPDATE_REQUEST_STREAM): { // If frame payload is not empty, FinishParsing() is skipped. QUICHE_DCHECK_EQ(0u, current_frame_length_); @@ -555,21 +455,8 @@ bool HttpDecoder::HandleUnknownFramePayload(QuicDataReader* reader) { return visitor_->OnUnknownFramePayload(payload); } -void HttpDecoder::DiscardFramePayload(QuicDataReader* reader) { - QuicByteCount bytes_to_read = std::min<QuicByteCount>( - remaining_frame_length_, reader->BytesRemaining()); - absl::string_view payload; - bool success = reader->ReadStringPiece(&payload, bytes_to_read); - QUICHE_DCHECK(success); - remaining_frame_length_ -= payload.length(); - if (remaining_frame_length_ == 0) { - state_ = STATE_READING_FRAME_TYPE; - current_length_field_length_ = 0; - current_type_field_length_ = 0; - } -} - bool HttpDecoder::BufferOrParsePayload(QuicDataReader* reader) { + QUICHE_DCHECK(IsFrameBuffered()); QUICHE_DCHECK_EQ(current_frame_length_, buffer_.size() + remaining_frame_length_); @@ -615,27 +502,14 @@ bool HttpDecoder::BufferOrParsePayload(QuicDataReader* reader) { } bool HttpDecoder::ParseEntirePayload(QuicDataReader* reader) { + QUICHE_DCHECK(IsFrameBuffered()); QUICHE_DCHECK_EQ(current_frame_length_, reader->BytesRemaining()); QUICHE_DCHECK_EQ(0u, remaining_frame_length_); switch (current_frame_type_) { case static_cast<uint64_t>(HttpFrameType::CANCEL_PUSH): { - if (error_on_http3_push_) { - QUICHE_NOTREACHED(); - return false; - } - CancelPushFrame frame; - if (!reader->ReadVarInt62(&frame.push_id)) { - RaiseError(QUIC_HTTP_FRAME_ERROR, - "Unable to read CANCEL_PUSH push_id."); - return false; - } - if (!reader->IsDoneReading()) { - RaiseError(QUIC_HTTP_FRAME_ERROR, - "Superfluous data in CANCEL_PUSH frame."); - return false; - } - return visitor_->OnCancelPushFrame(frame); + QUICHE_NOTREACHED(); + return false; } case static_cast<uint64_t>(HttpFrameType::SETTINGS): { SettingsFrame frame; @@ -670,21 +544,9 @@ bool HttpDecoder::ParseEntirePayload(QuicDataReader* reader) { } return visitor_->OnMaxPushIdFrame(frame); } - case static_cast<uint64_t>(HttpFrameType::PRIORITY_UPDATE): { - if (ignore_old_priority_update_) { - QUICHE_NOTREACHED(); - return false; - } else { - PriorityUpdateFrame frame; - if (!ParsePriorityUpdateFrame(reader, &frame)) { - return false; - } - return visitor_->OnPriorityUpdateFrame(frame); - } - } case static_cast<uint64_t>(HttpFrameType::PRIORITY_UPDATE_REQUEST_STREAM): { PriorityUpdateFrame frame; - if (!ParseNewPriorityUpdateFrame(reader, &frame)) { + if (!ParsePriorityUpdateFrame(reader, &frame)) { return false; } return visitor_->OnPriorityUpdateFrame(frame); @@ -725,19 +587,6 @@ void HttpDecoder::BufferFrameType(QuicDataReader* reader) { remaining_type_field_length_ -= bytes_to_read; } -void HttpDecoder::BufferPushId(QuicDataReader* reader) { - QUICHE_DCHECK_LE(remaining_push_id_length_, current_frame_length_); - QuicByteCount bytes_to_read = std::min<QuicByteCount>( - reader->BytesRemaining(), remaining_push_id_length_); - bool success = - reader->ReadBytes(push_id_buffer_.data() + current_push_id_length_ - - remaining_push_id_length_, - bytes_to_read); - QUICHE_DCHECK(success); - remaining_push_id_length_ -= bytes_to_read; - remaining_frame_length_ -= bytes_to_read; -} - void HttpDecoder::RaiseError(QuicErrorCode error, std::string error_detail) { state_ = STATE_ERROR; error_ = error; @@ -769,36 +618,6 @@ bool HttpDecoder::ParseSettingsFrame(QuicDataReader* reader, } bool HttpDecoder::ParsePriorityUpdateFrame(QuicDataReader* reader, - PriorityUpdateFrame* frame) { - uint8_t prioritized_element_type; - if (!reader->ReadUInt8(&prioritized_element_type)) { - RaiseError(QUIC_HTTP_FRAME_ERROR, - "Unable to read prioritized element type."); - return false; - } - - if (prioritized_element_type != REQUEST_STREAM && - prioritized_element_type != PUSH_STREAM) { - RaiseError(QUIC_HTTP_FRAME_ERROR, "Invalid prioritized element type."); - return false; - } - - frame->prioritized_element_type = - static_cast<PrioritizedElementType>(prioritized_element_type); - - if (!reader->ReadVarInt62(&frame->prioritized_element_id)) { - RaiseError(QUIC_HTTP_FRAME_ERROR, "Unable to read prioritized element id."); - return false; - } - - absl::string_view priority_field_value = reader->ReadRemainingPayload(); - frame->priority_field_value = - std::string(priority_field_value.data(), priority_field_value.size()); - - return true; -} - -bool HttpDecoder::ParseNewPriorityUpdateFrame(QuicDataReader* reader, PriorityUpdateFrame* frame) { frame->prioritized_element_type = REQUEST_STREAM; @@ -836,20 +655,13 @@ bool HttpDecoder::ParseAcceptChFrame(QuicDataReader* reader, QuicByteCount HttpDecoder::MaxFrameLength(uint64_t frame_type) { switch (frame_type) { - case static_cast<uint64_t>(HttpFrameType::CANCEL_PUSH): - // TODO(b/171463363): Remove. - return sizeof(PushId); case static_cast<uint64_t>(HttpFrameType::SETTINGS): // This limit is arbitrary. return 1024 * 1024; case static_cast<uint64_t>(HttpFrameType::GOAWAY): return VARIABLE_LENGTH_INTEGER_LENGTH_8; case static_cast<uint64_t>(HttpFrameType::MAX_PUSH_ID): - // TODO(b/171463363): Remove. - return sizeof(PushId); - case static_cast<uint64_t>(HttpFrameType::PRIORITY_UPDATE): - // This limit is arbitrary. - return 1024 * 1024; + return VARIABLE_LENGTH_INTEGER_LENGTH_8; case static_cast<uint64_t>(HttpFrameType::PRIORITY_UPDATE_REQUEST_STREAM): // This limit is arbitrary. return 1024 * 1024; diff --git a/chromium/net/third_party/quiche/src/quic/core/http/http_decoder.h b/chromium/net/third_party/quiche/src/quic/core/http/http_decoder.h index 3a970fb63b1..002ff68f008 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/http_decoder.h +++ b/chromium/net/third_party/quiche/src/quic/core/http/http_decoder.h @@ -44,10 +44,6 @@ class QUIC_EXPORT_PRIVATE HttpDecoder { // On*FrameStart() methods are called after the frame header is completely // processed. At that point it is safe to consume |header_length| bytes. - // Called when a CANCEL_PUSH frame has been successfully parsed. - // TODO(b/171463363): Remove. - virtual bool OnCancelPushFrame(const CancelPushFrame& frame) = 0; - // Called when a MAX_PUSH_ID frame has been successfully parsed. virtual bool OnMaxPushIdFrame(const MaxPushIdFrame& frame) = 0; @@ -84,24 +80,6 @@ class QUIC_EXPORT_PRIVATE HttpDecoder { // Called when a HEADERS frame has been completely processed. virtual bool OnHeadersFrameEnd() = 0; - // TODO(b/171463363): Remove all. - // Called when a PUSH_PROMISE frame has been received. - virtual bool OnPushPromiseFrameStart(QuicByteCount header_length) = 0; - // Called when the Push ID field of a PUSH_PROMISE frame has been parsed. - // Called exactly once for a valid PUSH_PROMISE frame. - // |push_id_length| is the length of the push ID field. - // |header_block_length| is the length of the compressed header block. - virtual bool OnPushPromiseFramePushId( - PushId push_id, - QuicByteCount push_id_length, - QuicByteCount header_block_length) = 0; - // Called when part of the header block of a PUSH_PROMISE frame has been - // read. May be called multiple times for a single frame. |payload| is - // guaranteed to be non-empty. - virtual bool OnPushPromiseFramePayload(absl::string_view payload) = 0; - // Called when a PUSH_PROMISE frame has been completely processed. - virtual bool OnPushPromiseFrameEnd() = 0; - // Called when a PRIORITY_UPDATE frame has been received. // |header_length| contains PRIORITY_UPDATE frame length and payload length. virtual bool OnPriorityUpdateFrameStart(QuicByteCount header_length) = 0; @@ -193,16 +171,23 @@ class QUIC_EXPORT_PRIVATE HttpDecoder { // if there are any errors. Returns whether processing should continue. bool ReadFrameLength(QuicDataReader* reader); - // Depending on the frame type, reads and processes the payload of the current - // frame from |reader| and calls visitor methods, or calls - // BufferOrParsePayload(). Returns whether processing should continue. + // Returns whether the current frame is of a buffered type. + // The payload of buffered frames is buffered by HttpDecoder, and parsed by + // HttpDecoder after the entire frame has been received. (Copying to the + // buffer is skipped if the ProcessInput() call covers the entire payload.) + // Frames that are not buffered have every payload fragment synchronously + // passed to the Visitor without buffering. + bool IsFrameBuffered(); + + // For buffered frame types, calls BufferOrParsePayload(). For other frame + // types, reads the payload of the current frame from |reader| and calls + // visitor methods. Returns whether processing should continue. bool ReadFramePayload(QuicDataReader* reader); - // For frame types parsed by BufferOrParsePayload(), this method is only - // called if frame payload is empty, at it calls BufferOrParsePayload(). For - // other frame types, this method directly calls visitor methods to signal - // that frame had been parsed completely. Returns whether processing should - // continue. + // For buffered frame types, this method is only called if frame payload is + // empty, and it calls BufferOrParsePayload(). For other frame types, this + // method directly calls visitor methods to signal that frame had been + // received completely. Returns whether processing should continue. bool FinishParsing(QuicDataReader* reader); // Read payload of unknown frame from |reader| and call @@ -210,18 +195,17 @@ class QUIC_EXPORT_PRIVATE HttpDecoder { // false if it should be paused. bool HandleUnknownFramePayload(QuicDataReader* reader); - // Discards any remaining frame payload from |reader|. - void DiscardFramePayload(QuicDataReader* reader); - // Buffers any remaining frame payload from |*reader| into |buffer_| if // necessary. Parses the frame payload if complete. Parses out of |*reader| - // without unnecessary copy if |*reader| has entire payload. + // without unnecessary copy if |*reader| contains entire payload. // Returns whether processing should continue. + // Must only be called when current frame type is buffered. bool BufferOrParsePayload(QuicDataReader* reader); // Parses the entire payload of certain kinds of frames that are parsed in a // single pass. |reader| must have at least |current_frame_length_| bytes. // Returns whether processing should continue. + // Must only be called when current frame type is buffered. bool ParseEntirePayload(QuicDataReader* reader); // Buffers any remaining frame length field from |reader| into @@ -231,28 +215,17 @@ class QUIC_EXPORT_PRIVATE HttpDecoder { // Buffers any remaining frame type field from |reader| into |type_buffer_|. void BufferFrameType(QuicDataReader* reader); - // Buffers at most |remaining_push_id_length_| from |reader| to - // |push_id_buffer_|. TODO(b/171463363): Remove. - void BufferPushId(QuicDataReader* reader); - // Sets |error_| and |error_detail_| accordingly. void RaiseError(QuicErrorCode error, std::string error_detail); // Parses the payload of a SETTINGS frame from |reader| into |frame|. bool ParseSettingsFrame(QuicDataReader* reader, SettingsFrame* frame); - // Parses the payload of a PRIORITY_UPDATE frame (draft-01, type 0x0f) + // Parses the payload of a PRIORITY_UPDATE frame (draft-02, type 0xf0700) // from |reader| into |frame|. - // TODO(b/147306124): Remove. bool ParsePriorityUpdateFrame(QuicDataReader* reader, PriorityUpdateFrame* frame); - // Parses the payload of a PRIORITY_UPDATE frame (draft-02, type 0xf0700) - // from |reader| into |frame|. - // TODO(b/147306124): Rename to ParsePriorityUpdateFrame(). - bool ParseNewPriorityUpdateFrame(QuicDataReader* reader, - PriorityUpdateFrame* frame); - // Parses the payload of an ACCEPT_CH frame from |reader| into |frame|. bool ParseAcceptChFrame(QuicDataReader* reader, AcceptChFrame* frame); @@ -279,12 +252,6 @@ class QUIC_EXPORT_PRIVATE HttpDecoder { QuicByteCount current_type_field_length_; // Remaining length that's needed for the frame's type field. QuicByteCount remaining_type_field_length_; - // Length of PUSH_PROMISE frame's push id. - // TODO(b/171463363): Remove. - QuicByteCount current_push_id_length_; - // Remaining length that's needed for PUSH_PROMISE frame's push id field. - // TODO(b/171463363): Remove. - QuicByteCount remaining_push_id_length_; // Last error. QuicErrorCode error_; // The issue which caused |error_| @@ -295,17 +262,6 @@ class QUIC_EXPORT_PRIVATE HttpDecoder { std::array<char, sizeof(uint64_t)> length_buffer_; // Remaining unparsed type field data. std::array<char, sizeof(uint64_t)> type_buffer_; - // Remaining unparsed push id data. - // TODO(b/171463363): Remove. - std::array<char, sizeof(uint64_t)> push_id_buffer_; - - // Latched value of - // gfe2_reloadable_flag_quic_ignore_old_priority_update_frame. - const bool ignore_old_priority_update_; - - // Latched value of - // gfe2_reloadable_flag_quic_error_on_http3_push. - const bool error_on_http3_push_; }; } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/http/http_decoder_test.cc b/chromium/net/third_party/quiche/src/quic/core/http/http_decoder_test.cc index 05d580e20e1..0266632aae7 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/http_decoder_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/http/http_decoder_test.cc @@ -45,10 +45,6 @@ class MockVisitor : public HttpDecoder::Visitor { MOCK_METHOD(void, OnError, (HttpDecoder*), (override)); MOCK_METHOD(bool, - OnCancelPushFrame, - (const CancelPushFrame& frame), - (override)); - MOCK_METHOD(bool, OnMaxPushIdFrame, (const MaxPushIdFrame& frame), (override)); @@ -80,22 +76,6 @@ class MockVisitor : public HttpDecoder::Visitor { MOCK_METHOD(bool, OnHeadersFrameEnd, (), (override)); MOCK_METHOD(bool, - OnPushPromiseFrameStart, - (QuicByteCount header_length), - (override)); - MOCK_METHOD(bool, - OnPushPromiseFramePushId, - (PushId push_id, - QuicByteCount push_id_length, - QuicByteCount header_block_length), - (override)); - MOCK_METHOD(bool, - OnPushPromiseFramePayload, - (absl::string_view payload), - (override)); - MOCK_METHOD(bool, OnPushPromiseFrameEnd, (), (override)); - - MOCK_METHOD(bool, OnPriorityUpdateFrameStart, (QuicByteCount header_length), (override)); @@ -130,7 +110,6 @@ class MockVisitor : public HttpDecoder::Visitor { class HttpDecoderTest : public QuicTest { public: HttpDecoderTest() : decoder_(&visitor_) { - ON_CALL(visitor_, OnCancelPushFrame(_)).WillByDefault(Return(true)); ON_CALL(visitor_, OnMaxPushIdFrame(_)).WillByDefault(Return(true)); ON_CALL(visitor_, OnGoAwayFrame(_)).WillByDefault(Return(true)); ON_CALL(visitor_, OnSettingsFrameStart(_)).WillByDefault(Return(true)); @@ -141,11 +120,6 @@ class HttpDecoderTest : public QuicTest { ON_CALL(visitor_, OnHeadersFrameStart(_, _)).WillByDefault(Return(true)); ON_CALL(visitor_, OnHeadersFramePayload(_)).WillByDefault(Return(true)); ON_CALL(visitor_, OnHeadersFrameEnd()).WillByDefault(Return(true)); - ON_CALL(visitor_, OnPushPromiseFrameStart(_)).WillByDefault(Return(true)); - ON_CALL(visitor_, OnPushPromiseFramePushId(_, _, _)) - .WillByDefault(Return(true)); - ON_CALL(visitor_, OnPushPromiseFramePayload(_)).WillByDefault(Return(true)); - ON_CALL(visitor_, OnPushPromiseFrameEnd()).WillByDefault(Return(true)); ON_CALL(visitor_, OnPriorityUpdateFrameStart(_)) .WillByDefault(Return(true)); ON_CALL(visitor_, OnPriorityUpdateFrame(_)).WillByDefault(Return(true)); @@ -248,144 +222,24 @@ TEST_F(HttpDecoderTest, CancelPush) { "01" // length "01"); // Push Id - if (GetQuicReloadableFlag(quic_error_on_http3_push)) { - EXPECT_CALL(visitor_, OnError(&decoder_)); - EXPECT_EQ(1u, ProcessInput(input)); - EXPECT_THAT(decoder_.error(), IsError(QUIC_HTTP_FRAME_ERROR)); - EXPECT_EQ("CANCEL_PUSH frame received.", decoder_.error_detail()); - return; - } - - // Visitor pauses processing. - EXPECT_CALL(visitor_, OnCancelPushFrame(CancelPushFrame({1}))) - .WillOnce(Return(false)); - EXPECT_EQ(input.size(), ProcessInputWithGarbageAppended(input)); - EXPECT_THAT(decoder_.error(), IsQuicNoError()); - EXPECT_EQ("", decoder_.error_detail()); - - // Process the full frame. - EXPECT_CALL(visitor_, OnCancelPushFrame(CancelPushFrame({1}))); - EXPECT_EQ(input.size(), ProcessInput(input)); - EXPECT_THAT(decoder_.error(), IsQuicNoError()); - EXPECT_EQ("", decoder_.error_detail()); - - // Process the frame incrementally. - EXPECT_CALL(visitor_, OnCancelPushFrame(CancelPushFrame({1}))); - ProcessInputCharByChar(input); - EXPECT_THAT(decoder_.error(), IsQuicNoError()); - EXPECT_EQ("", decoder_.error_detail()); + EXPECT_CALL(visitor_, OnError(&decoder_)); + EXPECT_EQ(1u, ProcessInput(input)); + EXPECT_THAT(decoder_.error(), IsError(QUIC_HTTP_FRAME_ERROR)); + EXPECT_EQ("CANCEL_PUSH frame received.", decoder_.error_detail()); } TEST_F(HttpDecoderTest, PushPromiseFrame) { InSequence s; std::string input = - absl::StrCat(absl::HexStringToBytes("05" // type (PUSH PROMISE) - "0f" // length - "C000000000000101"), // push id 257 - "Headers"); // headers - - if (GetQuicReloadableFlag(quic_error_on_http3_push)) { - EXPECT_CALL(visitor_, OnError(&decoder_)); - EXPECT_EQ(1u, ProcessInput(input)); - EXPECT_THAT(decoder_.error(), IsError(QUIC_HTTP_FRAME_ERROR)); - EXPECT_EQ("PUSH_PROMISE frame received.", decoder_.error_detail()); - return; - } - - // Visitor pauses processing. - EXPECT_CALL(visitor_, OnPushPromiseFrameStart(2)).WillOnce(Return(false)); - EXPECT_CALL(visitor_, OnPushPromiseFramePushId(257, 8, 7)) - .WillOnce(Return(false)); - absl::string_view remaining_input(input); - QuicByteCount processed_bytes = - ProcessInputWithGarbageAppended(remaining_input); - EXPECT_EQ(2u, processed_bytes); - remaining_input = remaining_input.substr(processed_bytes); - processed_bytes = ProcessInputWithGarbageAppended(remaining_input); - EXPECT_EQ(8u, processed_bytes); - remaining_input = remaining_input.substr(processed_bytes); - - EXPECT_CALL(visitor_, OnPushPromiseFramePayload(absl::string_view("Headers"))) - .WillOnce(Return(false)); - processed_bytes = ProcessInputWithGarbageAppended(remaining_input); - EXPECT_EQ(remaining_input.size(), processed_bytes); - - EXPECT_CALL(visitor_, OnPushPromiseFrameEnd()).WillOnce(Return(false)); - EXPECT_EQ(0u, ProcessInputWithGarbageAppended("")); - EXPECT_THAT(decoder_.error(), IsQuicNoError()); - EXPECT_EQ("", decoder_.error_detail()); - - // Process the full frame. - EXPECT_CALL(visitor_, OnPushPromiseFrameStart(2)); - EXPECT_CALL(visitor_, OnPushPromiseFramePushId(257, 8, 7)); - EXPECT_CALL(visitor_, - OnPushPromiseFramePayload(absl::string_view("Headers"))); - EXPECT_CALL(visitor_, OnPushPromiseFrameEnd()); - EXPECT_EQ(input.size(), ProcessInput(input)); - EXPECT_THAT(decoder_.error(), IsQuicNoError()); - EXPECT_EQ("", decoder_.error_detail()); - - // Process the frame incrementally. - EXPECT_CALL(visitor_, OnPushPromiseFrameStart(2)); - EXPECT_CALL(visitor_, OnPushPromiseFramePushId(257, 8, 7)); - EXPECT_CALL(visitor_, OnPushPromiseFramePayload(absl::string_view("H"))); - EXPECT_CALL(visitor_, OnPushPromiseFramePayload(absl::string_view("e"))); - EXPECT_CALL(visitor_, OnPushPromiseFramePayload(absl::string_view("a"))); - EXPECT_CALL(visitor_, OnPushPromiseFramePayload(absl::string_view("d"))); - EXPECT_CALL(visitor_, OnPushPromiseFramePayload(absl::string_view("e"))); - EXPECT_CALL(visitor_, OnPushPromiseFramePayload(absl::string_view("r"))); - EXPECT_CALL(visitor_, OnPushPromiseFramePayload(absl::string_view("s"))); - EXPECT_CALL(visitor_, OnPushPromiseFrameEnd()); - ProcessInputCharByChar(input); - EXPECT_THAT(decoder_.error(), IsQuicNoError()); - EXPECT_EQ("", decoder_.error_detail()); - - // Process push id incrementally and append headers with last byte of push id. - EXPECT_CALL(visitor_, OnPushPromiseFrameStart(2)); - EXPECT_CALL(visitor_, OnPushPromiseFramePushId(257, 8, 7)); - EXPECT_CALL(visitor_, - OnPushPromiseFramePayload(absl::string_view("Headers"))); - EXPECT_CALL(visitor_, OnPushPromiseFrameEnd()); - ProcessInputCharByChar(input.substr(0, 9)); - EXPECT_EQ(8u, ProcessInput(input.substr(9))); - EXPECT_THAT(decoder_.error(), IsQuicNoError()); - EXPECT_EQ("", decoder_.error_detail()); -} - -TEST_F(HttpDecoderTest, CorruptPushPromiseFrame) { - if (GetQuicReloadableFlag(quic_error_on_http3_push)) { - return; - } - - InSequence s; - - std::string input = absl::HexStringToBytes( - "05" // type (PUSH_PROMISE) - "01" // length - "40"); // first byte of two-byte varint push id - - { - HttpDecoder decoder(&visitor_); - EXPECT_CALL(visitor_, OnPushPromiseFrameStart(2)); - EXPECT_CALL(visitor_, OnError(&decoder)); - - decoder.ProcessInput(input.data(), input.size()); - - EXPECT_THAT(decoder.error(), IsError(QUIC_HTTP_FRAME_ERROR)); - EXPECT_EQ("Unable to read PUSH_PROMISE push_id.", decoder.error_detail()); - } - { - HttpDecoder decoder(&visitor_); - EXPECT_CALL(visitor_, OnPushPromiseFrameStart(2)); - EXPECT_CALL(visitor_, OnError(&decoder)); - - for (auto c : input) { - decoder.ProcessInput(&c, 1); - } + absl::StrCat(absl::HexStringToBytes("05" // type (PUSH PROMISE) + "08" // length + "1f"), // push id 31 + "Headers"); // headers - EXPECT_THAT(decoder.error(), IsError(QUIC_HTTP_FRAME_ERROR)); - EXPECT_EQ("Unable to read PUSH_PROMISE push_id.", decoder.error_detail()); - } + EXPECT_CALL(visitor_, OnError(&decoder_)); + EXPECT_EQ(1u, ProcessInput(input)); + EXPECT_THAT(decoder_.error(), IsError(QUIC_HTTP_FRAME_ERROR)); + EXPECT_EQ("PUSH_PROMISE frame received.", decoder_.error_detail()); } TEST_F(HttpDecoderTest, MaxPushId) { @@ -564,10 +418,8 @@ TEST_F(HttpDecoderTest, FrameHeaderPartialDelivery) { InSequence s; // A large input that will occupy more than 1 byte in the length field. std::string input(2048, 'x'); - std::unique_ptr<char[]> buffer; - QuicByteCount header_length = - HttpEncoder::SerializeDataFrameHeader(input.length(), &buffer); - std::string header = std::string(buffer.get(), header_length); + QuicBuffer header = HttpEncoder::SerializeDataFrameHeader( + input.length(), SimpleBufferAllocator::Get()); // Partially send only 1 byte of the header to process. EXPECT_EQ(1u, decoder_.ProcessInput(header.data(), 1)); EXPECT_THAT(decoder_.error(), IsQuicNoError()); @@ -575,8 +427,8 @@ TEST_F(HttpDecoderTest, FrameHeaderPartialDelivery) { // Send the rest of the header. EXPECT_CALL(visitor_, OnDataFrameStart(3, input.length())); - EXPECT_EQ(header_length - 1, - decoder_.ProcessInput(header.data() + 1, header_length - 1)); + EXPECT_EQ(header.size() - 1, + decoder_.ProcessInput(header.data() + 1, header.size() - 1)); EXPECT_THAT(decoder_.error(), IsQuicNoError()); EXPECT_EQ("", decoder_.error_detail()); @@ -751,55 +603,23 @@ TEST_F(HttpDecoderTest, EmptyHeadersFrame) { EXPECT_EQ("", decoder_.error_detail()); } -TEST_F(HttpDecoderTest, PushPromiseFrameNoHeaders) { - if (GetQuicReloadableFlag(quic_error_on_http3_push)) { - return; - } - - InSequence s; +TEST_F(HttpDecoderTest, GoawayWithOverlyLargePayload) { std::string input = absl::HexStringToBytes( - "05" // type (PUSH_PROMISE) - "01" // length - "01"); // Push Id - - // Visitor pauses processing. - EXPECT_CALL(visitor_, OnPushPromiseFrameStart(2)); - EXPECT_CALL(visitor_, OnPushPromiseFramePushId(1, 1, 0)) - .WillOnce(Return(false)); - EXPECT_EQ(input.size(), ProcessInputWithGarbageAppended(input)); - - EXPECT_CALL(visitor_, OnPushPromiseFrameEnd()).WillOnce(Return(false)); - EXPECT_EQ(0u, ProcessInputWithGarbageAppended("")); - EXPECT_THAT(decoder_.error(), IsQuicNoError()); - EXPECT_EQ("", decoder_.error_detail()); - - // Process the full frame. - EXPECT_CALL(visitor_, OnPushPromiseFrameStart(2)); - EXPECT_CALL(visitor_, OnPushPromiseFramePushId(1, 1, 0)); - EXPECT_CALL(visitor_, OnPushPromiseFrameEnd()); - EXPECT_EQ(input.size(), ProcessInput(input)); - EXPECT_THAT(decoder_.error(), IsQuicNoError()); - EXPECT_EQ("", decoder_.error_detail()); - - // Process the frame incrementally. - EXPECT_CALL(visitor_, OnPushPromiseFrameStart(2)); - EXPECT_CALL(visitor_, OnPushPromiseFramePushId(1, 1, 0)); - EXPECT_CALL(visitor_, OnPushPromiseFrameEnd()); - ProcessInputCharByChar(input); - EXPECT_THAT(decoder_.error(), IsQuicNoError()); - EXPECT_EQ("", decoder_.error_detail()); + "07" // type (GOAWAY) + "10"); // length exceeding the maximum possible length for GOAWAY frame + // Process all data at once. + EXPECT_CALL(visitor_, OnError(&decoder_)); + EXPECT_EQ(2u, ProcessInput(input)); + EXPECT_THAT(decoder_.error(), IsError(QUIC_HTTP_FRAME_TOO_LARGE)); + EXPECT_EQ("Frame is too large.", decoder_.error_detail()); } -TEST_F(HttpDecoderTest, MalformedFrameWithOverlyLargePayload) { - if (GetQuicReloadableFlag(quic_error_on_http3_push)) { - return; - } - +TEST_F(HttpDecoderTest, MaxPushIdWithOverlyLargePayload) { std::string input = absl::HexStringToBytes( - "03" // type (CANCEL_PUSH) - "10" // length - "15"); // malformed payload - // Process the full frame. + "0d" // type (MAX_PUSH_ID) + "10"); // length exceeding the maximum possible length for MAX_PUSH_ID + // frame + // Process all data at once. EXPECT_CALL(visitor_, OnError(&decoder_)); EXPECT_EQ(2u, ProcessInput(input)); EXPECT_THAT(decoder_.error(), IsError(QUIC_HTTP_FRAME_TOO_LARGE)); @@ -868,193 +688,87 @@ TEST_F(HttpDecoderTest, HeadersPausedThenData) { } TEST_F(HttpDecoderTest, CorruptFrame) { - if (GetQuicReloadableFlag(quic_error_on_http3_push)) { - InSequence s; - - struct { - const char* const input; - const char* const error_message; - } kTestData[] = {{"\x0D" // type (MAX_PUSH_ID) - "\x01" // length - "\x40", // first byte of two-byte varint push id - "Unable to read MAX_PUSH_ID push_id."}, - {"\x0D" // type (MAX_PUSH_ID) - "\x04" // length - "\x05" // valid push id - "foo", // superfluous data - "Superfluous data in MAX_PUSH_ID frame."}, - {"\x07" // type (GOAWAY) - "\x01" // length - "\x40", // first byte of two-byte varint stream id - "Unable to read GOAWAY ID."}, - {"\x07" // type (GOAWAY) - "\x04" // length - "\x05" // valid stream id - "foo", // superfluous data - "Superfluous data in GOAWAY frame."}, - {"\x40\x89" // type (ACCEPT_CH) - "\x01" // length - "\x40", // first byte of two-byte varint origin length - "Unable to read ACCEPT_CH origin."}, - {"\x40\x89" // type (ACCEPT_CH) - "\x01" // length - "\x05", // valid origin length but no origin string - "Unable to read ACCEPT_CH origin."}, - {"\x40\x89" // type (ACCEPT_CH) - "\x04" // length - "\x05" // valid origin length - "foo", // payload ends before origin ends - "Unable to read ACCEPT_CH origin."}, - {"\x40\x89" // type (ACCEPT_CH) - "\x04" // length - "\x03" // valid origin length - "foo", // payload ends at end of origin: no value - "Unable to read ACCEPT_CH value."}, - {"\x40\x89" // type (ACCEPT_CH) - "\x05" // length - "\x03" // valid origin length - "foo" // payload ends at end of origin: no value - "\x40", // first byte of two-byte varint value length - "Unable to read ACCEPT_CH value."}, - {"\x40\x89" // type (ACCEPT_CH) - "\x08" // length - "\x03" // valid origin length - "foo" // origin - "\x05" // valid value length - "bar", // payload ends before value ends - "Unable to read ACCEPT_CH value."}}; - - for (const auto& test_data : kTestData) { - { - HttpDecoder decoder(&visitor_); - EXPECT_CALL(visitor_, OnAcceptChFrameStart(_)).Times(AnyNumber()); - EXPECT_CALL(visitor_, OnError(&decoder)); - - absl::string_view input(test_data.input); - decoder.ProcessInput(input.data(), input.size()); - EXPECT_THAT(decoder.error(), IsError(QUIC_HTTP_FRAME_ERROR)); - EXPECT_EQ(test_data.error_message, decoder.error_detail()); - } - { - HttpDecoder decoder(&visitor_); - EXPECT_CALL(visitor_, OnAcceptChFrameStart(_)).Times(AnyNumber()); - EXPECT_CALL(visitor_, OnError(&decoder)); - - absl::string_view input(test_data.input); - for (auto c : input) { - decoder.ProcessInput(&c, 1); - } - EXPECT_THAT(decoder.error(), IsError(QUIC_HTTP_FRAME_ERROR)); - EXPECT_EQ(test_data.error_message, decoder.error_detail()); - } + InSequence s; + + struct { + const char* const input; + const char* const error_message; + } kTestData[] = {{"\x0D" // type (MAX_PUSH_ID) + "\x01" // length + "\x40", // first byte of two-byte varint push id + "Unable to read MAX_PUSH_ID push_id."}, + {"\x0D" // type (MAX_PUSH_ID) + "\x04" // length + "\x05" // valid push id + "foo", // superfluous data + "Superfluous data in MAX_PUSH_ID frame."}, + {"\x07" // type (GOAWAY) + "\x01" // length + "\x40", // first byte of two-byte varint stream id + "Unable to read GOAWAY ID."}, + {"\x07" // type (GOAWAY) + "\x04" // length + "\x05" // valid stream id + "foo", // superfluous data + "Superfluous data in GOAWAY frame."}, + {"\x40\x89" // type (ACCEPT_CH) + "\x01" // length + "\x40", // first byte of two-byte varint origin length + "Unable to read ACCEPT_CH origin."}, + {"\x40\x89" // type (ACCEPT_CH) + "\x01" // length + "\x05", // valid origin length but no origin string + "Unable to read ACCEPT_CH origin."}, + {"\x40\x89" // type (ACCEPT_CH) + "\x04" // length + "\x05" // valid origin length + "foo", // payload ends before origin ends + "Unable to read ACCEPT_CH origin."}, + {"\x40\x89" // type (ACCEPT_CH) + "\x04" // length + "\x03" // valid origin length + "foo", // payload ends at end of origin: no value + "Unable to read ACCEPT_CH value."}, + {"\x40\x89" // type (ACCEPT_CH) + "\x05" // length + "\x03" // valid origin length + "foo" // payload ends at end of origin: no value + "\x40", // first byte of two-byte varint value length + "Unable to read ACCEPT_CH value."}, + {"\x40\x89" // type (ACCEPT_CH) + "\x08" // length + "\x03" // valid origin length + "foo" // origin + "\x05" // valid value length + "bar", // payload ends before value ends + "Unable to read ACCEPT_CH value."}}; + + for (const auto& test_data : kTestData) { + { + HttpDecoder decoder(&visitor_); + EXPECT_CALL(visitor_, OnAcceptChFrameStart(_)).Times(AnyNumber()); + EXPECT_CALL(visitor_, OnError(&decoder)); + + absl::string_view input(test_data.input); + decoder.ProcessInput(input.data(), input.size()); + EXPECT_THAT(decoder.error(), IsError(QUIC_HTTP_FRAME_ERROR)); + EXPECT_EQ(test_data.error_message, decoder.error_detail()); } - } else { - InSequence s; - - struct { - const char* const input; - const char* const error_message; - } kTestData[] = {{"\x03" // type (CANCEL_PUSH) - "\x01" // length - "\x40", // first byte of two-byte varint push id - "Unable to read CANCEL_PUSH push_id."}, - {"\x03" // type (CANCEL_PUSH) - "\x04" // length - "\x05" // valid push id - "foo", // superfluous data - "Superfluous data in CANCEL_PUSH frame."}, - {"\x0D" // type (MAX_PUSH_ID) - "\x01" // length - "\x40", // first byte of two-byte varint push id - "Unable to read MAX_PUSH_ID push_id."}, - {"\x0D" // type (MAX_PUSH_ID) - "\x04" // length - "\x05" // valid push id - "foo", // superfluous data - "Superfluous data in MAX_PUSH_ID frame."}, - {"\x07" // type (GOAWAY) - "\x01" // length - "\x40", // first byte of two-byte varint stream id - "Unable to read GOAWAY ID."}, - {"\x07" // type (GOAWAY) - "\x04" // length - "\x05" // valid stream id - "foo", // superfluous data - "Superfluous data in GOAWAY frame."}, - {"\x40\x89" // type (ACCEPT_CH) - "\x01" // length - "\x40", // first byte of two-byte varint origin length - "Unable to read ACCEPT_CH origin."}, - {"\x40\x89" // type (ACCEPT_CH) - "\x01" // length - "\x05", // valid origin length but no origin string - "Unable to read ACCEPT_CH origin."}, - {"\x40\x89" // type (ACCEPT_CH) - "\x04" // length - "\x05" // valid origin length - "foo", // payload ends before origin ends - "Unable to read ACCEPT_CH origin."}, - {"\x40\x89" // type (ACCEPT_CH) - "\x04" // length - "\x03" // valid origin length - "foo", // payload ends at end of origin: no value - "Unable to read ACCEPT_CH value."}, - {"\x40\x89" // type (ACCEPT_CH) - "\x05" // length - "\x03" // valid origin length - "foo" // payload ends at end of origin: no value - "\x40", // first byte of two-byte varint value length - "Unable to read ACCEPT_CH value."}, - {"\x40\x89" // type (ACCEPT_CH) - "\x08" // length - "\x03" // valid origin length - "foo" // origin - "\x05" // valid value length - "bar", // payload ends before value ends - "Unable to read ACCEPT_CH value."}}; - - for (const auto& test_data : kTestData) { - { - HttpDecoder decoder(&visitor_); - EXPECT_CALL(visitor_, OnAcceptChFrameStart(_)).Times(AnyNumber()); - EXPECT_CALL(visitor_, OnError(&decoder)); - - absl::string_view input(test_data.input); - decoder.ProcessInput(input.data(), input.size()); - EXPECT_THAT(decoder.error(), IsError(QUIC_HTTP_FRAME_ERROR)); - EXPECT_EQ(test_data.error_message, decoder.error_detail()); - } - { - HttpDecoder decoder(&visitor_); - EXPECT_CALL(visitor_, OnAcceptChFrameStart(_)).Times(AnyNumber()); - EXPECT_CALL(visitor_, OnError(&decoder)); - - absl::string_view input(test_data.input); - for (auto c : input) { - decoder.ProcessInput(&c, 1); - } - EXPECT_THAT(decoder.error(), IsError(QUIC_HTTP_FRAME_ERROR)); - EXPECT_EQ(test_data.error_message, decoder.error_detail()); + { + HttpDecoder decoder(&visitor_); + EXPECT_CALL(visitor_, OnAcceptChFrameStart(_)).Times(AnyNumber()); + EXPECT_CALL(visitor_, OnError(&decoder)); + + absl::string_view input(test_data.input); + for (auto c : input) { + decoder.ProcessInput(&c, 1); } + EXPECT_THAT(decoder.error(), IsError(QUIC_HTTP_FRAME_ERROR)); + EXPECT_EQ(test_data.error_message, decoder.error_detail()); } } } -TEST_F(HttpDecoderTest, EmptyCancelPushFrame) { - if (GetQuicReloadableFlag(quic_error_on_http3_push)) { - return; - } - - std::string input = absl::HexStringToBytes( - "03" // type (CANCEL_PUSH) - "00"); // frame length - - EXPECT_CALL(visitor_, OnError(&decoder_)); - EXPECT_EQ(input.size(), ProcessInput(input)); - EXPECT_THAT(decoder_.error(), IsError(QUIC_HTTP_FRAME_ERROR)); - EXPECT_EQ("Unable to read CANCEL_PUSH push_id.", decoder_.error_detail()); -} - TEST_F(HttpDecoderTest, EmptySettingsFrame) { std::string input = absl::HexStringToBytes( "04" // type (SETTINGS) @@ -1070,22 +784,6 @@ TEST_F(HttpDecoderTest, EmptySettingsFrame) { EXPECT_EQ("", decoder_.error_detail()); } -// Regression test for https://crbug.com/1001823. -TEST_F(HttpDecoderTest, EmptyPushPromiseFrame) { - if (GetQuicReloadableFlag(quic_error_on_http3_push)) { - return; - } - - std::string input = absl::HexStringToBytes( - "05" // type (PUSH_PROMISE) - "00"); // frame length - - EXPECT_CALL(visitor_, OnError(&decoder_)); - EXPECT_EQ(input.size(), ProcessInput(input)); - EXPECT_THAT(decoder_.error(), IsError(QUIC_HTTP_FRAME_ERROR)); - EXPECT_EQ("PUSH_PROMISE frame with empty payload.", decoder_.error_detail()); -} - TEST_F(HttpDecoderTest, EmptyGoAwayFrame) { std::string input = absl::HexStringToBytes( "07" // type (GOAWAY) @@ -1120,97 +818,8 @@ TEST_F(HttpDecoderTest, LargeStreamIdInGoAway) { EXPECT_EQ("", decoder_.error_detail()); } -TEST_F(HttpDecoderTest, OldPriorityUpdateFrame) { - if (GetQuicReloadableFlag(quic_ignore_old_priority_update_frame)) { - return; - } - - InSequence s; - std::string input1 = absl::HexStringToBytes( - "0f" // type (PRIORITY_UPDATE) - "02" // length - "00" // prioritized element type: REQUEST_STREAM - "03"); // prioritized element id - - PriorityUpdateFrame priority_update1; - priority_update1.prioritized_element_type = REQUEST_STREAM; - priority_update1.prioritized_element_id = 0x03; - - // Visitor pauses processing. - EXPECT_CALL(visitor_, OnPriorityUpdateFrameStart(2)).WillOnce(Return(false)); - absl::string_view remaining_input(input1); - QuicByteCount processed_bytes = - ProcessInputWithGarbageAppended(remaining_input); - EXPECT_EQ(2u, processed_bytes); - remaining_input = remaining_input.substr(processed_bytes); - - EXPECT_CALL(visitor_, OnPriorityUpdateFrame(priority_update1)) - .WillOnce(Return(false)); - processed_bytes = ProcessInputWithGarbageAppended(remaining_input); - EXPECT_EQ(remaining_input.size(), processed_bytes); - EXPECT_THAT(decoder_.error(), IsQuicNoError()); - EXPECT_EQ("", decoder_.error_detail()); - - // Process the full frame. - EXPECT_CALL(visitor_, OnPriorityUpdateFrameStart(2)); - EXPECT_CALL(visitor_, OnPriorityUpdateFrame(priority_update1)); - EXPECT_EQ(input1.size(), ProcessInput(input1)); - EXPECT_THAT(decoder_.error(), IsQuicNoError()); - EXPECT_EQ("", decoder_.error_detail()); - - // Process the frame incrementally. - EXPECT_CALL(visitor_, OnPriorityUpdateFrameStart(2)); - EXPECT_CALL(visitor_, OnPriorityUpdateFrame(priority_update1)); - ProcessInputCharByChar(input1); - EXPECT_THAT(decoder_.error(), IsQuicNoError()); - EXPECT_EQ("", decoder_.error_detail()); - - std::string input2 = absl::HexStringToBytes( - "0f" // type (PRIORITY_UPDATE) - "05" // length - "80" // prioritized element type: PUSH_STREAM - "05" // prioritized element id - "666f6f"); // priority field value: "foo" - - PriorityUpdateFrame priority_update2; - priority_update2.prioritized_element_type = PUSH_STREAM; - priority_update2.prioritized_element_id = 0x05; - priority_update2.priority_field_value = "foo"; - - // Visitor pauses processing. - EXPECT_CALL(visitor_, OnPriorityUpdateFrameStart(2)).WillOnce(Return(false)); - remaining_input = input2; - processed_bytes = ProcessInputWithGarbageAppended(remaining_input); - EXPECT_EQ(2u, processed_bytes); - remaining_input = remaining_input.substr(processed_bytes); - - EXPECT_CALL(visitor_, OnPriorityUpdateFrame(priority_update2)) - .WillOnce(Return(false)); - processed_bytes = ProcessInputWithGarbageAppended(remaining_input); - EXPECT_EQ(remaining_input.size(), processed_bytes); - EXPECT_THAT(decoder_.error(), IsQuicNoError()); - EXPECT_EQ("", decoder_.error_detail()); - - // Process the full frame. - EXPECT_CALL(visitor_, OnPriorityUpdateFrameStart(2)); - EXPECT_CALL(visitor_, OnPriorityUpdateFrame(priority_update2)); - EXPECT_EQ(input2.size(), ProcessInput(input2)); - EXPECT_THAT(decoder_.error(), IsQuicNoError()); - EXPECT_EQ("", decoder_.error_detail()); - - // Process the frame incrementally. - EXPECT_CALL(visitor_, OnPriorityUpdateFrameStart(2)); - EXPECT_CALL(visitor_, OnPriorityUpdateFrame(priority_update2)); - ProcessInputCharByChar(input2); - EXPECT_THAT(decoder_.error(), IsQuicNoError()); - EXPECT_EQ("", decoder_.error_detail()); -} - +// Old PRIORITY_UPDATE frame is parsed as unknown frame. TEST_F(HttpDecoderTest, ObsoletePriorityUpdateFrame) { - if (!GetQuicReloadableFlag(quic_ignore_old_priority_update_frame)) { - return; - } - const QuicByteCount header_length = 2; const QuicByteCount payload_length = 3; InSequence s; @@ -1324,46 +933,6 @@ TEST_F(HttpDecoderTest, PriorityUpdateFrame) { } TEST_F(HttpDecoderTest, CorruptPriorityUpdateFrame) { - if (GetQuicReloadableFlag(quic_ignore_old_priority_update_frame)) { - return; - } - - std::string payload1 = absl::HexStringToBytes( - "80" // prioritized element type: PUSH_STREAM - "4005"); // prioritized element id - std::string payload2 = - absl::HexStringToBytes("42"); // invalid prioritized element type - struct { - const char* const payload; - size_t payload_length; - const char* const error_message; - } kTestData[] = { - {payload1.data(), 0, "Unable to read prioritized element type."}, - {payload1.data(), 1, "Unable to read prioritized element id."}, - {payload1.data(), 2, "Unable to read prioritized element id."}, - {payload2.data(), 1, "Invalid prioritized element type."}, - }; - - for (const auto& test_data : kTestData) { - std::string input; - input.push_back(15u); // type PRIORITY_UPDATE - input.push_back(test_data.payload_length); - size_t header_length = input.size(); - input.append(test_data.payload, test_data.payload_length); - - HttpDecoder decoder(&visitor_); - EXPECT_CALL(visitor_, OnPriorityUpdateFrameStart(header_length)); - EXPECT_CALL(visitor_, OnError(&decoder)); - - QuicByteCount processed_bytes = - decoder.ProcessInput(input.data(), input.size()); - EXPECT_EQ(input.size(), processed_bytes); - EXPECT_THAT(decoder.error(), IsError(QUIC_HTTP_FRAME_ERROR)); - EXPECT_EQ(test_data.error_message, decoder.error_detail()); - } -} - -TEST_F(HttpDecoderTest, CorruptNewPriorityUpdateFrame) { std::string payload = absl::HexStringToBytes("4005"); // prioritized element id struct { diff --git a/chromium/net/third_party/quiche/src/quic/core/http/http_encoder.cc b/chromium/net/third_party/quiche/src/quic/core/http/http_encoder.cc index 2fbc2380cf7..25f6200e59b 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/http_encoder.cc +++ b/chromium/net/third_party/quiche/src/quic/core/http/http_encoder.cc @@ -34,23 +34,30 @@ QuicByteCount GetTotalLength(QuicByteCount payload_length, HttpFrameType type) { } // namespace // static -QuicByteCount HttpEncoder::SerializeDataFrameHeader( +QuicByteCount HttpEncoder::GetDataFrameHeaderLength( + QuicByteCount payload_length) { + QUICHE_DCHECK_NE(0u, payload_length); + return QuicDataWriter::GetVarInt62Len(payload_length) + + QuicDataWriter::GetVarInt62Len( + static_cast<uint64_t>(HttpFrameType::DATA)); +} + +// static +QuicBuffer HttpEncoder::SerializeDataFrameHeader( QuicByteCount payload_length, - std::unique_ptr<char[]>* output) { + QuicBufferAllocator* allocator) { QUICHE_DCHECK_NE(0u, payload_length); - QuicByteCount header_length = QuicDataWriter::GetVarInt62Len(payload_length) + - QuicDataWriter::GetVarInt62Len( - static_cast<uint64_t>(HttpFrameType::DATA)); + QuicByteCount header_length = GetDataFrameHeaderLength(payload_length); - output->reset(new char[header_length]); - QuicDataWriter writer(header_length, output->get()); + QuicBuffer header(allocator, header_length); + QuicDataWriter writer(header.size(), header.data()); if (WriteFrameHeader(payload_length, HttpFrameType::DATA, &writer)) { - return header_length; + return header; } QUIC_DLOG(ERROR) << "Http encoder failed when attempting to serialize data frame header."; - return 0; + return QuicBuffer(); } // static diff --git a/chromium/net/third_party/quiche/src/quic/core/http/http_encoder.h b/chromium/net/third_party/quiche/src/quic/core/http/http_encoder.h index 0a8aa84e0b7..585e739ef53 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/http_encoder.h +++ b/chromium/net/third_party/quiche/src/quic/core/http/http_encoder.h @@ -7,6 +7,7 @@ #include <memory> #include "quic/core/http/http_frames.h" +#include "quic/core/quic_buffer_allocator.h" #include "quic/core/quic_error_codes.h" #include "quic/core/quic_types.h" #include "quic/platform/api/quic_export.h" @@ -21,11 +22,13 @@ class QUIC_EXPORT_PRIVATE HttpEncoder { public: HttpEncoder() = delete; - // Serializes a DATA frame header into a new buffer stored in |output|. - // Returns the length of the buffer on success, or 0 otherwise. - static QuicByteCount SerializeDataFrameHeader( - QuicByteCount payload_length, - std::unique_ptr<char[]>* output); + // Returns the length of the header for a DATA frame. + static QuicByteCount GetDataFrameHeaderLength(QuicByteCount payload_length); + + // Serializes a DATA frame header into a QuicBuffer; returns said QuicBuffer + // on success, empty buffer otherwise. + static QuicBuffer SerializeDataFrameHeader(QuicByteCount payload_length, + QuicBufferAllocator* allocator); // Serializes a HEADERS frame header into a new buffer stored in |output|. // Returns the length of the buffer on success, or 0 otherwise. diff --git a/chromium/net/third_party/quiche/src/quic/core/http/http_encoder_test.cc b/chromium/net/third_party/quiche/src/quic/core/http/http_encoder_test.cc index 66e988facca..c2b0f36946e 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/http_encoder_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/http/http_encoder_test.cc @@ -5,6 +5,7 @@ #include "quic/core/http/http_encoder.h" #include "absl/base/macros.h" +#include "quic/core/quic_simple_buffer_allocator.h" #include "quic/platform/api/quic_flags.h" #include "quic/platform/api/quic_test.h" #include "quic/test_tools/quic_test_utils.h" @@ -14,16 +15,15 @@ namespace quic { namespace test { TEST(HttpEncoderTest, SerializeDataFrameHeader) { - std::unique_ptr<char[]> buffer; - uint64_t length = - HttpEncoder::SerializeDataFrameHeader(/* payload_length = */ 5, &buffer); + QuicBuffer buffer = HttpEncoder::SerializeDataFrameHeader( + /* payload_length = */ 5, SimpleBufferAllocator::Get()); char output[] = {// type (DATA) 0x00, // length 0x05}; - EXPECT_EQ(ABSL_ARRAYSIZE(output), length); - quiche::test::CompareCharArraysWithHexError("DATA", buffer.get(), length, - output, ABSL_ARRAYSIZE(output)); + EXPECT_EQ(ABSL_ARRAYSIZE(output), buffer.size()); + quiche::test::CompareCharArraysWithHexError( + "DATA", buffer.data(), buffer.size(), output, ABSL_ARRAYSIZE(output)); } TEST(HttpEncoderTest, SerializeHeadersFrameHeader) { @@ -87,40 +87,42 @@ TEST(HttpEncoderTest, SerializePriorityUpdateFrame) { PriorityUpdateFrame priority_update1; priority_update1.prioritized_element_type = REQUEST_STREAM; priority_update1.prioritized_element_id = 0x03; - char output1[] = {0x80, 0x0f, 0x07, 0x00, // type (PRIORITY_UPDATE) - 0x01, // length - 0x03}; // prioritized element id + uint8_t output1[] = {0x80, 0x0f, 0x07, 0x00, // type (PRIORITY_UPDATE) + 0x01, // length + 0x03}; // prioritized element id std::unique_ptr<char[]> buffer; uint64_t length = HttpEncoder::SerializePriorityUpdateFrame(priority_update1, &buffer); EXPECT_EQ(ABSL_ARRAYSIZE(output1), length); - quiche::test::CompareCharArraysWithHexError("PRIORITY_UPDATE", buffer.get(), - length, output1, - ABSL_ARRAYSIZE(output1)); + quiche::test::CompareCharArraysWithHexError( + "PRIORITY_UPDATE", buffer.get(), length, reinterpret_cast<char*>(output1), + ABSL_ARRAYSIZE(output1)); } TEST(HttpEncoderTest, SerializeAcceptChFrame) { AcceptChFrame accept_ch; - char output1[] = {0x40, 0x89, // type (ACCEPT_CH) - 0x00}; // length + uint8_t output1[] = {0x40, 0x89, // type (ACCEPT_CH) + 0x00}; // length std::unique_ptr<char[]> buffer; uint64_t length = HttpEncoder::SerializeAcceptChFrame(accept_ch, &buffer); EXPECT_EQ(ABSL_ARRAYSIZE(output1), length); quiche::test::CompareCharArraysWithHexError("ACCEPT_CH", buffer.get(), length, - output1, ABSL_ARRAYSIZE(output1)); + reinterpret_cast<char*>(output1), + ABSL_ARRAYSIZE(output1)); accept_ch.entries.push_back({"foo", "bar"}); - char output2[] = {0x40, 0x89, // type (ACCEPT_CH) - 0x08, // payload length - 0x03, 0x66, 0x6f, 0x6f, // length of "foo"; "foo" - 0x03, 0x62, 0x61, 0x72}; // length of "bar"; "bar" + uint8_t output2[] = {0x40, 0x89, // type (ACCEPT_CH) + 0x08, // payload length + 0x03, 0x66, 0x6f, 0x6f, // length of "foo"; "foo" + 0x03, 0x62, 0x61, 0x72}; // length of "bar"; "bar" length = HttpEncoder::SerializeAcceptChFrame(accept_ch, &buffer); EXPECT_EQ(ABSL_ARRAYSIZE(output2), length); quiche::test::CompareCharArraysWithHexError("ACCEPT_CH", buffer.get(), length, - output2, ABSL_ARRAYSIZE(output2)); + reinterpret_cast<char*>(output2), + ABSL_ARRAYSIZE(output2)); } TEST(HttpEncoderTest, SerializeWebTransportStreamFrameHeader) { diff --git a/chromium/net/third_party/quiche/src/quic/core/http/http_frames.h b/chromium/net/third_party/quiche/src/quic/core/http/http_frames.h index b14ebe5a91b..6a00ad7bb53 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/http_frames.h +++ b/chromium/net/third_party/quiche/src/quic/core/http/http_frames.h @@ -20,6 +20,9 @@ namespace quic { +// TODO(b/171463363): Remove. +using PushId = uint64_t; + enum class HttpFrameType { DATA = 0x0, HEADERS = 0x1, @@ -28,12 +31,9 @@ enum class HttpFrameType { PUSH_PROMISE = 0x5, GOAWAY = 0x7, MAX_PUSH_ID = 0xD, - // https://tools.ietf.org/html/draft-ietf-httpbis-priority-01 - // TODO(b/147306124): Remove. - PRIORITY_UPDATE = 0XF, // https://tools.ietf.org/html/draft-davidben-http-client-hint-reliability-02 ACCEPT_CH = 0x89, - // https://tools.ietf.org/html/draft-ietf-httpbis-priority-02 + // https://tools.ietf.org/html/draft-ietf-httpbis-priority-03 PRIORITY_UPDATE_REQUEST_STREAM = 0xF0700, // https://www.ietf.org/archive/id/draft-ietf-webtrans-http3-00.html WEBTRANSPORT_STREAM = 0x41, @@ -55,20 +55,6 @@ struct QUIC_EXPORT_PRIVATE HeadersFrame { absl::string_view headers; }; -// 7.2.3. CANCEL_PUSH -// -// The CANCEL_PUSH frame (type=0x3) is used to request cancellation of -// server push prior to the push stream being created. -using PushId = uint64_t; - -struct QUIC_EXPORT_PRIVATE CancelPushFrame { - PushId push_id; - - bool operator==(const CancelPushFrame& rhs) const { - return push_id == rhs.push_id; - } -}; - // 7.2.4. SETTINGS // // The SETTINGS frame (type=0x4) conveys configuration parameters that @@ -102,20 +88,6 @@ struct QUIC_EXPORT_PRIVATE SettingsFrame { } }; -// 7.2.5. PUSH_PROMISE -// -// The PUSH_PROMISE frame (type=0x05) is used to carry a request header -// set from server to client, as in HTTP/2. -// TODO(b/171463363): Remove. -struct QUIC_EXPORT_PRIVATE PushPromiseFrame { - PushId push_id; - absl::string_view headers; - - bool operator==(const PushPromiseFrame& rhs) const { - return push_id == rhs.push_id && headers == rhs.headers; - } -}; - // 7.2.6. GOAWAY // // The GOAWAY frame (type=0x7) is used to initiate shutdown of a connection by @@ -144,10 +116,9 @@ struct QUIC_EXPORT_PRIVATE MaxPushIdFrame { // https://httpwg.org/http-extensions/draft-ietf-httpbis-priority.html // // The PRIORITY_UPDATE frame specifies the sender-advised priority of a stream. -// https://tools.ietf.org/html/draft-ietf-httpbis-priority-01 uses frame type -// 0x0f, both for request streams and push streams. -// https://tools.ietf.org/html/draft-ietf-httpbis-priority-02 uses frame types -// 0xf0700 for request streams and 0xf0701 for push streams (not implemented). +// Frame type 0xf0700 (called PRIORITY_UPDATE_REQUEST_STREAM in the +// implementation) is used for for request streams. +// Frame type 0xf0701 is used for push streams and is not implemented. // Length of a priority frame's first byte. const QuicByteCount kPriorityFirstByteLength = 1; diff --git a/chromium/net/third_party/quiche/src/quic/core/http/http_frames_test.cc b/chromium/net/third_party/quiche/src/quic/core/http/http_frames_test.cc index c9a285dc8d9..24fd5780af8 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/http_frames_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/http/http_frames_test.cc @@ -12,17 +12,6 @@ namespace quic { namespace test { -TEST(HttpFramesTest, CancelPushFrame) { - CancelPushFrame a{1}; - EXPECT_TRUE(a == a); - - CancelPushFrame b{1}; - EXPECT_TRUE(a == b); - - b.push_id = 2; - EXPECT_FALSE(a == b); -} - TEST(HttpFramesTest, SettingsFrame) { SettingsFrame a; EXPECT_TRUE(a == a); @@ -44,21 +33,6 @@ TEST(HttpFramesTest, SettingsFrame) { EXPECT_EQ("SETTINGS_QPACK_MAX_TABLE_CAPACITY = 1; ", s.str()); } -TEST(HttpFramesTest, PushPromiseFrame) { - PushPromiseFrame a{1, ""}; - EXPECT_TRUE(a == a); - - PushPromiseFrame b{2, ""}; - EXPECT_FALSE(a == b); - - b.push_id = 1; - EXPECT_TRUE(a == b); - - b.headers = "foo"; - EXPECT_FALSE(a == b); - EXPECT_TRUE(b == b); -} - TEST(HttpFramesTest, GoAwayFrame) { GoAwayFrame a{1}; EXPECT_TRUE(a == a); diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_client_promised_info.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_client_promised_info.cc index 36011804056..1c951ba108b 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/quic_client_promised_info.cc +++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_client_promised_info.cc @@ -24,7 +24,11 @@ QuicClientPromisedInfo::QuicClientPromisedInfo( url_(std::move(url)), client_request_delegate_(nullptr) {} -QuicClientPromisedInfo::~QuicClientPromisedInfo() {} +QuicClientPromisedInfo::~QuicClientPromisedInfo() { + if (cleanup_alarm_ != nullptr) { + cleanup_alarm_->PermanentCancel(); + } +} void QuicClientPromisedInfo::CleanupAlarm::OnAlarm() { QUIC_DVLOG(1) << "self GC alarm for stream " << promised_->id_; diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_receive_control_stream.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_receive_control_stream.cc index 39d8b42a5a2..9c164d0e448 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/quic_receive_control_stream.cc +++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_receive_control_stream.cc @@ -65,14 +65,6 @@ void QuicReceiveControlStream::OnError(HttpDecoder* decoder) { stream_delegate()->OnStreamError(decoder->error(), decoder->error_detail()); } -bool QuicReceiveControlStream::OnCancelPushFrame(const CancelPushFrame& frame) { - if (spdy_session()->debug_visitor()) { - spdy_session()->debug_visitor()->OnCancelPushFrameReceived(frame); - } - - return ValidateFrameType(HttpFrameType::CANCEL_PUSH); -} - bool QuicReceiveControlStream::OnMaxPushIdFrame(const MaxPushIdFrame& frame) { if (spdy_session()->debug_visitor()) { spdy_session()->debug_visitor()->OnMaxPushIdFrameReceived(frame); @@ -144,33 +136,9 @@ bool QuicReceiveControlStream::OnHeadersFrameEnd() { return false; } -bool QuicReceiveControlStream::OnPushPromiseFrameStart( - QuicByteCount /*header_length*/) { - return ValidateFrameType(HttpFrameType::PUSH_PROMISE); -} - -bool QuicReceiveControlStream::OnPushPromiseFramePushId( - PushId /*push_id*/, - QuicByteCount /*push_id_length*/, - QuicByteCount /*header_block_length*/) { - QUICHE_NOTREACHED(); - return false; -} - -bool QuicReceiveControlStream::OnPushPromiseFramePayload( - absl::string_view /*payload*/) { - QUICHE_NOTREACHED(); - return false; -} - -bool QuicReceiveControlStream::OnPushPromiseFrameEnd() { - QUICHE_NOTREACHED(); - return false; -} - bool QuicReceiveControlStream::OnPriorityUpdateFrameStart( QuicByteCount /*header_length*/) { - return ValidateFrameType(HttpFrameType::PRIORITY_UPDATE); + return ValidateFrameType(HttpFrameType::PRIORITY_UPDATE_REQUEST_STREAM); } bool QuicReceiveControlStream::OnPriorityUpdateFrame( @@ -264,9 +232,8 @@ bool QuicReceiveControlStream::OnUnknownFrameEnd() { bool QuicReceiveControlStream::ValidateFrameType(HttpFrameType frame_type) { // Certain frame types are forbidden. - if ((frame_type == HttpFrameType::DATA || - frame_type == HttpFrameType::HEADERS || - frame_type == HttpFrameType::PUSH_PROMISE) || + if (frame_type == HttpFrameType::DATA || + frame_type == HttpFrameType::HEADERS || (spdy_session()->perspective() == Perspective::IS_CLIENT && frame_type == HttpFrameType::MAX_PUSH_ID) || (spdy_session()->perspective() == Perspective::IS_SERVER && diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_receive_control_stream.h b/chromium/net/third_party/quiche/src/quic/core/http/quic_receive_control_stream.h index 4ed01043662..3c61d3cb621 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/quic_receive_control_stream.h +++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_receive_control_stream.h @@ -33,9 +33,8 @@ class QUIC_EXPORT_PRIVATE QuicReceiveControlStream // Implementation of QuicStream. void OnDataAvailable() override; - // HttpDecoderVisitor implementation. + // HttpDecoder::Visitor implementation. void OnError(HttpDecoder* decoder) override; - bool OnCancelPushFrame(const CancelPushFrame& frame) override; bool OnMaxPushIdFrame(const MaxPushIdFrame& frame) override; bool OnGoAwayFrame(const GoAwayFrame& frame) override; bool OnSettingsFrameStart(QuicByteCount header_length) override; @@ -48,12 +47,6 @@ class QUIC_EXPORT_PRIVATE QuicReceiveControlStream QuicByteCount payload_length) override; bool OnHeadersFramePayload(absl::string_view payload) override; bool OnHeadersFrameEnd() override; - bool OnPushPromiseFrameStart(QuicByteCount header_length) override; - bool OnPushPromiseFramePushId(PushId push_id, - QuicByteCount push_id_length, - QuicByteCount header_block_length) override; - bool OnPushPromiseFramePayload(absl::string_view payload) override; - bool OnPushPromiseFrameEnd() override; bool OnPriorityUpdateFrameStart(QuicByteCount header_length) override; bool OnPriorityUpdateFrame(const PriorityUpdateFrame& frame) override; bool OnAcceptChFrameStart(QuicByteCount header_length) override; diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_receive_control_stream_test.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_receive_control_stream_test.cc index 03f21d8a70f..ae7a0d818cc 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/quic_receive_control_stream_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_receive_control_stream_test.cc @@ -9,6 +9,7 @@ #include "absl/strings/string_view.h" #include "quic/core/http/http_constants.h" #include "quic/core/qpack/qpack_header_table.h" +#include "quic/core/quic_simple_buffer_allocator.h" #include "quic/core/quic_types.h" #include "quic/core/quic_utils.h" #include "quic/test_tools/qpack/qpack_encoder_peer.h" @@ -239,12 +240,11 @@ TEST_P(QuicReceiveControlStreamTest, ReceiveSettingsFragments) { TEST_P(QuicReceiveControlStreamTest, ReceiveWrongFrame) { // DATA frame header without payload. - std::unique_ptr<char[]> buffer; - QuicByteCount header_length = - HttpEncoder::SerializeDataFrameHeader(/* payload_length = */ 2, &buffer); - std::string data = std::string(buffer.get(), header_length); + QuicBuffer data = HttpEncoder::SerializeDataFrameHeader( + /* payload_length = */ 2, SimpleBufferAllocator::Get()); - QuicStreamFrame frame(receive_control_stream_->id(), false, 1, data); + QuicStreamFrame frame(receive_control_stream_->id(), false, 1, + data.AsStringView()); EXPECT_CALL( *connection_, CloseConnection(QUIC_HTTP_FRAME_UNEXPECTED_ON_CONTROL_STREAM, _, _)); @@ -260,7 +260,7 @@ TEST_P(QuicReceiveControlStreamTest, EXPECT_CALL(*connection_, CloseConnection(QUIC_HTTP_MISSING_SETTINGS_FRAME, "First frame received on control stream is type " - "15, but it must be SETTINGS.", + "984832, but it must be SETTINGS.", _)) .WillOnce( Invoke(connection_, &MockQuicConnection::ReallyCloseConnection)); @@ -308,12 +308,7 @@ TEST_P(QuicReceiveControlStreamTest, PushPromiseOnControlStreamShouldClose) { "00"); // push ID QuicStreamFrame frame(receive_control_stream_->id(), false, 1, push_promise_frame); - EXPECT_CALL( - *connection_, - CloseConnection(GetQuicReloadableFlag(quic_error_on_http3_push) - ? QUIC_HTTP_FRAME_ERROR - : QUIC_HTTP_FRAME_UNEXPECTED_ON_CONTROL_STREAM, - _, _)) + EXPECT_CALL(*connection_, CloseConnection(QUIC_HTTP_FRAME_ERROR, _, _)) .WillOnce( Invoke(connection_, &MockQuicConnection::ReallyCloseConnection)); EXPECT_CALL(*connection_, SendConnectionClosePacket(_, _, _)); @@ -384,21 +379,10 @@ TEST_P(QuicReceiveControlStreamTest, CancelPushFrameBeforeSettings) { "01" // payload length "01"); // push ID - if (GetQuicReloadableFlag(quic_error_on_http3_push)) { - EXPECT_CALL(*connection_, CloseConnection(QUIC_HTTP_FRAME_ERROR, - "CANCEL_PUSH frame received.", _)) - .WillOnce( - Invoke(connection_, &MockQuicConnection::ReallyCloseConnection)); - } else { - EXPECT_CALL( - *connection_, - CloseConnection(QUIC_HTTP_MISSING_SETTINGS_FRAME, - "First frame received on control stream is type " - "3, but it must be SETTINGS.", - _)) - .WillOnce( - Invoke(connection_, &MockQuicConnection::ReallyCloseConnection)); - } + EXPECT_CALL(*connection_, CloseConnection(QUIC_HTTP_FRAME_ERROR, + "CANCEL_PUSH frame received.", _)) + .WillOnce( + Invoke(connection_, &MockQuicConnection::ReallyCloseConnection)); EXPECT_CALL(*connection_, SendConnectionClosePacket(_, _, _)); EXPECT_CALL(session_, OnConnectionClosed(_, _)); diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_send_control_stream_test.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_send_control_stream_test.cc index c5af1dc4a90..3066cb38871 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/quic_send_control_stream_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_send_control_stream_test.cc @@ -133,7 +133,7 @@ TEST_P(QuicSendControlStreamTest, WriteSettings) { "4040" // 0x40 as the reserved frame type "01" // 1 byte frame length "61"); // payload "a" - if (GetQuicReloadableFlag(quic_h3_datagram)) { + if (QuicSpdySessionPeer::ShouldNegotiateHttp3Datagram(&session_)) { expected_write_data = absl::HexStringToBytes( "00" // stream type: control stream "04" // frame type: SETTINGS frame diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_server_session_base.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_server_session_base.cc index c78df58e18a..69d6cb47a9d 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/quic_server_session_base.cc +++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_server_session_base.cc @@ -282,4 +282,23 @@ void QuicServerSessionBase::SendSettingsToCryptoStream() { std::move(serialized_settings)); } +QuicSSLConfig QuicServerSessionBase::GetSSLConfig() const { + QUICHE_DCHECK(crypto_config_ && crypto_config_->proof_source()); + + QuicSSLConfig ssl_config = QuicSpdySession::GetSSLConfig(); + if (!GetQuicReloadableFlag(quic_tls_set_signature_algorithm_prefs) || + !crypto_config_ || !crypto_config_->proof_source()) { + return ssl_config; + } + + absl::InlinedVector<uint16_t, 8> signature_algorithms = + crypto_config_->proof_source()->SupportedTlsSignatureAlgorithms(); + if (!signature_algorithms.empty()) { + QUIC_RELOADABLE_FLAG_COUNT_N(quic_tls_set_signature_algorithm_prefs, 1, 2); + ssl_config.signing_algorithm_prefs = std::move(signature_algorithms); + } + + return ssl_config; +} + } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_server_session_base.h b/chromium/net/third_party/quiche/src/quic/core/http/quic_server_session_base.h index 9bdaa30adf9..bd9f5e71f42 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/quic_server_session_base.h +++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_server_session_base.h @@ -68,6 +68,8 @@ class QUIC_EXPORT_PRIVATE QuicServerSessionBase : public QuicSpdySession { serving_region_ = serving_region; } + QuicSSLConfig GetSSLConfig() const override; + protected: // QuicSession methods(override them with return type of QuicSpdyStream*): QuicCryptoServerStreamBase* GetMutableCryptoStream() override; diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.cc index b71cba9958f..45bdd1d31b1 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.cc +++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.cc @@ -126,6 +126,10 @@ int QuicSpdyClientSession::GetNumSentClientHellos() const { return crypto_stream_->num_sent_client_hellos(); } +bool QuicSpdyClientSession::IsResumption() const { + return crypto_stream_->IsResumption(); +} + bool QuicSpdyClientSession::EarlyDataAccepted() const { return crypto_stream_->EarlyDataAccepted(); } diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.h b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.h index 88df049e06d..a1c0883b8ad 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.h +++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.h @@ -61,6 +61,10 @@ class QUIC_EXPORT_PRIVATE QuicSpdyClientSession // than the number of round-trips needed for the handshake. int GetNumSentClientHellos() const; + // Return true if the handshake performed is a TLS resumption. + // Always return false for QUIC Crypto. + bool IsResumption() const; + // Returns true if early data (0-RTT data) was sent and the server accepted // it. bool EarlyDataAccepted() const; diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream_test.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream_test.cc index fa4943dcda7..9043d0b0e71 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream_test.cc @@ -12,6 +12,7 @@ #include "quic/core/crypto/null_encrypter.h" #include "quic/core/http/quic_spdy_client_session.h" #include "quic/core/http/spdy_utils.h" +#include "quic/core/quic_simple_buffer_allocator.h" #include "quic/core/quic_utils.h" #include "quic/platform/api/quic_logging.h" #include "quic/platform/api/quic_socket_address.h" @@ -131,12 +132,10 @@ TEST_P(QuicSpdyClientStreamTest, TestFraming) { auto headers = AsHeaderList(headers_); stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(), headers); - std::unique_ptr<char[]> buffer; - QuicByteCount header_length = - HttpEncoder::SerializeDataFrameHeader(body_.length(), &buffer); - std::string header = std::string(buffer.get(), header_length); + QuicBuffer header = HttpEncoder::SerializeDataFrameHeader( + body_.length(), SimpleBufferAllocator::Get()); std::string data = VersionUsesHttp3(connection_->transport_version()) - ? header + body_ + ? absl::StrCat(header.AsStringView(), body_) : body_; stream_->OnStreamFrame( QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data)); @@ -160,12 +159,11 @@ TEST_P(QuicSpdyClientStreamTest, Test100ContinueBeforeSuccessful) { headers = AsHeaderList(headers_); stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(), headers); - std::unique_ptr<char[]> buffer; - QuicByteCount header_length = - HttpEncoder::SerializeDataFrameHeader(body_.length(), &buffer); - std::string header = std::string(buffer.get(), header_length); - std::string data = - connection_->version().UsesHttp3() ? header + body_ : body_; + QuicBuffer header = HttpEncoder::SerializeDataFrameHeader( + body_.length(), SimpleBufferAllocator::Get()); + std::string data = VersionUsesHttp3(connection_->transport_version()) + ? absl::StrCat(header.AsStringView(), body_) + : body_; stream_->OnStreamFrame( QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data)); // Make sure the 200 response got parsed correctly. @@ -190,12 +188,11 @@ TEST_P(QuicSpdyClientStreamTest, TestUnknownInformationalBeforeSuccessful) { headers = AsHeaderList(headers_); stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(), headers); - std::unique_ptr<char[]> buffer; - QuicByteCount header_length = - HttpEncoder::SerializeDataFrameHeader(body_.length(), &buffer); - std::string header = std::string(buffer.get(), header_length); - std::string data = - connection_->version().UsesHttp3() ? header + body_ : body_; + QuicBuffer header = HttpEncoder::SerializeDataFrameHeader( + body_.length(), SimpleBufferAllocator::Get()); + std::string data = VersionUsesHttp3(connection_->transport_version()) + ? absl::StrCat(header.AsStringView(), body_) + : body_; stream_->OnStreamFrame( QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data)); // Make sure the 200 response got parsed correctly. @@ -222,12 +219,10 @@ TEST_P(QuicSpdyClientStreamTest, TestFramingOnePacket) { auto headers = AsHeaderList(headers_); stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(), headers); - std::unique_ptr<char[]> buffer; - QuicByteCount header_length = - HttpEncoder::SerializeDataFrameHeader(body_.length(), &buffer); - std::string header = std::string(buffer.get(), header_length); + QuicBuffer header = HttpEncoder::SerializeDataFrameHeader( + body_.length(), SimpleBufferAllocator::Get()); std::string data = VersionUsesHttp3(connection_->transport_version()) - ? header + body_ + ? absl::StrCat(header.AsStringView(), body_) : body_; stream_->OnStreamFrame( QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data)); @@ -247,12 +242,10 @@ TEST_P(QuicSpdyClientStreamTest, EXPECT_THAT(stream_->stream_error(), IsQuicStreamNoError()); EXPECT_EQ("200", stream_->response_headers().find(":status")->second); EXPECT_EQ(200, stream_->response_code()); - std::unique_ptr<char[]> buffer; - QuicByteCount header_length = - HttpEncoder::SerializeDataFrameHeader(large_body.length(), &buffer); - std::string header = std::string(buffer.get(), header_length); + QuicBuffer header = HttpEncoder::SerializeDataFrameHeader( + large_body.length(), SimpleBufferAllocator::Get()); std::string data = VersionUsesHttp3(connection_->transport_version()) - ? header + large_body + ? absl::StrCat(header.AsStringView(), large_body) : large_body; EXPECT_CALL(session_, WriteControlFrame(_, _)); EXPECT_CALL(*connection_, @@ -290,12 +283,10 @@ TEST_P(QuicSpdyClientStreamTest, ReceivingTrailers) { // Now send the body, which should close the stream as the FIN has been // received, as well as all data. - std::unique_ptr<char[]> buffer; - QuicByteCount header_length = - HttpEncoder::SerializeDataFrameHeader(body_.length(), &buffer); - std::string header = std::string(buffer.get(), header_length); + QuicBuffer header = HttpEncoder::SerializeDataFrameHeader( + body_.length(), SimpleBufferAllocator::Get()); std::string data = VersionUsesHttp3(connection_->transport_version()) - ? header + body_ + ? absl::StrCat(header.AsStringView(), body_) : body_; stream_->OnStreamFrame( QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data)); diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_session.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_session.cc index 276a7aff6b6..56b0db3ff48 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_session.cc +++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_session.cc @@ -72,10 +72,6 @@ class AlpsFrameDecoder : public HttpDecoder::Visitor { // HttpDecoder::Visitor implementation. void OnError(HttpDecoder* /*decoder*/) override {} - bool OnCancelPushFrame(const CancelPushFrame& /*frame*/) override { - error_detail_ = "CANCEL_PUSH frame forbidden"; - return false; - } bool OnMaxPushIdFrame(const MaxPushIdFrame& /*frame*/) override { error_detail_ = "MAX_PUSH_ID frame forbidden"; return false; @@ -124,26 +120,6 @@ class AlpsFrameDecoder : public HttpDecoder::Visitor { QUICHE_NOTREACHED(); return false; } - bool OnPushPromiseFrameStart(QuicByteCount /*header_length*/) override { - error_detail_ = "PUSH_PROMISE frame forbidden"; - return false; - } - bool OnPushPromiseFramePushId( - PushId /*push_id*/, - QuicByteCount - /*push_id_length*/, - QuicByteCount /*header_block_length*/) override { - QUICHE_NOTREACHED(); - return false; - } - bool OnPushPromiseFramePayload(absl::string_view /*payload*/) override { - QUICHE_NOTREACHED(); - return false; - } - bool OnPushPromiseFrameEnd() override { - QUICHE_NOTREACHED(); - return false; - } bool OnPriorityUpdateFrameStart(QuicByteCount /*header_length*/) override { error_detail_ = "PRIORITY_UPDATE frame forbidden"; return false; @@ -461,14 +437,9 @@ Http3DebugVisitor::~Http3DebugVisitor() {} // Expected unidirectional static streams Requirement can be found at // https://tools.ietf.org/html/draft-ietf-quic-http-22#section-6.2. QuicSpdySession::QuicSpdySession( - QuicConnection* connection, - QuicSession::Visitor* visitor, - const QuicConfig& config, - const ParsedQuicVersionVector& supported_versions) - : QuicSession(connection, - visitor, - config, - supported_versions, + QuicConnection* connection, QuicSession::Visitor* visitor, + const QuicConfig& config, const ParsedQuicVersionVector& supported_versions) + : QuicSession(connection, visitor, config, supported_versions, /*num_expected_unidirectional_static_streams = */ VersionUsesHttp3(connection->transport_version()) ? static_cast<QuicStreamCount>( @@ -495,13 +466,13 @@ QuicSpdySession::QuicSpdySession( spdy_framer_(SpdyFramer::ENABLE_COMPRESSION), spdy_framer_visitor_(new SpdyFramerVisitor(this)), debug_visitor_(nullptr), - destruction_indicator_(123456789), - next_available_datagram_flow_id_(perspective() == Perspective::IS_SERVER - ? kFirstDatagramFlowIdServer - : kFirstDatagramFlowIdClient) { + destruction_indicator_(123456789) { h2_deframer_.set_visitor(spdy_framer_visitor_.get()); h2_deframer_.set_debug_visitor(spdy_framer_visitor_.get()); spdy_framer_.set_debug_visitor(spdy_framer_visitor_.get()); + if (decline_server_push_stream_) { + QUIC_RELOADABLE_FLAG_COUNT(quic_decline_server_push_stream); + } } QuicSpdySession::~QuicSpdySession() { @@ -553,7 +524,6 @@ void QuicSpdySession::FillSettingsFrame() { settings_.values[SETTINGS_MAX_FIELD_SECTION_SIZE] = max_inbound_header_list_size_; if (ShouldNegotiateHttp3Datagram() && version().UsesHttp3()) { - QUIC_RELOADABLE_FLAG_COUNT(quic_h3_datagram); settings_.values[SETTINGS_H3_DATAGRAM] = 1; } if (WillNegotiateWebTransport()) { @@ -1391,6 +1361,11 @@ QuicStream* QuicSpdySession::ProcessPendingStream(PendingStream* pending) { return receive_control_stream_; } case kServerPushStream: { // Push Stream. + if (decline_server_push_stream_) { + CloseConnectionWithDetails(QUIC_HTTP_RECEIVE_SERVER_PUSH, + "Received server push stream"); + return nullptr; + } QuicSpdyStream* stream = CreateIncomingStream(pending); return stream; } @@ -1645,53 +1620,55 @@ void QuicSpdySession::LogHeaderCompressionRatioHistogram( } } -QuicDatagramFlowId QuicSpdySession::GetNextDatagramFlowId() { - QuicDatagramFlowId result = next_available_datagram_flow_id_; - next_available_datagram_flow_id_ += kDatagramFlowIdIncrement; - return result; -} - -MessageStatus QuicSpdySession::SendHttp3Datagram(QuicDatagramFlowId flow_id, - absl::string_view payload) { +MessageStatus QuicSpdySession::SendHttp3Datagram( + QuicDatagramStreamId stream_id, + absl::optional<QuicDatagramContextId> context_id, + absl::string_view payload) { size_t slice_length = - QuicDataWriter::GetVarInt62Len(flow_id) + payload.length(); - QuicUniqueBufferPtr buffer = MakeUniqueBuffer( - connection()->helper()->GetStreamSendBufferAllocator(), slice_length); - QuicDataWriter writer(slice_length, buffer.get()); - if (!writer.WriteVarInt62(flow_id)) { - QUIC_BUG(quic_bug_10360_10) << "Failed to write HTTP/3 datagram flow ID"; + QuicDataWriter::GetVarInt62Len(stream_id) + payload.length(); + if (context_id.has_value()) { + slice_length += QuicDataWriter::GetVarInt62Len(context_id.value()); + } + QuicBuffer buffer(connection()->helper()->GetStreamSendBufferAllocator(), + slice_length); + QuicDataWriter writer(slice_length, buffer.data()); + if (!writer.WriteVarInt62(stream_id)) { + QUIC_BUG(h3 datagram stream ID write fail) + << "Failed to write HTTP/3 datagram stream ID"; return MESSAGE_STATUS_INTERNAL_ERROR; } + if (context_id.has_value()) { + if (!writer.WriteVarInt62(context_id.value())) { + QUIC_BUG(h3 datagram context ID write fail) + << "Failed to write HTTP/3 datagram context ID"; + return MESSAGE_STATUS_INTERNAL_ERROR; + } + } if (!writer.WriteBytes(payload.data(), payload.length())) { - QUIC_BUG(quic_bug_10360_11) << "Failed to write HTTP/3 datagram payload"; + QUIC_BUG(h3 datagram payload write fail) + << "Failed to write HTTP/3 datagram payload"; return MESSAGE_STATUS_INTERNAL_ERROR; } - QuicMemSlice slice(std::move(buffer), slice_length); + QuicMemSlice slice(std::move(buffer)); return datagram_queue()->SendOrQueueDatagram(std::move(slice)); } -void QuicSpdySession::RegisterHttp3FlowId( - QuicDatagramFlowId flow_id, - QuicSpdySession::Http3DatagramVisitor* visitor) { - QUICHE_DCHECK_NE(visitor, nullptr); - auto insertion_result = h3_datagram_registrations_.insert({flow_id, visitor}); - QUIC_BUG_IF(quic_bug_12477_7, !insertion_result.second) - << "Attempted to doubly register HTTP/3 flow ID " << flow_id; +void QuicSpdySession::SetMaxDatagramTimeInQueueForStreamId( + QuicStreamId /*stream_id*/, QuicTime::Delta max_time_in_queue) { + // TODO(b/184598230): implement this in a way that works for multiple sessions + // on a same connection. + datagram_queue()->SetMaxTimeInQueue(max_time_in_queue); } -void QuicSpdySession::UnregisterHttp3FlowId(QuicDatagramFlowId flow_id) { - size_t num_erased = h3_datagram_registrations_.erase(flow_id); - QUIC_BUG_IF(quic_bug_12477_8, num_erased != 1) - << "Attempted to unregister unknown HTTP/3 flow ID " << flow_id; +void QuicSpdySession::RegisterHttp3DatagramFlowId(QuicDatagramStreamId flow_id, + QuicStreamId stream_id) { + h3_datagram_flow_id_to_stream_id_map_[flow_id] = stream_id; } -void QuicSpdySession::SetMaxTimeInQueueForFlowId( - QuicDatagramFlowId /*flow_id*/, - QuicTime::Delta max_time_in_queue) { - // TODO(b/184598230): implement this in a way that works for multiple sessions - // on a same connection. - datagram_queue()->SetMaxTimeInQueue(max_time_in_queue); +void QuicSpdySession::UnregisterHttp3DatagramFlowId( + QuicDatagramStreamId flow_id) { + h3_datagram_flow_id_to_stream_id_map_.erase(flow_id); } void QuicSpdySession::OnMessageReceived(absl::string_view message) { @@ -1701,20 +1678,38 @@ void QuicSpdySession::OnMessageReceived(absl::string_view message) { return; } QuicDataReader reader(message); - QuicDatagramFlowId flow_id; - if (!reader.ReadVarInt62(&flow_id)) { - QUIC_DLOG(ERROR) << "Failed to parse flow ID in received HTTP/3 datagram"; + uint64_t stream_id64; + if (!reader.ReadVarInt62(&stream_id64)) { + QUIC_DLOG(ERROR) << "Failed to parse stream ID in received HTTP/3 datagram"; return; } - auto it = h3_datagram_registrations_.find(flow_id); - if (it == h3_datagram_registrations_.end()) { - // TODO(dschinazi) buffer unknown HTTP/3 datagram flow IDs for a short + if (perspective() == Perspective::IS_SERVER) { + auto it = h3_datagram_flow_id_to_stream_id_map_.find(stream_id64); + if (it == h3_datagram_flow_id_to_stream_id_map_.end()) { + QUIC_DLOG(INFO) << "Received unknown HTTP/3 datagram flow ID " + << stream_id64; + return; + } + stream_id64 = it->second; + } + if (stream_id64 > std::numeric_limits<QuicStreamId>::max()) { + // TODO(b/181256914) make this a connection close once we deprecate + // draft-ietf-masque-h3-datagram-00 in favor of later drafts. + QUIC_DLOG(ERROR) << "Received unexpectedly high HTTP/3 datagram stream ID " + << stream_id64; + return; + } + QuicStreamId stream_id = static_cast<QuicStreamId>(stream_id64); + QuicSpdyStream* stream = + static_cast<QuicSpdyStream*>(GetActiveStream(stream_id)); + if (stream == nullptr) { + QUIC_DLOG(INFO) << "Received HTTP/3 datagram for unknown stream ID " + << stream_id; + // TODO(b/181256914) buffer unknown HTTP/3 datagram flow IDs for a short // period of time in case they were reordered. - QUIC_DLOG(ERROR) << "Received unknown HTTP/3 datagram flow ID " << flow_id; return; } - absl::string_view payload = reader.ReadRemainingPayload(); - it->second->OnHttp3Datagram(flow_id, payload); + stream->OnDatagramReceived(&reader); } bool QuicSpdySession::SupportsWebTransport() { @@ -1850,7 +1845,7 @@ void QuicSpdySession::DatagramObserver::OnDatagramProcessed( } bool QuicSpdySession::ShouldNegotiateHttp3Datagram() { - return GetQuicReloadableFlag(quic_h3_datagram); + return false; } #undef ENDPOINT // undef for jumbo builds diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_session.h b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_session.h index 214d1da13e2..78c132a6ad6 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_session.h +++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_session.h @@ -78,8 +78,6 @@ class QUIC_EXPORT_PRIVATE Http3DebugVisitor { virtual void OnAcceptChFrameReceivedViaAlps(const AcceptChFrame& /*frame*/) {} // Incoming HTTP/3 frames on the control stream. - // TODO(b/171463363): Remove. - virtual void OnCancelPushFrameReceived(const CancelPushFrame& /*frame*/) {} virtual void OnSettingsFrameReceived(const SettingsFrame& /*frame*/) = 0; virtual void OnGoAwayFrameReceived(const GoAwayFrame& /*frame*/) {} // TODO(b/171463363): Remove. @@ -96,15 +94,6 @@ class QUIC_EXPORT_PRIVATE Http3DebugVisitor { QuicByteCount /*compressed_headers_length*/) {} virtual void OnHeadersDecoded(QuicStreamId /*stream_id*/, QuicHeaderList /*headers*/) {} - // TODO(b/171463363): Remove. - virtual void OnPushPromiseFrameReceived(QuicStreamId /*stream_id*/, - QuicStreamId /*push_id*/, - QuicByteCount - /*compressed_headers_length*/) {} - // TODO(b/171463363): Remove. - virtual void OnPushPromiseDecoded(QuicStreamId /*stream_id*/, - QuicStreamId /*push_id*/, - QuicHeaderList /*headers*/) {} // Incoming HTTP/3 frames of unknown type on any stream. virtual void OnUnknownFrameReceived(QuicStreamId /*stream_id*/, @@ -125,12 +114,6 @@ class QUIC_EXPORT_PRIVATE Http3DebugVisitor { virtual void OnHeadersFrameSent( QuicStreamId /*stream_id*/, const spdy::SpdyHeaderBlock& /*header_block*/) {} - // TODO(b/171463363): Remove. - virtual void OnPushPromiseFrameSent( - QuicStreamId /*stream_id*/, - QuicStreamId - /*push_id*/, - const spdy::SpdyHeaderBlock& /*header_block*/) {} // 0-RTT related events. virtual void OnSettingsFrameResumed(const SettingsFrame& /*frame*/) {} @@ -391,41 +374,24 @@ class QUIC_EXPORT_PRIVATE QuicSpdySession // extension. virtual void OnAcceptChFrameReceivedViaAlps(const AcceptChFrame& /*frame*/); - // Generates a new HTTP/3 datagram flow ID. - QuicDatagramFlowId GetNextDatagramFlowId(); - // Whether HTTP/3 datagrams are supported on this session, based on received // SETTINGS. bool h3_datagram_supported() const { return h3_datagram_supported_; } - // Sends an HTTP/3 datagram. The flow ID is not part of |payload|. - MessageStatus SendHttp3Datagram(QuicDatagramFlowId flow_id, - absl::string_view payload); - - class QUIC_EXPORT_PRIVATE Http3DatagramVisitor { - public: - virtual ~Http3DatagramVisitor() {} - - // Called when an HTTP/3 datagram is received. |payload| does not contain - // the flow ID. - virtual void OnHttp3Datagram(QuicDatagramFlowId flow_id, - absl::string_view payload) = 0; - }; - - // Registers |visitor| to receive HTTP/3 datagrams for flow ID |flow_id|. This - // must not be called on a previously register flow ID without first calling - // UnregisterHttp3FlowId. |visitor| must be valid until a corresponding call - // to UnregisterHttp3FlowId. The flow ID must be unregistered before the - // QuicSpdySession is destroyed. - void RegisterHttp3FlowId(QuicDatagramFlowId flow_id, - Http3DatagramVisitor* visitor); - - // Unregister a given HTTP/3 datagram flow ID. - void UnregisterHttp3FlowId(QuicDatagramFlowId flow_id); - - // Sets max time in queue for a specified datagram flow ID. - void SetMaxTimeInQueueForFlowId(QuicDatagramFlowId flow_id, - QuicTime::Delta max_time_in_queue); + // This must not be used except by QuicSpdyStream::SendHttp3Datagram. + MessageStatus SendHttp3Datagram( + QuicDatagramStreamId stream_id, + absl::optional<QuicDatagramContextId> context_id, + absl::string_view payload); + // This must not be used except by QuicSpdyStream::SetMaxDatagramTimeInQueue. + void SetMaxDatagramTimeInQueueForStreamId(QuicStreamId stream_id, + QuicTime::Delta max_time_in_queue); + // This must not be used except by + // QuicSpdyStream::MaybeProcessReceivedWebTransportHeaders. + void RegisterHttp3DatagramFlowId(QuicDatagramStreamId flow_id, + QuicStreamId stream_id); + // This must not be used except by QuicSpdyStream::OnClose. + void UnregisterHttp3DatagramFlowId(QuicDatagramStreamId flow_id); // Override from QuicSession to support HTTP/3 datagrams. void OnMessageReceived(absl::string_view message) override; @@ -433,6 +399,9 @@ class QUIC_EXPORT_PRIVATE QuicSpdySession // Indicates whether the HTTP/3 session supports WebTransport. bool SupportsWebTransport(); + // Indicates whether both the peer and us support HTTP/3 Datagrams. + bool SupportsH3Datagram() { return h3_datagram_supported_; } + // Indicates whether the HTTP/3 session will indicate WebTransport support to // the peer. bool WillNegotiateWebTransport(); @@ -445,7 +414,7 @@ class QUIC_EXPORT_PRIVATE QuicSpdySession // until the SETTINGS are received. Only works for HTTP/3. bool ShouldBufferRequestsUntilSettings() { return version().UsesHttp3() && perspective() == Perspective::IS_SERVER && - WillNegotiateWebTransport(); + ShouldNegotiateHttp3Datagram(); } // Returns if the incoming bidirectional streams should process data. This is @@ -685,18 +654,17 @@ class QUIC_EXPORT_PRIVATE QuicSpdySession // frame has been sent yet. absl::optional<uint64_t> last_sent_http3_goaway_id_; - // Value of the smallest unused HTTP/3 datagram flow ID that this endpoint's - // datagram flow ID allocation service will use next. - QuicDatagramFlowId next_available_datagram_flow_id_; - // Whether both this endpoint and our peer support HTTP/3 datagrams. bool h3_datagram_supported_ = false; // Whether the peer has indicated WebTransport support. bool peer_supports_webtransport_ = false; - absl::flat_hash_map<QuicDatagramFlowId, Http3DatagramVisitor*> - h3_datagram_registrations_; + // This maps from draft-ietf-masque-h3-datagram-00 flow IDs to stream IDs. + // TODO(b/181256914) remove this when we deprecate support for that draft in + // favor of more recent ones. + absl::flat_hash_map<uint64_t, QuicStreamId> + h3_datagram_flow_id_to_stream_id_map_; // Whether any settings have been received, either from the peer or from a // session ticket. @@ -710,6 +678,10 @@ class QUIC_EXPORT_PRIVATE QuicSpdySession // Limited to kMaxUnassociatedWebTransportStreams; when the list is full, // oldest streams are evicated first. std::list<BufferedWebTransportStream> buffered_streams_; + + // Latched value of flag_quic_decline_server_push_stream. + const bool decline_server_push_stream_ = + GetQuicReloadableFlag(quic_decline_server_push_stream); }; } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_session_test.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_session_test.cc index 29e1f6b7bcd..5a984323de4 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_session_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_session_test.cc @@ -35,7 +35,6 @@ #include "quic/core/quic_versions.h" #include "quic/platform/api/quic_expect_bug.h" #include "quic/platform/api/quic_flags.h" -#include "quic/platform/api/quic_map_util.h" #include "quic/platform/api/quic_test.h" #include "quic/test_tools/qpack/qpack_encoder_peer.h" #include "quic/test_tools/qpack/qpack_test_utils.h" @@ -309,12 +308,10 @@ class TestSession : public QuicSpdySession { return QuicSpdySession::GetOrCreateStream(stream_id); } - QuicConsumedData WritevData(QuicStreamId id, - size_t write_length, - QuicStreamOffset offset, - StreamSendingState state, + QuicConsumedData WritevData(QuicStreamId id, size_t write_length, + QuicStreamOffset offset, StreamSendingState state, TransmissionType type, - absl::optional<EncryptionLevel> level) override { + EncryptionLevel level) override { bool fin = state != NO_FIN; QuicConsumedData consumed(write_length, fin); if (!writev_consumes_all_data_) { @@ -356,6 +353,13 @@ class TestSession : public QuicSpdySession { bool ShouldNegotiateWebTransport() override { return supports_webtransport_; } void set_supports_webtransport(bool value) { supports_webtransport_ = value; } + bool ShouldNegotiateHttp3Datagram() override { + return should_negotiate_h3_datagram_; + } + void set_should_negotiate_h3_datagram(bool value) { + should_negotiate_h3_datagram_ = value; + } + MOCK_METHOD(void, OnAcceptChFrame, (const AcceptChFrame&), (override)); using QuicSession::closed_streams; @@ -368,6 +372,7 @@ class TestSession : public QuicSpdySession { bool writev_consumes_all_data_; bool supports_webtransport_ = false; + bool should_negotiate_h3_datagram_ = false; }; class QuicSpdySessionTestBase : public QuicTestWithParam<ParsedQuicVersion> { @@ -420,7 +425,7 @@ class QuicSpdySessionTestBase : public QuicTestWithParam<ParsedQuicVersion> { first_stream_id = QuicUtils::GetCryptoStreamId(transport_version()); } for (QuicStreamId i = first_stream_id; i < 100; i++) { - if (!QuicContainsKey(closed_streams_, i)) { + if (closed_streams_.find(i) == closed_streams_.end()) { EXPECT_FALSE(session_.IsClosedStream(i)) << " stream id: " << i; } else { EXPECT_TRUE(session_.IsClosedStream(i)) << " stream id: " << i; @@ -566,8 +571,7 @@ class QuicSpdySessionTestBase : public QuicTestWithParam<ParsedQuicVersion> { headers.OnHeaderBlockStart(); headers.OnHeader(":method", "CONNECT"); headers.OnHeader(":protocol", "webtransport"); - headers.OnHeader("datagram-flow-id", - absl::StrCat(session_.GetNextDatagramFlowId())); + headers.OnHeader("datagram-flow-id", absl::StrCat(session_id)); stream->OnStreamHeaderList(/*fin=*/true, 0, headers); WebTransportHttp3* web_transport = session_.GetWebTransportSession(session_id); @@ -1495,61 +1499,6 @@ TEST_P(QuicSpdySessionTestServer, HandshakeUnblocksFlowControlBlockedStream) { EXPECT_FALSE(session_.IsStreamFlowControlBlocked()); } -TEST_P(QuicSpdySessionTestServer, - HandshakeUnblocksFlowControlBlockedCryptoStream) { - if (QuicVersionUsesCryptoFrames(transport_version()) || - connection_->encrypted_control_frames()) { - // QUIC version 47 onwards uses CRYPTO frames for the handshake, so this - // test doesn't make sense for those versions. With - // use_encryption_level_context, control frames can only be sent when - // encryption gets established, do not send BLOCKED for crypto streams. - return; - } - // Test that if the crypto stream is flow control blocked, then if the SHLO - // contains a larger send window offset, the stream becomes unblocked. - session_.set_writev_consumes_all_data(true); - TestCryptoStream* crypto_stream = session_.GetMutableCryptoStream(); - EXPECT_FALSE(crypto_stream->IsFlowControlBlocked()); - EXPECT_FALSE(session_.IsConnectionFlowControlBlocked()); - EXPECT_FALSE(session_.IsStreamFlowControlBlocked()); - QuicHeadersStream* headers_stream = - QuicSpdySessionPeer::GetHeadersStream(&session_); - EXPECT_FALSE(headers_stream->IsFlowControlBlocked()); - EXPECT_FALSE(session_.IsConnectionFlowControlBlocked()); - EXPECT_FALSE(session_.IsStreamFlowControlBlocked()); - EXPECT_CALL(*connection_, SendControlFrame(_)) - .WillOnce(Invoke(&ClearControlFrame)); - for (QuicStreamId i = 0; !crypto_stream->IsFlowControlBlocked() && i < 1000u; - i++) { - EXPECT_FALSE(session_.IsConnectionFlowControlBlocked()); - EXPECT_FALSE(session_.IsStreamFlowControlBlocked()); - QuicStreamOffset offset = crypto_stream->stream_bytes_written(); - QuicConfig config; - CryptoHandshakeMessage crypto_message; - config.ToHandshakeMessage(&crypto_message, transport_version()); - crypto_stream->SendHandshakeMessage(crypto_message, ENCRYPTION_INITIAL); - char buf[1000]; - QuicDataWriter writer(1000, buf, quiche::NETWORK_BYTE_ORDER); - crypto_stream->WriteStreamData(offset, crypto_message.size(), &writer); - } - EXPECT_TRUE(crypto_stream->IsFlowControlBlocked()); - EXPECT_FALSE(headers_stream->IsFlowControlBlocked()); - EXPECT_FALSE(session_.IsConnectionFlowControlBlocked()); - EXPECT_TRUE(session_.IsStreamFlowControlBlocked()); - EXPECT_FALSE(session_.HasDataToWrite()); - EXPECT_TRUE(crypto_stream->HasBufferedData()); - - // Now complete the crypto handshake, resulting in an increased flow control - // send window. - CompleteHandshake(); - EXPECT_TRUE(QuicSessionPeer::IsStreamWriteBlocked( - &session_, QuicUtils::GetCryptoStreamId(transport_version()))); - // Stream is now unblocked and will no longer have buffered data. - EXPECT_FALSE(crypto_stream->IsFlowControlBlocked()); - EXPECT_FALSE(session_.IsConnectionFlowControlBlocked()); - EXPECT_FALSE(session_.IsStreamFlowControlBlocked()); -} - #if !defined(OS_IOS) // This test is failing flakily for iOS bots. // http://crbug.com/425050 @@ -2058,25 +2007,33 @@ TEST_P(QuicSpdySessionTestClient, Http3ServerPush) { std::string frame_type1 = absl::HexStringToBytes("01"); QuicStreamId stream_id1 = GetNthServerInitiatedUnidirectionalStreamId(transport_version(), 0); - session_.OnStreamFrame(QuicStreamFrame(stream_id1, /* fin = */ false, - /* offset = */ 0, frame_type1)); + if (!GetQuicReloadableFlag(quic_decline_server_push_stream)) { + session_.OnStreamFrame(QuicStreamFrame(stream_id1, /* fin = */ false, + /* offset = */ 0, frame_type1)); - EXPECT_EQ(1u, QuicSessionPeer::GetNumOpenDynamicStreams(&session_)); - QuicStream* stream = session_.GetOrCreateStream(stream_id1); - EXPECT_EQ(1u, QuicStreamPeer::bytes_consumed(stream)); - EXPECT_EQ(1u, session_.flow_controller()->bytes_consumed()); - - // The same stream type can be encoded differently. - std::string frame_type2 = absl::HexStringToBytes("80000001"); - QuicStreamId stream_id2 = - GetNthServerInitiatedUnidirectionalStreamId(transport_version(), 1); - session_.OnStreamFrame(QuicStreamFrame(stream_id2, /* fin = */ false, - /* offset = */ 0, frame_type2)); - - EXPECT_EQ(2u, QuicSessionPeer::GetNumOpenDynamicStreams(&session_)); - stream = session_.GetOrCreateStream(stream_id2); - EXPECT_EQ(4u, QuicStreamPeer::bytes_consumed(stream)); - EXPECT_EQ(5u, session_.flow_controller()->bytes_consumed()); + EXPECT_EQ(1u, QuicSessionPeer::GetNumOpenDynamicStreams(&session_)); + QuicStream* stream = session_.GetOrCreateStream(stream_id1); + EXPECT_EQ(1u, QuicStreamPeer::bytes_consumed(stream)); + EXPECT_EQ(1u, session_.flow_controller()->bytes_consumed()); + + // The same stream type can be encoded differently. + std::string frame_type2 = absl::HexStringToBytes("80000001"); + QuicStreamId stream_id2 = + GetNthServerInitiatedUnidirectionalStreamId(transport_version(), 1); + session_.OnStreamFrame(QuicStreamFrame(stream_id2, /* fin = */ false, + /* offset = */ 0, frame_type2)); + + EXPECT_EQ(2u, QuicSessionPeer::GetNumOpenDynamicStreams(&session_)); + stream = session_.GetOrCreateStream(stream_id2); + EXPECT_EQ(4u, QuicStreamPeer::bytes_consumed(stream)); + EXPECT_EQ(5u, session_.flow_controller()->bytes_consumed()); + } else { + EXPECT_CALL(*connection_, + CloseConnection(QUIC_HTTP_RECEIVE_SERVER_PUSH, _, _)) + .Times(1); + session_.OnStreamFrame(QuicStreamFrame(stream_id1, /* fin = */ false, + /* offset = */ 0, frame_type1)); + } } TEST_P(QuicSpdySessionTestClient, Http3ServerPushOutofOrderFrame) { @@ -2104,10 +2061,17 @@ TEST_P(QuicSpdySessionTestClient, Http3ServerPushOutofOrderFrame) { session_.OnStreamFrame(data2); EXPECT_EQ(0u, QuicSessionPeer::GetNumOpenDynamicStreams(&session_)); - session_.OnStreamFrame(data1); - EXPECT_EQ(1u, QuicSessionPeer::GetNumOpenDynamicStreams(&session_)); - QuicStream* stream = session_.GetOrCreateStream(stream_id); - EXPECT_EQ(3u, stream->highest_received_byte_offset()); + if (!GetQuicReloadableFlag(quic_decline_server_push_stream)) { + session_.OnStreamFrame(data1); + EXPECT_EQ(1u, QuicSessionPeer::GetNumOpenDynamicStreams(&session_)); + QuicStream* stream = session_.GetOrCreateStream(stream_id); + EXPECT_EQ(3u, stream->highest_received_byte_offset()); + } else { + EXPECT_CALL(*connection_, + CloseConnection(QUIC_HTTP_RECEIVE_SERVER_PUSH, _, _)) + .Times(1); + session_.OnStreamFrame(data1); + } } TEST_P(QuicSpdySessionTestServer, OnStreamFrameLost) { @@ -2921,18 +2885,13 @@ TEST_P(QuicSpdySessionTestClient, CloseConnectionOnCancelPush) { "00"); // push ID QuicStreamFrame data3(receive_control_stream_id, /* fin = */ false, offset, cancel_push_frame); - if (GetQuicReloadableFlag(quic_error_on_http3_push)) { - EXPECT_CALL(*connection_, CloseConnection(QUIC_HTTP_FRAME_ERROR, - "CANCEL_PUSH frame received.", _)) - .WillOnce( - Invoke(connection_, &MockQuicConnection::ReallyCloseConnection)); - EXPECT_CALL(*connection_, - SendConnectionClosePacket(QUIC_HTTP_FRAME_ERROR, _, - "CANCEL_PUSH frame received.")); - } else { - // CANCEL_PUSH is ignored. - EXPECT_CALL(debug_visitor, OnCancelPushFrameReceived(_)); - } + EXPECT_CALL(*connection_, CloseConnection(QUIC_HTTP_FRAME_ERROR, + "CANCEL_PUSH frame received.", _)) + .WillOnce( + Invoke(connection_, &MockQuicConnection::ReallyCloseConnection)); + EXPECT_CALL(*connection_, + SendConnectionClosePacket(QUIC_HTTP_FRAME_ERROR, _, + "CANCEL_PUSH frame received.")); session_.OnStreamFrame(data3); } @@ -3128,18 +3087,13 @@ TEST_P(QuicSpdySessionTestServer, CloseConnectionOnCancelPush) { "00"); // push ID QuicStreamFrame data3(receive_control_stream_id, /* fin = */ false, offset, cancel_push_frame); - if (GetQuicReloadableFlag(quic_error_on_http3_push)) { - EXPECT_CALL(*connection_, CloseConnection(QUIC_HTTP_FRAME_ERROR, - "CANCEL_PUSH frame received.", _)) - .WillOnce( - Invoke(connection_, &MockQuicConnection::ReallyCloseConnection)); - EXPECT_CALL(*connection_, - SendConnectionClosePacket(QUIC_HTTP_FRAME_ERROR, _, - "CANCEL_PUSH frame received.")); - } else { - // CANCEL_PUSH is ignored. - EXPECT_CALL(debug_visitor, OnCancelPushFrameReceived(_)); - } + EXPECT_CALL(*connection_, CloseConnection(QUIC_HTTP_FRAME_ERROR, + "CANCEL_PUSH frame received.", _)) + .WillOnce( + Invoke(connection_, &MockQuicConnection::ReallyCloseConnection)); + EXPECT_CALL(*connection_, + SendConnectionClosePacket(QUIC_HTTP_FRAME_ERROR, _, + "CANCEL_PUSH frame received.")); session_.OnStreamFrame(data3); } @@ -3463,31 +3417,11 @@ TEST_P(QuicSpdySessionTestClient, AlpsTwoSettingsFrame) { EXPECT_EQ("multiple SETTINGS frames", error.value()); } -TEST_P(QuicSpdySessionTestClient, GetNextDatagramFlowId) { - if (!version().UsesHttp3()) { - return; - } - EXPECT_EQ(session_.GetNextDatagramFlowId(), 0u); - EXPECT_EQ(session_.GetNextDatagramFlowId(), 2u); - EXPECT_EQ(session_.GetNextDatagramFlowId(), 4u); - EXPECT_EQ(session_.GetNextDatagramFlowId(), 6u); -} - -TEST_P(QuicSpdySessionTestServer, GetNextDatagramFlowId) { - if (!version().UsesHttp3()) { - return; - } - EXPECT_EQ(session_.GetNextDatagramFlowId(), 1u); - EXPECT_EQ(session_.GetNextDatagramFlowId(), 3u); - EXPECT_EQ(session_.GetNextDatagramFlowId(), 5u); - EXPECT_EQ(session_.GetNextDatagramFlowId(), 7u); -} - TEST_P(QuicSpdySessionTestClient, H3DatagramSetting) { if (!version().UsesHttp3()) { return; } - SetQuicReloadableFlag(quic_h3_datagram, true); + session_.set_should_negotiate_h3_datagram(true); // HTTP/3 datagrams aren't supported before SETTINGS are received. EXPECT_FALSE(session_.h3_datagram_supported()); // Receive SETTINGS. @@ -3506,52 +3440,11 @@ TEST_P(QuicSpdySessionTestClient, H3DatagramSetting) { EXPECT_TRUE(session_.h3_datagram_supported()); } -TEST_P(QuicSpdySessionTestClient, H3DatagramRegistration) { - if (!version().UsesHttp3()) { - return; - } - CompleteHandshake(); - SetQuicReloadableFlag(quic_h3_datagram, true); - QuicSpdySessionPeer::SetH3DatagramSupported(&session_, true); - SavingHttp3DatagramVisitor h3_datagram_visitor; - QuicDatagramFlowId flow_id = session_.GetNextDatagramFlowId(); - ASSERT_EQ(QuicDataWriter::GetVarInt62Len(flow_id), 1); - uint8_t datagram[256]; - datagram[0] = flow_id; - for (size_t i = 1; i < ABSL_ARRAYSIZE(datagram); i++) { - datagram[i] = i; - } - session_.RegisterHttp3FlowId(flow_id, &h3_datagram_visitor); - session_.OnMessageReceived(absl::string_view( - reinterpret_cast<const char*>(datagram), sizeof(datagram))); - EXPECT_THAT( - h3_datagram_visitor.received_h3_datagrams(), - ElementsAre(SavingHttp3DatagramVisitor::SavedHttp3Datagram{ - flow_id, std::string(reinterpret_cast<const char*>(datagram + 1), - sizeof(datagram) - 1)})); - session_.UnregisterHttp3FlowId(flow_id); -} - -TEST_P(QuicSpdySessionTestClient, SendHttp3Datagram) { - if (!version().UsesHttp3()) { - return; - } - CompleteHandshake(); - SetQuicReloadableFlag(quic_h3_datagram, true); - QuicSpdySessionPeer::SetH3DatagramSupported(&session_, true); - QuicDatagramFlowId flow_id = session_.GetNextDatagramFlowId(); - std::string h3_datagram_payload = {1, 2, 3, 4, 5, 6}; - EXPECT_CALL(*connection_, SendMessage(1, _, false)) - .WillOnce(Return(MESSAGE_STATUS_SUCCESS)); - EXPECT_EQ(session_.SendHttp3Datagram(flow_id, h3_datagram_payload), - MESSAGE_STATUS_SUCCESS); -} - TEST_P(QuicSpdySessionTestClient, WebTransportSetting) { if (!version().UsesHttp3()) { return; } - SetQuicReloadableFlag(quic_h3_datagram, true); + session_.set_should_negotiate_h3_datagram(true); session_.set_supports_webtransport(true); EXPECT_FALSE(session_.SupportsWebTransport()); @@ -3581,7 +3474,7 @@ TEST_P(QuicSpdySessionTestClient, WebTransportSettingSetToZero) { if (!version().UsesHttp3()) { return; } - SetQuicReloadableFlag(quic_h3_datagram, true); + session_.set_should_negotiate_h3_datagram(true); session_.set_supports_webtransport(true); EXPECT_FALSE(session_.SupportsWebTransport()); @@ -3611,7 +3504,7 @@ TEST_P(QuicSpdySessionTestServer, WebTransportSetting) { if (!version().UsesHttp3()) { return; } - SetQuicReloadableFlag(quic_h3_datagram, true); + session_.set_should_negotiate_h3_datagram(true); session_.set_supports_webtransport(true); EXPECT_FALSE(session_.SupportsWebTransport()); @@ -3628,7 +3521,7 @@ TEST_P(QuicSpdySessionTestServer, BufferingIncomingStreams) { if (!version().UsesHttp3()) { return; } - SetQuicReloadableFlag(quic_h3_datagram, true); + session_.set_should_negotiate_h3_datagram(true); session_.set_supports_webtransport(true); CompleteHandshake(); @@ -3661,7 +3554,7 @@ TEST_P(QuicSpdySessionTestServer, BufferingIncomingStreamsLimit) { if (!version().UsesHttp3()) { return; } - SetQuicReloadableFlag(quic_h3_datagram, true); + session_.set_should_negotiate_h3_datagram(true); session_.set_supports_webtransport(true); CompleteHandshake(); @@ -3702,7 +3595,7 @@ TEST_P(QuicSpdySessionTestServer, ResetOutgoingWebTransportStreams) { if (!version().UsesHttp3()) { return; } - SetQuicReloadableFlag(quic_h3_datagram, true); + session_.set_should_negotiate_h3_datagram(true); session_.set_supports_webtransport(true); CompleteHandshake(); diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream.cc index 6ec48ca5896..bc23474e72a 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream.cc +++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream.cc @@ -50,11 +50,6 @@ class QuicSpdyStream::HttpDecoderVisitor : public HttpDecoder::Visitor { stream_->OnUnrecoverableError(decoder->error(), decoder->error_detail()); } - bool OnCancelPushFrame(const CancelPushFrame& /*frame*/) override { - CloseConnectionOnWrongFrame("Cancel Push"); - return false; - } - bool OnMaxPushIdFrame(const MaxPushIdFrame& /*frame*/) override { CloseConnectionOnWrongFrame("Max Push Id"); return false; @@ -113,42 +108,6 @@ class QuicSpdyStream::HttpDecoderVisitor : public HttpDecoder::Visitor { return stream_->OnHeadersFrameEnd(); } - bool OnPushPromiseFrameStart(QuicByteCount header_length) override { - if (!VersionUsesHttp3(stream_->transport_version())) { - CloseConnectionOnWrongFrame("Push Promise"); - return false; - } - return stream_->OnPushPromiseFrameStart(header_length); - } - - bool OnPushPromiseFramePushId(PushId push_id, - QuicByteCount push_id_length, - QuicByteCount header_block_length) override { - if (!VersionUsesHttp3(stream_->transport_version())) { - CloseConnectionOnWrongFrame("Push Promise"); - return false; - } - return stream_->OnPushPromiseFramePushId(push_id, push_id_length, - header_block_length); - } - - bool OnPushPromiseFramePayload(absl::string_view payload) override { - QUICHE_DCHECK(!payload.empty()); - if (!VersionUsesHttp3(stream_->transport_version())) { - CloseConnectionOnWrongFrame("Push Promise"); - return false; - } - return stream_->OnPushPromiseFramePayload(payload); - } - - bool OnPushPromiseFrameEnd() override { - if (!VersionUsesHttp3(stream_->transport_version())) { - CloseConnectionOnWrongFrame("Push Promise"); - return false; - } - return stream_->OnPushPromiseFrameEnd(); - } - bool OnPriorityUpdateFrameStart(QuicByteCount /*header_length*/) override { CloseConnectionOnWrongFrame("Priority update"); return false; @@ -213,8 +172,7 @@ HttpDecoder::Options HttpDecoderOptionsForBidiStream( } } // namespace -QuicSpdyStream::QuicSpdyStream(QuicStreamId id, - QuicSpdySession* spdy_session, +QuicSpdyStream::QuicSpdyStream(QuicStreamId id, QuicSpdySession* spdy_session, StreamType type) : QuicStream(id, spdy_session, /*is_static=*/false, type), spdy_session_(spdy_session), @@ -232,7 +190,11 @@ QuicSpdyStream::QuicSpdyStream(QuicStreamId id, sequencer_offset_(0), is_decoder_processing_input_(false), ack_listener_(nullptr), - last_sent_urgency_(kDefaultUrgency) { + last_sent_urgency_(kDefaultUrgency), + datagram_next_available_context_id_(spdy_session->perspective() == + Perspective::IS_SERVER + ? kFirstDatagramContextIdServer + : kFirstDatagramContextIdClient) { QUICHE_DCHECK_EQ(session()->connection(), spdy_session->connection()); QUICHE_DCHECK_EQ(transport_version(), spdy_session->transport_version()); QUICHE_DCHECK(!QuicUtils::IsCryptoStreamId(transport_version(), id)); @@ -342,18 +304,9 @@ void QuicSpdyStream::WriteOrBufferBody(absl::string_view data, bool fin) { spdy_session_->debug_visitor()->OnDataFrameSent(id(), data.length()); } - // Write frame header. - std::unique_ptr<char[]> buffer; - QuicByteCount header_length = - HttpEncoder::SerializeDataFrameHeader(data.length(), &buffer); - unacked_frame_headers_offsets_.Add( - send_buffer().stream_offset(), - send_buffer().stream_offset() + header_length); - QUIC_DLOG(INFO) << ENDPOINT << "Stream " << id() - << " is writing DATA frame header of length " - << header_length; - WriteOrBufferData(absl::string_view(buffer.get(), header_length), false, - nullptr); + const bool success = + WriteDataFrameHeader(data.length(), /*force_write=*/true); + QUICHE_DCHECK(success); // Write body. QUIC_DLOG(INFO) << ENDPOINT << "Stream " << id() @@ -413,49 +366,71 @@ QuicConsumedData QuicSpdyStream::WritevBody(const struct iovec* iov, return WriteBodySlices(storage.ToSpan(), fin); } +bool QuicSpdyStream::WriteDataFrameHeader(QuicByteCount data_length, + bool force_write) { + QUICHE_DCHECK(VersionUsesHttp3(transport_version())); + QUICHE_DCHECK_GT(data_length, 0u); + QuicBuffer header = HttpEncoder::SerializeDataFrameHeader( + data_length, + spdy_session_->connection()->helper()->GetStreamSendBufferAllocator()); + const bool can_write = CanWriteNewDataAfterData(header.size()); + if (!can_write && !force_write) { + return false; + } + + if (spdy_session_->debug_visitor()) { + spdy_session_->debug_visitor()->OnDataFrameSent(id(), data_length); + } + + unacked_frame_headers_offsets_.Add( + send_buffer().stream_offset(), + send_buffer().stream_offset() + header.size()); + QUIC_DLOG(INFO) << ENDPOINT << "Stream " << id() + << " is writing DATA frame header of length " + << header.size(); + if (can_write) { + // Save one copy and allocation if send buffer can accomodate the header. + QuicMemSlice header_slice(std::move(header)); + WriteMemSlices(QuicMemSliceSpan(&header_slice), false); + } else { + QUICHE_DCHECK(force_write); + WriteOrBufferData(header.AsStringView(), false, nullptr); + } + return true; +} + QuicConsumedData QuicSpdyStream::WriteBodySlices(QuicMemSliceSpan slices, bool fin) { if (!VersionUsesHttp3(transport_version()) || slices.empty()) { return WriteMemSlices(slices, fin); } - std::unique_ptr<char[]> buffer; - QuicByteCount header_length = - HttpEncoder::SerializeDataFrameHeader(slices.total_length(), &buffer); - if (!CanWriteNewDataAfterData(header_length)) { + QuicConnection::ScopedPacketFlusher flusher(spdy_session_->connection()); + if (!WriteDataFrameHeader(slices.total_length(), /*force_write=*/false)) { return {0, false}; } - if (spdy_session_->debug_visitor()) { - spdy_session_->debug_visitor()->OnDataFrameSent(id(), - slices.total_length()); + QUIC_DLOG(INFO) << ENDPOINT << "Stream " << id() + << " is writing DATA frame payload of length " + << slices.total_length(); + return WriteMemSlices(slices, fin); +} + +QuicConsumedData QuicSpdyStream::WriteBodySlices( + absl::Span<QuicMemSlice> slices, + bool fin) { + if (!VersionUsesHttp3(transport_version()) || slices.empty()) { + return WriteMemSlices(slices, fin); } QuicConnection::ScopedPacketFlusher flusher(spdy_session_->connection()); + const QuicByteCount data_size = MemSliceSpanTotalSize(slices); + if (!WriteDataFrameHeader(data_size, /*force_write=*/false)) { + return {0, false}; + } - // Write frame header. -#if !defined(__ANDROID__) - struct iovec header_iov = {static_cast<void*>(buffer.get()), header_length}; -#else - struct iovec header_iov = {static_cast<void*>(buffer.get()), - static_cast<__kernel_size_t>(header_length)}; -#endif - QuicMemSliceStorage storage( - &header_iov, 1, - spdy_session_->connection()->helper()->GetStreamSendBufferAllocator(), - GetQuicFlag(FLAGS_quic_send_buffer_max_data_slice_size)); - unacked_frame_headers_offsets_.Add( - send_buffer().stream_offset(), - send_buffer().stream_offset() + header_length); - QUIC_DLOG(INFO) << ENDPOINT << "Stream " << id() - << " is writing DATA frame header of length " - << header_length; - WriteMemSlices(storage.ToSpan(), false); - - // Write body. QUIC_DLOG(INFO) << ENDPOINT << "Stream " << id() - << " is writing DATA frame payload of length " - << slices.total_length(); + << " is writing DATA frame payload of length " << data_size; return WriteMemSlices(slices, fin); } @@ -598,10 +573,6 @@ void QuicSpdyStream::OnHeadersDecoded(QuicHeaderList headers, OnStreamHeaderList(/* fin = */ false, headers_payload_length_, headers); } else { - if (debug_visitor) { - debug_visitor->OnPushPromiseDecoded(id(), promised_stream_id, headers); - } - spdy_session_->OnHeaderList(headers); } @@ -873,6 +844,10 @@ void QuicSpdyStream::OnClose() { visitor->OnClose(this); } + if (datagram_flow_id_.has_value()) { + spdy_session_->UnregisterHttp3DatagramFlowId(datagram_flow_id_.value()); + } + if (web_transport_ != nullptr) { web_transport_->CloseAllAssociatedStreams(); } @@ -1085,50 +1060,6 @@ bool QuicSpdyStream::OnHeadersFrameEnd() { return !sequencer()->IsClosed() && !reading_stopped(); } -bool QuicSpdyStream::OnPushPromiseFrameStart(QuicByteCount header_length) { - QUICHE_DCHECK(VersionUsesHttp3(transport_version())); - QUICHE_DCHECK(!qpack_decoded_headers_accumulator_); - - sequencer()->MarkConsumed(body_manager_.OnNonBody(header_length)); - - return true; -} - -bool QuicSpdyStream::OnPushPromiseFramePushId( - PushId push_id, - QuicByteCount push_id_length, - QuicByteCount header_block_length) { - QUICHE_DCHECK(VersionUsesHttp3(transport_version())); - QUICHE_DCHECK(!qpack_decoded_headers_accumulator_); - - if (spdy_session_->debug_visitor()) { - spdy_session_->debug_visitor()->OnPushPromiseFrameReceived( - id(), push_id, header_block_length); - } - - // TODO(b/151749109): Check max push id and handle errors. - spdy_session_->OnPushPromise(id(), push_id); - sequencer()->MarkConsumed(body_manager_.OnNonBody(push_id_length)); - - qpack_decoded_headers_accumulator_ = - std::make_unique<QpackDecodedHeadersAccumulator>( - id(), spdy_session_->qpack_decoder(), this, - spdy_session_->max_inbound_header_list_size()); - - return true; -} - -bool QuicSpdyStream::OnPushPromiseFramePayload(absl::string_view payload) { - spdy_session_->OnCompressedFrameSize(payload.length()); - return OnHeadersFramePayload(payload); -} - -bool QuicSpdyStream::OnPushPromiseFrameEnd() { - QUICHE_DCHECK(VersionUsesHttp3(transport_version())); - - return OnHeadersFrameEnd(); -} - void QuicSpdyStream::OnWebTransportStreamFrameType( QuicByteCount header_length, WebTransportSessionId session_id) { @@ -1237,6 +1168,16 @@ size_t QuicSpdyStream::WriteHeadersImpl( return encoded_headers.size(); } +bool QuicSpdyStream::CanWriteNewBodyData(QuicByteCount write_size) const { + QUICHE_DCHECK_NE(0u, write_size); + if (!VersionUsesHttp3(transport_version())) { + return CanWriteNewData(); + } + + return CanWriteNewDataAfterData( + HttpEncoder::GetDataFrameHeaderLength(write_size)); +} + void QuicSpdyStream::MaybeProcessReceivedWebTransportHeaders() { if (!spdy_session_->SupportsWebTransport()) { return; @@ -1248,7 +1189,7 @@ void QuicSpdyStream::MaybeProcessReceivedWebTransportHeaders() { std::string method; std::string protocol; - absl::optional<QuicDatagramFlowId> flow_id; + absl::optional<QuicDatagramStreamId> flow_id; for (const auto& header : header_list_) { const std::string& header_name = header.first; const std::string& header_value = header.second; @@ -1268,7 +1209,7 @@ void QuicSpdyStream::MaybeProcessReceivedWebTransportHeaders() { if (flow_id.has_value() || header_value.empty()) { return; } - QuicDatagramFlowId flow_id_out; + QuicDatagramStreamId flow_id_out; if (!absl::SimpleAtoi(header_value, &flow_id_out)) { return; } @@ -1281,8 +1222,18 @@ void QuicSpdyStream::MaybeProcessReceivedWebTransportHeaders() { return; } + RegisterHttp3DatagramFlowId(*flow_id); + web_transport_ = - std::make_unique<WebTransportHttp3>(spdy_session_, this, id(), *flow_id); + std::make_unique<WebTransportHttp3>(spdy_session_, this, id()); + + // If we're in draft-ietf-masque-h3-datagram-00 mode, pretend we also received + // a REGISTER_DATAGRAM_NO_CONTEXT capsule with no extensions. + // TODO(b/181256914) remove this when we remove support for + // draft-ietf-masque-h3-datagram-00 in favor of later drafts. + RegisterHttp3DatagramContextId(/*context_id=*/absl::nullopt, + Http3DatagramContextExtensions(), + web_transport_.get()); } void QuicSpdyStream::MaybeProcessSentWebTransportHeaders( @@ -1304,11 +1255,14 @@ void QuicSpdyStream::MaybeProcessSentWebTransportHeaders( return; } - QuicDatagramFlowId flow_id = spdy_session_->GetNextDatagramFlowId(); - headers["datagram-flow-id"] = absl::StrCat(flow_id); + QuicDatagramStreamId stream_id = id(); + headers["datagram-flow-id"] = absl::StrCat(stream_id); web_transport_ = - std::make_unique<WebTransportHttp3>(spdy_session_, this, id(), flow_id); + std::make_unique<WebTransportHttp3>(spdy_session_, this, id()); + RegisterHttp3DatagramContextId(web_transport_->context_id(), + Http3DatagramContextExtensions(), + web_transport_.get()); } void QuicSpdyStream::OnCanWriteNewData() { @@ -1369,5 +1323,206 @@ QuicSpdyStream::WebTransportDataStream::WebTransportDataStream( : session_id(session_id), adapter(stream->spdy_session_, stream, stream->sequencer()) {} +MessageStatus QuicSpdyStream::SendHttp3Datagram( + absl::optional<QuicDatagramContextId> context_id, + absl::string_view payload) { + QuicDatagramStreamId stream_id = + datagram_flow_id_.has_value() ? datagram_flow_id_.value() : id(); + return spdy_session_->SendHttp3Datagram(stream_id, context_id, payload); +} + +void QuicSpdyStream::RegisterHttp3DatagramRegistrationVisitor( + Http3DatagramRegistrationVisitor* visitor) { + if (visitor == nullptr) { + QUIC_BUG(null datagram registration visitor) + << ENDPOINT << "Null datagram registration visitor for" << id(); + return; + } + QUIC_DLOG(INFO) << ENDPOINT << "Registering datagram stream ID " << id(); + datagram_registration_visitor_ = visitor; +} + +void QuicSpdyStream::UnregisterHttp3DatagramRegistrationVisitor() { + QUIC_BUG_IF(h3 datagram unregister unknown stream ID, + datagram_registration_visitor_ == nullptr) + << ENDPOINT + << "Attempted to unregister unknown HTTP/3 datagram stream ID " << id(); + QUIC_DLOG(INFO) << ENDPOINT << "Unregistering datagram stream ID " << id(); + datagram_registration_visitor_ = nullptr; +} + +void QuicSpdyStream::MoveHttp3DatagramRegistration( + Http3DatagramRegistrationVisitor* visitor) { + QUIC_BUG_IF(h3 datagram move unknown stream ID, + datagram_registration_visitor_ == nullptr) + << ENDPOINT << "Attempted to move unknown HTTP/3 datagram stream ID " + << id(); + QUIC_DLOG(INFO) << ENDPOINT << "Moving datagram stream ID " << id(); + datagram_registration_visitor_ = visitor; +} + +void QuicSpdyStream::RegisterHttp3DatagramContextId( + absl::optional<QuicDatagramContextId> context_id, + const Http3DatagramContextExtensions& /*extensions*/, + Http3DatagramVisitor* visitor) { + if (visitor == nullptr) { + QUIC_BUG(null datagram visitor) + << ENDPOINT << "Null datagram visitor for stream ID " << id() + << " context ID " << (context_id.has_value() ? context_id.value() : 0); + return; + } + if (datagram_registration_visitor_ == nullptr) { + QUIC_BUG(context registration without registration visitor) + << ENDPOINT << "Cannot register context ID " + << (context_id.has_value() ? context_id.value() : 0) + << " without registration visitor for stream ID " << id(); + return; + } + QUIC_DLOG(INFO) << ENDPOINT << "Registering datagram context ID " + << (context_id.has_value() ? context_id.value() : 0) + << " with stream ID " << id(); + if (context_id.has_value()) { + if (datagram_no_context_visitor_ != nullptr) { + QUIC_BUG(h3 datagram context ID mix1) + << ENDPOINT + << "Attempted to mix registrations without and with context IDs " + "for stream ID " + << id(); + return; + } + auto insertion_result = + datagram_context_visitors_.insert({context_id.value(), visitor}); + QUIC_BUG_IF(h3 datagram double context registration, + !insertion_result.second) + << ENDPOINT << "Attempted to doubly register HTTP/3 stream ID " << id() + << " context ID " << context_id.value(); + return; + } + // Registration without a context ID. + if (!datagram_context_visitors_.empty()) { + QUIC_BUG(h3 datagram context ID mix2) + << ENDPOINT + << "Attempted to mix registrations with and without context IDs " + "for stream ID " + << id(); + return; + } + if (datagram_no_context_visitor_ != nullptr) { + QUIC_BUG(h3 datagram double no context registration) + << ENDPOINT << "Attempted to doubly register HTTP/3 stream ID " << id() + << " with no context ID"; + return; + } + datagram_no_context_visitor_ = visitor; +} + +void QuicSpdyStream::UnregisterHttp3DatagramContextId( + absl::optional<QuicDatagramContextId> context_id) { + if (datagram_registration_visitor_ == nullptr) { + QUIC_BUG(context unregistration without registration visitor) + << ENDPOINT << "Cannot unregister context ID " + << (context_id.has_value() ? context_id.value() : 0) + << " without registration visitor for stream ID " << id(); + return; + } + QUIC_DLOG(INFO) << ENDPOINT << "Unregistering datagram context ID " + << (context_id.has_value() ? context_id.value() : 0) + << " with stream ID " << id(); + if (context_id.has_value()) { + size_t num_erased = datagram_context_visitors_.erase(context_id.value()); + QUIC_BUG_IF(h3 datagram unregister unknown context, num_erased != 1) + << "Attempted to unregister unknown HTTP/3 context ID " + << context_id.value() << " on stream ID " << id(); + return; + } + // Unregistration without a context ID. + QUIC_BUG_IF(h3 datagram unknown context unregistration, + datagram_no_context_visitor_ == nullptr) + << "Attempted to unregister unknown no context on HTTP/3 stream ID " + << id(); + datagram_no_context_visitor_ = nullptr; +} + +void QuicSpdyStream::MoveHttp3DatagramContextIdRegistration( + absl::optional<QuicDatagramContextId> context_id, + Http3DatagramVisitor* visitor) { + if (datagram_registration_visitor_ == nullptr) { + QUIC_BUG(context move without registration visitor) + << ENDPOINT << "Cannot move context ID " + << (context_id.has_value() ? context_id.value() : 0) + << " without registration visitor for stream ID " << id(); + return; + } + QUIC_DLOG(INFO) << ENDPOINT << "Moving datagram context ID " + << (context_id.has_value() ? context_id.value() : 0) + << " with stream ID " << id(); + if (context_id.has_value()) { + QUIC_BUG_IF(h3 datagram move unknown context, + !datagram_context_visitors_.contains(context_id.value())) + << ENDPOINT << "Attempted to move unknown context ID " + << context_id.value() << " on stream ID " << id(); + datagram_context_visitors_[context_id.value()] = visitor; + return; + } + // Move without a context ID. + QUIC_BUG_IF(h3 datagram unknown context move, + datagram_no_context_visitor_ == nullptr) + << "Attempted to move unknown no context on HTTP/3 stream ID " << id(); + datagram_no_context_visitor_ = visitor; +} + +void QuicSpdyStream::SetMaxDatagramTimeInQueue( + QuicTime::Delta max_time_in_queue) { + spdy_session_->SetMaxDatagramTimeInQueueForStreamId(id(), max_time_in_queue); +} + +QuicDatagramContextId QuicSpdyStream::GetNextDatagramContextId() { + QuicDatagramContextId result = datagram_next_available_context_id_; + datagram_next_available_context_id_ += kDatagramContextIdIncrement; + return result; +} + +void QuicSpdyStream::OnDatagramReceived(QuicDataReader* reader) { + absl::optional<QuicDatagramContextId> context_id; + const bool context_id_present = !datagram_context_visitors_.empty(); + Http3DatagramVisitor* visitor; + if (context_id_present) { + QuicDatagramContextId parsed_context_id; + if (!reader->ReadVarInt62(&parsed_context_id)) { + QUIC_DLOG(ERROR) << "Failed to parse context ID in received HTTP/3 " + "datagram on stream ID " + << id(); + return; + } + context_id = parsed_context_id; + auto it = datagram_context_visitors_.find(parsed_context_id); + if (it == datagram_context_visitors_.end()) { + // TODO(b/181256914) buffer unknown HTTP/3 datagrams for a short + // period of time in case they were reordered. + QUIC_DLOG(ERROR) << "Received unknown HTTP/3 datagram context ID " + << parsed_context_id << " on stream ID " << id(); + return; + } + visitor = it->second; + } else { + if (datagram_no_context_visitor_ == nullptr) { + // TODO(b/181256914) buffer unknown HTTP/3 datagrams for a short + // period of time in case they were reordered. + QUIC_DLOG(ERROR) + << "Received HTTP/3 datagram without any registrations on stream ID " + << id(); + return; + } + visitor = datagram_no_context_visitor_; + } + absl::string_view payload = reader->ReadRemainingPayload(); + visitor->OnHttp3Datagram(id(), context_id, payload); +} + +void QuicSpdyStream::RegisterHttp3DatagramFlowId(QuicDatagramStreamId flow_id) { + datagram_flow_id_ = flow_id; + spdy_session_->RegisterHttp3DatagramFlowId(datagram_flow_id_.value(), id()); +} + #undef ENDPOINT // undef for jumbo builds } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream.h b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream.h index 5f96d8395cf..4df1196abeb 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream.h +++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream.h @@ -16,7 +16,9 @@ #include <memory> #include <string> +#include "absl/base/attributes.h" #include "absl/strings/string_view.h" +#include "absl/types/span.h" #include "quic/core/http/http_decoder.h" #include "quic/core/http/http_encoder.h" #include "quic/core/http/quic_header_list.h" @@ -44,6 +46,8 @@ class QuicStreamPeer; class QuicSpdySession; class WebTransportHttp3; +class QUIC_EXPORT_PRIVATE Http3DatagramContextExtensions {}; + // A QUIC stream that can send and receive HTTP2 (SPDY) headers. class QUIC_EXPORT_PRIVATE QuicSpdyStream : public QuicStream, @@ -158,6 +162,7 @@ class QUIC_EXPORT_PRIVATE QuicSpdyStream // Does the same thing as WriteOrBufferBody except this method takes // memslicespan as the data input. Right now it only calls WriteMemSlices. QuicConsumedData WriteBodySlices(QuicMemSliceSpan slices, bool fin); + QuicConsumedData WriteBodySlices(absl::Span<QuicMemSlice> slices, bool fin); // Marks the trailers as consumed. This applies to the case where this object // receives headers and trailers as QuicHeaderLists via calls to @@ -244,6 +249,98 @@ class QUIC_EXPORT_PRIVATE QuicSpdyStream // error, and returns false. bool AssertNotWebTransportDataStream(absl::string_view operation); + // Indicates whether a call to WriteBodySlices will be successful and not + // rejected due to buffer being full. |write_size| must be non-zero. + bool CanWriteNewBodyData(QuicByteCount write_size) const; + + // Sends an HTTP/3 datagram. The stream and context IDs are not part of + // |payload|. + MessageStatus SendHttp3Datagram( + absl::optional<QuicDatagramContextId> context_id, + absl::string_view payload); + + class QUIC_EXPORT_PRIVATE Http3DatagramVisitor { + public: + virtual ~Http3DatagramVisitor() {} + + // Called when an HTTP/3 datagram is received. |payload| does not contain + // the stream or context IDs. Note that this contains the stream ID even if + // flow IDs from draft-ietf-masque-h3-datagram-00 are in use. + virtual void OnHttp3Datagram( + QuicStreamId stream_id, + absl::optional<QuicDatagramContextId> context_id, + absl::string_view payload) = 0; + }; + + class QUIC_EXPORT_PRIVATE Http3DatagramRegistrationVisitor { + public: + virtual ~Http3DatagramRegistrationVisitor() {} + + // Called when a REGISTER_DATAGRAM_CONTEXT or REGISTER_DATAGRAM_NO_CONTEXT + // capsule is received. Note that this contains the stream ID even if flow + // IDs from draft-ietf-masque-h3-datagram-00 are in use. + virtual void OnContextReceived( + QuicStreamId stream_id, + absl::optional<QuicDatagramContextId> context_id, + const Http3DatagramContextExtensions& extensions) = 0; + + // Called when a CLOSE_DATAGRAM_CONTEXT capsule is received. Note that this + // contains the stream ID even if flow IDs from + // draft-ietf-masque-h3-datagram-00 are in use. + virtual void OnContextClosed( + QuicStreamId stream_id, + absl::optional<QuicDatagramContextId> context_id, + const Http3DatagramContextExtensions& extensions) = 0; + }; + + // Registers |visitor| to receive HTTP/3 datagram context registrations. This + // must not be called without first calling + // UnregisterHttp3DatagramRegistrationVisitor. |visitor| must be valid until a + // corresponding call to UnregisterHttp3DatagramRegistrationVisitor. + void RegisterHttp3DatagramRegistrationVisitor( + Http3DatagramRegistrationVisitor* visitor); + + // Unregisters for HTTP/3 datagram context registrations. Must not be called + // unless previously registered. + void UnregisterHttp3DatagramRegistrationVisitor(); + + // Moves an HTTP/3 datagram registration to a different visitor. Mainly meant + // to be used by the visitors' move operators. + void MoveHttp3DatagramRegistration(Http3DatagramRegistrationVisitor* visitor); + + // Registers |visitor| to receive HTTP/3 datagrams for optional context ID + // |context_id|. This must not be called on a previously registered context ID + // without first calling UnregisterHttp3DatagramContextId. |visitor| must be + // valid until a corresponding call to UnregisterHttp3DatagramContextId. If + // this method is called multiple times, the context ID MUST either be always + // present, or always absent. + void RegisterHttp3DatagramContextId( + absl::optional<QuicDatagramContextId> context_id, + const Http3DatagramContextExtensions& extensions, + Http3DatagramVisitor* visitor); + + // Unregisters an HTTP/3 datagram context ID. Must be called on a previously + // registered context. + void UnregisterHttp3DatagramContextId( + absl::optional<QuicDatagramContextId> context_id); + + // Moves an HTTP/3 datagram context ID to a different visitor. Mainly meant + // to be used by the visitors' move operators. + void MoveHttp3DatagramContextIdRegistration( + absl::optional<QuicDatagramContextId> context_id, + Http3DatagramVisitor* visitor); + + // Sets max datagram time in queue. + void SetMaxDatagramTimeInQueue(QuicTime::Delta max_time_in_queue); + + // Generates a new HTTP/3 datagram context ID for this stream. A datagram + // registration visitor must be currently registered on this stream. + QuicDatagramContextId GetNextDatagramContextId(); + + void OnDatagramReceived(QuicDataReader* reader); + + void RegisterHttp3DatagramFlowId(QuicDatagramStreamId flow_id); + protected: // Called when the received headers are too large. By default this will // reset the stream. @@ -292,12 +389,6 @@ class QUIC_EXPORT_PRIVATE QuicSpdyStream QuicByteCount payload_length); bool OnHeadersFramePayload(absl::string_view payload); bool OnHeadersFrameEnd(); - bool OnPushPromiseFrameStart(QuicByteCount header_length); - bool OnPushPromiseFramePushId(PushId push_id, - QuicByteCount push_id_length, - QuicByteCount header_block_length); - bool OnPushPromiseFramePayload(absl::string_view payload); - bool OnPushPromiseFrameEnd(); void OnWebTransportStreamFrameType(QuicByteCount header_length, WebTransportSessionId session_id); bool OnUnknownFrameStart(uint64_t frame_type, @@ -314,6 +405,11 @@ class QUIC_EXPORT_PRIVATE QuicSpdyStream void MaybeProcessSentWebTransportHeaders(spdy::SpdyHeaderBlock& headers); void MaybeProcessReceivedWebTransportHeaders(); + // Writes HTTP/3 DATA frame header. If |force_write| is true, use + // WriteOrBufferData if send buffer cannot accomodate the header + data. + ABSL_MUST_USE_RESULT bool WriteDataFrameHeader(QuicByteCount data_length, + bool force_write); + QuicSpdySession* spdy_session_; bool on_body_available_called_because_sequencer_is_closed_; @@ -382,6 +478,14 @@ class QUIC_EXPORT_PRIVATE QuicSpdyStream // If this stream is a WebTransport data stream, |web_transport_data_| // contains all of the associated metadata. std::unique_ptr<WebTransportDataStream> web_transport_data_; + + // HTTP/3 Datagram support. + Http3DatagramRegistrationVisitor* datagram_registration_visitor_ = nullptr; + Http3DatagramVisitor* datagram_no_context_visitor_ = nullptr; + absl::optional<QuicDatagramStreamId> datagram_flow_id_; + QuicDatagramContextId datagram_next_available_context_id_; + absl::flat_hash_map<QuicDatagramContextId, Http3DatagramVisitor*> + datagram_context_visitors_; }; } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream_test.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream_test.cc index 8b814b8400b..c19dc3c8431 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_spdy_stream_test.cc @@ -19,12 +19,12 @@ #include "quic/core/http/spdy_utils.h" #include "quic/core/http/web_transport_http3.h" #include "quic/core/quic_connection.h" +#include "quic/core/quic_simple_buffer_allocator.h" #include "quic/core/quic_stream_sequencer_buffer.h" #include "quic/core/quic_utils.h" #include "quic/core/quic_versions.h" #include "quic/core/quic_write_blocked_list.h" #include "quic/platform/api/quic_expect_bug.h" -#include "quic/platform/api/quic_map_util.h" #include "quic/platform/api/quic_test.h" #include "quic/test_tools/qpack/qpack_test_utils.h" #include "quic/test_tools/quic_config_peer.h" @@ -259,8 +259,16 @@ class TestSession : public MockQuicSpdySession { bool ShouldNegotiateWebTransport() override { return enable_webtransport_; } void EnableWebTransport() { enable_webtransport_ = true; } + bool ShouldNegotiateHttp3Datagram() override { + return should_negotiate_h3_datagram_; + } + void set_should_negotiate_h3_datagram(bool value) { + should_negotiate_h3_datagram_ = value; + } + private: bool enable_webtransport_ = false; + bool should_negotiate_h3_datagram_ = false; StrictMock<TestCryptoStream> crypto_stream_; }; @@ -447,40 +455,10 @@ class QuicSpdyStreamTest : public QuicTestWithParam<ParsedQuicVersion> { return absl::StrCat(headers_frame_header, payload); } - // Construct PUSH_PROMISE frame with given payload. - // TODO(b/171463363): Remove. - std::string SerializePushPromiseFrame(PushId push_id, - absl::string_view headers) { - const QuicByteCount payload_length = - QuicDataWriter::GetVarInt62Len(push_id) + headers.length(); - - const QuicByteCount length_without_headers = - QuicDataWriter::GetVarInt62Len( - static_cast<uint64_t>(HttpFrameType::PUSH_PROMISE)) + - QuicDataWriter::GetVarInt62Len(payload_length) + - QuicDataWriter::GetVarInt62Len(push_id); - - std::string push_promise_frame(length_without_headers, '\0'); - QuicDataWriter writer(length_without_headers, &*push_promise_frame.begin()); - - QUICHE_CHECK(writer.WriteVarInt62( - static_cast<uint64_t>(HttpFrameType::PUSH_PROMISE))); - QUICHE_CHECK(writer.WriteVarInt62(payload_length)); - QUICHE_CHECK(writer.WriteVarInt62(push_id)); - QUICHE_CHECK_EQ(0u, writer.remaining()); - - absl::StrAppend(&push_promise_frame, headers); - - return push_promise_frame; - } - std::string DataFrame(absl::string_view payload) { - std::unique_ptr<char[]> data_buffer; - QuicByteCount data_frame_header_length = - HttpEncoder::SerializeDataFrameHeader(payload.length(), &data_buffer); - absl::string_view data_frame_header(data_buffer.get(), - data_frame_header_length); - return absl::StrCat(data_frame_header, payload); + QuicBuffer header = HttpEncoder::SerializeDataFrameHeader( + payload.length(), SimpleBufferAllocator::Get()); + return absl::StrCat(header.AsStringView(), payload); } std::string UnknownFrame(uint64_t frame_type, absl::string_view payload) { @@ -1005,11 +983,10 @@ TEST_P(QuicSpdyStreamTest, StreamFlowControlNoWindowUpdateIfNotConsumed) { std::string data; if (UsesHttp3()) { - std::unique_ptr<char[]> buffer; - header_length = - HttpEncoder::SerializeDataFrameHeader(body.length(), &buffer); - std::string header = std::string(buffer.get(), header_length); - data = header + body; + QuicBuffer header = HttpEncoder::SerializeDataFrameHeader( + body.length(), SimpleBufferAllocator::Get()); + data = absl::StrCat(header.AsStringView(), body); + header_length = header.size(); } else { data = body; } @@ -1049,11 +1026,10 @@ TEST_P(QuicSpdyStreamTest, StreamFlowControlWindowUpdate) { std::string data; if (UsesHttp3()) { - std::unique_ptr<char[]> buffer; - header_length = - HttpEncoder::SerializeDataFrameHeader(body.length(), &buffer); - std::string header = std::string(buffer.get(), header_length); - data = header + body; + QuicBuffer header = HttpEncoder::SerializeDataFrameHeader( + body.length(), SimpleBufferAllocator::Get()); + data = absl::StrCat(header.AsStringView(), body); + header_length = header.size(); } else { data = body; } @@ -1115,16 +1091,13 @@ TEST_P(QuicSpdyStreamTest, ConnectionFlowControlWindowUpdate) { if (UsesHttp3()) { body = std::string(kWindow / 4 - 2, 'a'); - std::unique_ptr<char[]> buffer; - header_length = - HttpEncoder::SerializeDataFrameHeader(body.length(), &buffer); - std::string header = std::string(buffer.get(), header_length); - data = header + body; - std::unique_ptr<char[]> buffer2; - QuicByteCount header_length2 = - HttpEncoder::SerializeDataFrameHeader(body2.length(), &buffer2); - std::string header2 = std::string(buffer2.get(), header_length2); - data2 = header2 + body2; + QuicBuffer header = HttpEncoder::SerializeDataFrameHeader( + body.length(), SimpleBufferAllocator::Get()); + data = absl::StrCat(header.AsStringView(), body); + header_length = header.size(); + QuicBuffer header2 = HttpEncoder::SerializeDataFrameHeader( + body.length(), SimpleBufferAllocator::Get()); + data2 = absl::StrCat(header2.AsStringView(), body2); } else { body = std::string(kWindow / 4, 'a'); data = body; @@ -1595,8 +1568,9 @@ TEST_P(QuicSpdyStreamTest, WritingTrailersFinalOffset) { std::string body(1024, 'x'); // 1 kB QuicByteCount header_length = 0; if (UsesHttp3()) { - std::unique_ptr<char[]> buf; - header_length = HttpEncoder::SerializeDataFrameHeader(body.length(), &buf); + header_length = HttpEncoder::SerializeDataFrameHeader( + body.length(), SimpleBufferAllocator::Get()) + .size(); } stream_->WriteOrBufferBody(body, false); @@ -1939,30 +1913,26 @@ TEST_P(QuicSpdyStreamTest, HeadersAckNotReportedWriteOrBufferBody) { stream_->WriteOrBufferBody(body, false); stream_->WriteOrBufferBody(body2, true); - std::unique_ptr<char[]> buffer; - QuicByteCount header_length = - HttpEncoder::SerializeDataFrameHeader(body.length(), &buffer); - std::string header = std::string(buffer.get(), header_length); - - header_length = - HttpEncoder::SerializeDataFrameHeader(body2.length(), &buffer); - std::string header2 = std::string(buffer.get(), header_length); + QuicBuffer header = HttpEncoder::SerializeDataFrameHeader( + body.length(), SimpleBufferAllocator::Get()); + QuicBuffer header2 = HttpEncoder::SerializeDataFrameHeader( + body2.length(), SimpleBufferAllocator::Get()); EXPECT_CALL(*mock_ack_listener, OnPacketAcked(body.length(), _)); - QuicStreamFrame frame(stream_->id(), false, 0, header + body); + QuicStreamFrame frame(stream_->id(), false, 0, + absl::StrCat(header.AsStringView(), body)); EXPECT_TRUE(session_->OnFrameAcked(QuicFrame(frame), QuicTime::Delta::Zero(), QuicTime::Zero())); EXPECT_CALL(*mock_ack_listener, OnPacketAcked(0, _)); - QuicStreamFrame frame2(stream_->id(), false, header.length() + body.length(), - header2); + QuicStreamFrame frame2(stream_->id(), false, header.size() + body.length(), + header2.AsStringView()); EXPECT_TRUE(session_->OnFrameAcked(QuicFrame(frame2), QuicTime::Delta::Zero(), QuicTime::Zero())); EXPECT_CALL(*mock_ack_listener, OnPacketAcked(body2.length(), _)); QuicStreamFrame frame3(stream_->id(), true, - header.length() + body.length() + header2.length(), - body2); + header.size() + body.length() + header2.size(), body2); EXPECT_TRUE(session_->OnFrameAcked(QuicFrame(frame3), QuicTime::Delta::Zero(), QuicTime::Zero())); @@ -2769,71 +2739,6 @@ TEST_P(QuicSpdyStreamIncrementalConsumptionTest, UnknownFramesInterleaved) { EXPECT_EQ(unknown_frame4.size(), NewlyConsumedBytes()); } -// TODO(b/171463363): Remove. -TEST_P(QuicSpdyStreamTest, PushPromiseOnDataStream) { - Initialize(kShouldProcessData); - if (GetQuicReloadableFlag(quic_error_on_http3_push)) { - return; - } - if (!UsesHttp3()) { - return; - } - - StrictMock<MockHttp3DebugVisitor> debug_visitor; - session_->set_debug_visitor(&debug_visitor); - - SpdyHeaderBlock pushed_headers; - pushed_headers["foo"] = "bar"; - std::string headers = EncodeQpackHeaders(pushed_headers); - - const QuicStreamId push_id = 1; - std::string data = SerializePushPromiseFrame(push_id, headers); - QuicStreamFrame frame(stream_->id(), false, 0, data); - - EXPECT_CALL(debug_visitor, OnPushPromiseFrameReceived(stream_->id(), push_id, - headers.length())); - EXPECT_CALL(debug_visitor, - OnPushPromiseDecoded(stream_->id(), push_id, - AsHeaderList(pushed_headers))); - EXPECT_CALL(*session_, - OnPromiseHeaderList(stream_->id(), push_id, headers.length(), _)); - stream_->OnStreamFrame(frame); -} - -// Regression test for b/152518220. -// TODO(b/171463363): Remove. -TEST_P(QuicSpdyStreamTest, - OnStreamHeaderBlockArgumentDoesNotIncludePushedHeaderBlock) { - Initialize(kShouldProcessData); - if (GetQuicReloadableFlag(quic_error_on_http3_push)) { - return; - } - if (!UsesHttp3()) { - return; - } - - std::string pushed_headers = EncodeQpackHeaders({{"foo", "bar"}}); - const QuicStreamId push_id = 1; - std::string push_promise_frame = - SerializePushPromiseFrame(push_id, pushed_headers); - QuicStreamOffset offset = 0; - QuicStreamFrame frame1(stream_->id(), /* fin = */ false, offset, - push_promise_frame); - offset += push_promise_frame.length(); - - EXPECT_CALL(*session_, OnPromiseHeaderList(stream_->id(), push_id, - pushed_headers.length(), _)); - stream_->OnStreamFrame(frame1); - - std::string headers = - EncodeQpackHeaders({{":method", "GET"}, {":path", "/"}}); - std::string headers_frame = HeadersFrame(headers); - QuicStreamFrame frame2(stream_->id(), /* fin = */ false, offset, - headers_frame); - stream_->OnStreamFrame(frame2); - EXPECT_EQ(headers.length(), stream_->headers_payload_length()); -} - // Close connection if a DATA frame is received before a HEADERS frame. TEST_P(QuicSpdyStreamTest, DataBeforeHeaders) { if (!UsesHttp3()) { @@ -3118,6 +3023,7 @@ TEST_P(QuicSpdyStreamTest, ProcessOutgoingWebTransportHeaders) { } InitializeWithPerspective(kShouldProcessData, Perspective::IS_CLIENT); + session_->set_should_negotiate_h3_datagram(true); session_->EnableWebTransport(); QuicSpdySessionPeer::EnableWebTransport(*session_); @@ -3128,7 +3034,7 @@ TEST_P(QuicSpdyStreamTest, ProcessOutgoingWebTransportHeaders) { spdy::SpdyHeaderBlock headers; headers[":method"] = "CONNECT"; headers[":protocol"] = "webtransport"; - headers["datagram-flow-id"] = absl::StrCat(session_->GetNextDatagramFlowId()); + headers["datagram-flow-id"] = absl::StrCat(stream_->id()); stream_->WriteHeaders(std::move(headers), /*fin=*/false, nullptr); ASSERT_TRUE(stream_->web_transport() != nullptr); EXPECT_EQ(stream_->id(), stream_->web_transport()->id()); @@ -3140,13 +3046,13 @@ TEST_P(QuicSpdyStreamTest, ProcessIncomingWebTransportHeaders) { } Initialize(kShouldProcessData); + session_->set_should_negotiate_h3_datagram(true); session_->EnableWebTransport(); QuicSpdySessionPeer::EnableWebTransport(*session_); headers_[":method"] = "CONNECT"; headers_[":protocol"] = "webtransport"; - headers_["datagram-flow-id"] = - absl::StrCat(session_->GetNextDatagramFlowId()); + headers_["datagram-flow-id"] = absl::StrCat(stream_->id()); stream_->OnStreamHeadersPriority( spdy::SpdyStreamPrecedence(kV3HighestPriority)); @@ -3158,6 +3064,155 @@ TEST_P(QuicSpdyStreamTest, ProcessIncomingWebTransportHeaders) { EXPECT_EQ(stream_->id(), stream_->web_transport()->id()); } +TEST_P(QuicSpdyStreamTest, + ProcessIncomingWebTransportHeadersWithMismatchedFlowId) { + if (!UsesHttp3()) { + return; + } + // TODO(b/181256914) Remove this test when we deprecate + // draft-ietf-masque-h3-datagram-00 in favor of later drafts. + + Initialize(kShouldProcessData); + session_->set_should_negotiate_h3_datagram(true); + session_->EnableWebTransport(); + QuicSpdySessionPeer::EnableWebTransport(*session_); + + headers_[":method"] = "CONNECT"; + headers_[":protocol"] = "webtransport"; + headers_["datagram-flow-id"] = "2"; + + stream_->OnStreamHeadersPriority( + spdy::SpdyStreamPrecedence(kV3HighestPriority)); + ProcessHeaders(false, headers_); + EXPECT_EQ("", stream_->data()); + EXPECT_FALSE(stream_->header_list().empty()); + EXPECT_FALSE(stream_->IsDoneReading()); + ASSERT_TRUE(stream_->web_transport() != nullptr); + EXPECT_EQ(stream_->id(), stream_->web_transport()->id()); +} + +TEST_P(QuicSpdyStreamTest, GetNextDatagramContextIdClient) { + if (!UsesHttp3()) { + return; + } + InitializeWithPerspective(kShouldProcessData, Perspective::IS_CLIENT); + ::testing::NiceMock<MockHttp3DatagramRegistrationVisitor> visitor; + stream_->RegisterHttp3DatagramRegistrationVisitor(&visitor); + EXPECT_EQ(stream_->GetNextDatagramContextId(), 0u); + EXPECT_EQ(stream_->GetNextDatagramContextId(), 2u); + EXPECT_EQ(stream_->GetNextDatagramContextId(), 4u); + EXPECT_EQ(stream_->GetNextDatagramContextId(), 6u); + stream_->UnregisterHttp3DatagramRegistrationVisitor(); +} + +TEST_P(QuicSpdyStreamTest, GetNextDatagramContextIdServer) { + if (!UsesHttp3()) { + return; + } + InitializeWithPerspective(kShouldProcessData, Perspective::IS_SERVER); + ::testing::NiceMock<MockHttp3DatagramRegistrationVisitor> visitor; + stream_->RegisterHttp3DatagramRegistrationVisitor(&visitor); + EXPECT_EQ(stream_->GetNextDatagramContextId(), 1u); + EXPECT_EQ(stream_->GetNextDatagramContextId(), 3u); + EXPECT_EQ(stream_->GetNextDatagramContextId(), 5u); + EXPECT_EQ(stream_->GetNextDatagramContextId(), 7u); + stream_->UnregisterHttp3DatagramRegistrationVisitor(); +} + +TEST_P(QuicSpdyStreamTest, H3DatagramRegistrationWithoutContext) { + if (!UsesHttp3()) { + return; + } + Initialize(kShouldProcessData); + session_->set_should_negotiate_h3_datagram(true); + QuicSpdySessionPeer::SetH3DatagramSupported(session_.get(), true); + session_->RegisterHttp3DatagramFlowId(stream_->id(), stream_->id()); + ::testing::NiceMock<MockHttp3DatagramRegistrationVisitor> + h3_datagram_registration_visitor; + SavingHttp3DatagramVisitor h3_datagram_visitor; + absl::optional<QuicDatagramContextId> context_id; + Http3DatagramContextExtensions extensions; + ASSERT_EQ(QuicDataWriter::GetVarInt62Len(stream_->id()), 1); + std::array<char, 256> datagram; + datagram[0] = stream_->id(); + for (size_t i = 1; i < datagram.size(); i++) { + datagram[i] = i; + } + stream_->RegisterHttp3DatagramRegistrationVisitor( + &h3_datagram_registration_visitor); + stream_->RegisterHttp3DatagramContextId(context_id, extensions, + &h3_datagram_visitor); + session_->OnMessageReceived( + absl::string_view(datagram.data(), datagram.size())); + EXPECT_THAT(h3_datagram_visitor.received_h3_datagrams(), + ElementsAre(SavingHttp3DatagramVisitor::SavedHttp3Datagram{ + stream_->id(), context_id, + std::string(&datagram[1], datagram.size() - 1)})); + // Test move. + ::testing::NiceMock<MockHttp3DatagramRegistrationVisitor> + h3_datagram_registration_visitor2; + stream_->MoveHttp3DatagramRegistration(&h3_datagram_registration_visitor2); + SavingHttp3DatagramVisitor h3_datagram_visitor2; + stream_->MoveHttp3DatagramContextIdRegistration(context_id, + &h3_datagram_visitor2); + EXPECT_TRUE(h3_datagram_visitor2.received_h3_datagrams().empty()); + session_->OnMessageReceived( + absl::string_view(datagram.data(), datagram.size())); + EXPECT_THAT(h3_datagram_visitor2.received_h3_datagrams(), + ElementsAre(SavingHttp3DatagramVisitor::SavedHttp3Datagram{ + stream_->id(), context_id, + std::string(&datagram[1], datagram.size() - 1)})); + // Cleanup. + stream_->UnregisterHttp3DatagramContextId(context_id); + stream_->UnregisterHttp3DatagramRegistrationVisitor(); + session_->UnregisterHttp3DatagramFlowId(stream_->id()); +} + +TEST_P(QuicSpdyStreamTest, H3DatagramRegistrationWithContext) { + if (!UsesHttp3()) { + return; + } + Initialize(kShouldProcessData); + session_->set_should_negotiate_h3_datagram(true); + QuicSpdySessionPeer::SetH3DatagramSupported(session_.get(), true); + session_->RegisterHttp3DatagramFlowId(stream_->id(), stream_->id()); + ::testing::NiceMock<MockHttp3DatagramRegistrationVisitor> + h3_datagram_registration_visitor; + SavingHttp3DatagramVisitor h3_datagram_visitor; + absl::optional<QuicDatagramContextId> context_id = 42; + Http3DatagramContextExtensions extensions; + stream_->RegisterHttp3DatagramRegistrationVisitor( + &h3_datagram_registration_visitor); + stream_->RegisterHttp3DatagramContextId(context_id, extensions, + &h3_datagram_visitor); + // Test move. + ::testing::NiceMock<MockHttp3DatagramRegistrationVisitor> + h3_datagram_registration_visitor2; + stream_->MoveHttp3DatagramRegistration(&h3_datagram_registration_visitor2); + SavingHttp3DatagramVisitor h3_datagram_visitor2; + stream_->MoveHttp3DatagramContextIdRegistration(context_id, + &h3_datagram_visitor2); + // Cleanup. + stream_->UnregisterHttp3DatagramContextId(context_id); + stream_->UnregisterHttp3DatagramRegistrationVisitor(); + session_->UnregisterHttp3DatagramFlowId(stream_->id()); +} + +TEST_P(QuicSpdyStreamTest, SendHttp3Datagram) { + if (!UsesHttp3()) { + return; + } + Initialize(kShouldProcessData); + session_->set_should_negotiate_h3_datagram(true); + QuicSpdySessionPeer::SetH3DatagramSupported(session_.get(), true); + absl::optional<QuicDatagramContextId> context_id; + std::string h3_datagram_payload = {1, 2, 3, 4, 5, 6}; + EXPECT_CALL(*connection_, SendMessage(1, _, false)) + .WillOnce(Return(MESSAGE_STATUS_SUCCESS)); + EXPECT_EQ(stream_->SendHttp3Datagram(context_id, h3_datagram_payload), + MESSAGE_STATUS_SUCCESS); +} + } // namespace } // namespace test } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/http/spdy_utils.cc b/chromium/net/third_party/quiche/src/quic/core/http/spdy_utils.cc index 28b463afebe..27f5314b05f 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/spdy_utils.cc +++ b/chromium/net/third_party/quiche/src/quic/core/http/spdy_utils.cc @@ -16,7 +16,6 @@ #include "quic/platform/api/quic_flag_utils.h" #include "quic/platform/api/quic_flags.h" #include "quic/platform/api/quic_logging.h" -#include "quic/platform/api/quic_map_util.h" #include "common/quiche_text_utils.h" #include "spdy/core/spdy_protocol.h" @@ -78,7 +77,7 @@ bool SpdyUtils::CopyAndValidateHeaders(const QuicHeaderList& header_list, headers->AppendValueOrAddHeader(name, p.second); } - if (QuicContainsKey(*headers, "content-length") && + if (headers->contains("content-length") && !ExtractContentLengthFromHeaders(content_length, headers)) { return false; } @@ -155,7 +154,7 @@ bool SpdyUtils::PopulateHeaderBlockFromUrl(const std::string url, } // static -absl::optional<QuicDatagramFlowId> SpdyUtils::ParseDatagramFlowIdHeader( +absl::optional<QuicDatagramStreamId> SpdyUtils::ParseDatagramFlowIdHeader( const spdy::SpdyHeaderBlock& headers) { auto flow_id_pair = headers.find("datagram-flow-id"); if (flow_id_pair == headers.end()) { @@ -163,7 +162,7 @@ absl::optional<QuicDatagramFlowId> SpdyUtils::ParseDatagramFlowIdHeader( } std::vector<absl::string_view> flow_id_strings = absl::StrSplit(flow_id_pair->second, ','); - absl::optional<QuicDatagramFlowId> first_named_flow_id; + absl::optional<QuicDatagramStreamId> first_named_flow_id; for (absl::string_view flow_id_string : flow_id_strings) { std::vector<absl::string_view> flow_id_components = absl::StrSplit(flow_id_string, ';'); @@ -173,7 +172,7 @@ absl::optional<QuicDatagramFlowId> SpdyUtils::ParseDatagramFlowIdHeader( absl::string_view flow_id_value_string = flow_id_components[0]; quiche::QuicheTextUtils::RemoveLeadingAndTrailingWhitespace( &flow_id_value_string); - QuicDatagramFlowId flow_id; + QuicDatagramStreamId flow_id; if (!absl::SimpleAtoi(flow_id_value_string, &flow_id)) { continue; } @@ -191,7 +190,7 @@ absl::optional<QuicDatagramFlowId> SpdyUtils::ParseDatagramFlowIdHeader( // static void SpdyUtils::AddDatagramFlowIdHeader(spdy::SpdyHeaderBlock* headers, - QuicDatagramFlowId flow_id) { + QuicDatagramStreamId flow_id) { (*headers)["datagram-flow-id"] = absl::StrCat(flow_id); } diff --git a/chromium/net/third_party/quiche/src/quic/core/http/spdy_utils.h b/chromium/net/third_party/quiche/src/quic/core/http/spdy_utils.h index e3a8a97de48..01fae4f17a7 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/spdy_utils.h +++ b/chromium/net/third_party/quiche/src/quic/core/http/spdy_utils.h @@ -55,12 +55,12 @@ class QUIC_EXPORT_PRIVATE SpdyUtils { // Parses the "datagram-flow-id" header, returns the flow ID on success, or // returns absl::nullopt if the header was not present or failed to parse. - static absl::optional<QuicDatagramFlowId> ParseDatagramFlowIdHeader( + static absl::optional<QuicDatagramStreamId> ParseDatagramFlowIdHeader( const spdy::SpdyHeaderBlock& headers); // Adds the "datagram-flow-id" header. static void AddDatagramFlowIdHeader(spdy::SpdyHeaderBlock* headers, - QuicDatagramFlowId flow_id); + QuicDatagramStreamId flow_id); }; } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/http/spdy_utils_test.cc b/chromium/net/third_party/quiche/src/quic/core/http/spdy_utils_test.cc index 7307665da3d..918739d6f90 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/spdy_utils_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/http/spdy_utils_test.cc @@ -34,7 +34,7 @@ static std::unique_ptr<QuicHeaderList> FromList( static void ValidateDatagramFlowId( const std::string& header_value, - absl::optional<QuicDatagramFlowId> expected_flow_id) { + absl::optional<QuicDatagramStreamId> expected_flow_id) { SpdyHeaderBlock headers; headers["datagram-flow-id"] = header_value; ASSERT_EQ(SpdyUtils::ParseDatagramFlowIdHeader(headers), expected_flow_id); @@ -391,7 +391,7 @@ TEST_F(DatagramFlowIdTest, DatagramFlowId) { SpdyHeaderBlock headers; EXPECT_EQ(SpdyUtils::ParseDatagramFlowIdHeader(headers), absl::nullopt); // Add header and verify it parses. - QuicDatagramFlowId flow_id = 123; + QuicDatagramStreamId flow_id = 123; SpdyUtils::AddDatagramFlowIdHeader(&headers, flow_id); EXPECT_EQ(SpdyUtils::ParseDatagramFlowIdHeader(headers), flow_id); // Test empty header. diff --git a/chromium/net/third_party/quiche/src/quic/core/http/web_transport_http3.cc b/chromium/net/third_party/quiche/src/quic/core/http/web_transport_http3.cc index 2a105c2e5bf..820950b31bd 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/web_transport_http3.cc +++ b/chromium/net/third_party/quiche/src/quic/core/http/web_transport_http3.cc @@ -36,17 +36,19 @@ class QUIC_NO_EXPORT NoopWebTransportVisitor : public WebTransportVisitor { WebTransportHttp3::WebTransportHttp3(QuicSpdySession* session, QuicSpdyStream* connect_stream, - WebTransportSessionId id, - QuicDatagramFlowId flow_id) + WebTransportSessionId id) : session_(session), connect_stream_(connect_stream), id_(id), - flow_id_(flow_id), visitor_(std::make_unique<NoopWebTransportVisitor>()) { QUICHE_DCHECK(session_->SupportsWebTransport()); QUICHE_DCHECK(IsValidWebTransportSessionId(id, session_->version())); QUICHE_DCHECK_EQ(connect_stream_->id(), id); - session_->RegisterHttp3FlowId(flow_id, this); + connect_stream_->RegisterHttp3DatagramRegistrationVisitor(this); + if (session_->perspective() == Perspective::IS_CLIENT) { + context_is_known_ = true; + context_currently_registered_ = true; + } } void WebTransportHttp3::AssociateStream(QuicStreamId stream_id) { @@ -74,7 +76,11 @@ void WebTransportHttp3::CloseAllAssociatedStreams() { for (QuicStreamId id : streams) { session_->ResetStream(id, QUIC_STREAM_WEBTRANSPORT_SESSION_GONE); } - session_->UnregisterHttp3FlowId(flow_id_); + if (context_currently_registered_) { + context_currently_registered_ = false; + connect_stream_->UnregisterHttp3DatagramContextId(context_id_); + } + connect_stream_->UnregisterHttp3DatagramRegistrationVisitor(); } void WebTransportHttp3::HeadersReceived(const spdy::SpdyHeaderBlock& headers) { @@ -153,21 +159,81 @@ WebTransportStream* WebTransportHttp3::OpenOutgoingUnidirectionalStream() { } MessageStatus WebTransportHttp3::SendOrQueueDatagram(QuicMemSlice datagram) { - return session_->SendHttp3Datagram( - flow_id_, absl::string_view(datagram.data(), datagram.length())); + return connect_stream_->SendHttp3Datagram( + context_id_, absl::string_view(datagram.data(), datagram.length())); } void WebTransportHttp3::SetDatagramMaxTimeInQueue( QuicTime::Delta max_time_in_queue) { - session_->SetMaxTimeInQueueForFlowId(flow_id_, max_time_in_queue); + connect_stream_->SetMaxDatagramTimeInQueue(max_time_in_queue); } -void WebTransportHttp3::OnHttp3Datagram(QuicDatagramFlowId flow_id, - absl::string_view payload) { - QUICHE_DCHECK_EQ(flow_id, flow_id_); +void WebTransportHttp3::OnHttp3Datagram( + QuicStreamId stream_id, absl::optional<QuicDatagramContextId> context_id, + absl::string_view payload) { + QUICHE_DCHECK_EQ(stream_id, connect_stream_->id()); + QUICHE_DCHECK(context_id == context_id_); visitor_->OnDatagramReceived(payload); } +void WebTransportHttp3::OnContextReceived( + QuicStreamId stream_id, absl::optional<QuicDatagramContextId> context_id, + const Http3DatagramContextExtensions& /*extensions*/) { + if (stream_id != connect_stream_->id()) { + QUIC_BUG(WT3 bad datagram context registration) + << ENDPOINT << "Registered stream ID " << stream_id << ", expected " + << connect_stream_->id(); + return; + } + if (!context_is_known_) { + context_is_known_ = true; + context_id_ = context_id; + } + if (context_id != context_id_) { + QUIC_DLOG(INFO) << ENDPOINT << "Ignoring unexpected context ID " + << (context_id.has_value() ? context_id.value() : 0) + << " instead of " + << (context_id_.has_value() ? context_id_.value() : 0) + << " on stream ID " << connect_stream_->id(); + return; + } + if (session_->perspective() == Perspective::IS_SERVER) { + if (context_currently_registered_) { + QUIC_DLOG(ERROR) << ENDPOINT << "Received duplicate context ID " + << (context_id_.has_value() ? context_id_.value() : 0) + << " on stream ID " << connect_stream_->id(); + session_->ResetStream(connect_stream_->id(), QUIC_STREAM_CANCELLED); + return; + } + context_currently_registered_ = true; + Http3DatagramContextExtensions reply_extensions; + connect_stream_->RegisterHttp3DatagramContextId(context_id_, + reply_extensions, this); + } +} + +void WebTransportHttp3::OnContextClosed( + QuicStreamId stream_id, absl::optional<QuicDatagramContextId> context_id, + const Http3DatagramContextExtensions& /*extensions*/) { + if (stream_id != connect_stream_->id()) { + QUIC_BUG(WT3 bad datagram context registration) + << ENDPOINT << "Closed context on stream ID " << stream_id + << ", expected " << connect_stream_->id(); + return; + } + if (context_id != context_id_) { + QUIC_DLOG(INFO) << ENDPOINT << "Ignoring unexpected close of context ID " + << (context_id.has_value() ? context_id.value() : 0) + << " instead of " + << (context_id_.has_value() ? context_id_.value() : 0) + << " on stream ID " << connect_stream_->id(); + return; + } + QUIC_DLOG(INFO) << ENDPOINT << "Received datagram context close on stream ID " + << connect_stream_->id() << ", resetting stream"; + session_->ResetStream(connect_stream_->id(), QUIC_STREAM_CANCELLED); +} + WebTransportHttp3UnidirectionalStream::WebTransportHttp3UnidirectionalStream( PendingStream* pending, QuicSpdySession* session) diff --git a/chromium/net/third_party/quiche/src/quic/core/http/web_transport_http3.h b/chromium/net/third_party/quiche/src/quic/core/http/web_transport_http3.h index 667256ea515..df5c4f61bf7 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/web_transport_http3.h +++ b/chromium/net/third_party/quiche/src/quic/core/http/web_transport_http3.h @@ -28,12 +28,11 @@ class QuicSpdyStream; // <https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3> class QUIC_EXPORT_PRIVATE WebTransportHttp3 : public WebTransportSession, - public QuicSpdySession::Http3DatagramVisitor { + public QuicSpdyStream::Http3DatagramRegistrationVisitor, + public QuicSpdyStream::Http3DatagramVisitor { public: - WebTransportHttp3(QuicSpdySession* session, - QuicSpdyStream* connect_stream, - WebTransportSessionId id, - QuicDatagramFlowId flow_id); + WebTransportHttp3(QuicSpdySession* session, QuicSpdyStream* connect_stream, + WebTransportSessionId id); void HeadersReceived(const spdy::SpdyHeaderBlock& headers); void SetVisitor(std::unique_ptr<WebTransportVisitor> visitor) { @@ -42,6 +41,9 @@ class QUIC_EXPORT_PRIVATE WebTransportHttp3 WebTransportSessionId id() { return id_; } bool ready() { return ready_; } + absl::optional<QuicDatagramContextId> context_id() const { + return context_id_; + } void AssociateStream(QuicStreamId stream_id); void OnStreamClosed(QuicStreamId stream_id) { streams_.erase(stream_id); } @@ -63,16 +65,32 @@ class QUIC_EXPORT_PRIVATE WebTransportHttp3 MessageStatus SendOrQueueDatagram(QuicMemSlice datagram) override; void SetDatagramMaxTimeInQueue(QuicTime::Delta max_time_in_queue) override; - void OnHttp3Datagram(QuicDatagramFlowId flow_id, + // From QuicSpdyStream::Http3DatagramVisitor. + void OnHttp3Datagram(QuicStreamId stream_id, + absl::optional<QuicDatagramContextId> context_id, absl::string_view payload) override; + // From QuicSpdyStream::Http3DatagramRegistrationVisitor. + void OnContextReceived( + QuicStreamId stream_id, absl::optional<QuicDatagramContextId> context_id, + const Http3DatagramContextExtensions& extensions) override; + void OnContextClosed( + QuicStreamId stream_id, absl::optional<QuicDatagramContextId> context_id, + const Http3DatagramContextExtensions& extensions) override; + private: QuicSpdySession* const session_; // Unowned. QuicSpdyStream* const connect_stream_; // Unowned. const WebTransportSessionId id_; - const QuicDatagramFlowId flow_id_; + absl::optional<QuicDatagramContextId> context_id_; // |ready_| is set to true when the peer has seen both sets of headers. bool ready_ = false; + // Whether we know which |context_id_| to use. On the client this is always + // true, and on the server it becomes true when we receive a context + // registeration capsule. + bool context_is_known_ = false; + // Whether |context_id_| is currently registered with |connect_stream_|. + bool context_currently_registered_ = false; std::unique_ptr<WebTransportVisitor> visitor_; absl::flat_hash_set<QuicStreamId> streams_; quiche::QuicheCircularDeque<QuicStreamId> incoming_bidirectional_streams_; diff --git a/chromium/net/third_party/quiche/src/quic/core/legacy_quic_stream_id_manager.cc b/chromium/net/third_party/quiche/src/quic/core/legacy_quic_stream_id_manager.cc index 2ddf4b2498a..19128abf2ca 100644 --- a/chromium/net/third_party/quiche/src/quic/core/legacy_quic_stream_id_manager.cc +++ b/chromium/net/third_party/quiche/src/quic/core/legacy_quic_stream_id_manager.cc @@ -7,7 +7,6 @@ #include "quic/core/quic_types.h" #include "quic/core/quic_utils.h" #include "quic/core/quic_versions.h" -#include "quic/platform/api/quic_map_util.h" namespace quic { @@ -125,7 +124,7 @@ bool LegacyQuicStreamIdManager::IsAvailableStream(QuicStreamId id) const { return largest_peer_created_stream_id_ == QuicUtils::GetInvalidStreamId(transport_version_) || id > largest_peer_created_stream_id_ || - QuicContainsKey(available_streams_, id); + available_streams_.contains(id); } bool LegacyQuicStreamIdManager::IsIncomingStream(QuicStreamId id) const { diff --git a/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_instruction_decoder.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_instruction_decoder.cc index 495a58ffdfd..acd52c2ce33 100644 --- a/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_instruction_decoder.cc +++ b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_instruction_decoder.cc @@ -86,8 +86,6 @@ bool QpackInstructionDecoder::Decode(absl::string_view data) { return true; } } - - return true; } bool QpackInstructionDecoder::AtInstructionBoundary() const { diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_alarm.cc b/chromium/net/third_party/quiche/src/quic/core/quic_alarm.cc index bfef316ff62..81640a4c68e 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_alarm.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_alarm.cc @@ -4,30 +4,74 @@ #include "quic/core/quic_alarm.h" +#include "quic/platform/api/quic_bug_tracker.h" +#include "quic/platform/api/quic_flag_utils.h" +#include "quic/platform/api/quic_flags.h" +#include "quic/platform/api/quic_stack_trace.h" + namespace quic { QuicAlarm::QuicAlarm(QuicArenaScopedPtr<Delegate> delegate) : delegate_(std::move(delegate)), deadline_(QuicTime::Zero()) {} -QuicAlarm::~QuicAlarm() {} +QuicAlarm::~QuicAlarm() { + if (GetQuicRestartFlag(quic_alarm_add_permanent_cancel) && IsSet()) { + QUIC_CODE_COUNT(quic_alarm_not_cancelled_in_dtor); + static uint64_t hit_count = 0; + ++hit_count; + if ((hit_count & (hit_count - 1)) == 0) { + QUIC_LOG(ERROR) << "QuicAlarm not cancelled at destruction. " + << QuicStackTrace(); + } + } +} void QuicAlarm::Set(QuicTime new_deadline) { QUICHE_DCHECK(!IsSet()); QUICHE_DCHECK(new_deadline.IsInitialized()); + + if (IsPermanentlyCancelled()) { + QUIC_BUG(quic_alarm_illegal_set) + << "Set called after alarm is permanently cancelled. new_deadline:" + << new_deadline; + return; + } + deadline_ = new_deadline; SetImpl(); } -void QuicAlarm::Cancel() { - if (!IsSet()) { - // Don't try to cancel an alarm that hasn't been set. +void QuicAlarm::CancelInternal(bool permanent) { + if (!GetQuicRestartFlag(quic_alarm_add_permanent_cancel)) { + if (!IsSet()) { + // Don't try to cancel an alarm that hasn't been set. + return; + } + deadline_ = QuicTime::Zero(); + CancelImpl(); return; } - deadline_ = QuicTime::Zero(); - CancelImpl(); + + if (IsSet()) { + deadline_ = QuicTime::Zero(); + CancelImpl(); + } + + if (permanent) { + delegate_.reset(); + } } +bool QuicAlarm::IsPermanentlyCancelled() const { return delegate_ == nullptr; } + void QuicAlarm::Update(QuicTime new_deadline, QuicTime::Delta granularity) { + if (IsPermanentlyCancelled()) { + QUIC_BUG(quic_alarm_illegal_update) + << "Update called after alarm is permanently cancelled. new_deadline:" + << new_deadline << ", granularity:" << granularity; + return; + } + if (!new_deadline.IsInitialized()) { Cancel(); return; @@ -55,7 +99,9 @@ void QuicAlarm::Fire() { } deadline_ = QuicTime::Zero(); - delegate_->OnAlarm(); + if (!IsPermanentlyCancelled()) { + delegate_->OnAlarm(); + } } void QuicAlarm::UpdateImpl() { diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_alarm.h b/chromium/net/third_party/quiche/src/quic/core/quic_alarm.h index 527d2e3c662..5080fd4b263 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_alarm.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_alarm.h @@ -36,11 +36,18 @@ class QUIC_EXPORT_PRIVATE QuicAlarm { // then Set(). void Set(QuicTime new_deadline); - // Cancels the alarm. May be called repeatedly. Does not - // guarantee that the underlying scheduling system will remove - // the alarm's associated task, but guarantees that the - // delegates OnAlarm method will not be called. - void Cancel(); + // Both PermanentCancel() and Cancel() can cancel the alarm. If permanent, + // future calls to Set() and Update() will become no-op except emitting an + // error log. + // + // Both may be called repeatedly. Does not guarantee that the underlying + // scheduling system will remove the alarm's associated task, but guarantees + // that the delegates OnAlarm method will not be called. + void PermanentCancel() { CancelInternal(true); } + void Cancel() { CancelInternal(false); } + + // Return true if PermanentCancel() has been called. + bool IsPermanentlyCancelled() const; // Cancels and sets the alarm if the |deadline| is farther from the current // deadline than |granularity|, and otherwise does nothing. If |deadline| is @@ -77,6 +84,8 @@ class QUIC_EXPORT_PRIVATE QuicAlarm { void Fire(); private: + void CancelInternal(bool permanent); + QuicArenaScopedPtr<Delegate> delegate_; QuicTime deadline_; }; diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_alarm_test.cc b/chromium/net/third_party/quiche/src/quic/core/quic_alarm_test.cc index 8f7296c8e10..ab070022eec 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_alarm_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_alarm_test.cc @@ -4,6 +4,7 @@ #include "quic/core/quic_alarm.h" +#include "quic/platform/api/quic_expect_bug.h" #include "quic/platform/api/quic_test.h" using testing::Invoke; @@ -111,6 +112,48 @@ TEST_F(QuicAlarmTest, Cancel) { EXPECT_EQ(QuicTime::Zero(), alarm_.deadline()); } +TEST_F(QuicAlarmTest, PermanentCancel) { + QuicTime deadline = QuicTime::Zero() + QuicTime::Delta::FromSeconds(7); + alarm_.Set(deadline); + alarm_.PermanentCancel(); + EXPECT_FALSE(alarm_.IsSet()); + EXPECT_FALSE(alarm_.scheduled()); + EXPECT_EQ(QuicTime::Zero(), alarm_.deadline()); + + if (!GetQuicRestartFlag(quic_alarm_add_permanent_cancel)) { + alarm_.Set(deadline); + // When flag is false, PermanentCancel should work like a normal Cancel. + EXPECT_TRUE(alarm_.IsSet()); + EXPECT_TRUE(alarm_.scheduled()); + EXPECT_EQ(deadline, alarm_.deadline()); + EXPECT_FALSE(alarm_.IsPermanentlyCancelled()); + alarm_.PermanentCancel(); + } else { + EXPECT_QUIC_BUG(alarm_.Set(deadline), + "Set called after alarm is permanently cancelled"); + EXPECT_TRUE(alarm_.IsPermanentlyCancelled()); + } + EXPECT_FALSE(alarm_.IsSet()); + EXPECT_FALSE(alarm_.scheduled()); + EXPECT_EQ(QuicTime::Zero(), alarm_.deadline()); + + if (!GetQuicRestartFlag(quic_alarm_add_permanent_cancel)) { + alarm_.Update(deadline, QuicTime::Delta::Zero()); + EXPECT_TRUE(alarm_.IsSet()); + EXPECT_TRUE(alarm_.scheduled()); + EXPECT_EQ(deadline, alarm_.deadline()); + EXPECT_FALSE(alarm_.IsPermanentlyCancelled()); + alarm_.PermanentCancel(); + } else { + EXPECT_QUIC_BUG(alarm_.Update(deadline, QuicTime::Delta::Zero()), + "Update called after alarm is permanently cancelled"); + EXPECT_TRUE(alarm_.IsPermanentlyCancelled()); + } + EXPECT_FALSE(alarm_.IsSet()); + EXPECT_FALSE(alarm_.scheduled()); + EXPECT_EQ(QuicTime::Zero(), alarm_.deadline()); +} + TEST_F(QuicAlarmTest, Update) { QuicTime deadline = QuicTime::Zero() + QuicTime::Delta::FromSeconds(7); alarm_.Set(deadline); diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_buffer_allocator.h b/chromium/net/third_party/quiche/src/quic/core/quic_buffer_allocator.h index a661a7ee76b..d3905e70064 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_buffer_allocator.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_buffer_allocator.h @@ -9,6 +9,7 @@ #include <memory> +#include "absl/strings/string_view.h" #include "quic/platform/api/quic_export.h" namespace quic { @@ -56,6 +57,58 @@ inline QuicUniqueBufferPtr MakeUniqueBuffer(QuicBufferAllocator* allocator, QuicBufferDeleter(allocator)); } +// QuicUniqueBufferPtr with a length attached to it. Similar to QuicMemSlice, +// except unlike QuicMemSlice, QuicBuffer is mutable and is not +// platform-specific. Also unlike QuicMemSlice, QuicBuffer can be empty. +class QUIC_EXPORT_PRIVATE QuicBuffer { + public: + QuicBuffer() : buffer_(nullptr, QuicBufferDeleter(nullptr)), size_(0) {} + QuicBuffer(QuicBufferAllocator* allocator, size_t size) + : buffer_(MakeUniqueBuffer(allocator, size)), size_(size) {} + + // Make sure the move constructor zeroes out the size field. + QuicBuffer(QuicBuffer&& other) + : buffer_(std::move(other.buffer_)), size_(other.size_) { + other.buffer_ = nullptr; + other.size_ = 0; + } + QuicBuffer& operator=(QuicBuffer&& other) { + buffer_ = std::move(other.buffer_); + size_ = other.size_; + + other.buffer_ = nullptr; + other.size_ = 0; + return *this; + } + + // Convenience method to initialize a QuicBuffer by copying from an existing + // one. + static QuicBuffer Copy(QuicBufferAllocator* allocator, + absl::string_view data) { + QuicBuffer result(allocator, data.size()); + memcpy(result.data(), data.data(), data.size()); + return result; + } + + const char* data() const { return buffer_.get(); } + char* data() { return buffer_.get(); } + size_t size() const { return size_; } + bool empty() const { return size_ == 0; } + absl::string_view AsStringView() const { + return absl::string_view(data(), size()); + } + + // Releases the ownership of the underlying buffer. + QuicUniqueBufferPtr Release() { + size_ = 0; + return std::move(buffer_); + } + + private: + QuicUniqueBufferPtr buffer_; + size_t size_; +}; + } // namespace quic #endif // QUICHE_QUIC_CORE_QUIC_BUFFER_ALLOCATOR_H_ diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_buffered_packet_store.cc b/chromium/net/third_party/quiche/src/quic/core/quic_buffered_packet_store.cc index e4d4c971338..9312ea5deae 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_buffered_packet_store.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_buffered_packet_store.cc @@ -8,7 +8,6 @@ #include "quic/platform/api/quic_bug_tracker.h" #include "quic/platform/api/quic_flags.h" -#include "quic/platform/api/quic_map_util.h" namespace quic { @@ -77,7 +76,12 @@ QuicBufferedPacketStore::QuicBufferedPacketStore( expiration_alarm_( alarm_factory->CreateAlarm(new ConnectionExpireAlarm(this))) {} -QuicBufferedPacketStore::~QuicBufferedPacketStore() {} +QuicBufferedPacketStore::~QuicBufferedPacketStore() { + if (GetQuicRestartFlag(quic_alarm_add_permanent_cancel) && + expiration_alarm_ != nullptr) { + expiration_alarm_->PermanentCancel(); + } +} EnqueuePacketResult QuicBufferedPacketStore::EnqueuePacket( QuicConnectionId connection_id, @@ -92,15 +96,14 @@ EnqueuePacketResult QuicBufferedPacketStore::EnqueuePacket( QUIC_BUG_IF(quic_bug_12410_1, !GetQuicFlag(FLAGS_quic_allow_chlo_buffering)) << "Shouldn't buffer packets if disabled via flag."; QUIC_BUG_IF(quic_bug_12410_2, - is_chlo && QuicContainsKey(connections_with_chlo_, connection_id)) + is_chlo && connections_with_chlo_.contains(connection_id)) << "Shouldn't buffer duplicated CHLO on connection " << connection_id; QUIC_BUG_IF(quic_bug_12410_3, !is_chlo && !alpns.empty()) << "Shouldn't have an ALPN defined for a non-CHLO packet."; QUIC_BUG_IF(quic_bug_12410_4, is_chlo && !version.IsKnown()) << "Should have version for CHLO packet."; - const bool is_first_packet = - !QuicContainsKey(undecryptable_packets_, connection_id); + const bool is_first_packet = !undecryptable_packets_.contains(connection_id); if (is_first_packet) { if (ShouldNotBufferPacket(is_chlo)) { // Drop the packet if the upper limit of undecryptable packets has been @@ -112,17 +115,16 @@ EnqueuePacketResult QuicBufferedPacketStore::EnqueuePacket( undecryptable_packets_.back().second.ietf_quic = ietf_quic; undecryptable_packets_.back().second.version = version; } - QUICHE_CHECK(QuicContainsKey(undecryptable_packets_, connection_id)); + QUICHE_CHECK(undecryptable_packets_.contains(connection_id)); BufferedPacketList& queue = undecryptable_packets_.find(connection_id)->second; if (!is_chlo) { // If current packet is not CHLO, it might not be buffered because store // only buffers certain number of undecryptable packets per connection. - size_t num_non_chlo_packets = - QuicContainsKey(connections_with_chlo_, connection_id) - ? (queue.buffered_packets.size() - 1) - : queue.buffered_packets.size(); + size_t num_non_chlo_packets = connections_with_chlo_.contains(connection_id) + ? (queue.buffered_packets.size() - 1) + : queue.buffered_packets.size(); if (num_non_chlo_packets >= kDefaultMaxUndecryptablePackets) { // If there are kMaxBufferedPacketsPerConnection packets buffered up for // this connection, drop the current packet. @@ -169,7 +171,7 @@ EnqueuePacketResult QuicBufferedPacketStore::EnqueuePacket( bool QuicBufferedPacketStore::HasBufferedPackets( QuicConnectionId connection_id) const { - return QuicContainsKey(undecryptable_packets_, connection_id); + return undecryptable_packets_.contains(connection_id); } bool QuicBufferedPacketStore::HasChlosBuffered() const { @@ -254,7 +256,7 @@ BufferedPacketList QuicBufferedPacketStore::DeliverPacketsForNextConnection( bool QuicBufferedPacketStore::HasChloForConnection( QuicConnectionId connection_id) { - return QuicContainsKey(connections_with_chlo_, connection_id); + return connections_with_chlo_.contains(connection_id); } bool QuicBufferedPacketStore::IngestPacketForTlsChloExtraction( diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_buffered_packet_store.h b/chromium/net/third_party/quiche/src/quic/core/quic_buffered_packet_store.h index 29be2c86a42..5ba0a25ed0b 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_buffered_packet_store.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_buffered_packet_store.h @@ -14,9 +14,9 @@ #include "quic/core/quic_packets.h" #include "quic/core/quic_time.h" #include "quic/core/tls_chlo_extractor.h" -#include "quic/platform/api/quic_containers.h" #include "quic/platform/api/quic_export.h" #include "quic/platform/api/quic_socket_address.h" +#include "common/quiche_linked_hash_map.h" namespace quic { @@ -78,9 +78,9 @@ class QUIC_NO_EXPORT QuicBufferedPacketStore { TlsChloExtractor tls_chlo_extractor; }; - using BufferedPacketMap = QuicLinkedHashMap<QuicConnectionId, - BufferedPacketList, - QuicConnectionIdHash>; + using BufferedPacketMap = quiche::QuicheLinkedHashMap<QuicConnectionId, + BufferedPacketList, + QuicConnectionIdHash>; class QUIC_NO_EXPORT VisitorInterface { public: @@ -186,7 +186,7 @@ class QUIC_NO_EXPORT QuicBufferedPacketStore { // Keeps track of connection with CHLO buffered up already and the order they // arrive. - QuicLinkedHashMap<QuicConnectionId, bool, QuicConnectionIdHash> + quiche::QuicheLinkedHashMap<QuicConnectionId, bool, QuicConnectionIdHash> connections_with_chlo_; }; diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_connection.cc b/chromium/net/third_party/quiche/src/quic/core/quic_connection.cc index 09806f5a8a8..12bf186d318 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_connection.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_connection.cc @@ -45,7 +45,6 @@ #include "quic/platform/api/quic_flags.h" #include "quic/platform/api/quic_hostname_utils.h" #include "quic/platform/api/quic_logging.h" -#include "quic/platform/api/quic_map_util.h" #include "quic/platform/api/quic_server_stats.h" #include "quic/platform/api/quic_socket_address.h" #include "common/quiche_text_utils.h" @@ -63,13 +62,23 @@ const QuicPacketCount kMaxConsecutiveNonRetransmittablePackets = 19; // The minimum release time into future in ms. const int kMinReleaseTimeIntoFutureMs = 1; -// An alarm that is scheduled to send an ack if a timeout occurs. -class AckAlarmDelegate : public QuicAlarm::Delegate { +// Base class of all alarms owned by a QuicConnection. +class QuicConnectionAlarmDelegate : public QuicAlarm::Delegate { public: - explicit AckAlarmDelegate(QuicConnection* connection) + explicit QuicConnectionAlarmDelegate(QuicConnection* connection) : connection_(connection) {} - AckAlarmDelegate(const AckAlarmDelegate&) = delete; - AckAlarmDelegate& operator=(const AckAlarmDelegate&) = delete; + QuicConnectionAlarmDelegate(const QuicConnectionAlarmDelegate&) = delete; + QuicConnectionAlarmDelegate& operator=(const QuicConnectionAlarmDelegate&) = + delete; + + protected: + QuicConnection* connection_; +}; + +// An alarm that is scheduled to send an ack if a timeout occurs. +class AckAlarmDelegate : public QuicConnectionAlarmDelegate { + public: + using QuicConnectionAlarmDelegate::QuicConnectionAlarmDelegate; void OnAlarm() override { QUICHE_DCHECK(connection_->ack_frame_updated()); @@ -81,136 +90,86 @@ class AckAlarmDelegate : public QuicAlarm::Delegate { connection_->SendAck(); } } - - private: - QuicConnection* connection_; }; // This alarm will be scheduled any time a data-bearing packet is sent out. // When the alarm goes off, the connection checks to see if the oldest packets // have been acked, and retransmit them if they have not. -class RetransmissionAlarmDelegate : public QuicAlarm::Delegate { +class RetransmissionAlarmDelegate : public QuicConnectionAlarmDelegate { public: - explicit RetransmissionAlarmDelegate(QuicConnection* connection) - : connection_(connection) {} - RetransmissionAlarmDelegate(const RetransmissionAlarmDelegate&) = delete; - RetransmissionAlarmDelegate& operator=(const RetransmissionAlarmDelegate&) = - delete; + using QuicConnectionAlarmDelegate::QuicConnectionAlarmDelegate; void OnAlarm() override { QUICHE_DCHECK(connection_->connected()); connection_->OnRetransmissionTimeout(); } - - private: - QuicConnection* connection_; }; // An alarm that is scheduled when the SentPacketManager requires a delay // before sending packets and fires when the packet may be sent. -class SendAlarmDelegate : public QuicAlarm::Delegate { +class SendAlarmDelegate : public QuicConnectionAlarmDelegate { public: - explicit SendAlarmDelegate(QuicConnection* connection) - : connection_(connection) {} - SendAlarmDelegate(const SendAlarmDelegate&) = delete; - SendAlarmDelegate& operator=(const SendAlarmDelegate&) = delete; + using QuicConnectionAlarmDelegate::QuicConnectionAlarmDelegate; void OnAlarm() override { QUICHE_DCHECK(connection_->connected()); connection_->WriteIfNotBlocked(); } - - private: - QuicConnection* connection_; }; -class PingAlarmDelegate : public QuicAlarm::Delegate { +class PingAlarmDelegate : public QuicConnectionAlarmDelegate { public: - explicit PingAlarmDelegate(QuicConnection* connection) - : connection_(connection) {} - PingAlarmDelegate(const PingAlarmDelegate&) = delete; - PingAlarmDelegate& operator=(const PingAlarmDelegate&) = delete; + using QuicConnectionAlarmDelegate::QuicConnectionAlarmDelegate; void OnAlarm() override { QUICHE_DCHECK(connection_->connected()); connection_->OnPingTimeout(); } - - private: - QuicConnection* connection_; }; -class MtuDiscoveryAlarmDelegate : public QuicAlarm::Delegate { +class MtuDiscoveryAlarmDelegate : public QuicConnectionAlarmDelegate { public: - explicit MtuDiscoveryAlarmDelegate(QuicConnection* connection) - : connection_(connection) {} - MtuDiscoveryAlarmDelegate(const MtuDiscoveryAlarmDelegate&) = delete; - MtuDiscoveryAlarmDelegate& operator=(const MtuDiscoveryAlarmDelegate&) = - delete; + using QuicConnectionAlarmDelegate::QuicConnectionAlarmDelegate; void OnAlarm() override { QUICHE_DCHECK(connection_->connected()); connection_->DiscoverMtu(); } - - private: - QuicConnection* connection_; }; -class ProcessUndecryptablePacketsAlarmDelegate : public QuicAlarm::Delegate { +class ProcessUndecryptablePacketsAlarmDelegate + : public QuicConnectionAlarmDelegate { public: - explicit ProcessUndecryptablePacketsAlarmDelegate(QuicConnection* connection) - : connection_(connection) {} - ProcessUndecryptablePacketsAlarmDelegate( - const ProcessUndecryptablePacketsAlarmDelegate&) = delete; - ProcessUndecryptablePacketsAlarmDelegate& operator=( - const ProcessUndecryptablePacketsAlarmDelegate&) = delete; + using QuicConnectionAlarmDelegate::QuicConnectionAlarmDelegate; void OnAlarm() override { QUICHE_DCHECK(connection_->connected()); QuicConnection::ScopedPacketFlusher flusher(connection_); connection_->MaybeProcessUndecryptablePackets(); } - - private: - QuicConnection* connection_; }; -class DiscardPreviousOneRttKeysAlarmDelegate : public QuicAlarm::Delegate { +class DiscardPreviousOneRttKeysAlarmDelegate + : public QuicConnectionAlarmDelegate { public: - explicit DiscardPreviousOneRttKeysAlarmDelegate(QuicConnection* connection) - : connection_(connection) {} - DiscardPreviousOneRttKeysAlarmDelegate( - const DiscardPreviousOneRttKeysAlarmDelegate&) = delete; - DiscardPreviousOneRttKeysAlarmDelegate& operator=( - const DiscardPreviousOneRttKeysAlarmDelegate&) = delete; + using QuicConnectionAlarmDelegate::QuicConnectionAlarmDelegate; void OnAlarm() override { QUICHE_DCHECK(connection_->connected()); connection_->DiscardPreviousOneRttKeys(); } - - private: - QuicConnection* connection_; }; -class DiscardZeroRttDecryptionKeysAlarmDelegate : public QuicAlarm::Delegate { +class DiscardZeroRttDecryptionKeysAlarmDelegate + : public QuicConnectionAlarmDelegate { public: - explicit DiscardZeroRttDecryptionKeysAlarmDelegate(QuicConnection* connection) - : connection_(connection) {} - DiscardZeroRttDecryptionKeysAlarmDelegate( - const DiscardZeroRttDecryptionKeysAlarmDelegate&) = delete; - DiscardZeroRttDecryptionKeysAlarmDelegate& operator=( - const DiscardZeroRttDecryptionKeysAlarmDelegate&) = delete; + using QuicConnectionAlarmDelegate::QuicConnectionAlarmDelegate; void OnAlarm() override { QUICHE_DCHECK(connection_->connected()); QUIC_DLOG(INFO) << "0-RTT discard alarm fired"; connection_->RemoveDecrypter(ENCRYPTION_ZERO_RTT); } - - private: - QuicConnection* connection_; }; // When the clearer goes out of scope, the coalesced packet gets cleared. @@ -278,8 +237,6 @@ QuicConnection::QuicConnection( encryption_level_(ENCRYPTION_INITIAL), clock_(helper->GetClock()), random_generator_(helper->GetRandomGenerator()), - server_connection_id_(server_connection_id), - client_connection_id_(EmptyQuicConnectionId()), client_connection_id_is_set_(false), direct_peer_address_(initial_peer_address), default_path_(initial_self_address, @@ -335,7 +292,7 @@ QuicConnection::QuicConnection( visitor_(nullptr), debug_visitor_(nullptr), packet_creator_(server_connection_id, &framer_, random_generator_, this), - time_of_last_received_packet_(clock_->ApproximateNow()), + last_received_packet_info_(clock_->ApproximateNow()), sent_packet_manager_(perspective, clock_, random_generator_, @@ -357,8 +314,6 @@ QuicConnection::QuicConnection( bundle_retransmittable_with_pto_ack_(false), fill_up_link_during_probing_(false), probing_retransmission_pending_(false), - stateless_reset_token_received_(false), - received_stateless_reset_token_({}), last_control_frame_id_(kInvalidControlFrameId), is_path_degrading_(false), processing_ack_frame_(false), @@ -369,18 +324,13 @@ QuicConnection::QuicConnection( clock_->ApproximateNow(), &arena_, alarm_factory_), - encrypted_control_frames_( - GetQuicReloadableFlag(quic_encrypted_control_frames)), - use_encryption_level_context_( - encrypted_control_frames_ && - GetQuicReloadableFlag(quic_use_encryption_level_context)), path_validator_(alarm_factory_, &arena_, this, random_generator_), most_recent_frame_type_(NUM_FRAME_TYPES) { QUICHE_DCHECK(perspective_ == Perspective::IS_CLIENT || default_path_.self_address.IsInitialized()); - if (use_encryption_level_context_) { - QUIC_RELOADABLE_FLAG_COUNT(quic_use_encryption_level_context); + if (add_missing_update_ack_timeout_) { + QUIC_RELOADABLE_FLAG_COUNT(quic_add_missing_update_ack_timeout); } support_multiple_connection_ids_ = @@ -417,7 +367,7 @@ QuicConnection::QuicConnection( MaybeEnableMultiplePacketNumberSpacesSupport(); QUICHE_DCHECK(perspective_ == Perspective::IS_CLIENT || supported_versions.size() == 1); - InstallInitialCrypters(ServerConnectionId()); + InstallInitialCrypters(default_path_.server_connection_id); // On the server side, version negotiation has been done by the dispatcher, // and the server connection is created with the right version. @@ -482,9 +432,9 @@ bool QuicConnection::ValidateConfigConnectionIds(const QuicConfig& config) { // Validate initial_source_connection_id. QuicConnectionId expected_initial_source_connection_id; if (perspective_ == Perspective::IS_CLIENT) { - expected_initial_source_connection_id = ServerConnectionId(); + expected_initial_source_connection_id = default_path_.server_connection_id; } else { - expected_initial_source_connection_id = ClientConnectionId(); + expected_initial_source_connection_id = default_path_.client_connection_id; } if (!config.HasReceivedInitialSourceConnectionId() || config.ReceivedInitialSourceConnectionId() != @@ -682,14 +632,8 @@ void QuicConnection::SetFromConfig(const QuicConfig& config) { no_stop_waiting_frames_ = true; } if (config.HasReceivedStatelessResetToken()) { - if (use_connection_id_on_default_path_) { - default_path_.stateless_reset_token_received = true; - default_path_.stateless_reset_token = - config.ReceivedStatelessResetToken(); - } else { - stateless_reset_token_received_ = true; - received_stateless_reset_token_ = config.ReceivedStatelessResetToken(); - } + default_path_.stateless_reset_token_received = true; + default_path_.stateless_reset_token = config.ReceivedStatelessResetToken(); } if (config.HasReceivedAckDelayExponent()) { framer_.set_peer_ack_delay_exponent(config.ReceivedAckDelayExponent()); @@ -720,7 +664,6 @@ void QuicConnection::SetFromConfig(const QuicConfig& config) { // 2) Client side's rollout can be protected by the same connection option. connection_migration_use_new_cid_ = support_multiple_connection_ids_ && validate_client_addresses_ && - use_connection_id_on_default_path_ && group_path_response_and_challenge_sending_closer_ && GetQuicReloadableFlag(quic_drop_unsent_path_response) && GetQuicReloadableFlag(quic_connection_migration_use_new_cid_v2); @@ -858,7 +801,8 @@ bool QuicConnection::SelectMutualVersion( framer_.supported_versions(); for (size_t i = 0; i < supported_versions.size(); ++i) { const ParsedQuicVersion& version = supported_versions[i]; - if (QuicContainsValue(available_versions, version)) { + if (std::find(available_versions.begin(), available_versions.end(), + version) != available_versions.end()) { framer_.set_version(version); return true; } @@ -885,7 +829,7 @@ void QuicConnection::OnPublicResetPacket(const QuicPublicResetPacket& packet) { // Check that any public reset packet with a different connection ID that was // routed to this QuicConnection has been redirected before control reaches // here. (Check for a bug regression.) - QUICHE_DCHECK_EQ(ServerConnectionId(), packet.connection_id); + QUICHE_DCHECK_EQ(default_path_.server_connection_id, packet.connection_id); QUICHE_DCHECK_EQ(perspective_, Perspective::IS_CLIENT); QUICHE_DCHECK(!version().HasIetfInvariantHeader()); if (debug_visitor_ != nullptr) { @@ -923,7 +867,7 @@ void QuicConnection::OnVersionNegotiationPacket( // Check that any public reset packet with a different connection ID that was // routed to this QuicConnection has been redirected before control reaches // here. (Check for a bug regression.) - QUICHE_DCHECK_EQ(ServerConnectionId(), packet.connection_id); + QUICHE_DCHECK_EQ(default_path_.server_connection_id, packet.connection_id); if (perspective_ == Perspective::IS_SERVER) { const std::string error_details = "Server received version negotiation packet."; @@ -942,7 +886,8 @@ void QuicConnection::OnVersionNegotiationPacket( return; } - if (QuicContainsValue(packet.versions, version())) { + if (std::find(packet.versions.begin(), packet.versions.end(), version()) != + packet.versions.end()) { const std::string error_details = absl::StrCat( "Server already supports client's version ", ParsedQuicVersionToString(version()), @@ -974,17 +919,17 @@ void QuicConnection::OnRetryPacket(QuicConnectionId original_connection_id, absl::string_view retry_without_tag) { QUICHE_DCHECK_EQ(Perspective::IS_CLIENT, perspective_); if (version().UsesTls()) { - if (!CryptoUtils::ValidateRetryIntegrityTag(version(), ServerConnectionId(), - retry_without_tag, - retry_integrity_tag)) { + if (!CryptoUtils::ValidateRetryIntegrityTag( + version(), default_path_.server_connection_id, retry_without_tag, + retry_integrity_tag)) { QUIC_DLOG(ERROR) << "Ignoring RETRY with invalid integrity tag"; return; } } else { - if (original_connection_id != ServerConnectionId()) { + if (original_connection_id != default_path_.server_connection_id) { QUIC_DLOG(ERROR) << "Ignoring RETRY with original connection ID " << original_connection_id << " not matching expected " - << ServerConnectionId() << " token " + << default_path_.server_connection_id << " token " << absl::BytesToHexString(retry_token); return; } @@ -992,10 +937,11 @@ void QuicConnection::OnRetryPacket(QuicConnectionId original_connection_id, framer_.set_drop_incoming_retry_packets(true); stats_.retry_packet_processed = true; QUIC_DLOG(INFO) << "Received RETRY, replacing connection ID " - << ServerConnectionId() << " with " << new_connection_id - << ", received token " << absl::BytesToHexString(retry_token); + << default_path_.server_connection_id << " with " + << new_connection_id << ", received token " + << absl::BytesToHexString(retry_token); if (!original_destination_connection_id_.has_value()) { - original_destination_connection_id_ = ServerConnectionId(); + original_destination_connection_id_ = default_path_.server_connection_id; } QUICHE_DCHECK(!retry_source_connection_id_.has_value()) << retry_source_connection_id_.value(); @@ -1004,51 +950,32 @@ void QuicConnection::OnRetryPacket(QuicConnectionId original_connection_id, packet_creator_.SetRetryToken(retry_token); // Reinstall initial crypters because the connection ID changed. - InstallInitialCrypters(ServerConnectionId()); + InstallInitialCrypters(default_path_.server_connection_id); sent_packet_manager_.MarkInitialPacketsForRetransmission(); } -bool QuicConnection::HasIncomingConnectionId( - QuicConnectionId connection_id) const { - if (quic_deprecate_incoming_connection_ids_) { - QUIC_RELOADABLE_FLAG_COUNT(quic_deprecate_incoming_connection_ids); - // TODO(haoyuewang) Inline this after the flag is deprecated. - return connection_id == original_destination_connection_id_; - } - for (QuicConnectionId const& incoming_connection_id : - incoming_connection_ids_) { - if (incoming_connection_id == connection_id) { - return true; - } - } - return false; -} - void QuicConnection::SetOriginalDestinationConnectionId( const QuicConnectionId& original_destination_connection_id) { QUIC_DLOG(INFO) << "Setting original_destination_connection_id to " << original_destination_connection_id << " on connection with server_connection_id " - << ServerConnectionId(); - QUICHE_DCHECK_NE(original_destination_connection_id, ServerConnectionId()); - if (!quic_deprecate_incoming_connection_ids_) { - if (!HasIncomingConnectionId(original_destination_connection_id)) { - incoming_connection_ids_.push_back(original_destination_connection_id); - } - } + << default_path_.server_connection_id; + QUICHE_DCHECK_NE(original_destination_connection_id, + default_path_.server_connection_id); InstallInitialCrypters(original_destination_connection_id); QUICHE_DCHECK(!original_destination_connection_id_.has_value()) << original_destination_connection_id_.value(); original_destination_connection_id_ = original_destination_connection_id; - original_destination_connection_id_replacement_ = ServerConnectionId(); + original_destination_connection_id_replacement_ = + default_path_.server_connection_id; } QuicConnectionId QuicConnection::GetOriginalDestinationConnectionId() { if (original_destination_connection_id_.has_value()) { return original_destination_connection_id_.value(); } - return ServerConnectionId(); + return default_path_.server_connection_id; } bool QuicConnection::ValidateServerConnectionId( @@ -1061,15 +988,15 @@ bool QuicConnection::ValidateServerConnectionId( QuicConnectionId server_connection_id = GetServerConnectionIdAsRecipient(header, perspective_); - if (server_connection_id == ServerConnectionId() || - HasIncomingConnectionId(server_connection_id)) { + if (server_connection_id == default_path_.server_connection_id || + server_connection_id == original_destination_connection_id_) { return true; } if (PacketCanReplaceServerConnectionId(header, perspective_)) { QUIC_DLOG(INFO) << ENDPOINT << "Accepting packet with new connection ID " << server_connection_id << " instead of " - << ServerConnectionId(); + << default_path_.server_connection_id; return true; } @@ -1089,13 +1016,10 @@ bool QuicConnection::OnUnauthenticatedPublicHeader( // If last packet destination connection ID is the original server // connection ID chosen by client, replaces it with the connection ID chosen // by server. - if (use_connection_id_on_default_path_ && - perspective_ == Perspective::IS_SERVER && + if (perspective_ == Perspective::IS_SERVER && original_destination_connection_id_.has_value() && last_packet_destination_connection_id_ == *original_destination_connection_id_) { - QUIC_RELOADABLE_FLAG_COUNT_N(quic_use_connection_id_on_default_path_v2, 3, - 3); last_packet_destination_connection_id_ = original_destination_connection_id_replacement_; } @@ -1112,7 +1036,7 @@ bool QuicConnection::OnUnauthenticatedPublicHeader( QUIC_DLOG(INFO) << ENDPOINT << "Ignoring packet from unexpected server connection ID " << server_connection_id << " instead of " - << ServerConnectionId(); + << default_path_.server_connection_id; if (debug_visitor_ != nullptr) { debug_visitor_->OnIncorrectConnectionId(server_connection_id); } @@ -1135,7 +1059,7 @@ bool QuicConnection::OnUnauthenticatedPublicHeader( QuicConnectionId client_connection_id = GetClientConnectionIdAsRecipient(header, perspective_); - if (client_connection_id == ClientConnectionId()) { + if (client_connection_id == default_path_.client_connection_id) { return true; } @@ -1158,7 +1082,7 @@ bool QuicConnection::OnUnauthenticatedPublicHeader( QUIC_DLOG(INFO) << ENDPOINT << "Ignoring packet from unexpected client connection ID " << client_connection_id << " instead of " - << ClientConnectionId(); + << default_path_.client_connection_id; return false; } @@ -1261,7 +1185,8 @@ void QuicConnection::OnDecryptedPacket(size_t /*length*/, default_path_.validated = true; stats_.address_validated_via_decrypting_packet = true; } - idle_network_detector_.OnPacketReceived(time_of_last_received_packet_); + idle_network_detector_.OnPacketReceived( + last_received_packet_info_.receipt_time); visitor_->OnPacketDecrypted(level); } @@ -1270,7 +1195,7 @@ QuicSocketAddress QuicConnection::GetEffectivePeerAddressFromCurrentPacket() const { // By default, the connection is not proxied, and the effective peer address // is the packet's source address, i.e. the direct peer address. - return last_packet_source_address_; + return last_received_packet_info_.source_address; } bool QuicConnection::OnPacketHeader(const QuicPacketHeader& header) { @@ -1300,7 +1225,7 @@ bool QuicConnection::OnPacketHeader(const QuicPacketHeader& header) { // for client connections. // TODO(fayang): only change peer addresses in application data packet // number space. - UpdatePeerAddress(last_packet_source_address_); + UpdatePeerAddress(last_received_packet_info_.source_address); default_path_.peer_address = GetEffectivePeerAddressFromCurrentPacket(); } } else { @@ -1338,19 +1263,20 @@ bool QuicConnection::OnPacketHeader(const QuicPacketHeader& header) { // last_packet_destination_connection_id_ has the advantage that it is // still present in the session map since the packet can be routed here // regardless of packet reordering. - if (IsDefaultPath(last_packet_destination_address_, + if (IsDefaultPath(last_received_packet_info_.destination_address, effective_peer_address)) { default_path_.server_connection_id = last_packet_destination_connection_id_; - } else if (IsAlternativePath(last_packet_destination_address_, - effective_peer_address)) { + } else if (IsAlternativePath( + last_received_packet_info_.destination_address, + effective_peer_address)) { alternative_path_.server_connection_id = last_packet_destination_connection_id_; } } - if (use_connection_id_on_default_path_ && - last_packet_destination_connection_id_ != ServerConnectionId() && + if (last_packet_destination_connection_id_ != + default_path_.server_connection_id && (!original_destination_connection_id_.has_value() || last_packet_destination_connection_id_ != *original_destination_connection_id_)) { @@ -1374,9 +1300,15 @@ bool QuicConnection::OnPacketHeader(const QuicPacketHeader& header) { // Record packet receipt to populate ack info before processing stream // frames, since the processing may result in sending a bundled ack. + QuicTime receipt_time = idle_network_detector_.time_of_last_received_packet(); + if (reset_per_packet_state_for_undecryptable_packets_ && + SupportsMultiplePacketNumberSpaces()) { + QUIC_RELOADABLE_FLAG_COUNT_N( + quic_reset_per_packet_state_for_undecryptable_packets, 2, 2); + receipt_time = last_received_packet_info_.receipt_time; + } uber_received_packet_manager_.RecordPacketReceived( - last_decrypted_packet_level_, last_header_, - idle_network_detector_.time_of_last_received_packet()); + last_decrypted_packet_level_, last_header_, receipt_time); if (EnforceAntiAmplificationLimit() && !IsHandshakeConfirmed() && !header.retry_token.empty() && visitor_->ValidateToken(header.retry_token)) { @@ -1422,6 +1354,9 @@ bool QuicConnection::OnStreamFrame(const QuicStreamFrame& frame) { ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); return false; } + // TODO(fayang): Consider moving UpdatePacketContent and + // MaybeUpdateAckTimeout to a stand-alone function instead of calling them for + // all frames. MaybeUpdateAckTimeout(); visitor_->OnStreamFrame(frame); stats_.stream_bytes_received += frame.data_length; @@ -1730,7 +1665,9 @@ bool QuicConnection::OnStopSendingFrame(const QuicStopSendingFrame& frame) { QUIC_DLOG(INFO) << ENDPOINT << "STOP_SENDING frame received for stream: " << frame.stream_id << " with error: " << frame.ietf_error_code; - + if (add_missing_update_ack_timeout_) { + MaybeUpdateAckTimeout(); + } visitor_->OnStopSendingFrame(frame); return connected_; } @@ -1777,7 +1714,7 @@ bool QuicConnection::OnPathChallengeFrame(const QuicPathChallengeFrame& frame) { group_path_response_and_challenge_sending_closer_ ? nullptr : std::make_unique<QuicPacketCreator::ScopedPeerAddressContext>( - &packet_creator_, last_packet_source_address_, + &packet_creator_, last_received_packet_info_.source_address, /*update_connection_id=*/false); if (!OnPathChallengeFrameInternal(frame)) { return false; @@ -1802,12 +1739,12 @@ bool QuicConnection::OnPathChallengeFrameInternal( GetEffectivePeerAddressFromCurrentPacket(); if (group_path_response_and_challenge_sending_closer_) { QuicConnectionId client_cid, server_cid; - FindOnPathConnectionIds(last_packet_destination_address_, + FindOnPathConnectionIds(last_received_packet_info_.destination_address, current_effective_peer_address, &client_cid, &server_cid); context = std::make_unique<QuicPacketCreator::ScopedPeerAddressContext>( - &packet_creator_, last_packet_source_address_, client_cid, server_cid, - connection_migration_use_new_cid_); + &packet_creator_, last_received_packet_info_.source_address, client_cid, + server_cid, connection_migration_use_new_cid_); } if (should_proactively_validate_peer_address_on_path_challenge_) { QUIC_RELOADABLE_FLAG_COUNT( @@ -1821,7 +1758,8 @@ bool QuicConnection::OnPathChallengeFrameInternal( << current_effective_peer_address; QUIC_CODE_COUNT_N(quic_kick_off_client_address_validation, 2, 6); ValidatePath(std::make_unique<ReversePathValidationContext>( - default_path_.self_address, last_packet_source_address_, + default_path_.self_address, + last_received_packet_info_.source_address, current_effective_peer_address, this), std::make_unique<ReversePathValidationResultDelegate>( this, peer_address())); @@ -1840,9 +1778,9 @@ bool QuicConnection::OnPathChallengeFrameInternal( // Queue or send PATH_RESPONSE. Send PATH_RESPONSE to the source address of // the current incoming packet, even if it's not the default path or the // alternative path. - const bool success = - SendPathResponse(frame.data_buffer, last_packet_source_address_, - current_effective_peer_address); + const bool success = SendPathResponse( + frame.data_buffer, last_received_packet_info_.source_address, + current_effective_peer_address); if (GetQuicReloadableFlag(quic_drop_unsent_path_response)) { QUIC_RELOADABLE_FLAG_COUNT(quic_drop_unsent_path_response); } @@ -1851,7 +1789,7 @@ bool QuicConnection::OnPathChallengeFrameInternal( if (!GetQuicReloadableFlag(quic_drop_unsent_path_response)) { // Queue the payloads to re-try later. pending_path_challenge_payloads_.push_back( - {frame.data_buffer, last_packet_source_address_}); + {frame.data_buffer, last_received_packet_info_.source_address}); } } // TODO(b/150095588): change the stats to @@ -1876,8 +1814,8 @@ bool QuicConnection::OnPathResponseFrame(const QuicPathResponseFrame& frame) { MaybeUpdateAckTimeout(); if (use_path_validator_) { QUIC_RELOADABLE_FLAG_COUNT_N(quic_pass_path_response_to_validator, 1, 4); - path_validator_.OnPathResponse(frame.data_buffer, - last_packet_destination_address_); + path_validator_.OnPathResponse( + frame.data_buffer, last_received_packet_info_.destination_address); } else { if (!transmitted_connectivity_probe_payload_ || *transmitted_connectivity_probe_payload_ != frame.data_buffer) { @@ -1953,6 +1891,9 @@ bool QuicConnection::OnMaxStreamsFrame(const QuicMaxStreamsFrame& frame) { if (debug_visitor_ != nullptr) { debug_visitor_->OnMaxStreamsFrame(frame); } + if (add_missing_update_ack_timeout_) { + MaybeUpdateAckTimeout(); + } return visitor_->OnMaxStreamsFrame(frame) && connected_; } @@ -1969,6 +1910,9 @@ bool QuicConnection::OnStreamsBlockedFrame( if (debug_visitor_ != nullptr) { debug_visitor_->OnStreamsBlockedFrame(frame); } + if (add_missing_update_ack_timeout_) { + MaybeUpdateAckTimeout(); + } return visitor_->OnStreamsBlockedFrame(frame) && connected_; } @@ -2018,12 +1962,14 @@ bool QuicConnection::OnWindowUpdateFrame(const QuicWindowUpdateFrame& frame) { } void QuicConnection::OnClientConnectionIdAvailable() { - QUIC_RELOADABLE_FLAG_COUNT_N(quic_connection_migration_use_new_cid_v2, 3, 5); QUICHE_DCHECK(perspective_ == Perspective::IS_SERVER); if (!peer_issued_cid_manager_->HasUnusedConnectionId()) { return; } if (default_path_.client_connection_id.IsEmpty()) { + // Count client connection ID patched onto the default path. + QUIC_RELOADABLE_FLAG_COUNT_N(quic_connection_migration_use_new_cid_v2, 3, + 6); const QuicConnectionIdData* unused_cid_data = peer_issued_cid_manager_->ConsumeOneUnusedConnectionId(); QUIC_DVLOG(1) << ENDPOINT << "Patch connection ID " @@ -2039,6 +1985,9 @@ void QuicConnection::OnClientConnectionIdAvailable() { } if (alternative_path_.peer_address.IsInitialized() && alternative_path_.client_connection_id.IsEmpty()) { + // Count client connection ID patched onto the alternative path. + QUIC_RELOADABLE_FLAG_COUNT_N(quic_connection_migration_use_new_cid_v2, 4, + 6); const QuicConnectionIdData* unused_cid_data = peer_issued_cid_manager_->ConsumeOneUnusedConnectionId(); QUIC_DVLOG(1) << ENDPOINT << "Patch connection ID " @@ -2050,6 +1999,28 @@ void QuicConnection::OnClientConnectionIdAvailable() { } } +bool QuicConnection::ShouldSetRetransmissionAlarmOnPacketSent( + bool in_flight, EncryptionLevel level) const { + if (!retransmission_alarm_->IsSet()) { + return true; + } + if (!in_flight) { + return false; + } + + if (!SupportsMultiplePacketNumberSpaces()) { + return true; + } + // Before handshake gets confirmed, do not re-arm PTO timer on application + // data. Think about this scenario: on the client side, the CHLO gets + // acknowledged and the SHLO is not received yet. The PTO alarm is set when + // the CHLO acknowledge is received (and there is no in flight INITIAL + // packet). Re-arming PTO alarm on 0-RTT packet would keep postponing the PTO + // alarm. + return IsHandshakeConfirmed() || level == ENCRYPTION_INITIAL || + level == ENCRYPTION_HANDSHAKE; +} + bool QuicConnection::OnNewConnectionIdFrameInner( const QuicNewConnectionIdFrame& frame) { QUICHE_DCHECK(support_multiple_connection_ids_); @@ -2068,11 +2039,14 @@ bool QuicConnection::OnNewConnectionIdFrameInner( ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); return false; } - if (use_connection_id_on_default_path_ && - perspective_ == Perspective::IS_SERVER) { + if (perspective_ == Perspective::IS_SERVER) { OnClientConnectionIdAvailable(); } QUIC_RELOADABLE_FLAG_COUNT_N(quic_connection_support_multiple_cids_v4, 1, 2); + if (ack_cid_frames_) { + QUIC_RELOADABLE_FLAG_COUNT_N(quic_ack_cid_frames, 1, 2); + MaybeUpdateAckTimeout(); + } return true; } @@ -2108,8 +2082,7 @@ bool QuicConnection::OnRetireConnectionIdFrame( if (debug_visitor_ != nullptr) { debug_visitor_->OnRetireConnectionIdFrame(frame); } - if (use_connection_id_on_default_path_ && - !connection_migration_use_new_cid_) { + if (!connection_migration_use_new_cid_) { // Do not respond to RetireConnectionId frame. return true; } @@ -2132,6 +2105,12 @@ bool QuicConnection::OnRetireConnectionIdFrame( return false; } QUIC_RELOADABLE_FLAG_COUNT_N(quic_connection_support_multiple_cids_v4, 2, 2); + // Count successfully received RETIRE_CONNECTION_ID frames. + QUIC_RELOADABLE_FLAG_COUNT_N(quic_connection_migration_use_new_cid_v2, 5, 6); + if (ack_cid_frames_) { + QUIC_RELOADABLE_FLAG_COUNT_N(quic_ack_cid_frames, 2, 2); + MaybeUpdateAckTimeout(); + } return true; } @@ -2311,8 +2290,8 @@ void QuicConnection::MaybeRespondToConnectivityProbingOrMigration() { if (perspective_ == Perspective::IS_CLIENT) { // This node is a client, notify that a speculative connectivity probing // packet has been received anyway. - visitor_->OnPacketReceived(last_packet_destination_address_, - last_packet_source_address_, + visitor_->OnPacketReceived(last_received_packet_info_.destination_address, + last_received_packet_info_.source_address, /*is_connectivity_probe=*/false); return; } @@ -2325,29 +2304,31 @@ void QuicConnection::MaybeRespondToConnectivityProbingOrMigration() { // If the packet contains PATH CHALLENGE, send appropriate RESPONSE. // There was at least one PATH CHALLENGE in the received packet, // Generate the required PATH RESPONSE. - SendGenericPathProbePacket(nullptr, last_packet_source_address_, + SendGenericPathProbePacket(nullptr, + last_received_packet_info_.source_address, /* is_response=*/true); return; } } else { if (IsCurrentPacketConnectivityProbing()) { - visitor_->OnPacketReceived(last_packet_destination_address_, - last_packet_source_address_, + visitor_->OnPacketReceived(last_received_packet_info_.destination_address, + last_received_packet_info_.source_address, /*is_connectivity_probe=*/true); return; } if (perspective_ == Perspective::IS_CLIENT) { // This node is a client, notify that a speculative connectivity probing // packet has been received anyway. - QUIC_DVLOG(1) << ENDPOINT - << "Received a speculative connectivity probing packet for " - << GetServerConnectionIdAsRecipient(last_header_, - perspective_) - << " from ip:port: " - << last_packet_source_address_.ToString() << " to ip:port: " - << last_packet_destination_address_.ToString(); - visitor_->OnPacketReceived(last_packet_destination_address_, - last_packet_source_address_, + QUIC_DVLOG(1) + << ENDPOINT + << "Received a speculative connectivity probing packet for " + << GetServerConnectionIdAsRecipient(last_header_, perspective_) + << " from ip:port: " + << last_received_packet_info_.source_address.ToString() + << " to ip:port: " + << last_received_packet_info_.destination_address.ToString(); + visitor_->OnPacketReceived(last_received_packet_info_.destination_address, + last_received_packet_info_.source_address, /*is_connectivity_probe=*/false); return; } @@ -2356,12 +2337,10 @@ void QuicConnection::MaybeRespondToConnectivityProbingOrMigration() { bool QuicConnection::IsValidStatelessResetToken( const StatelessResetToken& token) const { - if (use_connection_id_on_default_path_) { + QUICHE_DCHECK_EQ(perspective_, Perspective::IS_CLIENT); return default_path_.stateless_reset_token_received && - token == default_path_.stateless_reset_token; - } - return stateless_reset_token_received_ && - token == received_stateless_reset_token_; + QuicUtils::AreStatelessResetTokensEqual( + token, default_path_.stateless_reset_token); } void QuicConnection::OnAuthenticatedIetfStatelessResetPacket( @@ -2373,10 +2352,10 @@ void QuicConnection::OnAuthenticatedIetfStatelessResetPacket( if (use_path_validator_) { QUIC_RELOADABLE_FLAG_COUNT_N(quic_pass_path_response_to_validator, 4, 4); - if (!IsDefaultPath(last_packet_destination_address_, - last_packet_source_address_)) { + if (!IsDefaultPath(last_received_packet_info_.destination_address, + last_received_packet_info_.source_address)) { // This packet is received on a probing path. Do not close connection. - if (IsAlternativePath(last_packet_destination_address_, + if (IsAlternativePath(last_received_packet_info_.destination_address, GetEffectivePeerAddressFromCurrentPacket())) { QUIC_BUG_IF(quic_bug_12714_18, alternative_path_.validated) << "STATELESS_RESET received on alternate path after it's " @@ -2388,8 +2367,9 @@ void QuicConnection::OnAuthenticatedIetfStatelessResetPacket( } return; } - } else if (!visitor_->ValidateStatelessReset(last_packet_destination_address_, - last_packet_source_address_)) { + } else if (!visitor_->ValidateStatelessReset( + last_received_packet_info_.destination_address, + last_received_packet_info_.source_address)) { // This packet is received on a probing path. Do not close connection. return; } @@ -2570,15 +2550,12 @@ QuicConsumedData QuicConnection::SendStreamData(QuicStreamId id, } if (perspective_ == Perspective::IS_SERVER && version().CanSendCoalescedPackets() && !IsHandshakeConfirmed()) { - if (GetQuicReloadableFlag(quic_donot_pto_half_rtt_data)) { - QUIC_RELOADABLE_FLAG_COUNT(quic_donot_pto_half_rtt_data); - if (in_on_retransmission_time_out_ && - coalesced_packet_.NumberOfPackets() == 0u) { - // PTO fires while handshake is not confirmed. Do not preempt handshake - // data with stream data. - QUIC_CODE_COUNT(quic_try_to_send_half_rtt_data_when_pto_fires); - return QuicConsumedData(0, false); - } + if (in_on_retransmission_time_out_ && + coalesced_packet_.NumberOfPackets() == 0u) { + // PTO fires while handshake is not confirmed. Do not preempt handshake + // data with stream data. + QUIC_CODE_COUNT(quic_try_to_send_half_rtt_data_when_pto_fires); + return QuicConsumedData(0, false); } if (coalesced_packet_.ContainsPacketOfEncryptionLevel(ENCRYPTION_INITIAL) && coalesced_packet_.NumberOfPackets() == 1u) { @@ -2739,25 +2716,38 @@ void QuicConnection::OnUndecryptablePacket(const QuicEncryptedPacket& packet, } bool QuicConnection::ShouldEnqueueUnDecryptablePacket( - EncryptionLevel decryption_level, - bool has_decryption_key) const { - if (encryption_level_ == ENCRYPTION_FORWARD_SECURE) { + EncryptionLevel decryption_level, bool has_decryption_key) const { + if (!GetQuicReloadableFlag(quic_queue_until_handshake_complete) && + encryption_level_ == ENCRYPTION_FORWARD_SECURE) { // We do not expect to install any further keys. return false; } - if (undecryptable_packets_.size() >= max_undecryptable_packets_) { - // We do not queue more than max_undecryptable_packets_ packets. - return false; - } if (has_decryption_key) { // We already have the key for this decryption level, therefore no // future keys will allow it be decrypted. return false; } + if (GetQuicReloadableFlag(quic_queue_until_handshake_complete) && + IsHandshakeComplete()) { + QUICHE_RELOADABLE_FLAG_COUNT(quic_queue_until_handshake_complete); + // We do not expect to install any further keys. + return false; + } + if (undecryptable_packets_.size() >= max_undecryptable_packets_) { + // We do not queue more than max_undecryptable_packets_ packets. + return false; + } if (version().KnowsWhichDecrypterToUse() && - decryption_level <= encryption_level_) { - // On versions that know which decrypter to use, we install keys in order - // so we will not get newer keys for lower encryption levels. + decryption_level == ENCRYPTION_INITIAL) { + // When the corresponding decryption key is not available, all + // non-Initial packets should be buffered until the handshake is complete. + return false; + } + if (perspective_ == Perspective::IS_CLIENT && version().UsesTls() && + decryption_level == ENCRYPTION_ZERO_RTT) { + // Only clients send Zero RTT packets in IETF QUIC. + QUIC_PEER_BUG(quic_peer_bug_client_received_zero_rtt) + << "Client received a Zero RTT packet, not buffering."; return false; } return true; @@ -2812,18 +2802,17 @@ void QuicConnection::ProcessUdpPacket(const QuicSocketAddress& self_address, if (debug_visitor_ != nullptr) { debug_visitor_->OnPacketReceived(self_address, peer_address, packet); } - current_incoming_packet_received_bytes_counted_ = false; + last_received_packet_info_ = + ReceivedPacketInfo(self_address, peer_address, packet.receipt_time()); last_size_ = packet.length(); current_packet_data_ = packet.data(); - last_packet_destination_address_ = self_address; - last_packet_source_address_ = peer_address; if (!default_path_.self_address.IsInitialized()) { - default_path_.self_address = last_packet_destination_address_; + default_path_.self_address = last_received_packet_info_.destination_address; } if (!direct_peer_address_.IsInitialized()) { - UpdatePeerAddress(last_packet_source_address_); + UpdatePeerAddress(last_received_packet_info_.source_address); } if (!default_path_.peer_address.IsInitialized()) { @@ -2844,11 +2833,11 @@ void QuicConnection::ProcessUdpPacket(const QuicSocketAddress& self_address, if (EnforceAntiAmplificationLimit()) { default_path_.bytes_received_before_address_validation += last_size_; } - } else if (IsDefaultPath(last_packet_destination_address_, - last_packet_source_address_) && + } else if (IsDefaultPath(last_received_packet_info_.destination_address, + last_received_packet_info_.source_address) && EnforceAntiAmplificationLimit()) { QUIC_CODE_COUNT_N(quic_count_bytes_on_alternative_path_seperately, 1, 5); - current_incoming_packet_received_bytes_counted_ = true; + last_received_packet_info_.received_bytes_counted = true; default_path_.bytes_received_before_address_validation += last_size_; } @@ -2860,10 +2849,9 @@ void QuicConnection::ProcessUdpPacket(const QuicSocketAddress& self_address, << " too far from current time:" << clock_->ApproximateNow().ToDebuggingValue(); } - time_of_last_received_packet_ = packet.receipt_time(); QUIC_DVLOG(1) << ENDPOINT << "time of last received packet: " << packet.receipt_time().ToDebuggingValue() << " from peer " - << last_packet_source_address_; + << last_received_packet_info_.source_address; ScopedPacketFlusher flusher(this); if (!framer_.ProcessPacket(packet)) { @@ -2897,9 +2885,7 @@ void QuicConnection::ProcessUdpPacket(const QuicSocketAddress& self_address, } } - const bool processed = MaybeProcessCoalescedPackets(); - if (!donot_write_mid_packet_processing_ || !processed) { - QUIC_RELOADABLE_FLAG_COUNT_N(quic_donot_write_mid_packet_processing, 3, 3); + if (!MaybeProcessCoalescedPackets()) { MaybeProcessUndecryptablePackets(); MaybeSendInResponseToPacket(); } @@ -2980,7 +2966,7 @@ void QuicConnection::OnCanWrite() { } void QuicConnection::WriteIfNotBlocked() { - if (donot_write_mid_packet_processing_ && framer().is_processing_packet()) { + if (framer().is_processing_packet()) { QUIC_BUG(connection_write_mid_packet_processing) << ENDPOINT << "Tried to write in mid of packet processing"; return; @@ -2990,14 +2976,14 @@ void QuicConnection::WriteIfNotBlocked() { } } -void QuicConnection::SetServerConnectionId( - const QuicConnectionId& server_connection_id) { - if (use_connection_id_on_default_path_) { - QUIC_RELOADABLE_FLAG_COUNT_N(quic_use_connection_id_on_default_path_v2, 2, - 3); - default_path_.server_connection_id = server_connection_id; - } else { - server_connection_id_ = server_connection_id; +void QuicConnection::MaybeClearQueuedPacketsOnPathChange() { + if (connection_migration_use_new_cid_ && + peer_issued_cid_manager_ != nullptr && HasQueuedPackets()) { + // Discard packets serialized with the connection ID on the old code path. + // It is possible to clear queued packets only if connection ID changes. + // However, the case where connection ID is unchanged and queued packets are + // non-empty is quite rare. + ClearQueuedPackets(); } } @@ -3011,11 +2997,12 @@ void QuicConnection::ReplaceInitialServerConnectionId( if (peer_issued_cid_manager_ != nullptr) { QUIC_BUG_IF(quic_bug_12714_22, !peer_issued_cid_manager_->IsConnectionIdActive( - ServerConnectionId())) + default_path_.server_connection_id)) << "Connection ID replaced header is no longer active. old id: " - << ServerConnectionId() << " new_id: " << new_server_connection_id; - peer_issued_cid_manager_->ReplaceConnectionId(ServerConnectionId(), - new_server_connection_id); + << default_path_.server_connection_id + << " new_id: " << new_server_connection_id; + peer_issued_cid_manager_->ReplaceConnectionId( + default_path_.server_connection_id, new_server_connection_id); } else { peer_issued_cid_manager_ = std::make_unique<QuicPeerIssuedConnectionIdManager>( @@ -3024,8 +3011,8 @@ void QuicConnection::ReplaceInitialServerConnectionId( } } } - SetServerConnectionId(new_server_connection_id); - packet_creator_.SetServerConnectionId(ServerConnectionId()); + default_path_.server_connection_id = new_server_connection_id; + packet_creator_.SetServerConnectionId(default_path_.server_connection_id); } void QuicConnection::FindMatchingOrNewClientConnectionIdOrToken( @@ -3035,9 +3022,6 @@ void QuicConnection::FindMatchingOrNewClientConnectionIdOrToken( QuicConnectionId* client_connection_id, bool* stateless_reset_token_received, StatelessResetToken* stateless_reset_token) { - if (!use_connection_id_on_default_path_) { - return; - } QUICHE_DCHECK(perspective_ == Perspective::IS_SERVER); if (peer_issued_cid_manager_ == nullptr || server_connection_id == default_path.server_connection_id) { @@ -3097,9 +3081,10 @@ void QuicConnection::SetDefaultPathState(PathState new_path_state) { bool QuicConnection::ProcessValidatedPacket(const QuicPacketHeader& header) { if (perspective_ == Perspective::IS_CLIENT && version().HasIetfQuicFrames() && direct_peer_address_.IsInitialized() && - last_packet_source_address_.IsInitialized() && - direct_peer_address_ != last_packet_source_address_ && - !visitor_->IsKnownServerAddress(last_packet_source_address_)) { + last_received_packet_info_.source_address.IsInitialized() && + direct_peer_address_ != last_received_packet_info_.source_address && + !visitor_->IsKnownServerAddress( + last_received_packet_info_.source_address)) { // TODO(haoyuewang) Revisit this when preferred_address transport parameter // is used on the client side. // Discard packets received from unseen server addresses. @@ -3108,13 +3093,15 @@ bool QuicConnection::ProcessValidatedPacket(const QuicPacketHeader& header) { if (perspective_ == Perspective::IS_SERVER && default_path_.self_address.IsInitialized() && - last_packet_destination_address_.IsInitialized() && - default_path_.self_address != last_packet_destination_address_) { + last_received_packet_info_.destination_address.IsInitialized() && + default_path_.self_address != + last_received_packet_info_.destination_address) { // Allow change between pure IPv4 and equivalent mapped IPv4 address. if (default_path_.self_address.port() != - last_packet_destination_address_.port() || + last_received_packet_info_.destination_address.port() || default_path_.self_address.host().Normalized() != - last_packet_destination_address_.host().Normalized()) { + last_received_packet_info_.destination_address.host() + .Normalized()) { if (!visitor_->AllowSelfAddressChange()) { CloseConnection( QUIC_ERROR_MIGRATING_ADDRESS, @@ -3123,24 +3110,24 @@ bool QuicConnection::ProcessValidatedPacket(const QuicPacketHeader& header) { return false; } } - default_path_.self_address = last_packet_destination_address_; + default_path_.self_address = last_received_packet_info_.destination_address; } if (PacketCanReplaceServerConnectionId(header, perspective_) && - ServerConnectionId() != header.source_connection_id) { + default_path_.server_connection_id != header.source_connection_id) { QUICHE_DCHECK_EQ(header.long_packet_type, INITIAL); if (server_connection_id_replaced_by_initial_) { QUIC_DLOG(ERROR) << ENDPOINT << "Refusing to replace connection ID " - << ServerConnectionId() << " with " + << default_path_.server_connection_id << " with " << header.source_connection_id; return false; } server_connection_id_replaced_by_initial_ = true; QUIC_DLOG(INFO) << ENDPOINT << "Replacing connection ID " - << ServerConnectionId() << " with " + << default_path_.server_connection_id << " with " << header.source_connection_id; if (!original_destination_connection_id_.has_value()) { - original_destination_connection_id_ = ServerConnectionId(); + original_destination_connection_id_ = default_path_.server_connection_id; } ReplaceInitialServerConnectionId(header.source_connection_id); } @@ -3569,7 +3556,7 @@ bool QuicConnection::WritePacket(SerializedPacket* packet) { legacy_version_encapsulation_sni_, absl::string_view(packet->encrypted_buffer, packet->encrypted_length), - ServerConnectionId(), framer_.creation_time(), + default_path_.server_connection_id, framer_.creation_time(), GetLimitedMaxPacketSize(long_term_mtu_), const_cast<char*>(packet->encrypted_buffer)); if (encapsulated_length != 0) { @@ -3759,8 +3746,15 @@ bool QuicConnection::WritePacket(SerializedPacket* packet) { return true; } } - - if (in_flight || !retransmission_alarm_->IsSet()) { + if (GetQuicReloadableFlag( + quic_donot_rearm_pto_on_application_data_during_handshake)) { + QUIC_RELOADABLE_FLAG_COUNT( + quic_donot_rearm_pto_on_application_data_during_handshake); + if (ShouldSetRetransmissionAlarmOnPacketSent(in_flight, + packet->encryption_level)) { + SetRetransmissionAlarm(); + } + } else if (in_flight || !retransmission_alarm_->IsSet()) { SetRetransmissionAlarm(); } SetPingAlarm(); @@ -4078,13 +4072,14 @@ void QuicConnection::OnPathMtuIncreased(QuicPacketLength packet_size) { std::unique_ptr<QuicSelfIssuedConnectionIdManager> QuicConnection::MakeSelfIssuedConnectionIdManager() { QUICHE_DCHECK((perspective_ == Perspective::IS_CLIENT && - !ClientConnectionId().IsEmpty()) || + !default_path_.client_connection_id.IsEmpty()) || (perspective_ == Perspective::IS_SERVER && - !ServerConnectionId().IsEmpty())); + !default_path_.server_connection_id.IsEmpty())); return std::make_unique<QuicSelfIssuedConnectionIdManager>( kMinNumOfActiveConnectionIds, - perspective_ == Perspective::IS_CLIENT ? ClientConnectionId() - : ServerConnectionId(), + perspective_ == Perspective::IS_CLIENT + ? default_path_.client_connection_id + : default_path_.server_connection_id, clock_, alarm_factory_, this); } @@ -4149,9 +4144,7 @@ void QuicConnection::OnPingTimeout() { !visitor_->ShouldKeepConnectionAlive()) { return; } - SendPingAtLevel(use_encryption_level_context_ - ? framer().GetEncryptionLevelToSendApplicationData() - : encryption_level_); + SendPingAtLevel(framer().GetEncryptionLevelToSendApplicationData()); } void QuicConnection::SendAck() { @@ -4218,7 +4211,7 @@ void QuicConnection::OnRetransmissionTimeout() { blackhole_detector_.IsDetectionInProgress()) { // Stop detection in quiescence. QUICHE_DCHECK_EQ(QuicSentPacketManager::LOSS_MODE, retransmission_mode); - blackhole_detector_.StopDetection(); + blackhole_detector_.StopDetection(/*permanent=*/false); } WriteIfNotBlocked(); @@ -4249,15 +4242,28 @@ void QuicConnection::OnRetransmissionTimeout() { << retransmission_mode << ", send PING"; QUICHE_DCHECK_LT(0u, sent_packet_manager_.pending_timer_transmission_count()); - EncryptionLevel level = encryption_level_; - PacketNumberSpace packet_number_space = NUM_PACKET_NUMBER_SPACES; - if (SupportsMultiplePacketNumberSpaces() && - sent_packet_manager_ - .GetEarliestPacketSentTimeForPto(&packet_number_space) - .IsInitialized()) { - level = QuicUtils::GetEncryptionLevel(packet_number_space); + if (SupportsMultiplePacketNumberSpaces()) { + // Based on https://datatracker.ietf.org/doc/html/rfc9002#appendix-A.9 + PacketNumberSpace packet_number_space; + if (sent_packet_manager_ + .GetEarliestPacketSentTimeForPto(&packet_number_space) + .IsInitialized()) { + SendPingAtLevel(QuicUtils::GetEncryptionLevel(packet_number_space)); + } else { + // The client must PTO when there is nothing in flight if the server + // could be blocked from sending by the amplification limit + QUICHE_DCHECK_EQ(Perspective::IS_CLIENT, perspective_); + if (framer_.HasEncrypterOfEncryptionLevel(ENCRYPTION_HANDSHAKE)) { + SendPingAtLevel(ENCRYPTION_HANDSHAKE); + } else if (framer_.HasEncrypterOfEncryptionLevel(ENCRYPTION_INITIAL)) { + SendPingAtLevel(ENCRYPTION_INITIAL); + } else { + QUIC_BUG(quic_bug_no_pto) << "PTO fired but nothing was sent."; + } + } + } else { + SendPingAtLevel(encryption_level_); } - SendPingAtLevel(level); } if (retransmission_mode == QuicSentPacketManager::PTO_MODE) { sent_packet_manager_.AdjustPendingTimerTransmissions(); @@ -4417,9 +4423,14 @@ void QuicConnection::QueueUndecryptablePacket( } } QUIC_DVLOG(1) << ENDPOINT << "Queueing undecryptable packet."; - undecryptable_packets_.emplace_back(packet, decryption_level); + undecryptable_packets_.emplace_back(packet, decryption_level, + last_received_packet_info_); if (perspective_ == Perspective::IS_CLIENT) { - SetRetransmissionAlarm(); + if (!retransmission_alarm_->IsSet() || + GetRetransmissionDeadline() < retransmission_alarm_->deadline()) { + // Re-arm PTO only if we can make it sooner to speed up recovery. + SetRetransmissionAlarm(); + } } } @@ -4445,7 +4456,19 @@ void QuicConnection::MaybeProcessUndecryptablePackets() { debug_visitor_->OnAttemptingToProcessUndecryptablePacket( undecryptable_packet->encryption_level); } - if (framer_.ProcessPacket(*undecryptable_packet->packet)) { + bool processed = false; + if (reset_per_packet_state_for_undecryptable_packets_) { + QUIC_RELOADABLE_FLAG_COUNT_N( + quic_reset_per_packet_state_for_undecryptable_packets, 1, 2); + last_received_packet_info_ = undecryptable_packet->packet_info; + last_size_ = undecryptable_packet->packet->length(); + current_packet_data_ = undecryptable_packet->packet->data(); + processed = framer_.ProcessPacket(*undecryptable_packet->packet); + current_packet_data_ = nullptr; + } else { + processed = framer_.ProcessPacket(*undecryptable_packet->packet); + } + if (processed) { QUIC_DVLOG(1) << ENDPOINT << "Processed undecryptable packet!"; iter = undecryptable_packets_.erase(iter); ++stats_.packets_processed; @@ -4479,7 +4502,14 @@ void QuicConnection::MaybeProcessUndecryptablePackets() { undecryptable_packets_.clear(); } if (perspective_ == Perspective::IS_CLIENT) { - SetRetransmissionAlarm(); + if (!retransmission_alarm_->IsSet() || undecryptable_packets_.empty() || + GetRetransmissionDeadline() < retransmission_alarm_->deadline()) { + // 1) If there is still undecryptable packet, only re-arm PTO to make it + // sooner to speed up recovery. + // 2) If all undecryptable packets get processed, re-arm (which may + // postpone) PTO since no immediate recovery is needed. + SetRetransmissionAlarm(); + } } } @@ -4514,11 +4544,7 @@ bool QuicConnection::MaybeProcessCoalescedPackets() { } if (processed) { MaybeProcessUndecryptablePackets(); - if (donot_write_mid_packet_processing_) { - QUIC_RELOADABLE_FLAG_COUNT_N(quic_donot_write_mid_packet_processing, 2, - 3); - MaybeSendInResponseToPacket(); - } + MaybeSendInResponseToPacket(); } return processed; } @@ -4571,12 +4597,8 @@ void QuicConnection::SendConnectionClosePacket( default_path_.server_connection_id, connection_migration_use_new_cid_); if (!SupportsMultiplePacketNumberSpaces()) { QUIC_DLOG(INFO) << ENDPOINT << "Sending connection close packet."; - if (!use_encryption_level_context_) { - SetDefaultEncryptionLevel(GetConnectionCloseEncryptionLevel()); - } - ScopedEncryptionLevelContext context( - use_encryption_level_context_ ? this : nullptr, - GetConnectionCloseEncryptionLevel()); + ScopedEncryptionLevelContext context(this, + GetConnectionCloseEncryptionLevel()); if (version().CanSendCoalescedPackets()) { coalesced_packet_.Clear(); } @@ -4607,7 +4629,6 @@ void QuicConnection::SendConnectionClosePacket( ClearQueuedPackets(); return; } - const EncryptionLevel current_encryption_level = encryption_level_; ScopedPacketFlusher flusher(this); // Now that the connection is being closed, discard any unsent packets @@ -4625,11 +4646,7 @@ void QuicConnection::SendConnectionClosePacket( } QUIC_DLOG(INFO) << ENDPOINT << "Sending connection close packet at level: " << level; - if (!use_encryption_level_context_) { - SetDefaultEncryptionLevel(level); - } - ScopedEncryptionLevelContext context( - use_encryption_level_context_ ? this : nullptr, level); + ScopedEncryptionLevelContext context(this, level); // Bundle an ACK of the corresponding packet number space for debugging // purpose. bool send_ack = error != QUIC_PACKET_WRITE_ERROR && @@ -4662,9 +4679,6 @@ void QuicConnection::SendConnectionClosePacket( // Since the connection is closing, if the connection close packets were not // sent, then they should be discarded. ClearQueuedPackets(); - if (!use_encryption_level_context_) { - SetDefaultEncryptionLevel(current_encryption_level); - } } void QuicConnection::TearDownLocalConnectionState( @@ -4712,15 +4726,15 @@ void QuicConnection::TearDownLocalConnectionState( void QuicConnection::CancelAllAlarms() { QUIC_DVLOG(1) << "Cancelling all QuicConnection alarms."; - ack_alarm_->Cancel(); - ping_alarm_->Cancel(); - retransmission_alarm_->Cancel(); - send_alarm_->Cancel(); - mtu_discovery_alarm_->Cancel(); - process_undecryptable_packets_alarm_->Cancel(); - discard_previous_one_rtt_keys_alarm_->Cancel(); - discard_zero_rtt_decryption_keys_alarm_->Cancel(); - blackhole_detector_.StopDetection(); + ack_alarm_->PermanentCancel(); + ping_alarm_->PermanentCancel(); + retransmission_alarm_->PermanentCancel(); + send_alarm_->PermanentCancel(); + mtu_discovery_alarm_->PermanentCancel(); + process_undecryptable_packets_alarm_->PermanentCancel(); + discard_previous_one_rtt_keys_alarm_->PermanentCancel(); + discard_zero_rtt_decryption_keys_alarm_->PermanentCancel(); + blackhole_detector_.StopDetection(/*permanent=*/true); idle_network_detector_.StopDetection(); } @@ -4754,6 +4768,9 @@ void QuicConnection::SetNetworkTimeouts(QuicTime::Delta handshake_timeout, } void QuicConnection::SetPingAlarm() { + if (!connected_) { + return; + } if (perspective_ == Perspective::IS_SERVER && initial_retransmittable_on_wire_timeout_.IsInfinite()) { // The PING alarm exists to support two features: @@ -5097,7 +5114,7 @@ bool QuicConnection::SendGenericPathProbePacket( QUIC_DLOG(INFO) << ENDPOINT << "Sending path probe packet for connection_id = " - << ServerConnectionId(); + << default_path_.server_connection_id; std::unique_ptr<SerializedPacket> probing_packet; if (!version().HasIetfQuicFrames()) { @@ -5142,7 +5159,7 @@ bool QuicConnection::WritePacketUsingWriter( const QuicTime packet_send_time = clock_->Now(); QUIC_DVLOG(2) << ENDPOINT << "Sending path probe packet for server connection ID " - << ServerConnectionId() << std::endl + << default_path_.server_connection_id << std::endl << quiche::QuicheTextUtils::HexDump(absl::string_view( packet->encrypted_buffer, packet->encrypted_length)); WriteResult result = writer->WritePacket( @@ -5277,11 +5294,20 @@ void QuicConnection::StartEffectivePeerMigration(AddressChangeType type) { QUIC_CODE_COUNT_N(quic_server_reverse_validate_new_path3, 3, 6); if (type == NO_CHANGE) { - UpdatePeerAddress(last_packet_source_address_); + UpdatePeerAddress(last_received_packet_info_.source_address); QUIC_BUG(quic_bug_10511_36) << "EffectivePeerMigration started without address change."; return; } + if (packet_creator_.HasPendingFrames()) { + QUIC_BUG(bug_731_2) + << "Starts effective peer migration with pending frame types: " + << packet_creator_.GetPendingFramesInfo() << ". Address change type is " + << AddressChangeTypeToString(type) + << ". Current frame type: " << framer_.current_received_frame_type() + << ". Previous frame type: " + << framer_.previously_received_frame_type(); + } // Action items: // 1. Switch congestion controller; @@ -5302,6 +5328,7 @@ void QuicConnection::StartEffectivePeerMigration(AddressChangeType type) { const QuicSocketAddress previous_direct_peer_address = direct_peer_address_; PathState previous_default_path = std::move(default_path_); active_effective_peer_migration_type_ = type; + MaybeClearQueuedPacketsOnPathChange(); OnConnectionMigration(); // Update congestion controller if the address change type is not PORT_CHANGE. @@ -5338,9 +5365,18 @@ void QuicConnection::StartEffectivePeerMigration(AddressChangeType type) { } // Update to the new peer address. - UpdatePeerAddress(last_packet_source_address_); + if (packet_creator_.HasPendingFrames()) { + QUIC_BUG(bug_731_1) + << "Starts effective peer migration with pending frame types: " + << packet_creator_.GetPendingFramesInfo() << ". Address change type is " + << AddressChangeTypeToString(type) + << ". Current frame type: " << framer_.current_received_frame_type() + << ". Previous frame type: " + << framer_.previously_received_frame_type(); + } + UpdatePeerAddress(last_received_packet_info_.source_address); // Update the default path. - if (IsAlternativePath(last_packet_destination_address_, + if (IsAlternativePath(last_received_packet_info_.destination_address, current_effective_peer_address)) { SetDefaultPathState(std::move(alternative_path_)); } else { @@ -5351,10 +5387,11 @@ void QuicConnection::StartEffectivePeerMigration(AddressChangeType type) { previous_default_path, alternative_path_, last_packet_destination_connection_id_, &client_connection_id, &stateless_reset_token_received, &stateless_reset_token); - SetDefaultPathState(PathState( - last_packet_destination_address_, current_effective_peer_address, - client_connection_id, last_packet_destination_connection_id_, - stateless_reset_token_received, stateless_reset_token)); + SetDefaultPathState( + PathState(last_received_packet_info_.destination_address, + current_effective_peer_address, client_connection_id, + last_packet_destination_connection_id_, + stateless_reset_token_received, stateless_reset_token)); // The path is considered validated if its peer IP address matches any // validated path's peer IP address. default_path_.validated = @@ -5363,10 +5400,10 @@ void QuicConnection::StartEffectivePeerMigration(AddressChangeType type) { alternative_path_.validated) || (previous_default_path.validated && type == PORT_CHANGE); } - if (!current_incoming_packet_received_bytes_counted_) { + if (!last_received_packet_info_.received_bytes_counted) { // Increment bytes counting on the new default path. default_path_.bytes_received_before_address_validation += last_size_; - current_incoming_packet_received_bytes_counted_ = true; + last_received_packet_info_.received_bytes_counted = true; } if (!previous_default_path.validated) { @@ -5547,19 +5584,19 @@ bool QuicConnection::UpdatePacketContent(QuicFrameType type) { QuicSocketAddress current_effective_peer_address = GetEffectivePeerAddressFromCurrentPacket(); if (!count_bytes_on_alternative_path_separately_ || - IsDefaultPath(last_packet_destination_address_, - last_packet_source_address_)) { + IsDefaultPath(last_received_packet_info_.destination_address, + last_received_packet_info_.source_address)) { return connected_; } QUIC_CODE_COUNT_N(quic_count_bytes_on_alternative_path_seperately, 3, 5); if (perspective_ == Perspective::IS_SERVER && type == PATH_CHALLENGE_FRAME && - !IsAlternativePath(last_packet_destination_address_, + !IsAlternativePath(last_received_packet_info_.destination_address, current_effective_peer_address)) { QUIC_DVLOG(1) << "The peer is probing a new path with effective peer address " << current_effective_peer_address << ", self address " - << last_packet_destination_address_; + << last_received_packet_info_.destination_address; if (!validate_client_addresses_) { QuicConnectionId client_cid; bool stateless_reset_token_received = false; @@ -5568,10 +5605,11 @@ bool QuicConnection::UpdatePacketContent(QuicFrameType type) { default_path_, alternative_path_, last_packet_destination_connection_id_, &client_cid, &stateless_reset_token_received, &stateless_reset_token); - alternative_path_ = PathState( - last_packet_destination_address_, current_effective_peer_address, - client_cid, last_packet_destination_connection_id_, - stateless_reset_token_received, stateless_reset_token); + alternative_path_ = + PathState(last_received_packet_info_.destination_address, + current_effective_peer_address, client_cid, + last_packet_destination_connection_id_, + stateless_reset_token_received, stateless_reset_token); } else if (!default_path_.validated) { QUIC_CODE_COUNT_N(quic_server_reverse_validate_new_path3, 4, 6); // Skip reverse path validation because either handshake hasn't @@ -5598,10 +5636,11 @@ bool QuicConnection::UpdatePacketContent(QuicFrameType type) { // Only override alternative path state upon receiving a PATH_CHALLENGE // from an unvalidated peer address, and the connection isn't validating // a recent peer migration. - alternative_path_ = PathState( - last_packet_destination_address_, current_effective_peer_address, - client_connection_id, last_packet_destination_connection_id_, - stateless_reset_token_received, stateless_reset_token); + alternative_path_ = + PathState(last_received_packet_info_.destination_address, + current_effective_peer_address, client_connection_id, + last_packet_destination_connection_id_, + stateless_reset_token_received, stateless_reset_token); if (group_path_response_and_challenge_sending_closer_) { should_proactively_validate_peer_address_on_path_challenge_ = true; } else { @@ -5613,12 +5652,12 @@ bool QuicConnection::UpdatePacketContent(QuicFrameType type) { QUIC_DVLOG(1) << "Proactively validate the effective peer address " << current_effective_peer_address; QUIC_CODE_COUNT_N(quic_kick_off_client_address_validation, 1, 6); - ValidatePath( - std::make_unique<ReversePathValidationContext>( - default_path_.self_address, last_packet_source_address_, - current_effective_peer_address, this), - std::make_unique<ReversePathValidationResultDelegate>( - this, peer_address())); + ValidatePath(std::make_unique<ReversePathValidationContext>( + default_path_.self_address, + last_received_packet_info_.source_address, + current_effective_peer_address, this), + std::make_unique<ReversePathValidationResultDelegate>( + this, peer_address())); } } } @@ -5659,15 +5698,17 @@ bool QuicConnection::UpdatePacketContent(QuicFrameType type) { << current_effective_peer_migration_type_; } else { is_current_packet_connectivity_probing_ = - (last_packet_source_address_ != peer_address()) || - (last_packet_destination_address_ != default_path_.self_address); + (last_received_packet_info_.source_address != peer_address()) || + (last_received_packet_info_.destination_address != + default_path_.self_address); QUIC_DLOG_IF(INFO, is_current_packet_connectivity_probing_) << ENDPOINT << "Detected connectivity probing packet. " - "last_packet_source_address_:" - << last_packet_source_address_ << ", peer_address_:" << peer_address() - << ", last_packet_destination_address_:" - << last_packet_destination_address_ + "last_packet_source_address:" + << last_received_packet_info_.source_address + << ", peer_address_:" << peer_address() + << ", last_packet_destination_address:" + << last_received_packet_info_.destination_address << ", default path self_address :" << default_path_.self_address; } return connected_; @@ -5676,7 +5717,7 @@ bool QuicConnection::UpdatePacketContent(QuicFrameType type) { current_packet_content_ = NOT_PADDED_PING; if (GetLargestReceivedPacket().IsInitialized() && last_header_.packet_number == GetLargestReceivedPacket()) { - UpdatePeerAddress(last_packet_source_address_); + UpdatePeerAddress(last_received_packet_info_.source_address); if (current_effective_peer_migration_type_ != NO_CHANGE) { // Start effective peer migration immediately when the current packet is // confirmed not a connectivity probing packet. @@ -5715,11 +5756,11 @@ void QuicConnection::MaybeStartIetfPeerMigration() { // TODO(fayang): When multiple packet number spaces is supported, only // start peer migration for the application data. if (!validate_client_addresses_) { - UpdatePeerAddress(last_packet_source_address_); + UpdatePeerAddress(last_received_packet_info_.source_address); } StartEffectivePeerMigration(current_effective_peer_migration_type_); } else { - UpdatePeerAddress(last_packet_source_address_); + UpdatePeerAddress(last_received_packet_info_.source_address); } } current_effective_peer_migration_type_ = NO_CHANGE; @@ -5746,7 +5787,7 @@ void QuicConnection::PostProcessAfterAckFrame(bool send_stop_waiting, // In case no new packets get acknowledged, it is possible packets are // detected lost because of time based loss detection. Cancel blackhole // detection if there is no packets in flight. - blackhole_detector_.StopDetection(); + blackhole_detector_.StopDetection(/*permanent=*/false); } if (send_stop_waiting) { @@ -5793,14 +5834,14 @@ void QuicConnection::ResetAckStates() { } MessageStatus QuicConnection::SendMessage(QuicMessageId message_id, - QuicMemSliceSpan message, + absl::Span<QuicMemSlice> message, bool flush) { if (!VersionSupportsMessageFrames(transport_version())) { QUIC_BUG(quic_bug_10511_38) << "MESSAGE frame is not supported for version " << transport_version(); return MESSAGE_STATUS_UNSUPPORTED; } - if (message.total_length() > GetCurrentLargestMessagePayload()) { + if (MemSliceSpanTotalSize(message) > GetCurrentLargestMessagePayload()) { return MESSAGE_STATUS_TOO_LARGE; } if (!connected_ || (!flush && !CanWrite(HAS_RETRANSMITTABLE_DATA))) { @@ -5899,8 +5940,6 @@ void QuicConnection::SendAllPendingAcks() { if (!earliest_ack_timeout.IsInitialized()) { return; } - // Latches current encryption level. - const EncryptionLevel current_encryption_level = encryption_level_; for (int8_t i = INITIAL_DATA; i <= APPLICATION_DATA; ++i) { const QuicTime ack_timeout = uber_received_packet_manager_.GetAckTimeout( static_cast<PacketNumberSpace>(i)); @@ -5920,14 +5959,8 @@ void QuicConnection::SendAllPendingAcks() { QUIC_DVLOG(1) << ENDPOINT << "Sending ACK of packet number space " << PacketNumberSpaceToString( static_cast<PacketNumberSpace>(i)); - // Switch to the appropriate encryption level. - if (!use_encryption_level_context_) { - SetDefaultEncryptionLevel( - QuicUtils::GetEncryptionLevel(static_cast<PacketNumberSpace>(i))); - } ScopedEncryptionLevelContext context( - use_encryption_level_context_ ? this : nullptr, - QuicUtils::GetEncryptionLevel(static_cast<PacketNumberSpace>(i))); + this, QuicUtils::GetEncryptionLevel(static_cast<PacketNumberSpace>(i))); QuicFrames frames; frames.push_back(uber_received_packet_manager_.GetUpdatedAckFrame( static_cast<PacketNumberSpace>(i), clock_->ApproximateNow())); @@ -5943,10 +5976,6 @@ void QuicConnection::SendAllPendingAcks() { } ResetAckStates(); } - if (!use_encryption_level_context_) { - // Restores encryption level. - SetDefaultEncryptionLevel(current_encryption_level); - } const QuicTime timeout = uber_received_packet_manager_.GetEarliestAckTimeout(); @@ -6138,6 +6167,9 @@ void QuicConnection::SetLargestReceivedPacketWithAck( } void QuicConnection::OnForwardProgressMade() { + if (GetQuicRestartFlag(quic_alarm_add_permanent_cancel) && !connected_) { + return; + } if (is_path_degrading_) { visitor_->OnForwardProgressMadeAfterPathDegrading(); is_path_degrading_ = false; @@ -6149,7 +6181,7 @@ void QuicConnection::OnForwardProgressMade() { GetPathMtuReductionDeadline()); } else { // Stop detections in quiecense. - blackhole_detector_.StopDetection(); + blackhole_detector_.StopDetection(/*permanent=*/false); } QUIC_BUG_IF(quic_bug_12714_35, default_enable_5rto_blackhole_detection_ && @@ -6257,13 +6289,7 @@ void QuicConnection::set_client_connection_id( << client_connection_id << " with unsupported version " << version(); return; } - if (use_connection_id_on_default_path_) { - QUIC_RELOADABLE_FLAG_COUNT_N(quic_use_connection_id_on_default_path_v2, 1, - 3); - default_path_.client_connection_id = client_connection_id; - } else { - client_connection_id_ = client_connection_id; - } + default_path_.client_connection_id = client_connection_id; client_connection_id_is_set_ = true; if (support_multiple_connection_ids_ && !client_connection_id.IsEmpty()) { @@ -6280,11 +6306,12 @@ void QuicConnection::set_client_connection_id( } } QUIC_DLOG(INFO) << ENDPOINT << "setting client connection ID to " - << ClientConnectionId() + << default_path_.client_connection_id << " for connection with server connection ID " - << ServerConnectionId(); - packet_creator_.SetClientConnectionId(ClientConnectionId()); - framer_.SetExpectedClientConnectionIdLength(ClientConnectionId().length()); + << default_path_.server_connection_id; + packet_creator_.SetClientConnectionId(default_path_.client_connection_id); + framer_.SetExpectedClientConnectionIdLength( + default_path_.client_connection_id.length()); } void QuicConnection::OnPathDegradingDetected() { @@ -6333,6 +6360,10 @@ void QuicConnection::OnIdleNetworkDetected() { "No recent network activity after ", duration.ToDebuggingValue(), ". Timeout:", idle_network_detector_.idle_network_timeout().ToDebuggingValue()); + if (perspective() == Perspective::IS_CLIENT && version().UsesTls() && + !IsHandshakeComplete()) { + absl::StrAppend(&error_details, UndecryptablePacketsInfo()); + } QUIC_DVLOG(1) << ENDPOINT << error_details; const bool has_consecutive_pto = sent_packet_manager_.GetConsecutiveTlpCount() > 0 || @@ -6362,9 +6393,10 @@ void QuicConnection::OnIdleNetworkDetected() { void QuicConnection::OnPeerIssuedConnectionIdRetired() { QUICHE_DCHECK(peer_issued_cid_manager_ != nullptr); - QuicConnectionId* default_path_cid = perspective_ == Perspective::IS_CLIENT - ? &ServerConnectionId() - : &ClientConnectionId(); + QuicConnectionId* default_path_cid = + perspective_ == Perspective::IS_CLIENT + ? &default_path_.server_connection_id + : &default_path_.client_connection_id; QuicConnectionId* alternative_path_cid = perspective_ == Perspective::IS_CLIENT ? &alternative_path_.server_connection_id @@ -6376,8 +6408,7 @@ void QuicConnection::OnPeerIssuedConnectionIdRetired() { *default_path_cid = QuicConnectionId(); } // TODO(haoyuewang) Handle the change for default_path_ & alternatvie_path_ - // via the same helper function after use_connection_id_on_default_path_ is - // default true. + // via the same helper function. if (default_path_cid->IsEmpty()) { // Try setting a new connection ID now such that subsequent // RetireConnectionId frames can be sent on the default path. @@ -6385,15 +6416,9 @@ void QuicConnection::OnPeerIssuedConnectionIdRetired() { peer_issued_cid_manager_->ConsumeOneUnusedConnectionId(); if (unused_connection_id_data != nullptr) { *default_path_cid = unused_connection_id_data->connection_id; - if (use_connection_id_on_default_path_) { - default_path_.stateless_reset_token = - unused_connection_id_data->stateless_reset_token; - default_path_.stateless_reset_token_received = true; - } else { - received_stateless_reset_token_ = - unused_connection_id_data->stateless_reset_token; - stateless_reset_token_received_ = true; - } + default_path_.stateless_reset_token = + unused_connection_id_data->stateless_reset_token; + default_path_.stateless_reset_token_received = true; if (perspective_ == Perspective::IS_CLIENT) { packet_creator_.SetServerConnectionId( unused_connection_id_data->connection_id); @@ -6403,25 +6428,23 @@ void QuicConnection::OnPeerIssuedConnectionIdRetired() { } } } - if (use_connection_id_on_default_path_) { - if (default_path_and_alternative_path_use_the_same_peer_connection_id) { - *alternative_path_cid = *default_path_cid; - alternative_path_.stateless_reset_token_received = - default_path_.stateless_reset_token_received; + if (default_path_and_alternative_path_use_the_same_peer_connection_id) { + *alternative_path_cid = *default_path_cid; + alternative_path_.stateless_reset_token_received = + default_path_.stateless_reset_token_received; + alternative_path_.stateless_reset_token = + default_path_.stateless_reset_token; + } else if (!alternative_path_cid->IsEmpty() && + !peer_issued_cid_manager_->IsConnectionIdActive( + *alternative_path_cid)) { + *alternative_path_cid = EmptyQuicConnectionId(); + const QuicConnectionIdData* unused_connection_id_data = + peer_issued_cid_manager_->ConsumeOneUnusedConnectionId(); + if (unused_connection_id_data != nullptr) { + *alternative_path_cid = unused_connection_id_data->connection_id; alternative_path_.stateless_reset_token = - default_path_.stateless_reset_token; - } else if (!alternative_path_cid->IsEmpty() && - !peer_issued_cid_manager_->IsConnectionIdActive( - *alternative_path_cid)) { - *alternative_path_cid = EmptyQuicConnectionId(); - const QuicConnectionIdData* unused_connection_id_data = - peer_issued_cid_manager_->ConsumeOneUnusedConnectionId(); - if (unused_connection_id_data != nullptr) { - *alternative_path_cid = unused_connection_id_data->connection_id; - alternative_path_.stateless_reset_token = - unused_connection_id_data->stateless_reset_token; - alternative_path_.stateless_reset_token_received = true; - } + unused_connection_id_data->stateless_reset_token; + alternative_path_.stateless_reset_token_received = true; } } @@ -6532,6 +6555,9 @@ bool QuicConnection::SendPathChallenge( const QuicSocketAddress& peer_address, const QuicSocketAddress& effective_peer_address, QuicPacketWriter* writer) { + if (!framer_.HasEncrypterOfEncryptionLevel(ENCRYPTION_FORWARD_SECURE)) { + return connected_; + } if (connection_migration_use_new_cid_) { { QuicConnectionId client_cid, server_cid; @@ -6653,9 +6679,12 @@ bool QuicConnection::SendPathResponse( const QuicPathFrameBuffer& data_buffer, const QuicSocketAddress& peer_address_to_send, const QuicSocketAddress& effective_peer_address) { + if (!framer_.HasEncrypterOfEncryptionLevel(ENCRYPTION_FORWARD_SECURE)) { + return false; + } QuicConnectionId client_cid, server_cid; if (connection_migration_use_new_cid_) { - FindOnPathConnectionIds(last_packet_destination_address_, + FindOnPathConnectionIds(last_received_packet_info_.destination_address, effective_peer_address, &client_cid, &server_cid); } // Send PATH_RESPONSE using the provided peer address. If the creator has been @@ -6665,7 +6694,8 @@ bool QuicConnection::SendPathResponse( &packet_creator_, peer_address_to_send, client_cid, server_cid, connection_migration_use_new_cid_); QUIC_DVLOG(1) << ENDPOINT << "Send PATH_RESPONSE to " << peer_address_to_send; - if (default_path_.self_address == last_packet_destination_address_) { + if (default_path_.self_address == + last_received_packet_info_.destination_address) { // The PATH_CHALLENGE is received on the default socket. Respond on the same // socket. return packet_creator_.AddPathResponseFrame(data_buffer); @@ -6676,8 +6706,9 @@ bool QuicConnection::SendPathResponse( // used to send PATH_RESPONSE. if (!path_validator_.HasPendingPathValidation() || path_validator_.GetContext()->self_address() != - last_packet_destination_address_) { - // Ignore this PATH_CHALLENGE if it's received from an uninteresting socket. + last_received_packet_info_.destination_address) { + // Ignore this PATH_CHALLENGE if it's received from an uninteresting + // socket. return true; } QuicPacketWriter* writer = path_validator_.GetContext()->WriterToUse(); @@ -6688,12 +6719,13 @@ bool QuicConnection::SendPathResponse( QUICHE_DCHECK_EQ(IsRetransmittable(*probing_packet), NO_RETRANSMITTABLE_DATA); QUIC_DVLOG(1) << ENDPOINT << "Send PATH_RESPONSE from alternative socket with address " - << last_packet_destination_address_; + << last_received_packet_info_.destination_address; // Ignore the return value to treat write error on the alternative writer as // part of network error. If the writer becomes blocked, wait for the peer to // send another PATH_CHALLENGE. WritePacketUsingWriter(std::move(probing_packet), writer, - last_packet_destination_address_, peer_address_to_send, + last_received_packet_info_.destination_address, + peer_address_to_send, /*measure_rtt=*/false); return true; } @@ -6766,7 +6798,6 @@ bool QuicConnection::UpdateConnectionIdsOnClientMigration( } void QuicConnection::RetirePeerIssuedConnectionIdsNoLongerOnPath() { - QUIC_RELOADABLE_FLAG_COUNT_N(quic_connection_migration_use_new_cid_v2, 4, 5); if (!connection_migration_use_new_cid_ || peer_issued_cid_manager_ == nullptr) { return; @@ -6786,13 +6817,20 @@ bool QuicConnection::MigratePath(const QuicSocketAddress& self_address, const QuicSocketAddress& peer_address, QuicPacketWriter* writer, bool owns_writer) { + QUICHE_DCHECK(perspective_ == Perspective::IS_CLIENT); if (!connected_) { + if (owns_writer) { + delete writer; + } return false; } QUICHE_DCHECK(!version().UsesHttp3() || IsHandshakeConfirmed()); if (connection_migration_use_new_cid_) { if (!UpdateConnectionIdsOnClientMigration(self_address, peer_address)) { + if (owns_writer) { + delete writer; + } return false; } if (packet_creator_.GetServerConnectionId().length() != @@ -6816,6 +6854,7 @@ bool QuicConnection::MigratePath(const QuicSocketAddress& self_address, SetSelfAddress(self_address); UpdatePeerAddress(peer_address); SetQuicPacketWriter(writer, owns_writer); + MaybeClearQueuedPacketsOnPathChange(); OnSuccessfulMigration(is_port_change); return true; } @@ -6825,13 +6864,19 @@ void QuicConnection::OnPathValidationFailureAtClient() { QUICHE_DCHECK(perspective_ == Perspective::IS_CLIENT); alternative_path_.Clear(); } + // The alarm to retire connection IDs no longer on paths is scheduled at the + // end of writing and reading packet. On path validation failure, there could + // be no packet to write or read. Hence the retirement alarm for the + // connection ID associated with the failed path needs to be proactively + // scheduled here. + RetirePeerIssuedConnectionIdsNoLongerOnPath(); } std::vector<QuicConnectionId> QuicConnection::GetActiveServerConnectionIds() const { if (!support_multiple_connection_ids_ || self_issued_cid_manager_ == nullptr) { - return {ServerConnectionId()}; + return {default_path_.server_connection_id}; } return self_issued_cid_manager_->GetUnretiredConnectionIds(); } @@ -6842,14 +6887,14 @@ void QuicConnection::CreateConnectionIdManager() { } if (perspective_ == Perspective::IS_CLIENT) { - if (!ServerConnectionId().IsEmpty()) { + if (!default_path_.server_connection_id.IsEmpty()) { peer_issued_cid_manager_ = std::make_unique<QuicPeerIssuedConnectionIdManager>( - kMinNumOfActiveConnectionIds, ServerConnectionId(), clock_, - alarm_factory_, this); + kMinNumOfActiveConnectionIds, default_path_.server_connection_id, + clock_, alarm_factory_, this); } } else { - if (!ServerConnectionId().IsEmpty()) { + if (!default_path_.server_connection_id.IsEmpty()) { self_issued_cid_manager_ = MakeSelfIssuedConnectionIdManager(); } } @@ -6905,20 +6950,20 @@ void QuicConnection::MaybeUpdateBytesReceivedFromAlternativeAddress( QuicByteCount received_packet_size) { if (!version().SupportsAntiAmplificationLimit() || perspective_ != Perspective::IS_SERVER || - !IsAlternativePath(last_packet_destination_address_, + !IsAlternativePath(last_received_packet_info_.destination_address, GetEffectivePeerAddressFromCurrentPacket()) || - current_incoming_packet_received_bytes_counted_) { + last_received_packet_info_.received_bytes_counted) { return; } // Only update bytes received if this probing frame is received on the most // recent alternative path. - QUICHE_DCHECK(!IsDefaultPath(last_packet_destination_address_, + QUICHE_DCHECK(!IsDefaultPath(last_received_packet_info_.destination_address, GetEffectivePeerAddressFromCurrentPacket())); if (!alternative_path_.validated) { alternative_path_.bytes_received_before_address_validation += received_packet_size; } - current_incoming_packet_received_bytes_counted_ = true; + last_received_packet_info_.received_bytes_counted = true; } bool QuicConnection::IsDefaultPath( @@ -7061,6 +7106,7 @@ void QuicConnection::RestoreToLastValidatedPath( ConnectionCloseBehavior::SILENT_CLOSE); return; } + MaybeClearQueuedPacketsOnPathChange(); // Revert congestion control context to old state. OnPeerIpAddressChanged(); @@ -7097,7 +7143,7 @@ QuicConnection::OnPeerIpAddressChanged() { // re-arm it. SetRetransmissionAlarm(); // Stop detections in quiecense. - blackhole_detector_.StopDetection(); + blackhole_detector_.StopDetection(/*permanent=*/false); return old_send_algorithm; } diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_connection.h b/chromium/net/third_party/quiche/src/quic/core/quic_connection.h index b9567250637..dea513a1768 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_connection.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_connection.h @@ -36,6 +36,7 @@ #include "quic/core/quic_alarm.h" #include "quic/core/quic_alarm_factory.h" #include "quic/core/quic_blocked_writer_interface.h" +#include "quic/core/quic_connection_context.h" #include "quic/core/quic_connection_id.h" #include "quic/core/quic_connection_id_manager.h" #include "quic/core/quic_connection_stats.h" @@ -777,9 +778,11 @@ class QUIC_EXPORT_PRIVATE QuicConnection const QuicSocketAddress& effective_peer_address() const { return default_path_.peer_address; } - const QuicConnectionId& connection_id() const { return ServerConnectionId(); } + const QuicConnectionId& connection_id() const { + return default_path_.server_connection_id; + } const QuicConnectionId& client_connection_id() const { - return ClientConnectionId(); + return default_path_.client_connection_id; } void set_client_connection_id(QuicConnectionId client_connection_id); const QuicClock* clock() const { return clock_; } @@ -985,7 +988,7 @@ class QUIC_EXPORT_PRIVATE QuicConnection // If |flush| is false, this will return a MESSAGE_STATUS_BLOCKED // when the connection is deemed unwritable. virtual MessageStatus SendMessage(QuicMessageId message_id, - QuicMemSliceSpan message, + absl::Span<QuicMemSlice> message, bool flush); // Returns the largest payload that will fit into a single MESSAGE frame. @@ -1028,7 +1031,7 @@ class QUIC_EXPORT_PRIVATE QuicConnection } const QuicSocketAddress& last_packet_source_address() const { - return last_packet_source_address_; + return last_received_packet_info_.source_address; } bool fill_up_link_during_probing() const { @@ -1199,12 +1202,6 @@ class QUIC_EXPORT_PRIVATE QuicConnection bool is_processing_packet() const { return framer_.is_processing_packet(); } - bool encrypted_control_frames() const { return encrypted_control_frames_; } - - bool use_encryption_level_context() const { - return use_encryption_level_context_; - } - bool HasPendingPathValidation() const; QuicPathValidationContext* GetPathValidationContext() const; @@ -1225,9 +1222,7 @@ class QUIC_EXPORT_PRIVATE QuicConnection void SetSourceAddressTokenToSend(absl::string_view token); void SendPing() { - SendPingAtLevel(use_encryption_level_context_ - ? framer().GetEncryptionLevelToSendApplicationData() - : encryption_level_); + SendPingAtLevel(framer().GetEncryptionLevelToSendApplicationData()); } virtual std::vector<QuicConnectionId> GetActiveServerConnectionIds() const; @@ -1238,10 +1233,6 @@ class QUIC_EXPORT_PRIVATE QuicConnection return support_multiple_connection_ids_; } - bool use_connection_id_on_default_path() const { - return use_connection_id_on_default_path_; - } - bool connection_migration_use_new_cid() const { return connection_migration_use_new_cid_; } @@ -1253,8 +1244,11 @@ class QUIC_EXPORT_PRIVATE QuicConnection // Instantiates connection ID manager. void CreateConnectionIdManager(); - bool donot_write_mid_packet_processing() const { - return donot_write_mid_packet_processing_; + QuicConnectionContext* context() { return &context_; } + const QuicConnectionContext* context() const { return &context_; } + + void set_tracer(std::unique_ptr<QuicConnectionTracer> tracer) { + context_.tracer.swap(tracer); } protected: @@ -1423,15 +1417,38 @@ class QUIC_EXPORT_PRIVATE QuicConnection const QuicSocketAddress peer_address; }; - // UndecrytablePacket comprises a undecryptable packet and the its encryption - // level. + // ReceivedPacketInfo comprises the received packet information, which can be + // retrieved before the packet gets successfully decrypted. + struct QUIC_EXPORT_PRIVATE ReceivedPacketInfo { + explicit ReceivedPacketInfo(QuicTime receipt_time) + : received_bytes_counted(false), receipt_time(receipt_time) {} + ReceivedPacketInfo(const QuicSocketAddress& destination_address, + const QuicSocketAddress& source_address, + QuicTime receipt_time) + : received_bytes_counted(false), + destination_address(destination_address), + source_address(source_address), + receipt_time(receipt_time) {} + + bool received_bytes_counted; + QuicSocketAddress destination_address; + QuicSocketAddress source_address; + QuicTime receipt_time; + }; + + // UndecrytablePacket comprises a undecryptable packet and related + // information. struct QUIC_EXPORT_PRIVATE UndecryptablePacket { UndecryptablePacket(const QuicEncryptedPacket& packet, - EncryptionLevel encryption_level) - : packet(packet.Clone()), encryption_level(encryption_level) {} + EncryptionLevel encryption_level, + const ReceivedPacketInfo& packet_info) + : packet(packet.Clone()), + encryption_level(encryption_level), + packet_info(packet_info) {} std::unique_ptr<QuicEncryptedPacket> packet; EncryptionLevel encryption_level; + ReceivedPacketInfo packet_info; }; // Handles the reverse path validation result depending on connection state: @@ -1468,27 +1485,9 @@ class QUIC_EXPORT_PRIVATE QuicConnection QuicConnection* connection_; // Not owned. }; - QuicConnectionId& ClientConnectionId() { - return use_connection_id_on_default_path_ - ? default_path_.client_connection_id - : client_connection_id_; - } - const QuicConnectionId& ClientConnectionId() const { - return use_connection_id_on_default_path_ - ? default_path_.client_connection_id - : client_connection_id_; - } - QuicConnectionId& ServerConnectionId() { - return use_connection_id_on_default_path_ - ? default_path_.server_connection_id - : server_connection_id_; - } - const QuicConnectionId& ServerConnectionId() const { - return use_connection_id_on_default_path_ - ? default_path_.server_connection_id - : server_connection_id_; - } - void SetServerConnectionId(const QuicConnectionId& server_connection_id); + // If peer uses non-empty connection ID, discards any buffered packets on path + // change in IETF QUIC. + void MaybeClearQueuedPacketsOnPathChange(); // Notifies the visitor of the close and marks the connection as disconnected. // Does not send a connection close frame to the peer. It should only be @@ -1704,9 +1703,6 @@ class QUIC_EXPORT_PRIVATE QuicConnection // Returns the largest sent packet number that has been ACKed by peer. QuicPacketNumber GetLargestAckedPacket() const; - // Whether incoming_connection_ids_ contains connection_id. - bool HasIncomingConnectionId(QuicConnectionId connection_id) const; - // Whether connection is limited by amplification factor. bool LimitedByAmplificationFactor() const; @@ -1846,6 +1842,13 @@ class QUIC_EXPORT_PRIVATE QuicConnection // when a new client connection ID is received. void OnClientConnectionIdAvailable(); + // Returns true if connection needs to set retransmission alarm after a packet + // gets sent. + bool ShouldSetRetransmissionAlarmOnPacketSent(bool in_flight, + EncryptionLevel level) const; + + QuicConnectionContext context_; + QuicFramer framer_; // Contents received in the current packet, especially used to identify @@ -1873,8 +1876,6 @@ class QUIC_EXPORT_PRIVATE QuicConnection const QuicClock* clock_; QuicRandom* random_generator_; - QuicConnectionId server_connection_id_; - QuicConnectionId client_connection_id_; // On the server, the connection ID is set when receiving the first packet. // This variable ensures we only set it this way once. bool client_connection_id_is_set_; @@ -2022,10 +2023,9 @@ class QUIC_EXPORT_PRIVATE QuicConnection QuicPacketCreator packet_creator_; - // The time that a packet is received for this connection. Initialized to - // connection creation time. - // This does not indicate the packet was processed. - QuicTime time_of_last_received_packet_; + // Information about the last received QUIC packet, which may not have been + // successfully decrypted and processed. + ReceivedPacketInfo last_received_packet_info_; // Sent packet manager which tracks the status of packets sent by this // connection and contains the send and receive algorithms to determine when @@ -2043,12 +2043,6 @@ class QUIC_EXPORT_PRIVATE QuicConnection // close. bool connected_; - // Destination address of the last received packet. - QuicSocketAddress last_packet_destination_address_; - - // Source address of the last received packet. - QuicSocketAddress last_packet_source_address_; - // Destination connection ID of the last received packet. If this ID is the // original server connection ID chosen by client and server replaces it with // a different ID, last_packet_destination_connection_id_ is set to the @@ -2118,12 +2112,6 @@ class QUIC_EXPORT_PRIVATE QuicConnection // retransmission code. bool probing_retransmission_pending_; - // Indicates whether a stateless reset token has been received from peer. - bool stateless_reset_token_received_; - // Stores received stateless reset token from peer. Used to verify whether a - // packet is a stateless reset packet. - StatelessResetToken received_stateless_reset_token_; - // Id of latest sent control frame. 0 if no control frame has been sent. QuicControlFrameId last_control_frame_id_; @@ -2164,11 +2152,6 @@ class QUIC_EXPORT_PRIVATE QuicConnection quiche::QuicheCircularDeque<PendingPathChallenge> pending_path_challenge_payloads_; - // Set of connection IDs that should be accepted as destination on - // received packets. This is conceptually a set but is implemented as a - // vector to improve performance since it is expected to be very small. - std::vector<QuicConnectionId> incoming_connection_ids_; - // When we receive a RETRY packet or some INITIAL packets, we replace // |server_connection_id_| with the value from that packet and save off the // original value of |server_connection_id_| into @@ -2252,10 +2235,6 @@ class QUIC_EXPORT_PRIVATE QuicConnection // True if we are currently processing OnRetransmissionTimeout. bool in_on_retransmission_time_out_ = false; - const bool encrypted_control_frames_; - - const bool use_encryption_level_context_; - QuicPathValidator path_validator_; // Stores information of a path which maybe used as default path in the @@ -2273,8 +2252,6 @@ class QUIC_EXPORT_PRIVATE QuicConnection // This field is used to debug b/177312785. QuicFrameType most_recent_frame_type_; - bool current_incoming_packet_received_bytes_counted_ = false; - bool count_bytes_on_alternative_path_separately_ = GetQuicReloadableFlag(quic_count_bytes_on_alternative_path_seperately); @@ -2283,12 +2260,6 @@ class QUIC_EXPORT_PRIVATE QuicConnection bool support_multiple_connection_ids_ = false; - const bool donot_write_mid_packet_processing_ = - GetQuicReloadableFlag(quic_donot_write_mid_packet_processing); - - bool use_connection_id_on_default_path_ = - GetQuicReloadableFlag(quic_use_connection_id_on_default_path_v2); - // Indicates whether we should proactively validate peer address on a // PATH_CHALLENGE received. bool should_proactively_validate_peer_address_on_path_challenge_ = false; @@ -2300,8 +2271,14 @@ class QUIC_EXPORT_PRIVATE QuicConnection GetQuicReloadableFlag( quic_group_path_response_and_challenge_sending_closer); - const bool quic_deprecate_incoming_connection_ids_ = - GetQuicReloadableFlag(quic_deprecate_incoming_connection_ids); + const bool reset_per_packet_state_for_undecryptable_packets_ = + GetQuicReloadableFlag( + quic_reset_per_packet_state_for_undecryptable_packets); + + const bool add_missing_update_ack_timeout_ = + GetQuicReloadableFlag(quic_add_missing_update_ack_timeout); + + const bool ack_cid_frames_ = GetQuicReloadableFlag(quic_ack_cid_frames); }; } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_connection_context.cc b/chromium/net/third_party/quiche/src/quic/core/quic_connection_context.cc new file mode 100644 index 00000000000..e77fc431289 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/core/quic_connection_context.cc @@ -0,0 +1,36 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quic/core/quic_connection_context.h" + +#include "common/platform/api/quiche_thread_local.h" + +namespace quic { +namespace { +DEFINE_QUICHE_THREAD_LOCAL_POINTER(CurrentContext, QuicConnectionContext); +} // namespace + +// static +QuicConnectionContext* QuicConnectionContext::Current() { + return GET_QUICHE_THREAD_LOCAL_POINTER(CurrentContext); +} + +QuicConnectionContextSwitcher::QuicConnectionContextSwitcher( + QuicConnectionContext* new_context) + : old_context_(QuicConnectionContext::Current()) { + SET_QUICHE_THREAD_LOCAL_POINTER(CurrentContext, new_context); + if (new_context && new_context->tracer) { + new_context->tracer->Activate(); + } +} + +QuicConnectionContextSwitcher::~QuicConnectionContextSwitcher() { + QuicConnectionContext* current = QuicConnectionContext::Current(); + if (current && current->tracer) { + current->tracer->Deactivate(); + } + SET_QUICHE_THREAD_LOCAL_POINTER(CurrentContext, old_context_); +} + +} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_connection_context.h b/chromium/net/third_party/quiche/src/quic/core/quic_connection_context.h new file mode 100644 index 00000000000..7cedee63353 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/core/quic_connection_context.h @@ -0,0 +1,118 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_QUIC_CORE_QUIC_CONNECTION_CONTEXT_H_ +#define QUICHE_QUIC_CORE_QUIC_CONNECTION_CONTEXT_H_ + +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" +#include "quic/platform/api/quic_export.h" +#include "common/platform/api/quiche_logging.h" + +namespace quic { + +// QuicConnectionTracer is responsible for emit trace messages for a single +// QuicConnection. +// QuicConnectionTracer is part of the QuicConnectionContext. +class QUIC_EXPORT_PRIVATE QuicConnectionTracer { + public: + virtual ~QuicConnectionTracer() = default; + + // Emit a trace message from a string literal. The trace may simply remember + // the address of the literal in this function and read it at a later time. + virtual void PrintLiteral(const char* literal) = 0; + + // Emit a trace message from a string_view. Unlike PrintLiteral, this function + // will not read |s| after it returns. + virtual void PrintString(absl::string_view s) = 0; + + // Emit a trace message from printf-style arguments. + template <typename... Args> + void Printf(const absl::FormatSpec<Args...>& format, const Args&... args) { + std::string s = absl::StrFormat(format, args...); + PrintString(s); + } + + private: + friend class QuicConnectionContextSwitcher; + + // Called by QuicConnectionContextSwitcher, when |this| becomes the current + // thread's QUIC connection tracer. + // + // Activate/Deactivate are only called by QuicConnectionContextSwitcher's + // constructor/destructor, they always come in pairs. + virtual void Activate() {} + + // Called by QuicConnectionContextSwitcher, when |this| stops from being the + // current thread's QUIC connection tracer. + // + // Activate/Deactivate are only called by QuicConnectionContextSwitcher's + // constructor/destructor, they always come in pairs. + virtual void Deactivate() {} +}; + +// QuicConnectionContext is a per-QuicConnection context that includes +// facilities useable by any part of a QuicConnection. A QuicConnectionContext +// is owned by a QuicConnection. +// +// The 'top-level' QuicConnection functions are responsible for maintaining the +// thread-local QuicConnectionContext pointer, such that any function called by +// them(directly or indirectly) can access the context. +// +// Like QuicConnection, all facilities in QuicConnectionContext are assumed to +// be called from a single thread at a time, they are NOT thread-safe. +struct QUIC_EXPORT_PRIVATE QuicConnectionContext final { + // Get the context on the current executing thread. nullptr if the current + // function is not called from a 'top-level' QuicConnection function. + static QuicConnectionContext* Current(); + + std::unique_ptr<QuicConnectionTracer> tracer; +}; + +// QuicConnectionContextSwitcher is a RAII object used for maintaining the +// thread-local QuicConnectionContext pointer. +class QUIC_EXPORT_PRIVATE QuicConnectionContextSwitcher final { + public: + // The constructor switches from QuicConnectionContext::Current() to + // |new_context|. + explicit QuicConnectionContextSwitcher(QuicConnectionContext* new_context); + + // The destructor switches from QuicConnectionContext::Current() back to the + // old context. + ~QuicConnectionContextSwitcher(); + + private: + QuicConnectionContext* old_context_; +}; + +// Emit a trace message from a string literal to the current tracer(if any). +inline void QUIC_TRACELITERAL(const char* literal) { + QuicConnectionContext* current = QuicConnectionContext::Current(); + if (current && current->tracer) { + current->tracer->PrintLiteral(literal); + } +} + +// Emit a trace message from a string_view to the current tracer(if any). +inline void QUIC_TRACESTRING(absl::string_view s) { + QuicConnectionContext* current = QuicConnectionContext::Current(); + if (current && current->tracer) { + current->tracer->PrintString(s); + } +} + +// Emit a trace message from printf-style arguments to the current tracer(if +// any). +template <typename... Args> +void QUIC_TRACEPRINTF(const absl::FormatSpec<Args...>& format, + const Args&... args) { + QuicConnectionContext* current = QuicConnectionContext::Current(); + if (current && current->tracer) { + current->tracer->Printf(format, args...); + } +} + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_QUIC_CONNECTION_CONTEXT_H_ diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_connection_context_test.cc b/chromium/net/third_party/quiche/src/quic/core/quic_connection_context_test.cc new file mode 100644 index 00000000000..0f5b2c481b6 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/core/quic_connection_context_test.cc @@ -0,0 +1,173 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quic/core/quic_connection_context.h" + +#include "quic/platform/api/quic_test.h" +#include "quic/platform/api/quic_thread.h" + +using testing::ElementsAre; + +namespace quic { +namespace { + +class TraceCollector : public QuicConnectionTracer { + public: + ~TraceCollector() override = default; + + void PrintLiteral(const char* literal) override { trace_.push_back(literal); } + + void PrintString(absl::string_view s) override { + trace_.push_back(std::string(s)); + } + + const std::vector<std::string>& trace() const { return trace_; } + + private: + std::vector<std::string> trace_; +}; + +struct FakeConnection { + FakeConnection() { context.tracer = std::make_unique<TraceCollector>(); } + + const std::vector<std::string>& trace() const { + return static_cast<const TraceCollector*>(context.tracer.get())->trace(); + } + + QuicConnectionContext context; +}; + +void SimpleSwitch() { + FakeConnection connection; + + // These should be ignored since current context is nullptr. + EXPECT_EQ(QuicConnectionContext::Current(), nullptr); + QUIC_TRACELITERAL("before switch: literal"); + QUIC_TRACESTRING(std::string("before switch: string")); + QUIC_TRACEPRINTF("%s: %s", "before switch", "printf"); + + { + QuicConnectionContextSwitcher switcher(&connection.context); + QUIC_TRACELITERAL("literal"); + QUIC_TRACESTRING(std::string("string")); + QUIC_TRACEPRINTF("%s", "printf"); + } + + EXPECT_EQ(QuicConnectionContext::Current(), nullptr); + QUIC_TRACELITERAL("after switch: literal"); + QUIC_TRACESTRING(std::string("after switch: string")); + QUIC_TRACEPRINTF("%s: %s", "after switch", "printf"); + + EXPECT_THAT(connection.trace(), ElementsAre("literal", "string", "printf")); +} + +void NestedSwitch() { + FakeConnection outer, inner; + + { + QuicConnectionContextSwitcher switcher(&outer.context); + QUIC_TRACELITERAL("outer literal 0"); + QUIC_TRACESTRING(std::string("outer string 0")); + QUIC_TRACEPRINTF("%s %s %d", "outer", "printf", 0); + + { + QuicConnectionContextSwitcher switcher(&inner.context); + QUIC_TRACELITERAL("inner literal"); + QUIC_TRACESTRING(std::string("inner string")); + QUIC_TRACEPRINTF("%s %s", "inner", "printf"); + } + + QUIC_TRACELITERAL("outer literal 1"); + QUIC_TRACESTRING(std::string("outer string 1")); + QUIC_TRACEPRINTF("%s %s %d", "outer", "printf", 1); + } + + EXPECT_THAT(outer.trace(), ElementsAre("outer literal 0", "outer string 0", + "outer printf 0", "outer literal 1", + "outer string 1", "outer printf 1")); + + EXPECT_THAT(inner.trace(), + ElementsAre("inner literal", "inner string", "inner printf")); +} + +void AlternatingSwitch() { + FakeConnection zero, one, two; + for (int i = 0; i < 15; ++i) { + FakeConnection* connection = + ((i % 3) == 0) ? &zero : (((i % 3) == 1) ? &one : &two); + + QuicConnectionContextSwitcher switcher(&connection->context); + QUIC_TRACEPRINTF("%d", i); + } + + EXPECT_THAT(zero.trace(), ElementsAre("0", "3", "6", "9", "12")); + EXPECT_THAT(one.trace(), ElementsAre("1", "4", "7", "10", "13")); + EXPECT_THAT(two.trace(), ElementsAre("2", "5", "8", "11", "14")); +} + +typedef void (*ThreadFunction)(); + +template <ThreadFunction func> +class TestThread : public QuicThread { + public: + TestThread() : QuicThread("TestThread") {} + ~TestThread() override = default; + + protected: + void Run() override { func(); } +}; + +template <ThreadFunction func> +void RunInThreads(size_t n_threads) { + using ThreadType = TestThread<func>; + std::vector<ThreadType> threads(n_threads); + + for (ThreadType& t : threads) { + t.Start(); + } + + for (ThreadType& t : threads) { + t.Join(); + } +} + +class QuicConnectionContextTest : public QuicTest { + protected: +}; + +TEST_F(QuicConnectionContextTest, NullTracerOK) { + FakeConnection connection; + std::unique_ptr<QuicConnectionTracer> tracer; + + { + QuicConnectionContextSwitcher switcher(&connection.context); + QUIC_TRACELITERAL("msg 1 recorded"); + } + + connection.context.tracer.swap(tracer); + + { + QuicConnectionContextSwitcher switcher(&connection.context); + // Should be a no-op since connection.context.tracer is nullptr. + QUIC_TRACELITERAL("msg 2 ignored"); + } + + EXPECT_THAT(static_cast<TraceCollector*>(tracer.get())->trace(), + ElementsAre("msg 1 recorded")); +} + +TEST_F(QuicConnectionContextTest, TestSimpleSwitch) { + RunInThreads<SimpleSwitch>(10); +} + +TEST_F(QuicConnectionContextTest, TestNestedSwitch) { + RunInThreads<NestedSwitch>(10); +} + +TEST_F(QuicConnectionContextTest, TestAlternatingSwitch) { + RunInThreads<AlternatingSwitch>(10); +} + +} // namespace +} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_connection_test.cc b/chromium/net/third_party/quiche/src/quic/core/quic_connection_test.cc index fe2ff6cab6e..69666301c59 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_connection_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_connection_test.cc @@ -23,6 +23,7 @@ #include "quic/core/frames/quic_connection_close_frame.h" #include "quic/core/frames/quic_new_connection_id_frame.h" #include "quic/core/frames/quic_path_response_frame.h" +#include "quic/core/frames/quic_rst_stream_frame.h" #include "quic/core/quic_connection_id.h" #include "quic/core/quic_constants.h" #include "quic/core/quic_error_codes.h" @@ -33,6 +34,7 @@ #include "quic/core/quic_types.h" #include "quic/core/quic_utils.h" #include "quic/core/quic_versions.h" +#include "quic/platform/api/quic_error_code_wrappers.h" #include "quic/platform/api/quic_expect_bug.h" #include "quic/platform/api/quic_flags.h" #include "quic/platform/api/quic_ip_address.h" @@ -1102,12 +1104,9 @@ class QuicConnectionTest : public QuicTestWithParam<TestParams> { MessageStatus SendMessage(absl::string_view message) { connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); - QuicMemSliceStorage storage(nullptr, 0, nullptr, 0); - return connection_.SendMessage( - 1, - MakeSpan(connection_.helper()->GetStreamSendBufferAllocator(), message, - &storage), - false); + QuicMemSlice slice(QuicBuffer::Copy( + connection_.helper()->GetStreamSendBufferAllocator(), message)); + return connection_.SendMessage(1, absl::MakeSpan(&slice, 1), false); } void ProcessAckPacket(uint64_t packet_number, QuicAckFrame* frame) { @@ -1179,7 +1178,12 @@ class QuicConnectionTest : public QuicTestWithParam<TestParams> { } if (peer_framer_.version().HasIetfInvariantHeader() && peer_framer_.perspective() == Perspective::IS_SERVER) { - header.destination_connection_id_included = CONNECTION_ID_ABSENT; + if (!connection_.client_connection_id().IsEmpty()) { + header.destination_connection_id = connection_.client_connection_id(); + header.destination_connection_id_included = CONNECTION_ID_PRESENT; + } else { + header.destination_connection_id_included = CONNECTION_ID_ABSENT; + } if (header.version_flag) { header.source_connection_id = connection_id_; header.source_connection_id_included = CONNECTION_ID_PRESENT; @@ -2171,6 +2175,10 @@ TEST_P(QuicConnectionTest, ReversePathValidationFailureAtServer) { EXPECT_EQ(IPV6_TO_IPV4_CHANGE, connection_.active_effective_peer_migration_type()); + // Make sure anti-amplification limit is not reached. + ProcessFramesPacketWithAddresses( + {QuicFrame(QuicPingFrame()), QuicFrame(QuicPaddingFrame())}, kSelfAddress, + kNewPeerAddress, ENCRYPTION_FORWARD_SECURE); SendStreamDataToPeer(1, "foo", 0, NO_FIN, nullptr); EXPECT_TRUE(connection_.GetRetransmissionAlarm()->IsSet()); @@ -3754,11 +3762,9 @@ TEST_P(QuicConnectionTest, OnCanWrite) { TEST_P(QuicConnectionTest, RetransmitOnNack) { QuicPacketNumber last_packet; - QuicByteCount second_packet_size; - SendStreamDataToPeer(3, "foo", 0, NO_FIN, &last_packet); // Packet 1 - second_packet_size = - SendStreamDataToPeer(3, "foos", 3, NO_FIN, &last_packet); // Packet 2 - SendStreamDataToPeer(3, "fooos", 7, NO_FIN, &last_packet); // Packet 3 + SendStreamDataToPeer(3, "foo", 0, NO_FIN, &last_packet); + SendStreamDataToPeer(3, "foos", 3, NO_FIN, &last_packet); + SendStreamDataToPeer(3, "fooos", 7, NO_FIN, &last_packet); EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); @@ -8775,8 +8781,7 @@ TEST_P(QuicConnectionTest, SendMessage) { connection_.SetFromConfig(config); } std::string message(connection_.GetCurrentLargestMessagePayload() * 2, 'a'); - absl::string_view message_data(message); - QuicMemSliceStorage storage(nullptr, 0, nullptr, 0); + QuicMemSlice slice; { QuicConnection::ScopedPacketFlusher flusher(&connection_); connection_.SendStreamData3(); @@ -8784,36 +8789,23 @@ TEST_P(QuicConnectionTest, SendMessage) { // get sent, one contains stream frame, and the other only contains the // message frame. EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(2); + slice = MemSliceFromString(absl::string_view( + message.data(), connection_.GetCurrentLargestMessagePayload())); EXPECT_EQ(MESSAGE_STATUS_SUCCESS, - connection_.SendMessage( - 1, - MakeSpan(connection_.helper()->GetStreamSendBufferAllocator(), - absl::string_view( - message_data.data(), - connection_.GetCurrentLargestMessagePayload()), - &storage), - false)); + connection_.SendMessage(1, absl::MakeSpan(&slice, 1), false)); } // Fail to send a message if connection is congestion control blocked. EXPECT_CALL(*send_algorithm_, CanSend(_)).WillOnce(Return(false)); + slice = MemSliceFromString("message"); EXPECT_EQ(MESSAGE_STATUS_BLOCKED, - connection_.SendMessage( - 2, - MakeSpan(connection_.helper()->GetStreamSendBufferAllocator(), - "message", &storage), - false)); + connection_.SendMessage(2, absl::MakeSpan(&slice, 1), false)); // Always fail to send a message which cannot fit into one packet. EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(0); + slice = MemSliceFromString(absl::string_view( + message.data(), connection_.GetCurrentLargestMessagePayload() + 1)); EXPECT_EQ(MESSAGE_STATUS_TOO_LARGE, - connection_.SendMessage( - 3, - MakeSpan(connection_.helper()->GetStreamSendBufferAllocator(), - absl::string_view( - message_data.data(), - connection_.GetCurrentLargestMessagePayload() + 1), - &storage), - false)); + connection_.SendMessage(3, absl::MakeSpan(&slice, 1), false)); } TEST_P(QuicConnectionTest, GetCurrentLargestMessagePayload) { @@ -10545,16 +10537,16 @@ void QuicConnectionTest::TestClientRetryHandling( } // These values come from draft-ietf-quic-tls Appendix A.4. - char retry_packet_rfcv1[] = { + uint8_t retry_packet_rfcv1[] = { 0xff, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x04, 0xa2, 0x65, 0xba, 0x2e, 0xff, 0x4d, 0x82, 0x90, 0x58, 0xfb, 0x3f, 0x0f, 0x24, 0x96, 0xba}; - char retry_packet29[] = { + uint8_t retry_packet29[] = { 0xff, 0xff, 0x00, 0x00, 0x1d, 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0xd1, 0x69, 0x26, 0xd8, 0x1f, 0x6f, 0x9c, 0xa2, 0x95, 0x3a, 0x8a, 0xa4, 0x57, 0x5e, 0x1e, 0x49}; - char* retry_packet; + uint8_t* retry_packet; size_t retry_packet_length; if (version() == ParsedQuicVersion::RFCv1()) { retry_packet = retry_packet_rfcv1; @@ -10568,19 +10560,21 @@ void QuicConnectionTest::TestClientRetryHandling( return; } - char original_connection_id_bytes[] = {0x83, 0x94, 0xc8, 0xf0, - 0x3e, 0x51, 0x57, 0x08}; - char new_connection_id_bytes[] = {0xf0, 0x67, 0xa5, 0x50, - 0x2a, 0x42, 0x62, 0xb5}; - char retry_token_bytes[] = {0x74, 0x6f, 0x6b, 0x65, 0x6e}; + uint8_t original_connection_id_bytes[] = {0x83, 0x94, 0xc8, 0xf0, + 0x3e, 0x51, 0x57, 0x08}; + uint8_t new_connection_id_bytes[] = {0xf0, 0x67, 0xa5, 0x50, + 0x2a, 0x42, 0x62, 0xb5}; + uint8_t retry_token_bytes[] = {0x74, 0x6f, 0x6b, 0x65, 0x6e}; QuicConnectionId original_connection_id( - original_connection_id_bytes, + reinterpret_cast<char*>(original_connection_id_bytes), ABSL_ARRAYSIZE(original_connection_id_bytes)); - QuicConnectionId new_connection_id(new_connection_id_bytes, - ABSL_ARRAYSIZE(new_connection_id_bytes)); + QuicConnectionId new_connection_id( + reinterpret_cast<char*>(new_connection_id_bytes), + ABSL_ARRAYSIZE(new_connection_id_bytes)); - std::string retry_token(retry_token_bytes, ABSL_ARRAYSIZE(retry_token_bytes)); + std::string retry_token(reinterpret_cast<char*>(retry_token_bytes), + ABSL_ARRAYSIZE(retry_token_bytes)); if (invalid_retry_tag) { // Flip the last bit of the retry packet to prevent the integrity tag @@ -10611,7 +10605,8 @@ void QuicConnectionTest::TestClientRetryHandling( // Process the RETRY packet. connection_.ProcessUdpPacket( kSelfAddress, kPeerAddress, - QuicReceivedPacket(retry_packet, retry_packet_length, clock_.Now())); + QuicReceivedPacket(reinterpret_cast<char*>(retry_packet), + retry_packet_length, clock_.Now())); if (invalid_retry_tag) { // Make sure we refuse to process a RETRY with invalid tag. @@ -11572,6 +11567,67 @@ TEST_P(QuicConnectionTest, CoalscingPacketCausesInfiniteLoop) { connection_.GetRetransmissionAlarm()->Fire(); } +TEST_P(QuicConnectionTest, ClientAckDelayForAsyncPacketProcessing) { + if (!version().HasIetfQuicFrames()) { + return; + } + // SetFromConfig is always called after construction from InitializeSession. + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); + EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); + EXPECT_CALL(visitor_, OnHandshakePacketSent()).Times(AnyNumber()); + QuicConfig config; + connection_.SetFromConfig(config); + connection_.SetDefaultEncryptionLevel(ENCRYPTION_INITIAL); + use_tagging_decrypter(); + connection_.SetEncrypter(ENCRYPTION_INITIAL, + std::make_unique<TaggingEncrypter>(0x01)); + peer_framer_.SetEncrypter(ENCRYPTION_HANDSHAKE, + std::make_unique<TaggingEncrypter>(0x01)); + EXPECT_EQ(0u, QuicConnectionPeer::NumUndecryptablePackets(&connection_)); + + // Received undecryptable HANDSHAKE 2. + ProcessDataPacketAtLevel(2, !kHasStopWaiting, ENCRYPTION_HANDSHAKE); + ASSERT_EQ(1u, QuicConnectionPeer::NumUndecryptablePackets(&connection_)); + // Received INITIAL 4 (which is retransmission of INITIAL 1) after 100ms. + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(100)); + ProcessDataPacketAtLevel(4, !kHasStopWaiting, ENCRYPTION_INITIAL); + // Generate HANDSHAKE key. + SetDecrypter(ENCRYPTION_HANDSHAKE, + std::make_unique<StrictTaggingDecrypter>(0x01)); + EXPECT_TRUE(connection_.GetProcessUndecryptablePacketsAlarm()->IsSet()); + connection_.SetEncrypter(ENCRYPTION_HANDSHAKE, + std::make_unique<TaggingEncrypter>(0x01)); + connection_.SetDefaultEncryptionLevel(ENCRYPTION_HANDSHAKE); + // Verify HANDSHAKE packet gets processed. + connection_.GetProcessUndecryptablePacketsAlarm()->Fire(); + ASSERT_TRUE(connection_.HasPendingAcks()); + // Send ACKs. + clock_.AdvanceTime(connection_.GetAckAlarm()->deadline() - clock_.Now()); + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(2); + connection_.GetAckAlarm()->Fire(); + ASSERT_FALSE(writer_->ack_frames().empty()); + // Verify the ack_delay_time in the INITIAL ACK frame is 1ms. + EXPECT_EQ(QuicTime::Delta::FromMilliseconds(1), + writer_->ack_frames()[0].ack_delay_time); + // Process the coalesced HANDSHAKE packet. + ASSERT_TRUE(writer_->coalesced_packet() != nullptr); + auto packet = writer_->coalesced_packet()->Clone(); + writer_->framer()->ProcessPacket(*packet); + ASSERT_FALSE(writer_->ack_frames().empty()); + if (GetQuicReloadableFlag( + quic_reset_per_packet_state_for_undecryptable_packets)) { + // Verify the ack_delay_time in the HANDSHAKE ACK frame includes the + // buffering time. + EXPECT_EQ(QuicTime::Delta::FromMilliseconds(101), + writer_->ack_frames()[0].ack_delay_time); + } else { + // This ack_delay_time is wrong. + EXPECT_EQ(QuicTime::Delta::FromMilliseconds(1), + writer_->ack_frames()[0].ack_delay_time); + } + ASSERT_TRUE(writer_->coalesced_packet() == nullptr); +} + TEST_P(QuicConnectionTest, TestingLiveness) { const size_t kMinRttMs = 40; RttStats* rtt_stats = const_cast<RttStats*>(manager_->GetRttStats()); @@ -12017,7 +12073,7 @@ TEST_P(QuicConnectionTest, PathValidationReceivesStatelessReset) { // writer. TEST_P(QuicConnectionTest, SendPathChallengeUsingBlockedNewSocket) { if (!VersionHasIetfQuicFrames(connection_.version().transport_version) || - !connection_.use_path_validator()) { + !connection_.connection_migration_use_new_cid()) { return; } PathProbeTestInit(Perspective::IS_CLIENT); @@ -12027,6 +12083,7 @@ TEST_P(QuicConnectionTest, SendPathChallengeUsingBlockedNewSocket) { new_writer.BlockOnNextWrite(); EXPECT_CALL(visitor_, OnWriteBlocked()).Times(0); EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)) + .Times(AtLeast(1)) .WillOnce(Invoke([&]() { // Even though the socket is blocked, the PATH_CHALLENGE should still be // treated as sent. @@ -12047,7 +12104,13 @@ TEST_P(QuicConnectionTest, SendPathChallengeUsingBlockedNewSocket) { new_writer.SetWritable(); // Write event on the default socket shouldn't make any difference. connection_.OnCanWrite(); - EXPECT_EQ(0u, writer_->packets_write_attempts()); + if (GetQuicReloadableFlag(quic_ack_cid_frames)) { + // A NEW_CONNECTION_ID frame is received in PathProbeTestInit and OnCanWrite + // will write a acking packet. + EXPECT_EQ(1u, writer_->packets_write_attempts()); + } else { + EXPECT_EQ(0u, writer_->packets_write_attempts()); + } EXPECT_EQ(1u, new_writer.packets_write_attempts()); } @@ -12232,9 +12295,12 @@ TEST_P(QuicConnectionTest, return; } PathProbeTestInit(Perspective::IS_CLIENT); + // Make sure there is no outstanding ACK_FRAME to write. + connection_.OnCanWrite(); + uint32_t num_packets_write_attempts = writer_->packets_write_attempts(); writer_->SetShouldWriteFail(); - writer_->SetWriteError(EMSGSIZE); + writer_->SetWriteError(QUIC_EMSGSIZE); const QuicSocketAddress kNewPeerAddress(QuicIpAddress::Any4(), 12345); EXPECT_CALL(visitor_, OnConnectionClosed(_, ConnectionCloseSource::FROM_SELF)) .Times(0u); @@ -12250,7 +12316,7 @@ TEST_P(QuicConnectionTest, EXPECT_TRUE(connection_.HasPendingPathValidation()); // Connection shouldn't be closed. EXPECT_TRUE(connection_.connected()); - EXPECT_EQ(1u, writer_->packets_write_attempts()); + EXPECT_EQ(++num_packets_write_attempts, writer_->packets_write_attempts()); EXPECT_EQ(1u, writer_->path_challenge_frames().size()); EXPECT_EQ(1u, writer_->padding_frames().size()); EXPECT_EQ(kNewPeerAddress, writer_->last_write_peer_address()); @@ -12656,10 +12722,6 @@ TEST_P(QuicConnectionTest, ZeroRttRejectionAndMissingInitialKeys) { connection_.SetEncrypter(ENCRYPTION_FORWARD_SECURE, std::make_unique<TaggingEncrypter>(0x04)); connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); - if (!GetQuicReloadableFlag(quic_donot_write_mid_packet_processing)) { - // Retransmit rejected 0-RTT packets. - connection_.OnCanWrite(); - } // Advance INITIAL ack delay to trigger initial ACK to be sent AFTER // the retransmission of rejected 0-RTT packets while the HANDSHAKE // packet is still in the coalescer, such that the INITIAL key gets @@ -13576,19 +13638,33 @@ TEST_P(QuicConnectionTest, ServerHelloGetsReordered) { TEST_P(QuicConnectionTest, MigratePath) { EXPECT_CALL(visitor_, GetHandshakeState()) - .Times(testing::AtMost(1)) - .WillOnce(Return(HANDSHAKE_CONFIRMED)); + .Times(testing::AtMost(2)) + .WillRepeatedly(Return(HANDSHAKE_CONFIRMED)); EXPECT_CALL(visitor_, OnPathDegrading()); connection_.OnPathDegradingDetected(); const QuicSocketAddress kNewSelfAddress(QuicIpAddress::Any4(), 12345); EXPECT_NE(kNewSelfAddress, connection_.self_address()); + + // Buffer a packet. + EXPECT_CALL(visitor_, OnWriteBlocked()).Times(1); + writer_->SetWriteBlocked(); + connection_.SendMtuDiscoveryPacket(kMaxOutgoingPacketSize); + EXPECT_EQ(1u, connection_.NumQueuedPackets()); + TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT); EXPECT_CALL(visitor_, OnForwardProgressMadeAfterPathDegrading()); connection_.MigratePath(kNewSelfAddress, connection_.peer_address(), &new_writer, /*owns_writer=*/false); + EXPECT_EQ(kNewSelfAddress, connection_.self_address()); EXPECT_EQ(&new_writer, QuicConnectionPeer::GetWriter(&connection_)); EXPECT_FALSE(connection_.IsPathDegrading()); + // Buffered packet on the old path should be discarded. + if (connection_.connection_migration_use_new_cid()) { + EXPECT_EQ(0u, connection_.NumQueuedPackets()); + } else { + EXPECT_EQ(1u, connection_.NumQueuedPackets()); + } } TEST_P(QuicConnectionTest, MigrateToNewPathDuringProbing) { @@ -14453,9 +14529,11 @@ TEST_P(QuicConnectionTest, // would fail due to lack of client connection ID. const QuicSocketAddress kSelfAddress2(QuicIpAddress::Loopback4(), /*port=*/45678); - ASSERT_FALSE(connection_.MigratePath(kSelfAddress2, - connection_.peer_address(), &new_writer, - /*owns_writer=*/false)); + auto new_writer2 = std::make_unique<TestPacketWriter>(version(), &clock_, + Perspective::IS_CLIENT); + ASSERT_FALSE(connection_.MigratePath( + kSelfAddress2, connection_.peer_address(), new_writer2.release(), + /*owns_writer=*/true)); } TEST_P(QuicConnectionTest, @@ -14587,7 +14665,6 @@ TEST_P( QuicConnectionTest, ReplacePeerIssuedConnectionIdOnBothPathsTriggeredByNewConnectionIdFrame) { if (!version().HasIetfQuicFrames() || !connection_.use_path_validator() || - !connection_.use_connection_id_on_default_path() || !connection_.count_bytes_on_alternative_path_separately()) { return; } @@ -14647,7 +14724,7 @@ TEST_P( TEST_P(QuicConnectionTest, CloseConnectionAfterReceiveRetireConnectionIdWhenNoCIDIssued) { if (!version().HasIetfQuicFrames() || - GetQuicReloadableFlag(quic_use_connection_id_on_default_path_v2)) { + !connection_.connection_migration_use_new_cid()) { return; } QuicConnectionPeer::EnableMultipleConnectionIdSupport(&connection_); @@ -14668,7 +14745,7 @@ TEST_P(QuicConnectionTest, TEST_P(QuicConnectionTest, RetireConnectionIdFrameResultsInError) { if (!version().HasIetfQuicFrames() || - GetQuicReloadableFlag(quic_use_connection_id_on_default_path_v2)) { + !connection_.connection_migration_use_new_cid()) { return; } QuicConnectionPeer::EnableMultipleConnectionIdSupport(&connection_); @@ -14708,24 +14785,16 @@ TEST_P(QuicConnectionTest, QuicConnectionId cid0 = connection_id_; QuicRetireConnectionIdFrame frame; frame.sequence_number = 0u; - if (!GetQuicReloadableFlag(quic_use_connection_id_on_default_path_v2) || - connection_.connection_migration_use_new_cid()) { + if (connection_.connection_migration_use_new_cid()) { EXPECT_CALL(visitor_, OnServerConnectionIdIssued(_)).Times(2); EXPECT_CALL(visitor_, SendNewConnectionId(_)).Times(2); } EXPECT_TRUE(connection_.OnRetireConnectionIdFrame(frame)); - if (!GetQuicReloadableFlag(quic_use_connection_id_on_default_path_v2)) { - ASSERT_TRUE(retire_self_issued_cid_alarm->IsSet()); - // cid0 is retired when the retire CID alarm fires. - if (!GetQuicReloadableFlag(quic_use_connection_id_on_default_path_v2)) - EXPECT_CALL(visitor_, OnServerConnectionIdRetired(cid0)); - retire_self_issued_cid_alarm->Fire(); - } } TEST_P(QuicConnectionTest, ServerRetireSelfIssuedConnectionId) { if (!version().HasIetfQuicFrames() || - GetQuicReloadableFlag(quic_use_connection_id_on_default_path_v2)) { + !connection_.connection_migration_use_new_cid()) { return; } QuicConnectionPeer::EnableMultipleConnectionIdSupport(&connection_); @@ -14770,8 +14839,7 @@ TEST_P(QuicConnectionTest, ServerRetireSelfIssuedConnectionId) { TEST_P(QuicConnectionTest, PatchMissingClientConnectionIdOntoAlternativePath) { if (!version().HasIetfQuicFrames() || - !connection_.support_multiple_connection_ids() || - !connection_.use_connection_id_on_default_path()) { + !connection_.support_multiple_connection_ids()) { return; } set_perspective(Perspective::IS_SERVER); @@ -14807,8 +14875,7 @@ TEST_P(QuicConnectionTest, PatchMissingClientConnectionIdOntoAlternativePath) { TEST_P(QuicConnectionTest, PatchMissingClientConnectionIdOntoDefaultPath) { if (!version().HasIetfQuicFrames() || - !connection_.support_multiple_connection_ids() || - !connection_.use_connection_id_on_default_path()) { + !connection_.support_multiple_connection_ids()) { return; } set_perspective(Perspective::IS_SERVER); @@ -14923,23 +14990,13 @@ TEST_P(QuicConnectionTest, LostDataThenGetAcknowledged) { InvokeWithoutArgs(¬ifier_, &SimpleSessionNotifier::OnCanWrite)); QuicIpAddress ip_address; ASSERT_TRUE(ip_address.FromString("127.0.52.223")); - if (GetQuicReloadableFlag(quic_donot_write_mid_packet_processing)) { - EXPECT_QUIC_BUG( - ProcessFramesPacketWithAddresses(frames, kSelfAddress, - QuicSocketAddress(ip_address, 1000), - ENCRYPTION_FORWARD_SECURE), - "Try to write mid packet processing"); - EXPECT_EQ(1u, writer_->path_challenge_frames().size()); - // Verify stream frame will not be retransmitted. - EXPECT_TRUE(writer_->stream_frames().empty()); - } else { - ProcessFramesPacketWithAddresses(frames, kSelfAddress, - QuicSocketAddress(ip_address, 1000), - ENCRYPTION_FORWARD_SECURE); - // In prod, this would cause FAILED_TO_SERIALIZE_PACKET since the stream - // data has been freed, but simple_data_producer does not free data. - EXPECT_EQ(1u, writer_->stream_frames().size()); - } + EXPECT_QUIC_BUG(ProcessFramesPacketWithAddresses( + frames, kSelfAddress, QuicSocketAddress(ip_address, 1000), + ENCRYPTION_FORWARD_SECURE), + "Try to write mid packet processing"); + EXPECT_EQ(1u, writer_->path_challenge_frames().size()); + // Verify stream frame will not be retransmitted. + EXPECT_TRUE(writer_->stream_frames().empty()); } TEST_P(QuicConnectionTest, PtoSendStreamData) { @@ -14980,12 +15037,320 @@ TEST_P(QuicConnectionTest, PtoSendStreamData) { ASSERT_TRUE(connection_.GetRetransmissionAlarm()->IsSet()); connection_.GetRetransmissionAlarm()->Fire(); - if (GetQuicReloadableFlag(quic_donot_pto_half_rtt_data)) { - // Verify INITIAL and HANDSHAKE get retransmitted. - EXPECT_EQ(0x02020202u, writer_->final_bytes_of_last_packet()); + // Verify INITIAL and HANDSHAKE get retransmitted. + EXPECT_EQ(0x02020202u, writer_->final_bytes_of_last_packet()); +} + +TEST_P(QuicConnectionTest, SendingZeroRttPacketsDoesNotPostponePTO) { + if (!connection_.SupportsMultiplePacketNumberSpaces()) { + return; + } + use_tagging_decrypter(); + connection_.SetEncrypter(ENCRYPTION_INITIAL, + std::make_unique<TaggingEncrypter>(0x01)); + connection_.SetDefaultEncryptionLevel(ENCRYPTION_INITIAL); + // Send CHLO. + connection_.SendCryptoStreamData(); + ASSERT_TRUE(connection_.GetRetransmissionAlarm()->IsSet()); + // Install 0-RTT keys. + connection_.SetEncrypter(ENCRYPTION_ZERO_RTT, + std::make_unique<TaggingEncrypter>(0x02)); + connection_.SetDefaultEncryptionLevel(ENCRYPTION_ZERO_RTT); + + // CHLO gets acknowledged after 10ms. + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(10)); + QuicAckFrame frame1 = InitAckFrame(1); + EXPECT_CALL(*send_algorithm_, OnCongestionEvent(_, _, _, _, _)); + ProcessFramePacketAtLevel(1, QuicFrame(&frame1), ENCRYPTION_INITIAL); + // Verify PTO is still armed since address validation is not finished yet. + ASSERT_TRUE(connection_.GetRetransmissionAlarm()->IsSet()); + QuicTime pto_deadline = connection_.GetRetransmissionAlarm()->deadline(); + + // Send 0-RTT packet. + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); + connection_.SetEncrypter(ENCRYPTION_ZERO_RTT, + std::make_unique<TaggingEncrypter>(0x02)); + connection_.SetDefaultEncryptionLevel(ENCRYPTION_ZERO_RTT); + connection_.SendStreamDataWithString(2, "foo", 0, NO_FIN); + ASSERT_TRUE(connection_.GetRetransmissionAlarm()->IsSet()); + if (GetQuicReloadableFlag( + quic_donot_rearm_pto_on_application_data_during_handshake)) { + // PTO deadline should be unchanged. + EXPECT_EQ(pto_deadline, connection_.GetRetransmissionAlarm()->deadline()); } else { - // Application data preempts handshake data when PTO fires. - EXPECT_EQ(0x03030303u, writer_->final_bytes_of_last_packet()); + // PTO gets re-armed. + EXPECT_NE(pto_deadline, connection_.GetRetransmissionAlarm()->deadline()); + } +} + +TEST_P(QuicConnectionTest, QueueingUndecryptablePacketsDoesntPostponePTO) { + if (!connection_.SupportsMultiplePacketNumberSpaces()) { + return; + } + EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); + QuicConfig config; + config.set_max_undecryptable_packets(3); + connection_.SetFromConfig(config); + use_tagging_decrypter(); + connection_.SetEncrypter(ENCRYPTION_INITIAL, + std::make_unique<TaggingEncrypter>(0x01)); + connection_.SetDefaultEncryptionLevel(ENCRYPTION_INITIAL); + connection_.RemoveDecrypter(ENCRYPTION_FORWARD_SECURE); + // Send CHLO. + connection_.SendCryptoStreamData(); + + // Send 0-RTT packet. + connection_.SetEncrypter(ENCRYPTION_ZERO_RTT, + std::make_unique<TaggingEncrypter>(0x02)); + connection_.SetDefaultEncryptionLevel(ENCRYPTION_ZERO_RTT); + connection_.SendStreamDataWithString(2, "foo", 0, NO_FIN); + + // CHLO gets acknowledged after 10ms. + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(10)); + QuicAckFrame frame1 = InitAckFrame(1); + EXPECT_CALL(*send_algorithm_, OnCongestionEvent(_, _, _, _, _)); + ProcessFramePacketAtLevel(1, QuicFrame(&frame1), ENCRYPTION_INITIAL); + // Verify PTO is still armed since address validation is not finished yet. + ASSERT_TRUE(connection_.GetRetransmissionAlarm()->IsSet()); + QuicTime pto_deadline = connection_.GetRetransmissionAlarm()->deadline(); + + // Receive an undecryptable packets. + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); + peer_framer_.SetEncrypter(ENCRYPTION_FORWARD_SECURE, + std::make_unique<TaggingEncrypter>(0xFF)); + ProcessDataPacketAtLevel(3, !kHasStopWaiting, ENCRYPTION_FORWARD_SECURE); + // Verify PTO deadline is sooner. + EXPECT_GT(pto_deadline, connection_.GetRetransmissionAlarm()->deadline()); + pto_deadline = connection_.GetRetransmissionAlarm()->deadline(); + + // PTO fires. + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1); + clock_.AdvanceTime(pto_deadline - clock_.ApproximateNow()); + connection_.GetRetransmissionAlarm()->Fire(); + // Verify PTO is still armed since address validation is not finished yet. + ASSERT_TRUE(connection_.GetRetransmissionAlarm()->IsSet()); + pto_deadline = connection_.GetRetransmissionAlarm()->deadline(); + + // Verify PTO deadline does not change. + ProcessDataPacketAtLevel(4, !kHasStopWaiting, ENCRYPTION_FORWARD_SECURE); + EXPECT_EQ(pto_deadline, connection_.GetRetransmissionAlarm()->deadline()); +} + +TEST_P(QuicConnectionTest, QueueUndecryptableHandshakePackets) { + if (!connection_.SupportsMultiplePacketNumberSpaces()) { + return; + } + EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); + QuicConfig config; + config.set_max_undecryptable_packets(3); + connection_.SetFromConfig(config); + use_tagging_decrypter(); + connection_.SetEncrypter(ENCRYPTION_INITIAL, + std::make_unique<TaggingEncrypter>(0x01)); + connection_.SetDefaultEncryptionLevel(ENCRYPTION_INITIAL); + connection_.RemoveDecrypter(ENCRYPTION_HANDSHAKE); + // Send CHLO. + connection_.SendCryptoStreamData(); + + // Send 0-RTT packet. + connection_.SetEncrypter(ENCRYPTION_ZERO_RTT, + std::make_unique<TaggingEncrypter>(0x02)); + connection_.SetDefaultEncryptionLevel(ENCRYPTION_ZERO_RTT); + connection_.SendStreamDataWithString(2, "foo", 0, NO_FIN); + EXPECT_EQ(0u, QuicConnectionPeer::NumUndecryptablePackets(&connection_)); + + // Receive an undecryptable handshake packet. + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); + peer_framer_.SetEncrypter(ENCRYPTION_HANDSHAKE, + std::make_unique<TaggingEncrypter>(0xFF)); + ProcessDataPacketAtLevel(3, !kHasStopWaiting, ENCRYPTION_HANDSHAKE); + // Verify this handshake packet gets queued. + EXPECT_EQ(1u, QuicConnectionPeer::NumUndecryptablePackets(&connection_)); +} + +TEST_P(QuicConnectionTest, PingNotSentAt0RTTLevelWhenInitialAvailable) { + if (!connection_.SupportsMultiplePacketNumberSpaces()) { + return; + } + use_tagging_decrypter(); + connection_.SetEncrypter(ENCRYPTION_INITIAL, + std::make_unique<TaggingEncrypter>(0x01)); + connection_.SetDefaultEncryptionLevel(ENCRYPTION_INITIAL); + // Send CHLO. + connection_.SendCryptoStreamData(); + // Send 0-RTT packet. + connection_.SetEncrypter(ENCRYPTION_ZERO_RTT, + std::make_unique<TaggingEncrypter>(0x02)); + connection_.SetDefaultEncryptionLevel(ENCRYPTION_ZERO_RTT); + connection_.SendStreamDataWithString(2, "foo", 0, NO_FIN); + + // CHLO gets acknowledged after 10ms. + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(10)); + QuicAckFrame frame1 = InitAckFrame(1); + EXPECT_CALL(*send_algorithm_, OnCongestionEvent(_, _, _, _, _)); + ProcessFramePacketAtLevel(1, QuicFrame(&frame1), ENCRYPTION_INITIAL); + // Verify PTO is still armed since address validation is not finished yet. + ASSERT_TRUE(connection_.GetRetransmissionAlarm()->IsSet()); + QuicTime pto_deadline = connection_.GetRetransmissionAlarm()->deadline(); + + // PTO fires. + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1); + clock_.AdvanceTime(pto_deadline - clock_.ApproximateNow()); + connection_.GetRetransmissionAlarm()->Fire(); + // Verify the PING gets sent in ENCRYPTION_INITIAL. + EXPECT_EQ(0x01010101u, writer_->final_bytes_of_last_packet()); +} + +TEST_P(QuicConnectionTest, AckElicitingFrames) { + QuicConfig config; + config.SetConnectionOptionsToSend({kRVCM}); + EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); + connection_.SetFromConfig(config); + if (!version().HasIetfQuicFrames() || + !connection_.connection_migration_use_new_cid() || + !GetQuicReloadableFlag(quic_ack_cid_frames) || + !GetQuicReloadableFlag(quic_add_missing_update_ack_timeout)) { + return; + } + EXPECT_CALL(visitor_, SendNewConnectionId(_)).Times(2); + EXPECT_CALL(visitor_, OnRstStream(_)); + EXPECT_CALL(visitor_, OnWindowUpdateFrame(_)); + EXPECT_CALL(visitor_, OnBlockedFrame(_)); + EXPECT_CALL(visitor_, OnHandshakeDoneReceived()); + EXPECT_CALL(visitor_, OnStreamFrame(_)); + EXPECT_CALL(*send_algorithm_, OnCongestionEvent(_, _, _, _, _)); + EXPECT_CALL(visitor_, OnMaxStreamsFrame(_)); + EXPECT_CALL(visitor_, OnStreamsBlockedFrame(_)); + EXPECT_CALL(visitor_, OnStopSendingFrame(_)); + EXPECT_CALL(visitor_, OnMessageReceived("")); + EXPECT_CALL(visitor_, OnNewTokenReceived("")); + + SetClientConnectionId(TestConnectionId(12)); + connection_.CreateConnectionIdManager(); + QuicConnectionPeer::GetSelfIssuedConnectionIdManager(&connection_) + ->MaybeSendNewConnectionIds(); + connection_.set_can_receive_ack_frequency_frame(); + + QuicAckFrame ack_frame = InitAckFrame(1); + QuicRstStreamFrame rst_stream_frame; + QuicWindowUpdateFrame window_update_frame; + QuicPathChallengeFrame path_challenge_frame; + QuicNewConnectionIdFrame new_connection_id_frame; + QuicRetireConnectionIdFrame retire_connection_id_frame; + retire_connection_id_frame.sequence_number = 1u; + QuicStopSendingFrame stop_sending_frame; + QuicPathResponseFrame path_response_frame; + QuicMessageFrame message_frame; + QuicNewTokenFrame new_token_frame; + QuicAckFrequencyFrame ack_frequency_frame; + QuicBlockedFrame blocked_frame; + size_t packet_number = 1; + + connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); + + for (uint8_t i = 0; i < NUM_FRAME_TYPES; ++i) { + QuicFrameType frame_type = static_cast<QuicFrameType>(i); + bool skipped = false; + QuicFrame frame; + QuicFrames frames; + // Add some padding to fullfill the min size requirement of header + // protection. + frames.push_back(QuicFrame(QuicPaddingFrame(10))); + switch (frame_type) { + case PADDING_FRAME: + frame = QuicFrame(QuicPaddingFrame(10)); + break; + case MTU_DISCOVERY_FRAME: + frame = QuicFrame(QuicMtuDiscoveryFrame()); + break; + case PING_FRAME: + frame = QuicFrame(QuicPingFrame()); + break; + case MAX_STREAMS_FRAME: + frame = QuicFrame(QuicMaxStreamsFrame()); + break; + case STOP_WAITING_FRAME: + // Not supported. + skipped = true; + break; + case STREAMS_BLOCKED_FRAME: + frame = QuicFrame(QuicStreamsBlockedFrame()); + break; + case STREAM_FRAME: + frame = QuicFrame(QuicStreamFrame()); + break; + case HANDSHAKE_DONE_FRAME: + frame = QuicFrame(QuicHandshakeDoneFrame()); + break; + case ACK_FRAME: + frame = QuicFrame(&ack_frame); + break; + case RST_STREAM_FRAME: + frame = QuicFrame(&rst_stream_frame); + break; + case CONNECTION_CLOSE_FRAME: + // Do not test connection close. + skipped = true; + break; + case GOAWAY_FRAME: + // Does not exist in IETF QUIC. + skipped = true; + break; + case BLOCKED_FRAME: + frame = QuicFrame(&blocked_frame); + break; + case WINDOW_UPDATE_FRAME: + frame = QuicFrame(&window_update_frame); + break; + case PATH_CHALLENGE_FRAME: + frame = QuicFrame(&path_challenge_frame); + break; + case STOP_SENDING_FRAME: + frame = QuicFrame(&stop_sending_frame); + break; + case NEW_CONNECTION_ID_FRAME: + frame = QuicFrame(&new_connection_id_frame); + break; + case RETIRE_CONNECTION_ID_FRAME: + frame = QuicFrame(&retire_connection_id_frame); + break; + case PATH_RESPONSE_FRAME: + frame = QuicFrame(&path_response_frame); + break; + case MESSAGE_FRAME: + frame = QuicFrame(&message_frame); + break; + case CRYPTO_FRAME: + // CRYPTO_FRAME is ack eliciting is covered by other tests. + skipped = true; + break; + case NEW_TOKEN_FRAME: + frame = QuicFrame(&new_token_frame); + break; + case ACK_FREQUENCY_FRAME: + frame = QuicFrame(&ack_frequency_frame); + break; + case NUM_FRAME_TYPES: + skipped = true; + break; + } + if (skipped) { + continue; + } + ASSERT_EQ(frame_type, frame.type); + frames.push_back(frame); + EXPECT_FALSE(connection_.HasPendingAcks()); + // Process frame. + ProcessFramesPacketAtLevel(packet_number++, frames, + ENCRYPTION_FORWARD_SECURE); + if (QuicUtils::IsAckElicitingFrame(frame_type)) { + ASSERT_TRUE(connection_.HasPendingAcks()) << frame; + // Flush ACK. + clock_.AdvanceTime(DefaultDelayedAckTime()); + connection_.GetAckAlarm()->Fire(); + } + EXPECT_FALSE(connection_.HasPendingAcks()); + ASSERT_TRUE(connection_.connected()); } } diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_constants.h b/chromium/net/third_party/quiche/src/quic/core/quic_constants.h index b750abb3a4f..162de8909bd 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_constants.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_constants.h @@ -295,10 +295,10 @@ QUIC_EXPORT_PRIVATE extern const char* const kEPIDGoogleFrontEnd; QUIC_EXPORT_PRIVATE extern const char* const kEPIDGoogleFrontEnd0; // HTTP/3 Datagrams. -enum : QuicDatagramFlowId { - kFirstDatagramFlowIdClient = 0, - kFirstDatagramFlowIdServer = 1, - kDatagramFlowIdIncrement = 2, +enum : QuicDatagramContextId { + kFirstDatagramContextIdClient = 0, + kFirstDatagramContextIdServer = 1, + kDatagramContextIdIncrement = 2, }; } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_control_frame_manager.cc b/chromium/net/third_party/quiche/src/quic/core/quic_control_frame_manager.cc index f41a3560009..42862494745 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_control_frame_manager.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_control_frame_manager.cc @@ -17,7 +17,6 @@ #include "quic/core/quic_utils.h" #include "quic/platform/api/quic_bug_tracker.h" #include "quic/platform/api/quic_flag_utils.h" -#include "quic/platform/api/quic_map_util.h" namespace quic { @@ -167,14 +166,14 @@ void QuicControlFrameManager::OnControlFrameSent(const QuicFrame& frame) { } if (frame.type == WINDOW_UPDATE_FRAME) { QuicStreamId stream_id = frame.window_update_frame->stream_id; - if (QuicContainsKey(window_update_frames_, stream_id) && + if (window_update_frames_.contains(stream_id) && id > window_update_frames_[stream_id]) { // Consider the older window update of the same stream as acked. OnControlFrameIdAcked(window_update_frames_[stream_id]); } window_update_frames_[stream_id] = id; } - if (QuicContainsKey(pending_retransmissions_, id)) { + if (pending_retransmissions_.contains(id)) { // This is retransmitted control frame. pending_retransmissions_.erase(id); return; @@ -197,7 +196,7 @@ bool QuicControlFrameManager::OnControlFrameAcked(const QuicFrame& frame) { } if (frame.type == WINDOW_UPDATE_FRAME) { QuicStreamId stream_id = frame.window_update_frame->stream_id; - if (QuicContainsKey(window_update_frames_, stream_id) && + if (window_update_frames_.contains(stream_id) && window_update_frames_[stream_id] == id) { window_update_frames_.erase(stream_id); } @@ -223,7 +222,7 @@ void QuicControlFrameManager::OnControlFrameLost(const QuicFrame& frame) { // This frame has already been acked. return; } - if (!QuicContainsKey(pending_retransmissions_, id)) { + if (!pending_retransmissions_.contains(id)) { pending_retransmissions_[id] = true; QUIC_BUG_IF(quic_bug_12727_2, pending_retransmissions_.size() > control_frames_.size()) diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_control_frame_manager.h b/chromium/net/third_party/quiche/src/quic/core/quic_control_frame_manager.h index 48d228bff31..962733a147d 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_control_frame_manager.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_control_frame_manager.h @@ -13,6 +13,7 @@ #include "quic/core/quic_connection_id.h" #include "quic/core/quic_types.h" #include "common/quiche_circular_deque.h" +#include "common/quiche_linked_hash_map.h" namespace quic { @@ -178,7 +179,8 @@ class QUIC_EXPORT_PRIVATE QuicControlFrameManager { // TODO(fayang): switch to linked_hash_set when chromium supports it. The bool // is not used here. // Lost control frames waiting to be retransmitted. - QuicLinkedHashMap<QuicControlFrameId, bool> pending_retransmissions_; + quiche::QuicheLinkedHashMap<QuicControlFrameId, bool> + pending_retransmissions_; DelegateInterface* delegate_; diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_crypto_client_handshaker_test.cc b/chromium/net/third_party/quiche/src/quic/core/quic_crypto_client_handshaker_test.cc index 660663a9acd..ed5da6cb544 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_crypto_client_handshaker_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_crypto_client_handshaker_test.cc @@ -105,6 +105,11 @@ class DummyProofSource : public ProofSource { callback->Run(true, "Dummy signature", /*details=*/nullptr); } + absl::InlinedVector<uint16_t, 8> SupportedTlsSignatureAlgorithms() + const override { + return {}; + } + TicketCrypter* GetTicketCrypter() override { return nullptr; } }; diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_crypto_handshaker.cc b/chromium/net/third_party/quiche/src/quic/core/quic_crypto_handshaker.cc index 25bf9dcdf15..79142c4681e 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_crypto_handshaker.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_crypto_handshaker.cc @@ -27,10 +27,7 @@ void QuicCryptoHandshaker::SendHandshakeMessage( session()->OnCryptoHandshakeMessageSent(message); last_sent_handshake_message_tag_ = message.tag(); const QuicData& data = message.GetSerialized(); - stream_->WriteCryptoData(session_->use_write_or_buffer_data_at_level() - ? level - : session_->connection()->encryption_level(), - data.AsStringPiece()); + stream_->WriteCryptoData(level, data.AsStringPiece()); } void QuicCryptoHandshaker::OnError(CryptoFramer* framer) { diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_crypto_server_stream.cc b/chromium/net/third_party/quiche/src/quic/core/quic_crypto_server_stream.cc index 1dd68995e2f..9bd9c7bc2b9 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_crypto_server_stream.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_crypto_server_stream.cc @@ -287,15 +287,8 @@ void QuicCryptoServerStream::FinishSendServerConfigUpdate( QUIC_DVLOG(1) << "Server: Sending server config update: " << message.DebugString(); - if (!session()->use_write_or_buffer_data_at_level() && - !QuicVersionUsesCryptoFrames(transport_version())) { - const QuicData& data = message.GetSerialized(); - WriteOrBufferData(absl::string_view(data.data(), data.length()), false, - nullptr); - } else { - // Send server config update in ENCRYPTION_FORWARD_SECURE. - SendHandshakeMessage(message, ENCRYPTION_FORWARD_SECURE); - } + // Send server config update in ENCRYPTION_FORWARD_SECURE. + SendHandshakeMessage(message, ENCRYPTION_FORWARD_SECURE); ++num_server_config_update_messages_sent_; } diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_crypto_stream.cc b/chromium/net/third_party/quiche/src/quic/core/quic_crypto_stream.cc index f8f34dfb22c..a14cef07f27 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_crypto_stream.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_crypto_stream.cc @@ -144,15 +144,8 @@ bool QuicCryptoStream::ExportKeyingMaterial(absl::string_view label, void QuicCryptoStream::WriteCryptoData(EncryptionLevel level, absl::string_view data) { if (!QuicVersionUsesCryptoFrames(session()->transport_version())) { - if (session()->use_write_or_buffer_data_at_level()) { - WriteOrBufferDataAtLevel(data, /*fin=*/false, level, - /*ack_listener=*/nullptr); - return; - } - // The QUIC crypto handshake takes care of setting the appropriate - // encryption level before writing data. Since that is the only handshake - // supported in versions less than 47, |level| can be ignored here. - WriteOrBufferData(data, /* fin */ false, /* ack_listener */ nullptr); + WriteOrBufferDataAtLevel(data, /*fin=*/false, level, + /*ack_listener=*/nullptr); return; } if (data.empty()) { diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_datagram_queue.cc b/chromium/net/third_party/quiche/src/quic/core/quic_datagram_queue.cc index d4513e48f69..e6f3e6f427a 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_datagram_queue.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_datagram_queue.cc @@ -4,6 +4,7 @@ #include "quic/core/quic_datagram_queue.h" +#include "absl/types/span.h" #include "quic/core/quic_constants.h" #include "quic/core/quic_session.h" #include "quic/core/quic_time.h" @@ -30,8 +31,7 @@ MessageStatus QuicDatagramQueue::SendOrQueueDatagram(QuicMemSlice datagram) { // the datagrams are sent in the same order that they were sent by the // application. if (queue_.empty()) { - QuicMemSliceSpan span(&datagram); - MessageResult result = session_->SendMessage(span); + MessageResult result = session_->SendMessage(absl::MakeSpan(&datagram, 1)); if (result.status != MESSAGE_STATUS_BLOCKED) { if (observer_) { observer_->OnDatagramProcessed(result.status); @@ -51,8 +51,8 @@ absl::optional<MessageStatus> QuicDatagramQueue::TrySendingNextDatagram() { return absl::nullopt; } - QuicMemSliceSpan span(&queue_.front().datagram); - MessageResult result = session_->SendMessage(span); + MessageResult result = + session_->SendMessage(absl::MakeSpan(&queue_.front().datagram, 1)); if (result.status != MESSAGE_STATUS_BLOCKED) { queue_.pop_front(); if (observer_) { diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_datagram_queue_test.cc b/chromium/net/third_party/quiche/src/quic/core/quic_datagram_queue_test.cc index f47bbb883cb..68a876b4cd0 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_datagram_queue_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_datagram_queue_test.cc @@ -176,8 +176,9 @@ TEST_F(QuicDatagramQueueTest, Expiry) { std::vector<std::string> messages; EXPECT_CALL(*connection_, SendMessage(_, _, _)) .WillRepeatedly([&messages](QuicMessageId /*id*/, - QuicMemSliceSpan message, bool /*flush*/) { - messages.push_back(std::string(message.GetData(0))); + absl::Span<QuicMemSlice> message, + bool /*flush*/) { + messages.push_back(std::string(message[0].AsStringView())); return MESSAGE_STATUS_SUCCESS; }); EXPECT_EQ(2u, queue_.SendDatagrams()); diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_dispatcher.cc b/chromium/net/third_party/quiche/src/quic/core/quic_dispatcher.cc index e1a999c706a..11f4bcd4b3d 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_dispatcher.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_dispatcher.cc @@ -205,6 +205,10 @@ class ChloAlpnSniExtractor : public ChloExtractor::Delegate { if (chlo.GetStringPiece(quic::kSNI, &sni)) { sni_ = std::string(sni); } + absl::string_view uaid_value; + if (chlo.GetStringPiece(quic::kUAID, &uaid_value)) { + uaid_ = std::string(uaid_value); + } if (version == LegacyVersionForEncapsulation().transport_version) { absl::string_view qlve_value; if (chlo.GetStringPiece(kQLVE, &qlve_value)) { @@ -217,6 +221,8 @@ class ChloAlpnSniExtractor : public ChloExtractor::Delegate { std::string&& ConsumeSni() { return std::move(sni_); } + std::string&& ConsumeUaid() { return std::move(uaid_); } + std::string&& ConsumeLegacyVersionEncapsulationInnerPacket() { return std::move(legacy_version_encapsulation_inner_packet_); } @@ -224,15 +230,14 @@ class ChloAlpnSniExtractor : public ChloExtractor::Delegate { private: std::string alpn_; std::string sni_; + std::string uaid_; std::string legacy_version_encapsulation_inner_packet_; }; bool MaybeHandleLegacyVersionEncapsulation( QuicDispatcher* dispatcher, - ChloAlpnSniExtractor* alpn_extractor, + std::string legacy_version_encapsulation_inner_packet, const ReceivedPacketInfo& packet_info) { - std::string legacy_version_encapsulation_inner_packet = - alpn_extractor->ConsumeLegacyVersionEncapsulationInnerPacket(); if (legacy_version_encapsulation_inner_packet.empty()) { // This CHLO did not contain the Legacy Version Encapsulation tag. return false; @@ -335,9 +340,14 @@ QuicDispatcher::QuicDispatcher( << "Trying to create dispatcher without any supported versions"; QUIC_DLOG(INFO) << "Created QuicDispatcher with versions: " << ParsedQuicVersionVectorToString(GetSupportedVersions()); + QUIC_RESTART_FLAG_COUNT(quic_alarm_add_permanent_cancel); } QuicDispatcher::~QuicDispatcher() { + if (GetQuicRestartFlag(quic_alarm_add_permanent_cancel) && + delete_sessions_alarm_ != nullptr) { + delete_sessions_alarm_->PermanentCancel(); + } reference_counted_session_map_.clear(); closed_ref_counted_session_list_.clear(); if (support_multiple_cid_per_connection_) { @@ -532,8 +542,10 @@ bool QuicDispatcher::MaybeDispatchPacket( config_->create_session_tag_indicators(), &alpn_extractor, server_connection_id.length())) { - if (MaybeHandleLegacyVersionEncapsulation(this, &alpn_extractor, - packet_info)) { + if (MaybeHandleLegacyVersionEncapsulation( + this, + alpn_extractor.ConsumeLegacyVersionEncapsulationInnerPacket(), + packet_info)) { return true; } } @@ -647,76 +659,38 @@ void QuicDispatcher::ProcessHeader(ReceivedPacketInfo* packet_info) { packet_info->destination_connection_id; // Packet's connection ID is unknown. Apply the validity checks. QuicPacketFate fate = ValidityChecks(*packet_info); - ChloAlpnSniExtractor alpn_extractor; - switch (fate) { - case kFateProcess: { - if (packet_info->version.handshake_protocol == PROTOCOL_TLS1_3) { - bool has_full_tls_chlo = false; - std::string sni; - std::vector<std::string> alpns; - if (buffered_packets_.HasBufferedPackets( - packet_info->destination_connection_id)) { - // If we already have buffered packets for this connection ID, - // use the associated TlsChloExtractor to parse this packet. - has_full_tls_chlo = - buffered_packets_.IngestPacketForTlsChloExtraction( - packet_info->destination_connection_id, packet_info->version, - packet_info->packet, &alpns, &sni); - } else { - // If we do not have a BufferedPacketList for this connection ID, - // create a single-use one to check whether this packet contains a - // full single-packet CHLO. - TlsChloExtractor tls_chlo_extractor; - tls_chlo_extractor.IngestPacket(packet_info->version, - packet_info->packet); - if (tls_chlo_extractor.HasParsedFullChlo()) { - // This packet contains a full single-packet CHLO. - has_full_tls_chlo = true; - alpns = tls_chlo_extractor.alpns(); - sni = tls_chlo_extractor.server_name(); - } - } - if (has_full_tls_chlo) { - ProcessChlo(alpns, sni, packet_info); - } else { - // This packet does not contain a full CHLO. It could be a 0-RTT - // packet that arrived before the CHLO (due to loss or reordering), - // or it could be a fragment of a multi-packet CHLO. - BufferEarlyPacket(*packet_info); - } - break; - } - if (GetQuicFlag(FLAGS_quic_allow_chlo_buffering) && - !ChloExtractor::Extract(packet_info->packet, packet_info->version, - config_->create_session_tag_indicators(), - &alpn_extractor, - server_connection_id.length())) { - // Buffer non-CHLO packets. - BufferEarlyPacket(*packet_info); - break; - } - // We only apply this check for versions that do not use the IETF - // invariant header because those versions are already checked in - // QuicDispatcher::MaybeDispatchPacket. - if (packet_info->version_flag && - !packet_info->version.HasIetfInvariantHeader() && - crypto_config()->validate_chlo_size() && - packet_info->packet.length() < kMinClientInitialPacketLength) { - QUIC_DVLOG(1) << "Dropping CHLO packet which is too short, length: " - << packet_info->packet.length(); - QUIC_CODE_COUNT(quic_drop_small_chlo_packets); - break; - } + if (fate == kFateProcess) { + std::string sni, uaid, legacy_version_encapsulation_inner_packet; + std::vector<std::string> alpns; + if (!TryExtractChloOrBufferEarlyPacket( + *packet_info, &sni, &uaid, &alpns, + &legacy_version_encapsulation_inner_packet)) { + // Client Hello incomplete. Packet has been buffered or (rarely) dropped. + return; + } + + // Client Hello fully received. + fate = ValidityChecksOnFullChlo(*packet_info, sni, uaid, alpns); - if (MaybeHandleLegacyVersionEncapsulation(this, &alpn_extractor, - *packet_info)) { - break; + if (fate == kFateProcess) { + QUICHE_DCHECK(legacy_version_encapsulation_inner_packet.empty() || + !packet_info->version.UsesTls()); + if (MaybeHandleLegacyVersionEncapsulation( + this, legacy_version_encapsulation_inner_packet, *packet_info)) { + return; } - ProcessChlo({alpn_extractor.ConsumeAlpn()}, alpn_extractor.ConsumeSni(), - packet_info); - } break; + ProcessChlo(alpns, sni, packet_info); + return; + } + } + + switch (fate) { + case kFateProcess: + // kFateProcess have been processed above. + QUIC_BUG(quic_dispatcher_bad_packet_fate) << fate; + break; case kFateTimeWait: // Add this connection_id to the time-wait state, to safely reject // future packets. @@ -743,6 +717,81 @@ void QuicDispatcher::ProcessHeader(ReceivedPacketInfo* packet_info) { } } +bool QuicDispatcher::TryExtractChloOrBufferEarlyPacket( + const ReceivedPacketInfo& packet_info, + std::string* sni, + std::string* uaid, + std::vector<std::string>* alpns, + std::string* legacy_version_encapsulation_inner_packet) { + sni->clear(); + uaid->clear(); + alpns->clear(); + legacy_version_encapsulation_inner_packet->clear(); + + if (packet_info.version.UsesTls()) { + bool has_full_tls_chlo = false; + if (buffered_packets_.HasBufferedPackets( + packet_info.destination_connection_id)) { + // If we already have buffered packets for this connection ID, + // use the associated TlsChloExtractor to parse this packet. + has_full_tls_chlo = buffered_packets_.IngestPacketForTlsChloExtraction( + packet_info.destination_connection_id, packet_info.version, + packet_info.packet, alpns, sni); + } else { + // If we do not have a BufferedPacketList for this connection ID, + // create a single-use one to check whether this packet contains a + // full single-packet CHLO. + TlsChloExtractor tls_chlo_extractor; + tls_chlo_extractor.IngestPacket(packet_info.version, packet_info.packet); + if (tls_chlo_extractor.HasParsedFullChlo()) { + // This packet contains a full single-packet CHLO. + has_full_tls_chlo = true; + *alpns = tls_chlo_extractor.alpns(); + *sni = tls_chlo_extractor.server_name(); + } + } + if (!has_full_tls_chlo) { + // This packet does not contain a full CHLO. It could be a 0-RTT + // packet that arrived before the CHLO (due to loss or reordering), + // or it could be a fragment of a multi-packet CHLO. + BufferEarlyPacket(packet_info); + } + + return has_full_tls_chlo; + } + + ChloAlpnSniExtractor alpn_extractor; + if (GetQuicFlag(FLAGS_quic_allow_chlo_buffering) && + !ChloExtractor::Extract(packet_info.packet, packet_info.version, + config_->create_session_tag_indicators(), + &alpn_extractor, + packet_info.destination_connection_id.length())) { + // Buffer non-CHLO packets. + BufferEarlyPacket(packet_info); + return false; + } + + // We only apply this check for versions that do not use the IETF + // invariant header because those versions are already checked in + // QuicDispatcher::MaybeDispatchPacket. + if (packet_info.version_flag && + !packet_info.version.HasIetfInvariantHeader() && + crypto_config()->validate_chlo_size() && + packet_info.packet.length() < kMinClientInitialPacketLength) { + QUIC_DVLOG(1) << "Dropping CHLO packet which is too short, length: " + << packet_info.packet.length(); + QUIC_CODE_COUNT(quic_drop_small_chlo_packets); + return false; + } + + *legacy_version_encapsulation_inner_packet = + alpn_extractor.ConsumeLegacyVersionEncapsulationInnerPacket(); + *sni = alpn_extractor.ConsumeSni(); + *uaid = alpn_extractor.ConsumeUaid(); + *alpns = {alpn_extractor.ConsumeAlpn()}; + return true; +} + std::string QuicDispatcher::SelectAlpn(const std::vector<std::string>& alpns) { if (alpns.empty()) { return ""; @@ -1015,7 +1064,8 @@ void QuicDispatcher::OnNewConnectionIdSent( << server_connection_id << " new_connection_id: " << new_connection_id; return; } - QUIC_RELOADABLE_FLAG_COUNT_N(quic_connection_migration_use_new_cid_v2, 5, 5); + // Count new connection ID added to the dispatcher map. + QUIC_RELOADABLE_FLAG_COUNT_N(quic_connection_migration_use_new_cid_v2, 6, 6); auto insertion_result = reference_counted_session_map_.insert( std::make_pair(new_connection_id, it->second)); QUICHE_DCHECK(insertion_result.second); diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_dispatcher.h b/chromium/net/third_party/quiche/src/quic/core/quic_dispatcher.h index 053f856da98..a6ba1a6e975 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_dispatcher.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_dispatcher.h @@ -27,9 +27,9 @@ #include "quic/core/quic_session.h" #include "quic/core/quic_time_wait_list_manager.h" #include "quic/core/quic_version_manager.h" -#include "quic/platform/api/quic_containers.h" #include "quic/platform/api/quic_reference_counted.h" #include "quic/platform/api/quic_socket_address.h" +#include "common/quiche_linked_hash_map.h" namespace quic { namespace test { @@ -45,7 +45,8 @@ class QUIC_NO_EXPORT QuicDispatcher public QuicBufferedPacketStore::VisitorInterface { public: // Ideally we'd have a linked_hash_set: the boolean is unused. - using WriteBlockedList = QuicLinkedHashMap<QuicBlockedWriterInterface*, bool>; + using WriteBlockedList = + quiche::QuicheLinkedHashMap<QuicBlockedWriterInterface*, bool>; QuicDispatcher( const QuicConfig* config, @@ -120,10 +121,6 @@ class QUIC_NO_EXPORT QuicDispatcher void OnConnectionAddedToTimeWaitList( QuicConnectionId server_connection_id) override; - using SessionMap = absl::flat_hash_map<QuicConnectionId, - std::unique_ptr<QuicSession>, - QuicConnectionIdHash>; - using ReferenceCountedSessionMap = absl::flat_hash_map<QuicConnectionId, std::shared_ptr<QuicSession>, @@ -131,8 +128,6 @@ class QUIC_NO_EXPORT QuicDispatcher size_t NumSessions() const; - const SessionMap& session_map() const { return session_map_; } - // Deletes all sessions on the closed session list and clears the list. virtual void DeleteSessions(); @@ -229,6 +224,17 @@ class QUIC_NO_EXPORT QuicDispatcher // TODO(fayang): Merge ValidityChecks into MaybeDispatchPacket. virtual QuicPacketFate ValidityChecks(const ReceivedPacketInfo& packet_info); + // Extra validity checks after the full Client Hello is parsed, this allows + // subclasses to reject a connection based on sni or alpn. + // Only called if ValidityChecks returns kFateProcess. + virtual QuicPacketFate ValidityChecksOnFullChlo( + const ReceivedPacketInfo& /*packet_info*/, + const std::string& /*sni*/, + const std::string& /*uaid*/, + const std::vector<std::string>& /*alpns*/) const { + return kFateProcess; + } + // Create and return the time wait list manager for this dispatcher, which // will be owned by the dispatcher as time_wait_list_manager_ virtual QuicTimeWaitListManager* CreateQuicTimeWaitListManager(); @@ -268,6 +274,10 @@ class QUIC_NO_EXPORT QuicDispatcher return session_helper_.get(); } + const QuicCryptoServerStreamBase::Helper* session_helper() const { + return session_helper_.get(); + } + QuicAlarmFactory* alarm_factory() { return alarm_factory_.get(); } QuicPacketWriter* writer() { return writer_.get(); } @@ -371,6 +381,21 @@ class QUIC_NO_EXPORT QuicDispatcher // ProcessValidatedPacketWithUnknownConnectionId. void ProcessHeader(ReceivedPacketInfo* packet_info); + // Try to extract information(sni, alpns, ...) if the full Client Hello has + // been parsed. + // + // If the full Client Hello has been parsed, return true and set |sni|, + // |alpns| and |legacy_version_encapsulation_inner_packet|. |uaid| will be + // populated for QUIC_CRYPTO only. + // + // Otherwise return false and either buffer or (rarely) drop the packet. + bool TryExtractChloOrBufferEarlyPacket( + const ReceivedPacketInfo& packet_info, + std::string* sni, + std::string* uaid, + std::vector<std::string>* alpns, + std::string* legacy_version_encapsulation_inner_packet); + // Deliver |packets| to |session| for further processing. void DeliverPacketsToSession( const std::list<QuicBufferedPacketStore::BufferedPacket>& packets, @@ -389,7 +414,6 @@ class QUIC_NO_EXPORT QuicDispatcher // The list of connections waiting to write. WriteBlockedList write_blocked_list_; - SessionMap session_map_; ReferenceCountedSessionMap reference_counted_session_map_; // Entity that manages connection_ids in time wait state. diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_dispatcher_test.cc b/chromium/net/third_party/quiche/src/quic/core/quic_dispatcher_test.cc index 5a7a709f91b..3a98be3df90 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_dispatcher_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_dispatcher_test.cc @@ -924,10 +924,12 @@ TEST_P(QuicDispatcherTestAllVersions, QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1); CreateTimeWaitListManager(); - char short_packet[21] = {0x70, 0xa7, 0x02, 0x6b}; - QuicReceivedPacket packet(short_packet, 21, QuicTime::Zero()); - char valid_size_packet[23] = {0x70, 0xa7, 0x02, 0x6c}; - QuicReceivedPacket packet2(valid_size_packet, 23, QuicTime::Zero()); + uint8_t short_packet[21] = {0x70, 0xa7, 0x02, 0x6b}; + QuicReceivedPacket packet(reinterpret_cast<char*>(short_packet), 21, + QuicTime::Zero()); + uint8_t valid_size_packet[23] = {0x70, 0xa7, 0x02, 0x6c}; + QuicReceivedPacket packet2(reinterpret_cast<char*>(valid_size_packet), 23, + QuicTime::Zero()); EXPECT_CALL(*dispatcher_, CreateQuicSession(_, _, _, _, _, _)).Times(0); EXPECT_CALL(*time_wait_list_manager_, ProcessPacket(_, _, _, _, _, _)) .Times(0); @@ -1186,10 +1188,10 @@ TEST_P(QuicDispatcherTestOneVersion, RejectDeprecatedVersionDraft28WithVersionNegotiation) { QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1); CreateTimeWaitListManager(); - char packet[kMinPacketSizeForVersionNegotiation] = { + uint8_t packet[kMinPacketSizeForVersionNegotiation] = { 0xC0, 0xFF, 0x00, 0x00, 28, /*destination connection ID length*/ 0x08}; - QuicReceivedPacket received_packet(packet, ABSL_ARRAYSIZE(packet), - QuicTime::Zero()); + QuicReceivedPacket received_packet(reinterpret_cast<char*>(packet), + ABSL_ARRAYSIZE(packet), QuicTime::Zero()); EXPECT_CALL(*dispatcher_, CreateQuicSession(_, _, _, _, _, _)).Times(0); EXPECT_CALL( *time_wait_list_manager_, @@ -1203,10 +1205,10 @@ TEST_P(QuicDispatcherTestOneVersion, RejectDeprecatedVersionDraft27WithVersionNegotiation) { QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1); CreateTimeWaitListManager(); - char packet[kMinPacketSizeForVersionNegotiation] = { + uint8_t packet[kMinPacketSizeForVersionNegotiation] = { 0xC0, 0xFF, 0x00, 0x00, 27, /*destination connection ID length*/ 0x08}; - QuicReceivedPacket received_packet(packet, ABSL_ARRAYSIZE(packet), - QuicTime::Zero()); + QuicReceivedPacket received_packet(reinterpret_cast<char*>(packet), + ABSL_ARRAYSIZE(packet), QuicTime::Zero()); EXPECT_CALL(*dispatcher_, CreateQuicSession(_, _, _, _, _, _)).Times(0); EXPECT_CALL( *time_wait_list_manager_, @@ -1220,10 +1222,10 @@ TEST_P(QuicDispatcherTestOneVersion, RejectDeprecatedVersionDraft25WithVersionNegotiation) { QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1); CreateTimeWaitListManager(); - char packet[kMinPacketSizeForVersionNegotiation] = { + uint8_t packet[kMinPacketSizeForVersionNegotiation] = { 0xC0, 0xFF, 0x00, 0x00, 25, /*destination connection ID length*/ 0x08}; - QuicReceivedPacket received_packet(packet, ABSL_ARRAYSIZE(packet), - QuicTime::Zero()); + QuicReceivedPacket received_packet(reinterpret_cast<char*>(packet), + ABSL_ARRAYSIZE(packet), QuicTime::Zero()); EXPECT_CALL(*dispatcher_, CreateQuicSession(_, _, _, _, _, _)).Times(0); EXPECT_CALL( *time_wait_list_manager_, @@ -1237,10 +1239,10 @@ TEST_P(QuicDispatcherTestOneVersion, RejectDeprecatedVersionT050WithVersionNegotiation) { QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1); CreateTimeWaitListManager(); - char packet[kMinPacketSizeForVersionNegotiation] = { + uint8_t packet[kMinPacketSizeForVersionNegotiation] = { 0xC0, 'T', '0', '5', '0', /*destination connection ID length*/ 0x08}; - QuicReceivedPacket received_packet(packet, ABSL_ARRAYSIZE(packet), - QuicTime::Zero()); + QuicReceivedPacket received_packet(reinterpret_cast<char*>(packet), + ABSL_ARRAYSIZE(packet), QuicTime::Zero()); EXPECT_CALL(*dispatcher_, CreateQuicSession(_, _, _, _, _, _)).Times(0); EXPECT_CALL( *time_wait_list_manager_, @@ -1254,10 +1256,10 @@ TEST_P(QuicDispatcherTestOneVersion, RejectDeprecatedVersionQ049WithVersionNegotiation) { QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1); CreateTimeWaitListManager(); - char packet[kMinPacketSizeForVersionNegotiation] = { + uint8_t packet[kMinPacketSizeForVersionNegotiation] = { 0xC0, 'Q', '0', '4', '9', /*destination connection ID length*/ 0x08}; - QuicReceivedPacket received_packet(packet, ABSL_ARRAYSIZE(packet), - QuicTime::Zero()); + QuicReceivedPacket received_packet(reinterpret_cast<char*>(packet), + ABSL_ARRAYSIZE(packet), QuicTime::Zero()); EXPECT_CALL(*dispatcher_, CreateQuicSession(_, _, _, _, _, _)).Times(0); EXPECT_CALL( *time_wait_list_manager_, @@ -1271,10 +1273,10 @@ TEST_P(QuicDispatcherTestOneVersion, RejectDeprecatedVersionQ048WithVersionNegotiation) { QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1); CreateTimeWaitListManager(); - char packet[kMinPacketSizeForVersionNegotiation] = { + uint8_t packet[kMinPacketSizeForVersionNegotiation] = { 0xC0, 'Q', '0', '4', '8', /*connection ID length byte*/ 0x50}; - QuicReceivedPacket received_packet(packet, ABSL_ARRAYSIZE(packet), - QuicTime::Zero()); + QuicReceivedPacket received_packet(reinterpret_cast<char*>(packet), + ABSL_ARRAYSIZE(packet), QuicTime::Zero()); EXPECT_CALL(*dispatcher_, CreateQuicSession(_, _, _, _, _, _)).Times(0); EXPECT_CALL( *time_wait_list_manager_, @@ -1288,10 +1290,10 @@ TEST_P(QuicDispatcherTestOneVersion, RejectDeprecatedVersionQ047WithVersionNegotiation) { QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1); CreateTimeWaitListManager(); - char packet[kMinPacketSizeForVersionNegotiation] = { + uint8_t packet[kMinPacketSizeForVersionNegotiation] = { 0xC0, 'Q', '0', '4', '7', /*connection ID length byte*/ 0x50}; - QuicReceivedPacket received_packet(packet, ABSL_ARRAYSIZE(packet), - QuicTime::Zero()); + QuicReceivedPacket received_packet(reinterpret_cast<char*>(packet), + ABSL_ARRAYSIZE(packet), QuicTime::Zero()); EXPECT_CALL(*dispatcher_, CreateQuicSession(_, _, _, _, _, _)).Times(0); EXPECT_CALL( *time_wait_list_manager_, @@ -1305,10 +1307,10 @@ TEST_P(QuicDispatcherTestOneVersion, RejectDeprecatedVersionQ045WithVersionNegotiation) { QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1); CreateTimeWaitListManager(); - char packet[kMinPacketSizeForVersionNegotiation] = { + uint8_t packet[kMinPacketSizeForVersionNegotiation] = { 0xC0, 'Q', '0', '4', '5', /*connection ID length byte*/ 0x50}; - QuicReceivedPacket received_packet(packet, ABSL_ARRAYSIZE(packet), - QuicTime::Zero()); + QuicReceivedPacket received_packet(reinterpret_cast<char*>(packet), + ABSL_ARRAYSIZE(packet), QuicTime::Zero()); EXPECT_CALL(*dispatcher_, CreateQuicSession(_, _, _, _, _, _)).Times(0); EXPECT_CALL( *time_wait_list_manager_, @@ -1322,10 +1324,11 @@ TEST_P(QuicDispatcherTestOneVersion, RejectDeprecatedVersionQ044WithVersionNegotiation) { QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1); CreateTimeWaitListManager(); - char packet44[kMinPacketSizeForVersionNegotiation] = { + uint8_t packet44[kMinPacketSizeForVersionNegotiation] = { 0xFF, 'Q', '0', '4', '4', /*connection ID length byte*/ 0x50}; - QuicReceivedPacket received_packet44( - packet44, kMinPacketSizeForVersionNegotiation, QuicTime::Zero()); + QuicReceivedPacket received_packet44(reinterpret_cast<char*>(packet44), + kMinPacketSizeForVersionNegotiation, + QuicTime::Zero()); EXPECT_CALL(*dispatcher_, CreateQuicSession(_, _, _, _, _, _)).Times(0); EXPECT_CALL( *time_wait_list_manager_, @@ -1549,7 +1552,7 @@ TEST_P(QuicDispatcherTestAllVersions, ProcessSmallCoalescedPacket) { EXPECT_CALL(*time_wait_list_manager_, SendPacket(_, _, _)).Times(0); // clang-format off - char coalesced_packet[1200] = { + uint8_t coalesced_packet[1200] = { // first coalesced packet // public flags (long header with packet type INITIAL and // 4-byte packet number) @@ -1586,7 +1589,8 @@ TEST_P(QuicDispatcherTestAllVersions, ProcessSmallCoalescedPacket) { 0x12, 0x34, 0x56, 0x79, }; // clang-format on - QuicReceivedPacket packet(coalesced_packet, 1200, QuicTime::Zero()); + QuicReceivedPacket packet(reinterpret_cast<char*>(coalesced_packet), 1200, + QuicTime::Zero()); dispatcher_->ProcessPacket(server_address_, client_address, packet); } diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_error_codes.cc b/chromium/net/third_party/quiche/src/quic/core/quic_error_codes.cc index e82e7cb2eab..c3fcb887066 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_error_codes.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_error_codes.cc @@ -238,6 +238,7 @@ const char* QuicErrorCodeToString(QuicErrorCode error) { RETURN_STRING_LITERAL(QUIC_HTTP_GOAWAY_ID_LARGER_THAN_PREVIOUS); RETURN_STRING_LITERAL(QUIC_HTTP_RECEIVE_SPDY_SETTING); RETURN_STRING_LITERAL(QUIC_HTTP_RECEIVE_SPDY_FRAME); + RETURN_STRING_LITERAL(QUIC_HTTP_RECEIVE_SERVER_PUSH); RETURN_STRING_LITERAL(QUIC_HPACK_INDEX_VARINT_ERROR); RETURN_STRING_LITERAL(QUIC_HPACK_NAME_LENGTH_VARINT_ERROR); RETURN_STRING_LITERAL(QUIC_HPACK_VALUE_LENGTH_VARINT_ERROR); @@ -678,6 +679,9 @@ QuicErrorCodeToIetfMapping QuicErrorCodeToTransportErrorCode( case QUIC_HTTP_STREAM_LIMIT_TOO_LOW: return {false, static_cast<uint64_t>( QuicHttp3ErrorCode::GENERAL_PROTOCOL_ERROR)}; + case QUIC_HTTP_RECEIVE_SERVER_PUSH: + return {false, static_cast<uint64_t>( + QuicHttp3ErrorCode::GENERAL_PROTOCOL_ERROR)}; case QUIC_HTTP_ZERO_RTT_RESUMPTION_SETTINGS_MISMATCH: return {false, static_cast<uint64_t>(QuicHttp3ErrorCode::SETTINGS_ERROR)}; case QUIC_HTTP_ZERO_RTT_REJECTION_SETTINGS_MISMATCH: diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_error_codes.h b/chromium/net/third_party/quiche/src/quic/core/quic_error_codes.h index 9fa422f578c..53a881014ce 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_error_codes.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_error_codes.h @@ -510,6 +510,9 @@ enum QuicErrorCode { QUIC_HTTP_RECEIVE_SPDY_SETTING = 169, // HTTP/3 session received an HTTP/2 only frame. QUIC_HTTP_RECEIVE_SPDY_FRAME = 171, + // HTTP/3 session received SERVER_PUSH stream, which is an error because + // PUSH_PROMISE is not accepted. + QUIC_HTTP_RECEIVE_SERVER_PUSH = 205, // HPACK header block decoding errors. // Index varint beyond implementation limit. @@ -597,7 +600,7 @@ enum QuicErrorCode { QUIC_TLS_CERTIFICATE_REQUIRED = 202, // No error. Used as bound while iterating. - QUIC_LAST_ERROR = 205, + QUIC_LAST_ERROR = 206, }; // QuicErrorCodes is encoded as four octets on-the-wire when doing Google QUIC, // or a varint62 when doing IETF QUIC. Ensure that its value does not exceed diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_flags_list.h b/chromium/net/third_party/quiche/src/quic/core/quic_flags_list.h index df0df50f3fc..9bb54b26abd 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_flags_list.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_flags_list.h @@ -17,30 +17,30 @@ QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_testonly_default_true, true) QUIC_FLAG(FLAGS_quic_restart_flag_quic_testonly_default_false, false) // A testonly restart flag that will always default to true. QUIC_FLAG(FLAGS_quic_restart_flag_quic_testonly_default_true, true) -// Fix QUIC BBRv2\'s bandwidth_lo modes to better approximate packet conservation. -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_fix_bw_lo_mode2, false) -// If true, GFE will consider SNI values which do not contain dots (instead of throwing them away - see b/176998377). -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_and_tls_allow_sni_without_dots, true) -// If true, QUIC BBRv2\'s PROBE_BW mode will not reduce cwnd below BDP+ack_height. -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_avoid_too_low_probe_bw_cwnd, false) +// If true and a QUIC connection is traced, add ssl events to the trace. +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_trace_ssl_events, true) +// If true, GFE will explicitly configure its signature algorithm preference. +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_tls_set_signature_algorithm_prefs, false) // If true, QUIC will default enable MTU discovery at server, with a target of 1450 bytes. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_enable_mtu_discovery_at_server, false) -// If true, QuicBatchWriterBase will mark the writer as blocked when the write status is WRITE_STATUS_BLOCKED_DATA_BUFFERED. -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_batch_writer_fix_write_blocked, true) // If true, QuicGsoBatchWriter will support release time if it is available and the process has the permission to do so. QUIC_FLAG(FLAGS_quic_restart_flag_quic_support_release_time_for_gso, false) -// If true, TlsHandshaker::AdvanceHandshake will retry SSL_do_handshake when entered early data. -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_tls_retry_handshake_on_early_data, true) -// If true, TlsServerHandshaker will install a packet flusher when async operation completes. -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_add_packet_flusher_on_async_op_done, true) -// If true, TlsServerHandshaker will use handshake hints(if present) to speed up handshakes. -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_tls_server_use_handshake_hints, true) +// If true, QuicIdleNetworkDetector::SetAlarm will become a noop if dectection has been stopped by QuicIdleNetworkDetector::StopDetection. +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_idle_network_detector_no_alarm_after_stopped, true) +// If true, TlsServerHandshaker::DefaultProofSourceHandle::DefaultSignatureCallback will run at most once. +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_run_default_signature_callback_once, true) // If true, abort async QPACK header decompression in QuicSpdyStream::Reset() and in QuicSpdyStream::OnStreamReset(). QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_abort_qpack_on_stream_reset, true) // If true, ack frequency frame can be sent from server to client. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_can_send_ack_frequency, true) +// If true, add missing MaybeUpdateAckTimeout for ack-eliciting frames. +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_add_missing_update_ack_timeout, true) +// If true, allow QuicAlarm to be permanently cancelled. +QUIC_FLAG(FLAGS_quic_restart_flag_quic_alarm_add_permanent_cancel, false) // If true, allow client to enable BBRv2 on server via connection option \'B2ON\'. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_allow_client_enabled_bbr_v2, false) +// If true, allow ticket open to be ignored in TlsServerHandshaker. Also fixes TlsServerHandshaker::ResumptionAttempted when handshake hints is used. +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_tls_allow_ignore_ticket_open, true) // If true, close read side but not write side in QuicSpdyStream::OnStreamReset(). QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_on_stream_reset, true) // If true, default on PTO which unifies TLP + RTO loss recovery. @@ -48,13 +48,15 @@ QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_default_on_pto, false) // If true, default-enable 5RTO blachole detection. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_default_enable_5rto_blackhole_detection2, true) // If true, determine stateless reset packet length based on the received packet length. -QUIC_FLAG(FLAGS_quic_restart_flag_quic_fix_stateless_reset2, false) +QUIC_FLAG(FLAGS_quic_restart_flag_quic_fix_stateless_reset2, true) // If true, disable QUIC version Q043. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_disable_version_q043, false) // If true, disable QUIC version Q046. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_disable_version_q046, false) // If true, disable QUIC version Q050. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_disable_version_q050, false) +// If true, disable QUIC version h3 (RFCv1). +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_disable_version_rfcv1, false) // If true, disable QUIC version h3-29. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_disable_version_draft_29, false) // If true, disable QUIC version h3-T051. @@ -67,70 +69,62 @@ QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_discard_initial_packet_with_key_droppe QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_single_ack_in_packet2, false) // If true, do not count bytes sent/received on the alternative path into the bytes sent/received on the default path. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_count_bytes_on_alternative_path_seperately, true) -// If true, do not send control frames before encryption is established. -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_encrypted_control_frames, false) -// If true, do not send stream data when PTO fires. -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_donot_pto_half_rtt_data, true) -// If true, do not write stream data and control frames in the middle of packet processing. -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_donot_write_mid_packet_processing, true) +// If true, do not re-arm PTO while sending application data during handshake. +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_donot_rearm_pto_on_application_data_during_handshake, true) // If true, drop unsent PATH_RESPONSEs and rely on peer\'s retry. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_drop_unsent_path_response, true) -// If true, enable QUIC version h3 (RFCv1). -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_enable_version_rfcv1, false) // If true, enable server retransmittable on wire PING. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_enable_server_on_wire_ping, true) +// If true, ignore peer_max_ack_delay during handshake. +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_ignore_peer_max_ack_delay_during_handshake, true) +// If true, include non-default port in the origin field of the ACCEPT_CH frame in ALPS. +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_include_port_in_alps_origin, true) // If true, include stream information in idle timeout connection close detail. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_add_stream_info_to_idle_close_detail, true) // If true, increase the size of stream sequencer buffer block container on demand. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_allocate_stream_sequencer_buffer_blocks_on_demand, false) // If true, pass the received PATH_RESPONSE payload to path validator to move forward the path validation. -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_pass_path_response_to_validator, false) +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_pass_path_response_to_validator, true) // If true, quic connection sends/recieives NewConnectionId & RetireConnectionId frames. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_connection_support_multiple_cids_v4, true) // If true, quic dispatcher discards packets with invalid server connection ID. -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_discard_packets_with_invalid_cid, false) +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_discard_packets_with_invalid_cid, true) // If true, quic dispatcher supports one connection to use multiple connection IDs. QUIC_FLAG(FLAGS_quic_restart_flag_quic_dispatcher_support_multiple_cid_per_connection_v2, true) +// If true, receiving server push stream will trigger QUIC connection close. +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_decline_server_push_stream, false) // If true, require handshake confirmation for QUIC connections, functionally disabling 0-rtt handshakes. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_require_handshake_confirmation, false) +// If true, reset per packet state before processing undecryptable packets. +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_reset_per_packet_state_for_undecryptable_packets, true) // If true, send PATH_RESPONSE upon receiving PATH_CHALLENGE regardless of perspective. --gfe2_reloadable_flag_quic_start_peer_migration_earlier has to be true before turn on this flag. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_send_path_response2, true) // If true, set burst token to 2 in cwnd bootstrapping experiment. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_conservative_bursts, false) -// If true, signal error in HttpDecoder upon receiving a PUSH_PROMISE or CANCEL_PUSH frame. -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_error_on_http3_push, true) // If true, stop resetting ideal_next_packet_send_time_ in pacing sender. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_donot_reset_ideal_next_packet_send_time, false) // If true, time_wait_list can support multiple connection IDs. QUIC_FLAG(FLAGS_quic_restart_flag_quic_time_wait_list_support_multiple_cid_v2, true) -// If true, treat old (pre-draft02) PRIORITY_UPDATE frame as unknown frame. -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_ignore_old_priority_update_frame, true) +// If true, update ACK timeout for NEW_CONNECTION_ID and RETIRE_CONNECTION_ID frames. +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_ack_cid_frames, true) // If true, upon receiving path challenge, send path response and reverse path challenge in the same function. -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_group_path_response_and_challenge_sending_closer, false) +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_group_path_response_and_challenge_sending_closer, true) // If true, use BBRv2 as the default congestion controller. Takes precedence over --quic_default_to_bbr. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_default_to_bbr_v2, false) -// If true, use ScopedEncryptionLevelContext when sending data. -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_use_encryption_level_context, true) -// If true, use WriteOrBufferDataAtLevel to send crypto data. Existing WriteOrBufferData is used to send application data. -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_use_write_or_buffer_data_at_level, false) // If true, use new connection ID in connection migration. -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_connection_migration_use_new_cid_v2, false) -// If true, use the connection ID and stateless reset token on default PathState. -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_use_connection_id_on_default_path_v2, true) +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_connection_migration_use_new_cid_v2, true) // If true, uses conservative cwnd gain and pacing gain when cwnd gets bootstrapped. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_conservative_cwnd_and_pacing_gains, false) // If true, validate that peer owns the new address once the server detects peer migration or is probed from that address, and also apply anti-amplification limit while sending to that address. -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_server_reverse_validate_new_path3, false) -// If ture, replace the incoming_connection_ids check with original_destination_connection_id check. -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_deprecate_incoming_connection_ids, true) +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_server_reverse_validate_new_path3, true) +// Queue packets to attempt decryption later until the handshake is complete. +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_queue_until_handshake_complete, true) // When the STMP connection option is sent by the client, timestamps in the QUIC ACK frame are sent and processed. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_send_timestamps, false) -// When true, QuicSpdySession supports draft-ietf-masque-h3-datagram. -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_h3_datagram, false) // When true, defaults to BBR congestion control instead of Cubic. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_default_to_bbr, false) -// When true, makes the QUIC BBRv2 bw_lo modes more similar to standard BBRv2. -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_fix_bw_lo_mode, true) +// When true, prevents QUIC\'s PacingSender from generating bursts when the congestion controller is CWND limited and not pacing limited. +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_pacing_sender_bursts, false) // When true, set the initial congestion control window from connection options in QuicSentPacketManager rather than TcpCubicSenderBytes. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_unified_iw_options, false) diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_framer.cc b/chromium/net/third_party/quiche/src/quic/core/quic_framer.cc index e186ce33ecb..24b2be86484 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_framer.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_framer.cc @@ -47,7 +47,6 @@ #include "quic/platform/api/quic_flag_utils.h" #include "quic/platform/api/quic_flags.h" #include "quic/platform/api/quic_logging.h" -#include "quic/platform/api/quic_map_util.h" #include "quic/platform/api/quic_stack_trace.h" #include "common/quiche_text_utils.h" @@ -399,8 +398,7 @@ std::string GenerateErrorString(std::string initial_error_string, } // namespace QuicFramer::QuicFramer(const ParsedQuicVersionVector& supported_versions, - QuicTime creation_time, - Perspective perspective, + QuicTime creation_time, Perspective perspective, uint8_t expected_server_connection_id_length) : visitor_(nullptr), error_(QUIC_NO_ERROR), @@ -430,7 +428,8 @@ QuicFramer::QuicFramer(const ParsedQuicVersionVector& supported_versions, last_written_packet_number_length_(0), peer_ack_delay_exponent_(kDefaultAckDelayExponent), local_ack_delay_exponent_(kDefaultAckDelayExponent), - current_received_frame_type_(0) { + current_received_frame_type_(0), + previously_received_frame_type_(0) { QUICHE_DCHECK(!supported_versions.empty()); version_ = supported_versions_[0]; QUICHE_DCHECK(version_.IsKnown()) @@ -1503,6 +1502,7 @@ QuicFramer::BuildIetfVersionNegotiationPacket( } bool QuicFramer::ProcessPacket(const QuicEncryptedPacket& packet) { + QUICHE_DCHECK(!is_processing_packet_) << ENDPOINT << "Nested ProcessPacket"; is_processing_packet_ = true; bool result = ProcessPacketInternal(packet); is_processing_packet_ = false; @@ -1964,8 +1964,10 @@ bool QuicFramer::ProcessIetfDataPacket(QuicDataReader* encrypted_reader, // Handle the payload. if (VersionHasIetfQuicFrames(version_.transport_version)) { current_received_frame_type_ = 0; + previously_received_frame_type_ = 0; if (!ProcessIetfFrameData(&reader, *header, decrypted_level)) { current_received_frame_type_ = 0; + previously_received_frame_type_ = 0; QUICHE_DCHECK_NE(QUIC_NO_ERROR, error_); // ProcessIetfFrameData sets the error. QUICHE_DCHECK_NE("", detailed_error_); @@ -1974,6 +1976,7 @@ bool QuicFramer::ProcessIetfDataPacket(QuicDataReader* encrypted_reader, return false; } current_received_frame_type_ = 0; + previously_received_frame_type_ = 0; } else { if (!ProcessFrameData(&reader, *header)) { QUICHE_DCHECK_NE(QUIC_NO_ERROR, @@ -3215,6 +3218,7 @@ bool QuicFramer::ProcessIetfFrameData(QuicDataReader* reader, EncryptionLevelToString(decrypted_level))); return RaiseError(IETF_QUIC_PROTOCOL_VIOLATION); } + previously_received_frame_type_ = current_received_frame_type_; current_received_frame_type_ = frame_type; // Is now the number of bytes into which the frame type was encoded. diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_framer.h b/chromium/net/third_party/quiche/src/quic/core/quic_framer.h index 2920db351c4..31cc756990b 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_framer.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_framer.h @@ -650,6 +650,10 @@ class QUIC_EXPORT_PRIVATE QuicFramer { return current_received_frame_type_; } + uint64_t previously_received_frame_type() const { + return previously_received_frame_type_; + } + // The connection ID length the framer expects on incoming IETF short headers // on the server. uint8_t GetExpectedServerConnectionIdLength() { @@ -1190,6 +1194,11 @@ class QUIC_EXPORT_PRIVATE QuicFramer { // the Transport Connection Close when there is an error during frame // processing. uint64_t current_received_frame_type_; + + // TODO(haoyuewang) Remove this debug utility. + // The type of the IETF frame preceding the frame currently being processed. 0 + // when not processing a frame or only 1 frame has been processed. + uint64_t previously_received_frame_type_; }; // Look for and parse the error code from the "<quic_error_code>:" text that diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_framer_test.cc b/chromium/net/third_party/quiche/src/quic/core/quic_framer_test.cc index 63a1ad5ed4f..f36bf5c78c7 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_framer_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_framer_test.cc @@ -61,9 +61,10 @@ QuicConnectionId FramerTestConnectionIdPlusOne() { } QuicConnectionId FramerTestConnectionIdNineBytes() { - char connection_id_bytes[9] = {0xFE, 0xDC, 0xBA, 0x98, 0x76, - 0x54, 0x32, 0x10, 0x42}; - return QuicConnectionId(connection_id_bytes, sizeof(connection_id_bytes)); + uint8_t connection_id_bytes[9] = {0xFE, 0xDC, 0xBA, 0x98, 0x76, + 0x54, 0x32, 0x10, 0x42}; + return QuicConnectionId(reinterpret_cast<char*>(connection_id_bytes), + sizeof(connection_id_bytes)); } const QuicPacketNumber kPacketNumber = QuicPacketNumber(UINT64_C(0x12345678)); @@ -8946,10 +8947,9 @@ TEST_P(QuicFramerTest, BuildMessagePacket) { header.reset_flag = false; header.version_flag = false; header.packet_number = kPacketNumber; - QuicMemSliceStorage storage(nullptr, 0, nullptr, 0); - QuicMessageFrame frame(1, MakeSpan(&allocator_, "message", &storage)); - QuicMessageFrame frame2(2, MakeSpan(&allocator_, "message2", &storage)); + QuicMessageFrame frame(1, MemSliceFromString("message")); + QuicMessageFrame frame2(2, MemSliceFromString("message2")); QuicFrames frames = {QuicFrame(&frame), QuicFrame(&frame2)}; // clang-format off @@ -13730,9 +13730,9 @@ TEST_P(QuicFramerTest, PacketHeaderWithVariableLengthConnectionId) { return; } SetDecrypterLevel(ENCRYPTION_FORWARD_SECURE); - char connection_id_bytes[9] = {0xFE, 0xDC, 0xBA, 0x98, 0x76, - 0x54, 0x32, 0x10, 0x42}; - QuicConnectionId connection_id(connection_id_bytes, + uint8_t connection_id_bytes[9] = {0xFE, 0xDC, 0xBA, 0x98, 0x76, + 0x54, 0x32, 0x10, 0x42}; + QuicConnectionId connection_id(reinterpret_cast<char*>(connection_id_bytes), sizeof(connection_id_bytes)); QuicFramerPeer::SetLargestPacketNumber(&framer_, kPacketNumber - 2); QuicFramerPeer::SetExpectedServerConnectionIDLength(&framer_, @@ -14031,7 +14031,7 @@ TEST_P(QuicFramerTest, ProcessMismatchedHeaderVersion) { TEST_P(QuicFramerTest, WriteClientVersionNegotiationProbePacket) { // clang-format off - static const char expected_packet[1200] = { + static const uint8_t expected_packet[1200] = { // IETF long header with fixed bit set, type initial, all-0 encrypted bits. 0xc0, // Version, part of the IETF space reserved for negotiation. @@ -14080,9 +14080,9 @@ TEST_P(QuicFramerTest, WriteClientVersionNegotiationProbePacket) { EXPECT_TRUE(QuicFramer::WriteClientVersionNegotiationProbePacket( packet, sizeof(packet), destination_connection_id_bytes, sizeof(destination_connection_id_bytes))); - quiche::test::CompareCharArraysWithHexError("constructed packet", packet, - sizeof(packet), expected_packet, - sizeof(expected_packet)); + quiche::test::CompareCharArraysWithHexError( + "constructed packet", packet, sizeof(packet), + reinterpret_cast<const char*>(expected_packet), sizeof(expected_packet)); QuicEncryptedPacket encrypted(reinterpret_cast<const char*>(packet), sizeof(packet), false); if (!framer_.version().HasLengthPrefixedConnectionIds()) { @@ -14102,7 +14102,7 @@ TEST_P(QuicFramerTest, WriteClientVersionNegotiationProbePacket) { TEST_P(QuicFramerTest, DispatcherParseOldClientVersionNegotiationProbePacket) { // clang-format off - static const char packet[1200] = { + static const uint8_t packet[1200] = { // IETF long header with fixed bit set, type initial, all-0 encrypted bits. 0xc0, // Version, part of the IETF space reserved for negotiation. @@ -14179,7 +14179,7 @@ TEST_P(QuicFramerTest, DispatcherParseOldClientVersionNegotiationProbePacket) { TEST_P(QuicFramerTest, DispatcherParseClientVersionNegotiationProbePacket) { // clang-format off - static const char packet[1200] = { + static const uint8_t packet[1200] = { // IETF long header with fixed bit set, type initial, all-0 encrypted bits. 0xc0, // Version, part of the IETF space reserved for negotiation. @@ -14257,7 +14257,7 @@ TEST_P(QuicFramerTest, DispatcherParseClientVersionNegotiationProbePacket) { TEST_P(QuicFramerTest, ParseServerVersionNegotiationProbeResponse) { // clang-format off - const char packet[] = { + const uint8_t packet[] = { // IETF long header with fixed bit set, type initial, all-0 encrypted bits. 0xc0, // Version of 0, indicating version negotiation. diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_idle_network_detector.cc b/chromium/net/third_party/quiche/src/quic/core/quic_idle_network_detector.cc index 8046e446cee..ac6799ee46d 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_idle_network_detector.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_idle_network_detector.cc @@ -5,6 +5,7 @@ #include "quic/core/quic_idle_network_detector.h" #include "quic/core/quic_constants.h" +#include "quic/platform/api/quic_flag_utils.h" namespace quic { @@ -26,9 +27,7 @@ class AlarmDelegate : public QuicAlarm::Delegate { } // namespace QuicIdleNetworkDetector::QuicIdleNetworkDetector( - Delegate* delegate, - QuicTime now, - QuicConnectionArena* arena, + Delegate* delegate, QuicTime now, QuicConnectionArena* arena, QuicAlarmFactory* alarm_factory) : delegate_(delegate), start_time_(now), @@ -37,7 +36,12 @@ QuicIdleNetworkDetector::QuicIdleNetworkDetector( time_of_first_packet_sent_after_receiving_(QuicTime::Zero()), idle_network_timeout_(QuicTime::Delta::Infinite()), alarm_( - alarm_factory->CreateAlarm(arena->New<AlarmDelegate>(this), arena)) {} + alarm_factory->CreateAlarm(arena->New<AlarmDelegate>(this), arena)) { + if (no_alarm_after_stopped_) { + QUIC_RELOADABLE_FLAG_COUNT_N( + quic_idle_network_detector_no_alarm_after_stopped, 1, 2); + } +} void QuicIdleNetworkDetector::OnAlarm() { if (handshake_timeout_.IsInfinite()) { @@ -66,9 +70,10 @@ void QuicIdleNetworkDetector::SetTimeouts( } void QuicIdleNetworkDetector::StopDetection() { - alarm_->Cancel(); + alarm_->PermanentCancel(); handshake_timeout_ = QuicTime::Delta::Infinite(); idle_network_timeout_ = QuicTime::Delta::Infinite(); + stopped_ = true; } void QuicIdleNetworkDetector::OnPacketSent(QuicTime now, @@ -94,6 +99,17 @@ void QuicIdleNetworkDetector::OnPacketReceived(QuicTime now) { } void QuicIdleNetworkDetector::SetAlarm() { + if (no_alarm_after_stopped_ && stopped_) { + QUIC_RELOADABLE_FLAG_COUNT_N( + quic_idle_network_detector_no_alarm_after_stopped, 2, 2); + + // TODO(wub): If this QUIC_BUG fires, it indicates a problem in the + // QuicConnection, which somehow called this function while disconnected. + // That problem needs to be fixed. + QUIC_BUG(quic_idle_detector_set_alarm_after_stopped) + << "SetAlarm called after stopped"; + return; + } // Set alarm to the nearer deadline. QuicTime new_deadline = QuicTime::Zero(); if (!handshake_timeout_.IsInfinite()) { diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_idle_network_detector.h b/chromium/net/third_party/quiche/src/quic/core/quic_idle_network_detector.h index e138d6a068e..1665104a186 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_idle_network_detector.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_idle_network_detector.h @@ -10,6 +10,7 @@ #include "quic/core/quic_one_block_arena.h" #include "quic/core/quic_time.h" #include "quic/platform/api/quic_export.h" +#include "quic/platform/api/quic_flags.h" namespace quic { @@ -46,6 +47,7 @@ class QUIC_EXPORT_PRIVATE QuicIdleNetworkDetector { void SetTimeouts(QuicTime::Delta handshake_timeout, QuicTime::Delta idle_network_timeout); + // Stop the detection once and for all. void StopDetection(); // Called when a packet gets sent. @@ -106,6 +108,12 @@ class QUIC_EXPORT_PRIVATE QuicIdleNetworkDetector { QuicArenaScopedPtr<QuicAlarm> alarm_; bool shorter_idle_timeout_on_sent_packet_ = false; + + // Whether |StopDetection| has been called. + bool stopped_ = false; + + const bool no_alarm_after_stopped_ = + GetQuicReloadableFlag(quic_idle_network_detector_no_alarm_after_stopped); }; } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_idle_network_detector_test.cc b/chromium/net/third_party/quiche/src/quic/core/quic_idle_network_detector_test.cc index 271247808c9..11d0cf76777 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_idle_network_detector_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_idle_network_detector_test.cc @@ -5,6 +5,7 @@ #include "quic/core/quic_idle_network_detector.h" #include "quic/core/quic_one_block_arena.h" +#include "quic/platform/api/quic_expect_bug.h" #include "quic/platform/api/quic_test.h" #include "quic/test_tools/quic_test_utils.h" @@ -181,6 +182,25 @@ TEST_F(QuicIdleNetworkDetectorTest, ShorterIdleTimeoutOnSentPacket) { EXPECT_EQ(clock_.Now() + QuicTime::Delta::FromSeconds(2), alarm_->deadline()); } +TEST_F(QuicIdleNetworkDetectorTest, NoAlarmAfterStopped) { + detector_->StopDetection(); + + if (GetQuicReloadableFlag( + quic_idle_network_detector_no_alarm_after_stopped)) { + EXPECT_QUIC_BUG( + detector_->SetTimeouts( + /*handshake_timeout=*/QuicTime::Delta::FromSeconds(30), + /*idle_network_timeout=*/QuicTime::Delta::FromSeconds(20)), + "SetAlarm called after stopped"); + EXPECT_FALSE(alarm_->IsSet()); + } else { + detector_->SetTimeouts( + /*handshake_timeout=*/QuicTime::Delta::FromSeconds(30), + /*idle_network_timeout=*/QuicTime::Delta::FromSeconds(20)); + EXPECT_TRUE(alarm_->IsSet()); + } +} + } // namespace } // namespace test diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_interval_set.h b/chromium/net/third_party/quiche/src/quic/core/quic_interval_set.h index fef089313fa..d69fa710604 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_interval_set.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_interval_set.h @@ -85,9 +85,7 @@ class QUIC_NO_EXPORT QuicIntervalSet { bool operator()(T&& point, const value_type& a) const; }; - using Set = QuicOrderedSet<value_type, - IntervalLess, - QuicInlinedVector<value_type, 10>>; + using Set = QuicSmallOrderedSet<value_type, IntervalLess>; public: using const_iterator = typename Set::const_iterator; diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_lru_cache.h b/chromium/net/third_party/quiche/src/quic/core/quic_lru_cache.h index d4b11e0ce5f..67541ee3a23 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_lru_cache.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_lru_cache.h @@ -7,11 +7,11 @@ #include <memory> -#include "quic/platform/api/quic_containers.h" #include "quic/platform/api/quic_export.h" #include "quic/platform/api/quic_flag_utils.h" #include "quic/platform/api/quic_flags.h" #include "quic/platform/api/quic_logging.h" +#include "common/quiche_linked_hash_map.h" namespace quic { @@ -67,7 +67,7 @@ class QUIC_NO_EXPORT QuicLRUCache { size_t Size() const { return cache_.size(); } private: - QuicLinkedHashMap<K, std::unique_ptr<V>> cache_; + quiche::QuicheLinkedHashMap<K, std::unique_ptr<V>> cache_; const size_t capacity_; }; diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_network_blackhole_detector.cc b/chromium/net/third_party/quiche/src/quic/core/quic_network_blackhole_detector.cc index 5281ffb980a..4dca50832ac 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_network_blackhole_detector.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_network_blackhole_detector.cc @@ -64,8 +64,12 @@ void QuicNetworkBlackholeDetector::OnAlarm() { UpdateAlarm(); } -void QuicNetworkBlackholeDetector::StopDetection() { - alarm_->Cancel(); +void QuicNetworkBlackholeDetector::StopDetection(bool permanent) { + if (permanent) { + alarm_->PermanentCancel(); + } else { + alarm_->Cancel(); + } path_degrading_deadline_ = QuicTime::Zero(); blackhole_deadline_ = QuicTime::Zero(); path_mtu_reduction_deadline_ = QuicTime::Zero(); @@ -108,6 +112,12 @@ QuicTime QuicNetworkBlackholeDetector::GetLastDeadline() const { } void QuicNetworkBlackholeDetector::UpdateAlarm() const { + // If called after OnBlackholeDetected(), the alarm may have been permanently + // cancelled and is not safe to be armed again. + if (alarm_->IsPermanentlyCancelled()) { + return; + } + QuicTime next_deadline = GetEarliestDeadline(); QUIC_DVLOG(1) << "Updating alarm. next_deadline:" << next_deadline diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_network_blackhole_detector.h b/chromium/net/third_party/quiche/src/quic/core/quic_network_blackhole_detector.h index 7010eb581a8..82753f934a6 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_network_blackhole_detector.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_network_blackhole_detector.h @@ -44,8 +44,9 @@ class QUIC_EXPORT_PRIVATE QuicNetworkBlackholeDetector { QuicConnectionArena* arena, QuicAlarmFactory* alarm_factory); - // Called to stop all detections. - void StopDetection(); + // Called to stop all detections. If |permanent|, the alarm will be cancelled + // permanently and future calls to RestartDetection will be no-op. + void StopDetection(bool permanent); // Called to restart path degrading, path mtu reduction and blackhole // detections. Please note, if |blackhole_deadline| is set, it must be the diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_network_blackhole_detector_test.cc b/chromium/net/third_party/quiche/src/quic/core/quic_network_blackhole_detector_test.cc index 3bc6ad73cbb..66bebfd455f 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_network_blackhole_detector_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_network_blackhole_detector_test.cc @@ -106,7 +106,7 @@ TEST_F(QuicNetworkBlackholeDetectorTest, RestartAndStop) { RestartDetection(); EXPECT_EQ(clock_.Now() + path_degrading_delay_, alarm_->deadline()); - detector_.StopDetection(); + detector_.StopDetection(/*permanent=*/false); EXPECT_FALSE(detector_.IsDetectionInProgress()); } diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_packet_creator.cc b/chromium/net/third_party/quiche/src/quic/core/quic_packet_creator.cc index cbca89dae55..94cf48a1d51 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_packet_creator.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_packet_creator.cc @@ -34,6 +34,7 @@ #include "quic/platform/api/quic_flags.h" #include "quic/platform/api/quic_logging.h" #include "quic/platform/api/quic_server_stats.h" +#include "common/print_elements.h" namespace quic { namespace { @@ -696,6 +697,10 @@ bool QuicPacketCreator::HasPendingFrames() const { return !queued_frames_.empty(); } +std::string QuicPacketCreator::GetPendingFramesInfo() const { + return QuicFramesToString(queued_frames_); +} + bool QuicPacketCreator::HasPendingRetransmittableFrames() const { return !packet_.retransmittable_frames.empty(); } @@ -1522,7 +1527,7 @@ bool QuicPacketCreator::FlushAckFrame(const QuicFrames& frames) { QUIC_BUG_IF(quic_bug_12398_18, GetQuicReloadableFlag(quic_single_ack_in_packet2) && !frames.empty() && has_ack()) - << ENDPOINT << "Trying to flush " << frames + << ENDPOINT << "Trying to flush " << quiche::PrintElements(frames) << " when there is ACK queued"; for (const auto& frame : frames) { QUICHE_DCHECK(frame.type == ACK_FRAME || frame.type == STOP_WAITING_FRAME) @@ -1596,14 +1601,14 @@ void QuicPacketCreator::SetTransmissionType(TransmissionType type) { next_transmission_type_ = type; } -MessageStatus QuicPacketCreator::AddMessageFrame(QuicMessageId message_id, - QuicMemSliceSpan message) { +MessageStatus QuicPacketCreator::AddMessageFrame( + QuicMessageId message_id, absl::Span<QuicMemSlice> message) { QUIC_BUG_IF(quic_bug_10752_33, !flusher_attached_) << ENDPOINT << "Packet flusher is not attached when " "generator tries to add message frame."; MaybeBundleAckOpportunistically(); - const QuicByteCount message_length = message.total_length(); + const QuicByteCount message_length = MemSliceSpanTotalSize(message); if (message_length > GetCurrentLargestMessagePayload()) { return MESSAGE_STATUS_TOO_LARGE; } @@ -1618,6 +1623,8 @@ MessageStatus QuicPacketCreator::AddMessageFrame(QuicMessageId message_id, delete frame; return MESSAGE_STATUS_INTERNAL_ERROR; } + QUICHE_DCHECK_EQ(MemSliceSpanTotalSize(message), + 0u); // Ensure the old slices are empty. return MESSAGE_STATUS_SUCCESS; } diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_packet_creator.h b/chromium/net/third_party/quiche/src/quic/core/quic_packet_creator.h index d4a58055282..9cc4bcae92e 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_packet_creator.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_packet_creator.h @@ -205,6 +205,10 @@ class QUIC_EXPORT_PRIVATE QuicPacketCreator { // Returns true if there are frames pending to be serialized. bool HasPendingFrames() const; + // TODO(haoyuewang) Remove this debug utility. + // Returns the information of pending frames as a string. + std::string GetPendingFramesInfo() const; + // Returns true if there are retransmittable frames pending to be serialized. bool HasPendingRetransmittableFrames() const; @@ -417,7 +421,7 @@ class QUIC_EXPORT_PRIVATE QuicPacketCreator { // Tries to add a message frame containing |message| and returns the status. MessageStatus AddMessageFrame(QuicMessageId message_id, - QuicMemSliceSpan message); + absl::Span<QuicMemSlice> message); // Returns the largest payload that will fit into a single MESSAGE frame. QuicPacketLength GetCurrentLargestMessagePayload() const; diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_packet_creator_test.cc b/chromium/net/third_party/quiche/src/quic/core/quic_packet_creator_test.cc index 57d1018d94c..3dc1a4b184b 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_packet_creator_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_packet_creator_test.cc @@ -423,6 +423,8 @@ TEST_P(QuicPacketCreatorTest, ConsumeDataFinOnly) { EXPECT_EQ(0u, consumed); CheckStreamFrame(frame, stream_id, std::string(), 0u, true); EXPECT_TRUE(creator_.HasPendingFrames()); + EXPECT_TRUE(absl::StartsWith(creator_.GetPendingFramesInfo(), + "type { STREAM_FRAME }")); } TEST_P(QuicPacketCreatorTest, CreateAllFreeBytesForStreamFrames) { @@ -1767,25 +1769,24 @@ TEST_P(QuicPacketCreatorTest, AddMessageFrame) { .Times(3) .WillRepeatedly( Invoke(this, &QuicPacketCreatorTest::ClearSerializedPacketForTests)); - QuicMemSliceStorage storage(nullptr, 0, nullptr, 0); // Verify that there is enough room for the largest message payload. EXPECT_TRUE(creator_.HasRoomForMessageFrame( creator_.GetCurrentLargestMessagePayload())); - std::string message(creator_.GetCurrentLargestMessagePayload(), 'a'); + std::string large_message(creator_.GetCurrentLargestMessagePayload(), 'a'); QuicMessageFrame* message_frame = - new QuicMessageFrame(1, MakeSpan(&allocator_, message, &storage)); + new QuicMessageFrame(1, MemSliceFromString(large_message)); EXPECT_TRUE(creator_.AddFrame(QuicFrame(message_frame), NOT_RETRANSMISSION)); EXPECT_TRUE(creator_.HasPendingFrames()); creator_.FlushCurrentPacket(); QuicMessageFrame* frame2 = - new QuicMessageFrame(2, MakeSpan(&allocator_, "message", &storage)); + new QuicMessageFrame(2, MemSliceFromString("message")); EXPECT_TRUE(creator_.AddFrame(QuicFrame(frame2), NOT_RETRANSMISSION)); EXPECT_TRUE(creator_.HasPendingFrames()); // Verify if a new frame is added, 1 byte message length will be added. EXPECT_EQ(1u, creator_.ExpansionOnNewFrame()); QuicMessageFrame* frame3 = - new QuicMessageFrame(3, MakeSpan(&allocator_, "message2", &storage)); + new QuicMessageFrame(3, MemSliceFromString("message2")); EXPECT_TRUE(creator_.AddFrame(QuicFrame(frame3), NOT_RETRANSMISSION)); EXPECT_EQ(1u, creator_.ExpansionOnNewFrame()); creator_.FlushCurrentPacket(); @@ -1798,14 +1799,14 @@ TEST_P(QuicPacketCreatorTest, AddMessageFrame) { stream_id, &iov_, 1u, iov_.iov_len, 0u, 0u, false, false, NOT_RETRANSMISSION, &frame)); QuicMessageFrame* frame4 = - new QuicMessageFrame(4, MakeSpan(&allocator_, "message", &storage)); + new QuicMessageFrame(4, MemSliceFromString("message")); EXPECT_TRUE(creator_.AddFrame(QuicFrame(frame4), NOT_RETRANSMISSION)); EXPECT_TRUE(creator_.HasPendingFrames()); // Verify there is not enough room for largest payload. EXPECT_FALSE(creator_.HasRoomForMessageFrame( creator_.GetCurrentLargestMessagePayload())); // Add largest message will causes the flush of the stream frame. - QuicMessageFrame frame5(5, MakeSpan(&allocator_, message, &storage)); + QuicMessageFrame frame5(5, MemSliceFromString(large_message)); EXPECT_FALSE(creator_.AddFrame(QuicFrame(&frame5), NOT_RETRANSMISSION)); EXPECT_FALSE(creator_.HasPendingFrames()); } @@ -1818,8 +1819,6 @@ TEST_P(QuicPacketCreatorTest, MessageFrameConsumption) { creator_.SetMaxDatagramFrameSize(kMaxAcceptedDatagramFrameSize); } std::string message_data(kDefaultMaxPacketSize, 'a'); - absl::string_view message_buffer(message_data); - QuicMemSliceStorage storage(nullptr, 0, nullptr, 0); // Test all possible encryption levels of message frames. for (EncryptionLevel level : {ENCRYPTION_ZERO_RTT, ENCRYPTION_FORWARD_SECURE}) { @@ -1828,10 +1827,9 @@ TEST_P(QuicPacketCreatorTest, MessageFrameConsumption) { for (size_t message_size = 0; message_size <= creator_.GetCurrentLargestMessagePayload(); ++message_size) { - QuicMessageFrame* frame = new QuicMessageFrame( - 0, MakeSpan(&allocator_, - absl::string_view(message_buffer.data(), message_size), - &storage)); + QuicMessageFrame* frame = + new QuicMessageFrame(0, MemSliceFromString(absl::string_view( + message_data.data(), message_size))); EXPECT_TRUE(creator_.AddFrame(QuicFrame(frame), NOT_RETRANSMISSION)); EXPECT_TRUE(creator_.HasPendingFrames()); @@ -2450,12 +2448,13 @@ class MultiplePacketsTestPacketCreator : public QuicPacketCreator { } MessageStatus AddMessageFrame(QuicMessageId message_id, - QuicMemSliceSpan message) { + QuicMemSlice message) { if (!has_ack() && delegate_->ShouldGeneratePacket(NO_RETRANSMITTABLE_DATA, NOT_HANDSHAKE)) { EXPECT_CALL(*delegate_, MaybeBundleAckOpportunistically()).Times(1); } - return QuicPacketCreator::AddMessageFrame(message_id, message); + return QuicPacketCreator::AddMessageFrame(message_id, + absl::MakeSpan(&message, 1)); } size_t ConsumeCryptoData(EncryptionLevel level, @@ -3792,7 +3791,6 @@ TEST_F(QuicPacketCreatorMultiplePacketsTest, AddMessageFrame) { if (framer_.version().UsesTls()) { creator_.SetMaxDatagramFrameSize(kMaxAcceptedDatagramFrameSize); } - quic::QuicMemSliceStorage storage(nullptr, 0, nullptr, 0); delegate_.SetCanWriteAnything(); EXPECT_CALL(delegate_, OnSerializedPacket(_)) .WillOnce( @@ -3802,30 +3800,23 @@ TEST_F(QuicPacketCreatorMultiplePacketsTest, AddMessageFrame) { creator_.ConsumeData(QuicUtils::GetFirstBidirectionalStreamId( framer_.transport_version(), Perspective::IS_CLIENT), &iov_, 1u, iov_.iov_len, 0, FIN); - EXPECT_EQ( - MESSAGE_STATUS_SUCCESS, - creator_.AddMessageFrame(1, MakeSpan(&allocator_, "message", &storage))); + EXPECT_EQ(MESSAGE_STATUS_SUCCESS, + creator_.AddMessageFrame(1, MemSliceFromString("message"))); EXPECT_TRUE(creator_.HasPendingFrames()); EXPECT_TRUE(creator_.HasPendingRetransmittableFrames()); // Add a message which causes the flush of current packet. - EXPECT_EQ( - MESSAGE_STATUS_SUCCESS, - creator_.AddMessageFrame( - 2, - MakeSpan(&allocator_, - std::string(creator_.GetCurrentLargestMessagePayload(), 'a'), - &storage))); + EXPECT_EQ(MESSAGE_STATUS_SUCCESS, + creator_.AddMessageFrame( + 2, MemSliceFromString(std::string( + creator_.GetCurrentLargestMessagePayload(), 'a')))); EXPECT_TRUE(creator_.HasPendingRetransmittableFrames()); // Failed to send messages which cannot fit into one packet. - EXPECT_EQ( - MESSAGE_STATUS_TOO_LARGE, - creator_.AddMessageFrame( - 3, MakeSpan(&allocator_, - std::string( - creator_.GetCurrentLargestMessagePayload() + 10, 'a'), - &storage))); + EXPECT_EQ(MESSAGE_STATUS_TOO_LARGE, + creator_.AddMessageFrame( + 3, MemSliceFromString(std::string( + creator_.GetCurrentLargestMessagePayload() + 10, 'a')))); } TEST_F(QuicPacketCreatorMultiplePacketsTest, ConnectionId) { diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_path_validator.h b/chromium/net/third_party/quiche/src/quic/core/quic_path_validator.h index ccd3a6cbcaf..9ea1554efa4 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_path_validator.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_path_validator.h @@ -7,6 +7,7 @@ #include <ostream> +#include "absl/container/inlined_vector.h" #include "quic/core/crypto/quic_random.h" #include "quic/core/quic_alarm.h" #include "quic/core/quic_alarm_factory.h" @@ -145,7 +146,7 @@ class QUIC_EXPORT_PRIVATE QuicPathValidator { void ResetPathValidation(); // Has at most 3 entries due to validation timeout. - QuicInlinedVector<QuicPathFrameBuffer, 3> probing_data_; + absl::InlinedVector<QuicPathFrameBuffer, 3> probing_data_; SendDelegate* send_delegate_; QuicRandom* random_; std::unique_ptr<QuicPathValidationContext> path_context_; diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_sent_packet_manager.cc b/chromium/net/third_party/quiche/src/quic/core/quic_sent_packet_manager.cc index 53d9f0eca22..fd1190bde7b 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_sent_packet_manager.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_sent_packet_manager.cc @@ -24,7 +24,7 @@ #include "quic/platform/api/quic_flag_utils.h" #include "quic/platform/api/quic_flags.h" #include "quic/platform/api/quic_logging.h" -#include "quic/platform/api/quic_map_util.h" +#include "common/print_elements.h" namespace quic { @@ -117,7 +117,8 @@ QuicSentPacketManager::QuicSentPacketManager( use_standard_deviation_for_pto_(false), pto_multiplier_without_rtt_samples_(3), num_ptos_for_path_degrading_(0), - ignore_pings_(false) { + ignore_pings_(false), + ignore_ack_delay_(false) { SetSendAlgorithm(congestion_control_type); if (pto_enabled_) { QUIC_RELOADABLE_FLAG_COUNT_N(quic_default_on_pto, 1, 2); @@ -158,7 +159,7 @@ void QuicSentPacketManager::SetFromConfig(const QuicConfig& config) { } } if (config.HasClientSentConnectionOption(kMAD0, perspective)) { - rtt_stats_.set_ignore_max_ack_delay(true); + ignore_ack_delay_ = true; } if (config.HasClientSentConnectionOption(kMAD2, perspective)) { // Set the minimum to the alarm granularity. @@ -1526,8 +1527,18 @@ void QuicSentPacketManager::OnAckFrameStart(QuicPacketNumber largest_acked, QuicTime ack_receive_time) { QUICHE_DCHECK(packets_acked_.empty()); QUICHE_DCHECK_LE(largest_acked, unacked_packets_.largest_sent_packet()); - if (ack_delay_time > peer_max_ack_delay()) { - ack_delay_time = peer_max_ack_delay(); + if (GetQuicReloadableFlag(quic_ignore_peer_max_ack_delay_during_handshake) && + supports_multiple_packet_number_spaces() && !handshake_finished_) { + QUIC_RELOADABLE_FLAG_COUNT(quic_ignore_peer_max_ack_delay_during_handshake); + // Ignore peer_max_ack_delay and use received ack_delay during + // handshake. + } else { + if (ack_delay_time > peer_max_ack_delay()) { + ack_delay_time = peer_max_ack_delay(); + } + if (ignore_ack_delay_) { + ack_delay_time = QuicTime::Delta::Zero(); + } } rtt_updated_ = MaybeUpdateRTT(largest_acked, ack_delay_time, ack_receive_time); @@ -1601,7 +1612,7 @@ AckResult QuicSentPacketManager::OnAckFrameEnd( << acked_packet.packet_number << ", last_ack_frame_: " << last_ack_frame_ << ", least_unacked: " << unacked_packets_.GetLeastUnacked() - << ", packets_acked_: " << packets_acked_; + << ", packets_acked_: " << quiche::PrintElements(packets_acked_); } else { QUIC_PEER_BUG(quic_peer_bug_10750_6) << "Received " << ack_decrypted_level diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_sent_packet_manager.h b/chromium/net/third_party/quiche/src/quic/core/quic_sent_packet_manager.h index 781f6f95b90..eeb474f9f04 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_sent_packet_manager.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_sent_packet_manager.h @@ -745,6 +745,9 @@ class QUIC_EXPORT_PRIVATE QuicSentPacketManager { // If true, do not use PING only packets for RTT measurement or congestion // control. bool ignore_pings_; + + // Whether to ignore the ack_delay in received ACKs. + bool ignore_ack_delay_; }; } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_sent_packet_manager_test.cc b/chromium/net/third_party/quiche/src/quic/core/quic_sent_packet_manager_test.cc index 388a662096e..e457e09884f 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_sent_packet_manager_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_sent_packet_manager_test.cc @@ -4637,8 +4637,7 @@ TEST_F(QuicSentPacketManagerTest, ClearDataInMessageFrameAfterPacketSent) { QuicMessageFrame* message_frame = nullptr; { QuicMemSlice slice(MakeUniqueBuffer(&allocator_, 1024), 1024); - message_frame = - new QuicMessageFrame(/*message_id=*/1, QuicMemSliceSpan(&slice)); + message_frame = new QuicMessageFrame(/*message_id=*/1, std::move(slice)); EXPECT_FALSE(message_frame->message_data.empty()); EXPECT_EQ(message_frame->message_length, 1024); @@ -4684,6 +4683,125 @@ TEST_F(QuicSentPacketManagerTest, BuildAckFrequencyFrame) { EXPECT_EQ(frame.packet_tolerance, 10u); } +TEST_F(QuicSentPacketManagerTest, SmoothedRttIgnoreAckDelay) { + QuicConfig config; + QuicTagVector options; + options.push_back(kMAD0); + QuicConfigPeer::SetReceivedConnectionOptions(&config, options); + EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); + EXPECT_CALL(*network_change_visitor_, OnCongestionChange()); + EXPECT_CALL(*send_algorithm_, CanSend(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*send_algorithm_, GetCongestionWindow()) + .WillRepeatedly(Return(10 * kDefaultTCPMSS)); + manager_.SetFromConfig(config); + + SendDataPacket(1); + // Ack 1. + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(300)); + ExpectAck(1); + manager_.OnAckFrameStart(QuicPacketNumber(1), + QuicTime::Delta::FromMilliseconds(100), + clock_.Now()); + manager_.OnAckRange(QuicPacketNumber(1), QuicPacketNumber(2)); + EXPECT_EQ(PACKETS_NEWLY_ACKED, + manager_.OnAckFrameEnd(clock_.Now(), QuicPacketNumber(1), + ENCRYPTION_INITIAL)); + // Verify that ack_delay is ignored in the first measurement. + EXPECT_EQ(QuicTime::Delta::FromMilliseconds(300), + manager_.GetRttStats()->latest_rtt()); + EXPECT_EQ(QuicTime::Delta::FromMilliseconds(300), + manager_.GetRttStats()->smoothed_rtt()); + + SendDataPacket(2); + // Ack 2. + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(300)); + ExpectAck(2); + manager_.OnAckFrameStart(QuicPacketNumber(2), + QuicTime::Delta::FromMilliseconds(100), + clock_.Now()); + manager_.OnAckRange(QuicPacketNumber(2), QuicPacketNumber(3)); + EXPECT_EQ(PACKETS_NEWLY_ACKED, + manager_.OnAckFrameEnd(clock_.Now(), QuicPacketNumber(2), + ENCRYPTION_INITIAL)); + EXPECT_EQ(QuicTime::Delta::FromMilliseconds(300), + manager_.GetRttStats()->latest_rtt()); + EXPECT_EQ(QuicTime::Delta::FromMilliseconds(300), + manager_.GetRttStats()->smoothed_rtt()); + + SendDataPacket(3); + // Ack 3. + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(300)); + ExpectAck(3); + manager_.OnAckFrameStart(QuicPacketNumber(3), + QuicTime::Delta::FromMilliseconds(50), clock_.Now()); + manager_.OnAckRange(QuicPacketNumber(3), QuicPacketNumber(4)); + EXPECT_EQ(PACKETS_NEWLY_ACKED, + manager_.OnAckFrameEnd(clock_.Now(), QuicPacketNumber(3), + ENCRYPTION_INITIAL)); + EXPECT_EQ(QuicTime::Delta::FromMilliseconds(300), + manager_.GetRttStats()->latest_rtt()); + EXPECT_EQ(QuicTime::Delta::FromMilliseconds(300), + manager_.GetRttStats()->smoothed_rtt()); + + SendDataPacket(4); + // Ack 4. + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(200)); + ExpectAck(4); + manager_.OnAckFrameStart(QuicPacketNumber(4), + QuicTime::Delta::FromMilliseconds(300), + clock_.Now()); + manager_.OnAckRange(QuicPacketNumber(4), QuicPacketNumber(5)); + EXPECT_EQ(PACKETS_NEWLY_ACKED, + manager_.OnAckFrameEnd(clock_.Now(), QuicPacketNumber(4), + ENCRYPTION_INITIAL)); + // Verify that large erroneous ack_delay does not change Smoothed RTT. + EXPECT_EQ(QuicTime::Delta::FromMilliseconds(200), + manager_.GetRttStats()->latest_rtt()); + EXPECT_EQ(QuicTime::Delta::FromMicroseconds(287500), + manager_.GetRttStats()->smoothed_rtt()); +} + +TEST_F(QuicSentPacketManagerTest, IgnorePeerMaxAckDelayDuringHandshake) { + manager_.EnableMultiplePacketNumberSpacesSupport(); + // 100ms RTT. + const QuicTime::Delta kTestRTT = QuicTime::Delta::FromMilliseconds(100); + + // Server sends INITIAL 1 and HANDSHAKE 2. + SendDataPacket(1, ENCRYPTION_INITIAL); + SendDataPacket(2, ENCRYPTION_HANDSHAKE); + + // Receive client ACK for INITIAL 1 after one RTT. + clock_.AdvanceTime(kTestRTT); + ExpectAck(1); + manager_.OnAckFrameStart(QuicPacketNumber(1), QuicTime::Delta::Infinite(), + clock_.Now()); + manager_.OnAckRange(QuicPacketNumber(1), QuicPacketNumber(2)); + EXPECT_EQ(PACKETS_NEWLY_ACKED, + manager_.OnAckFrameEnd(clock_.Now(), QuicPacketNumber(1), + ENCRYPTION_INITIAL)); + EXPECT_EQ(kTestRTT, manager_.GetRttStats()->latest_rtt()); + + // Assume the cert verification on client takes 50ms, such that the HANDSHAKE + // packet is queued for 50ms. + const QuicTime::Delta queuing_delay = QuicTime::Delta::FromMilliseconds(50); + clock_.AdvanceTime(queuing_delay); + // Ack 2. + ExpectAck(2); + manager_.OnAckFrameStart(QuicPacketNumber(2), queuing_delay, clock_.Now()); + manager_.OnAckRange(QuicPacketNumber(2), QuicPacketNumber(3)); + EXPECT_EQ(PACKETS_NEWLY_ACKED, + manager_.OnAckFrameEnd(clock_.Now(), QuicPacketNumber(2), + ENCRYPTION_HANDSHAKE)); + if (GetQuicReloadableFlag(quic_ignore_peer_max_ack_delay_during_handshake)) { + EXPECT_EQ(kTestRTT, manager_.GetRttStats()->latest_rtt()); + } else { + // Verify the ack_delay gets capped by the peer_max_ack_delay. + EXPECT_EQ(kTestRTT + queuing_delay - + QuicTime::Delta::FromMilliseconds(kDefaultDelayedAckTimeMs), + manager_.GetRttStats()->latest_rtt()); + } +} + TEST_F(QuicSentPacketManagerTest, BuildAckFrequencyFrameWithSRTT) { SetQuicReloadableFlag(quic_can_send_ack_frequency, true); EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_server_id.cc b/chromium/net/third_party/quiche/src/quic/core/quic_server_id.cc index 1d2a2adb8c9..0c034514ef4 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_server_id.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_server_id.cc @@ -7,8 +7,6 @@ #include <string> #include <tuple> -#include "quic/platform/api/quic_estimate_memory_usage.h" - namespace quic { QuicServerId::QuicServerId() : QuicServerId("", 0, false) {} @@ -33,8 +31,8 @@ bool QuicServerId::operator==(const QuicServerId& other) const { host_ == other.host_ && port_ == other.port_; } -size_t QuicServerId::EstimateMemoryUsage() const { - return QuicEstimateMemoryUsage(host_); +bool QuicServerId::operator!=(const QuicServerId& other) const { + return !(*this == other); } } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_server_id.h b/chromium/net/third_party/quiche/src/quic/core/quic_server_id.h index 01320014585..fa6b3c3cfdb 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_server_id.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_server_id.h @@ -23,18 +23,18 @@ class QUIC_EXPORT_PRIVATE QuicServerId { bool privacy_mode_enabled); ~QuicServerId(); - // Needed to be an element of std::set. + // Needed to be an element of an ordered container. bool operator<(const QuicServerId& other) const; bool operator==(const QuicServerId& other) const; + bool operator!=(const QuicServerId& other) const; + const std::string& host() const { return host_; } uint16_t port() const { return port_; } bool privacy_mode_enabled() const { return privacy_mode_enabled_; } - size_t EstimateMemoryUsage() const; - private: std::string host_; uint16_t port_; diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_server_id_test.cc b/chromium/net/third_party/quiche/src/quic/core/quic_server_id_test.cc index 67231abf866..1ad425abbd8 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_server_id_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_server_id_test.cc @@ -6,7 +6,6 @@ #include <string> -#include "quic/platform/api/quic_estimate_memory_usage.h" #include "quic/platform/api/quic_test.h" namespace quic { @@ -87,6 +86,10 @@ TEST_F(QuicServerIdTest, Equals) { QuicServerId b_10_https_right_private("b.com", 10, right_privacy); QuicServerId b_11_https_right_private("b.com", 11, right_privacy); + EXPECT_NE(a_10_https_right_private, a_11_https_right_private); + EXPECT_NE(a_10_https_right_private, b_10_https_right_private); + EXPECT_NE(a_10_https_right_private, b_11_https_right_private); + QuicServerId new_a_10_https_left_private("a.com", 10, left_privacy); QuicServerId new_a_11_https_left_private("a.com", 11, left_privacy); QuicServerId new_b_10_https_left_private("b.com", 10, left_privacy); @@ -107,21 +110,13 @@ TEST_F(QuicServerIdTest, Equals) { QuicServerId new_a_10_https_left_private("a.com", 10, false); - EXPECT_FALSE(new_a_10_https_left_private == a_11_https_right_private); - EXPECT_FALSE(new_a_10_https_left_private == b_10_https_right_private); - EXPECT_FALSE(new_a_10_https_left_private == b_11_https_right_private); + EXPECT_NE(new_a_10_https_left_private, a_11_https_right_private); + EXPECT_NE(new_a_10_https_left_private, b_10_https_right_private); + EXPECT_NE(new_a_10_https_left_private, b_11_https_right_private); } QuicServerId a_10_https_private("a.com", 10, true); QuicServerId new_a_10_https_no_private("a.com", 10, false); - EXPECT_FALSE(new_a_10_https_no_private == a_10_https_private); -} - -TEST_F(QuicServerIdTest, EstimateMemoryUsage) { - std::string host = "this is a rather very quite long hostname"; - uint16_t port = 10; - bool privacy_mode_enabled = true; - QuicServerId server_id(host, port, privacy_mode_enabled); - EXPECT_EQ(QuicEstimateMemoryUsage(host), QuicEstimateMemoryUsage(server_id)); + EXPECT_NE(new_a_10_https_no_private, a_10_https_private); } } // namespace diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_session.cc b/chromium/net/third_party/quiche/src/quic/core/quic_session.cc index 1bcb9dcb7bd..aef3fc255a8 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_session.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_session.cc @@ -13,6 +13,7 @@ #include "absl/strings/string_view.h" #include "quic/core/frames/quic_ack_frequency_frame.h" #include "quic/core/quic_connection.h" +#include "quic/core/quic_connection_context.h" #include "quic/core/quic_error_codes.h" #include "quic/core/quic_flow_controller.h" #include "quic/core/quic_types.h" @@ -22,7 +23,6 @@ #include "quic/platform/api/quic_flag_utils.h" #include "quic/platform/api/quic_flags.h" #include "quic/platform/api/quic_logging.h" -#include "quic/platform/api/quic_map_util.h" #include "quic/platform/api/quic_server_stats.h" #include "quic/platform/api/quic_stack_trace.h" #include "common/quiche_text_utils.h" @@ -135,11 +135,15 @@ void QuicSession::Initialize() { connection_->SetDataProducer(this); connection_->SetUnackedMapInitialCapacity(); connection_->SetFromConfig(config_); - if (perspective_ == Perspective::IS_CLIENT && - config_.HasClientRequestedIndependentOption(kAFFE, perspective_) && - version().HasIetfQuicFrames()) { - connection_->set_can_receive_ack_frequency_frame(); - config_.SetMinAckDelayMs(kDefaultMinAckDelayTimeMs); + if (perspective_ == Perspective::IS_CLIENT) { + if (config_.HasClientRequestedIndependentOption(kAFFE, perspective_) && + version().HasIetfQuicFrames()) { + connection_->set_can_receive_ack_frequency_frame(); + config_.SetMinAckDelayMs(kDefaultMinAckDelayTimeMs); + } + if (config_.HasClientRequestedIndependentOption(kBPTE, perspective_)) { + permutes_tls_extensions_ = true; + } } connection_->CreateConnectionIdManager(); @@ -162,7 +166,12 @@ void QuicSession::Initialize() { GetMutableCryptoStream()->id()); } -QuicSession::~QuicSession() {} +QuicSession::~QuicSession() { + if (GetQuicRestartFlag(quic_alarm_add_permanent_cancel) && + closed_streams_clean_up_alarm_ != nullptr) { + closed_streams_clean_up_alarm_->PermanentCancel(); + } +} void QuicSession::PendingStreamOnStreamFrame(const QuicStreamFrame& frame) { QUICHE_DCHECK(VersionUsesHttp3(transport_version())); @@ -563,17 +572,14 @@ bool QuicSession::CheckStreamWriteBlocked(QuicStream* stream) const { } void QuicSession::OnCanWrite() { - if (connection_->donot_write_mid_packet_processing()) { - QUIC_RELOADABLE_FLAG_COUNT_N(quic_donot_write_mid_packet_processing, 1, 3); - if (connection_->framer().is_processing_packet()) { - // Do not write data in the middle of packet processing because rest - // frames in the packet may change the data to write. For example, lost - // data could be acknowledged. Also, connection is going to emit - // OnCanWrite signal post packet processing. - QUIC_BUG(session_write_mid_packet_processing) - << ENDPOINT << "Try to write mid packet processing."; - return; - } + if (connection_->framer().is_processing_packet()) { + // Do not write data in the middle of packet processing because rest + // frames in the packet may change the data to write. For example, lost + // data could be acknowledged. Also, connection is going to emit + // OnCanWrite signal post packet processing. + QUIC_BUG(session_write_mid_packet_processing) + << ENDPOINT << "Try to write mid packet processing."; + return; } if (!RetransmitLostData()) { // Cannot finish retransmitting lost data, connection is write blocked. @@ -739,8 +745,8 @@ bool QuicSession::HasPendingHandshake() const { return GetCryptoStream()->HasPendingCryptoRetransmission() || GetCryptoStream()->HasBufferedCryptoFrames(); } - return QuicContainsKey(streams_with_pending_retransmission_, - QuicUtils::GetCryptoStreamId(transport_version())) || + return streams_with_pending_retransmission_.contains( + QuicUtils::GetCryptoStreamId(transport_version())) || write_blocked_streams_.IsStreamBlocked( QuicUtils::GetCryptoStreamId(transport_version())); } @@ -748,19 +754,17 @@ bool QuicSession::HasPendingHandshake() const { void QuicSession::ProcessUdpPacket(const QuicSocketAddress& self_address, const QuicSocketAddress& peer_address, const QuicReceivedPacket& packet) { + QuicConnectionContextSwitcher cs(connection_->context()); connection_->ProcessUdpPacket(self_address, peer_address, packet); } -QuicConsumedData QuicSession::WritevData( - QuicStreamId id, - size_t write_length, - QuicStreamOffset offset, - StreamSendingState state, - TransmissionType type, - absl::optional<EncryptionLevel> level) { +QuicConsumedData QuicSession::WritevData(QuicStreamId id, size_t write_length, + QuicStreamOffset offset, + StreamSendingState state, + TransmissionType type, + EncryptionLevel level) { QUICHE_DCHECK(connection_->connected()) << ENDPOINT << "Try to write stream data when connection is closed."; - QUICHE_DCHECK(!use_write_or_buffer_data_at_level_ || level.has_value()); if (!IsEncryptionEstablished() && !QuicUtils::IsCryptoStreamId(transport_version(), id)) { // Do not let streams write without encryption. The calling stream will end @@ -768,11 +772,10 @@ QuicConsumedData QuicSession::WritevData( if (was_zero_rtt_rejected_ && !OneRttKeysAvailable()) { QUICHE_DCHECK(version().UsesTls() && perspective() == Perspective::IS_CLIENT); - QUIC_BUG_IF(quic_bug_12435_3, type == NOT_RETRANSMISSION) - << ENDPOINT << "Try to send new data on stream " << id - << "before 1-RTT keys are available while 0-RTT is rejected. " - "Version: " - << ParsedQuicVersionToString(version()); + QUIC_DLOG(INFO) << ENDPOINT + << "Suppress the write while 0-RTT gets rejected and " + "1-RTT keys are not available. Version: " + << ParsedQuicVersionToString(version()); } else if (version().UsesTls() || perspective() == Perspective::IS_SERVER) { QUIC_BUG(quic_bug_10866_2) << ENDPOINT << "Try to send data of stream " << id @@ -793,15 +796,7 @@ QuicConsumedData QuicSession::WritevData( } SetTransmissionType(type); - const auto current_level = connection()->encryption_level(); - if (!use_encryption_level_context()) { - if (level.has_value()) { - connection()->SetDefaultEncryptionLevel(level.value()); - } - } - QuicConnection::ScopedEncryptionLevelContext context( - use_encryption_level_context() ? connection() : nullptr, - use_encryption_level_context() ? level.value() : NUM_ENCRYPTION_LEVELS); + QuicConnection::ScopedEncryptionLevelContext context(connection(), level); QuicConsumedData data = connection_->SendStreamData(id, write_length, offset, state); @@ -810,14 +805,6 @@ QuicConsumedData QuicSession::WritevData( write_blocked_streams_.UpdateBytesForStream(id, data.bytes_consumed); } - // Restore the encryption level. - if (!use_encryption_level_context()) { - // Restore the encryption level. - if (level.has_value()) { - connection()->SetDefaultEncryptionLevel(current_level); - } - } - return data; } @@ -837,18 +824,9 @@ size_t QuicSession::SendCryptoData(EncryptionLevel level, return 0; } SetTransmissionType(type); - const auto current_level = connection()->encryption_level(); - if (!use_encryption_level_context()) { - connection_->SetDefaultEncryptionLevel(level); - } - QuicConnection::ScopedEncryptionLevelContext context( - use_encryption_level_context() ? connection() : nullptr, level); + QuicConnection::ScopedEncryptionLevelContext context(connection(), level); const auto bytes_consumed = connection_->SendCryptoData(level, write_length, offset); - if (!use_encryption_level_context()) { - // Restores encryption level. - connection_->SetDefaultEncryptionLevel(current_level); - } return bytes_consumed; } @@ -863,21 +841,13 @@ bool QuicSession::WriteControlFrame(const QuicFrame& frame, TransmissionType type) { QUICHE_DCHECK(connection()->connected()) << ENDPOINT << "Try to write control frames when connection is closed."; - if (connection_->encrypted_control_frames()) { - QUIC_RELOADABLE_FLAG_COUNT(quic_encrypted_control_frames); - if (!IsEncryptionEstablished()) { - QUIC_BUG(quic_bug_10866_4) - << ENDPOINT << "Tried to send control frame " << frame - << " before encryption is established. Last decrypted level: " - << EncryptionLevelToString(connection_->last_decrypted_level()); - return false; - } + if (!IsEncryptionEstablished()) { + // Suppress the write before encryption gets established. + return false; } SetTransmissionType(type); QuicConnection::ScopedEncryptionLevelContext context( - use_encryption_level_context() ? connection() : nullptr, - use_encryption_level_context() ? GetEncryptionLevelToSendApplicationData() - : NUM_ENCRYPTION_LEVELS); + connection(), GetEncryptionLevelToSendApplicationData()); return connection_->SendControlFrame(frame); } @@ -1329,8 +1299,7 @@ void QuicSession::OnConfigNegotiated() { // Or if this session is configured on TLS enabled QUIC versions, // attempt to retransmit 0-RTT data if there's any. // TODO(fayang): consider removing this OnCanWrite call. - if ((!connection_->donot_write_mid_packet_processing() || - !connection_->framer().is_processing_packet()) && + if (!connection_->framer().is_processing_packet() && (connection_->version().AllowsLowFlowControlLimits() || version().UsesTls())) { QUIC_CODE_COUNT(quic_session_on_can_write_on_config_negotiated); @@ -1637,8 +1606,7 @@ void QuicSession::SetDefaultEncryptionLevel(EncryptionLevel level) { // Retransmit old 0-RTT data (if any) with the new 0-RTT keys, since // they can't be decrypted by the server. connection_->MarkZeroRttPacketsForRetransmission(0); - if (!connection_->donot_write_mid_packet_processing() || - !connection_->framer().is_processing_packet()) { + if (!connection_->framer().is_processing_packet()) { // TODO(fayang): consider removing this OnCanWrite call. // Given any streams blocked by encryption a chance to write. QUIC_CODE_COUNT( @@ -1817,7 +1785,7 @@ void QuicSession::ActivateStream(std::unique_ptr<QuicStream> stream) { bool is_static = stream->is_static(); QUIC_DVLOG(1) << ENDPOINT << "num_streams: " << stream_map_.size() << ". activating stream " << stream_id; - QUICHE_DCHECK(!QuicContainsKey(stream_map_, stream_id)); + QUICHE_DCHECK(!stream_map_.contains(stream_id)); stream_map_[stream_id] = std::move(stream); if (is_static) { ++num_static_streams_; @@ -1897,7 +1865,7 @@ QuicStreamCount QuicSession::GetAdvertisedMaxIncomingBidirectionalStreams() } QuicStream* QuicSession::GetOrCreateStream(const QuicStreamId stream_id) { - QUICHE_DCHECK(!QuicContainsKey(pending_stream_map_, stream_id)); + QUICHE_DCHECK(!pending_stream_map_.contains(stream_id)); if (QuicUtils::IsCryptoStreamId(transport_version(), stream_id)) { return GetMutableCryptoStream(); } @@ -1936,7 +1904,7 @@ QuicStream* QuicSession::GetOrCreateStream(const QuicStreamId stream_id) { } void QuicSession::StreamDraining(QuicStreamId stream_id, bool unidirectional) { - QUICHE_DCHECK(QuicContainsKey(stream_map_, stream_id)); + QUICHE_DCHECK(stream_map_.contains(stream_id)); QUIC_DVLOG(1) << ENDPOINT << "Stream " << stream_id << " is draining"; if (VersionHasIetfQuicFrames(transport_version())) { ietf_streamid_manager_.OnStreamClosed(stream_id); @@ -2052,7 +2020,7 @@ bool QuicSession::IsOpenStream(QuicStreamId id) { if (it != stream_map_.end()) { return !it->second->IsZombie(); } - if (QuicContainsKey(pending_stream_map_, id) || + if (pending_stream_map_.contains(id) || QuicUtils::IsCryptoStreamId(transport_version(), id)) { // Stream is active return true; @@ -2106,14 +2074,16 @@ void QuicSession::SendAckFrequency(const QuicAckFrequencyFrame& frame) { } void QuicSession::SendNewConnectionId(const QuicNewConnectionIdFrame& frame) { - QUIC_RELOADABLE_FLAG_COUNT_N(quic_connection_migration_use_new_cid_v2, 1, 5); + // Count NEW_CONNECTION_ID frames sent to client. + QUIC_RELOADABLE_FLAG_COUNT_N(quic_connection_migration_use_new_cid_v2, 1, 6); control_frame_manager_.WriteOrBufferNewConnectionId( frame.connection_id, frame.sequence_number, frame.retire_prior_to, frame.stateless_reset_token); } void QuicSession::SendRetireConnectionId(uint64_t sequence_number) { - QUIC_RELOADABLE_FLAG_COUNT_N(quic_connection_migration_use_new_cid_v2, 2, 5); + // Count RETIRE_CONNECTION_ID frames sent to client. + QUIC_RELOADABLE_FLAG_COUNT_N(quic_connection_migration_use_new_cid_v2, 2, 6); control_frame_manager_.WriteOrBufferRetireConnectionId(sequence_number); } @@ -2272,8 +2242,8 @@ void QuicSession::OnFrameLost(const QuicFrame& frame) { frame.stream_frame.data_length, frame.stream_frame.fin); if (stream->HasPendingRetransmission() && - !QuicContainsKey(streams_with_pending_retransmission_, - frame.stream_frame.stream_id)) { + !streams_with_pending_retransmission_.contains( + frame.stream_frame.stream_id)) { streams_with_pending_retransmission_.insert( std::make_pair(frame.stream_frame.stream_id, true)); } @@ -2397,8 +2367,8 @@ bool QuicSession::RetransmitLostData() { } // Retransmit crypto data in stream 1 frames (version < 47). if (!uses_crypto_frames && - QuicContainsKey(streams_with_pending_retransmission_, - QuicUtils::GetCryptoStreamId(transport_version()))) { + streams_with_pending_retransmission_.contains( + QuicUtils::GetCryptoStreamId(transport_version()))) { // Retransmit crypto data first. QuicStream* crypto_stream = GetStream(QuicUtils::GetCryptoStreamId(transport_version())); @@ -2463,20 +2433,23 @@ void QuicSession::SetTransmissionType(TransmissionType type) { connection_->SetTransmissionType(type); } -MessageResult QuicSession::SendMessage(QuicMemSliceSpan message) { +MessageResult QuicSession::SendMessage(absl::Span<QuicMemSlice> message) { return SendMessage(message, /*flush=*/false); } -MessageResult QuicSession::SendMessage(QuicMemSliceSpan message, bool flush) { +MessageResult QuicSession::SendMessage(QuicMemSlice message) { + return SendMessage(absl::MakeSpan(&message, 1), /*flush=*/false); +} + +MessageResult QuicSession::SendMessage(absl::Span<QuicMemSlice> message, + bool flush) { QUICHE_DCHECK(connection_->connected()) << ENDPOINT << "Try to write messages when connection is closed."; if (!IsEncryptionEstablished()) { return {MESSAGE_STATUS_ENCRYPTION_NOT_ESTABLISHED, 0}; } QuicConnection::ScopedEncryptionLevelContext context( - use_encryption_level_context() ? connection() : nullptr, - use_encryption_level_context() ? GetEncryptionLevelToSendApplicationData() - : NUM_ENCRYPTION_LEVELS); + connection(), GetEncryptionLevelToSendApplicationData()); MessageStatus result = connection_->SendMessage(last_message_id_ + 1, message, flush); if (result == MESSAGE_STATUS_SUCCESS) { diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_session.h b/chromium/net/third_party/quiche/src/quic/core/quic_session.h index b6c1367019f..9eb7bd1614f 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_session.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_session.h @@ -17,6 +17,8 @@ #include "absl/container/flat_hash_map.h" #include "absl/strings/string_view.h" #include "absl/types/optional.h" +#include "absl/types/span.h" +#include "quic/core/crypto/tls_connection.h" #include "quic/core/frames/quic_ack_frequency_frame.h" #include "quic/core/handshaker_delegate_interface.h" #include "quic/core/legacy_quic_stream_id_manager.h" @@ -35,10 +37,11 @@ #include "quic/core/session_notifier_interface.h" #include "quic/core/stream_delegate_interface.h" #include "quic/core/uber_quic_stream_id_manager.h" -#include "quic/platform/api/quic_containers.h" #include "quic/platform/api/quic_export.h" #include "quic/platform/api/quic_flags.h" +#include "quic/platform/api/quic_mem_slice.h" #include "quic/platform/api/quic_socket_address.h" +#include "common/quiche_linked_hash_map.h" namespace quic { @@ -208,31 +211,39 @@ class QUIC_EXPORT_PRIVATE QuicSession const QuicSocketAddress& peer_address, const QuicReceivedPacket& packet); - // Called by application to send |message|. Data copy can be avoided if - // |message| is provided in reference counted memory. - // Please note, |message| provided in reference counted memory would be moved - // internally when message is successfully sent. Thereafter, it would be - // undefined behavior if callers try to access the slices through their own - // copy of the span object. - // Returns the message result which includes the message status and message ID - // (valid if the write succeeds). SendMessage flushes a message packet even it - // is not full. If the application wants to bundle other data in the same - // packet, please consider adding a packet flusher around the SendMessage - // and/or WritevData calls. + // Sends |message| as a QUIC DATAGRAM frame (QUIC MESSAGE frame in gQUIC). + // See <https://datatracker.ietf.org/doc/html/draft-ietf-quic-datagram> for + // more details. // - // OnMessageAcked and OnMessageLost are called when a particular message gets - // acked or lost. + // Returns a MessageResult struct which includes the status of the write + // operation and a message ID. The message ID (not sent on the wire) can be + // used to track the message; OnMessageAcked and OnMessageLost are called when + // a specific message gets acked or lost. + // + // If the write operation is successful, all of the slices in |message| are + // consumed, leaving them empty. If MESSAGE_STATUS_INTERNAL_ERROR is + // returned, the slices in question may or may not be consumed; it is no + // longer safe to access those. For all other status codes, |message| is kept + // intact. // // Note that SendMessage will fail with status = MESSAGE_STATUS_BLOCKED - // if connection is congestion control blocked or underlying socket is write - // blocked. In this case the caller can retry sending message again when + // if the connection is congestion control blocked or the underlying socket is + // write blocked. In this case the caller can retry sending message again when // connection becomes available, for example after getting OnCanWrite() // callback. - MessageResult SendMessage(QuicMemSliceSpan message); + // + // SendMessage flushes the current packet even it is not full; if the + // application needs to bundle other data in the same packet, consider using + // QuicConnection::ScopedPacketFlusher around the relevant write operations. + MessageResult SendMessage(absl::Span<QuicMemSlice> message); // Same as above SendMessage, except caller can specify if the given |message| // should be flushed even if the underlying connection is deemed unwritable. - MessageResult SendMessage(QuicMemSliceSpan message, bool flush); + MessageResult SendMessage(absl::Span<QuicMemSlice> message, bool flush); + + // Single-slice version of SendMessage(). Unlike the version above, this + // version always takes ownership of the slice. + MessageResult SendMessage(QuicMemSlice message); // Called when message with |message_id| gets acked. virtual void OnMessageAcked(QuicMessageId message_id, @@ -327,12 +338,10 @@ class QUIC_EXPORT_PRIVATE QuicSession // indicating if the fin bit was consumed. This does not indicate the data // has been sent on the wire: it may have been turned into a packet and queued // if the socket was unexpectedly blocked. - QuicConsumedData WritevData(QuicStreamId id, - size_t write_length, - QuicStreamOffset offset, - StreamSendingState state, + QuicConsumedData WritevData(QuicStreamId id, size_t write_length, + QuicStreamOffset offset, StreamSendingState state, TransmissionType type, - absl::optional<EncryptionLevel> level) override; + EncryptionLevel level) override; size_t SendCryptoData(EncryptionLevel level, size_t write_length, @@ -610,14 +619,9 @@ class QUIC_EXPORT_PRIVATE QuicSession return liveness_testing_in_progress_; } - bool use_write_or_buffer_data_at_level() const { - return use_write_or_buffer_data_at_level_; - } + bool permutes_tls_extensions() const { return permutes_tls_extensions_; } - bool use_encryption_level_context() const { - return connection_->use_encryption_level_context() && - use_write_or_buffer_data_at_level_; - } + virtual QuicSSLConfig GetSSLConfig() const { return QuicSSLConfig(); } protected: using StreamMap = @@ -925,7 +929,8 @@ class QUIC_EXPORT_PRIVATE QuicSession // TODO(fayang): switch to linked_hash_set when chromium supports it. The bool // is not used here. // List of streams with pending retransmissions. - QuicLinkedHashMap<QuicStreamId, bool> streams_with_pending_retransmission_; + quiche::QuicheLinkedHashMap<QuicStreamId, bool> + streams_with_pending_retransmission_; // Clean up closed_streams_ when this alarm fires. std::unique_ptr<QuicAlarm> closed_streams_clean_up_alarm_; @@ -947,8 +952,8 @@ class QUIC_EXPORT_PRIVATE QuicSession // creation of new outgoing bidirectional streams. bool liveness_testing_in_progress_; - const bool use_write_or_buffer_data_at_level_ = - GetQuicReloadableFlag(quic_use_write_or_buffer_data_at_level); + // Whether BoringSSL randomizes the order of TLS extensions. + bool permutes_tls_extensions_ = false; }; } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_session_test.cc b/chromium/net/third_party/quiche/src/quic/core/quic_session_test.cc index 81282fe9eeb..84b9e2edc7a 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_session_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_session_test.cc @@ -27,7 +27,6 @@ #include "quic/core/quic_versions.h" #include "quic/platform/api/quic_expect_bug.h" #include "quic/platform/api/quic_flags.h" -#include "quic/platform/api/quic_map_util.h" #include "quic/platform/api/quic_mem_slice_storage.h" #include "quic/platform/api/quic_test.h" #include "quic/platform/api/quic_test_mem_slice_vector.h" @@ -314,12 +313,10 @@ class TestSession : public QuicSession { return GetNumActiveStreams() > 0; } - QuicConsumedData WritevData(QuicStreamId id, - size_t write_length, - QuicStreamOffset offset, - StreamSendingState state, + QuicConsumedData WritevData(QuicStreamId id, size_t write_length, + QuicStreamOffset offset, StreamSendingState state, TransmissionType type, - absl::optional<EncryptionLevel> level) override { + EncryptionLevel level) override { bool fin = state != NO_FIN; QuicConsumedData consumed(write_length, fin); if (!writev_consumes_all_data_) { @@ -452,7 +449,7 @@ class QuicSessionTestBase : public QuicTestWithParam<ParsedQuicVersion> { QuicUtils::GetCryptoStreamId(connection_->transport_version()); } for (QuicStreamId i = first_stream_id; i < 100; i++) { - if (!QuicContainsKey(closed_streams_, i)) { + if (closed_streams_.find(i) == closed_streams_.end()) { EXPECT_FALSE(session_.IsClosedStream(i)) << " stream id: " << i; } else { EXPECT_TRUE(session_.IsClosedStream(i)) << " stream id: " << i; @@ -1529,56 +1526,6 @@ TEST_P(QuicSessionTestServer, HandshakeUnblocksFlowControlBlockedStream) { EXPECT_FALSE(session_.IsStreamFlowControlBlocked()); } -TEST_P(QuicSessionTestServer, HandshakeUnblocksFlowControlBlockedCryptoStream) { - if (QuicVersionUsesCryptoFrames(GetParam().transport_version) || - connection_->encrypted_control_frames()) { - // QUIC version 47 onwards uses CRYPTO frames for the handshake, so this - // test doesn't make sense for those versions since CRYPTO frames aren't - // flow controlled. - return; - } - // Test that if the crypto stream is flow control blocked, then if the SHLO - // contains a larger send window offset, the stream becomes unblocked. - session_.set_writev_consumes_all_data(true); - TestCryptoStream* crypto_stream = session_.GetMutableCryptoStream(); - EXPECT_FALSE(crypto_stream->IsFlowControlBlocked()); - EXPECT_FALSE(session_.IsConnectionFlowControlBlocked()); - EXPECT_FALSE(session_.IsStreamFlowControlBlocked()); - EXPECT_FALSE(session_.IsConnectionFlowControlBlocked()); - EXPECT_FALSE(session_.IsStreamFlowControlBlocked()); - EXPECT_CALL(*connection_, SendControlFrame(_)) - .WillOnce(Invoke(&ClearControlFrame)); - for (QuicStreamId i = 0; !crypto_stream->IsFlowControlBlocked() && i < 1000u; - i++) { - EXPECT_FALSE(session_.IsConnectionFlowControlBlocked()); - EXPECT_FALSE(session_.IsStreamFlowControlBlocked()); - QuicStreamOffset offset = crypto_stream->stream_bytes_written(); - QuicConfig config; - CryptoHandshakeMessage crypto_message; - config.ToHandshakeMessage(&crypto_message, transport_version()); - crypto_stream->SendHandshakeMessage(crypto_message, ENCRYPTION_INITIAL); - char buf[1000]; - QuicDataWriter writer(1000, buf, quiche::NETWORK_BYTE_ORDER); - crypto_stream->WriteStreamData(offset, crypto_message.size(), &writer); - } - EXPECT_TRUE(crypto_stream->IsFlowControlBlocked()); - EXPECT_FALSE(session_.IsConnectionFlowControlBlocked()); - EXPECT_TRUE(session_.IsStreamFlowControlBlocked()); - EXPECT_FALSE(session_.HasDataToWrite()); - EXPECT_TRUE(crypto_stream->HasBufferedData()); - - // Now complete the crypto handshake, resulting in an increased flow control - // send window. - CompleteHandshake(); - EXPECT_TRUE(QuicSessionPeer::IsStreamWriteBlocked( - &session_, - QuicUtils::GetCryptoStreamId(connection_->transport_version()))); - // Stream is now unblocked and will no longer have buffered data. - EXPECT_FALSE(crypto_stream->IsFlowControlBlocked()); - EXPECT_FALSE(session_.IsConnectionFlowControlBlocked()); - EXPECT_FALSE(session_.IsStreamFlowControlBlocked()); -} - TEST_P(QuicSessionTestServer, ConnectionFlowControlAccountingRstOutOfOrder) { CompleteHandshake(); // Test that when we receive an out of order stream RST we correctly adjust @@ -2451,36 +2398,26 @@ TEST_P(QuicSessionTestServer, RetransmitLostDataCausesConnectionClose) { TEST_P(QuicSessionTestServer, SendMessage) { // Cannot send message when encryption is not established. EXPECT_FALSE(session_.OneRttKeysAvailable()); - quic::QuicMemSliceStorage storage(nullptr, 0, nullptr, 0); EXPECT_EQ(MessageResult(MESSAGE_STATUS_ENCRYPTION_NOT_ESTABLISHED, 0), - session_.SendMessage( - MakeSpan(connection_->helper()->GetStreamSendBufferAllocator(), - "", &storage))); + session_.SendMessage(MemSliceFromString(""))); CompleteHandshake(); EXPECT_TRUE(session_.OneRttKeysAvailable()); - absl::string_view message; EXPECT_CALL(*connection_, SendMessage(1, _, false)) .WillOnce(Return(MESSAGE_STATUS_SUCCESS)); EXPECT_EQ(MessageResult(MESSAGE_STATUS_SUCCESS, 1), - session_.SendMessage( - MakeSpan(connection_->helper()->GetStreamSendBufferAllocator(), - message, &storage))); + session_.SendMessage(MemSliceFromString(""))); // Verify message_id increases. EXPECT_CALL(*connection_, SendMessage(2, _, false)) .WillOnce(Return(MESSAGE_STATUS_TOO_LARGE)); EXPECT_EQ(MessageResult(MESSAGE_STATUS_TOO_LARGE, 0), - session_.SendMessage( - MakeSpan(connection_->helper()->GetStreamSendBufferAllocator(), - message, &storage))); + session_.SendMessage(MemSliceFromString(""))); // Verify unsent message does not consume a message_id. EXPECT_CALL(*connection_, SendMessage(2, _, false)) .WillOnce(Return(MESSAGE_STATUS_SUCCESS)); EXPECT_EQ(MessageResult(MESSAGE_STATUS_SUCCESS, 2), - session_.SendMessage( - MakeSpan(connection_->helper()->GetStreamSendBufferAllocator(), - message, &storage))); + session_.SendMessage(MemSliceFromString(""))); QuicMessageFrame frame(1); QuicMessageFrame frame2(2); @@ -2507,7 +2444,7 @@ TEST_P(QuicSessionTestServer, LocallyResetZombieStreams) { EXPECT_TRUE(stream2->IsWaitingForAcks()); // Verify stream2 is a zombie streams. auto& stream_map = QuicSessionPeer::stream_map(&session_); - ASSERT_TRUE(QuicContainsKey(stream_map, stream2->id())); + ASSERT_TRUE(stream_map.contains(stream2->id())); auto* stream = stream_map.find(stream2->id())->second.get(); EXPECT_TRUE(stream->IsZombie()); @@ -2556,7 +2493,7 @@ TEST_P(QuicSessionTestServer, WriteUnidirectionalStream) { stream4->WriteOrBufferData(body, false, nullptr); stream4->WriteOrBufferData(body, true, nullptr); auto& stream_map = QuicSessionPeer::stream_map(&session_); - ASSERT_TRUE(QuicContainsKey(stream_map, stream4->id())); + ASSERT_TRUE(stream_map.contains(stream4->id())); auto* stream = stream_map.find(stream4->id())->second.get(); EXPECT_TRUE(stream->IsZombie()); } diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_simple_buffer_allocator.h b/chromium/net/third_party/quiche/src/quic/core/quic_simple_buffer_allocator.h index cbf73b3dcf4..c2a800d5753 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_simple_buffer_allocator.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_simple_buffer_allocator.h @@ -12,6 +12,11 @@ namespace quic { class QUIC_EXPORT_PRIVATE SimpleBufferAllocator : public QuicBufferAllocator { public: + static SimpleBufferAllocator* Get() { + static SimpleBufferAllocator* singleton = new SimpleBufferAllocator(); + return singleton; + } + char* New(size_t size) override; char* New(size_t size, bool flag_enable) override; void Delete(char* buffer) override; diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_simple_buffer_allocator_test.cc b/chromium/net/third_party/quiche/src/quic/core/quic_simple_buffer_allocator_test.cc index bc3f84437f8..5416cd424ef 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_simple_buffer_allocator_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_simple_buffer_allocator_test.cc @@ -24,5 +24,43 @@ TEST_F(SimpleBufferAllocatorTest, DeleteNull) { alloc.Delete(nullptr); } +TEST_F(SimpleBufferAllocatorTest, MoveBuffersConstructor) { + SimpleBufferAllocator alloc; + QuicBuffer buffer1(&alloc, 16); + + EXPECT_NE(buffer1.data(), nullptr); + EXPECT_EQ(buffer1.size(), 16u); + + QuicBuffer buffer2(std::move(buffer1)); + EXPECT_EQ(buffer1.data(), nullptr); // NOLINT(bugprone-use-after-move) + EXPECT_EQ(buffer1.size(), 0u); + EXPECT_NE(buffer2.data(), nullptr); + EXPECT_EQ(buffer2.size(), 16u); +} + +TEST_F(SimpleBufferAllocatorTest, MoveBuffersAssignment) { + SimpleBufferAllocator alloc; + QuicBuffer buffer1(&alloc, 16); + QuicBuffer buffer2; + + EXPECT_NE(buffer1.data(), nullptr); + EXPECT_EQ(buffer1.size(), 16u); + EXPECT_EQ(buffer2.data(), nullptr); + EXPECT_EQ(buffer2.size(), 0u); + + buffer2 = std::move(buffer1); + EXPECT_EQ(buffer1.data(), nullptr); // NOLINT(bugprone-use-after-move) + EXPECT_EQ(buffer1.size(), 0u); + EXPECT_NE(buffer2.data(), nullptr); + EXPECT_EQ(buffer2.size(), 16u); +} + +TEST_F(SimpleBufferAllocatorTest, CopyBuffer) { + SimpleBufferAllocator alloc; + const absl::string_view original = "Test string"; + QuicBuffer copy = QuicBuffer::Copy(&alloc, original); + EXPECT_EQ(copy.AsStringView(), original); +} + } // namespace } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_stream.cc b/chromium/net/third_party/quiche/src/quic/core/quic_stream.cc index 3f1b7d94e18..cde50ef214d 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_stream.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_stream.cc @@ -19,6 +19,7 @@ #include "quic/platform/api/quic_flag_utils.h" #include "quic/platform/api/quic_flags.h" #include "quic/platform/api/quic_logging.h" +#include "quic/platform/api/quic_mem_slice.h" using spdy::SpdyPriority; @@ -630,23 +631,18 @@ void QuicStream::WriteOrBufferData( absl::string_view data, bool fin, QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) { - if (session()->use_write_or_buffer_data_at_level()) { - QUIC_BUG_IF(quic_bug_12570_4, - QuicUtils::IsCryptoStreamId(transport_version(), id_)) - << ENDPOINT - << "WriteOrBufferData is used to send application data, use " - "WriteOrBufferDataAtLevel to send crypto data."; - return WriteOrBufferDataAtLevel( - data, fin, session()->GetEncryptionLevelToSendApplicationData(), - ack_listener); - } - return WriteOrBufferDataInner(data, fin, absl::nullopt, ack_listener); + QUIC_BUG_IF(quic_bug_12570_4, + QuicUtils::IsCryptoStreamId(transport_version(), id_)) + << ENDPOINT + << "WriteOrBufferData is used to send application data, use " + "WriteOrBufferDataAtLevel to send crypto data."; + return WriteOrBufferDataAtLevel( + data, fin, session()->GetEncryptionLevelToSendApplicationData(), + ack_listener); } -void QuicStream::WriteOrBufferDataInner( - absl::string_view data, - bool fin, - absl::optional<EncryptionLevel> level, +void QuicStream::WriteOrBufferDataAtLevel( + absl::string_view data, bool fin, EncryptionLevel level, QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) { if (data.empty() && !fin) { QUIC_BUG(quic_bug_10586_2) << "data.empty() && !fin"; @@ -691,16 +687,6 @@ void QuicStream::WriteOrBufferDataInner( } } -void QuicStream::WriteOrBufferDataAtLevel( - absl::string_view data, - bool fin, - EncryptionLevel level, - QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) { - QUICHE_DCHECK(session()->use_write_or_buffer_data_at_level()); - QUIC_RELOADABLE_FLAG_COUNT(quic_use_write_or_buffer_data_at_level); - return WriteOrBufferDataInner(data, fin, level, ack_listener); -} - void QuicStream::OnCanWrite() { if (HasDeadlinePassed()) { OnDeadlinePassed(); @@ -720,11 +706,7 @@ void QuicStream::OnCanWrite() { return; } if (HasBufferedData() || (fin_buffered_ && !fin_sent_)) { - absl::optional<EncryptionLevel> send_level = absl::nullopt; - if (session()->use_write_or_buffer_data_at_level()) { - send_level = session()->GetEncryptionLevelToSendApplicationData(); - } - WriteBufferedData(send_level); + WriteBufferedData(session()->GetEncryptionLevelToSendApplicationData()); } if (!fin_buffered_ && !fin_sent_ && CanWriteNewData()) { // Notify upper layer to write new data when buffered data size is below @@ -759,6 +741,16 @@ void QuicStream::MaybeSendBlocked() { } QuicConsumedData QuicStream::WriteMemSlices(QuicMemSliceSpan span, bool fin) { + return WriteMemSlicesInner(MemSliceSpanWrapper(span), fin); +} + +QuicConsumedData QuicStream::WriteMemSlices(absl::Span<QuicMemSlice> span, + bool fin) { + return WriteMemSlicesInner(MemSliceSpanWrapper(span), fin); +} + +QuicConsumedData QuicStream::WriteMemSlicesInner(MemSliceSpanWrapper span, + bool fin) { QuicConsumedData consumed_data(0, false); if (span.empty() && !fin) { QUIC_BUG(quic_bug_10586_6) << "span.empty() && !fin"; @@ -786,7 +778,7 @@ QuicConsumedData QuicStream::WriteMemSlices(QuicMemSliceSpan span, bool fin) { if (!span.empty()) { // Buffer all data if buffered data size is below limit. QuicStreamOffset offset = send_buffer_.stream_offset(); - consumed_data.bytes_consumed = send_buffer_.SaveMemSliceSpan(span); + consumed_data.bytes_consumed = span.SaveTo(send_buffer_); if (offset > send_buffer_.stream_offset() || kMaxStreamLength < send_buffer_.stream_offset()) { QUIC_BUG(quic_bug_10586_8) << "Write too many data via stream " << id_; @@ -802,11 +794,7 @@ QuicConsumedData QuicStream::WriteMemSlices(QuicMemSliceSpan span, bool fin) { if (!had_buffered_data && (HasBufferedData() || fin_buffered_)) { // Write data if there is no buffered data before. - absl::optional<EncryptionLevel> send_level = absl::nullopt; - if (session()->use_write_or_buffer_data_at_level()) { - send_level = session()->GetEncryptionLevelToSendApplicationData(); - } - WriteBufferedData(send_level); + WriteBufferedData(session()->GetEncryptionLevelToSendApplicationData()); } return consumed_data; @@ -1148,10 +1136,6 @@ bool QuicStream::RetransmitStreamData(QuicStreamOffset offset, if (retransmission.Empty() && !retransmit_fin) { return true; } - absl::optional<EncryptionLevel> send_level = absl::nullopt; - if (session()->use_write_or_buffer_data_at_level()) { - send_level = session()->GetEncryptionLevelToSendApplicationData(); - } QuicConsumedData consumed(0, false); for (const auto& interval : retransmission) { QuicStreamOffset retransmission_offset = interval.min(); @@ -1161,7 +1145,8 @@ bool QuicStream::RetransmitStreamData(QuicStreamOffset offset, stream_bytes_written()); consumed = stream_delegate_->WritevData( id_, retransmission_length, retransmission_offset, - can_bundle_fin ? FIN : NO_FIN, type, send_level); + can_bundle_fin ? FIN : NO_FIN, type, + session()->GetEncryptionLevelToSendApplicationData()); QUIC_DVLOG(1) << ENDPOINT << "stream " << id_ << " is forced to retransmit stream data [" << retransmission_offset << ", " @@ -1182,8 +1167,9 @@ bool QuicStream::RetransmitStreamData(QuicStreamOffset offset, if (retransmit_fin) { QUIC_DVLOG(1) << ENDPOINT << "stream " << id_ << " retransmits fin only frame."; - consumed = stream_delegate_->WritevData(id_, 0, stream_bytes_written(), FIN, - type, send_level); + consumed = stream_delegate_->WritevData( + id_, 0, stream_bytes_written(), FIN, type, + session()->GetEncryptionLevelToSendApplicationData()); if (!consumed.fin_consumed) { return false; } @@ -1209,7 +1195,7 @@ bool QuicStream::WriteStreamData(QuicStreamOffset offset, return send_buffer_.WriteStreamData(offset, data_length, writer); } -void QuicStream::WriteBufferedData(absl::optional<EncryptionLevel> level) { +void QuicStream::WriteBufferedData(EncryptionLevel level) { QUICHE_DCHECK(!write_side_closed_ && (HasBufferedData() || fin_buffered_)); if (session_->ShouldYield(id())) { @@ -1333,15 +1319,12 @@ void QuicStream::OnStreamDataConsumed(QuicByteCount bytes_consumed) { void QuicStream::WritePendingRetransmission() { while (HasPendingRetransmission()) { QuicConsumedData consumed(0, false); - absl::optional<EncryptionLevel> send_level = absl::nullopt; - if (session()->use_write_or_buffer_data_at_level()) { - send_level = session()->GetEncryptionLevelToSendApplicationData(); - } if (!send_buffer_.HasPendingRetransmission()) { QUIC_DVLOG(1) << ENDPOINT << "stream " << id_ << " retransmits fin only frame."; consumed = stream_delegate_->WritevData( - id_, 0, stream_bytes_written(), FIN, LOSS_RETRANSMISSION, send_level); + id_, 0, stream_bytes_written(), FIN, LOSS_RETRANSMISSION, + session()->GetEncryptionLevelToSendApplicationData()); fin_lost_ = !consumed.fin_consumed; if (fin_lost_) { // Connection is write blocked. @@ -1356,7 +1339,8 @@ void QuicStream::WritePendingRetransmission() { (pending.offset + pending.length == stream_bytes_written()); consumed = stream_delegate_->WritevData( id_, pending.length, pending.offset, can_bundle_fin ? FIN : NO_FIN, - LOSS_RETRANSMISSION, send_level); + LOSS_RETRANSMISSION, + session()->GetEncryptionLevelToSendApplicationData()); QUIC_DVLOG(1) << ENDPOINT << "stream " << id_ << " tries to retransmit stream data [" << pending.offset << ", " << pending.offset + pending.length diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_stream.h b/chromium/net/third_party/quiche/src/quic/core/quic_stream.h index 6e6e623b9f2..6374d8ab6f6 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_stream.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_stream.h @@ -24,6 +24,7 @@ #include "absl/strings/string_view.h" #include "absl/types/optional.h" +#include "absl/types/span.h" #include "quic/core/quic_flow_controller.h" #include "quic/core/quic_packets.h" #include "quic/core/quic_stream_send_buffer.h" @@ -32,6 +33,7 @@ #include "quic/core/session_notifier_interface.h" #include "quic/core/stream_delegate_interface.h" #include "quic/platform/api/quic_export.h" +#include "quic/platform/api/quic_mem_slice.h" #include "quic/platform/api/quic_mem_slice_span.h" #include "quic/platform/api/quic_reference_counted.h" #include "spdy/core/spdy_protocol.h" @@ -343,9 +345,13 @@ class QUIC_EXPORT_PRIVATE QuicStream // succeeds. bool MaybeSetTtl(QuicTime::Delta ttl); - // Same as WritevData except data is provided in reference counted memory so - // that data copy is avoided. + // Commits data into the stream write buffer, and potentially sends it over + // the wire. This method has all-or-nothing semantics: if the write buffer is + // not full, all of the memslices in |span| are moved into it; otherwise, + // nothing happens. + // TODO(vasilvv): deprecate and remove QuicMemSliceSpan version. QuicConsumedData WriteMemSlices(QuicMemSliceSpan span, bool fin); + QuicConsumedData WriteMemSlices(absl::Span<QuicMemSlice> span, bool fin); // Returns true if any stream data is lost (including fin) and needs to be // retransmitted. @@ -464,6 +470,26 @@ class QUIC_EXPORT_PRIVATE QuicStream friend class test::QuicStreamPeer; friend class QuicStreamUtils; + // Wraps around either QuicMemSliceSpan or absl::Span<QuicMemSlice>. + // TODO(vasilvv): delete this after QuicMemSliceSpan is gone. + class QUIC_EXPORT_PRIVATE MemSliceSpanWrapper { + public: + explicit MemSliceSpanWrapper(QuicMemSliceSpan span) : old_(span) {} + explicit MemSliceSpanWrapper(absl::Span<QuicMemSlice> span) : new_(span) {} + + bool empty() { return old_.has_value() ? old_->empty() : new_.empty(); } + QuicByteCount SaveTo(QuicStreamSendBuffer& send_buffer) { + if (old_.has_value()) { + return send_buffer.SaveMemSliceSpan(*old_); + } + return send_buffer.SaveMemSliceSpan(new_); + } + + private: + absl::optional<QuicMemSliceSpan> old_; + absl::Span<QuicMemSlice> new_; + }; + QuicStream(QuicStreamId id, QuicSession* session, QuicStreamSequencer sequencer, @@ -480,10 +506,8 @@ class QUIC_EXPORT_PRIVATE QuicStream // controller, marks this stream as connection-level write blocked. void MaybeSendBlocked(); - // Write buffered data in send buffer. - // TODO(fayang): Change absl::optional<EncryptionLevel> to EncryptionLevel - // when deprecating quic_use_write_or_buffer_data_at_level. - void WriteBufferedData(absl::optional<EncryptionLevel> level); + // Write buffered data (in send buffer) at |level|. + void WriteBufferedData(EncryptionLevel level); // Close the read side of the stream. May cause the stream to be closed. void CloseReadSide(); @@ -491,17 +515,11 @@ class QUIC_EXPORT_PRIVATE QuicStream // Called when bytes are sent to the peer. void AddBytesSent(QuicByteCount bytes); - // TODO(fayang): Inline this function when deprecating - // quic_use_write_or_buffer_data_at_level. - void WriteOrBufferDataInner( - absl::string_view data, - bool fin, - absl::optional<EncryptionLevel> level, - QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener); - // Returns true if deadline_ has passed. bool HasDeadlinePassed() const; + QuicConsumedData WriteMemSlicesInner(MemSliceSpanWrapper span, bool fin); + QuicStreamSequencer sequencer_; QuicStreamId id_; // Pointer to the owning QuicSession object. diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_stream_id_manager.cc b/chromium/net/third_party/quiche/src/quic/core/quic_stream_id_manager.cc index 05562125557..40aa3d0f949 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_stream_id_manager.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_stream_id_manager.cc @@ -217,7 +217,7 @@ bool QuicStreamIdManager::IsAvailableStream(QuicStreamId id) const { return largest_peer_created_stream_id_ == QuicUtils::GetInvalidStreamId(version_.transport_version) || id > largest_peer_created_stream_id_ || - QuicContainsKey(available_streams_, id); + available_streams_.contains(id); } QuicStreamId QuicStreamIdManager::GetFirstOutgoingStreamId() const { diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_stream_send_buffer.cc b/chromium/net/third_party/quiche/src/quic/core/quic_stream_send_buffer.cc index 3b1b37bd1f3..1e32e544cd1 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_stream_send_buffer.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_stream_send_buffer.cc @@ -12,6 +12,7 @@ #include "quic/platform/api/quic_flag_utils.h" #include "quic/platform/api/quic_flags.h" #include "quic/platform/api/quic_logging.h" +#include "quic/platform/api/quic_mem_slice.h" namespace quic { @@ -96,6 +97,20 @@ QuicByteCount QuicStreamSendBuffer::SaveMemSliceSpan(QuicMemSliceSpan span) { [&](QuicMemSlice slice) { SaveMemSlice(std::move(slice)); }); } +QuicByteCount QuicStreamSendBuffer::SaveMemSliceSpan( + absl::Span<QuicMemSlice> span) { + QuicByteCount total = 0; + for (QuicMemSlice& slice : span) { + if (slice.length() == 0) { + // Skip empty slices. + continue; + } + total += slice.length(); + SaveMemSlice(std::move(slice)); + } + return total; +} + void QuicStreamSendBuffer::OnStreamDataConsumed(size_t bytes_consumed) { stream_bytes_written_ += bytes_consumed; stream_bytes_outstanding_ += bytes_consumed; diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_stream_send_buffer.h b/chromium/net/third_party/quiche/src/quic/core/quic_stream_send_buffer.h index 4b23d3d82cd..f91e4760870 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_stream_send_buffer.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_stream_send_buffer.h @@ -5,6 +5,7 @@ #ifndef QUICHE_QUIC_CORE_QUIC_STREAM_SEND_BUFFER_H_ #define QUICHE_QUIC_CORE_QUIC_STREAM_SEND_BUFFER_H_ +#include "absl/types/span.h" #include "quic/core/frames/quic_stream_frame.h" #include "quic/core/quic_interval_deque.h" #include "quic/core/quic_interval_set.h" @@ -80,6 +81,7 @@ class QUIC_EXPORT_PRIVATE QuicStreamSendBuffer { // Save all slices in |span| to send buffer. Return total bytes saved. QuicByteCount SaveMemSliceSpan(QuicMemSliceSpan span); + QuicByteCount SaveMemSliceSpan(absl::Span<QuicMemSlice> span); // Called when |bytes_consumed| bytes has been consumed by the stream. void OnStreamDataConsumed(size_t bytes_consumed); diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_stream_send_buffer_test.cc b/chromium/net/third_party/quiche/src/quic/core/quic_stream_send_buffer_test.cc index 25dda04c566..e1a2358acba 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_stream_send_buffer_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_stream_send_buffer_test.cc @@ -39,12 +39,12 @@ class QuicStreamSendBufferTest : public QuicTest { iov[0] = MakeIovec(absl::string_view(data1)); iov[1] = MakeIovec(absl::string_view(data2)); - QuicUniqueBufferPtr buffer1 = MakeUniqueBuffer(&allocator_, 1024); - memset(buffer1.get(), 'c', 1024); - QuicMemSlice slice1(std::move(buffer1), 1024); - QuicUniqueBufferPtr buffer2 = MakeUniqueBuffer(&allocator_, 768); - memset(buffer2.get(), 'd', 768); - QuicMemSlice slice2(std::move(buffer2), 768); + QuicBuffer buffer1(&allocator_, 1024); + memset(buffer1.data(), 'c', buffer1.size()); + QuicMemSlice slice1(std::move(buffer1)); + QuicBuffer buffer2(&allocator_, 768); + memset(buffer2.data(), 'd', buffer2.size()); + QuicMemSlice slice2(std::move(buffer2)); // The stream offset should be 0 since nothing is written. EXPECT_EQ(0u, QuicStreamSendBufferPeer::EndOffset(&send_buffer_)); @@ -309,9 +309,9 @@ TEST_F(QuicStreamSendBufferTest, EndOffset) { // Last offset is end offset of last slice. EXPECT_EQ(3840u, QuicStreamSendBufferPeer::EndOffset(&send_buffer_)); - QuicUniqueBufferPtr buffer = MakeUniqueBuffer(&allocator_, 60); - memset(buffer.get(), 'e', 60); - QuicMemSlice slice(std::move(buffer), 60); + QuicBuffer buffer(&allocator_, 60); + memset(buffer.data(), 'e', buffer.size()); + QuicMemSlice slice(std::move(buffer)); send_buffer_.SaveMemSlice(std::move(slice)); EXPECT_EQ(3840u, QuicStreamSendBufferPeer::EndOffset(&send_buffer_)); diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_stream_test.cc b/chromium/net/third_party/quiche/src/quic/core/quic_stream_test.cc index 2d3298b6fc9..e69b657177d 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_stream_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_stream_test.cc @@ -1178,14 +1178,17 @@ TEST_P(QuicStreamTest, WriteMemSlices) { SetQuicFlag(FLAGS_quic_buffered_data_threshold, 100); Initialize(); - char data[1024]; - std::vector<std::pair<char*, size_t>> buffers; - buffers.push_back(std::make_pair(data, ABSL_ARRAYSIZE(data))); - buffers.push_back(std::make_pair(data, ABSL_ARRAYSIZE(data))); - QuicTestMemSliceVector vector1(buffers); - QuicTestMemSliceVector vector2(buffers); - QuicMemSliceSpan span1 = vector1.span(); - QuicMemSliceSpan span2 = vector2.span(); + constexpr QuicByteCount kDataSize = 1024; + QuicBufferAllocator* allocator = + connection_->helper()->GetStreamSendBufferAllocator(); + std::vector<QuicMemSlice> vector1; + vector1.push_back(QuicMemSlice(QuicBuffer(allocator, kDataSize))); + vector1.push_back(QuicMemSlice(QuicBuffer(allocator, kDataSize))); + std::vector<QuicMemSlice> vector2; + vector2.push_back(QuicMemSlice(QuicBuffer(allocator, kDataSize))); + vector2.push_back(QuicMemSlice(QuicBuffer(allocator, kDataSize))); + absl::Span<QuicMemSlice> span1(vector1); + absl::Span<QuicMemSlice> span2(vector2); EXPECT_CALL(*session_, WritevData(_, _, _, _, _, _)) .WillOnce(InvokeWithoutArgs([this]() { @@ -1196,7 +1199,7 @@ TEST_P(QuicStreamTest, WriteMemSlices) { QuicConsumedData consumed = stream_->WriteMemSlices(span1, false); EXPECT_EQ(2048u, consumed.bytes_consumed); EXPECT_FALSE(consumed.fin_consumed); - EXPECT_EQ(2 * ABSL_ARRAYSIZE(data) - 100, stream_->BufferedDataBytes()); + EXPECT_EQ(2 * kDataSize - 100, stream_->BufferedDataBytes()); EXPECT_FALSE(stream_->fin_buffered()); EXPECT_CALL(*session_, WritevData(_, _, _, _, _, _)).Times(0); @@ -1204,12 +1207,11 @@ TEST_P(QuicStreamTest, WriteMemSlices) { consumed = stream_->WriteMemSlices(span2, true); EXPECT_EQ(0u, consumed.bytes_consumed); EXPECT_FALSE(consumed.fin_consumed); - EXPECT_EQ(2 * ABSL_ARRAYSIZE(data) - 100, stream_->BufferedDataBytes()); + EXPECT_EQ(2 * kDataSize - 100, stream_->BufferedDataBytes()); EXPECT_FALSE(stream_->fin_buffered()); QuicByteCount data_to_write = - 2 * ABSL_ARRAYSIZE(data) - 100 - - GetQuicFlag(FLAGS_quic_buffered_data_threshold) + 1; + 2 * kDataSize - 100 - GetQuicFlag(FLAGS_quic_buffered_data_threshold) + 1; EXPECT_CALL(*session_, WritevData(_, _, _, _, _, _)) .WillOnce(InvokeWithoutArgs([this, data_to_write]() { return session_->ConsumeData(stream_->id(), data_to_write, 100u, NO_FIN, @@ -1225,8 +1227,7 @@ TEST_P(QuicStreamTest, WriteMemSlices) { consumed = stream_->WriteMemSlices(span2, true); EXPECT_EQ(2048u, consumed.bytes_consumed); EXPECT_TRUE(consumed.fin_consumed); - EXPECT_EQ(2 * ABSL_ARRAYSIZE(data) + - GetQuicFlag(FLAGS_quic_buffered_data_threshold) - 1, + EXPECT_EQ(2 * kDataSize + GetQuicFlag(FLAGS_quic_buffered_data_threshold) - 1, stream_->BufferedDataBytes()); EXPECT_TRUE(stream_->fin_buffered()); diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_time_wait_list_manager.cc b/chromium/net/third_party/quiche/src/quic/core/quic_time_wait_list_manager.cc index dbaa38bdefe..12f5ef9aae2 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_time_wait_list_manager.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_time_wait_list_manager.cc @@ -22,7 +22,6 @@ #include "quic/platform/api/quic_flag_utils.h" #include "quic/platform/api/quic_flags.h" #include "quic/platform/api/quic_logging.h" -#include "quic/platform/api/quic_map_util.h" #include "quic/platform/api/quic_socket_address.h" #include "common/quiche_text_utils.h" @@ -174,7 +173,7 @@ bool QuicTimeWaitListManager::IsConnectionIdInTimeWait( if (use_indirect_connection_id_map_) { return indirect_connection_id_map_.contains(connection_id); } - return QuicContainsKey(connection_id_map_, connection_id); + return connection_id_map_.contains(connection_id); } void QuicTimeWaitListManager::OnBlockedWriterCanWrite() { diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_time_wait_list_manager.h b/chromium/net/third_party/quiche/src/quic/core/quic_time_wait_list_manager.h index ac371f2c180..fe15159d98c 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_time_wait_list_manager.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_time_wait_list_manager.h @@ -19,8 +19,8 @@ #include "quic/core/quic_packets.h" #include "quic/core/quic_session.h" #include "quic/core/quic_types.h" -#include "quic/platform/api/quic_containers.h" #include "quic/platform/api/quic_flags.h" +#include "common/quiche_linked_hash_map.h" namespace quic { @@ -285,10 +285,11 @@ class QUIC_NO_EXPORT QuicTimeWaitListManager TimeWaitConnectionInfo info; }; - // QuicLinkedHashMap allows lookup by ConnectionId and traversal in add order. - using ConnectionIdMap = QuicLinkedHashMap<QuicConnectionId, - ConnectionIdData, - QuicConnectionIdHash>; + // QuicheLinkedHashMap allows lookup by ConnectionId + // and traversal in add order. + using ConnectionIdMap = quiche::QuicheLinkedHashMap<QuicConnectionId, + ConnectionIdData, + QuicConnectionIdHash>; // Do not use find/emplace/erase on this map directly. Use // FindConnectionIdDataInMap, AddConnectionIdDateToMap, // RemoveConnectionDataFromMap instead. diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_time_wait_list_manager_test.cc b/chromium/net/third_party/quiche/src/quic/core/quic_time_wait_list_manager_test.cc index 4e9f957182b..f47bdf2fed8 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_time_wait_list_manager_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_time_wait_list_manager_test.cc @@ -204,13 +204,13 @@ class QuicTimeWaitListManagerTest : public QuicTest { bool ValidPublicResetPacketPredicate( QuicConnectionId expected_connection_id, - const testing::tuple<const char*, int>& packet_buffer) { + const std::tuple<const char*, int>& packet_buffer) { FramerVisitorCapturingPublicReset visitor(expected_connection_id); QuicFramer framer(AllSupportedVersions(), QuicTime::Zero(), Perspective::IS_CLIENT, kQuicDefaultConnectionIdLength); framer.set_visitor(&visitor); - QuicEncryptedPacket encrypted(testing::get<0>(packet_buffer), - testing::get<1>(packet_buffer)); + QuicEncryptedPacket encrypted(std::get<0>(packet_buffer), + std::get<1>(packet_buffer)); framer.ProcessPacket(encrypted); QuicPublicResetPacket packet = visitor.public_reset_packet(); bool public_reset_is_valid = @@ -230,10 +230,10 @@ bool ValidPublicResetPacketPredicate( return public_reset_is_valid || stateless_reset_is_valid; } -Matcher<const testing::tuple<const char*, int>> PublicResetPacketEq( +Matcher<const std::tuple<const char*, int>> PublicResetPacketEq( QuicConnectionId connection_id) { return Truly( - [connection_id](const testing::tuple<const char*, int> packet_buffer) { + [connection_id](const std::tuple<const char*, int> packet_buffer) { return ValidPublicResetPacketPredicate(connection_id, packet_buffer); }); } diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_types.cc b/chromium/net/third_party/quiche/src/quic/core/quic_types.cc index 57163abbe42..f578bb46c36 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_types.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_types.cc @@ -225,7 +225,6 @@ std::string TransmissionTypeToString(TransmissionType transmission_type) { return "INVALID_TRANSMISSION_TYPE"; } return absl::StrCat("Unknown(", static_cast<int>(transmission_type), ")"); - break; } } @@ -267,7 +266,6 @@ std::string MessageStatusToString(MessageStatus message_status) { RETURN_STRING_LITERAL(MESSAGE_STATUS_INTERNAL_ERROR); default: return absl::StrCat("Unknown(", static_cast<int>(message_status), ")"); - break; } } @@ -292,7 +290,6 @@ std::string PacketNumberSpaceToString(PacketNumberSpace packet_number_space) { default: return absl::StrCat("Unknown(", static_cast<int>(packet_number_space), ")"); - break; } } @@ -320,7 +317,6 @@ std::string EncryptionLevelToString(EncryptionLevel level) { RETURN_STRING_LITERAL(ENCRYPTION_FORWARD_SECURE); default: return absl::StrCat("Unknown(", static_cast<int>(level), ")"); - break; } } @@ -336,7 +332,6 @@ std::string QuicConnectionCloseTypeString(QuicConnectionCloseType type) { RETURN_STRING_LITERAL(IETF_QUIC_APPLICATION_CONNECTION_CLOSE); default: return absl::StrCat("Unknown(", static_cast<int>(type), ")"); - break; } } @@ -378,7 +373,6 @@ std::string KeyUpdateReasonString(KeyUpdateReason reason) { RETURN_REASON_LITERAL(kLocalKeyUpdateLimitOverride); default: return absl::StrCat("Unknown(", static_cast<int>(reason), ")"); - break; } #undef RETURN_REASON_LITERAL } diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_types.h b/chromium/net/third_party/quiche/src/quic/core/quic_types.h index 2e65e5f12a4..eb4db074ef4 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_types.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_types.h @@ -12,6 +12,7 @@ #include <ostream> #include <vector> +#include "absl/container/inlined_vector.h" #include "quic/core/quic_connection_id.h" #include "quic/core/quic_error_codes.h" #include "quic/core/quic_packet_number.h" @@ -25,7 +26,13 @@ namespace quic { using QuicPacketLength = uint16_t; using QuicControlFrameId = uint32_t; using QuicMessageId = uint32_t; -using QuicDatagramFlowId = uint64_t; + +// TODO(b/181256914) replace QuicDatagramStreamId with QuicStreamId once we +// remove support for draft-ietf-masque-h3-datagram-00 in favor of later drafts. +using QuicDatagramStreamId = uint64_t; +using QuicDatagramContextId = uint64_t; +// Note that for draft-ietf-masque-h3-datagram-00, we represent the flow ID as a +// QuicDatagramStreamId. // IMPORTANT: IETF QUIC defines stream IDs and stream counts as being unsigned // 62-bit numbers. However, we have decided to only support up to 2^32-1 streams @@ -580,7 +587,7 @@ struct QUIC_EXPORT_PRIVATE AckedPacket { }; // A vector of acked packets. -using AckedPacketVector = QuicInlinedVector<AckedPacket, 2>; +using AckedPacketVector = absl::InlinedVector<AckedPacket, 2>; // Information about a newly lost packet. struct QUIC_EXPORT_PRIVATE LostPacket { @@ -597,7 +604,7 @@ struct QUIC_EXPORT_PRIVATE LostPacket { }; // A vector of lost packets. -using LostPacketVector = QuicInlinedVector<LostPacket, 2>; +using LostPacketVector = absl::InlinedVector<LostPacket, 2>; // Please note, this value cannot used directly for packet serialization. enum QuicLongHeaderType : uint8_t { @@ -826,6 +833,16 @@ QUIC_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os, QUIC_EXPORT_PRIVATE std::string KeyUpdateReasonString(KeyUpdateReason reason); +// QuicSSLConfig contains configurations to be applied on a SSL object, which +// overrides the configurations in SSL_CTX. +struct QUIC_NO_EXPORT QuicSSLConfig { + // Whether TLS early data should be enabled. If not set, default to enabled. + absl::optional<bool> early_data_enabled; + // If set, used to configure the SSL object with + // SSL_set_signing_algorithm_prefs. + absl::optional<absl::InlinedVector<uint16_t, 8>> signing_algorithm_prefs; +}; + } // namespace quic #endif // QUICHE_QUIC_CORE_QUIC_TYPES_H_ diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_unacked_packet_map.cc b/chromium/net/third_party/quiche/src/quic/core/quic_unacked_packet_map.cc index fbdcd3eeb8a..9f9919c1be9 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_unacked_packet_map.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_unacked_packet_map.cc @@ -8,6 +8,7 @@ #include <limits> #include <type_traits> +#include "absl/container/inlined_vector.h" #include "quic/core/quic_connection_stats.h" #include "quic/core/quic_packet_number.h" #include "quic/core/quic_types.h" @@ -174,7 +175,7 @@ void QuicUnackedPacketMap::AddSentPacket(SerializedPacket* mutable_packet, last_inflight_packet_sent_time_ = sent_time; last_inflight_packets_sent_time_[packet_number_space] = sent_time; } - unacked_packets_.push_back(info); + unacked_packets_.push_back(std::move(info)); // Swap the retransmittable frames to avoid allocations. // TODO(ianswett): Could use emplace_back when Chromium can. if (has_crypto_handshake) { @@ -325,9 +326,9 @@ void QuicUnackedPacketMap::RemoveFromInFlight(QuicPacketNumber packet_number) { RemoveFromInFlight(info); } -QuicInlinedVector<QuicPacketNumber, 2> +absl::InlinedVector<QuicPacketNumber, 2> QuicUnackedPacketMap::NeuterUnencryptedPackets() { - QuicInlinedVector<QuicPacketNumber, 2> neutered_packets; + absl::InlinedVector<QuicPacketNumber, 2> neutered_packets; QuicPacketNumber packet_number = GetLeastUnacked(); for (QuicUnackedPacketMap::iterator it = begin(); it != end(); ++it, ++packet_number) { @@ -353,9 +354,9 @@ QuicUnackedPacketMap::NeuterUnencryptedPackets() { return neutered_packets; } -QuicInlinedVector<QuicPacketNumber, 2> +absl::InlinedVector<QuicPacketNumber, 2> QuicUnackedPacketMap::NeuterHandshakePackets() { - QuicInlinedVector<QuicPacketNumber, 2> neutered_packets; + absl::InlinedVector<QuicPacketNumber, 2> neutered_packets; QuicPacketNumber packet_number = GetLeastUnacked(); for (QuicUnackedPacketMap::iterator it = begin(); it != end(); ++it, ++packet_number) { diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_unacked_packet_map.h b/chromium/net/third_party/quiche/src/quic/core/quic_unacked_packet_map.h index 676f054b1fb..b8f8ffe9c87 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_unacked_packet_map.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_unacked_packet_map.h @@ -8,6 +8,7 @@ #include <cstddef> #include <cstdint> +#include "absl/container/inlined_vector.h" #include "absl/strings/str_cat.h" #include "quic/core/quic_packets.h" #include "quic/core/quic_transmission_info.h" @@ -69,12 +70,12 @@ class QUIC_EXPORT_PRIVATE QuicUnackedPacketMap { // Called to neuter all unencrypted packets to ensure they do not get // retransmitted. Returns a vector of neutered packet numbers. - QuicInlinedVector<QuicPacketNumber, 2> NeuterUnencryptedPackets(); + absl::InlinedVector<QuicPacketNumber, 2> NeuterUnencryptedPackets(); // Called to neuter packets in handshake packet number space to ensure they do // not get retransmitted. Returns a vector of neutered packet numbers. // TODO(fayang): Consider to combine this with NeuterUnencryptedPackets. - QuicInlinedVector<QuicPacketNumber, 2> NeuterHandshakePackets(); + absl::InlinedVector<QuicPacketNumber, 2> NeuterHandshakePackets(); // Returns true if |packet_number| has retransmittable frames. This will // return false if all frames of this packet are either non-retransmittable or diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_utils.cc b/chromium/net/third_party/quiche/src/quic/core/quic_utils.cc index 1f0137a91bc..98fbf55bdaa 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_utils.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_utils.cc @@ -21,6 +21,7 @@ #include "quic/platform/api/quic_bug_tracker.h" #include "quic/platform/api/quic_flag_utils.h" #include "quic/platform/api/quic_flags.h" +#include "quic/platform/api/quic_mem_slice.h" #include "common/platform/api/quiche_logging.h" #include "common/platform/api/quiche_prefetch.h" #include "common/quiche_endian.h" @@ -693,6 +694,32 @@ bool QuicUtils::IsProbingFrame(QuicFrameType type) { } } +// static +bool QuicUtils::IsAckElicitingFrame(QuicFrameType type) { + switch (type) { + case PADDING_FRAME: + case STOP_WAITING_FRAME: + case ACK_FRAME: + case CONNECTION_CLOSE_FRAME: + return false; + default: + return true; + } +} + +// static +bool QuicUtils::AreStatelessResetTokensEqual( + const StatelessResetToken& token1, + const StatelessResetToken& token2) { + char byte = 0; + for (size_t i = 0; i < kStatelessResetTokenLength; i++) { + // This avoids compiler optimizations that could make us stop comparing + // after we find a byte that doesn't match. + byte |= (token1[i] ^ token2[i]); + } + return byte == 0; +} + bool IsValidWebTransportSessionId(WebTransportSessionId id, ParsedQuicVersion version) { QUICHE_DCHECK(version.UsesHttp3()); @@ -701,5 +728,13 @@ bool IsValidWebTransportSessionId(WebTransportSessionId id, QuicUtils::IsClientInitiatedStreamId(version.transport_version, id); } +QuicByteCount MemSliceSpanTotalSize(absl::Span<QuicMemSlice> span) { + QuicByteCount total = 0; + for (const QuicMemSlice& slice : span) { + total += slice.length(); + } + return total; +} + #undef RETURN_STRING_LITERAL // undef for jumbo builds } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_utils.h b/chromium/net/third_party/quiche/src/quic/core/quic_utils.h index 0b089b4bad6..c335bc0e1c1 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_utils.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_utils.h @@ -13,6 +13,7 @@ #include "absl/numeric/int128.h" #include "absl/strings/string_view.h" +#include "absl/types/span.h" #include "quic/core/crypto/quic_random.h" #include "quic/core/frames/quic_frame.h" #include "quic/core/quic_connection_id.h" @@ -21,6 +22,7 @@ #include "quic/core/quic_versions.h" #include "quic/platform/api/quic_export.h" #include "quic/platform/api/quic_iovec.h" +#include "quic/platform/api/quic_mem_slice.h" #include "quic/platform/api/quic_socket_address.h" namespace quic { @@ -237,6 +239,14 @@ class QUIC_EXPORT_PRIVATE QuicUtils { // Return true if this frame is an IETF probing frame. static bool IsProbingFrame(QuicFrameType type); + + // Return true if the two stateless reset tokens are equal. Performs the + // comparison in constant time. + static bool AreStatelessResetTokensEqual(const StatelessResetToken& token1, + const StatelessResetToken& token2); + + // Return ture if this frame is an ack-eliciting frame. + static bool IsAckElicitingFrame(QuicFrameType type); }; // Returns true if the specific ID is a valid WebTransport session ID that our @@ -244,6 +254,8 @@ class QUIC_EXPORT_PRIVATE QuicUtils { bool IsValidWebTransportSessionId(WebTransportSessionId id, ParsedQuicVersion transport_version); +QuicByteCount MemSliceSpanTotalSize(absl::Span<QuicMemSlice> span); + template <typename Mask> class QUIC_EXPORT_PRIVATE BitMask { public: diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_utils_test.cc b/chromium/net/third_party/quiche/src/quic/core/quic_utils_test.cc index dc1165242f5..a02633046e5 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_utils_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_utils_test.cc @@ -320,6 +320,8 @@ TEST_F(QuicUtilsTest, StatelessResetToken) { QuicUtils::GenerateStatelessResetToken(connection_id2); EXPECT_EQ(token1a, token1b); EXPECT_NE(token1a, token2); + EXPECT_TRUE(QuicUtils::AreStatelessResetTokensEqual(token1a, token1b)); + EXPECT_FALSE(QuicUtils::AreStatelessResetTokensEqual(token1a, token2)); } enum class TestEnumClassBit : uint8_t { diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_version_manager.cc b/chromium/net/third_party/quiche/src/quic/core/quic_version_manager.cc index 1d4dcca70e4..911370f2618 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_version_manager.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_version_manager.cc @@ -15,7 +15,7 @@ namespace quic { QuicVersionManager::QuicVersionManager( ParsedQuicVersionVector supported_versions) - : enable_version_rfcv1_(GetQuicReloadableFlag(quic_enable_version_rfcv1)), + : disable_version_rfcv1_(GetQuicReloadableFlag(quic_disable_version_rfcv1)), disable_version_draft_29_( GetQuicReloadableFlag(quic_disable_version_draft_29)), disable_version_t051_(GetQuicReloadableFlag(quic_disable_version_t051)), @@ -49,8 +49,8 @@ const std::vector<std::string>& QuicVersionManager::GetSupportedAlpns() { void QuicVersionManager::MaybeRefilterSupportedVersions() { static_assert(SupportedVersions().size() == 6u, "Supported versions out of sync"); - if (enable_version_rfcv1_ != - GetQuicReloadableFlag(quic_enable_version_rfcv1) || + if (disable_version_rfcv1_ != + GetQuicReloadableFlag(quic_disable_version_rfcv1) || disable_version_draft_29_ != GetQuicReloadableFlag(quic_disable_version_draft_29) || disable_version_t051_ != @@ -61,7 +61,7 @@ void QuicVersionManager::MaybeRefilterSupportedVersions() { GetQuicReloadableFlag(quic_disable_version_q046) || disable_version_q043_ != GetQuicReloadableFlag(quic_disable_version_q043)) { - enable_version_rfcv1_ = GetQuicReloadableFlag(quic_enable_version_rfcv1); + disable_version_rfcv1_ = GetQuicReloadableFlag(quic_disable_version_rfcv1); disable_version_draft_29_ = GetQuicReloadableFlag(quic_disable_version_draft_29); disable_version_t051_ = GetQuicReloadableFlag(quic_disable_version_t051); diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_version_manager.h b/chromium/net/third_party/quiche/src/quic/core/quic_version_manager.h index f443395e295..da73f37b910 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_version_manager.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_version_manager.h @@ -50,8 +50,8 @@ class QUIC_EXPORT_PRIVATE QuicVersionManager { private: // Cached value of reloadable flags. - // quic_enable_version_rfcv1 flag - bool enable_version_rfcv1_; + // quic_disable_version_rfcv1 flag + bool disable_version_rfcv1_; // quic_disable_version_draft_29 flag bool disable_version_draft_29_; // quic_disable_version_t051 flag diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_versions.cc b/chromium/net/third_party/quiche/src/quic/core/quic_versions.cc index ba2ec7ee29e..717932f5a6c 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_versions.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_versions.cc @@ -47,7 +47,7 @@ void SetVersionFlag(const ParsedQuicVersion& version, bool should_enable) { const bool enable = should_enable; const bool disable = !should_enable; if (version == ParsedQuicVersion::RFCv1()) { - SetQuicReloadableFlag(quic_enable_version_rfcv1, enable); + SetQuicReloadableFlag(quic_disable_version_rfcv1, disable); } else if (version == ParsedQuicVersion::Draft29()) { SetQuicReloadableFlag(quic_disable_version_draft_29, disable); } else if (version == ParsedQuicVersion::T051()) { @@ -297,6 +297,18 @@ ParsedQuicVersionVector CurrentSupportedVersionsWithTls() { return versions; } +ParsedQuicVersionVector CurrentSupportedHttp3Versions() { + ParsedQuicVersionVector versions; + for (const ParsedQuicVersion& version : CurrentSupportedVersions()) { + if (version.UsesHttp3()) { + versions.push_back(version); + } + } + QUIC_BUG_IF(no_version_uses_http3, versions.empty()) + << "No version speaking Http3 found."; + return versions; +} + ParsedQuicVersion ParseQuicVersionLabel(QuicVersionLabel version_label) { for (const ParsedQuicVersion& version : AllSupportedVersions()) { if (version_label == CreateQuicVersionLabel(version)) { @@ -395,7 +407,7 @@ ParsedQuicVersionVector FilterSupportedVersions( filtered_versions.reserve(versions.size()); for (const ParsedQuicVersion& version : versions) { if (version == ParsedQuicVersion::RFCv1()) { - if (GetQuicReloadableFlag(quic_enable_version_rfcv1)) { + if (!GetQuicReloadableFlag(quic_disable_version_rfcv1)) { filtered_versions.push_back(version); } } else if (version == ParsedQuicVersion::Draft29()) { diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_versions.h b/chromium/net/third_party/quiche/src/quic/core/quic_versions.h index 75a6826ae81..52e829c2275 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_versions.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_versions.h @@ -446,6 +446,10 @@ QUIC_EXPORT_PRIVATE ParsedQuicVersionVector AllSupportedVersionsWithTls(); // PROTOCOL_TLS1_3. QUIC_EXPORT_PRIVATE ParsedQuicVersionVector CurrentSupportedVersionsWithTls(); +// Returns a subset of CurrentSupportedVersions() using HTTP/3 at the HTTP +// layer. +QUIC_EXPORT_PRIVATE ParsedQuicVersionVector CurrentSupportedHttp3Versions(); + // Returns QUIC version of |index| in result of |versions|. Returns // UnsupportedQuicVersion() if |index| is out of bounds. QUIC_EXPORT_PRIVATE ParsedQuicVersionVector diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_versions_test.cc b/chromium/net/third_party/quiche/src/quic/core/quic_versions_test.cc index c9bc8b311ed..31e365e10c1 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_versions_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_versions_test.cc @@ -445,6 +445,25 @@ TEST_F(QuicVersionsTest, SupportedVersionsAllDistinct) { } } +TEST_F(QuicVersionsTest, CurrentSupportedHttp3Versions) { + ParsedQuicVersionVector h3_versions = CurrentSupportedHttp3Versions(); + ParsedQuicVersionVector all_current_supported_versions = + CurrentSupportedVersions(); + for (auto& version : all_current_supported_versions) { + bool version_is_h3 = false; + for (auto& h3_version : h3_versions) { + if (version == h3_version) { + EXPECT_TRUE(version.UsesHttp3()); + version_is_h3 = true; + break; + } + } + if (!version_is_h3) { + EXPECT_FALSE(version.UsesHttp3()); + } + } +} + } // namespace } // namespace test } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_write_blocked_list.h b/chromium/net/third_party/quiche/src/quic/core/quic_write_blocked_list.h index dbc2ca9173d..54441e778f1 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_write_blocked_list.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_write_blocked_list.h @@ -9,13 +9,13 @@ #include <cstdint> #include <utility> +#include "absl/container/inlined_vector.h" #include "http2/core/priority_write_scheduler.h" #include "quic/core/quic_packets.h" #include "quic/platform/api/quic_bug_tracker.h" #include "quic/platform/api/quic_containers.h" #include "quic/platform/api/quic_export.h" #include "quic/platform/api/quic_flags.h" -#include "quic/platform/api/quic_map_util.h" namespace quic { @@ -102,7 +102,7 @@ class QUIC_EXPORT_PRIVATE QuicWriteBlockedList { }; // Optimized for the typical case of 2 static streams per session. - using StreamsVector = QuicInlinedVector<StreamIdBlockedPair, 2>; + using StreamsVector = absl::InlinedVector<StreamIdBlockedPair, 2>; StreamsVector::const_iterator begin() const { return streams_.cbegin(); } diff --git a/chromium/net/third_party/quiche/src/quic/core/stream_delegate_interface.h b/chromium/net/third_party/quiche/src/quic/core/stream_delegate_interface.h index 38b8e5326e7..02b195ef0c4 100644 --- a/chromium/net/third_party/quiche/src/quic/core/stream_delegate_interface.h +++ b/chromium/net/third_party/quiche/src/quic/core/stream_delegate_interface.h @@ -28,18 +28,13 @@ class QUIC_EXPORT_PRIVATE StreamDelegateInterface { virtual void OnStreamError(QuicErrorCode error_code, QuicIetfTransportErrorCodes ietf_error, std::string error_details) = 0; - // Called when the stream needs to write data. If |level| is present, the data - // will be written at the specified |level|. The data will be written - // at specified transmission |type|. - // TODO(fayang): Change absl::optional<EncryptionLevel> to EncryptionLevel - // when deprecating quic_use_write_or_buffer_data_at_level. - virtual QuicConsumedData WritevData( - QuicStreamId id, - size_t write_length, - QuicStreamOffset offset, - StreamSendingState state, - TransmissionType type, - absl::optional<EncryptionLevel> level) = 0; + // Called when the stream needs to write data at specified |level| and + // transmission |type|. + virtual QuicConsumedData WritevData(QuicStreamId id, size_t write_length, + QuicStreamOffset offset, + StreamSendingState state, + TransmissionType type, + EncryptionLevel level) = 0; // Called to write crypto data. virtual size_t SendCryptoData(EncryptionLevel level, size_t write_length, diff --git a/chromium/net/third_party/quiche/src/quic/core/tls_client_handshaker.cc b/chromium/net/third_party/quiche/src/quic/core/tls_client_handshaker.cc index 1b9de030860..737f449c59f 100644 --- a/chromium/net/third_party/quiche/src/quic/core/tls_client_handshaker.cc +++ b/chromium/net/third_party/quiche/src/quic/core/tls_client_handshaker.cc @@ -41,12 +41,16 @@ TlsClientHandshaker::TlsClientHandshaker( crypto_negotiated_params_(new QuicCryptoNegotiatedParameters), has_application_state_(has_application_state), crypto_config_(crypto_config), - tls_connection_(crypto_config->ssl_ctx(), this) { + tls_connection_(crypto_config->ssl_ctx(), this, session->GetSSLConfig()) { std::string token = crypto_config->LookupOrCreate(server_id)->source_address_token(); if (!token.empty()) { session->SetSourceAddressTokenToSend(token); } + if (crypto_config->tls_signature_algorithms().has_value()) { + SSL_set1_sigalgs_list(ssl(), + crypto_config->tls_signature_algorithms()->c_str()); + } } TlsClientHandshaker::~TlsClientHandshaker() {} @@ -68,6 +72,16 @@ bool TlsClientHandshaker::CryptoConnect() { } SSL_set_quic_use_legacy_codepoint(ssl(), use_legacy_extension); + // TODO(b/193650832) Add SetFromConfig to QUIC handshakers and remove reliance + // on session pointer. + if (session()->permutes_tls_extensions()) { + // Ask BoringSSL to randomize the order of TLS extensions. +#if BORINGSSL_API_VERSION >= 16 + QUIC_DLOG(INFO) << "Enabling TLS extension permutation"; + SSL_set_permute_extensions(ssl(), true); +#endif // BORINGSSL_API_VERSION + } + // Set the SNI to send, if any. SSL_set_connect_state(ssl()); if (QUIC_DLOG_INFO_IS_ON() && @@ -444,22 +458,8 @@ void TlsClientHandshaker::OnProofVerifyDetailsAvailable( void TlsClientHandshaker::FinishHandshake() { FillNegotiatedParams(); - if (retry_handshake_on_early_data_) { - QUICHE_CHECK(!SSL_in_early_data(ssl())); - } else { - if (SSL_in_early_data(ssl())) { - // SSL_do_handshake returns after sending the ClientHello if the session - // is 0-RTT-capable, which means that FinishHandshake will get called - // twice - the first time after sending the ClientHello, and the second - // time after the handshake is complete. If we're in the first time - // FinishHandshake is called, we can't do any end-of-handshake processing. - - // If we're attempting a 0-RTT handshake, then we need to let the - // transport and application know what state to apply to early data. - PrepareZeroRttConfig(cached_state_.get()); - return; - } - } + QUICHE_CHECK(!SSL_in_early_data(ssl())); + QUIC_LOG(INFO) << "Client: handshake finished"; std::string error_details; @@ -518,7 +518,6 @@ void TlsClientHandshaker::FinishHandshake() { } void TlsClientHandshaker::OnEnterEarlyData() { - QUICHE_DCHECK(retry_handshake_on_early_data_); QUICHE_DCHECK(SSL_in_early_data(ssl())); // TODO(wub): It might be unnecessary to FillNegotiatedParams() at this time, diff --git a/chromium/net/third_party/quiche/src/quic/core/tls_client_handshaker_test.cc b/chromium/net/third_party/quiche/src/quic/core/tls_client_handshaker_test.cc index 8365f633f0f..4bbb4ed6d0f 100644 --- a/chromium/net/third_party/quiche/src/quic/core/tls_client_handshaker_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/tls_client_handshaker_test.cc @@ -458,13 +458,6 @@ TEST_P(TlsClientHandshakerTest, ZeroRttResumptionWithAyncProofVerifier) { proof_verifier->InvokePendingCallback(0); QuicFramer* framer = QuicConnectionPeer::GetFramer(connection_); - if (!GetQuicReloadableFlag(quic_tls_retry_handshake_on_early_data)) { - // Client does not have HANDSHAKE key due to b/186438140. - EXPECT_EQ(nullptr, - QuicFramerPeer::GetEncrypter(framer, ENCRYPTION_HANDSHAKE)); - return; - } - // Verify client has derived HANDSHAKE key. EXPECT_NE(nullptr, QuicFramerPeer::GetEncrypter(framer, ENCRYPTION_HANDSHAKE)); diff --git a/chromium/net/third_party/quiche/src/quic/core/tls_handshaker.cc b/chromium/net/third_party/quiche/src/quic/core/tls_handshaker.cc index 9ba5f3c12f6..4b2d717befc 100644 --- a/chromium/net/third_party/quiche/src/quic/core/tls_handshaker.cc +++ b/chromium/net/third_party/quiche/src/quic/core/tls_handshaker.cc @@ -96,12 +96,10 @@ void TlsHandshaker::AdvanceHandshake() { return; } - QUICHE_BUG_IF(quic_tls_server_async_done_no_flusher, - SSL_is_server(ssl()) && add_packet_flusher_on_async_op_done_ && - !handshaker_delegate_->PacketFlusherAttached()) - << "is_server:" << SSL_is_server(ssl()) - << ", add_packet_flusher_on_async_op_done_:" - << add_packet_flusher_on_async_op_done_; + QUICHE_BUG_IF( + quic_tls_server_async_done_no_flusher, + SSL_is_server(ssl()) && !handshaker_delegate_->PacketFlusherAttached()) + << "is_server:" << SSL_is_server(ssl()); QUIC_VLOG(1) << ENDPOINT << "Continuing handshake"; int rv = SSL_do_handshake(ssl()); @@ -111,8 +109,7 @@ void TlsHandshaker::AdvanceHandshake() { // processed. Retry SSL_do_handshake once will advance the handshake more in // that case. If there are no unprocessed ServerHello, the retry will return a // non-positive number. - if (retry_handshake_on_early_data_ && rv == 1 && SSL_in_early_data(ssl())) { - QUIC_RELOADABLE_FLAG_COUNT_N(quic_tls_retry_handshake_on_early_data, 1, 2); + if (rv == 1 && SSL_in_early_data(ssl())) { OnEnterEarlyData(); rv = SSL_do_handshake(ssl()); QUIC_VLOG(1) << ENDPOINT diff --git a/chromium/net/third_party/quiche/src/quic/core/tls_handshaker.h b/chromium/net/third_party/quiche/src/quic/core/tls_handshaker.h index 0b5ddc36769..72ae0993983 100644 --- a/chromium/net/third_party/quiche/src/quic/core/tls_handshaker.h +++ b/chromium/net/third_party/quiche/src/quic/core/tls_handshaker.h @@ -57,7 +57,7 @@ class QUIC_EXPORT_PRIVATE TlsHandshaker : public TlsConnection::Delegate, protected: // Called when a new message is received on the crypto stream and is available // for the TLS stack to read. - void AdvanceHandshake(); + virtual void AdvanceHandshake(); void CloseConnection(QuicErrorCode error, const std::string& reason_phrase); // Closes the connection, specifying the wire error code |ietf_error| @@ -71,12 +71,8 @@ class QUIC_EXPORT_PRIVATE TlsHandshaker : public TlsConnection::Delegate, bool is_connection_closed() const { return is_connection_closed_; } // Called when |SSL_do_handshake| returns 1, indicating that the handshake has - // finished. Note that due to 0-RTT, the handshake may "finish" twice; - // |SSL_in_early_data| can be used to determine whether the handshake is truly - // done. - // TODO(wub): When --quic_tls_retry_handshake_on_early_data is true, this - // function will only be called once when the handshake actually finishes. - // Update comment when deprecating the flag. + // finished. Note that a handshake only finishes once, entering early data + // does not count. virtual void FinishHandshake() = 0; // Called when |SSL_do_handshake| returns 1 and the connection is in early @@ -84,7 +80,6 @@ class QUIC_EXPORT_PRIVATE TlsHandshaker : public TlsConnection::Delegate, // retry |SSL_do_handshake| once. virtual void OnEnterEarlyData() { // By default, do nothing but check the preconditions. - QUICHE_DCHECK(retry_handshake_on_early_data_); QUICHE_DCHECK(SSL_in_early_data(ssl())); } @@ -168,11 +163,10 @@ class QUIC_EXPORT_PRIVATE TlsHandshaker : public TlsConnection::Delegate, // error code corresponding to the TLS alert description |desc|. void SendAlert(EncryptionLevel level, uint8_t desc) override; - const bool add_packet_flusher_on_async_op_done_ = - GetQuicReloadableFlag(quic_add_packet_flusher_on_async_op_done); - - const bool retry_handshake_on_early_data_ = - GetQuicReloadableFlag(quic_tls_retry_handshake_on_early_data); + // Informational callback from BoringSSL. Subclasses can override it to do + // logging, tracing, etc. + // See |SSL_CTX_set_info_callback| for the meaning of |type| and |value|. + void InfoCallback(int /*type*/, int /*value*/) override {} private: // ProofVerifierCallbackImpl handles the result of an asynchronous certificate diff --git a/chromium/net/third_party/quiche/src/quic/core/tls_server_handshaker.cc b/chromium/net/third_party/quiche/src/quic/core/tls_server_handshaker.cc index efd4d275013..b2817b15b22 100644 --- a/chromium/net/third_party/quiche/src/quic/core/tls_server_handshaker.cc +++ b/chromium/net/third_party/quiche/src/quic/core/tls_server_handshaker.cc @@ -34,17 +34,24 @@ namespace quic { +namespace { + +// Default port for HTTP/3. +uint16_t kDefaultPort = 443; + +} // namespace + TlsServerHandshaker::DefaultProofSourceHandle::DefaultProofSourceHandle( TlsServerHandshaker* handshaker, ProofSource* proof_source) : handshaker_(handshaker), proof_source_(proof_source) {} TlsServerHandshaker::DefaultProofSourceHandle::~DefaultProofSourceHandle() { - CancelPendingOperation(); + CloseHandle(); } -void TlsServerHandshaker::DefaultProofSourceHandle::CancelPendingOperation() { - QUIC_DVLOG(1) << "CancelPendingOperation. is_signature_pending=" +void TlsServerHandshaker::DefaultProofSourceHandle::CloseHandle() { + QUIC_DVLOG(1) << "CloseHandle. is_signature_pending=" << (signature_callback_ != nullptr); if (signature_callback_) { signature_callback_->Cancel(); @@ -62,7 +69,8 @@ TlsServerHandshaker::DefaultProofSourceHandle::SelectCertificate( const std::string& /*alpn*/, absl::optional<std::string> /*alps*/, const std::vector<uint8_t>& /*quic_transport_params*/, - const absl::optional<std::vector<uint8_t>>& /*early_data_context*/) { + const absl::optional<std::vector<uint8_t>>& /*early_data_context*/, + const QuicSSLConfig& /*ssl_config*/) { if (!handshaker_ || !proof_source_) { QUIC_BUG(quic_bug_10341_1) << "SelectCertificate called on a detached handle"; @@ -74,7 +82,8 @@ TlsServerHandshaker::DefaultProofSourceHandle::SelectCertificate( handshaker_->OnSelectCertificateDone( /*ok=*/true, /*is_sync=*/true, chain.get(), - /*handshake_hints=*/absl::string_view()); + /*handshake_hints=*/absl::string_view(), + /*ticket_encryption_key=*/absl::string_view()); if (!handshaker_->select_cert_status().has_value()) { QUIC_BUG(quic_bug_12423_1) << "select_cert_status() has no value after a synchronous select cert"; @@ -164,7 +173,7 @@ TlsServerHandshaker::TlsServerHandshaker( proof_source_(crypto_config->proof_source()), pre_shared_key_(crypto_config->pre_shared_key()), crypto_negotiated_params_(new QuicCryptoNegotiatedParameters), - tls_connection_(crypto_config->ssl_ctx(), this), + tls_connection_(crypto_config->ssl_ctx(), this, session->GetSSLConfig()), crypto_config_(crypto_config) { QUICHE_DCHECK_EQ(PROTOCOL_TLS1_3, session->connection()->version().handshake_protocol); @@ -182,15 +191,19 @@ TlsServerHandshaker::TlsServerHandshaker( if (GetQuicFlag(FLAGS_quic_disable_server_tls_resumption)) { SSL_set_options(ssl(), SSL_OP_NO_TICKET); } -} -TlsServerHandshaker::~TlsServerHandshaker() { - CancelOutstandingCallbacks(); + if (GetQuicReloadableFlag(quic_trace_ssl_events) && + session->connection()->context()->tracer) { + QUIC_RELOADABLE_FLAG_COUNT(quic_trace_ssl_events); + tls_connection_.EnableInfoCallback(); + } } +TlsServerHandshaker::~TlsServerHandshaker() { CancelOutstandingCallbacks(); } + void TlsServerHandshaker::CancelOutstandingCallbacks() { if (proof_source_handle_) { - proof_source_handle_->CancelPendingOperation(); + proof_source_handle_->CloseHandle(); } if (ticket_decryption_callback_) { ticket_decryption_callback_->Cancel(); @@ -198,6 +211,40 @@ void TlsServerHandshaker::CancelOutstandingCallbacks() { } } +void TlsServerHandshaker::InfoCallback(int type, int value) { + QuicConnectionTracer* tracer = + session()->connection()->context()->tracer.get(); + + if (tracer == nullptr) { + return; + } + + if (type & SSL_CB_LOOP) { + tracer->PrintString( + absl::StrCat("SSL:ACCEPT_LOOP:", SSL_state_string_long(ssl()))); + } else if (type & SSL_CB_ALERT) { + const char* prefix = + (type & SSL_CB_READ) ? "SSL:READ_ALERT:" : "SSL:WRITE_ALERT:"; + tracer->PrintString(absl::StrCat(prefix, SSL_alert_type_string_long(value), + ":", SSL_alert_desc_string_long(value))); + } else if (type & SSL_CB_EXIT) { + const char* prefix = + (value == 1) ? "SSL:ACCEPT_EXIT_OK:" : "SSL:ACCEPT_EXIT_FAIL:"; + tracer->PrintString(absl::StrCat(prefix, SSL_state_string_long(ssl()))); + } else if (type & SSL_CB_HANDSHAKE_START) { + tracer->PrintString( + absl::StrCat("SSL:HANDSHAKE_START:", SSL_state_string_long(ssl()))); + } else if (type & SSL_CB_HANDSHAKE_DONE) { + tracer->PrintString( + absl::StrCat("SSL:HANDSHAKE_DONE:", SSL_state_string_long(ssl()))); + } else { + QUIC_DLOG(INFO) << "Unknown event type " << type << ": " + << SSL_state_string_long(ssl()); + tracer->PrintString( + absl::StrCat("SSL:unknown:", value, ":", SSL_state_string_long(ssl()))); + } +} + std::unique_ptr<ProofSourceHandle> TlsServerHandshaker::MaybeCreateProofSourceHandle() { return std::make_unique<DefaultProofSourceHandle>(this, proof_source_); @@ -351,18 +398,7 @@ TlsServerHandshaker::CreateCurrentOneRttEncrypter() { void TlsServerHandshaker::OverrideQuicConfigDefaults(QuicConfig* /*config*/) {} void TlsServerHandshaker::AdvanceHandshakeFromCallback() { - std::unique_ptr<QuicConnection::ScopedPacketFlusher> flusher; - if (add_packet_flusher_on_async_op_done_) { - if (session()->PacketFlusherAttached()) { - QUIC_RELOADABLE_FLAG_COUNT_N(quic_add_packet_flusher_on_async_op_done, 1, - 2); - } else { - QUIC_RELOADABLE_FLAG_COUNT_N(quic_add_packet_flusher_on_async_op_done, 2, - 2); - } - flusher = std::make_unique<QuicConnection::ScopedPacketFlusher>( - session()->connection()); - } + QuicConnection::ScopedPacketFlusher flusher(session()->connection()); AdvanceHandshake(); if (!is_connection_closed()) { @@ -514,27 +550,14 @@ void TlsServerHandshaker::SetWriteSecret( TlsHandshaker::SetWriteSecret(level, cipher, write_secret); } -std::string TlsServerHandshaker::GetAcceptChValueForOrigin( - const std::string& /*origin*/) const { +std::string TlsServerHandshaker::GetAcceptChValueForHostname( + const std::string& /*hostname*/) const { return {}; } void TlsServerHandshaker::FinishHandshake() { - if (retry_handshake_on_early_data_) { - QUIC_RELOADABLE_FLAG_COUNT_N(quic_tls_retry_handshake_on_early_data, 2, 2); - QUICHE_DCHECK(!SSL_in_early_data(ssl())); - } else { - if (SSL_in_early_data(ssl())) { - // If the server accepts early data, SSL_do_handshake returns success - // twice: once after processing the ClientHello and sending the server's - // first flight, and then again after the handshake is complete. This - // results in FinishHandshake getting called twice. On the first call to - // FinishHandshake, we don't have any confirmation that the client is - // live, so all end of handshake processing is deferred until the - // handshake is actually complete. - return; - } - } + QUICHE_DCHECK(!SSL_in_early_data(ssl())); + if (!valid_alpn_received_) { QUIC_DLOG(ERROR) << "Server: handshake finished without receiving a known ALPN"; @@ -668,7 +691,8 @@ int TlsServerHandshaker::SessionTicketSeal(uint8_t* out, size_t max_out_len, absl::string_view in) { QUICHE_DCHECK(proof_source_->GetTicketCrypter()); - std::vector<uint8_t> ticket = proof_source_->GetTicketCrypter()->Encrypt(in); + std::vector<uint8_t> ticket = + proof_source_->GetTicketCrypter()->Encrypt(in, ticket_encryption_key_); if (max_out_len < ticket.size()) { QUIC_BUG(quic_bug_12423_2) << "TicketCrypter returned " << ticket.size() @@ -689,8 +713,18 @@ ssl_ticket_aead_result_t TlsServerHandshaker::SessionTicketOpen( absl::string_view in) { QUICHE_DCHECK(proof_source_->GetTicketCrypter()); + if (allow_ignore_ticket_open_ && ignore_ticket_open_) { + // SetIgnoreTicketOpen has been called. Typically this means the caller is + // using handshake hints and expect the hints to contain ticket decryption + // results. + QUIC_CODE_COUNT(quic_tls_server_handshaker_tickets_ignored_1); + return ssl_ticket_aead_ignore_ticket; + } + if (!ticket_decryption_callback_) { - ticket_received_ = true; + if (!allow_ignore_ticket_open_) { + ticket_received_ = true; + } ticket_decryption_callback_ = new DecryptCallback(this); proof_source_->GetTicketCrypter()->Decrypt( in, std::unique_ptr<DecryptCallback>(ticket_decryption_callback_)); @@ -742,7 +776,7 @@ ssl_ticket_aead_result_t TlsServerHandshaker::FinalizeSessionTicketOpen( if (decrypted_session_ticket_.empty()) { QUIC_DLOG(ERROR) << "Session ticket decryption failed; ignoring ticket"; // Ticket decryption failed. Ignore the ticket. - QUIC_CODE_COUNT(quic_tls_server_handshaker_tickets_ignored); + QUIC_CODE_COUNT(quic_tls_server_handshaker_tickets_ignored_2); return ssl_ticket_aead_ignore_ticket; } if (max_out_len < decrypted_session_ticket_.size()) { @@ -783,6 +817,15 @@ ssl_select_cert_result_t TlsServerHandshaker::EarlySelectCertCallback( return ssl_select_cert_error; } + if (allow_ignore_ticket_open_) { + QUIC_RELOADABLE_FLAG_COUNT(quic_tls_allow_ignore_ticket_open); + const uint8_t* unused_extension_bytes; + size_t unused_extension_len; + ticket_received_ = SSL_early_callback_ctx_extension_get( + client_hello, TLSEXT_TYPE_pre_shared_key, &unused_extension_bytes, + &unused_extension_len); + } + // This callback is called very early by Boring SSL, most of the SSL_get_foo // function do not work at this point, but SSL_get_servername does. const char* hostname = SSL_get_servername(ssl(), TLSEXT_NAMETYPE_host_name); @@ -824,37 +867,36 @@ ssl_select_cert_result_t TlsServerHandshaker::EarlySelectCertCallback( absl::string_view ssl_capabilities_view; absl::optional<std::string> alps; - if (use_handshake_hints_) { - QUIC_RELOADABLE_FLAG_COUNT(quic_tls_server_use_handshake_hints); - if (CryptoUtils::GetSSLCapabilities(ssl(), &ssl_capabilities, - &ssl_capabilities_len)) { - ssl_capabilities_view = absl::string_view( - reinterpret_cast<const char*>(ssl_capabilities.get()), - ssl_capabilities_len); - } - // Enable ALPS for the session's ALPN. - SetApplicationSettingsResult alps_result = - SetApplicationSettings(AlpnForVersion(session()->version())); - if (!alps_result.success) { - return ssl_select_cert_error; - } - alps = alps_result.alps_length > 0 - ? std::string(alps_result.alps_buffer.get(), - alps_result.alps_length) - : std::string(); + if (CryptoUtils::GetSSLCapabilities(ssl(), &ssl_capabilities, + &ssl_capabilities_len)) { + ssl_capabilities_view = + absl::string_view(reinterpret_cast<const char*>(ssl_capabilities.get()), + ssl_capabilities_len); + } + + // Enable ALPS for the session's ALPN. + SetApplicationSettingsResult alps_result = + SetApplicationSettings(AlpnForVersion(session()->version())); + if (!alps_result.success) { + return ssl_select_cert_error; } + alps = + alps_result.alps_length > 0 + ? std::string(alps_result.alps_buffer.get(), alps_result.alps_length) + : std::string(); const QuicAsyncStatus status = proof_source_handle_->SelectCertificate( - session()->connection()->self_address(), - session()->connection()->peer_address(), ssl_capabilities_view, - crypto_negotiated_params_->sni, + session()->connection()->self_address().Normalized(), + session()->connection()->peer_address().Normalized(), + ssl_capabilities_view, crypto_negotiated_params_->sni, absl::string_view( reinterpret_cast<const char*>(client_hello->client_hello), client_hello->client_hello_len), AlpnForVersion(session()->version()), std::move(alps), set_transport_params_result.quic_transport_params, - set_transport_params_result.early_data_context); + set_transport_params_result.early_data_context, + tls_connection_.ssl_config()); QUICHE_DCHECK_EQ(status, select_cert_status().value()); @@ -876,28 +918,28 @@ ssl_select_cert_result_t TlsServerHandshaker::EarlySelectCertCallback( } void TlsServerHandshaker::OnSelectCertificateDone( - bool ok, - bool is_sync, - const ProofSource::Chain* chain, - absl::string_view handshake_hints) { + bool ok, bool is_sync, const ProofSource::Chain* chain, + absl::string_view handshake_hints, + absl::string_view ticket_encryption_key) { QUIC_DVLOG(1) << "OnSelectCertificateDone. ok:" << ok << ", is_sync:" << is_sync - << ", len(handshake_hints):" << handshake_hints.size(); + << ", len(handshake_hints):" << handshake_hints.size() + << ", len(ticket_encryption_key):" + << ticket_encryption_key.size(); + ticket_encryption_key_ = std::string(ticket_encryption_key); select_cert_status_ = QUIC_FAILURE; if (ok) { if (chain && !chain->certs.empty()) { tls_connection_.SetCertChain(chain->ToCryptoBuffers().value); - if (use_handshake_hints_) { - if (!handshake_hints.empty() && - !SSL_set_handshake_hints( - ssl(), reinterpret_cast<const uint8_t*>(handshake_hints.data()), - handshake_hints.size())) { - // If |SSL_set_handshake_hints| fails, the ssl() object will remain - // intact, it is as if we didn't call it. The handshaker will - // continue to compute signature/decrypt ticket as normal. - QUIC_CODE_COUNT(quic_tls_server_set_handshake_hints_failed); - QUIC_DVLOG(1) << "SSL_set_handshake_hints failed"; - } + if (!handshake_hints.empty() && + !SSL_set_handshake_hints( + ssl(), reinterpret_cast<const uint8_t*>(handshake_hints.data()), + handshake_hints.size())) { + // If |SSL_set_handshake_hints| fails, the ssl() object will remain + // intact, it is as if we didn't call it. The handshaker will + // continue to compute signature/decrypt ticket as normal. + QUIC_CODE_COUNT(quic_tls_server_set_handshake_hints_failed); + QUIC_DVLOG(1) << "SSL_set_handshake_hints failed"; } select_cert_status_ = QUIC_SUCCESS; } else { @@ -929,6 +971,10 @@ void TlsServerHandshaker::OnSelectCertificateDone( } } +bool TlsServerHandshaker::WillNotCallComputeSignature() const { + return SSL_can_release_private_key(ssl()); +} + bool TlsServerHandshaker::ValidateHostname(const std::string& hostname) const { if (!QuicHostnameUtils::IsValidSNI(hostname)) { // TODO(b/151676147): Include this error string in the CONNECTION_CLOSE @@ -986,13 +1032,6 @@ int TlsServerHandshaker::SelectAlpn(const uint8_t** out, return SSL_TLSEXT_ERR_NOACK; } - if (!use_handshake_hints_) { - // Enable ALPS for the selected ALPN protocol. - if (!SetApplicationSettings(*selected_alpn).success) { - return SSL_TLSEXT_ERR_NOACK; - } - } - session()->OnAlpnSelected(*selected_alpn); valid_alpn_received_ = true; *out_len = selected_alpn->size(); @@ -1006,8 +1045,17 @@ TlsServerHandshaker::SetApplicationSettings(absl::string_view alpn) { const uint8_t* alps_data = nullptr; const std::string& hostname = crypto_negotiated_params_->sni; - std::string accept_ch_value = GetAcceptChValueForOrigin(hostname); + std::string accept_ch_value = GetAcceptChValueForHostname(hostname); std::string origin = absl::StrCat("https://", hostname); + if (GetQuicReloadableFlag(quic_include_port_in_alps_origin)) { + QUIC_RELOADABLE_FLAG_COUNT(quic_include_port_in_alps_origin); + uint16_t port = session()->self_address().port(); + if (port != kDefaultPort) { + // This should be rare in production, but useful for test servers. + QUIC_CODE_COUNT(quic_server_alps_non_default_port); + absl::StrAppend(&origin, ":", port); + } + } if (!accept_ch_value.empty()) { AcceptChFrame frame{{{std::move(origin), std::move(accept_ch_value)}}}; diff --git a/chromium/net/third_party/quiche/src/quic/core/tls_server_handshaker.h b/chromium/net/third_party/quiche/src/quic/core/tls_server_handshaker.h index 260b094bda7..998cc15ad7c 100644 --- a/chromium/net/third_party/quiche/src/quic/core/tls_server_handshaker.h +++ b/chromium/net/third_party/quiche/src/quic/core/tls_server_handshaker.h @@ -19,6 +19,7 @@ #include "quic/core/quic_types.h" #include "quic/core/tls_handshaker.h" #include "quic/platform/api/quic_export.h" +#include "quic/platform/api/quic_flag_utils.h" #include "quic/platform/api/quic_flags.h" namespace quic { @@ -82,12 +83,15 @@ class QUIC_EXPORT_PRIVATE TlsServerHandshaker const SSL_CIPHER* cipher, const std::vector<uint8_t>& write_secret) override; - // Called with normalized SNI hostname as |origin|. Return value will be sent - // in an ACCEPT_CH frame in the TLS ALPS extension, unless empty. - virtual std::string GetAcceptChValueForOrigin( - const std::string& origin) const; + // Called with normalized SNI hostname as |hostname|. Return value will be + // sent in an ACCEPT_CH frame in the TLS ALPS extension, unless empty. + virtual std::string GetAcceptChValueForHostname( + const std::string& hostname) const; protected: + // Override for tracing. + void InfoCallback(int type, int value) override; + // Creates a proof source handle for selecting cert and computing signature. virtual std::unique_ptr<ProofSourceHandle> MaybeCreateProofSourceHandle(); @@ -165,10 +169,10 @@ class QUIC_EXPORT_PRIVATE TlsServerHandshaker bool HasValidSignature(size_t max_signature_size) const; // ProofSourceHandleCallback implementation: - void OnSelectCertificateDone(bool ok, - bool is_sync, - const ProofSource::Chain* chain, - absl::string_view handshake_hints) override; + void OnSelectCertificateDone( + bool ok, bool is_sync, const ProofSource::Chain* chain, + absl::string_view handshake_hints, + absl::string_view ticket_encryption_key) override; void OnComputeSignatureDone( bool ok, @@ -176,6 +180,17 @@ class QUIC_EXPORT_PRIVATE TlsServerHandshaker std::string signature, std::unique_ptr<ProofSource::Details> details) override; + void set_encryption_established(bool encryption_established) { + encryption_established_ = encryption_established; + } + + bool WillNotCallComputeSignature() const override; + + void SetIgnoreTicketOpen(bool value) { ignore_ticket_open_ = value; } + + const bool allow_ignore_ticket_open_ = + GetQuicReloadableFlag(quic_tls_allow_ignore_ticket_open); + private: class QUIC_EXPORT_PRIVATE DecryptCallback : public ProofSource::DecryptCallback { @@ -200,8 +215,8 @@ class QUIC_EXPORT_PRIVATE TlsServerHandshaker ~DefaultProofSourceHandle() override; - // Cancel the pending signature operation, if any. - void CancelPendingOperation() override; + // Close the handle. Cancel the pending signature operation, if any. + void CloseHandle() override; // Delegates to proof_source_->GetCertChain. // Returns QUIC_SUCCESS or QUIC_FAILURE. Never returns QUIC_PENDING. @@ -214,8 +229,8 @@ class QUIC_EXPORT_PRIVATE TlsServerHandshaker const std::string& alpn, absl::optional<std::string> alps, const std::vector<uint8_t>& quic_transport_params, - const absl::optional<std::vector<uint8_t>>& early_data_context) - override; + const absl::optional<std::vector<uint8_t>>& early_data_context, + const QuicSSLConfig& ssl_config) override; // Delegates to proof_source_->ComputeTlsSignature. // Returns QUIC_SUCCESS, QUIC_FAILURE or QUIC_PENDING. @@ -243,6 +258,20 @@ class QUIC_EXPORT_PRIVATE TlsServerHandshaker // Operation has been canceled, or Run has been called. return; } + + if (GetQuicReloadableFlag(quic_run_default_signature_callback_once)) { + QUIC_RELOADABLE_FLAG_COUNT(quic_run_default_signature_callback_once); + DefaultProofSourceHandle* handle = handle_; + handle_ = nullptr; + + handle->signature_callback_ = nullptr; + if (handle->handshaker_ != nullptr) { + handle->handshaker_->OnComputeSignatureDone( + ok, is_sync_, std::move(signature), std::move(details)); + } + return; + } + handle_->signature_callback_ = nullptr; if (handle_->handshaker_ != nullptr) { handle_->handshaker_->OnComputeSignatureDone( @@ -309,6 +338,9 @@ class QUIC_EXPORT_PRIVATE TlsServerHandshaker // indicates that the client attempted a resumption. bool ticket_received_ = false; + // Force SessionTicketOpen to return ssl_ticket_aead_ignore_ticket if called. + bool ignore_ticket_open_ = false; + // nullopt means select cert hasn't started. absl::optional<QuicAsyncStatus> select_cert_status_; @@ -323,6 +355,9 @@ class QUIC_EXPORT_PRIVATE TlsServerHandshaker // Pre-shared key used during the handshake. std::string pre_shared_key_; + // (optional) Key to use for encrypting TLS resumption tickets. + std::string ticket_encryption_key_; + HandshakeState state_ = HANDSHAKE_START; bool encryption_established_ = false; bool valid_alpn_received_ = false; @@ -330,8 +365,6 @@ class QUIC_EXPORT_PRIVATE TlsServerHandshaker crypto_negotiated_params_; TlsServerConnection tls_connection_; const QuicCryptoServerConfig* crypto_config_; // Unowned. - const bool use_handshake_hints_ = - GetQuicReloadableFlag(quic_tls_server_use_handshake_hints); }; } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/tls_server_handshaker_test.cc b/chromium/net/third_party/quiche/src/quic/core/tls_server_handshaker_test.cc index d67c8791d2c..6f8db123c99 100644 --- a/chromium/net/third_party/quiche/src/quic/core/tls_server_handshaker_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/tls_server_handshaker_test.cc @@ -561,6 +561,23 @@ TEST_P(TlsServerHandshakerTest, HostnameForCertSelectionAndComputeSignature) { EXPECT_EQ(last_compute_signature_args().hostname, "test.example.com"); } +TEST_P(TlsServerHandshakerTest, SSLConfigForCertSelection) { + InitializeServerWithFakeProofSourceHandle(); + + // Disable early data. + server_session_->ssl_config()->early_data_enabled = false; + + server_handshaker_->SetupProofSourceHandle( + /*select_cert_action=*/FakeProofSourceHandle::Action::DELEGATE_SYNC, + /*compute_signature_action=*/FakeProofSourceHandle::Action:: + DELEGATE_SYNC); + InitializeFakeClient(); + CompleteCryptoHandshake(); + ExpectHandshakeSuccessful(); + + EXPECT_FALSE(last_select_cert_args().ssl_config.early_data_enabled); +} + TEST_P(TlsServerHandshakerTest, ConnectionClosedOnTlsError) { EXPECT_CALL(*server_connection_, CloseConnection(QUIC_HANDSHAKE_FAILED, _, _, _)); diff --git a/chromium/net/third_party/quiche/src/quic/core/web_transport_stream_adapter.cc b/chromium/net/third_party/quiche/src/quic/core/web_transport_stream_adapter.cc index 78227049dbd..2470aacf4cc 100644 --- a/chromium/net/third_party/quiche/src/quic/core/web_transport_stream_adapter.cc +++ b/chromium/net/third_party/quiche/src/quic/core/web_transport_stream_adapter.cc @@ -42,11 +42,8 @@ bool WebTransportStreamAdapter::Write(absl::string_view data) { return false; } - QuicUniqueBufferPtr buffer = MakeUniqueBuffer( - session_->connection()->helper()->GetStreamSendBufferAllocator(), - data.size()); - memcpy(buffer.get(), data.data(), data.size()); - QuicMemSlice memslice(std::move(buffer), data.size()); + QuicMemSlice memslice(QuicBuffer::Copy( + session_->connection()->helper()->GetStreamSendBufferAllocator(), data)); QuicConsumedData consumed = stream_->WriteMemSlices(QuicMemSliceSpan(&memslice), /*fin=*/false); diff --git a/chromium/net/third_party/quiche/src/quic/masque/masque_client_bin.cc b/chromium/net/third_party/quiche/src/quic/masque/masque_client_bin.cc index 920372574a1..bb9964e4207 100644 --- a/chromium/net/third_party/quiche/src/quic/masque/masque_client_bin.cc +++ b/chromium/net/third_party/quiche/src/quic/masque/masque_client_bin.cc @@ -50,8 +50,6 @@ int RunMasqueClient(int argc, char* argv[]) { return 1; } - SetQuicReloadableFlag(quic_h3_datagram, true); - const bool disable_certificate_verification = GetQuicFlag(FLAGS_disable_certificate_verification); QuicEpollServer epoll_server; diff --git a/chromium/net/third_party/quiche/src/quic/masque/masque_client_session.cc b/chromium/net/third_party/quiche/src/quic/masque/masque_client_session.cc index 6eee553e076..0d865692e46 100644 --- a/chromium/net/third_party/quiche/src/quic/masque/masque_client_session.cc +++ b/chromium/net/third_party/quiche/src/quic/masque/masque_client_session.cc @@ -92,11 +92,8 @@ MasqueClientSession::GetOrCreateConnectUdpClientState( return nullptr; } - QuicDatagramFlowId flow_id = GetNextDatagramFlowId(); - QUIC_DLOG(INFO) << "Sending CONNECT-UDP request for " << target_server_address - << " using flow ID " << flow_id << " on stream " - << stream->id(); + << " on stream " << stream->id(); // Send the request. spdy::Http2HeaderBlock headers; @@ -104,7 +101,7 @@ MasqueClientSession::GetOrCreateConnectUdpClientState( headers[":scheme"] = "masque"; headers[":path"] = "/"; headers[":authority"] = target_server_address.ToString(); - SpdyUtils::AddDatagramFlowIdHeader(&headers, flow_id); + SpdyUtils::AddDatagramFlowIdHeader(&headers, stream->id()); size_t bytes_sent = stream->SendRequest(std::move(headers), /*body=*/"", /*fin=*/false); if (bytes_sent == 0) { @@ -112,9 +109,10 @@ MasqueClientSession::GetOrCreateConnectUdpClientState( return nullptr; } + absl::optional<QuicDatagramContextId> context_id; connect_udp_client_states_.push_back( - ConnectUdpClientState(stream, encapsulated_client_session, this, flow_id, - target_server_address)); + ConnectUdpClientState(stream, encapsulated_client_session, this, + context_id, target_server_address)); return &connect_udp_client_states_.back(); } @@ -137,12 +135,15 @@ void MasqueClientSession::SendPacket( return; } - QuicDatagramFlowId flow_id = connect_udp->flow_id(); - MessageStatus message_status = - SendHttp3Datagram(connect_udp->flow_id(), packet); + MessageStatus message_status = SendHttp3Datagram( + connect_udp->stream()->id(), connect_udp->context_id(), packet); QUIC_DVLOG(1) << "Sent packet to " << target_server_address - << " compressed with flow ID " << flow_id + << " compressed with stream ID " << connect_udp->stream()->id() + << " context ID " + << (connect_udp->context_id().has_value() + ? connect_udp->context_id().value() + : 0) << " and got message status " << MessageStatusToString(message_status); } @@ -178,7 +179,11 @@ void MasqueClientSession::UnregisterConnectionId( for (auto it = connect_udp_client_states_.begin(); it != connect_udp_client_states_.end();) { if (it->encapsulated_client_session() == encapsulated_client_session) { - QUIC_DLOG(INFO) << "Removing state for flow_id " << it->flow_id(); + QUIC_DLOG(INFO) << "Removing state for stream ID " << it->stream()->id() + << " context ID " + << (it->context_id().has_value() + ? it->context_id().value() + : 0); auto* stream = it->stream(); it = connect_udp_client_states_.erase(it); if (!stream->write_side_closed()) { @@ -217,8 +222,10 @@ void MasqueClientSession::OnStreamClosed(QuicStreamId stream_id) { it != connect_udp_client_states_.end();) { if (it->stream()->id() == stream_id) { QUIC_DLOG(INFO) << "Stream " << stream_id - << " was closed, removing state for flow_id " - << it->flow_id(); + << " was closed, removing state for context ID " + << (it->context_id().has_value() + ? it->context_id().value() + : 0); auto* encapsulated_client_session = it->encapsulated_client_session(); it = connect_udp_client_states_.erase(it); encapsulated_client_session->CloseConnection( @@ -233,24 +240,41 @@ void MasqueClientSession::OnStreamClosed(QuicStreamId stream_id) { QuicSpdyClientSession::OnStreamClosed(stream_id); } +bool MasqueClientSession::OnSettingsFrame(const SettingsFrame& frame) { + if (!QuicSpdyClientSession::OnSettingsFrame(frame)) { + QUIC_DLOG(ERROR) << "Failed to parse received settings"; + return false; + } + if (!SupportsH3Datagram()) { + QUIC_DLOG(ERROR) << "Refusing to use MASQUE without HTTP/3 Datagrams"; + return false; + } + owner_->OnSettingsReceived(); + return true; +} + MasqueClientSession::ConnectUdpClientState::ConnectUdpClientState( QuicSpdyClientStream* stream, EncapsulatedClientSession* encapsulated_client_session, MasqueClientSession* masque_session, - QuicDatagramFlowId flow_id, + absl::optional<QuicDatagramContextId> context_id, const QuicSocketAddress& target_server_address) : stream_(stream), encapsulated_client_session_(encapsulated_client_session), masque_session_(masque_session), - flow_id_(flow_id), + context_id_(context_id), target_server_address_(target_server_address) { QUICHE_DCHECK_NE(masque_session_, nullptr); - masque_session_->RegisterHttp3FlowId(this->flow_id(), this); + this->stream()->RegisterHttp3DatagramRegistrationVisitor(this); + Http3DatagramContextExtensions extensions; + this->stream()->RegisterHttp3DatagramContextId(this->context_id(), extensions, + this); } MasqueClientSession::ConnectUdpClientState::~ConnectUdpClientState() { - if (flow_id_.has_value()) { - masque_session_->UnregisterHttp3FlowId(flow_id()); + if (stream() != nullptr) { + stream()->UnregisterHttp3DatagramContextId(context_id()); + stream()->UnregisterHttp3DatagramRegistrationVisitor(); } } @@ -265,23 +289,69 @@ MasqueClientSession::ConnectUdpClientState::operator=( stream_ = other.stream_; encapsulated_client_session_ = other.encapsulated_client_session_; masque_session_ = other.masque_session_; - flow_id_ = other.flow_id_; + context_id_ = other.context_id_; target_server_address_ = other.target_server_address_; - other.flow_id_.reset(); - if (flow_id_.has_value()) { - masque_session_->UnregisterHttp3FlowId(flow_id()); - masque_session_->RegisterHttp3FlowId(flow_id(), this); + other.stream_ = nullptr; + if (stream() != nullptr) { + stream()->MoveHttp3DatagramRegistration(this); + stream()->MoveHttp3DatagramContextIdRegistration(context_id(), this); } return *this; } void MasqueClientSession::ConnectUdpClientState::OnHttp3Datagram( - QuicDatagramFlowId flow_id, + QuicStreamId stream_id, absl::optional<QuicDatagramContextId> context_id, absl::string_view payload) { - QUICHE_DCHECK_EQ(flow_id, this->flow_id()); + QUICHE_DCHECK_EQ(stream_id, stream()->id()); + QUICHE_DCHECK(context_id == context_id_); encapsulated_client_session_->ProcessPacket(payload, target_server_address_); QUIC_DVLOG(1) << "Sent " << payload.size() - << " bytes to connection for flow_id " << flow_id; + << " bytes to connection for stream ID " << stream_id + << " context ID " + << (context_id.has_value() ? context_id.value() : 0); +} + +void MasqueClientSession::ConnectUdpClientState::OnContextReceived( + QuicStreamId stream_id, absl::optional<QuicDatagramContextId> context_id, + const Http3DatagramContextExtensions& /*extensions*/) { + if (stream_id != stream_->id()) { + QUIC_BUG(MASQUE client bad datagram context registration) + << "Registered stream ID " << stream_id << ", expected " + << stream_->id(); + return; + } + if (context_id != context_id_) { + QUIC_DLOG(INFO) << "Ignoring unexpected context ID " + << (context_id.has_value() ? context_id.value() : 0) + << " instead of " + << (context_id_.has_value() ? context_id_.value() : 0) + << " on stream ID " << stream_->id(); + return; + } + // Do nothing since the client registers first and we currently ignore + // extensions. +} + +void MasqueClientSession::ConnectUdpClientState::OnContextClosed( + QuicStreamId stream_id, absl::optional<QuicDatagramContextId> context_id, + const Http3DatagramContextExtensions& /*extensions*/) { + if (stream_id != stream_->id()) { + QUIC_BUG(MASQUE client bad datagram context registration) + << "Closed context on stream ID " << stream_id << ", expected " + << stream_->id(); + return; + } + if (context_id != context_id_) { + QUIC_DLOG(INFO) << "Ignoring unexpected close of context ID " + << (context_id.has_value() ? context_id.value() : 0) + << " instead of " + << (context_id_.has_value() ? context_id_.value() : 0) + << " on stream ID " << stream_->id(); + return; + } + QUIC_DLOG(INFO) << "Received datagram context close on stream ID " + << stream_->id() << ", closing stream"; + masque_session_->ResetStream(stream_->id(), QUIC_STREAM_CANCELLED); } } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/masque/masque_client_session.h b/chromium/net/third_party/quiche/src/quic/masque/masque_client_session.h index e055a7699be..1c130873340 100644 --- a/chromium/net/third_party/quiche/src/quic/masque/masque_client_session.h +++ b/chromium/net/third_party/quiche/src/quic/masque/masque_client_session.h @@ -31,6 +31,9 @@ class QUIC_NO_EXPORT MasqueClientSession : public QuicSpdyClientSession { // Notifies the owner that the client connection ID is no longer in use. virtual void UnregisterClientConnectionId( QuicConnectionId client_connection_id) = 0; + + // Notifies the owner that a settings frame has been received. + virtual void OnSettingsReceived() = 0; }; // Interface meant to be implemented by encapsulated client sessions, i.e. // the end-to-end QUIC client sessions that run inside MASQUE encapsulation. @@ -75,6 +78,9 @@ class QUIC_NO_EXPORT MasqueClientSession : public QuicSpdyClientSession { ConnectionCloseSource source) override; void OnStreamClosed(QuicStreamId stream_id) override; + // From QuicSpdySession. + bool OnSettingsFrame(const SettingsFrame& frame) override; + // Send encapsulated packet. void SendPacket(QuicConnectionId client_connection_id, QuicConnectionId server_connection_id, @@ -101,7 +107,8 @@ class QUIC_NO_EXPORT MasqueClientSession : public QuicSpdyClientSession { private: // State that the MasqueClientSession keeps for each CONNECT-UDP request. class QUIC_NO_EXPORT ConnectUdpClientState - : public QuicSpdySession::Http3DatagramVisitor { + : public QuicSpdyStream::Http3DatagramRegistrationVisitor, + public QuicSpdyStream::Http3DatagramVisitor { public: // |stream| and |encapsulated_client_session| must be valid for the lifetime // of the ConnectUdpClientState. @@ -109,7 +116,7 @@ class QUIC_NO_EXPORT MasqueClientSession : public QuicSpdyClientSession { QuicSpdyClientStream* stream, EncapsulatedClientSession* encapsulated_client_session, MasqueClientSession* masque_session, - QuicDatagramFlowId flow_id, + absl::optional<QuicDatagramContextId> context_id, const QuicSocketAddress& target_server_address); ~ConnectUdpClientState(); @@ -124,26 +131,38 @@ class QUIC_NO_EXPORT MasqueClientSession : public QuicSpdyClientSession { EncapsulatedClientSession* encapsulated_client_session() const { return encapsulated_client_session_; } - QuicDatagramFlowId flow_id() const { - QUICHE_DCHECK(flow_id_.has_value()); - return *flow_id_; + absl::optional<QuicDatagramContextId> context_id() const { + return context_id_; } const QuicSocketAddress& target_server_address() const { return target_server_address_; } - // From QuicSpdySession::Http3DatagramVisitor. - void OnHttp3Datagram(QuicDatagramFlowId flow_id, + // From QuicSpdyStream::Http3DatagramVisitor. + void OnHttp3Datagram(QuicStreamId stream_id, + absl::optional<QuicDatagramContextId> context_id, absl::string_view payload) override; + // From QuicSpdyStream::Http3DatagramRegistrationVisitor. + void OnContextReceived( + QuicStreamId stream_id, + absl::optional<QuicDatagramContextId> context_id, + const Http3DatagramContextExtensions& extensions) override; + void OnContextClosed( + QuicStreamId stream_id, + absl::optional<QuicDatagramContextId> context_id, + const Http3DatagramContextExtensions& extensions) override; + private: QuicSpdyClientStream* stream_; // Unowned. EncapsulatedClientSession* encapsulated_client_session_; // Unowned. MasqueClientSession* masque_session_; // Unowned. - absl::optional<QuicDatagramFlowId> flow_id_; + absl::optional<QuicDatagramContextId> context_id_; QuicSocketAddress target_server_address_; }; + bool ShouldNegotiateHttp3Datagram() override { return true; } + const ConnectUdpClientState* GetOrCreateConnectUdpClientState( const QuicSocketAddress& target_server_address, EncapsulatedClientSession* encapsulated_client_session); diff --git a/chromium/net/third_party/quiche/src/quic/masque/masque_compression_engine.cc b/chromium/net/third_party/quiche/src/quic/masque/masque_compression_engine.cc index b021b41a0a6..035b9f9d030 100644 --- a/chromium/net/third_party/quiche/src/quic/masque/masque_compression_engine.cc +++ b/chromium/net/third_party/quiche/src/quic/masque/masque_compression_engine.cc @@ -21,7 +21,7 @@ namespace quic { namespace { // |kFlowId0| is used to indicate creation of a new compression context. -const QuicDatagramFlowId kFlowId0 = 0; +const QuicDatagramStreamId kFlowId0 = 0; enum MasqueAddressFamily : uint8_t { MasqueAddressFamilyIPv4 = 4, @@ -32,16 +32,16 @@ enum MasqueAddressFamily : uint8_t { MasqueCompressionEngine::MasqueCompressionEngine( QuicSpdySession* masque_session) - : masque_session_(masque_session) {} + : masque_session_(masque_session), + next_available_flow_id_( + masque_session_->perspective() == Perspective::IS_CLIENT ? 0 : 1) {} -QuicDatagramFlowId MasqueCompressionEngine::FindOrCreateCompressionContext( +QuicDatagramStreamId MasqueCompressionEngine::FindOrCreateCompressionContext( QuicConnectionId client_connection_id, QuicConnectionId server_connection_id, - const QuicSocketAddress& server_address, - bool client_connection_id_present, - bool server_connection_id_present, - bool* validated) { - QuicDatagramFlowId flow_id = kFlowId0; + const QuicSocketAddress& server_address, bool client_connection_id_present, + bool server_connection_id_present, bool* validated) { + QuicDatagramStreamId flow_id = kFlowId0; *validated = false; for (const auto& kv : contexts_) { const MasqueCompressionContext& context = kv.second; @@ -74,11 +74,8 @@ QuicDatagramFlowId MasqueCompressionEngine::FindOrCreateCompressionContext( } // Create new compression context. - flow_id = masque_session_->GetNextDatagramFlowId(); - if (flow_id == kFlowId0) { - // Do not use value zero which is reserved in this mode. - flow_id = masque_session_->GetNextDatagramFlowId(); - } + next_available_flow_id_ += 2; + flow_id = next_available_flow_id_; QUIC_DVLOG(1) << "Compression assigning new flow_id " << flow_id << " to " << server_address << " client " << client_connection_id << " server " << server_connection_id; @@ -96,13 +93,9 @@ bool MasqueCompressionEngine::WriteCompressedPacketToSlice( QuicConnectionId server_connection_id, const QuicSocketAddress& server_address, QuicConnectionId destination_connection_id, - QuicConnectionId source_connection_id, - QuicDatagramFlowId flow_id, - bool validated, - uint8_t first_byte, - bool long_header, - QuicDataReader* reader, - QuicDataWriter* writer) { + QuicConnectionId source_connection_id, QuicDatagramStreamId flow_id, + bool validated, uint8_t first_byte, bool long_header, + QuicDataReader* reader, QuicDataWriter* writer) { if (validated) { QUIC_DVLOG(1) << "Compressing using validated flow_id " << flow_id; if (!writer->WriteVarInt62(flow_id)) { @@ -222,8 +215,7 @@ bool MasqueCompressionEngine::WriteCompressedPacketToSlice( } void MasqueCompressionEngine::CompressAndSendPacket( - absl::string_view packet, - QuicConnectionId client_connection_id, + absl::string_view packet, QuicConnectionId client_connection_id, QuicConnectionId server_connection_id, const QuicSocketAddress& server_address) { QUIC_DVLOG(2) << "Compressing client " << client_connection_id << " server " @@ -258,7 +250,7 @@ void MasqueCompressionEngine::CompressAndSendPacket( } bool validated = false; - QuicDatagramFlowId flow_id = FindOrCreateCompressionContext( + QuicDatagramStreamId flow_id = FindOrCreateCompressionContext( client_connection_id, server_connection_id, server_address, client_connection_id_present, server_connection_id_present, &validated); @@ -276,10 +268,10 @@ void MasqueCompressionEngine::CompressAndSendPacket( sizeof(server_address.port()) + sizeof(uint8_t) + server_address.host().ToPackedString().length(); } - QuicUniqueBufferPtr buffer = MakeUniqueBuffer( + QuicBuffer buffer( masque_session_->connection()->helper()->GetStreamSendBufferAllocator(), slice_length); - QuicDataWriter writer(slice_length, buffer.get()); + QuicDataWriter writer(buffer.size(), buffer.data()); if (!WriteCompressedPacketToSlice( client_connection_id, server_connection_id, server_address, @@ -288,18 +280,16 @@ void MasqueCompressionEngine::CompressAndSendPacket( return; } - QuicMemSlice slice(std::move(buffer), slice_length); MessageResult message_result = - masque_session_->SendMessage(QuicMemSliceSpan(&slice)); + masque_session_->SendMessage(QuicMemSlice(std::move(buffer))); QUIC_DVLOG(1) << "Sent packet compressed with flow ID " << flow_id << " and got message result " << message_result; } bool MasqueCompressionEngine::ParseCompressionContext( - QuicDataReader* reader, - MasqueCompressionContext* context) { - QuicDatagramFlowId new_flow_id; + QuicDataReader* reader, MasqueCompressionContext* context) { + QuicDatagramStreamId new_flow_id; if (!reader->ReadVarInt62(&new_flow_id)) { QUIC_DLOG(ERROR) << "Could not read new_flow_id"; return false; @@ -398,10 +388,8 @@ bool MasqueCompressionEngine::ParseCompressionContext( } bool MasqueCompressionEngine::WriteDecompressedPacket( - QuicDataReader* reader, - const MasqueCompressionContext& context, - std::vector<char>* packet, - bool* version_present) { + QuicDataReader* reader, const MasqueCompressionContext& context, + std::vector<char>* packet, bool* version_present) { QuicConnectionId destination_connection_id, source_connection_id; if (masque_session_->perspective() == Perspective::IS_SERVER) { destination_connection_id = context.server_connection_id; @@ -464,16 +452,13 @@ bool MasqueCompressionEngine::WriteDecompressedPacket( } bool MasqueCompressionEngine::DecompressDatagram( - absl::string_view datagram, - QuicConnectionId* client_connection_id, - QuicConnectionId* server_connection_id, - QuicSocketAddress* server_address, - std::vector<char>* packet, - bool* version_present) { + absl::string_view datagram, QuicConnectionId* client_connection_id, + QuicConnectionId* server_connection_id, QuicSocketAddress* server_address, + std::vector<char>* packet, bool* version_present) { QUIC_DVLOG(1) << "Decompressing DATAGRAM frame of length " << datagram.length(); QuicDataReader reader(datagram); - QuicDatagramFlowId flow_id; + QuicDatagramStreamId flow_id; if (!reader.ReadVarInt62(&flow_id)) { QUIC_DLOG(ERROR) << "Could not read flow_id"; return false; @@ -525,14 +510,14 @@ bool MasqueCompressionEngine::DecompressDatagram( void MasqueCompressionEngine::UnregisterClientConnectionId( QuicConnectionId client_connection_id) { - std::vector<QuicDatagramFlowId> flow_ids_to_remove; + std::vector<QuicDatagramStreamId> flow_ids_to_remove; for (const auto& kv : contexts_) { const MasqueCompressionContext& context = kv.second; if (context.client_connection_id == client_connection_id) { flow_ids_to_remove.push_back(kv.first); } } - for (QuicDatagramFlowId flow_id : flow_ids_to_remove) { + for (QuicDatagramStreamId flow_id : flow_ids_to_remove) { contexts_.erase(flow_id); } } diff --git a/chromium/net/third_party/quiche/src/quic/masque/masque_compression_engine.h b/chromium/net/third_party/quiche/src/quic/masque/masque_compression_engine.h index 9bea618ae76..581b9501791 100644 --- a/chromium/net/third_party/quiche/src/quic/masque/masque_compression_engine.h +++ b/chromium/net/third_party/quiche/src/quic/masque/masque_compression_engine.h @@ -63,8 +63,7 @@ class QUIC_NO_EXPORT MasqueCompressionEngine { QuicConnectionId* client_connection_id, QuicConnectionId* server_connection_id, QuicSocketAddress* server_address, - std::vector<char>* packet, - bool* version_present); + std::vector<char>* packet, bool* version_present); // Clears all entries referencing |client_connection_id| from the // compression table. @@ -83,12 +82,11 @@ class QUIC_NO_EXPORT MasqueCompressionEngine { // whether the corresponding connection ID is present in the current packet. // |validated| will contain whether the compression context that matches // these arguments is currently validated or not. - QuicDatagramFlowId FindOrCreateCompressionContext( + QuicDatagramStreamId FindOrCreateCompressionContext( QuicConnectionId client_connection_id, QuicConnectionId server_connection_id, const QuicSocketAddress& server_address, - bool client_connection_id_present, - bool server_connection_id_present, + bool client_connection_id_present, bool server_connection_id_present, bool* validated); // Writes compressed packet to |slice| during compression. @@ -97,11 +95,9 @@ class QUIC_NO_EXPORT MasqueCompressionEngine { const QuicSocketAddress& server_address, QuicConnectionId destination_connection_id, QuicConnectionId source_connection_id, - QuicDatagramFlowId flow_id, - bool validated, - uint8_t first_byte, - bool long_header, - QuicDataReader* reader, + QuicDatagramStreamId flow_id, + bool validated, uint8_t first_byte, + bool long_header, QuicDataReader* reader, QuicDataWriter* writer); // Parses compression context from flow ID 0 during decompression. @@ -115,7 +111,8 @@ class QUIC_NO_EXPORT MasqueCompressionEngine { bool* version_present); QuicSpdySession* masque_session_; // Unowned. - absl::flat_hash_map<QuicDatagramFlowId, MasqueCompressionContext> contexts_; + absl::flat_hash_map<QuicDatagramStreamId, MasqueCompressionContext> contexts_; + QuicDatagramStreamId next_available_flow_id_; }; } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/masque/masque_epoll_client.cc b/chromium/net/third_party/quiche/src/quic/masque/masque_epoll_client.cc index 5ad91cfd9ad..ec475205ac8 100644 --- a/chromium/net/third_party/quiche/src/quic/masque/masque_epoll_client.cc +++ b/chromium/net/third_party/quiche/src/quic/masque/masque_epoll_client.cc @@ -81,6 +81,11 @@ std::unique_ptr<MasqueEpollClient> MasqueEpollClient::Create( return nullptr; } + if (!masque_client->WaitUntilSettingsReceived()) { + QUIC_LOG(ERROR) << "Failed to receive settings"; + return nullptr; + } + if (masque_client->masque_mode() == MasqueMode::kLegacy) { // Construct the legacy mode init request. spdy::Http2HeaderBlock header_block; @@ -114,6 +119,17 @@ std::unique_ptr<MasqueEpollClient> MasqueEpollClient::Create( return masque_client; } +void MasqueEpollClient::OnSettingsReceived() { + settings_received_ = true; +} + +bool MasqueEpollClient::WaitUntilSettingsReceived() { + while (connected() && !settings_received_) { + network_helper()->RunEventLoop(); + } + return connected() && settings_received_; +} + void MasqueEpollClient::UnregisterClientConnectionId( QuicConnectionId client_connection_id) { std::string body(client_connection_id.data(), client_connection_id.length()); diff --git a/chromium/net/third_party/quiche/src/quic/masque/masque_epoll_client.h b/chromium/net/third_party/quiche/src/quic/masque/masque_epoll_client.h index 1db161f5aaa..2f5faca0aff 100644 --- a/chromium/net/third_party/quiche/src/quic/masque/masque_epoll_client.h +++ b/chromium/net/third_party/quiche/src/quic/masque/masque_epoll_client.h @@ -35,6 +35,8 @@ class QUIC_NO_EXPORT MasqueEpollClient : public QuicClient, // Convenience accessor for the underlying connection ID. QuicConnectionId connection_id(); + // From MasqueClientSession::Owner. + void OnSettingsReceived() override; // Send a MASQUE client connection ID unregister command to the server. void UnregisterClientConnectionId( QuicConnectionId client_connection_id) override; @@ -50,12 +52,17 @@ class QUIC_NO_EXPORT MasqueEpollClient : public QuicClient, std::unique_ptr<ProofVerifier> proof_verifier, const std::string& authority); + // Wait synchronously until we receive the peer's settings. Returns whether + // they were received. + bool WaitUntilSettingsReceived(); + // Disallow copy and assign. MasqueEpollClient(const MasqueEpollClient&) = delete; MasqueEpollClient& operator=(const MasqueEpollClient&) = delete; MasqueMode masque_mode_; std::string authority_; + bool settings_received_ = false; }; } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/masque/masque_server_bin.cc b/chromium/net/third_party/quiche/src/quic/masque/masque_server_bin.cc index d5a5d9f7d36..aaccda3722e 100644 --- a/chromium/net/third_party/quiche/src/quic/masque/masque_server_bin.cc +++ b/chromium/net/third_party/quiche/src/quic/masque/masque_server_bin.cc @@ -52,8 +52,6 @@ int main(int argc, char* argv[]) { return 0; } - SetQuicReloadableFlag(quic_h3_datagram, true); - quic::MasqueMode masque_mode = quic::MasqueMode::kOpen; std::string mode_string = GetQuicFlag(FLAGS_masque_mode); if (mode_string == "legacy") { diff --git a/chromium/net/third_party/quiche/src/quic/masque/masque_server_session.cc b/chromium/net/third_party/quiche/src/quic/masque/masque_server_session.cc index 8a531cab3a0..effc17ff316 100644 --- a/chromium/net/third_party/quiche/src/quic/masque/masque_server_session.cc +++ b/chromium/net/third_party/quiche/src/quic/masque/masque_server_session.cc @@ -58,8 +58,7 @@ class FdWrapper { }; std::unique_ptr<QuicBackendResponse> CreateBackendErrorResponse( - absl::string_view status, - absl::string_view error_details) { + absl::string_view status, absl::string_view error_details) { spdy::Http2HeaderBlock response_headers; response_headers[":status"] = status; response_headers["masque-debug-info"] = error_details; @@ -72,24 +71,15 @@ std::unique_ptr<QuicBackendResponse> CreateBackendErrorResponse( } // namespace MasqueServerSession::MasqueServerSession( - MasqueMode masque_mode, - const QuicConfig& config, + MasqueMode masque_mode, const QuicConfig& config, const ParsedQuicVersionVector& supported_versions, - QuicConnection* connection, - QuicSession::Visitor* visitor, - Visitor* owner, - QuicEpollServer* epoll_server, - QuicCryptoServerStreamBase::Helper* helper, + QuicConnection* connection, QuicSession::Visitor* visitor, Visitor* owner, + QuicEpollServer* epoll_server, QuicCryptoServerStreamBase::Helper* helper, const QuicCryptoServerConfig* crypto_config, QuicCompressedCertsCache* compressed_certs_cache, MasqueServerBackend* masque_server_backend) - : QuicSimpleServerSession(config, - supported_versions, - connection, - visitor, - helper, - crypto_config, - compressed_certs_cache, + : QuicSimpleServerSession(config, supported_versions, connection, visitor, + helper, crypto_config, compressed_certs_cache, masque_server_backend), masque_server_backend_(masque_server_backend), owner_(owner), @@ -153,8 +143,7 @@ void MasqueServerSession::OnMessageLost(QuicMessageId message_id) { } void MasqueServerSession::OnConnectionClosed( - const QuicConnectionCloseFrame& frame, - ConnectionCloseSource source) { + const QuicConnectionCloseFrame& frame, ConnectionCloseSource source) { QuicSimpleServerSession::OnConnectionClosed(frame, source); QUIC_DLOG(INFO) << "Closing connection for " << connection_id(); masque_server_backend_->RemoveBackendClient(connection_id()); @@ -165,7 +154,7 @@ void MasqueServerSession::OnConnectionClosed( void MasqueServerSession::OnStreamClosed(QuicStreamId stream_id) { connect_udp_server_states_.remove_if( [stream_id](const ConnectUdpServerState& connect_udp) { - return connect_udp.stream_id() == stream_id; + return connect_udp.stream()->id() == stream_id; }); QuicSimpleServerSession::OnStreamClosed(stream_id); @@ -213,7 +202,7 @@ std::unique_ptr<QuicBackendResponse> MasqueServerSession::HandleMasqueRequest( QUIC_DLOG(ERROR) << "MASQUE request with bad method \"" << method << "\""; return CreateBackendErrorResponse("400", "Bad method"); } - absl::optional<QuicDatagramFlowId> flow_id = + absl::optional<QuicDatagramStreamId> flow_id = SpdyUtils::ParseDatagramFlowIdHeader(request_headers); if (!flow_id.has_value()) { QUIC_DLOG(ERROR) @@ -255,17 +244,36 @@ std::unique_ptr<QuicBackendResponse> MasqueServerSession::HandleMasqueRequest( QUIC_DLOG(ERROR) << "Socket creation failed"; return CreateBackendErrorResponse("500", "Socket creation failed"); } - QuicSocketAddress any_v6_address(QuicIpAddress::Any6(), 0); + QuicSocketAddress empty_address(QuicIpAddress::Any6(), 0); + if (target_server_address.host().IsIPv4()) { + empty_address = QuicSocketAddress(QuicIpAddress::Any4(), 0); + } QuicUdpSocketApi socket_api; - if (!socket_api.Bind(fd_wrapper.fd(), any_v6_address)) { + if (!socket_api.Bind(fd_wrapper.fd(), empty_address)) { QUIC_DLOG(ERROR) << "Socket bind failed"; return CreateBackendErrorResponse("500", "Socket bind failed"); } epoll_server_->RegisterFDForRead(fd_wrapper.fd(), this); - connect_udp_server_states_.emplace_back(ConnectUdpServerState( - *flow_id, request_handler->stream_id(), target_server_address, - fd_wrapper.extract_fd(), this)); + absl::optional<QuicDatagramContextId> context_id; + QuicSpdyStream* stream = static_cast<QuicSpdyStream*>( + GetActiveStream(request_handler->stream_id())); + if (stream == nullptr) { + QUIC_BUG(bad masque server stream type) + << "Unexpected stream type for stream ID " + << request_handler->stream_id(); + return CreateBackendErrorResponse("500", "Bad stream type"); + } + stream->RegisterHttp3DatagramFlowId(*flow_id); + connect_udp_server_states_.push_back( + ConnectUdpServerState(stream, context_id, target_server_address, + fd_wrapper.extract_fd(), this)); + + // TODO(b/181256914) remove this when we drop support for + // draft-ietf-masque-h3-datagram-00 in favor of later drafts. + Http3DatagramContextExtensions extensions; + stream->RegisterHttp3DatagramContextId(context_id, extensions, + &connect_udp_server_states_.back()); spdy::Http2HeaderBlock response_headers; response_headers[":status"] = "200"; @@ -326,8 +334,7 @@ void MasqueServerSession::HandlePacketFromServer( } void MasqueServerSession::OnRegistration(QuicEpollServer* /*eps*/, - QuicUdpSocketFd fd, - int event_mask) { + QuicUdpSocketFd fd, int event_mask) { QUIC_DVLOG(1) << "OnRegistration " << fd << " event_mask " << event_mask; } @@ -350,13 +357,12 @@ void MasqueServerSession::OnEvent(QuicUdpSocketFd fd, QuicEpollEvent* event) { << event->in_events << " on unknown fd " << fd; return; } - QuicDatagramFlowId flow_id = it->flow_id(); QuicSocketAddress expected_target_server_address = it->target_server_address(); QUICHE_DCHECK(expected_target_server_address.IsInitialized()); QUIC_DVLOG(1) << "Received readable event on fd " << fd << " (mask " - << event->in_events << ") flow_id " << flow_id << " server " - << expected_target_server_address; + << event->in_events << ") stream ID " << it->stream()->id() + << " server " << expected_target_server_address; QuicUdpSocketApi socket_api; BitMask64 packet_info_interested(QuicUdpPacketInfoBit::PEER_ADDRESS); char packet_buffer[kMaxIncomingPacketSize]; @@ -392,12 +398,14 @@ void MasqueServerSession::OnEvent(QuicUdpSocketFd fd, QuicEpollEvent* event) { return; } // The packet is valid, send it to the client in a DATAGRAM frame. - MessageStatus message_status = SendHttp3Datagram( - flow_id, absl::string_view(read_result.packet_buffer.buffer, - read_result.packet_buffer.buffer_len)); + MessageStatus message_status = it->stream()->SendHttp3Datagram( + it->context_id(), + absl::string_view(read_result.packet_buffer.buffer, + read_result.packet_buffer.buffer_len)); QUIC_DVLOG(1) << "Sent UDP packet from " << expected_target_server_address << " of length " << read_result.packet_buffer.buffer_len - << " with flow ID " << flow_id << " and got message status " + << " with stream ID " << it->stream()->id() + << " and got message status " << MessageStatusToString(message_status); } } @@ -417,24 +425,25 @@ std::string MasqueServerSession::Name() const { } MasqueServerSession::ConnectUdpServerState::ConnectUdpServerState( - QuicDatagramFlowId flow_id, - QuicStreamId stream_id, - const QuicSocketAddress& target_server_address, - QuicUdpSocketFd fd, + QuicSpdyStream* stream, absl::optional<QuicDatagramContextId> context_id, + const QuicSocketAddress& target_server_address, QuicUdpSocketFd fd, MasqueServerSession* masque_session) - : flow_id_(flow_id), - stream_id_(stream_id), + : stream_(stream), + context_id_(context_id), target_server_address_(target_server_address), fd_(fd), masque_session_(masque_session) { QUICHE_DCHECK_NE(fd_, kQuicInvalidSocketFd); QUICHE_DCHECK_NE(masque_session_, nullptr); - masque_session_->RegisterHttp3FlowId(this->flow_id(), this); + this->stream()->RegisterHttp3DatagramRegistrationVisitor(this); } MasqueServerSession::ConnectUdpServerState::~ConnectUdpServerState() { - if (flow_id_.has_value()) { - masque_session_->UnregisterHttp3FlowId(flow_id()); + if (stream() != nullptr) { + stream()->UnregisterHttp3DatagramRegistrationVisitor(); + if (context_registered_) { + stream()->UnregisterHttp3DatagramContextId(context_id()); + } } if (fd_ == kQuicInvalidSocketFd) { return; @@ -460,24 +469,29 @@ MasqueServerSession::ConnectUdpServerState::operator=( masque_session_->epoll_server()->UnregisterFD(fd_); socket_api.Destroy(fd_); } - flow_id_ = other.flow_id_; - stream_id_ = other.stream_id_; + stream_ = other.stream_; + other.stream_ = nullptr; + context_id_ = other.context_id_; target_server_address_ = other.target_server_address_; fd_ = other.fd_; masque_session_ = other.masque_session_; other.fd_ = kQuicInvalidSocketFd; - other.flow_id_.reset(); - if (flow_id_.has_value()) { - masque_session_->UnregisterHttp3FlowId(flow_id()); - masque_session_->RegisterHttp3FlowId(flow_id(), this); + context_registered_ = other.context_registered_; + other.context_registered_ = false; + if (stream() != nullptr) { + stream()->MoveHttp3DatagramRegistration(this); + if (context_registered_) { + stream()->MoveHttp3DatagramContextIdRegistration(context_id(), this); + } } return *this; } void MasqueServerSession::ConnectUdpServerState::OnHttp3Datagram( - QuicDatagramFlowId flow_id, + QuicStreamId stream_id, absl::optional<QuicDatagramContextId> context_id, absl::string_view payload) { - QUICHE_DCHECK_EQ(flow_id, this->flow_id()); + QUICHE_DCHECK_EQ(stream_id, stream()->id()); + QUICHE_DCHECK(context_id == context_id_); QuicUdpSocketApi socket_api; QuicUdpPacketInfo packet_info; packet_info.SetPeerAddress(target_server_address_); @@ -487,4 +501,58 @@ void MasqueServerSession::ConnectUdpServerState::OnHttp3Datagram( << target_server_address_ << " with result " << write_result; } +void MasqueServerSession::ConnectUdpServerState::OnContextReceived( + QuicStreamId stream_id, absl::optional<QuicDatagramContextId> context_id, + const Http3DatagramContextExtensions& /*extensions*/) { + if (stream_id != stream()->id()) { + QUIC_BUG(MASQUE server bad datagram context registration) + << "Registered stream ID " << stream_id << ", expected " + << stream()->id(); + return; + } + if (!context_received_) { + context_received_ = true; + context_id_ = context_id; + } + if (context_id != context_id_) { + QUIC_DLOG(INFO) << "Ignoring unexpected context ID " + << (context_id.has_value() ? context_id.value() : 0) + << " instead of " + << (context_id_.has_value() ? context_id_.value() : 0) + << " on stream ID " << stream()->id(); + return; + } + if (context_registered_) { + QUIC_BUG(MASQUE server double datagram context registration) + << "Try to re-register stream ID " << stream_id << " context ID " + << (context_id_.has_value() ? context_id_.value() : 0); + return; + } + context_registered_ = true; + Http3DatagramContextExtensions reply_extensions; + stream()->RegisterHttp3DatagramContextId(context_id_, reply_extensions, this); +} + +void MasqueServerSession::ConnectUdpServerState::OnContextClosed( + QuicStreamId stream_id, absl::optional<QuicDatagramContextId> context_id, + const Http3DatagramContextExtensions& /*extensions*/) { + if (stream_id != stream()->id()) { + QUIC_BUG(MASQUE server bad datagram context registration) + << "Closed context on stream ID " << stream_id << ", expected " + << stream()->id(); + return; + } + if (context_id != context_id_) { + QUIC_DLOG(INFO) << "Ignoring unexpected close of context ID " + << (context_id.has_value() ? context_id.value() : 0) + << " instead of " + << (context_id_.has_value() ? context_id_.value() : 0) + << " on stream ID " << stream()->id(); + return; + } + QUIC_DLOG(INFO) << "Received datagram context close on stream ID " + << stream()->id() << ", closing stream"; + masque_session_->ResetStream(stream()->id(), QUIC_STREAM_CANCELLED); +} + } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/masque/masque_server_session.h b/chromium/net/third_party/quiche/src/quic/masque/masque_server_session.h index 14b8293e9b1..1bec14c5464 100644 --- a/chromium/net/third_party/quiche/src/quic/masque/masque_server_session.h +++ b/chromium/net/third_party/quiche/src/quic/masque/masque_server_session.h @@ -38,14 +38,10 @@ class QUIC_NO_EXPORT MasqueServerSession }; explicit MasqueServerSession( - MasqueMode masque_mode, - const QuicConfig& config, + MasqueMode masque_mode, const QuicConfig& config, const ParsedQuicVersionVector& supported_versions, - QuicConnection* connection, - QuicSession::Visitor* visitor, - Visitor* owner, - QuicEpollServer* epoll_server, - QuicCryptoServerStreamBase::Helper* helper, + QuicConnection* connection, QuicSession::Visitor* visitor, Visitor* owner, + QuicEpollServer* epoll_server, QuicCryptoServerStreamBase::Helper* helper, const QuicCryptoServerConfig* crypto_config, QuicCompressedCertsCache* compressed_certs_cache, MasqueServerBackend* masque_server_backend); @@ -71,8 +67,7 @@ class QUIC_NO_EXPORT MasqueServerSession QuicSimpleServerBackend::RequestHandler* request_handler) override; // From QuicEpollCallbackInterface. - void OnRegistration(QuicEpollServer* eps, - QuicUdpSocketFd fd, + void OnRegistration(QuicEpollServer* eps, QuicUdpSocketFd fd, int event_mask) override; void OnModification(QuicUdpSocketFd fd, int event_mask) override; void OnEvent(QuicUdpSocketFd fd, QuicEpollEvent* event) override; @@ -88,15 +83,15 @@ class QUIC_NO_EXPORT MasqueServerSession private: // State that the MasqueServerSession keeps for each CONNECT-UDP request. class QUIC_NO_EXPORT ConnectUdpServerState - : public QuicSpdySession::Http3DatagramVisitor { + : public QuicSpdyStream::Http3DatagramRegistrationVisitor, + public QuicSpdyStream::Http3DatagramVisitor { public: // ConnectUdpServerState takes ownership of |fd|. It will unregister it // from |epoll_server| and close the file descriptor when destructed. explicit ConnectUdpServerState( - QuicDatagramFlowId flow_id, - QuicStreamId stream_id, - const QuicSocketAddress& target_server_address, - QuicUdpSocketFd fd, + QuicSpdyStream* stream, + absl::optional<QuicDatagramContextId> context_id, + const QuicSocketAddress& target_server_address, QuicUdpSocketFd fd, MasqueServerSession* masque_session); ~ConnectUdpServerState(); @@ -107,28 +102,42 @@ class QUIC_NO_EXPORT MasqueServerSession ConnectUdpServerState& operator=(const ConnectUdpServerState&) = delete; ConnectUdpServerState& operator=(ConnectUdpServerState&&); - QuicDatagramFlowId flow_id() const { - QUICHE_DCHECK(flow_id_.has_value()); - return *flow_id_; + QuicSpdyStream* stream() const { return stream_; } + absl::optional<QuicDatagramContextId> context_id() const { + return context_id_; } - QuicStreamId stream_id() const { return stream_id_; } const QuicSocketAddress& target_server_address() const { return target_server_address_; } QuicUdpSocketFd fd() const { return fd_; } - // From QuicSpdySession::Http3DatagramVisitor. - void OnHttp3Datagram(QuicDatagramFlowId flow_id, + // From QuicSpdyStream::Http3DatagramVisitor. + void OnHttp3Datagram(QuicStreamId stream_id, + absl::optional<QuicDatagramContextId> context_id, absl::string_view payload) override; + // From QuicSpdyStream::Http3DatagramRegistrationVisitor. + void OnContextReceived( + QuicStreamId stream_id, + absl::optional<QuicDatagramContextId> context_id, + const Http3DatagramContextExtensions& extensions) override; + void OnContextClosed( + QuicStreamId stream_id, + absl::optional<QuicDatagramContextId> context_id, + const Http3DatagramContextExtensions& extensions) override; + private: - absl::optional<QuicDatagramFlowId> flow_id_; - QuicStreamId stream_id_; + QuicSpdyStream* stream_; + absl::optional<QuicDatagramContextId> context_id_; QuicSocketAddress target_server_address_; - QuicUdpSocketFd fd_; // Owned. + QuicUdpSocketFd fd_; // Owned. MasqueServerSession* masque_session_; // Unowned. + bool context_received_ = false; + bool context_registered_ = false; }; + bool ShouldNegotiateHttp3Datagram() override { return true; } + MasqueServerBackend* masque_server_backend_; // Unowned. Visitor* owner_; // Unowned. QuicEpollServer* epoll_server_; // Unowned. diff --git a/chromium/net/third_party/quiche/src/quic/platform/api/quic_containers.h b/chromium/net/third_party/quiche/src/quic/platform/api/quic_containers.h index f7fd922f594..eb16bfa18c3 100644 --- a/chromium/net/third_party/quiche/src/quic/platform/api/quic_containers.h +++ b/chromium/net/third_party/quiche/src/quic/platform/api/quic_containers.h @@ -7,25 +7,15 @@ #include "net/quic/platform/impl/quic_containers_impl.h" -#include "absl/hash/hash.h" - namespace quic { -// A map which offers insertion-ordered iteration. -template <typename Key, typename Value, typename Hash = absl::Hash<Key>> -using QuicLinkedHashMap = QuicLinkedHashMapImpl<Key, Value, Hash>; - -// A vector optimized for small sizes. Provides the same APIs as a std::vector. -template <typename T, size_t N, typename A = std::allocator<T>> -using QuicInlinedVector = QuicInlinedVectorImpl<T, N, A>; - -// An ordered set of values. +// An ordered container optimized for small sets. +// An implementation with O(n) mutations might be chosen +// in case it has better memory usage and/or faster access. // // DOES NOT GUARANTEE POINTER OR ITERATOR STABILITY! -template <typename Key, - typename Compare = std::less<Key>, - typename Rep = std::vector<Key>> -using QuicOrderedSet = QuicOrderedSetImpl<Key, Compare, Rep>; +template <typename Key, typename Compare = std::less<Key>> +using QuicSmallOrderedSet = QuicSmallOrderedSetImpl<Key, Compare>; } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/platform/api/quic_containers_test.cc b/chromium/net/third_party/quiche/src/quic/platform/api/quic_containers_test.cc deleted file mode 100644 index bc255d36dcf..00000000000 --- a/chromium/net/third_party/quiche/src/quic/platform/api/quic_containers_test.cc +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "quic/platform/api/quic_containers.h" - -#include "quic/platform/api/quic_test.h" - -using ::testing::ElementsAre; - -namespace quic { -namespace test { -namespace { - -TEST(QuicInlinedVectorTest, Swap) { - { - // Inline to inline. - QuicInlinedVector<int, 2> self({1, 2}); - QuicInlinedVector<int, 2> other({3}); - - self.swap(other); - - EXPECT_THAT(self, ElementsAre(3)); - EXPECT_THAT(other, ElementsAre(1, 2)); - } - - { - // Inline to out-of-line. - QuicInlinedVector<int, 2> self({1, 2}); - QuicInlinedVector<int, 2> other({3, 4, 5, 6}); - - self.swap(other); - - EXPECT_THAT(self, ElementsAre(3, 4, 5, 6)); - EXPECT_THAT(other, ElementsAre(1, 2)); - } - - { - // Out-of-line to inline. - QuicInlinedVector<int, 2> self({1, 2, 3}); - QuicInlinedVector<int, 2> other({4, 5}); - - self.swap(other); - - EXPECT_THAT(self, ElementsAre(4, 5)); - EXPECT_THAT(other, ElementsAre(1, 2, 3)); - } - - { - // Out-of-line to Out-of-line. - QuicInlinedVector<int, 2> self({1, 2, 3}); - QuicInlinedVector<int, 2> other({4, 5, 6, 7}); - - self.swap(other); - - EXPECT_THAT(self, ElementsAre(4, 5, 6, 7)); - EXPECT_THAT(other, ElementsAre(1, 2, 3)); - } -} - -} // namespace -} // namespace test -} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/platform/api/quic_estimate_memory_usage.h b/chromium/net/third_party/quiche/src/quic/platform/api/quic_estimate_memory_usage.h deleted file mode 100644 index a5a093b9f1c..00000000000 --- a/chromium/net/third_party/quiche/src/quic/platform/api/quic_estimate_memory_usage.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef QUICHE_QUIC_PLATFORM_API_QUIC_ESTIMATE_MEMORY_USAGE_H_ -#define QUICHE_QUIC_PLATFORM_API_QUIC_ESTIMATE_MEMORY_USAGE_H_ - -#include <cstddef> - -#include "quiche_platform_impl/quiche_estimate_memory_usage_impl.h" - -namespace quic { - -template <class T> -size_t QuicEstimateMemoryUsage(const T& object) { - return quiche::QuicheEstimateMemoryUsageImpl(object); -} - -} // namespace quic - -#endif // QUICHE_QUIC_PLATFORM_API_QUIC_ESTIMATE_MEMORY_USAGE_H_ diff --git a/chromium/net/third_party/quiche/src/quic/platform/api/quic_file_utils.cc b/chromium/net/third_party/quiche/src/quic/platform/api/quic_file_utils.cc deleted file mode 100644 index 5cb310baf9f..00000000000 --- a/chromium/net/third_party/quiche/src/quic/platform/api/quic_file_utils.cc +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "quic/platform/api/quic_file_utils.h" - -#include "absl/strings/string_view.h" -#include "net/quic/platform/impl/quic_file_utils_impl.h" - -namespace quic { - -// Traverses the directory |dirname| and retuns all of the files -// it contains. -std::vector<std::string> ReadFileContents(const std::string& dirname) { - return ReadFileContentsImpl(dirname); -} - -// Reads the contents of |filename| as a string into |contents|. -void ReadFileContents(absl::string_view filename, std::string* contents) { - ReadFileContentsImpl(filename, contents); -} - -} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/platform/api/quic_file_utils.h b/chromium/net/third_party/quiche/src/quic/platform/api/quic_file_utils.h deleted file mode 100644 index a1f881f63a6..00000000000 --- a/chromium/net/third_party/quiche/src/quic/platform/api/quic_file_utils.h +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef QUICHE_QUIC_PLATFORM_API_QUIC_FILE_UTILS_H_ -#define QUICHE_QUIC_PLATFORM_API_QUIC_FILE_UTILS_H_ - -#include <string> -#include <vector> - -#include "absl/strings/string_view.h" -#include "quic/platform/api/quic_export.h" - -namespace quic { - -// Traverses the directory |dirname| and returns all of the files it contains. -QUIC_EXPORT_PRIVATE std::vector<std::string> ReadFileContents( - const std::string& dirname); - -// Reads the contents of |filename| as a string into |contents|. -QUIC_EXPORT_PRIVATE void ReadFileContents(absl::string_view filename, - std::string* contents); - -} // namespace quic - -#endif // QUICHE_QUIC_PLATFORM_API_QUIC_FILE_UTILS_H_ diff --git a/chromium/net/third_party/quiche/src/quic/platform/api/quic_hostname_utils_test.cc b/chromium/net/third_party/quiche/src/quic/platform/api/quic_hostname_utils_test.cc index 788cb2d5425..398f44a4a75 100644 --- a/chromium/net/third_party/quiche/src/quic/platform/api/quic_hostname_utils_test.cc +++ b/chromium/net/third_party/quiche/src/quic/platform/api/quic_hostname_utils_test.cc @@ -19,10 +19,7 @@ TEST_F(QuicHostnameUtilsTest, IsValidSNI) { // IP as SNI. EXPECT_FALSE(QuicHostnameUtils::IsValidSNI("192.168.0.1")); // SNI without any dot. - SetQuicReloadableFlag(quic_and_tls_allow_sni_without_dots, true); EXPECT_TRUE(QuicHostnameUtils::IsValidSNI("somedomain")); - SetQuicReloadableFlag(quic_and_tls_allow_sni_without_dots, false); - EXPECT_FALSE(QuicHostnameUtils::IsValidSNI("somedomain")); // Invalid by RFC2396 but unfortunately domains of this form exist. EXPECT_TRUE(QuicHostnameUtils::IsValidSNI("some_domain.com")); // An empty string must be invalid otherwise the QUIC client will try sending diff --git a/chromium/net/third_party/quiche/src/quic/platform/api/quic_map_util.h b/chromium/net/third_party/quiche/src/quic/platform/api/quic_map_util.h deleted file mode 100644 index 95daec82c30..00000000000 --- a/chromium/net/third_party/quiche/src/quic/platform/api/quic_map_util.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef QUICHE_QUIC_PLATFORM_API_QUIC_MAP_UTIL_H_ -#define QUICHE_QUIC_PLATFORM_API_QUIC_MAP_UTIL_H_ - -#include "net/quic/platform/impl/quic_map_util_impl.h" - -namespace quic { - -template <class Collection, class Key> -bool QuicContainsKey(const Collection& collection, const Key& key) { - return QuicContainsKeyImpl(collection, key); -} - -template <typename Collection, typename Value> -bool QuicContainsValue(const Collection& collection, const Value& value) { - return QuicContainsValueImpl(collection, value); -} - -} // namespace quic - -#endif // QUICHE_QUIC_PLATFORM_API_QUIC_MAP_UTIL_H_ diff --git a/chromium/net/third_party/quiche/src/quic/platform/api/quic_mem_slice.h b/chromium/net/third_party/quiche/src/quic/platform/api/quic_mem_slice.h index 32e64575b25..adc03d45d8c 100644 --- a/chromium/net/third_party/quiche/src/quic/platform/api/quic_mem_slice.h +++ b/chromium/net/third_party/quiche/src/quic/platform/api/quic_mem_slice.h @@ -6,6 +6,8 @@ #define QUICHE_QUIC_PLATFORM_API_QUIC_MEM_SLICE_H_ #include <memory> + +#include "absl/strings/string_view.h" #include "quic/platform/api/quic_export.h" #include "net/quic/platform/impl/quic_mem_slice_impl.h" @@ -31,9 +33,20 @@ class QUIC_EXPORT_PRIVATE QuicMemSlice { // Constructs a QuicMemSlice that takes ownership of |buffer|. |length| must // not be zero. To construct an empty QuicMemSlice, use the zero-argument // constructor instead. + // TODO(vasilvv): switch all users to QuicBuffer version, and make this + // private. QuicMemSlice(QuicUniqueBufferPtr buffer, size_t length) : impl_(std::move(buffer), length) {} + // Constructs a QuicMemSlice that takes ownership of |buffer|. The length of + // the |buffer| must not be zero. To construct an empty QuicMemSlice, use the + // zero-argument constructor instead. + explicit QuicMemSlice(QuicBuffer buffer) : QuicMemSlice() { + // Store the size of the buffer *before* calling buffer.Release(). + const size_t size = buffer.size(); + *this = QuicMemSlice(buffer.Release(), size); + } + // Constructs a QuicMemSlice that takes ownership of |buffer| allocated on // heap. |length| must not be zero. QuicMemSlice(std::unique_ptr<char[]> buffer, size_t length) @@ -61,6 +74,10 @@ class QUIC_EXPORT_PRIVATE QuicMemSlice { const char* data() const { return impl_.data(); } // Returns the length of underlying data buffer. size_t length() const { return impl_.length(); } + // Returns the representation of the underlying data as a string view. + absl::string_view AsStringView() const { + return absl::string_view(data(), length()); + } bool empty() const { return impl_.empty(); } diff --git a/chromium/net/third_party/quiche/src/quic/platform/api/quic_mem_slice_test.cc b/chromium/net/third_party/quiche/src/quic/platform/api/quic_mem_slice_test.cc index 380c462f880..44095dffa01 100644 --- a/chromium/net/third_party/quiche/src/quic/platform/api/quic_mem_slice_test.cc +++ b/chromium/net/third_party/quiche/src/quic/platform/api/quic_mem_slice_test.cc @@ -5,6 +5,9 @@ #include "quic/platform/api/quic_mem_slice.h" #include <memory> + +#include "absl/strings/string_view.h" +#include "quic/core/quic_buffer_allocator.h" #include "quic/core/quic_simple_buffer_allocator.h" #include "quic/platform/api/quic_test.h" @@ -56,6 +59,18 @@ TEST_F(QuicMemSliceTest, SliceAllocatedOnHeap) { EXPECT_EQ(moved.length(), used_length); } +TEST_F(QuicMemSliceTest, SliceFromBuffer) { + const absl::string_view kTestString = + "RFC 9000 Release Celebration Memorial Test String"; + auto buffer = QuicBuffer::Copy(&allocator_, kTestString); + QuicMemSlice slice(std::move(buffer)); + + EXPECT_EQ(buffer.data(), nullptr); // NOLINT(bugprone-use-after-move) + EXPECT_EQ(buffer.size(), 0u); + EXPECT_EQ(slice.AsStringView(), kTestString); + EXPECT_EQ(slice.length(), kTestString.length()); +} + } // namespace } // namespace test } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/qbone/bonnet/tun_device_controller.cc b/chromium/net/third_party/quiche/src/quic/qbone/bonnet/tun_device_controller.cc index c8a6517a4c3..dd996c5c55b 100644 --- a/chromium/net/third_party/quiche/src/quic/qbone/bonnet/tun_device_controller.cc +++ b/chromium/net/third_party/quiche/src/quic/qbone/bonnet/tun_device_controller.cc @@ -49,6 +49,10 @@ bool TunDeviceController::UpdateAddress(const IpRange& desired_range) { if (address_updated) { current_address_ = desired_address; + + for (const auto& cb : address_update_cbs_) { + cb(current_address_); + } } return address_updated; @@ -110,6 +114,19 @@ bool TunDeviceController::UpdateRoutes( return true; } +bool TunDeviceController::UpdateRoutesWithRetries( + const IpRange& desired_range, + const std::vector<IpRange>& desired_routes, + int retries) { + while (retries-- > 0) { + if (UpdateRoutes(desired_range, desired_routes)) { + return true; + } + absl::SleepFor(absl::Milliseconds(100)); + } + return false; +} + bool TunDeviceController::UpdateRules(IpRange desired_range) { if (!absl::GetFlag(FLAGS_qbone_tun_device_replace_default_routing_rules)) { return true; @@ -148,4 +165,9 @@ QuicIpAddress TunDeviceController::current_address() { return current_address_; } +void TunDeviceController::RegisterAddressUpdateCallback( + const std::function<void(QuicIpAddress)>& cb) { + address_update_cbs_.push_back(cb); +} + } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/qbone/bonnet/tun_device_controller.h b/chromium/net/third_party/quiche/src/quic/qbone/bonnet/tun_device_controller.h index 6854521014f..612e98f5222 100644 --- a/chromium/net/third_party/quiche/src/quic/qbone/bonnet/tun_device_controller.h +++ b/chromium/net/third_party/quiche/src/quic/qbone/bonnet/tun_device_controller.h @@ -39,6 +39,20 @@ class TunDeviceController { virtual bool UpdateRoutes(const IpRange& desired_range, const std::vector<IpRange>& desired_routes); + // Same as UpdateRoutes, but will wait and retry up to the number of times + // given by |retries| before giving up. This is an unpleasant workaround to + // deal with older kernels that aren't always able to set a route with a + // source address immediately after adding the address to the interface. + // + // TODO(b/179430548): Remove this once we've root-caused the underlying issue. + virtual bool UpdateRoutesWithRetries( + const IpRange& desired_range, + const std::vector<IpRange>& desired_routes, + int retries); + + virtual void RegisterAddressUpdateCallback( + const std::function<void(QuicIpAddress)>& cb); + virtual QuicIpAddress current_address(); private: @@ -51,6 +65,8 @@ class TunDeviceController { NetlinkInterface* netlink_; QuicIpAddress current_address_; + + std::vector<std::function<void(QuicIpAddress)>> address_update_cbs_; }; } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/qbone/bonnet/tun_device_controller_test.cc b/chromium/net/third_party/quiche/src/quic/qbone/bonnet/tun_device_controller_test.cc index 73a7abc2864..53e5b3c14c2 100644 --- a/chromium/net/third_party/quiche/src/quic/qbone/bonnet/tun_device_controller_test.cc +++ b/chromium/net/third_party/quiche/src/quic/qbone/bonnet/tun_device_controller_test.cc @@ -44,8 +44,10 @@ class TunDeviceControllerTest : public QuicTest { public: TunDeviceControllerTest() : controller_(kIfname, true, &netlink_), - link_local_range_( - *QboneConstants::TerminatorLocalAddressRange()) {} + link_local_range_(*QboneConstants::TerminatorLocalAddressRange()) { + controller_.RegisterAddressUpdateCallback( + [this](QuicIpAddress address) { notified_address_ = address; }); + } protected: void ExpectLinkInfo(const std::string& interface_name, int ifindex) { @@ -60,6 +62,7 @@ class TunDeviceControllerTest : public QuicTest { MockNetlink netlink_; TunDeviceController controller_; + QuicIpAddress notified_address_; IpRange link_local_range_; }; @@ -77,6 +80,7 @@ TEST_F(TunDeviceControllerTest, AddressAppliedWhenNoneExisted) { .WillOnce(Return(true)); EXPECT_TRUE(controller_.UpdateAddress(kIpRange)); + EXPECT_THAT(notified_address_, Eq(kIpRange.FirstAddressInRange())); } TEST_F(TunDeviceControllerTest, OldAddressesAreRemoved) { @@ -110,6 +114,7 @@ TEST_F(TunDeviceControllerTest, OldAddressesAreRemoved) { .WillOnce(Return(true)); EXPECT_TRUE(controller_.UpdateAddress(kIpRange)); + EXPECT_THAT(notified_address_, Eq(kIpRange.FirstAddressInRange())); } TEST_F(TunDeviceControllerTest, UpdateRoutesRemovedOldRoutes) { diff --git a/chromium/net/third_party/quiche/src/quic/qbone/platform/netlink.cc b/chromium/net/third_party/quiche/src/quic/qbone/platform/netlink.cc index 52196239d13..985632ab72c 100644 --- a/chromium/net/third_party/quiche/src/quic/qbone/platform/netlink.cc +++ b/chromium/net/third_party/quiche/src/quic/qbone/platform/netlink.cc @@ -12,7 +12,6 @@ #include "quic/core/crypto/quic_random.h" #include "quic/platform/api/quic_ip_address.h" #include "quic/platform/api/quic_logging.h" -#include "net/quic/platform/impl/quic_ip_address_impl.h" #include "quic/qbone/platform/rtnetlink_message.h" namespace quic { diff --git a/chromium/net/third_party/quiche/src/quic/qbone/qbone_control_stream.cc b/chromium/net/third_party/quiche/src/quic/qbone/qbone_control_stream.cc index 944257f7af2..7303b06091a 100644 --- a/chromium/net/third_party/quiche/src/quic/qbone/qbone_control_stream.cc +++ b/chromium/net/third_party/quiche/src/quic/qbone/qbone_control_stream.cc @@ -4,6 +4,8 @@ #include "quic/qbone/qbone_control_stream.h" +#include <cstdint> +#include <limits> #include "absl/strings/string_view.h" #include "quic/core/quic_session.h" #include "quic/platform/api/quic_bug_tracker.h" @@ -51,10 +53,10 @@ bool QboneControlStreamBase::SendMessage(const proto2::Message& proto) { QUIC_BUG(quic_bug_11023_1) << "Failed to serialize QboneControlRequest"; return false; } - if (tmp.size() > kuint16max) { + if (tmp.size() > std::numeric_limits<uint16_t>::max()) { QUIC_BUG(quic_bug_11023_2) << "QboneControlRequest too large: " << tmp.size() << " > " - << kuint16max; + << std::numeric_limits<uint16_t>::max(); return false; } uint16_t size = tmp.size(); diff --git a/chromium/net/third_party/quiche/src/quic/qbone/qbone_packet_exchanger.cc b/chromium/net/third_party/quiche/src/quic/qbone/qbone_packet_exchanger.cc index 5ee52e1e065..f8acb4f6a86 100644 --- a/chromium/net/third_party/quiche/src/quic/qbone/qbone_packet_exchanger.cc +++ b/chromium/net/third_party/quiche/src/quic/qbone/qbone_packet_exchanger.cc @@ -14,7 +14,7 @@ bool QbonePacketExchanger::ReadAndDeliverPacket( std::string error; std::unique_ptr<QuicData> packet = ReadPacket(&blocked, &error); if (packet == nullptr) { - if (!blocked) { + if (!blocked && visitor_) { visitor_->OnReadError(error); } return false; @@ -31,11 +31,14 @@ void QbonePacketExchanger::WritePacketToNetwork(const char* packet, if (WritePacket(packet, size, &blocked, &error)) { return; } - if (!blocked) { - visitor_->OnWriteError(error); - return; + if (blocked) { + write_blocked_ = true; + } else { + QUIC_LOG_EVERY_N_SEC(ERROR, 60) << "Packet write failed: " << error; + if (visitor_) { + visitor_->OnWriteError(error); + } } - write_blocked_ = true; } // Drop the packet on the floor if the queue if full. @@ -58,7 +61,7 @@ void QbonePacketExchanger::SetWritable() { packet_queue_.front()->length(), &blocked, &error)) { packet_queue_.pop_front(); } else { - if (!blocked) { + if (!blocked && visitor_) { visitor_->OnWriteError(error); } write_blocked_ = blocked; diff --git a/chromium/net/third_party/quiche/src/quic/qbone/qbone_packet_exchanger_test.cc b/chromium/net/third_party/quiche/src/quic/qbone/qbone_packet_exchanger_test.cc index a2b502fa26f..5db87536aba 100644 --- a/chromium/net/third_party/quiche/src/quic/qbone/qbone_packet_exchanger_test.cc +++ b/chromium/net/third_party/quiche/src/quic/qbone/qbone_packet_exchanger_test.cc @@ -251,5 +251,21 @@ TEST(QbonePacketExchangerTest, WriteErrorsGetNotified) { ASSERT_TRUE(exchanger.packets_written().empty()); } +TEST(QbonePacketExchangerTest, NullVisitorDoesntCrash) { + FakeQbonePacketExchanger exchanger(nullptr, kMaxPendingPackets); + MockQboneClient client; + std::string packet = "data"; + + // Force read error. + std::string io_error = "I/O error"; + exchanger.SetReadError(io_error); + EXPECT_FALSE(exchanger.ReadAndDeliverPacket(&client)); + + // Force write error + exchanger.ForceWriteFailure(false, io_error); + exchanger.WritePacketToNetwork(packet.data(), packet.length()); + EXPECT_TRUE(exchanger.packets_written().empty()); +} + } // namespace } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/qbone/qbone_packet_processor.cc b/chromium/net/third_party/quiche/src/quic/qbone/qbone_packet_processor.cc index 7b45ed1050b..6e83e760f3b 100644 --- a/chromium/net/third_party/quiche/src/quic/qbone/qbone_packet_processor.cc +++ b/chromium/net/third_party/quiche/src/quic/qbone/qbone_packet_processor.cc @@ -103,6 +103,10 @@ void QbonePacketProcessor::ProcessPacket(std::string* packet, SendTcpReset(*packet, direction); stats_->OnPacketDroppedWithTcpReset(direction); break; + case ProcessingResult::TCP_RESET: + SendTcpReset(*packet, direction); + stats_->OnPacketDroppedWithTcpReset(direction); + break; } } diff --git a/chromium/net/third_party/quiche/src/quic/qbone/qbone_packet_processor.h b/chromium/net/third_party/quiche/src/quic/qbone/qbone_packet_processor.h index c85280fe18f..8c2375a288c 100644 --- a/chromium/net/third_party/quiche/src/quic/qbone/qbone_packet_processor.h +++ b/chromium/net/third_party/quiche/src/quic/qbone/qbone_packet_processor.h @@ -46,6 +46,8 @@ class QbonePacketProcessor { // RST requires information from the current connection state to be // well-formed. ICMP_AND_TCP_RESET = 4, + // Send a TCP RST. + TCP_RESET = 5, }; class OutputInterface { diff --git a/chromium/net/third_party/quiche/src/quic/qbone/qbone_session_base.cc b/chromium/net/third_party/quiche/src/quic/qbone/qbone_session_base.cc index e984196faf0..2f2f31e3239 100644 --- a/chromium/net/third_party/quiche/src/quic/qbone/qbone_session_base.cc +++ b/chromium/net/third_party/quiche/src/quic/qbone/qbone_session_base.cc @@ -140,11 +140,9 @@ void QboneSessionBase::SendPacketToPeer(absl::string_view packet) { } if (send_packets_as_messages_) { - QuicUniqueBufferPtr buffer = MakeUniqueBuffer( - connection()->helper()->GetStreamSendBufferAllocator(), packet.size()); - memcpy(buffer.get(), packet.data(), packet.size()); - QuicMemSlice slice(std::move(buffer), packet.size()); - switch (SendMessage(QuicMemSliceSpan(&slice), /*flush=*/true).status) { + QuicMemSlice slice(QuicBuffer::Copy( + connection()->helper()->GetStreamSendBufferAllocator(), packet)); + switch (SendMessage(absl::MakeSpan(&slice, 1), /*flush=*/true).status) { case MESSAGE_STATUS_SUCCESS: break; case MESSAGE_STATUS_TOO_LARGE: { diff --git a/chromium/net/third_party/quiche/src/quic/qbone/qbone_session_test.cc b/chromium/net/third_party/quiche/src/quic/qbone/qbone_session_test.cc index fb9a288cbeb..9887f79a46e 100644 --- a/chromium/net/third_party/quiche/src/quic/qbone/qbone_session_test.cc +++ b/chromium/net/third_party/quiche/src/quic/qbone/qbone_session_test.cc @@ -117,6 +117,14 @@ class IndirectionProofSource : public ProofSource { std::move(callback)); } + absl::InlinedVector<uint16_t, 8> SupportedTlsSignatureAlgorithms() + const override { + if (!proof_source_) { + return {}; + } + return proof_source_->SupportedTlsSignatureAlgorithms(); + } + TicketCrypter* GetTicketCrypter() override { return nullptr; } private: diff --git a/chromium/net/third_party/quiche/src/quic/qbone/qbone_stream_test.cc b/chromium/net/third_party/quiche/src/quic/qbone/qbone_stream_test.cc index bcbc5a514e8..fc2ce819d90 100644 --- a/chromium/net/third_party/quiche/src/quic/qbone/qbone_stream_test.cc +++ b/chromium/net/third_party/quiche/src/quic/qbone/qbone_stream_test.cc @@ -40,12 +40,10 @@ class MockQuicSession : public QboneSessionBase { ~MockQuicSession() override {} // Writes outgoing data from QuicStream to a string. - QuicConsumedData WritevData(QuicStreamId id, - size_t write_length, - QuicStreamOffset offset, - StreamSendingState state, + QuicConsumedData WritevData(QuicStreamId id, size_t write_length, + QuicStreamOffset offset, StreamSendingState state, TransmissionType type, - absl::optional<EncryptionLevel> level) override { + EncryptionLevel level) override { if (!writable_) { return QuicConsumedData(0, false); } diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/crypto_test_utils.cc b/chromium/net/third_party/quiche/src/quic/test_tools/crypto_test_utils.cc index 00264e59a09..d28b4faff85 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/crypto_test_utils.cc +++ b/chromium/net/third_party/quiche/src/quic/test_tools/crypto_test_utils.cc @@ -743,6 +743,12 @@ void MovePackets(PacketSavingConnection* source_conn, size_t index = *inout_packet_index; for (; index < source_conn->encrypted_packets_.size(); index++) { + if (!dest_conn->connected()) { + QUIC_LOG(INFO) + << "Destination connection disconnected. Skipping packet at index " + << index; + continue; + } // In order to properly test the code we need to perform encryption and // decryption so that the crypters latch when expected. The crypters are in // |dest_conn|, but we don't want to try and use them there. Instead we swap diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/failing_proof_source.h b/chromium/net/third_party/quiche/src/quic/test_tools/failing_proof_source.h index 4f771a33ebc..447b77066bd 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/failing_proof_source.h +++ b/chromium/net/third_party/quiche/src/quic/test_tools/failing_proof_source.h @@ -34,6 +34,11 @@ class FailingProofSource : public ProofSource { absl::string_view in, std::unique_ptr<SignatureCallback> callback) override; + absl::InlinedVector<uint16_t, 8> SupportedTlsSignatureAlgorithms() + const override { + return {}; + } + TicketCrypter* GetTicketCrypter() override { return nullptr; } }; diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/fake_proof_source.cc b/chromium/net/third_party/quiche/src/quic/test_tools/fake_proof_source.cc index bc75678872f..1109d659fac 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/fake_proof_source.cc +++ b/chromium/net/third_party/quiche/src/quic/test_tools/fake_proof_source.cc @@ -123,6 +123,11 @@ void FakeProofSource::ComputeTlsSignature( std::move(callback), delegate_.get())); } +absl::InlinedVector<uint16_t, 8> +FakeProofSource::SupportedTlsSignatureAlgorithms() const { + return delegate_->SupportedTlsSignatureAlgorithms(); +} + ProofSource::TicketCrypter* FakeProofSource::GetTicketCrypter() { if (ticket_crypter_) { return ticket_crypter_.get(); diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/fake_proof_source.h b/chromium/net/third_party/quiche/src/quic/test_tools/fake_proof_source.h index 077f34d074f..c088d43a9bb 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/fake_proof_source.h +++ b/chromium/net/third_party/quiche/src/quic/test_tools/fake_proof_source.h @@ -52,6 +52,8 @@ class FakeProofSource : public ProofSource { uint16_t signature_algorithm, absl::string_view in, std::unique_ptr<ProofSource::SignatureCallback> callback) override; + absl::InlinedVector<uint16_t, 8> SupportedTlsSignatureAlgorithms() + const override; TicketCrypter* GetTicketCrypter() override; // Sets the TicketCrypter to use. If nullptr, the TicketCrypter from diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/fake_proof_source_handle.cc b/chromium/net/third_party/quiche/src/quic/test_tools/fake_proof_source_handle.cc index b1fedf8c161..f34247e9bae 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/fake_proof_source_handle.cc +++ b/chromium/net/third_party/quiche/src/quic/test_tools/fake_proof_source_handle.cc @@ -63,9 +63,10 @@ FakeProofSourceHandle::FakeProofSourceHandle( select_cert_action_(select_cert_action), compute_signature_action_(compute_signature_action) {} -void FakeProofSourceHandle::CancelPendingOperation() { +void FakeProofSourceHandle::CloseHandle() { select_cert_op_.reset(); compute_signature_op_.reset(); + closed_ = true; } QuicAsyncStatus FakeProofSourceHandle::SelectCertificate( @@ -77,10 +78,12 @@ QuicAsyncStatus FakeProofSourceHandle::SelectCertificate( const std::string& alpn, absl::optional<std::string> alps, const std::vector<uint8_t>& quic_transport_params, - const absl::optional<std::vector<uint8_t>>& early_data_context) { + const absl::optional<std::vector<uint8_t>>& early_data_context, + const QuicSSLConfig& ssl_config) { + QUICHE_CHECK(!closed_); all_select_cert_args_.push_back(SelectCertArgs( server_address, client_address, ssl_capabilities, hostname, client_hello, - alpn, alps, quic_transport_params, early_data_context)); + alpn, alps, quic_transport_params, early_data_context, ssl_config)); if (select_cert_action_ == Action::DELEGATE_ASYNC || select_cert_action_ == Action::FAIL_ASYNC) { @@ -90,7 +93,8 @@ QuicAsyncStatus FakeProofSourceHandle::SelectCertificate( } else if (select_cert_action_ == Action::FAIL_SYNC) { callback()->OnSelectCertificateDone( /*ok=*/false, - /*is_sync=*/true, nullptr, /*handshake_hints=*/absl::string_view()); + /*is_sync=*/true, nullptr, /*handshake_hints=*/absl::string_view(), + /*ticket_encryption_key=*/absl::string_view()); return QUIC_FAILURE; } @@ -99,8 +103,10 @@ QuicAsyncStatus FakeProofSourceHandle::SelectCertificate( delegate_->GetCertChain(server_address, client_address, hostname); bool ok = chain && !chain->certs.empty(); - callback_->OnSelectCertificateDone(ok, /*is_sync=*/true, chain.get(), - /*handshake_hints=*/absl::string_view()); + callback_->OnSelectCertificateDone( + ok, /*is_sync=*/true, chain.get(), + /*handshake_hints=*/absl::string_view(), + /*ticket_encryption_key=*/absl::string_view()); return ok ? QUIC_SUCCESS : QUIC_FAILURE; } @@ -111,6 +117,7 @@ QuicAsyncStatus FakeProofSourceHandle::ComputeSignature( uint16_t signature_algorithm, absl::string_view in, size_t max_signature_size) { + QUICHE_CHECK(!closed_); all_compute_signature_args_.push_back( ComputeSignatureArgs(server_address, client_address, hostname, signature_algorithm, in, max_signature_size)); @@ -171,16 +178,20 @@ FakeProofSourceHandle::SelectCertOperation::SelectCertOperation( void FakeProofSourceHandle::SelectCertOperation::Run() { if (action_ == Action::FAIL_ASYNC) { - callback_->OnSelectCertificateDone(/*ok=*/false, - /*is_sync=*/false, nullptr, - /*handshake_hints=*/absl::string_view()); + callback_->OnSelectCertificateDone( + /*ok=*/false, + /*is_sync=*/false, nullptr, + /*handshake_hints=*/absl::string_view(), + /*ticket_encryption_key=*/absl::string_view()); } else if (action_ == Action::DELEGATE_ASYNC) { QuicReferenceCountedPointer<ProofSource::Chain> chain = delegate_->GetCertChain(args_.server_address, args_.client_address, args_.hostname); bool ok = chain && !chain->certs.empty(); - callback_->OnSelectCertificateDone(ok, /*is_sync=*/false, chain.get(), - /*handshake_hints=*/absl::string_view()); + callback_->OnSelectCertificateDone( + ok, /*is_sync=*/false, chain.get(), + /*handshake_hints=*/absl::string_view(), + /*ticket_encryption_key=*/absl::string_view()); } else { QUIC_BUG(quic_bug_10139_1) << "Unexpected action: " << static_cast<int>(action_); diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/fake_proof_source_handle.h b/chromium/net/third_party/quiche/src/quic/test_tools/fake_proof_source_handle.h index d2b991896f8..3d038a4d58b 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/fake_proof_source_handle.h +++ b/chromium/net/third_party/quiche/src/quic/test_tools/fake_proof_source_handle.h @@ -35,7 +35,7 @@ class FakeProofSourceHandle : public ProofSourceHandle { ~FakeProofSourceHandle() override = default; - void CancelPendingOperation() override; + void CloseHandle() override; QuicAsyncStatus SelectCertificate( const QuicSocketAddress& server_address, @@ -46,7 +46,8 @@ class FakeProofSourceHandle : public ProofSourceHandle { const std::string& alpn, absl::optional<std::string> alps, const std::vector<uint8_t>& quic_transport_params, - const absl::optional<std::vector<uint8_t>>& early_data_context) override; + const absl::optional<std::vector<uint8_t>>& early_data_context, + const QuicSSLConfig& ssl_config) override; QuicAsyncStatus ComputeSignature(const QuicSocketAddress& server_address, const QuicSocketAddress& client_address, @@ -70,7 +71,8 @@ class FakeProofSourceHandle : public ProofSourceHandle { std::string alpn, absl::optional<std::string> alps, std::vector<uint8_t> quic_transport_params, - absl::optional<std::vector<uint8_t>> early_data_context) + absl::optional<std::vector<uint8_t>> early_data_context, + QuicSSLConfig ssl_config) : server_address(server_address), client_address(client_address), ssl_capabilities(ssl_capabilities), @@ -79,7 +81,8 @@ class FakeProofSourceHandle : public ProofSourceHandle { alpn(alpn), alps(alps), quic_transport_params(quic_transport_params), - early_data_context(early_data_context) {} + early_data_context(early_data_context), + ssl_config(ssl_config) {} QuicSocketAddress server_address; QuicSocketAddress client_address; @@ -90,6 +93,7 @@ class FakeProofSourceHandle : public ProofSourceHandle { absl::optional<std::string> alps; std::vector<uint8_t> quic_transport_params; absl::optional<std::vector<uint8_t>> early_data_context; + QuicSSLConfig ssl_config; }; struct ComputeSignatureArgs { @@ -171,6 +175,7 @@ class FakeProofSourceHandle : public ProofSourceHandle { private: int NumPendingOperations() const; + bool closed_ = false; ProofSource* delegate_; ProofSourceHandleCallback* callback_; // Action for the next select cert operation. diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/packet_dropping_test_writer.cc b/chromium/net/third_party/quiche/src/quic/test_tools/packet_dropping_test_writer.cc index 35cc9785209..76dd778734c 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/packet_dropping_test_writer.cc +++ b/chromium/net/third_party/quiche/src/quic/test_tools/packet_dropping_test_writer.cc @@ -68,7 +68,14 @@ PacketDroppingTestWriter::PacketDroppingTestWriter() simple_random_.set_seed(seed); } -PacketDroppingTestWriter::~PacketDroppingTestWriter() = default; +PacketDroppingTestWriter::~PacketDroppingTestWriter() { + if (write_unblocked_alarm_ != nullptr) { + write_unblocked_alarm_->PermanentCancel(); + } + if (delay_alarm_ != nullptr) { + delay_alarm_->PermanentCancel(); + } +} void PacketDroppingTestWriter::Initialize( QuicConnectionHelperInterface* helper, diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/qpack/qpack_offline_decoder.cc b/chromium/net/third_party/quiche/src/quic/test_tools/qpack/qpack_offline_decoder.cc index 0f681fef83a..a7ea1be114b 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/qpack/qpack_offline_decoder.cc +++ b/chromium/net/third_party/quiche/src/quic/test_tools/qpack/qpack_offline_decoder.cc @@ -35,9 +35,9 @@ #include "absl/strings/str_split.h" #include "absl/strings/string_view.h" #include "quic/core/quic_types.h" -#include "quic/platform/api/quic_file_utils.h" #include "quic/platform/api/quic_logging.h" #include "quic/test_tools/qpack/qpack_test_utils.h" +#include "common/platform/api/quiche_file_utils.h" #include "common/quiche_endian.h" namespace quic { @@ -69,8 +69,7 @@ bool QpackOfflineDecoder::DecodeAndVerifyOfflineData( } void QpackOfflineDecoder::OnEncoderStreamError( - QuicErrorCode error_code, - absl::string_view error_message) { + QuicErrorCode error_code, absl::string_view error_message) { QUIC_LOG(ERROR) << "Encoder stream error: " << QuicErrorCodeToString(error_code) << " " << error_message; encoder_stream_error_detected_ = true; @@ -87,12 +86,7 @@ bool QpackOfflineDecoder::ParseInputFilename(absl::string_view input_filename) { auto piece_it = pieces.rbegin(); // Acknowledgement mode: 1 for immediate, 0 for none. - bool immediate_acknowledgement = false; - if (*piece_it == "0") { - immediate_acknowledgement = false; - } else if (*piece_it == "1") { - immediate_acknowledgement = true; - } else { + if (*piece_it != "0" && *piece_it != "1") { QUIC_LOG(ERROR) << "Header acknowledgement field must be 0 or 1 in input filename " << input_filename; @@ -136,9 +130,10 @@ bool QpackOfflineDecoder::DecodeHeaderBlocksFromFile( absl::string_view input_filename) { // Store data in |input_data_storage|; use a absl::string_view to // efficiently keep track of remaining portion yet to be decoded. - std::string input_data_storage; - ReadFileContents(input_filename, &input_data_storage); - absl::string_view input_data(input_data_storage); + absl::optional<std::string> input_data_storage = + quiche::ReadFileContents(input_filename); + QUICHE_DCHECK(input_data_storage.has_value()); + absl::string_view input_data(*input_data_storage); while (!input_data.empty()) { // Parse stream_id and length. @@ -233,9 +228,10 @@ bool QpackOfflineDecoder::VerifyDecodedHeaderLists( // Store data in |expected_headers_data_storage|; use a // absl::string_view to efficiently keep track of remaining portion // yet to be decoded. - std::string expected_headers_data_storage; - ReadFileContents(expected_headers_filename, &expected_headers_data_storage); - absl::string_view expected_headers_data(expected_headers_data_storage); + absl::optional<std::string> expected_headers_data_storage = + quiche::ReadFileContents(expected_headers_filename); + QUICHE_DCHECK(expected_headers_data_storage.has_value()); + absl::string_view expected_headers_data(*expected_headers_data_storage); while (!decoded_header_lists_.empty()) { spdy::Http2HeaderBlock decoded_header_list = diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/quic_connection_peer.cc b/chromium/net/third_party/quiche/src/quic/test_tools/quic_connection_peer.cc index 71036cb2512..43c345094ef 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/quic_connection_peer.cc +++ b/chromium/net/third_party/quiche/src/quic/test_tools/quic_connection_peer.cc @@ -398,11 +398,7 @@ QuicIdleNetworkDetector& QuicConnectionPeer::GetIdleNetworkDetector( void QuicConnectionPeer::SetServerConnectionId( QuicConnection* connection, const QuicConnectionId& server_connection_id) { - if (connection->use_connection_id_on_default_path_) { - connection->default_path_.server_connection_id = server_connection_id; - } else { - connection->server_connection_id_ = server_connection_id; - } + connection->default_path_.server_connection_id = server_connection_id; connection->InstallInitialCrypters(server_connection_id); } @@ -430,7 +426,7 @@ void QuicConnectionPeer::SendPing(QuicConnection* connection) { void QuicConnectionPeer::SetLastPacketDestinationAddress( QuicConnection* connection, const QuicSocketAddress& address) { - connection->last_packet_destination_address_ = address; + connection->last_received_packet_info_.destination_address = address; } // static @@ -525,5 +521,12 @@ bool QuicConnectionPeer::HasSelfIssuedConnectionIdToConsume( return connection->self_issued_cid_manager_->HasConnectionIdToConsume(); } +// static +QuicSelfIssuedConnectionIdManager* +QuicConnectionPeer::GetSelfIssuedConnectionIdManager( + QuicConnection* connection) { + return connection->self_issued_cid_manager_.get(); +} + } // namespace test } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/quic_connection_peer.h b/chromium/net/third_party/quiche/src/quic/test_tools/quic_connection_peer.h index cbc829a7a65..99b8b94fd74 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/quic_connection_peer.h +++ b/chromium/net/third_party/quiche/src/quic/test_tools/quic_connection_peer.h @@ -213,6 +213,9 @@ class QuicConnectionPeer { static bool HasSelfIssuedConnectionIdToConsume( const QuicConnection* connection); + + static QuicSelfIssuedConnectionIdManager* GetSelfIssuedConnectionIdManager( + QuicConnection* connection); }; } // namespace test diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/quic_framer_peer.cc b/chromium/net/third_party/quiche/src/quic/test_tools/quic_framer_peer.cc index 14cc59418f5..8692a25c685 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/quic_framer_peer.cc +++ b/chromium/net/third_party/quiche/src/quic/test_tools/quic_framer_peer.cc @@ -6,7 +6,6 @@ #include "quic/core/quic_framer.h" #include "quic/core/quic_packets.h" -#include "quic/platform/api/quic_map_util.h" namespace quic { namespace test { diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/quic_session_peer.cc b/chromium/net/third_party/quiche/src/quic/test_tools/quic_session_peer.cc index a3dbf843e58..e734a794aea 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/quic_session_peer.cc +++ b/chromium/net/third_party/quiche/src/quic/test_tools/quic_session_peer.cc @@ -8,7 +8,6 @@ #include "quic/core/quic_session.h" #include "quic/core/quic_stream.h" #include "quic/core/quic_utils.h" -#include "quic/platform/api/quic_map_util.h" namespace quic { namespace test { @@ -152,24 +151,20 @@ bool QuicSessionPeer::IsStreamClosed(QuicSession* session, QuicStreamId id) { // static bool QuicSessionPeer::IsStreamCreated(QuicSession* session, QuicStreamId id) { - return QuicContainsKey(session->stream_map_, id); + return session->stream_map_.contains(id); } // static bool QuicSessionPeer::IsStreamAvailable(QuicSession* session, QuicStreamId id) { if (VersionHasIetfQuicFrames(session->transport_version())) { if (id % QuicUtils::StreamIdDelta(session->transport_version()) < 2) { - return QuicContainsKey( - session->ietf_streamid_manager_.bidirectional_stream_id_manager_ - .available_streams_, - id); + return session->ietf_streamid_manager_.bidirectional_stream_id_manager_ + .available_streams_.contains(id); } - return QuicContainsKey( - session->ietf_streamid_manager_.unidirectional_stream_id_manager_ - .available_streams_, - id); + return session->ietf_streamid_manager_.unidirectional_stream_id_manager_ + .available_streams_.contains(id); } - return QuicContainsKey(session->stream_id_manager_.available_streams_, id); + return session->stream_id_manager_.available_streams_.contains(id); } // static diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/quic_spdy_session_peer.cc b/chromium/net/third_party/quiche/src/quic/test_tools/quic_spdy_session_peer.cc index c547ac22610..a27f073e993 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/quic_spdy_session_peer.cc +++ b/chromium/net/third_party/quiche/src/quic/test_tools/quic_spdy_session_peer.cc @@ -106,8 +106,13 @@ void QuicSpdySessionPeer::SetH3DatagramSupported(QuicSpdySession* session, } // static +bool QuicSpdySessionPeer::ShouldNegotiateHttp3Datagram( + QuicSpdySession* session) { + return session->ShouldNegotiateHttp3Datagram(); +} + +// static void QuicSpdySessionPeer::EnableWebTransport(QuicSpdySession& session) { - SetQuicReloadableFlag(quic_h3_datagram, true); QUICHE_DCHECK(session.WillNegotiateWebTransport()); session.h3_datagram_supported_ = true; session.peer_supports_webtransport_ = true; diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/quic_spdy_session_peer.h b/chromium/net/third_party/quiche/src/quic/test_tools/quic_spdy_session_peer.h index 9ba54979e9a..0d06c4ddfad 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/quic_spdy_session_peer.h +++ b/chromium/net/third_party/quiche/src/quic/test_tools/quic_spdy_session_peer.h @@ -52,6 +52,7 @@ class QuicSpdySessionPeer { QuicSpdySession* session); static void SetH3DatagramSupported(QuicSpdySession* session, bool h3_datagram_supported); + static bool ShouldNegotiateHttp3Datagram(QuicSpdySession* session); static void EnableWebTransport(QuicSpdySession& session); }; diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/quic_test_client.cc b/chromium/net/third_party/quiche/src/quic/test_tools/quic_test_client.cc index 7cc3ff9be3b..598e14cb1b0 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/quic_test_client.cc +++ b/chromium/net/third_party/quiche/src/quic/test_tools/quic_test_client.cc @@ -618,16 +618,12 @@ const std::string& QuicTestClient::cert_sct() const { ->cert_sct(); } -QuicTagValueMap QuicTestClient::GetServerConfig() const { +const QuicTagValueMap& QuicTestClient::GetServerConfig() const { QuicCryptoClientConfig* config = client_->crypto_config(); - QuicCryptoClientConfig::CachedState* state = + const QuicCryptoClientConfig::CachedState* state = config->LookupOrCreate(client_->server_id()); const CryptoHandshakeMessage* handshake_msg = state->GetServerConfig(); - if (handshake_msg != nullptr) { - return handshake_msg->tag_value_map(); - } else { - return QuicTagValueMap(); - } + return handshake_msg->tag_value_map(); } bool QuicTestClient::connected() const { @@ -786,7 +782,7 @@ void QuicTestClient::OnClose(QuicSpdyStream* stream) { // written. client()->OnClose(stream); ++num_responses_; - if (!QuicContainsKey(open_streams_, stream->id())) { + if (open_streams_.find(stream->id()) == open_streams_.end()) { return; } if (latest_created_stream_ == stream) { diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/quic_test_client.h b/chromium/net/third_party/quiche/src/quic/test_tools/quic_test_client.h index d764ca9d2a5..66ebb5bc736 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/quic_test_client.h +++ b/chromium/net/third_party/quiche/src/quic/test_tools/quic_test_client.h @@ -14,11 +14,10 @@ #include "quic/core/quic_framer.h" #include "quic/core/quic_packet_creator.h" #include "quic/core/quic_packets.h" -#include "quic/platform/api/quic_containers.h" #include "quic/platform/api/quic_epoll.h" -#include "quic/platform/api/quic_map_util.h" #include "quic/platform/api/quic_test.h" #include "quic/tools/quic_client.h" +#include "common/quiche_linked_hash_map.h" namespace quic { @@ -282,8 +281,8 @@ class QuicTestClient : public QuicSpdyStream::Visitor, // or the empty std::string if no signed timestamp was presented. const std::string& cert_sct() const; - // Get the server config map. - QuicTagValueMap GetServerConfig() const; + // Get the server config map. Server config must exist. + const QuicTagValueMap& GetServerConfig() const; void set_auto_reconnect(bool reconnect) { auto_reconnect_ = reconnect; } @@ -401,7 +400,8 @@ class QuicTestClient : public QuicSpdyStream::Visitor, QuicSpdyClientStream* latest_created_stream_; std::map<QuicStreamId, QuicSpdyClientStream*> open_streams_; // Received responses of closed streams. - QuicLinkedHashMap<QuicStreamId, PerStreamState> closed_stream_states_; + quiche::QuicheLinkedHashMap<QuicStreamId, PerStreamState> + closed_stream_states_; QuicRstStreamErrorCode stream_error_; diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/quic_test_utils.cc b/chromium/net/third_party/quiche/src/quic/test_tools/quic_test_utils.cc index 5feb6462321..126b87e039c 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/quic_test_utils.cc +++ b/chromium/net/third_party/quiche/src/quic/test_tools/quic_test_utils.cc @@ -191,7 +191,11 @@ std::unique_ptr<QuicPacket> BuildUnsizedDataPacket( EncryptionLevel level = HeaderToEncryptionLevel(header); size_t length = framer->BuildDataPacket(header, frames, buffer, packet_size, level); - QUICHE_DCHECK_NE(0u, length); + + if (length == 0) { + delete[] buffer; + return nullptr; + } // Re-construct the data packet with data ownership. return std::make_unique<QuicPacket>( buffer, length, /* owns_buffer */ true, @@ -1323,10 +1327,12 @@ QuicMemSliceSpan MakeSpan(QuicBufferAllocator* allocator, } QuicMemSlice MemSliceFromString(absl::string_view data) { + if (data.empty()) { + return QuicMemSlice(); + } + static SimpleBufferAllocator* allocator = new SimpleBufferAllocator(); - QuicUniqueBufferPtr buffer = MakeUniqueBuffer(allocator, data.size()); - memcpy(buffer.get(), data.data(), data.size()); - return QuicMemSlice(std::move(buffer), data.size()); + return QuicMemSlice(QuicBuffer::Copy(allocator, data)); } bool TaggingEncrypter::EncryptPacket(uint64_t /*packet_number*/, diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/quic_test_utils.h b/chromium/net/third_party/quiche/src/quic/test_tools/quic_test_utils.h index 9fbffe0b76c..06f3eeca7c2 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/quic_test_utils.h +++ b/chromium/net/third_party/quiche/src/quic/test_tools/quic_test_utils.h @@ -282,7 +282,7 @@ class SimpleRandom : public QuicRandom { private: uint8_t buffer_[4096]; - size_t buffer_offset_; + size_t buffer_offset_ = 0; uint8_t key_[32]; void FillBuffer(); @@ -763,10 +763,8 @@ class MockQuicConnection : public QuicConnection { (QuicStreamId, QuicRstStreamErrorCode), (override)); MOCK_METHOD(bool, SendControlFrame, (const QuicFrame& frame), (override)); - MOCK_METHOD(MessageStatus, - SendMessage, - (QuicMessageId, QuicMemSliceSpan, bool), - (override)); + MOCK_METHOD(MessageStatus, SendMessage, + (QuicMessageId, absl::Span<QuicMemSlice>, bool), (override)); MOCK_METHOD(bool, SendPathChallenge, (const QuicPathFrameBuffer&, @@ -904,14 +902,10 @@ class MockQuicSession : public QuicSession { CreateIncomingStream, (PendingStream*), (override)); - MOCK_METHOD(QuicConsumedData, - WritevData, - (QuicStreamId id, - size_t write_length, - QuicStreamOffset offset, - StreamSendingState state, - TransmissionType type, - absl::optional<EncryptionLevel> level), + MOCK_METHOD(QuicConsumedData, WritevData, + (QuicStreamId id, size_t write_length, QuicStreamOffset offset, + StreamSendingState state, TransmissionType type, + EncryptionLevel level), (override)); MOCK_METHOD(bool, WriteControlFrame, @@ -1042,14 +1036,10 @@ class MockQuicSpdySession : public QuicSpdySession { MOCK_METHOD(bool, ShouldCreateIncomingStream, (QuicStreamId id), (override)); MOCK_METHOD(bool, ShouldCreateOutgoingBidirectionalStream, (), (override)); MOCK_METHOD(bool, ShouldCreateOutgoingUnidirectionalStream, (), (override)); - MOCK_METHOD(QuicConsumedData, - WritevData, - (QuicStreamId id, - size_t write_length, - QuicStreamOffset offset, - StreamSendingState state, - TransmissionType type, - absl::optional<EncryptionLevel> level), + MOCK_METHOD(QuicConsumedData, WritevData, + (QuicStreamId id, size_t write_length, QuicStreamOffset offset, + StreamSendingState state, TransmissionType type, + EncryptionLevel level), (override)); MOCK_METHOD(void, MaybeSendRstStreamFrame, @@ -1132,10 +1122,6 @@ class MockHttp3DebugVisitor : public Http3DebugVisitor { (override)); MOCK_METHOD(void, - OnCancelPushFrameReceived, - (const CancelPushFrame&), - (override)); - MOCK_METHOD(void, OnSettingsFrameReceived, (const SettingsFrame&), (override)); @@ -1166,14 +1152,6 @@ class MockHttp3DebugVisitor : public Http3DebugVisitor { (QuicStreamId, QuicHeaderList), (override)); MOCK_METHOD(void, - OnPushPromiseFrameReceived, - (QuicStreamId, QuicStreamId, QuicByteCount), - (override)); - MOCK_METHOD(void, - OnPushPromiseDecoded, - (QuicStreamId, QuicStreamId, QuicHeaderList), - (override)); - MOCK_METHOD(void, OnUnknownFrameReceived, (QuicStreamId, uint64_t, QuicByteCount), (override)); @@ -1191,10 +1169,6 @@ class MockHttp3DebugVisitor : public Http3DebugVisitor { OnHeadersFrameSent, (QuicStreamId, const spdy::SpdyHeaderBlock&), (override)); - MOCK_METHOD(void, - OnPushPromiseFrameSent, - (QuicStreamId, QuicStreamId, const spdy::SpdyHeaderBlock&), - (override)); }; class TestQuicSpdyServerSession : public QuicServerSessionBase { @@ -1241,9 +1215,14 @@ class TestQuicSpdyServerSession : public QuicServerSessionBase { MockQuicCryptoServerStreamHelper* helper() { return &helper_; } + QuicSSLConfig GetSSLConfig() const override { return ssl_config_; } + + QuicSSLConfig* ssl_config() { return &ssl_config_; } + private: MockQuicSessionVisitor visitor_; MockQuicCryptoServerStreamHelper helper_; + QuicSSLConfig ssl_config_; }; // A test implementation of QuicClientPushPromiseIndex::Delegate. @@ -2318,31 +2297,49 @@ bool WriteServerVersionNegotiationProbeResponse( uint8_t source_connection_id_length); // Implementation of Http3DatagramVisitor which saves all received datagrams. -class SavingHttp3DatagramVisitor - : public QuicSpdySession::Http3DatagramVisitor { +class SavingHttp3DatagramVisitor : public QuicSpdyStream::Http3DatagramVisitor { public: struct SavedHttp3Datagram { - QuicDatagramFlowId flow_id; + QuicStreamId stream_id; + absl::optional<QuicDatagramContextId> context_id; std::string payload; bool operator==(const SavedHttp3Datagram& o) const { - return flow_id == o.flow_id && payload == o.payload; + return stream_id == o.stream_id && context_id == o.context_id && + payload == o.payload; } }; const std::vector<SavedHttp3Datagram>& received_h3_datagrams() const { return received_h3_datagrams_; } - // Override from QuicSpdySession::Http3DatagramVisitor. - void OnHttp3Datagram(QuicDatagramFlowId flow_id, + // Override from QuicSpdyStream::Http3DatagramVisitor. + void OnHttp3Datagram(QuicStreamId stream_id, + absl::optional<QuicDatagramContextId> context_id, absl::string_view payload) override { received_h3_datagrams_.push_back( - SavedHttp3Datagram{flow_id, std::string(payload)}); + SavedHttp3Datagram{stream_id, context_id, std::string(payload)}); } private: std::vector<SavedHttp3Datagram> received_h3_datagrams_; }; +class MockHttp3DatagramRegistrationVisitor + : public QuicSpdyStream::Http3DatagramRegistrationVisitor { + public: + MOCK_METHOD(void, OnContextReceived, + (QuicStreamId stream_id, + absl::optional<QuicDatagramContextId> context_id, + const Http3DatagramContextExtensions& extensions), + (override)); + + MOCK_METHOD(void, OnContextClosed, + (QuicStreamId stream_id, + absl::optional<QuicDatagramContextId> context_id, + const Http3DatagramContextExtensions& extensions), + (override)); +}; + } // namespace test } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/simple_data_producer.cc b/chromium/net/third_party/quiche/src/quic/test_tools/simple_data_producer.cc index 5ef53f4bf23..413a840e32c 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/simple_data_producer.cc +++ b/chromium/net/third_party/quiche/src/quic/test_tools/simple_data_producer.cc @@ -10,7 +10,6 @@ #include "quic/core/quic_data_writer.h" #include "quic/platform/api/quic_bug_tracker.h" #include "quic/platform/api/quic_flags.h" -#include "quic/platform/api/quic_map_util.h" namespace quic { @@ -28,7 +27,7 @@ void SimpleDataProducer::SaveStreamData(QuicStreamId id, if (data_length == 0) { return; } - if (!QuicContainsKey(send_buffer_map_, id)) { + if (!send_buffer_map_.contains(id)) { send_buffer_map_[id] = std::make_unique<QuicStreamSendBuffer>(&allocator_); } send_buffer_map_[id]->SaveStreamData(iov, iov_count, iov_offset, data_length); diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/simple_session_notifier.cc b/chromium/net/third_party/quiche/src/quic/test_tools/simple_session_notifier.cc index 51d4017948f..a1383b0e41d 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/simple_session_notifier.cc +++ b/chromium/net/third_party/quiche/src/quic/test_tools/simple_session_notifier.cc @@ -6,7 +6,6 @@ #include "quic/core/quic_utils.h" #include "quic/platform/api/quic_logging.h" -#include "quic/platform/api/quic_map_util.h" #include "quic/test_tools/quic_test_utils.h" namespace quic { @@ -40,7 +39,7 @@ QuicConsumedData SimpleSessionNotifier::WriteOrBufferData( QuicStreamId id, QuicByteCount data_length, StreamSendingState state) { - if (!QuicContainsKey(stream_map_, id)) { + if (!stream_map_.contains(id)) { stream_map_[id] = StreamState(); } StreamState& stream_state = stream_map_.find(id)->second; @@ -163,16 +162,14 @@ void SimpleSessionNotifier::NeuterUnencryptedData() { } void SimpleSessionNotifier::OnCanWrite() { - if (connection_->donot_write_mid_packet_processing()) { - if (connection_->framer().is_processing_packet()) { - // Do not write data in the middle of packet processing because rest - // frames in the packet may change the data to write. For example, lost - // data could be acknowledged. Also, connection is going to emit - // OnCanWrite signal post packet processing. - QUIC_BUG(simple_notifier_write_mid_packet_processing) - << "Try to write mid packet processing."; - return; - } + if (connection_->framer().is_processing_packet()) { + // Do not write data in the middle of packet processing because rest + // frames in the packet may change the data to write. For example, lost + // data could be acknowledged. Also, connection is going to emit + // OnCanWrite signal post packet processing. + QUIC_BUG(simple_notifier_write_mid_packet_processing) + << "Try to write mid packet processing."; + return; } if (!RetransmitLostCryptoData() || !RetransmitLostControlFrames() || !RetransmitLostStreamData()) { @@ -263,7 +260,7 @@ bool SimpleSessionNotifier::OnFrameAcked(const QuicFrame& frame, if (frame.type != STREAM_FRAME) { return OnControlFrameAcked(frame); } - if (!QuicContainsKey(stream_map_, frame.stream_frame.stream_id)) { + if (!stream_map_.contains(frame.stream_frame.stream_id)) { return false; } auto* state = &stream_map_.find(frame.stream_frame.stream_id)->second; @@ -304,7 +301,7 @@ void SimpleSessionNotifier::OnFrameLost(const QuicFrame& frame) { OnControlFrameLost(frame); return; } - if (!QuicContainsKey(stream_map_, frame.stream_frame.stream_id)) { + if (!stream_map_.contains(frame.stream_frame.stream_id)) { return; } auto* state = &stream_map_.find(frame.stream_frame.stream_id)->second; @@ -359,7 +356,7 @@ void SimpleSessionNotifier::RetransmitFrames(const QuicFrames& frames, } continue; } - if (!QuicContainsKey(stream_map_, frame.stream_frame.stream_id)) { + if (!stream_map_.contains(frame.stream_frame.stream_id)) { continue; } const auto& state = stream_map_.find(frame.stream_frame.stream_id)->second; @@ -437,7 +434,7 @@ bool SimpleSessionNotifier::IsFrameOutstanding(const QuicFrame& frame) const { if (frame.type != STREAM_FRAME) { return IsControlFrameOutstanding(frame); } - if (!QuicContainsKey(stream_map_, frame.stream_frame.stream_id)) { + if (!stream_map_.contains(frame.stream_frame.stream_id)) { return false; } const auto& state = stream_map_.find(frame.stream_frame.stream_id)->second; @@ -463,8 +460,8 @@ bool SimpleSessionNotifier::HasUnackedCryptoData() const { } return false; } - if (!QuicContainsKey(stream_map_, QuicUtils::GetCryptoStreamId( - connection_->transport_version()))) { + if (!stream_map_.contains( + QuicUtils::GetCryptoStreamId(connection_->transport_version()))) { return false; } const auto& state = @@ -521,7 +518,7 @@ void SimpleSessionNotifier::OnControlFrameLost(const QuicFrame& frame) { kInvalidControlFrameId) { return; } - if (!QuicContainsKey(lost_control_frames_, id)) { + if (!lost_control_frames_.contains(id)) { lost_control_frames_[id] = true; } } @@ -584,8 +581,8 @@ bool SimpleSessionNotifier::RetransmitLostCryptoData() { } return true; } - if (!QuicContainsKey(stream_map_, QuicUtils::GetCryptoStreamId( - connection_->transport_version()))) { + if (!stream_map_.contains( + QuicUtils::GetCryptoStreamId(connection_->transport_version()))) { return true; } auto& state = @@ -701,7 +698,7 @@ bool SimpleSessionNotifier::HasBufferedStreamData() const { } bool SimpleSessionNotifier::StreamIsWaitingForAcks(QuicStreamId id) const { - if (!QuicContainsKey(stream_map_, id)) { + if (!stream_map_.contains(id)) { return false; } const StreamState& state = stream_map_.find(id)->second; @@ -710,7 +707,7 @@ bool SimpleSessionNotifier::StreamIsWaitingForAcks(QuicStreamId id) const { } bool SimpleSessionNotifier::StreamHasBufferedData(QuicStreamId id) const { - if (!QuicContainsKey(stream_map_, id)) { + if (!stream_map_.contains(id)) { return false; } const StreamState& state = stream_map_.find(id)->second; diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/simple_session_notifier.h b/chromium/net/third_party/quiche/src/quic/test_tools/simple_session_notifier.h index bc76ddc877a..8bdadb2f45d 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/simple_session_notifier.h +++ b/chromium/net/third_party/quiche/src/quic/test_tools/simple_session_notifier.h @@ -10,6 +10,7 @@ #include "quic/core/session_notifier_interface.h" #include "quic/platform/api/quic_test.h" #include "common/quiche_circular_deque.h" +#include "common/quiche_linked_hash_map.h" namespace quic { @@ -134,7 +135,7 @@ class SimpleSessionNotifier : public SessionNotifierInterface { quiche::QuicheCircularDeque<QuicFrame> control_frames_; - QuicLinkedHashMap<QuicControlFrameId, bool> lost_control_frames_; + quiche::QuicheLinkedHashMap<QuicControlFrameId, bool> lost_control_frames_; // Id of latest saved control frame. 0 if no control frame has been saved. QuicControlFrameId last_control_frame_id_; diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/simulator/queue.cc b/chromium/net/third_party/quiche/src/quic/test_tools/simulator/queue.cc index c286bf6e853..c7e23cd0711 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/simulator/queue.cc +++ b/chromium/net/third_party/quiche/src/quic/test_tools/simulator/queue.cc @@ -26,7 +26,7 @@ Queue::Queue(Simulator* simulator, std::string name, QuicByteCount capacity) new AggregationAlarmDelegate(this))); } -Queue::~Queue() {} +Queue::~Queue() { aggregation_timeout_alarm_->PermanentCancel(); } void Queue::set_tx_port(ConstrainedPortInterface* port) { tx_port_ = port; diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/test_ticket_crypter.cc b/chromium/net/third_party/quiche/src/quic/test_tools/test_ticket_crypter.cc index 4c4cfbbac2f..be9021a7baa 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/test_ticket_crypter.cc +++ b/chromium/net/third_party/quiche/src/quic/test_tools/test_ticket_crypter.cc @@ -37,7 +37,8 @@ size_t TestTicketCrypter::MaxOverhead() { return ticket_prefix_.size(); } -std::vector<uint8_t> TestTicketCrypter::Encrypt(absl::string_view in) { +std::vector<uint8_t> TestTicketCrypter::Encrypt( + absl::string_view in, absl::string_view /* encryption_key */) { size_t prefix_len = ticket_prefix_.size(); std::vector<uint8_t> out(prefix_len + in.size()); memcpy(out.data(), ticket_prefix_.data(), prefix_len); diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/test_ticket_crypter.h b/chromium/net/third_party/quiche/src/quic/test_tools/test_ticket_crypter.h index 63919c61bba..0300998fa37 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/test_ticket_crypter.h +++ b/chromium/net/third_party/quiche/src/quic/test_tools/test_ticket_crypter.h @@ -19,7 +19,8 @@ class TestTicketCrypter : public ProofSource::TicketCrypter { // TicketCrypter interface size_t MaxOverhead() override; - std::vector<uint8_t> Encrypt(absl::string_view in) override; + std::vector<uint8_t> Encrypt(absl::string_view in, + absl::string_view encryption_key) override; void Decrypt(absl::string_view in, std::unique_ptr<ProofSource::DecryptCallback> callback) override; diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_client_base.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_client_base.cc index 53c6800a2e5..6d10de282cd 100644 --- a/chromium/net/third_party/quiche/src/quic/tools/quic_client_base.cc +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_client_base.cc @@ -3,6 +3,8 @@ // found in the LICENSE file. #include "quic/tools/quic_client_base.h" + +#include <algorithm> #include <memory> #include "quic/core/crypto/quic_random.h" @@ -442,13 +444,20 @@ QuicConnectionId QuicClientBase::GetClientConnectionId() { bool QuicClientBase::CanReconnectWithDifferentVersion( ParsedQuicVersion* version) const { if (session_ == nullptr || session_->connection() == nullptr || - session_->error() != QUIC_INVALID_VERSION || - session_->connection()->server_supported_versions().empty()) { + session_->error() != QUIC_INVALID_VERSION) { return false; } + + const auto& server_supported_versions = + session_->connection()->server_supported_versions(); + if (server_supported_versions.empty()) { + return false; + } + for (const auto& client_version : supported_versions_) { - if (QuicContainsValue(session_->connection()->server_supported_versions(), - client_version)) { + if (std::find(server_supported_versions.begin(), + server_supported_versions.end(), + client_version) != server_supported_versions.end()) { *version = client_version; return true; } diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_client_base.h b/chromium/net/third_party/quiche/src/quic/tools/quic_client_base.h index 82ee39256cf..51e3433d90a 100644 --- a/chromium/net/third_party/quiche/src/quic/tools/quic_client_base.h +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_client_base.h @@ -145,6 +145,11 @@ class QuicClientBase { crypto_config_.set_user_agent_id(user_agent_id); } + void SetTlsSignatureAlgorithms(std::string signature_algorithms) { + crypto_config_.set_tls_signature_algorithms( + std::move(signature_algorithms)); + } + const ParsedQuicVersionVector& supported_versions() const { return supported_versions_; } diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_client_epoll_network_helper.h b/chromium/net/third_party/quiche/src/quic/tools/quic_client_epoll_network_helper.h index df0a3b4a4f5..bb8a898384c 100644 --- a/chromium/net/third_party/quiche/src/quic/tools/quic_client_epoll_network_helper.h +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_client_epoll_network_helper.h @@ -15,9 +15,9 @@ #include "quic/core/http/quic_client_push_promise_index.h" #include "quic/core/quic_config.h" #include "quic/core/quic_packet_reader.h" -#include "quic/platform/api/quic_containers.h" #include "quic/platform/api/quic_epoll.h" #include "quic/tools/quic_client_base.h" +#include "common/quiche_linked_hash_map.h" namespace quic { @@ -73,7 +73,8 @@ class QuicClientEpollNetworkHelper : public QuicClientBase::NetworkHelper, QuicEpollServer* epoll_server() { return epoll_server_; } - const QuicLinkedHashMap<int, QuicSocketAddress>& fd_address_map() const { + const quiche::QuicheLinkedHashMap<int, QuicSocketAddress>& fd_address_map() + const { return fd_address_map_; } @@ -110,7 +111,7 @@ class QuicClientEpollNetworkHelper : public QuicClientBase::NetworkHelper, // Map mapping created UDP sockets to their addresses. By using linked hash // map, the order of socket creation can be recorded. - QuicLinkedHashMap<int, QuicSocketAddress> fd_address_map_; + quiche::QuicheLinkedHashMap<int, QuicSocketAddress> fd_address_map_; // If overflow_supported_ is true, this will be the number of packets dropped // during the lifetime of the server. diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_memory_cache_backend.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_memory_cache_backend.cc index de2064831f4..58aa7a94ce8 100644 --- a/chromium/net/third_party/quiche/src/quic/tools/quic_memory_cache_backend.cc +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_memory_cache_backend.cc @@ -12,10 +12,9 @@ #include "absl/strings/string_view.h" #include "quic/core/http/spdy_utils.h" #include "quic/platform/api/quic_bug_tracker.h" -#include "quic/platform/api/quic_file_utils.h" #include "quic/platform/api/quic_logging.h" -#include "quic/platform/api/quic_map_util.h" #include "quic/tools/web_transport_test_visitors.h" +#include "common/platform/api/quiche_file_utils.h" #include "common/quiche_text_utils.h" using spdy::Http2HeaderBlock; @@ -29,7 +28,14 @@ QuicMemoryCacheBackend::ResourceFile::ResourceFile(const std::string& file_name) QuicMemoryCacheBackend::ResourceFile::~ResourceFile() = default; void QuicMemoryCacheBackend::ResourceFile::Read() { - ReadFileContents(file_name_, &file_contents_); + absl::optional<std::string> maybe_file_contents = + quiche::ReadFileContents(file_name_); + if (!maybe_file_contents) { + QUIC_LOG(DFATAL) << "Failed to read file for the memory cache backend: " + << file_name_; + return; + } + file_contents_ = *maybe_file_contents; // First read the headers. size_t start = 0; @@ -140,8 +146,7 @@ void QuicMemoryCacheBackend::ResourceFile::HandleXOriginalUrl() { } const QuicBackendResponse* QuicMemoryCacheBackend::GetResponse( - absl::string_view host, - absl::string_view path) const { + absl::string_view host, absl::string_view path) const { QuicWriterMutexLock lock(&response_mutex_); auto it = responses_.find(GetKey(host, path)); @@ -179,11 +184,8 @@ void QuicMemoryCacheBackend::AddSimpleResponse(absl::string_view host, } void QuicMemoryCacheBackend::AddSimpleResponseWithServerPushResources( - absl::string_view host, - absl::string_view path, - int response_code, - absl::string_view body, - std::list<ServerPushInfo> push_resources) { + absl::string_view host, absl::string_view path, int response_code, + absl::string_view body, std::list<ServerPushInfo> push_resources) { AddSimpleResponse(host, path, response_code, body); MaybeAddServerPushResources(host, path, push_resources); } @@ -214,10 +216,8 @@ void QuicMemoryCacheBackend::AddResponse(absl::string_view host, } void QuicMemoryCacheBackend::AddResponseWithEarlyHints( - absl::string_view host, - absl::string_view path, - spdy::Http2HeaderBlock response_headers, - absl::string_view response_body, + absl::string_view host, absl::string_view path, + spdy::Http2HeaderBlock response_headers, absl::string_view response_body, const std::vector<spdy::Http2HeaderBlock>& early_hints) { AddResponseImpl(host, path, QuicBackendResponse::REGULAR_RESPONSE, std::move(response_headers), response_body, @@ -225,18 +225,15 @@ void QuicMemoryCacheBackend::AddResponseWithEarlyHints( } void QuicMemoryCacheBackend::AddSpecialResponse( - absl::string_view host, - absl::string_view path, + absl::string_view host, absl::string_view path, SpecialResponseType response_type) { AddResponseImpl(host, path, response_type, Http2HeaderBlock(), "", Http2HeaderBlock(), std::vector<spdy::Http2HeaderBlock>()); } void QuicMemoryCacheBackend::AddSpecialResponse( - absl::string_view host, - absl::string_view path, - spdy::Http2HeaderBlock response_headers, - absl::string_view response_body, + absl::string_view host, absl::string_view path, + spdy::Http2HeaderBlock response_headers, absl::string_view response_body, SpecialResponseType response_type) { AddResponseImpl(host, path, response_type, std::move(response_headers), response_body, Http2HeaderBlock(), @@ -254,7 +251,12 @@ bool QuicMemoryCacheBackend::InitializeBackend( QUIC_LOG(INFO) << "Attempting to initialize QuicMemoryCacheBackend from directory: " << cache_directory; - std::vector<std::string> files = ReadFileContents(cache_directory); + std::vector<std::string> files; + if (!quiche::EnumerateDirectoryRecursively(cache_directory, files)) { + QUIC_BUG(QuicMemoryCacheBackend unreadable directory) + << "Can't read QuicMemoryCacheBackend directory: " << cache_directory; + return false; + } std::list<std::unique_ptr<ResourceFile>> resource_files; for (const auto& filename : files) { std::unique_ptr<ResourceFile> resource_file(new ResourceFile(filename)); @@ -402,19 +404,16 @@ QuicMemoryCacheBackend::~QuicMemoryCacheBackend() { } void QuicMemoryCacheBackend::AddResponseImpl( - absl::string_view host, - absl::string_view path, - SpecialResponseType response_type, - Http2HeaderBlock response_headers, - absl::string_view response_body, - Http2HeaderBlock response_trailers, + absl::string_view host, absl::string_view path, + SpecialResponseType response_type, Http2HeaderBlock response_headers, + absl::string_view response_body, Http2HeaderBlock response_trailers, const std::vector<spdy::Http2HeaderBlock>& early_hints) { QuicWriterMutexLock lock(&response_mutex_); QUICHE_DCHECK(!host.empty()) << "Host must be populated, e.g. \"www.google.com\""; std::string key = GetKey(host, path); - if (QuicContainsKey(responses_, key)) { + if (responses_.contains(key)) { QUIC_BUG(quic_bug_10932_3) << "Response for '" << key << "' already exists!"; return; @@ -441,8 +440,7 @@ std::string QuicMemoryCacheBackend::GetKey(absl::string_view host, } void QuicMemoryCacheBackend::MaybeAddServerPushResources( - absl::string_view request_host, - absl::string_view request_path, + absl::string_view request_host, absl::string_view request_path, std::list<ServerPushInfo> push_resources) { std::string request_url = GetKey(request_host, request_path); @@ -468,7 +466,7 @@ void QuicMemoryCacheBackend::MaybeAddServerPushResources( bool found_existing_response = false; { QuicWriterMutexLock lock(&response_mutex_); - found_existing_response = QuicContainsKey(responses_, GetKey(host, path)); + found_existing_response = responses_.contains(GetKey(host, path)); } if (!found_existing_response) { // Add a server push response to responses map, if it is not in the map. @@ -481,8 +479,7 @@ void QuicMemoryCacheBackend::MaybeAddServerPushResources( } bool QuicMemoryCacheBackend::PushResourceExistsInCache( - std::string original_request_url, - ServerPushInfo resource) { + std::string original_request_url, ServerPushInfo resource) { QuicWriterMutexLock lock(&response_mutex_); auto resource_range = server_push_resources_.equal_range(original_request_url); diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_memory_cache_backend_test.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_memory_cache_backend_test.cc index d0383e19b6e..71f5dc5bbb4 100644 --- a/chromium/net/third_party/quiche/src/quic/tools/quic_memory_cache_backend_test.cc +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_memory_cache_backend_test.cc @@ -4,12 +4,13 @@ #include "quic/tools/quic_memory_cache_backend.h" +#include <vector> + #include "absl/strings/match.h" #include "absl/strings/str_cat.h" -#include "quic/platform/api/quic_file_utils.h" -#include "quic/platform/api/quic_map_util.h" #include "quic/platform/api/quic_test.h" #include "quic/tools/quic_backend_response.h" +#include "common/platform/api/quiche_file_utils.h" namespace quic { namespace test { @@ -21,8 +22,7 @@ using ServerPushInfo = QuicBackendResponse::ServerPushInfo; class QuicMemoryCacheBackendTest : public QuicTest { protected: - void CreateRequest(std::string host, - std::string path, + void CreateRequest(std::string host, std::string path, spdy::Http2HeaderBlock* headers) { (*headers)[":method"] = "GET"; (*headers)[":path"] = path; @@ -49,7 +49,7 @@ TEST_F(QuicMemoryCacheBackendTest, AddSimpleResponseGetResponse) { CreateRequest("www.google.com", "/", &request_headers); const Response* response = cache_.GetResponse("www.google.com", "/"); ASSERT_TRUE(response); - ASSERT_TRUE(QuicContainsKey(response->headers(), ":status")); + ASSERT_TRUE(response->headers().contains(":status")); EXPECT_EQ("200", response->headers().find(":status")->second); EXPECT_EQ(response_body.size(), response->body().length()); } @@ -82,10 +82,10 @@ TEST_F(QuicMemoryCacheBackendTest, ReadsCacheDir) { const Response* response = cache_.GetResponse("test.example.com", "/index.html"); ASSERT_TRUE(response); - ASSERT_TRUE(QuicContainsKey(response->headers(), ":status")); + ASSERT_TRUE(response->headers().contains(":status")); EXPECT_EQ("200", response->headers().find(":status")->second); // Connection headers are not valid in HTTP/2. - EXPECT_FALSE(QuicContainsKey(response->headers(), "connection")); + EXPECT_FALSE(response->headers().contains("connection")); EXPECT_LT(0U, response->body().length()); } @@ -108,10 +108,10 @@ TEST_F(QuicMemoryCacheBackendTest, UsesOriginalUrl) { const Response* response = cache_.GetResponse("test.example.com", "/site_map.html"); ASSERT_TRUE(response); - ASSERT_TRUE(QuicContainsKey(response->headers(), ":status")); + ASSERT_TRUE(response->headers().contains(":status")); EXPECT_EQ("200", response->headers().find(":status")->second); // Connection headers are not valid in HTTP/2. - EXPECT_FALSE(QuicContainsKey(response->headers(), "connection")); + EXPECT_FALSE(response->headers().contains("connection")); EXPECT_LT(0U, response->body().length()); } @@ -121,7 +121,9 @@ TEST_F(QuicMemoryCacheBackendTest, UsesOriginalUrlOnly) { // X-Original-Url header's value will be used. std::string dir; std::string path = "map.html"; - for (const std::string& file : ReadFileContents(CacheDirectory())) { + std::vector<std::string> files; + ASSERT_TRUE(quiche::EnumerateDirectoryRecursively(CacheDirectory(), files)); + for (const std::string& file : files) { if (absl::EndsWithIgnoreCase(file, "map.html")) { dir = file; dir.erase(dir.length() - path.length() - 1); @@ -134,10 +136,10 @@ TEST_F(QuicMemoryCacheBackendTest, UsesOriginalUrlOnly) { const Response* response = cache_.GetResponse("test.example.com", "/site_map.html"); ASSERT_TRUE(response); - ASSERT_TRUE(QuicContainsKey(response->headers(), ":status")); + ASSERT_TRUE(response->headers().contains(":status")); EXPECT_EQ("200", response->headers().find(":status")->second); // Connection headers are not valid in HTTP/2. - EXPECT_FALSE(QuicContainsKey(response->headers(), "connection")); + EXPECT_FALSE(response->headers().contains("connection")); EXPECT_LT(0U, response->body().length()); } @@ -157,20 +159,20 @@ TEST_F(QuicMemoryCacheBackendTest, DefaultResponse) { // Now we should get the default response for the original request. response = cache_.GetResponse("www.google.com", "/"); ASSERT_TRUE(response); - ASSERT_TRUE(QuicContainsKey(response->headers(), ":status")); + ASSERT_TRUE(response->headers().contains(":status")); EXPECT_EQ("200", response->headers().find(":status")->second); // Now add a set response for / and make sure it is returned cache_.AddSimpleResponse("www.google.com", "/", 302, ""); response = cache_.GetResponse("www.google.com", "/"); ASSERT_TRUE(response); - ASSERT_TRUE(QuicContainsKey(response->headers(), ":status")); + ASSERT_TRUE(response->headers().contains(":status")); EXPECT_EQ("302", response->headers().find(":status")->second); // We should get the default response for other requests. response = cache_.GetResponse("www.google.com", "/asd"); ASSERT_TRUE(response); - ASSERT_TRUE(QuicContainsKey(response->headers(), ":status")); + ASSERT_TRUE(response->headers().contains(":status")); EXPECT_EQ("200", response->headers().find(":status")->second); } @@ -243,7 +245,7 @@ TEST_F(QuicMemoryCacheBackendTest, GetServerPushResourcesAndPushResponses) { std::string path = url.path(); const Response* response = cache_.GetResponse(host, path); ASSERT_TRUE(response); - ASSERT_TRUE(QuicContainsKey(response->headers(), ":status")); + ASSERT_TRUE(response->headers().contains(":status")); EXPECT_EQ(push_response_status[i++], response->headers().find(":status")->second); EXPECT_EQ(push_resource.body, response->body()); diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_simple_client_session.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_client_session.cc index d1efcba4fe0..07c46289da8 100644 --- a/chromium/net/third_party/quiche/src/quic/tools/quic_simple_client_session.cc +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_client_session.cc @@ -54,4 +54,8 @@ bool QuicSimpleClientSession::ShouldNegotiateWebTransport() { return enable_web_transport_; } +bool QuicSimpleClientSession::ShouldNegotiateHttp3Datagram() { + return enable_web_transport_; +} + } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_simple_client_session.h b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_client_session.h index aa86d9a83c9..124e56f8bf1 100644 --- a/chromium/net/third_party/quiche/src/quic/tools/quic_simple_client_session.h +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_client_session.h @@ -30,6 +30,7 @@ class QuicSimpleClientSession : public QuicSpdyClientSession { std::unique_ptr<QuicSpdyClientStream> CreateClientStream() override; bool ShouldNegotiateWebTransport() override; + bool ShouldNegotiateHttp3Datagram() override; private: const bool drop_response_body_; diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_stream.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_stream.cc index ac95e46fb97..5cd365298f3 100644 --- a/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_stream.cc +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_stream.cc @@ -18,7 +18,6 @@ #include "quic/platform/api/quic_bug_tracker.h" #include "quic/platform/api/quic_flags.h" #include "quic/platform/api/quic_logging.h" -#include "quic/platform/api/quic_map_util.h" #include "quic/tools/quic_simple_server_session.h" #include "spdy/core/spdy_protocol.h" @@ -155,13 +154,13 @@ void QuicSimpleServerStream::SendResponse() { return; } - if (!QuicContainsKey(request_headers_, ":authority")) { + if (!request_headers_.contains(":authority")) { QUIC_DVLOG(1) << "Request headers do not contain :authority."; SendErrorResponse(); return; } - if (!QuicContainsKey(request_headers_, ":path")) { + if (!request_headers_.contains(":path")) { // CONNECT and other CONNECT-like methods (such as CONNECT-UDP) do not all // require :path to be present. auto it = request_headers_.find(":method"); diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_stream_test.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_stream_test.cc index 25423114fab..5c9cd2274d9 100644 --- a/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_stream_test.cc +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_stream_test.cc @@ -16,6 +16,7 @@ #include "quic/core/http/http_encoder.h" #include "quic/core/http/spdy_utils.h" #include "quic/core/quic_error_codes.h" +#include "quic/core/quic_simple_buffer_allocator.h" #include "quic/core/quic_types.h" #include "quic/core/quic_utils.h" #include "quic/platform/api/quic_expect_bug.h" @@ -156,14 +157,10 @@ class MockQuicSimpleServerSession : public QuicSimpleServerSession { CreateIncomingStream, (QuicStreamId id), (override)); - MOCK_METHOD(QuicConsumedData, - WritevData, - (QuicStreamId id, - size_t write_length, - QuicStreamOffset offset, - StreamSendingState state, - TransmissionType type, - absl::optional<EncryptionLevel> level), + MOCK_METHOD(QuicConsumedData, WritevData, + (QuicStreamId id, size_t write_length, QuicStreamOffset offset, + StreamSendingState state, TransmissionType type, + EncryptionLevel level), (override)); MOCK_METHOD(void, OnStreamHeaderList, @@ -311,11 +308,10 @@ TEST_P(QuicSimpleServerStreamTest, TestFraming) { .WillRepeatedly( Invoke(&session_, &MockQuicSimpleServerSession::ConsumeData)); stream_->OnStreamHeaderList(false, kFakeFrameLen, header_list_); - std::unique_ptr<char[]> buffer; - QuicByteCount header_length = - HttpEncoder::SerializeDataFrameHeader(body_.length(), &buffer); - std::string header = std::string(buffer.get(), header_length); - std::string data = UsesHttp3() ? header + body_ : body_; + QuicBuffer header = HttpEncoder::SerializeDataFrameHeader( + body_.length(), SimpleBufferAllocator::Get()); + std::string data = + UsesHttp3() ? absl::StrCat(header.AsStringView(), body_) : body_; stream_->OnStreamFrame( QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data)); EXPECT_EQ("11", StreamHeadersValue("content-length")); @@ -330,11 +326,10 @@ TEST_P(QuicSimpleServerStreamTest, TestFramingOnePacket) { Invoke(&session_, &MockQuicSimpleServerSession::ConsumeData)); stream_->OnStreamHeaderList(false, kFakeFrameLen, header_list_); - std::unique_ptr<char[]> buffer; - QuicByteCount header_length = - HttpEncoder::SerializeDataFrameHeader(body_.length(), &buffer); - std::string header = std::string(buffer.get(), header_length); - std::string data = UsesHttp3() ? header + body_ : body_; + QuicBuffer header = HttpEncoder::SerializeDataFrameHeader( + body_.length(), SimpleBufferAllocator::Get()); + std::string data = + UsesHttp3() ? absl::StrCat(header.AsStringView(), body_) : body_; stream_->OnStreamFrame( QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data)); EXPECT_EQ("11", StreamHeadersValue("content-length")); @@ -377,20 +372,20 @@ TEST_P(QuicSimpleServerStreamTest, TestFramingExtraData) { EXPECT_CALL(session_, WritevData(_, kErrorLength, _, FIN, _, _)); stream_->OnStreamHeaderList(false, kFakeFrameLen, header_list_); - std::unique_ptr<char[]> buffer; - QuicByteCount header_length = - HttpEncoder::SerializeDataFrameHeader(body_.length(), &buffer); - std::string header = std::string(buffer.get(), header_length); - std::string data = UsesHttp3() ? header + body_ : body_; + QuicBuffer header = HttpEncoder::SerializeDataFrameHeader( + body_.length(), SimpleBufferAllocator::Get()); + std::string data = + UsesHttp3() ? absl::StrCat(header.AsStringView(), body_) : body_; stream_->OnStreamFrame( QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data)); // Content length is still 11. This will register as an error and we won't // accept the bytes. - header_length = - HttpEncoder::SerializeDataFrameHeader(large_body.length(), &buffer); - header = std::string(buffer.get(), header_length); - std::string data2 = UsesHttp3() ? header + large_body : large_body; + header = HttpEncoder::SerializeDataFrameHeader(large_body.length(), + SimpleBufferAllocator::Get()); + std::string data2 = UsesHttp3() + ? absl::StrCat(header.AsStringView(), large_body) + : large_body; stream_->OnStreamFrame( QuicStreamFrame(stream_->id(), /*fin=*/true, data.size(), data2)); EXPECT_EQ("11", StreamHeadersValue("content-length")); @@ -409,9 +404,8 @@ TEST_P(QuicSimpleServerStreamTest, SendResponseWithIllegalResponseStatus) { response_headers_[":status"] = "200 OK"; response_headers_["content-length"] = "5"; std::string body = "Yummm"; - std::unique_ptr<char[]> buffer; - QuicByteCount header_length = - HttpEncoder::SerializeDataFrameHeader(body.length(), &buffer); + QuicBuffer header = HttpEncoder::SerializeDataFrameHeader( + body.length(), SimpleBufferAllocator::Get()); memory_cache_backend_.AddResponse("www.google.com", "/bar", std::move(response_headers_), body); @@ -421,7 +415,7 @@ TEST_P(QuicSimpleServerStreamTest, SendResponseWithIllegalResponseStatus) { InSequence s; EXPECT_CALL(*stream_, WriteHeadersMock(false)); if (UsesHttp3()) { - EXPECT_CALL(session_, WritevData(_, header_length, _, NO_FIN, _, _)); + EXPECT_CALL(session_, WritevData(_, header.size(), _, NO_FIN, _, _)); } EXPECT_CALL(session_, WritevData(_, kErrorLength, _, FIN, _, _)); @@ -442,9 +436,8 @@ TEST_P(QuicSimpleServerStreamTest, SendResponseWithIllegalResponseStatus2) { response_headers_["content-length"] = "5"; std::string body = "Yummm"; - std::unique_ptr<char[]> buffer; - QuicByteCount header_length = - HttpEncoder::SerializeDataFrameHeader(body.length(), &buffer); + QuicBuffer header = HttpEncoder::SerializeDataFrameHeader( + body.length(), SimpleBufferAllocator::Get()); memory_cache_backend_.AddResponse("www.google.com", "/bar", std::move(response_headers_), body); @@ -454,7 +447,7 @@ TEST_P(QuicSimpleServerStreamTest, SendResponseWithIllegalResponseStatus2) { InSequence s; EXPECT_CALL(*stream_, WriteHeadersMock(false)); if (UsesHttp3()) { - EXPECT_CALL(session_, WritevData(_, header_length, _, NO_FIN, _, _)); + EXPECT_CALL(session_, WritevData(_, header.size(), _, NO_FIN, _, _)); } EXPECT_CALL(session_, WritevData(_, kErrorLength, _, FIN, _, _)); @@ -507,9 +500,8 @@ TEST_P(QuicSimpleServerStreamTest, SendResponseWithValidHeaders) { response_headers_["content-length"] = "5"; std::string body = "Yummm"; - std::unique_ptr<char[]> buffer; - QuicByteCount header_length = - HttpEncoder::SerializeDataFrameHeader(body.length(), &buffer); + QuicBuffer header = HttpEncoder::SerializeDataFrameHeader( + body.length(), SimpleBufferAllocator::Get()); memory_cache_backend_.AddResponse("www.google.com", "/bar", std::move(response_headers_), body); @@ -518,7 +510,7 @@ TEST_P(QuicSimpleServerStreamTest, SendResponseWithValidHeaders) { InSequence s; EXPECT_CALL(*stream_, WriteHeadersMock(false)); if (UsesHttp3()) { - EXPECT_CALL(session_, WritevData(_, header_length, _, NO_FIN, _, _)); + EXPECT_CALL(session_, WritevData(_, header.size(), _, NO_FIN, _, _)); } EXPECT_CALL(session_, WritevData(_, body.length(), _, FIN, _, _)); @@ -538,9 +530,8 @@ TEST_P(QuicSimpleServerStreamTest, SendResponseWithEarlyHints) { (*request_headers)[":authority"] = host; (*request_headers)[":method"] = "GET"; - std::unique_ptr<char[]> buffer; - QuicByteCount header_length = - HttpEncoder::SerializeDataFrameHeader(body.length(), &buffer); + QuicBuffer header = HttpEncoder::SerializeDataFrameHeader( + body.length(), SimpleBufferAllocator::Get()); std::vector<spdy::Http2HeaderBlock> early_hints; // Add two Early Hints. const size_t kNumEarlyHintsResponses = 2; @@ -562,7 +553,7 @@ TEST_P(QuicSimpleServerStreamTest, SendResponseWithEarlyHints) { } EXPECT_CALL(*stream_, WriteHeadersMock(false)); if (UsesHttp3()) { - EXPECT_CALL(session_, WritevData(_, header_length, _, NO_FIN, _, _)); + EXPECT_CALL(session_, WritevData(_, header.size(), _, NO_FIN, _, _)); } EXPECT_CALL(session_, WritevData(_, body.length(), _, FIN, _, _)); @@ -608,9 +599,8 @@ TEST_P(QuicSimpleServerStreamTest, PushResponseOnServerInitiatedStream) { response_headers_[":status"] = "200"; response_headers_["content-length"] = "5"; const std::string kBody = "Hello"; - std::unique_ptr<char[]> buffer; - QuicByteCount header_length = - HttpEncoder::SerializeDataFrameHeader(kBody.length(), &buffer); + QuicBuffer header = HttpEncoder::SerializeDataFrameHeader( + body_.length(), SimpleBufferAllocator::Get()); memory_cache_backend_.AddResponse(kHost, kPath, std::move(response_headers_), kBody); @@ -620,7 +610,7 @@ TEST_P(QuicSimpleServerStreamTest, PushResponseOnServerInitiatedStream) { EXPECT_CALL(*server_initiated_stream, WriteHeadersMock(false)); if (UsesHttp3()) { - EXPECT_CALL(session_, WritevData(kServerInitiatedStreamId, header_length, _, + EXPECT_CALL(session_, WritevData(kServerInitiatedStreamId, header.size(), _, NO_FIN, _, _)); } EXPECT_CALL(session_, @@ -728,27 +718,27 @@ TEST_P(QuicSimpleServerStreamTest, TEST_P(QuicSimpleServerStreamTest, InvalidHeadersWithFin) { char arr[] = { - 0x3a, 0x68, 0x6f, 0x73, // :hos - 0x74, 0x00, 0x00, 0x00, // t... - 0x00, 0x00, 0x00, 0x00, // .... - 0x07, 0x3a, 0x6d, 0x65, // .:me - 0x74, 0x68, 0x6f, 0x64, // thod - 0x00, 0x00, 0x00, 0x03, // .... - 0x47, 0x45, 0x54, 0x00, // GET. - 0x00, 0x00, 0x05, 0x3a, // ...: - 0x70, 0x61, 0x74, 0x68, // path - 0x00, 0x00, 0x00, 0x04, // .... - 0x2f, 0x66, 0x6f, 0x6f, // /foo - 0x00, 0x00, 0x00, 0x07, // .... - 0x3a, 0x73, 0x63, 0x68, // :sch - 0x65, 0x6d, 0x65, 0x00, // eme. - 0x00, 0x00, 0x00, 0x00, // .... - 0x00, 0x00, 0x08, 0x3a, // ...: - 0x76, 0x65, 0x72, 0x73, // vers - 0x96, 0x6f, 0x6e, 0x00, // <i(69)>on. - 0x00, 0x00, 0x08, 0x48, // ...H - 0x54, 0x54, 0x50, 0x2f, // TTP/ - 0x31, 0x2e, 0x31, // 1.1 + 0x3a, 0x68, 0x6f, 0x73, // :hos + 0x74, 0x00, 0x00, 0x00, // t... + 0x00, 0x00, 0x00, 0x00, // .... + 0x07, 0x3a, 0x6d, 0x65, // .:me + 0x74, 0x68, 0x6f, 0x64, // thod + 0x00, 0x00, 0x00, 0x03, // .... + 0x47, 0x45, 0x54, 0x00, // GET. + 0x00, 0x00, 0x05, 0x3a, // ...: + 0x70, 0x61, 0x74, 0x68, // path + 0x00, 0x00, 0x00, 0x04, // .... + 0x2f, 0x66, 0x6f, 0x6f, // /foo + 0x00, 0x00, 0x00, 0x07, // .... + 0x3a, 0x73, 0x63, 0x68, // :sch + 0x65, 0x6d, 0x65, 0x00, // eme. + 0x00, 0x00, 0x00, 0x00, // .... + 0x00, 0x00, 0x08, 0x3a, // ...: + 0x76, 0x65, 0x72, 0x73, // vers + '\x96', 0x6f, 0x6e, 0x00, // <i(69)>on. + 0x00, 0x00, 0x08, 0x48, // ...H + 0x54, 0x54, 0x50, 0x2f, // TTP/ + 0x31, 0x2e, 0x31, // 1.1 }; absl::string_view data(arr, ABSL_ARRAYSIZE(arr)); QuicStreamFrame frame(stream_->id(), true, 0, data); @@ -767,11 +757,10 @@ TEST_P(QuicSimpleServerStreamTest, ConnectSendsResponseBeforeFinReceived) { header_list.OnHeaderBlockEnd(128, 128); EXPECT_CALL(*stream_, WriteHeadersMock(/*fin=*/false)); stream_->OnStreamHeaderList(/*fin=*/false, kFakeFrameLen, header_list); - std::unique_ptr<char[]> buffer; - QuicByteCount header_length = - HttpEncoder::SerializeDataFrameHeader(body_.length(), &buffer); - std::string header = std::string(buffer.get(), header_length); - std::string data = UsesHttp3() ? header + body_ : body_; + QuicBuffer header = HttpEncoder::SerializeDataFrameHeader( + body_.length(), SimpleBufferAllocator::Get()); + std::string data = + UsesHttp3() ? absl::StrCat(header.AsStringView(), body_) : body_; stream_->OnStreamFrame( QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data)); EXPECT_EQ("CONNECT-SILLY", StreamHeadersValue(":method")); @@ -793,11 +782,10 @@ TEST_P(QuicSimpleServerStreamTest, ConnectWithInvalidHeader) { header_list.OnHeaderBlockEnd(128, 128); EXPECT_CALL(*stream_, WriteHeadersMock(/*fin=*/false)); stream_->OnStreamHeaderList(/*fin=*/false, kFakeFrameLen, header_list); - std::unique_ptr<char[]> buffer; - QuicByteCount header_length = - HttpEncoder::SerializeDataFrameHeader(body_.length(), &buffer); - std::string header = std::string(buffer.get(), header_length); - std::string data = UsesHttp3() ? header + body_ : body_; + QuicBuffer header = HttpEncoder::SerializeDataFrameHeader( + body_.length(), SimpleBufferAllocator::Get()); + std::string data = + UsesHttp3() ? absl::StrCat(header.AsStringView(), body_) : body_; stream_->OnStreamFrame( QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data)); EXPECT_EQ("CONNECT-SILLY", StreamHeadersValue(":method")); diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_spdy_client_base.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_spdy_client_base.cc index 4f51eb47eba..464fa70fda7 100644 --- a/chromium/net/third_party/quiche/src/quic/tools/quic_spdy_client_base.cc +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_spdy_client_base.cc @@ -67,6 +67,10 @@ const QuicSpdyClientSession* QuicSpdyClientBase::client_session() const { } void QuicSpdyClientBase::InitializeSession() { + if (max_inbound_header_list_size_ > 0) { + client_session()->set_max_inbound_header_list_size( + max_inbound_header_list_size_); + } client_session()->Initialize(); client_session()->CryptoConnect(); } diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_spdy_client_base.h b/chromium/net/third_party/quiche/src/quic/tools/quic_spdy_client_base.h index 2ba55f11693..c49d0ed84a5 100644 --- a/chromium/net/third_party/quiche/src/quic/tools/quic_spdy_client_base.h +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_spdy_client_base.h @@ -149,6 +149,10 @@ class QuicSpdyClientBase : public QuicClientBase, bool EarlyDataAccepted() override; bool ReceivedInchoateReject() override; + void set_max_inbound_header_list_size(size_t size) { + max_inbound_header_list_size_ = size; + } + protected: int GetNumSentClientHellosFromSession() override; int GetNumReceivedServerConfigUpdatesFromSession() override; @@ -223,6 +227,9 @@ class QuicSpdyClientBase : public QuicClientBase, bool drop_response_body_ = false; bool enable_web_transport_ = false; + // If not zero, used to set client's max inbound header size before session + // initialize. + size_t max_inbound_header_list_size_ = 0; }; } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_toy_client.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_toy_client.cc index 8d227913ce5..0db266b9e11 100644 --- a/chromium/net/third_party/quiche/src/quic/tools/quic_toy_client.cc +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_toy_client.cc @@ -211,6 +211,13 @@ DEFINE_QUIC_COMMAND_LINE_FLAG(int32_t, -1, "Length of the client connection ID used."); +DEFINE_QUIC_COMMAND_LINE_FLAG(int32_t, max_time_before_crypto_handshake_ms, + 10000, + "Max time to wait before handshake completes."); + +DEFINE_QUIC_COMMAND_LINE_FLAG(int32_t, max_inbound_header_list_size, 128 * 1024, + "Max inbound header list size. 0 means default."); + namespace quic { QuicToyClient::QuicToyClient(ClientFactory* client_factory) @@ -293,6 +300,8 @@ int QuicToyClient::SendRequestsAndPrintResponses( config.custom_transport_parameters_to_send()[kCustomParameter] = custom_value; } + config.set_max_time_before_crypto_handshake(QuicTime::Delta::FromMilliseconds( + GetQuicFlag(FLAGS_max_time_before_crypto_handshake_ms))); int address_family_for_lookup = AF_UNSPEC; if (GetQuicFlag(FLAGS_ip_version_for_host_lookup) == "4") { @@ -325,6 +334,11 @@ int QuicToyClient::SendRequestsAndPrintResponses( if (client_connection_id_length >= 0) { client->set_client_connection_id_length(client_connection_id_length); } + const size_t max_inbound_header_list_size = + GetQuicFlag(FLAGS_max_inbound_header_list_size); + if (max_inbound_header_list_size > 0) { + client->set_max_inbound_header_list_size(max_inbound_header_list_size); + } if (!client->Initialize()) { std::cerr << "Failed to initialize client." << std::endl; return 1; diff --git a/chromium/net/third_party/quiche/src/quic/tools/simple_ticket_crypter.cc b/chromium/net/third_party/quiche/src/quic/tools/simple_ticket_crypter.cc index 0130352ecfd..149adda20ff 100644 --- a/chromium/net/third_party/quiche/src/quic/tools/simple_ticket_crypter.cc +++ b/chromium/net/third_party/quiche/src/quic/tools/simple_ticket_crypter.cc @@ -38,7 +38,12 @@ size_t SimpleTicketCrypter::MaxOverhead() { return kEpochSize + kIVSize + kAuthTagSize; } -std::vector<uint8_t> SimpleTicketCrypter::Encrypt(absl::string_view in) { +std::vector<uint8_t> SimpleTicketCrypter::Encrypt( + absl::string_view in, absl::string_view encryption_key) { + // This class is only used in Chromium, in which the |encryption_key| argument + // will never be populated and an internally-cached key should be used for + // encrypting tickets. + QUICHE_DCHECK(encryption_key.empty()); MaybeRotateKeys(); std::vector<uint8_t> out(in.size() + MaxOverhead()); out[0] = key_epoch_; diff --git a/chromium/net/third_party/quiche/src/quic/tools/simple_ticket_crypter.h b/chromium/net/third_party/quiche/src/quic/tools/simple_ticket_crypter.h index d547a25bf9e..052bc5872a3 100644 --- a/chromium/net/third_party/quiche/src/quic/tools/simple_ticket_crypter.h +++ b/chromium/net/third_party/quiche/src/quic/tools/simple_ticket_crypter.h @@ -24,7 +24,8 @@ class QUIC_NO_EXPORT SimpleTicketCrypter ~SimpleTicketCrypter() override; size_t MaxOverhead() override; - std::vector<uint8_t> Encrypt(absl::string_view in) override; + std::vector<uint8_t> Encrypt(absl::string_view in, + absl::string_view encryption_key) override; void Decrypt( absl::string_view in, std::unique_ptr<quic::ProofSource::DecryptCallback> callback) override; diff --git a/chromium/net/third_party/quiche/src/quic/tools/simple_ticket_crypter_test.cc b/chromium/net/third_party/quiche/src/quic/tools/simple_ticket_crypter_test.cc index ad041c10de8..be71018de23 100644 --- a/chromium/net/third_party/quiche/src/quic/tools/simple_ticket_crypter_test.cc +++ b/chromium/net/third_party/quiche/src/quic/tools/simple_ticket_crypter_test.cc @@ -42,7 +42,7 @@ class SimpleTicketCrypterTest : public QuicTest { TEST_F(SimpleTicketCrypterTest, EncryptDecrypt) { std::vector<uint8_t> plaintext = {1, 2, 3, 4, 5}; std::vector<uint8_t> ciphertext = - ticket_crypter_.Encrypt(StringPiece(plaintext)); + ticket_crypter_.Encrypt(StringPiece(plaintext), {}); EXPECT_NE(plaintext, ciphertext); std::vector<uint8_t> out_plaintext; @@ -54,16 +54,16 @@ TEST_F(SimpleTicketCrypterTest, EncryptDecrypt) { TEST_F(SimpleTicketCrypterTest, CiphertextsDiffer) { std::vector<uint8_t> plaintext = {1, 2, 3, 4, 5}; std::vector<uint8_t> ciphertext1 = - ticket_crypter_.Encrypt(StringPiece(plaintext)); + ticket_crypter_.Encrypt(StringPiece(plaintext), {}); std::vector<uint8_t> ciphertext2 = - ticket_crypter_.Encrypt(StringPiece(plaintext)); + ticket_crypter_.Encrypt(StringPiece(plaintext), {}); EXPECT_NE(ciphertext1, ciphertext2); } TEST_F(SimpleTicketCrypterTest, DecryptionFailureWithModifiedCiphertext) { std::vector<uint8_t> plaintext = {1, 2, 3, 4, 5}; std::vector<uint8_t> ciphertext = - ticket_crypter_.Encrypt(StringPiece(plaintext)); + ticket_crypter_.Encrypt(StringPiece(plaintext), {}); EXPECT_NE(plaintext, ciphertext); // Check that a bit flip in any byte will cause a decryption failure. @@ -88,7 +88,7 @@ TEST_F(SimpleTicketCrypterTest, DecryptionFailureWithEmptyCiphertext) { TEST_F(SimpleTicketCrypterTest, KeyRotation) { std::vector<uint8_t> plaintext = {1, 2, 3}; std::vector<uint8_t> ciphertext = - ticket_crypter_.Encrypt(StringPiece(plaintext)); + ticket_crypter_.Encrypt(StringPiece(plaintext), {}); EXPECT_FALSE(ciphertext.empty()); // Advance the clock 8 days, so the key used for |ciphertext| is now the diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.cc index 3fd6ebcf87d..03db87d2e51 100644 --- a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.cc +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.cc @@ -7,7 +7,6 @@ #include "http2/decoder/decode_buffer.h" #include "http2/decoder/decode_status.h" #include "common/platform/api/quiche_logging.h" -#include "spdy/platform/api/spdy_estimate_memory_usage.h" using ::http2::DecodeBuffer; @@ -118,10 +117,6 @@ void HpackDecoderAdapter::set_max_header_block_bytes( max_header_block_bytes_ = max_header_block_bytes; } -size_t HpackDecoderAdapter::EstimateMemoryUsage() const { - return SpdyEstimateMemoryUsage(hpack_decoder_); -} - HpackDecoderAdapter::ListenerAdapter::ListenerAdapter() : handler_(nullptr) {} HpackDecoderAdapter::ListenerAdapter::~ListenerAdapter() = default; diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.h b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.h index 98d10fe2c9a..43a762bb757 100644 --- a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.h +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.h @@ -67,6 +67,12 @@ class QUICHE_EXPORT_PRIVATE HpackDecoderAdapter { // a SpdyHeadersHandlerInterface. const SpdyHeaderBlock& decoded_block() const; + // Returns the current dynamic table size, including the 32 bytes per entry + // overhead mentioned in RFC 7541 section 4.1. + size_t GetDynamicTableSize() const { + return hpack_decoder_.GetDynamicTableSize(); + } + // Set how much encoded data this decoder is willing to buffer. // TODO(jamessynge): Resolve definition of this value, as it is currently // too tied to a single implementation. We probably want to limit one or more @@ -79,8 +85,6 @@ class QUICHE_EXPORT_PRIVATE HpackDecoderAdapter { // accepted. void set_max_header_block_bytes(size_t max_header_block_bytes); - size_t EstimateMemoryUsage() const; - // Error code if an error has occurred, Error::kOk otherwise. http2::HpackDecodingError error() const { return error_; } diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter_test.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter_test.cc index d223ccf9f5a..bab6c481e78 100644 --- a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter_test.cc +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter_test.cc @@ -21,12 +21,12 @@ #include "http2/test_tools/http2_random.h" #include "common/platform/api/quiche_logging.h" #include "common/platform/api/quiche_test.h" +#include "common/quiche_text_utils.h" #include "spdy/core/hpack/hpack_constants.h" #include "spdy/core/hpack/hpack_encoder.h" #include "spdy/core/hpack/hpack_output_stream.h" #include "spdy/core/recording_headers_handler.h" #include "spdy/core/spdy_test_utils.h" -#include "spdy/platform/api/spdy_string_utils.h" using ::http2::HpackEntryType; using ::http2::HpackStringPair; @@ -135,7 +135,8 @@ class HpackDecoderAdapterTest } bool HandleControlFrameHeadersData(absl::string_view str) { - QUICHE_VLOG(3) << "HandleControlFrameHeadersData:\n" << SpdyHexDump(str); + QUICHE_VLOG(3) << "HandleControlFrameHeadersData:\n" + << quiche::QuicheTextUtils::HexDump(str); bytes_passed_in_ += str.size(); return decoder_.HandleControlFrameHeadersData(str.data(), str.size()); } diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.cc index 27f0444f274..ccf17da20b2 100644 --- a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.cc +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.cc @@ -14,7 +14,6 @@ #include "spdy/core/hpack/hpack_constants.h" #include "spdy/core/hpack/hpack_header_table.h" #include "spdy/core/hpack/hpack_output_stream.h" -#include "spdy/platform/api/spdy_estimate_memory_usage.h" namespace spdy { @@ -124,11 +123,6 @@ void HpackEncoder::ApplyHeaderTableSizeSetting(size_t size_setting) { should_emit_table_size_ = true; } -size_t HpackEncoder::EstimateMemoryUsage() const { - return SpdyEstimateMemoryUsage(header_table_) + - SpdyEstimateMemoryUsage(output_stream_); -} - void HpackEncoder::EncodeRepresentations(RepresentationIterator* iter, std::string* output) { MaybeEmitTableSize(); diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.h b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.h index d85b98ede0b..5e2ea03980c 100644 --- a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.h +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.h @@ -95,8 +95,9 @@ class QUICHE_EXPORT_PRIVATE HpackEncoder { void DisableCompression() { enable_compression_ = false; } - // Returns the estimate of dynamically allocated memory in bytes. - size_t EstimateMemoryUsage() const; + // Returns the current dynamic table size, including the 32 bytes per entry + // overhead mentioned in RFC 7541 section 4.1. + size_t GetDynamicTableSize() const { return header_table_.size(); } private: friend class test::HpackEncoderPeer; diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder_test.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder_test.cc index 774a06e8129..dfd8d8fea2c 100644 --- a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder_test.cc +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder_test.cc @@ -155,6 +155,7 @@ class HpackEncoderTest : public QuicheTestWithParam<EncodeStrategy> { // No further insertions may occur without evictions. peer_.table()->SetMaxSize(peer_.table()->size()); + QUICHE_CHECK_EQ(kInitialDynamicTableSize, peer_.table()->size()); } void SaveHeaders(absl::string_view name, absl::string_view value) { @@ -254,6 +255,9 @@ class HpackEncoderTest : public QuicheTestWithParam<EncodeStrategy> { HpackEncoder encoder_; test::HpackEncoderPeer peer_; + // Calculated based on the names and values inserted in SetUp(), above. + const size_t kInitialDynamicTableSize = 4 * (10 + 32); + const HpackEntry* static_; const HpackEntry* key_1_; const HpackEntry* key_2_; @@ -280,6 +284,7 @@ INSTANTIATE_TEST_SUITE_P(HpackEncoderTests, ::testing::Values(kDefault)); TEST_P(HpackEncoderTestWithDefaultStrategy, EncodeRepresentations) { + EXPECT_EQ(kInitialDynamicTableSize, encoder_.GetDynamicTableSize()); encoder_.SetHeaderListener( [this](absl::string_view name, absl::string_view value) { this->SaveHeaders(name, value); @@ -308,6 +313,30 @@ TEST_P(HpackEncoderTestWithDefaultStrategy, EncodeRepresentations) { Pair("accept", "text/html, text/plain,application/xml"), Pair("cookie", "val4"), Pair("withnul", absl::string_view("one\0two", 7)))); + // Insertions and evictions have happened over the course of the test. + EXPECT_GE(kInitialDynamicTableSize, encoder_.GetDynamicTableSize()); +} + +TEST_P(HpackEncoderTestWithDefaultStrategy, DynamicTableGrows) { + EXPECT_EQ(kInitialDynamicTableSize, encoder_.GetDynamicTableSize()); + peer_.table()->SetMaxSize(4096); + encoder_.SetHeaderListener( + [this](absl::string_view name, absl::string_view value) { + this->SaveHeaders(name, value); + }); + const std::vector<std::pair<absl::string_view, absl::string_view>> + header_list = {{"cookie", "val1; val2;val3"}, + {":path", "/home"}, + {"accept", "text/html, text/plain,application/xml"}, + {"cookie", "val4"}, + {"withnul", absl::string_view("one\0two", 7)}}; + std::string out; + EXPECT_TRUE(test::HpackEncoderPeer::EncodeRepresentations(&encoder_, + header_list, &out)); + + EXPECT_FALSE(out.empty()); + // Insertions have happened over the course of the test. + EXPECT_GT(encoder_.GetDynamicTableSize(), kInitialDynamicTableSize); } INSTANTIATE_TEST_SUITE_P(HpackEncoderTests, @@ -479,6 +508,7 @@ TEST_P(HpackEncoderTest, EncodingWithoutCompression) { Pair("hello", "aloha"), Pair("multivalue", "value1, value2"))); } + EXPECT_EQ(kInitialDynamicTableSize, encoder_.GetDynamicTableSize()); } TEST_P(HpackEncoderTest, MultipleEncodingPasses) { diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_entry.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_entry.cc index 4deb0dfcc81..d755214b995 100644 --- a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_entry.cc +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_entry.cc @@ -5,7 +5,6 @@ #include "spdy/core/hpack/hpack_entry.h" #include "absl/strings/str_cat.h" -#include "spdy/platform/api/spdy_estimate_memory_usage.h" namespace spdy { @@ -24,8 +23,4 @@ std::string HpackEntry::GetDebugString() const { return absl::StrCat("{ name: \"", name_, "\", value: \"", value_, "\" }"); } -size_t HpackEntry::EstimateMemoryUsage() const { - return SpdyEstimateMemoryUsage(name_) + SpdyEstimateMemoryUsage(value_); -} - } // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_entry.h b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_entry.h index 0c263584f66..86e39779b7d 100644 --- a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_entry.h +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_entry.h @@ -71,9 +71,6 @@ class QUICHE_EXPORT_PRIVATE HpackEntry { std::string GetDebugString() const; - // Returns the estimate of dynamically allocated memory in bytes. - size_t EstimateMemoryUsage() const; - private: std::string name_; std::string value_; diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.cc index 8536a8523fc..6358003c2ba 100644 --- a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.cc +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.cc @@ -9,7 +9,6 @@ #include "common/platform/api/quiche_logging.h" #include "spdy/core/hpack/hpack_constants.h" #include "spdy/core/hpack/hpack_static_table.h" -#include "spdy/platform/api/spdy_estimate_memory_usage.h" namespace spdy { @@ -186,10 +185,4 @@ const HpackEntry* HpackHeaderTable::TryAddEntry(absl::string_view name, return &dynamic_entries_.front(); } -size_t HpackHeaderTable::EstimateMemoryUsage() const { - return SpdyEstimateMemoryUsage(dynamic_entries_) + - SpdyEstimateMemoryUsage(dynamic_index_) + - SpdyEstimateMemoryUsage(dynamic_name_index_); -} - } // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h index 49478cd35b2..427fc857ef5 100644 --- a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h @@ -98,9 +98,6 @@ class QUICHE_EXPORT_PRIVATE HpackHeaderTable { const HpackEntry* TryAddEntry(absl::string_view name, absl::string_view value); - // Returns the estimate of dynamically allocated memory in bytes. - size_t EstimateMemoryUsage() const; - private: // Returns number of evictions required to enter |name| & |value|. size_t EvictionCountForEntry(absl::string_view name, diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.cc index 7b925680d4b..bce24fa610d 100644 --- a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.cc +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.cc @@ -7,7 +7,6 @@ #include <utility> #include "common/platform/api/quiche_logging.h" -#include "spdy/platform/api/spdy_estimate_memory_usage.h" namespace spdy { @@ -97,8 +96,4 @@ void HpackOutputStream::BoundedTakeString(size_t max_size, } } -size_t HpackOutputStream::EstimateMemoryUsage() const { - return SpdyEstimateMemoryUsage(buffer_); -} - } // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h index 59e3bb33a42..ec657b54064 100644 --- a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h @@ -61,9 +61,6 @@ class QUICHE_EXPORT_PRIVATE HpackOutputStream { // Size in bytes of stream's internal buffer. size_t size() const { return buffer_.size(); } - // Returns the estimate of dynamically allocated memory in bytes. - size_t EstimateMemoryUsage() const; - private: // The internal bit buffer. std::string buffer_; diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.cc index 7594943b43e..e5a403d7162 100644 --- a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.cc +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.cc @@ -8,7 +8,6 @@ #include "common/platform/api/quiche_logging.h" #include "spdy/core/hpack/hpack_constants.h" #include "spdy/core/hpack/hpack_entry.h" -#include "spdy/platform/api/spdy_estimate_memory_usage.h" namespace spdy { @@ -48,10 +47,4 @@ bool HpackStaticTable::IsInitialized() const { return !static_entries_.empty(); } -size_t HpackStaticTable::EstimateMemoryUsage() const { - return SpdyEstimateMemoryUsage(static_entries_) + - SpdyEstimateMemoryUsage(static_index_) + - SpdyEstimateMemoryUsage(static_name_index_); -} - } // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.h b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.h index 346332e4134..307fc1cc990 100644 --- a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.h +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.h @@ -43,9 +43,6 @@ class QUICHE_EXPORT_PRIVATE HpackStaticTable { return static_name_index_; } - // Returns the estimate of dynamically allocated memory in bytes. - size_t EstimateMemoryUsage() const; - private: HpackHeaderTable::StaticEntryTable static_entries_; // The following two members have string_views that point to strings stored in diff --git a/chromium/net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.cc b/chromium/net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.cc index b20d0032aff..e2e311dda07 100644 --- a/chromium/net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.cc +++ b/chromium/net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.cc @@ -30,8 +30,6 @@ #include "spdy/core/spdy_header_block.h" #include "spdy/core/spdy_headers_handler_interface.h" #include "spdy/core/spdy_protocol.h" -#include "spdy/platform/api/spdy_estimate_memory_usage.h" -#include "spdy/platform/api/spdy_string_utils.h" using ::spdy::ExtensionVisitorInterface; using ::spdy::HpackDecoderAdapter; @@ -40,7 +38,6 @@ using ::spdy::ParseErrorCode; using ::spdy::ParseFrameType; using ::spdy::SpdyAltSvcWireFormat; using ::spdy::SpdyErrorCode; -using ::spdy::SpdyEstimateMemoryUsage; using ::spdy::SpdyFramerDebugVisitorInterface; using ::spdy::SpdyFramerVisitorInterface; using ::spdy::SpdyFrameType; @@ -249,6 +246,8 @@ const char* Http2DecoderAdapter::SpdyFramerErrorToString( return "HPACK_FRAGMENT_TOO_LONG"; case SPDY_HPACK_COMPRESSED_HEADER_SIZE_EXCEEDS_LIMIT: return "HPACK_COMPRESSED_HEADER_SIZE_EXCEEDS_LIMIT"; + case SPDY_STOP_PROCESSING: + return "STOP_PROCESSING"; case LAST_ERROR: return "UNKNOWN_ERROR"; } @@ -320,11 +319,9 @@ bool Http2DecoderAdapter::probable_http_response() const { return latched_probable_http_response_; } -size_t Http2DecoderAdapter::EstimateMemoryUsage() const { - // Skip |frame_decoder_|, |frame_header_| and |hpack_first_frame_header_| as - // they don't allocate. - return SpdyEstimateMemoryUsage(alt_svc_origin_) + - SpdyEstimateMemoryUsage(alt_svc_value_); +void Http2DecoderAdapter::StopProcessing() { + SetSpdyErrorAndNotify(SpdyFramerError::SPDY_STOP_PROCESSING, + "Ignoring further events on this connection."); } // =========================================================================== @@ -784,12 +781,12 @@ void Http2DecoderAdapter::OnFrameSizeError(const Http2FrameHeader& header) { QUICHE_DVLOG(1) << "OnFrameSizeError: " << header; size_t recv_limit = recv_frame_size_limit_; if (header.payload_length > recv_limit) { - SetSpdyErrorAndNotify(SpdyFramerError::SPDY_OVERSIZED_PAYLOAD, ""); - return; - } - if (header.type != Http2FrameType::DATA && - header.payload_length > recv_limit) { - SetSpdyErrorAndNotify(SpdyFramerError::SPDY_CONTROL_PAYLOAD_TOO_LARGE, ""); + if (header.type == Http2FrameType::DATA) { + SetSpdyErrorAndNotify(SpdyFramerError::SPDY_OVERSIZED_PAYLOAD, ""); + } else { + SetSpdyErrorAndNotify(SpdyFramerError::SPDY_CONTROL_PAYLOAD_TOO_LARGE, + ""); + } return; } switch (header.type) { diff --git a/chromium/net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.h b/chromium/net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.h index f05152b6468..81f12572c35 100644 --- a/chromium/net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.h +++ b/chromium/net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.h @@ -95,6 +95,10 @@ class QUICHE_EXPORT_PRIVATE Http2DecoderAdapter SPDY_HPACK_FRAGMENT_TOO_LONG, SPDY_HPACK_COMPRESSED_HEADER_SIZE_EXCEEDS_LIMIT, + // Set if the visitor no longer wishes to receive events for this + // connection. + SPDY_STOP_PROCESSING, + LAST_ERROR, // Must be the last entry in the enum. }; @@ -153,13 +157,17 @@ class QUICHE_EXPORT_PRIVATE Http2DecoderAdapter // has responded with an HTTP/1.1 (or earlier) response. bool probable_http_response() const; - // Returns the estimate of dynamically allocated memory in bytes. - size_t EstimateMemoryUsage() const; - spdy::HpackDecoderAdapter* GetHpackDecoder(); + const spdy::HpackDecoderAdapter* GetHpackDecoder() const { + return hpack_decoder_.get(); + } bool HasError() const; + // A visitor may call this method to indicate it no longer wishes to receive + // events for this connection. + void StopProcessing(); + private: bool OnFrameHeader(const Http2FrameHeader& header) override; void OnDataStart(const Http2FrameHeader& header) override; diff --git a/chromium/net/third_party/quiche/src/spdy/core/http2_header_block_hpack_listener.h b/chromium/net/third_party/quiche/src/spdy/core/http2_header_block_hpack_listener.h new file mode 100644 index 00000000000..19352e54058 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/http2_header_block_hpack_listener.h @@ -0,0 +1,47 @@ +#ifndef QUICHE_SPDY_CORE_HTTP2_HEADER_BLOCK_HPACK_LISTENER_H_ +#define QUICHE_SPDY_CORE_HTTP2_HEADER_BLOCK_HPACK_LISTENER_H_ + +#include "absl/strings/string_view.h" +#include "http2/hpack/decoder/hpack_decoder_listener.h" +#include "common/platform/api/quiche_logging.h" +#include "spdy/core/spdy_header_block.h" + +namespace spdy { + +// This class simply gathers the key-value pairs emitted by an HpackDecoder in +// a SpdyHeaderBlock. +class Http2HeaderBlockHpackListener : public http2::HpackDecoderListener { + public: + Http2HeaderBlockHpackListener() {} + + void OnHeaderListStart() override { + header_block_.clear(); + hpack_error_ = false; + } + + void OnHeader(const std::string& name, const std::string& value) override { + header_block_.AppendValueOrAddHeader(name, value); + } + + void OnHeaderListEnd() override {} + + void OnHeaderErrorDetected(absl::string_view error_message) override { + QUICHE_VLOG(1) << error_message; + hpack_error_ = true; + } + + SpdyHeaderBlock release_header_block() { + SpdyHeaderBlock block = std::move(header_block_); + header_block_ = {}; + return block; + } + bool hpack_error() const { return hpack_error_; } + + private: + SpdyHeaderBlock header_block_; + bool hpack_error_ = false; +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_HTTP2_HEADER_BLOCK_HPACK_LISTENER_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/metadata_extension.cc b/chromium/net/third_party/quiche/src/spdy/core/metadata_extension.cc new file mode 100644 index 00000000000..12f6af14843 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/metadata_extension.cc @@ -0,0 +1,195 @@ +#include "spdy/core/metadata_extension.h" + +#include <list> +#include <string> + +#include "absl/memory/memory.h" +#include "absl/strings/str_cat.h" +#include "http2/decoder/decode_buffer.h" +#include "http2/hpack/decoder/hpack_decoder.h" +#include "common/platform/api/quiche_bug_tracker.h" +#include "common/platform/api/quiche_logging.h" +#include "spdy/core/hpack/hpack_encoder.h" +#include "spdy/core/http2_header_block_hpack_listener.h" + +namespace spdy { + +// Non-standard constants related to METADATA frames. +const SpdySettingsId MetadataVisitor::kMetadataExtensionId = 0x4d44; +const uint8_t MetadataVisitor::kMetadataFrameType = 0x4d; +const uint8_t MetadataVisitor::kEndMetadataFlag = 0x4; + +namespace { + +const size_t kMaxMetadataBlockSize = 1 << 20; // 1 MB + +// This class uses an HpackEncoder to serialize a METADATA block as a series of +// METADATA frames. +class MetadataFrameSequence : public MetadataSerializer::FrameSequence { + public: + MetadataFrameSequence(SpdyStreamId stream_id, spdy::SpdyHeaderBlock payload) + : stream_id_(stream_id), payload_(std::move(payload)) { + // Metadata should not use HPACK compression. + encoder_.DisableCompression(); + HpackEncoder::Representations r; + for (const auto& kv_pair : payload_) { + r.push_back(kv_pair); + } + progressive_encoder_ = encoder_.EncodeRepresentations(r); + } + + // Copies are not allowed. + MetadataFrameSequence(const MetadataFrameSequence& other) = delete; + MetadataFrameSequence& operator=(const MetadataFrameSequence& other) = delete; + + std::unique_ptr<spdy::SpdyFrameIR> Next() override; + + private: + SpdyStreamId stream_id_; + SpdyHeaderBlock payload_; + HpackEncoder encoder_; + std::unique_ptr<HpackEncoder::ProgressiveEncoder> progressive_encoder_; +}; + +std::unique_ptr<spdy::SpdyFrameIR> MetadataFrameSequence::Next() { + if (!progressive_encoder_->HasNext()) { + return nullptr; + } + std::string payload; + // METADATA frames obey the HTTP/2 maximum frame size. + progressive_encoder_->Next(spdy::kHttp2DefaultFramePayloadLimit, &payload); + const bool end_metadata = (!progressive_encoder_->HasNext()); + const uint8_t flags = end_metadata ? MetadataVisitor::kEndMetadataFlag : 0; + return absl::make_unique<spdy::SpdyUnknownIR>( + stream_id_, MetadataVisitor::kMetadataFrameType, flags, + std::move(payload)); +} + +} // anonymous namespace + +struct MetadataVisitor::MetadataPayloadState { + MetadataPayloadState(size_t remaining, bool end) + : bytes_remaining(remaining), end_metadata(end) {} + std::list<std::string> buffer; + size_t bytes_remaining; + bool end_metadata; +}; + +MetadataVisitor::MetadataVisitor(OnCompletePayload on_payload, + OnMetadataSupport on_support) + : on_payload_(std::move(on_payload)), + on_support_(std::move(on_support)), + peer_supports_metadata_(MetadataSupportState::UNSPECIFIED) {} + +MetadataVisitor::~MetadataVisitor() {} + +void MetadataVisitor::OnSetting(SpdySettingsId id, uint32_t value) { + QUICHE_VLOG(1) << "MetadataVisitor::OnSetting(" << id << ", " << value << ")"; + if (id == kMetadataExtensionId) { + if (value == 0) { + const MetadataSupportState previous_state = peer_supports_metadata_; + peer_supports_metadata_ = MetadataSupportState::NOT_SUPPORTED; + if (previous_state == MetadataSupportState::UNSPECIFIED || + previous_state == MetadataSupportState::SUPPORTED) { + on_support_(false); + } + } else if (value == 1) { + const MetadataSupportState previous_state = peer_supports_metadata_; + peer_supports_metadata_ = MetadataSupportState::SUPPORTED; + if (previous_state == MetadataSupportState::UNSPECIFIED || + previous_state == MetadataSupportState::NOT_SUPPORTED) { + on_support_(true); + } + } else { + LOG_EVERY_N_SEC(WARNING, 1) + << "Unrecognized value for setting " << id << ": " << value; + } + } +} + +bool MetadataVisitor::OnFrameHeader(SpdyStreamId stream_id, size_t length, + uint8_t type, uint8_t flags) { + QUICHE_VLOG(1) << "OnFrameHeader(stream_id=" << stream_id + << ", length=" << length << ", type=" << static_cast<int>(type) + << ", flags=" << static_cast<int>(flags); + // TODO(birenroy): Consider disabling METADATA handling until our setting + // advertising METADATA support has been acked. + if (type != kMetadataFrameType) { + return false; + } + auto it = metadata_map_.find(stream_id); + if (it == metadata_map_.end()) { + auto state = absl::make_unique<MetadataPayloadState>( + length, flags & kEndMetadataFlag); + auto result = metadata_map_.insert(std::make_pair(stream_id, + std::move(state))); + QUICHE_BUG_IF(bug_if_2781_1, !result.second) << "Map insertion failed."; + it = result.first; + } else { + QUICHE_BUG_IF(bug_22051_1, it->second->end_metadata) + << "Inconsistent metadata payload state!"; + QUICHE_BUG_IF(bug_if_2781_2, it->second->bytes_remaining > 0) + << "Incomplete metadata block!"; + } + + if (it->second == nullptr) { + QUICHE_BUG(bug_2781_3) << "Null metadata payload state!"; + return false; + } + current_stream_ = stream_id; + it->second->bytes_remaining = length; + it->second->end_metadata = (flags & kEndMetadataFlag); + return true; +} + +void MetadataVisitor::OnFramePayload(const char* data, size_t len) { + QUICHE_VLOG(1) << "OnFramePayload(stream_id=" << current_stream_ + << ", len=" << len << ")"; + auto it = metadata_map_.find(current_stream_); + if (it == metadata_map_.end() || it->second == nullptr) { + QUICHE_BUG(bug_2781_4) << "Invalid order of operations on MetadataVisitor."; + } else { + MetadataPayloadState* state = it->second.get(); // For readability. + state->buffer.push_back(std::string(data, len)); + if (len < state->bytes_remaining) { + state->bytes_remaining -= len; + } else { + QUICHE_BUG_IF(bug_22051_2, len > state->bytes_remaining) + << "Metadata payload overflow! len: " << len + << " bytes_remaining: " << state->bytes_remaining; + state->bytes_remaining = 0; + if (state->end_metadata) { + // The whole process of decoding the HPACK-encoded metadata block, + // below, is more cumbersome than it ought to be. + spdy::Http2HeaderBlockHpackListener listener; + http2::HpackDecoder decoder(&listener, kMaxMetadataBlockSize); + + // If any operations fail, the decode process should be aborted. + bool success = decoder.StartDecodingBlock(); + for (const std::string& slice : state->buffer) { + if (!success) { + break; + } + http2::DecodeBuffer buffer(slice.data(), slice.size()); + success = success && decoder.DecodeFragment(&buffer); + } + success = + success && decoder.EndDecodingBlock() && !listener.hpack_error(); + if (success) { + on_payload_(current_stream_, listener.release_header_block()); + } + // TODO(birenroy): add varz counting metadata decode successes/failures. + metadata_map_.erase(it); + } + } + } +} + +std::unique_ptr<MetadataSerializer::FrameSequence> +MetadataSerializer::FrameSequenceForPayload(SpdyStreamId stream_id, + MetadataPayload payload) { + return absl::make_unique<MetadataFrameSequence>(stream_id, + std::move(payload)); +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/metadata_extension.h b/chromium/net/third_party/quiche/src/spdy/core/metadata_extension.h new file mode 100644 index 00000000000..043d6f0ebcd --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/metadata_extension.h @@ -0,0 +1,116 @@ +#ifndef QUICHE_SPDY_CORE_METADATA_EXTENSION_H_ +#define QUICHE_SPDY_CORE_METADATA_EXTENSION_H_ + +#include <memory> +#include <string> +#include <vector> + +#include "absl/container/flat_hash_map.h" +#include "spdy/core/http2_frame_decoder_adapter.h" +#include "spdy/core/spdy_header_block.h" +#include "spdy/core/spdy_protocol.h" +#include "spdy/core/zero_copy_output_buffer.h" + +namespace spdy { + +// An implementation of the ExtensionVisitorInterface that can parse +// METADATA frames. METADATA is a non-standard HTTP/2 extension developed and +// used internally at Google. A peer advertises support for METADATA by sending +// a setting with a setting ID of kMetadataExtensionId and a value of 1. +// +// Metadata is represented as a HPACK header block with literal encoding. +class MetadataVisitor : public spdy::ExtensionVisitorInterface { + public: + using MetadataPayload = spdy::SpdyHeaderBlock; + + static_assert(!std::is_copy_constructible<MetadataPayload>::value, + "MetadataPayload should be a move-only type!"); + + using OnMetadataSupport = std::function<void(bool)>; + using OnCompletePayload = + std::function<void(spdy::SpdyStreamId, MetadataPayload)>; + + // The HTTP/2 SETTINGS ID that is used to indicate support for METADATA + // frames. + static const spdy::SpdySettingsId kMetadataExtensionId; + + // The 8-bit frame type code for a METADATA frame. + static const uint8_t kMetadataFrameType; + + // The flag that indicates the end of a logical metadata block. Due to frame + // size limits, a single metadata block may be emitted as several HTTP/2 + // frames. + static const uint8_t kEndMetadataFlag; + + // |on_payload| is invoked whenever a complete metadata payload is received. + // |on_support| is invoked whenever the peer's advertised support for metadata + // changes. + MetadataVisitor(OnCompletePayload on_payload, OnMetadataSupport on_support); + ~MetadataVisitor() override; + + MetadataVisitor(const MetadataVisitor&) = delete; + MetadataVisitor& operator=(const MetadataVisitor&) = delete; + + // Interprets the non-standard setting indicating support for METADATA. + void OnSetting(spdy::SpdySettingsId id, uint32_t value) override; + + // Returns true iff |type| indicates a METADATA frame. + bool OnFrameHeader(spdy::SpdyStreamId stream_id, size_t length, uint8_t type, + uint8_t flags) override; + + // Consumes a METADATA frame payload. Invokes the registered callback when a + // complete payload has been received. + void OnFramePayload(const char* data, size_t len) override; + + // Returns true if the peer has advertised support for METADATA via the + // appropriate setting. + bool PeerSupportsMetadata() const { + return peer_supports_metadata_ == MetadataSupportState::SUPPORTED; + } + + private: + enum class MetadataSupportState : uint8_t { + UNSPECIFIED, + SUPPORTED, + NOT_SUPPORTED, + }; + + struct MetadataPayloadState; + + using StreamMetadataMap = + absl::flat_hash_map<spdy::SpdyStreamId, + std::unique_ptr<MetadataPayloadState>>; + + OnCompletePayload on_payload_; + OnMetadataSupport on_support_; + StreamMetadataMap metadata_map_; + spdy::SpdyStreamId current_stream_; + MetadataSupportState peer_supports_metadata_; +}; + +// A class that serializes metadata blocks as sequences of frames. +class MetadataSerializer { + public: + using MetadataPayload = spdy::SpdyHeaderBlock; + + class FrameSequence { + public: + virtual ~FrameSequence() {} + + // Returns nullptr once the sequence has been exhausted. + virtual std::unique_ptr<spdy::SpdyFrameIR> Next() = 0; + }; + + MetadataSerializer() {} + + MetadataSerializer(const MetadataSerializer&) = delete; + MetadataSerializer& operator=(const MetadataSerializer&) = delete; + + // Returns nullptr on failure. + std::unique_ptr<FrameSequence> FrameSequenceForPayload( + spdy::SpdyStreamId stream_id, MetadataPayload payload); +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_METADATA_EXTENSION_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/metadata_extension_test.cc b/chromium/net/third_party/quiche/src/spdy/core/metadata_extension_test.cc new file mode 100644 index 00000000000..07a5ee1113f --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/metadata_extension_test.cc @@ -0,0 +1,226 @@ +#include "spdy/core/metadata_extension.h" + +#include "absl/container/flat_hash_map.h" +#include "absl/functional/bind_front.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "common/platform/api/quiche_test.h" +#include "spdy/core/array_output_buffer.h" +#include "spdy/core/mock_spdy_framer_visitor.h" +#include "spdy/core/spdy_framer.h" +#include "spdy/core/spdy_header_block.h" +#include "spdy/core/spdy_no_op_visitor.h" +#include "spdy/core/spdy_protocol.h" + +namespace spdy { +namespace test { +namespace { + +using ::absl::bind_front; +using ::spdy::SpdyFramer; +using ::spdy::SpdyHeaderBlock; +using ::spdy::test::MockSpdyFramerVisitor; +using ::testing::ElementsAre; +using ::testing::IsEmpty; + +const size_t kBufferSize = 64 * 1024; +char kBuffer[kBufferSize]; + +class MetadataExtensionTest : public QuicheTest { + protected: + MetadataExtensionTest() : test_buffer_(kBuffer, kBufferSize) {} + + void SetUp() override { + extension_ = absl::make_unique<MetadataVisitor>( + bind_front(&MetadataExtensionTest::OnCompletePayload, this), + bind_front(&MetadataExtensionTest::OnMetadataSupport, this)); + } + + void OnCompletePayload(spdy::SpdyStreamId stream_id, + MetadataVisitor::MetadataPayload payload) { + ++received_count_; + received_payload_map_.insert(std::make_pair(stream_id, std::move(payload))); + } + + void OnMetadataSupport(bool peer_supports_metadata) { + EXPECT_EQ(peer_supports_metadata, extension_->PeerSupportsMetadata()); + received_metadata_support_.push_back(peer_supports_metadata); + } + + MetadataSerializer::MetadataPayload PayloadForData(absl::string_view data) { + SpdyHeaderBlock block; + block["example-payload"] = data; + return block; + } + + std::unique_ptr<MetadataVisitor> extension_; + absl::flat_hash_map<spdy::SpdyStreamId, SpdyHeaderBlock> + received_payload_map_; + std::vector<bool> received_metadata_support_; + size_t received_count_ = 0; + spdy::ArrayOutputBuffer test_buffer_; +}; + +// This test verifies that the MetadataVisitor is initialized to a state where +// it believes the peer does not support metadata. +TEST_F(MetadataExtensionTest, MetadataNotSupported) { + EXPECT_FALSE(extension_->PeerSupportsMetadata()); + EXPECT_THAT(received_metadata_support_, IsEmpty()); +} + +// This test verifies that upon receiving a specific setting, the extension +// realizes that the peer supports metadata. +TEST_F(MetadataExtensionTest, MetadataSupported) { + EXPECT_FALSE(extension_->PeerSupportsMetadata()); + // 3 is not an appropriate value for the metadata extension key. + extension_->OnSetting(MetadataVisitor::kMetadataExtensionId, 3); + EXPECT_FALSE(extension_->PeerSupportsMetadata()); + extension_->OnSetting(MetadataVisitor::kMetadataExtensionId, 1); + ASSERT_TRUE(extension_->PeerSupportsMetadata()); + extension_->OnSetting(MetadataVisitor::kMetadataExtensionId, 0); + EXPECT_FALSE(extension_->PeerSupportsMetadata()); + EXPECT_THAT(received_metadata_support_, ElementsAre(true, false)); +} + +TEST_F(MetadataExtensionTest, MetadataIgnoredWithoutExtension) { + const char kData[] = "some payload"; + SpdyHeaderBlock payload = PayloadForData(kData); + + extension_->OnSetting(MetadataVisitor::kMetadataExtensionId, 1); + ASSERT_TRUE(extension_->PeerSupportsMetadata()); + + MetadataSerializer serializer; + auto sequence = serializer.FrameSequenceForPayload(3, std::move(payload)); + ASSERT_TRUE(sequence != nullptr); + + http2::Http2DecoderAdapter deframer; + ::testing::StrictMock<MockSpdyFramerVisitor> visitor; + deframer.set_visitor(&visitor); + + // The Return(true) should not be necessary. http://b/36023792 + EXPECT_CALL(visitor, OnUnknownFrame(3, MetadataVisitor::kMetadataFrameType)) + .WillOnce(::testing::Return(true)); + + SpdyFramer framer(SpdyFramer::ENABLE_COMPRESSION); + auto frame = sequence->Next(); + ASSERT_TRUE(frame != nullptr); + while (frame != nullptr) { + const size_t frame_size = framer.SerializeFrame(*frame, &test_buffer_); + ASSERT_GT(frame_size, 0); + ASSERT_FALSE(deframer.HasError()); + ASSERT_EQ(frame_size, test_buffer_.Size()); + EXPECT_EQ(frame_size, deframer.ProcessInput(kBuffer, frame_size)); + test_buffer_.Reset(); + frame = sequence->Next(); + } + EXPECT_FALSE(deframer.HasError()); + EXPECT_THAT(received_metadata_support_, ElementsAre(true)); +} + +// This test verifies that the METADATA frame emitted by a MetadataExtension +// can be parsed by another SpdyFramer with a MetadataVisitor. +TEST_F(MetadataExtensionTest, MetadataPayloadEndToEnd) { + SpdyHeaderBlock block1; + block1["foo"] = "Some metadata value."; + SpdyHeaderBlock block2; + block2["bar"] = + "The color taupe truly represents a triumph of the human spirit over " + "adversity."; + block2["baz"] = + "Or perhaps it represents abject surrender to the implacable and " + "incomprehensible forces of the universe."; + const absl::string_view binary_payload{"binary\0payload", 14}; + block2["qux"] = binary_payload; + EXPECT_EQ(binary_payload, block2["qux"]); + for (const SpdyHeaderBlock& payload_block : + {std::move(block1), std::move(block2)}) { + extension_->OnSetting(MetadataVisitor::kMetadataExtensionId, 1); + ASSERT_TRUE(extension_->PeerSupportsMetadata()); + + MetadataSerializer serializer; + auto sequence = + serializer.FrameSequenceForPayload(3, payload_block.Clone()); + ASSERT_TRUE(sequence != nullptr); + + http2::Http2DecoderAdapter deframer; + ::spdy::SpdyNoOpVisitor visitor; + deframer.set_visitor(&visitor); + deframer.set_extension_visitor(extension_.get()); + SpdyFramer framer(SpdyFramer::ENABLE_COMPRESSION); + auto frame = sequence->Next(); + ASSERT_TRUE(frame != nullptr); + while (frame != nullptr) { + const size_t frame_size = framer.SerializeFrame(*frame, &test_buffer_); + ASSERT_GT(frame_size, 0); + ASSERT_FALSE(deframer.HasError()); + ASSERT_EQ(frame_size, test_buffer_.Size()); + EXPECT_EQ(frame_size, deframer.ProcessInput(kBuffer, frame_size)); + test_buffer_.Reset(); + frame = sequence->Next(); + } + EXPECT_EQ(1, received_count_); + auto it = received_payload_map_.find(3); + ASSERT_TRUE(it != received_payload_map_.end()); + EXPECT_EQ(payload_block, it->second); + + received_count_ = 0; + received_payload_map_.clear(); + } +} + +// This test verifies that METADATA frames for two different streams can be +// interleaved and still successfully parsed by another SpdyFramer with a +// MetadataVisitor. +TEST_F(MetadataExtensionTest, MetadataPayloadInterleaved) { + const std::string kData1 = std::string(65 * 1024, 'a'); + const std::string kData2 = std::string(65 * 1024, 'b'); + const SpdyHeaderBlock payload1 = PayloadForData(kData1); + const SpdyHeaderBlock payload2 = PayloadForData(kData2); + + extension_->OnSetting(MetadataVisitor::kMetadataExtensionId, 1); + ASSERT_TRUE(extension_->PeerSupportsMetadata()); + + MetadataSerializer serializer; + auto sequence1 = serializer.FrameSequenceForPayload(3, payload1.Clone()); + ASSERT_TRUE(sequence1 != nullptr); + + auto sequence2 = serializer.FrameSequenceForPayload(5, payload2.Clone()); + ASSERT_TRUE(sequence2 != nullptr); + + http2::Http2DecoderAdapter deframer; + ::spdy::SpdyNoOpVisitor visitor; + deframer.set_visitor(&visitor); + deframer.set_extension_visitor(extension_.get()); + + SpdyFramer framer(SpdyFramer::ENABLE_COMPRESSION); + auto frame1 = sequence1->Next(); + ASSERT_TRUE(frame1 != nullptr); + auto frame2 = sequence2->Next(); + ASSERT_TRUE(frame2 != nullptr); + while (frame1 != nullptr || frame2 != nullptr) { + for (auto frame : {frame1.get(), frame2.get()}) { + if (frame != nullptr) { + const size_t frame_size = framer.SerializeFrame(*frame, &test_buffer_); + ASSERT_GT(frame_size, 0); + ASSERT_FALSE(deframer.HasError()); + ASSERT_EQ(frame_size, test_buffer_.Size()); + EXPECT_EQ(frame_size, deframer.ProcessInput(kBuffer, frame_size)); + test_buffer_.Reset(); + } + } + frame1 = sequence1->Next(); + frame2 = sequence2->Next(); + } + EXPECT_EQ(2, received_count_); + auto it = received_payload_map_.find(3); + ASSERT_TRUE(it != received_payload_map_.end()); + EXPECT_EQ(payload1, it->second); + + it = received_payload_map_.find(5); + ASSERT_TRUE(it != received_payload_map_.end()); + EXPECT_EQ(payload2, it->second); +} + +} // anonymous namespace +} // namespace test +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.cc index 94218f697da..f1cca0da3b1 100644 --- a/chromium/net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.cc +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.cc @@ -11,7 +11,6 @@ #include "absl/strings/str_cat.h" #include "common/platform/api/quiche_logging.h" -#include "spdy/platform/api/spdy_string_utils.h" namespace spdy { diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_framer.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_framer.cc index d6f0f889876..1b6eb8a6a9f 100644 --- a/chromium/net/third_party/quiche/src/spdy/core/spdy_framer.cc +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_framer.cc @@ -18,8 +18,6 @@ #include "spdy/core/spdy_bitmasks.h" #include "spdy/core/spdy_frame_builder.h" #include "spdy/core/spdy_frame_reader.h" -#include "spdy/platform/api/spdy_estimate_memory_usage.h" -#include "spdy/platform/api/spdy_string_utils.h" namespace spdy { @@ -303,9 +301,8 @@ size_t SpdyFramer::SpdyFrameIterator::NextFrame(ZeroCopyOutputBuffer* output) { const size_t size_without_block = is_first_frame_ ? GetFrameSizeSansBlock() : kContinuationFrameMinimumSize; - auto encoding = std::make_unique<std::string>(); - encoder_->Next(kHttp2MaxControlFrameSendSize - size_without_block, - encoding.get()); + std::string encoding; + encoder_->Next(kHttp2MaxControlFrameSendSize - size_without_block, &encoding); has_next_frame_ = encoder_->HasNext(); if (framer_->debug_visitor_ != nullptr) { @@ -316,14 +313,14 @@ size_t SpdyFramer::SpdyFrameIterator::NextFrame(ZeroCopyOutputBuffer* output) { framer_->debug_visitor_->OnSendCompressedFrame( frame_ir.stream_id(), is_first_frame_ ? frame_ir.frame_type() : SpdyFrameType::CONTINUATION, - header_list_size, size_without_block + encoding->size()); + header_list_size, size_without_block + encoding.size()); } const size_t free_bytes_before = output->BytesFree(); bool ok = false; if (is_first_frame_) { is_first_frame_ = false; - ok = SerializeGivenEncoding(*encoding, output); + ok = SerializeGivenEncoding(encoding, output); } else { SpdyContinuationIR continuation_ir(frame_ir.stream_id()); continuation_ir.take_encoding(std::move(encoding)); @@ -1380,8 +1377,4 @@ size_t SpdyFramer::header_encoder_table_size() const { } } -size_t SpdyFramer::EstimateMemoryUsage() const { - return SpdyEstimateMemoryUsage(hpack_encoder_); -} - } // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_framer.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_framer.h index 4e3dc35f1d3..6f7a5dc9bd6 100644 --- a/chromium/net/third_party/quiche/src/spdy/core/spdy_framer.h +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_framer.h @@ -236,8 +236,9 @@ class QUICHE_EXPORT_PRIVATE SpdyFramer { // Get (and lazily initialize) the HPACK encoder state. HpackEncoder* GetHpackEncoder(); - // Returns the estimate of dynamically allocated memory in bytes. - size_t EstimateMemoryUsage() const; + // Gets the HPACK encoder state. Returns nullptr if the encoder has not been + // initialized. + const HpackEncoder* GetHpackEncoder() const { return hpack_encoder_.get(); } protected: friend class test::SpdyFramerPeer; diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_framer_test.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_framer_test.cc index c4b5979a874..138c4873b79 100644 --- a/chromium/net/third_party/quiche/src/spdy/core/spdy_framer_test.cc +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_framer_test.cc @@ -16,6 +16,7 @@ #include "absl/base/macros.h" #include "common/platform/api/quiche_logging.h" #include "common/platform/api/quiche_test.h" +#include "common/quiche_text_utils.h" #include "spdy/core/array_output_buffer.h" #include "spdy/core/mock_spdy_framer_visitor.h" #include "spdy/core/recording_headers_handler.h" @@ -24,7 +25,6 @@ #include "spdy/core/spdy_frame_reader.h" #include "spdy/core/spdy_protocol.h" #include "spdy/core/spdy_test_utils.h" -#include "spdy/platform/api/spdy_string_utils.h" using ::http2::Http2DecoderAdapter; using ::testing::_; @@ -287,7 +287,8 @@ class TestSpdyVisitor : public SpdyFramerVisitorInterface, QUICHE_VLOG(1) << "OnStreamFrameData(" << stream_id << ", data, " << len << ", " << ") data:\n" - << SpdyHexDump(absl::string_view(data, len)); + << quiche::QuicheTextUtils::HexDump( + absl::string_view(data, len)); EXPECT_EQ(header_stream_id_, stream_id); data_bytes_ += len; @@ -1040,8 +1041,7 @@ TEST_P(SpdyFramerTest, ContinuationWithStreamIdZero) { deframer_.set_visitor(&visitor); SpdyContinuationIR continuation(/* stream_id = */ 0); - auto some_nonsense_encoding = - std::make_unique<std::string>("some nonsense encoding"); + std::string some_nonsense_encoding = "some nonsense encoding"; continuation.take_encoding(std::move(some_nonsense_encoding)); continuation.set_end_headers(true); SpdySerializedFrame frame(framer_.SerializeContinuation(continuation)); @@ -1230,6 +1230,92 @@ TEST_P(SpdyFramerTest, Basic) { EXPECT_EQ(4, visitor.data_frame_count_); } +// Verifies that the decoder stops delivering events after a user error. +TEST_P(SpdyFramerTest, BasicWithError) { + // Send HEADERS frames with PRIORITY and END_HEADERS set. + // frame-format off + const unsigned char kH2Input[] = { + 0x00, 0x00, 0x01, // Length: 1 + 0x01, // Type: HEADERS + 0x04, // Flags: END_HEADERS + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x8c, // :status: 200 + + 0x00, 0x00, 0x0c, // Length: 12 + 0x00, // Type: DATA + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0xde, 0xad, 0xbe, 0xef, // Payload + 0xde, 0xad, 0xbe, 0xef, // + 0xde, 0xad, 0xbe, 0xef, // + + 0x00, 0x00, 0x06, // Length: 5 + 0x01, // Type: HEADERS + 0x24, // Flags: END_HEADERS|PRIORITY + 0x00, 0x00, 0x00, 0x03, // Stream: 3 + 0x00, 0x00, 0x00, 0x00, // Parent: 0 + 0x82, // Weight: 131 + 0x8c, // :status: 200 + + 0x00, 0x00, 0x08, // Length: 8 + 0x00, // Type: DATA + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x03, // Stream: 3 + 0xde, 0xad, 0xbe, 0xef, // Payload + 0xde, 0xad, 0xbe, 0xef, // + + 0x00, 0x00, 0x04, // Length: 4 + 0x00, // Type: DATA + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0xde, 0xad, 0xbe, 0xef, // Payload + + 0x00, 0x00, 0x04, // Length: 4 + 0x03, // Type: RST_STREAM + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, 0x00, 0x00, 0x08, // Error: CANCEL + + 0x00, 0x00, 0x00, // Length: 0 + 0x00, // Type: DATA + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x03, // Stream: 3 + + 0x00, 0x00, 0x04, // Length: 4 + 0x03, // Type: RST_STREAM + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x03, // Stream: 3 + 0x00, 0x00, 0x00, 0x08, // Error: CANCEL + }; + // frame-format on + + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + + deframer_.set_visitor(&visitor); + + testing::InSequence s; + EXPECT_CALL(visitor, OnHeaders(1, false, 0, 0, false, false, true)); + EXPECT_CALL(visitor, OnHeaderFrameStart(1)); + EXPECT_CALL(visitor, OnHeaderFrameEnd(1)); + EXPECT_CALL(visitor, OnDataFrameHeader(1, 12, false)); + EXPECT_CALL(visitor, OnStreamFrameData(1, _, 12)); + EXPECT_CALL(visitor, OnHeaders(3, true, 131, 0, false, false, true)); + EXPECT_CALL(visitor, OnHeaderFrameStart(3)); + EXPECT_CALL(visitor, OnHeaderFrameEnd(3)); + EXPECT_CALL(visitor, OnDataFrameHeader(3, 8, false)) + .WillOnce( + testing::InvokeWithoutArgs([this]() { deframer_.StopProcessing(); })); + // Remaining frames are not processed due to the error. + EXPECT_CALL( + visitor, + OnError(http2::Http2DecoderAdapter::SpdyFramerError::SPDY_STOP_PROCESSING, + "Ignoring further events on this connection.")); + + size_t processed = deframer_.ProcessInput( + reinterpret_cast<const char*>(kH2Input), sizeof(kH2Input)); + EXPECT_LT(processed, sizeof(kH2Input)); +} + // Test that the FIN flag on a data frame signifies EOF. TEST_P(SpdyFramerTest, FinOnDataFrame) { // Send HEADERS frames with END_HEADERS set. @@ -2315,10 +2401,10 @@ TEST_P(SpdyFramerTest, CreateContinuationUncompressed) { Http2HeaderBlock header_block; header_block["bar"] = "foo"; header_block["foo"] = "bar"; - auto buffer = std::make_unique<std::string>(); + std::string buffer; HpackEncoder encoder; encoder.DisableCompression(); - encoder.EncodeHeaderSet(header_block, buffer.get()); + encoder.EncodeHeaderSet(header_block, &buffer); SpdyContinuationIR continuation(/* stream_id = */ 42); continuation.take_encoding(std::move(buffer)); diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_header_block.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_header_block.cc index b5790470e02..2190c6bbdad 100644 --- a/chromium/net/third_party/quiche/src/spdy/core/spdy_header_block.cc +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_header_block.cc @@ -11,7 +11,6 @@ #include "absl/strings/str_cat.h" #include "common/platform/api/quiche_logging.h" -#include "spdy/platform/api/spdy_estimate_memory_usage.h" namespace spdy { namespace { @@ -299,12 +298,6 @@ void Http2HeaderBlock::AppendValueOrAddHeader(const absl::string_view key, iter->second.Append(storage_.Write(value)); } -size_t Http2HeaderBlock::EstimateMemoryUsage() const { - // TODO(xunjieli): https://crbug.com/669108. Also include |map_| when EMU() - // supports linked_hash_map. - return SpdyEstimateMemoryUsage(storage_); -} - void Http2HeaderBlock::AppendHeader(const absl::string_view key, const absl::string_view value) { auto backed_key = WriteKey(key); diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_header_block.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_header_block.h index 46584191461..c3c5bad7205 100644 --- a/chromium/net/third_party/quiche/src/spdy/core/spdy_header_block.h +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_header_block.h @@ -20,8 +20,8 @@ #include "absl/strings/string_view.h" #include "common/platform/api/quiche_export.h" #include "common/platform/api/quiche_logging.h" +#include "common/quiche_linked_hash_map.h" #include "spdy/core/spdy_header_storage.h" -#include "spdy/platform/api/spdy_containers.h" namespace spdy { @@ -108,10 +108,10 @@ class QUICHE_EXPORT_PRIVATE Http2HeaderBlock { } }; - typedef SpdyLinkedHashMap<absl::string_view, - HeaderValue, - StringPieceCaseHash, - StringPieceCaseEqual> + typedef quiche::QuicheLinkedHashMap<absl::string_view, + HeaderValue, + StringPieceCaseHash, + StringPieceCaseEqual> MapType; public: @@ -201,6 +201,7 @@ class QUICHE_EXPORT_PRIVATE Http2HeaderBlock { const_iterator find(absl::string_view key) const { return wrap_const_iterator(map_.find(key)); } + bool contains(absl::string_view key) const { return find(key) != end(); } void erase(absl::string_view key); // Clears both our MapType member and the memory used to hold headers. @@ -262,9 +263,6 @@ class QUICHE_EXPORT_PRIVATE Http2HeaderBlock { // Allows either lookup or mutation of the value associated with a key. ABSL_MUST_USE_RESULT ValueProxy operator[](const absl::string_view key); - // Returns the estimate of dynamically allocated memory in bytes. - size_t EstimateMemoryUsage() const; - size_t TotalBytesUsed() const { return key_size_ + value_size_; } private: diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_header_block_test.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_header_block_test.cc index 2435ea9c043..0767dc9e875 100644 --- a/chromium/net/third_party/quiche/src/spdy/core/spdy_header_block_test.cc +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_header_block_test.cc @@ -33,6 +33,7 @@ TEST(Http2HeaderBlockTest, EmptyBlock) { EXPECT_TRUE(block.empty()); EXPECT_EQ(0u, block.size()); EXPECT_EQ(block.end(), block.find("foo")); + EXPECT_FALSE(block.contains("foo")); EXPECT_TRUE(block.end() == block.begin()); // Should have no effect. @@ -83,6 +84,7 @@ TEST(Http2HeaderBlockTest, AddHeaders) { std::string qux("qux"); EXPECT_EQ("qux2", block[qux]); ASSERT_NE(block.end(), block.find("key")); + ASSERT_TRUE(block.contains("key")); EXPECT_EQ(Pair("key", "value"), *block.find("key")); block.erase("key"); diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_header_storage.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_header_storage.h index 24c64eeae87..54267db51a4 100644 --- a/chromium/net/third_party/quiche/src/spdy/core/spdy_header_storage.h +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_header_storage.h @@ -43,8 +43,6 @@ class QUICHE_EXPORT_PRIVATE SpdyHeaderStorage { size_t bytes_allocated() const { return arena_.status().bytes_allocated(); } - size_t EstimateMemoryUsage() const { return bytes_allocated(); } - private: SpdySimpleArena arena_; }; diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_intrusive_list.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_intrusive_list.h index 2d9f749f7fa..3af20538578 100644 --- a/chromium/net/third_party/quiche/src/spdy/core/spdy_intrusive_list.h +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_intrusive_list.h @@ -267,9 +267,10 @@ template <typename T, typename ListID = void> class SpdyIntrusiveList { public: typedef std::iterator<std::bidirectional_iterator_tag, QualifiedT> base; - iterator_impl() : link_(nullptr) {} + iterator_impl() = default; iterator_impl(QualifiedLinkT* link) : link_(link) {} - iterator_impl(const iterator_impl& x) : link_(x.link_) {} + iterator_impl(const iterator_impl& x) = default; + iterator_impl& operator=(const iterator_impl& x) = default; // Allow converting and comparing across iterators where the pointer // assignment and comparisons (respectively) are allowed. @@ -310,7 +311,7 @@ template <typename T, typename ListID = void> class SpdyIntrusiveList { // Ensure iterators can access other iterators node directly. template <typename U, typename V> friend class iterator_impl; - QualifiedLinkT* link_; + QualifiedLinkT* link_ = nullptr; }; // This bare link acts as the sentinel node. diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol.cc index 4bde56a29d7..12a716e486c 100644 --- a/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol.cc +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol.cc @@ -446,7 +446,6 @@ size_t SpdyGoAwayIR::size() const { SpdyContinuationIR::SpdyContinuationIR(SpdyStreamId stream_id) : SpdyFrameIR(stream_id), end_headers_(false) { - encoding_ = std::make_unique<std::string>(); } SpdyContinuationIR::~SpdyContinuationIR() = default; diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol.h index 16d6b9a7ecd..eafb845883a 100644 --- a/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol.h +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol.h @@ -260,7 +260,7 @@ QUICHE_EXPORT_PRIVATE bool IsValidHTTP2FrameStreamId( SpdyFrameType frame_type_field); // Serialize |frame_type| to string for logging/debugging. -const char* FrameTypeToString(SpdyFrameType frame_type); +QUICHE_EXPORT_PRIVATE const char* FrameTypeToString(SpdyFrameType frame_type); // If |wire_setting_id| is the on-the-wire representation of a defined SETTINGS // parameter, parse it to |*setting_id| and return true. @@ -328,7 +328,7 @@ const int32_t kInitialStreamWindowSize = 64 * 1024 - 1; // Initial window size for a session in bytes. const int32_t kInitialSessionWindowSize = 64 * 1024 - 1; // The NPN string for HTTP2, "h2". -extern const char* const kHttp2Npn; +QUICHE_EXPORT_PRIVATE extern const char* const kHttp2Npn; // An estimate size of the HPACK overhead for each header field. 1 bytes for // indexed literal, 1 bytes for key literal and length encoding, and 2 bytes for // value literal and length encoding. @@ -351,7 +351,7 @@ QUICHE_EXPORT_PRIVATE size_t GetNumberRequiredContinuationFrames(size_t size); // exclusive bit}. Templated to allow for use by QUIC code; SPDY and HTTP/2 // code should use the concrete type instantiation SpdyStreamPrecedence. template <typename StreamIdType> -class StreamPrecedence { +class QUICHE_EXPORT_PRIVATE StreamPrecedence { public: // Constructs instance that is a SPDY 3.x priority. Clamps priority value to // the valid range [0, 7]. @@ -427,7 +427,7 @@ class StreamPrecedence { } private: - struct Http2StreamDependency { + struct QUICHE_EXPORT_PRIVATE Http2StreamDependency { StreamIdType parent_id; int weight; bool is_exclusive; @@ -827,14 +827,12 @@ class QUICHE_EXPORT_PRIVATE SpdyContinuationIR : public SpdyFrameIR { bool end_headers() const { return end_headers_; } void set_end_headers(bool end_headers) { end_headers_ = end_headers; } - const std::string& encoding() const { return *encoding_; } - void take_encoding(std::unique_ptr<std::string> encoding) { - encoding_ = std::move(encoding); - } + const std::string& encoding() const { return encoding_; } + void take_encoding(std::string encoding) { encoding_ = std::move(encoding); } size_t size() const override; private: - std::unique_ptr<std::string> encoding_; + std::string encoding_; bool end_headers_; }; @@ -920,7 +918,7 @@ class QUICHE_EXPORT_PRIVATE SpdyPriorityUpdateIR : public SpdyFrameIR { std::string priority_field_value_; }; -struct AcceptChOriginValuePair { +struct QUICHE_EXPORT_PRIVATE AcceptChOriginValuePair { std::string origin; std::string value; bool operator==(const AcceptChOriginValuePair& rhs) const { @@ -1061,9 +1059,6 @@ class QUICHE_EXPORT_PRIVATE SpdySerializedFrame { return buffer; } - // Returns the estimate of dynamically allocated memory in bytes. - size_t EstimateMemoryUsage() const { return owns_buffer_ ? size_ : 0; } - protected: char* frame_; diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_containers.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_containers.h deleted file mode 100644 index 2adf3f4b87a..00000000000 --- a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_containers.h +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_CONTAINERS_H_ -#define QUICHE_SPDY_PLATFORM_API_SPDY_CONTAINERS_H_ - -#include "net/spdy/platform/impl/spdy_containers_impl.h" - -namespace spdy { - -// A map which offers insertion-ordered iteration. -template <typename Key, typename Value, typename Hash, typename Eq> -using SpdyLinkedHashMap = SpdyLinkedHashMapImpl<Key, Value, Hash, Eq>; - -} // namespace spdy - -#endif // QUICHE_SPDY_PLATFORM_API_SPDY_CONTAINERS_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h deleted file mode 100644 index 4e22d1eabb0..00000000000 --- a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_ESTIMATE_MEMORY_USAGE_H_ -#define QUICHE_SPDY_PLATFORM_API_SPDY_ESTIMATE_MEMORY_USAGE_H_ - -#include <cstddef> - -#include "quiche_platform_impl/quiche_estimate_memory_usage_impl.h" - -namespace spdy { - -template <class T> -size_t SpdyEstimateMemoryUsage(const T& object) { - return quiche::QuicheEstimateMemoryUsageImpl(object); -} - -} // namespace spdy - -#endif // QUICHE_SPDY_PLATFORM_API_SPDY_ESTIMATE_MEMORY_USAGE_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h deleted file mode 100644 index 83660bab06f..00000000000 --- a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_STRING_UTILS_H_ -#define QUICHE_SPDY_PLATFORM_API_SPDY_STRING_UTILS_H_ - -#include <string> - -#include "absl/strings/string_view.h" -#include "net/spdy/platform/impl/spdy_string_utils_impl.h" - -namespace spdy { - -inline std::string SpdyHexDump(absl::string_view data) { - return SpdyHexDumpImpl(data); -} - -} // namespace spdy - -#endif // QUICHE_SPDY_PLATFORM_API_SPDY_STRING_UTILS_H_ |