diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2022-05-12 15:59:20 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2022-05-25 06:57:22 +0000 |
commit | f7eaed5286974984ba5f9e3189d8f49d03e99f81 (patch) | |
tree | caed19b2af2024f35449fb0b781d0a25e09d4f8f /chromium/net/third_party | |
parent | 9729c4479fe23554eae6e6dd1f30ff488f470c84 (diff) | |
download | qtwebengine-chromium-f7eaed5286974984ba5f9e3189d8f49d03e99f81.tar.gz |
BASELINE: Update Chromium to 100.0.4896.167
Change-Id: I98cbeb5d7543d966ffe04d8cefded0c493a11333
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/net/third_party')
219 files changed, 10415 insertions, 3612 deletions
diff --git a/chromium/net/third_party/quiche/BUILD.gn b/chromium/net/third_party/quiche/BUILD.gn index 23537cba4fa..179e315cf35 100644 --- a/chromium/net/third_party/quiche/BUILD.gn +++ b/chromium/net/third_party/quiche/BUILD.gn @@ -31,6 +31,8 @@ source_set("quiche_public_deps") { source_set("quiche") { sources = [ + "overrides/quiche_platform_impl/quic_flags_impl.cc", + "overrides/quiche_platform_impl/quic_flags_impl.h", "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", @@ -174,7 +176,6 @@ source_set("quiche") { "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/quic/core/congestion_control/bandwidth_sampler.cc", "src/quic/core/congestion_control/bandwidth_sampler.h", "src/quic/core/congestion_control/bbr2_drain.cc", @@ -278,6 +279,8 @@ source_set("quiche") { "src/quic/core/crypto/p256_key_exchange.h", "src/quic/core/crypto/proof_source.cc", "src/quic/core/crypto/proof_source.h", + "src/quic/core/crypto/proof_source_x509.cc", + "src/quic/core/crypto/proof_source_x509.h", "src/quic/core/crypto/proof_verifier.h", "src/quic/core/crypto/quic_client_session_cache.cc", "src/quic/core/crypto/quic_client_session_cache.h", @@ -644,8 +647,6 @@ source_set("quiche") { "src/spdy/core/spdy_bitmasks.h", "src/spdy/core/spdy_frame_builder.cc", "src/spdy/core/spdy_frame_builder.h", - "src/spdy/core/spdy_frame_reader.cc", - "src/spdy/core/spdy_frame_reader.h", "src/spdy/core/spdy_framer.cc", "src/spdy/core/spdy_framer.h", "src/spdy/core/spdy_header_block.cc", @@ -1236,6 +1237,7 @@ source_set("quiche_tests") { "src/common/platform/api/quiche_time_utils_test.cc", "src/common/platform/api/quiche_url_utils_test.cc", "src/common/quiche_circular_deque_test.cc", + "src/common/quiche_data_reader_test.cc", "src/common/quiche_data_writer_test.cc", "src/common/quiche_endian_test.cc", "src/common/quiche_linked_hash_map_test.cc", @@ -1360,6 +1362,7 @@ source_set("quiche_tests") { "src/quic/core/crypto/null_decrypter_test.cc", "src/quic/core/crypto/null_encrypter_test.cc", "src/quic/core/crypto/p256_key_exchange_test.cc", + "src/quic/core/crypto/proof_source_x509_test.cc", "src/quic/core/crypto/quic_client_session_cache_test.cc", "src/quic/core/crypto/quic_compressed_certs_cache_test.cc", "src/quic/core/crypto/quic_crypto_client_config_test.cc", @@ -1499,7 +1502,6 @@ source_set("quiche_tests") { "src/spdy/core/mock_spdy_framer_visitor.h", "src/spdy/core/spdy_alt_svc_wire_format_test.cc", "src/spdy/core/spdy_frame_builder_test.cc", - "src/spdy/core/spdy_frame_reader_test.cc", "src/spdy/core/spdy_framer_test.cc", "src/spdy/core/spdy_header_block_test.cc", "src/spdy/core/spdy_header_storage_test.cc", diff --git a/chromium/net/third_party/quiche/overrides/quiche_platform_impl/quic_flags_impl.cc b/chromium/net/third_party/quiche/overrides/quiche_platform_impl/quic_flags_impl.cc new file mode 100644 index 00000000000..798bccf03f8 --- /dev/null +++ b/chromium/net/third_party/quiche/overrides/quiche_platform_impl/quic_flags_impl.cc @@ -0,0 +1,85 @@ +// Copyright 2014 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 "net/third_party/quiche/overrides/quiche_platform_impl/quic_flags_impl.h" + +#include <string> + +#include "base/strings/string_number_conversions.h" + +#define DEFINE_QUIC_PROTOCOL_FLAG_SINGLE_VALUE(type, flag, value, doc) \ + type FLAGS_##flag = value; + +#define DEFINE_QUIC_PROTOCOL_FLAG_TWO_VALUES(type, flag, internal_value, \ + external_value, doc) \ + type FLAGS_##flag = external_value; + +// Preprocessor macros can only have one definition. +// Select the right macro based on the number of arguments. +#define GET_6TH_ARG(arg1, arg2, arg3, arg4, arg5, arg6, ...) arg6 +#define QUIC_PROTOCOL_FLAG_MACRO_CHOOSER(...) \ + GET_6TH_ARG(__VA_ARGS__, DEFINE_QUIC_PROTOCOL_FLAG_TWO_VALUES, \ + DEFINE_QUIC_PROTOCOL_FLAG_SINGLE_VALUE) +#define QUIC_PROTOCOL_FLAG(...) \ + QUIC_PROTOCOL_FLAG_MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__) + +#include "net/third_party/quiche/src/quic/core/quic_protocol_flags_list.h" + +#undef QUIC_PROTOCOL_FLAG +#undef QUIC_PROTOCOL_FLAG_MACRO_CHOOSER +#undef GET_6TH_ARG +#undef DEFINE_QUIC_PROTOCOL_FLAG_TWO_VALUES +#undef DEFINE_QUIC_PROTOCOL_FLAG_SINGLE_VALUE + +namespace { + +void SetQuicFlagByName_bool(bool* flag, const std::string& value) { + if (value == "true" || value == "True") + *flag = true; + else if (value == "false" || value == "False") + *flag = false; +} +void SetQuicFlagByName_double(double* flag, const std::string& value) { + double val; + if (base::StringToDouble(value, &val)) + *flag = val; +} + +void SetQuicFlagByName_uint64_t(uint64_t* flag, const std::string& value) { + uint64_t val; + if (base::StringToUint64(value, &val) && val >= 0) + *flag = val; +} + +void SetQuicFlagByName_int32_t(int32_t* flag, const std::string& value) { + int val; + if (base::StringToInt(value, &val)) + *flag = val; +} + +void SetQuicFlagByName_int64_t(int64_t* flag, const std::string& value) { + int64_t val; + if (base::StringToInt64(value, &val)) + *flag = val; +} + +} // namespace + +void SetQuicFlagByName(const std::string& flag_name, const std::string& value) { +#define QUIC_FLAG(flag, default_value) \ + if (flag_name == #flag) { \ + SetQuicFlagByName_bool(&flag, value); \ + return; \ + } +#include "net/third_party/quiche/src/quic/core/quic_flags_list.h" +#undef QUIC_FLAG + +#define QUIC_PROTOCOL_FLAG(type, flag, ...) \ + if (flag_name == "FLAGS_" #flag) { \ + SetQuicFlagByName_##type(&FLAGS_##flag, value); \ + return; \ + } +#include "net/third_party/quiche/src/quic/core/quic_protocol_flags_list.h" +#undef QUIC_PROTOCOL_FLAG +} diff --git a/chromium/net/third_party/quiche/overrides/quiche_platform_impl/quic_flags_impl.h b/chromium/net/third_party/quiche/overrides/quiche_platform_impl/quic_flags_impl.h new file mode 100644 index 00000000000..fab627d8a1e --- /dev/null +++ b/chromium/net/third_party/quiche/overrides/quiche_platform_impl/quic_flags_impl.h @@ -0,0 +1,25 @@ +// Copyright 2014 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_QUIC_FLAGS_IMPL_H_ +#define NET_THIRD_PARTY_QUICHE_OVERRIDES_QUICHE_PLATFORM_IMPL_QUIC_FLAGS_IMPL_H_ + +#include <string> + +#include "net/third_party/quiche/src/common/platform/api/quiche_flags.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_export.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +#define QUIC_PROTOCOL_FLAG(type, flag, ...) \ + QUIC_EXPORT_PRIVATE extern type FLAGS_##flag; +#include "net/third_party/quiche/src/quic/core/quic_protocol_flags_list.h" +#undef QUIC_PROTOCOL_FLAG + +// Sets the flag named |flag_name| to the value of |value| after converting +// it from a string to the appropriate type. If |value| is invalid or out of +// range, the flag will be unchanged. +QUIC_EXPORT_PRIVATE void SetQuicFlagByName(const std::string& flag_name, + const std::string& value); + +#endif // NET_THIRD_PARTY_QUICHE_OVERRIDES_QUICHE_PLATFORM_IMPL_QUIC_FLAGS_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 326ed930701..006a80e7242 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 @@ -61,7 +61,7 @@ #endif #define QUICHE_DLOG_INFO_IS_ON_IMPL() 0 -#if defined(OS_WIN) +#if BUILDFLAG(IS_WIN) // wingdi.h defines ERROR to be 0. When we call QUICHE_DLOG(ERROR), it gets // substituted with 0, and it expands to QUICHE_CHROMIUM_DLOG_0. To allow us to // keep using this syntax, we define this macro to do the same thing as diff --git a/chromium/net/third_party/quiche/src/common/platform/api/quiche_command_line_flags.h b/chromium/net/third_party/quiche/src/common/platform/api/quiche_command_line_flags.h new file mode 100644 index 00000000000..3ba48bbab5b --- /dev/null +++ b/chromium/net/third_party/quiche/src/common/platform/api/quiche_command_line_flags.h @@ -0,0 +1,38 @@ +// 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_COMMON_PLATFORM_API_QUICHE_COMMAND_LINE_FLAGS_H_ +#define QUICHE_COMMON_PLATFORM_API_QUICHE_COMMAND_LINE_FLAGS_H_ + +#include <string> +#include <vector> + +#include "net/quiche/common/platform/impl/quiche_command_line_flags_impl.h" + +// Define a command-line flag that can be automatically set via +// QuicheParseCommandLineFlags(). +#define DEFINE_QUICHE_COMMAND_LINE_FLAG(type, name, default_value, help) \ + DEFINE_QUICHE_COMMAND_LINE_FLAG_IMPL(type, name, default_value, help) + +namespace quiche { + +// Parses command-line flags, setting flag variables defined using +// DEFINE_QUICHE_COMMAND_LINE_FLAG if they appear in the command line, and +// returning a list of any non-flag arguments specified in the command line. If +// the command line specifies '-h' or '--help', prints a usage message with flag +// descriptions to stdout and exits with status 0. If a flag has an unparsable +// value, writes an error message to stderr and exits with status 1. +inline std::vector<std::string> QuicheParseCommandLineFlags( + const char* usage, int argc, const char* const* argv) { + return QuicheParseCommandLineFlagsImpl(usage, argc, argv); +} + +// Prints a usage message with flag descriptions to stdout. +inline void QuichePrintCommandLineFlagHelp(const char* usage) { + QuichePrintCommandLineFlagHelpImpl(usage); +} + +} // namespace quiche + +#endif // QUICHE_COMMON_PLATFORM_API_QUICHE_COMMAND_LINE_FLAGS_H_ diff --git a/chromium/net/third_party/quiche/src/common/platform/api/quiche_export.h b/chromium/net/third_party/quiche/src/common/platform/api/quiche_export.h index 343985f98ea..4f64819d366 100644 --- a/chromium/net/third_party/quiche/src/common/platform/api/quiche_export.h +++ b/chromium/net/third_party/quiche/src/common/platform/api/quiche_export.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef THIRD_PARTY_QUICHE_PLATFORM_API_QUICHE_EXPORT_H_ -#define THIRD_PARTY_QUICHE_PLATFORM_API_QUICHE_EXPORT_H_ +#ifndef QUICHE_COMMON_PLATFORM_API_QUICHE_EXPORT_H_ +#define QUICHE_COMMON_PLATFORM_API_QUICHE_EXPORT_H_ #include "quiche_platform_impl/quiche_export_impl.h" @@ -18,4 +18,4 @@ // defined in a header, or is built in Chromium as part of tests or tools. #define QUICHE_NO_EXPORT QUICHE_NO_EXPORT_IMPL -#endif // THIRD_PARTY_QUICHE_PLATFORM_API_QUICHE_EXPORT_H_ +#endif // QUICHE_COMMON_PLATFORM_API_QUICHE_EXPORT_H_ diff --git a/chromium/net/third_party/quiche/src/common/platform/api/quiche_flag_utils.h b/chromium/net/third_party/quiche/src/common/platform/api/quiche_flag_utils.h index 0534064e2e7..fcd66231e7d 100644 --- a/chromium/net/third_party/quiche/src/common/platform/api/quiche_flag_utils.h +++ b/chromium/net/third_party/quiche/src/common/platform/api/quiche_flag_utils.h @@ -5,7 +5,7 @@ #ifndef QUICHE_COMMON_PLATFORM_API_QUICHE_FLAG_UTILS_H_ #define QUICHE_COMMON_PLATFORM_API_QUICHE_FLAG_UTILS_H_ -#include "net/quiche/common/platform/impl/quiche_flag_utils_impl.h" +#include "quiche_platform_impl/quiche_flag_utils_impl.h" #define QUICHE_RELOADABLE_FLAG_COUNT QUICHE_RELOADABLE_FLAG_COUNT_IMPL #define QUICHE_RELOADABLE_FLAG_COUNT_N QUICHE_RELOADABLE_FLAG_COUNT_N_IMPL diff --git a/chromium/net/third_party/quiche/src/common/platform/api/quiche_flags.h b/chromium/net/third_party/quiche/src/common/platform/api/quiche_flags.h index 83db9d06948..8b26edb0d9a 100644 --- a/chromium/net/third_party/quiche/src/common/platform/api/quiche_flags.h +++ b/chromium/net/third_party/quiche/src/common/platform/api/quiche_flags.h @@ -5,7 +5,7 @@ #ifndef QUICHE_COMMON_PLATFORM_API_QUICHE_FLAGS_H_ #define QUICHE_COMMON_PLATFORM_API_QUICHE_FLAGS_H_ -#include "net/quiche/common/platform/impl/quiche_flags_impl.h" +#include "quiche_platform_impl/quiche_flags_impl.h" #define GetQuicheReloadableFlag(module, flag) \ GetQuicheReloadableFlagImpl(module, flag) diff --git a/chromium/net/third_party/quiche/src/common/platform/api/quiche_lower_case_string.h b/chromium/net/third_party/quiche/src/common/platform/api/quiche_lower_case_string.h new file mode 100644 index 00000000000..4492b3c7f51 --- /dev/null +++ b/chromium/net/third_party/quiche/src/common/platform/api/quiche_lower_case_string.h @@ -0,0 +1,16 @@ +// Copyright 2022 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_LOWER_CASE_STRING_H_ +#define QUICHE_COMMON_PLATFORM_API_QUICHE_LOWER_CASE_STRING_H_ + +#include "net/quiche/common/platform/impl/quiche_lower_case_string_impl.h" + +namespace quiche { + +using QuicheLowerCaseString = QuicheLowerCaseStringImpl; + +} // namespace quiche + +#endif // QUICHE_COMMON_PLATFORM_API_QUICHE_LOWER_CASE_STRING_H_ diff --git a/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quic_flags_impl.cc b/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quic_flags_impl.cc new file mode 100644 index 00000000000..81b1d8f23ed --- /dev/null +++ b/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quic_flags_impl.cc @@ -0,0 +1,29 @@ +// Copyright 2014 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/quic_flags_impl.h" + +#define DEFINE_QUIC_PROTOCOL_FLAG_SINGLE_VALUE(type, flag, value, doc) \ + type FLAGS_##flag = value; + +#define DEFINE_QUIC_PROTOCOL_FLAG_TWO_VALUES(type, flag, internal_value, \ + external_value, doc) \ + type FLAGS_##flag = external_value; + +// Preprocessor macros can only have one definition. +// Select the right macro based on the number of arguments. +#define GET_6TH_ARG(arg1, arg2, arg3, arg4, arg5, arg6, ...) arg6 +#define QUIC_PROTOCOL_FLAG_MACRO_CHOOSER(...) \ + GET_6TH_ARG(__VA_ARGS__, DEFINE_QUIC_PROTOCOL_FLAG_TWO_VALUES, \ + DEFINE_QUIC_PROTOCOL_FLAG_SINGLE_VALUE) +#define QUIC_PROTOCOL_FLAG(...) \ + QUIC_PROTOCOL_FLAG_MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__) + +#include "quic/core/quic_protocol_flags_list.h" + +#undef QUIC_PROTOCOL_FLAG +#undef QUIC_PROTOCOL_FLAG_MACRO_CHOOSER +#undef GET_6TH_ARG +#undef DEFINE_QUIC_PROTOCOL_FLAG_TWO_VALUES +#undef DEFINE_QUIC_PROTOCOL_FLAG_SINGLE_VALUE diff --git a/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quic_flags_impl.h b/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quic_flags_impl.h new file mode 100644 index 00000000000..b58d24d6c58 --- /dev/null +++ b/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quic_flags_impl.h @@ -0,0 +1,18 @@ +// Copyright 2014 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_QUIC_FLAGS_IMPL_H_ +#define QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUIC_FLAGS_IMPL_H_ + +#include <string> + +#include "common/platform/api/quiche_export.h" +#include "common/platform/api/quiche_flags.h" + +#define QUIC_PROTOCOL_FLAG(type, flag, ...) \ + QUICHE_EXPORT_PRIVATE extern type FLAGS_##flag; +#include "quic/core/quic_protocol_flags_list.h" +#undef QUIC_PROTOCOL_FLAG + +#endif // QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUIC_FLAGS_IMPL_H_ diff --git a/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quic_testvalue_impl.h b/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quic_testvalue_impl.h index f4678e7fbeb..bc0d95e58ef 100644 --- a/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quic_testvalue_impl.h +++ b/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quic_testvalue_impl.h @@ -1,5 +1,5 @@ -#ifndef QUICHE_COMMON_PLATFORM_DEFAULT_QUIC_TESTVALUE_IMPL_H_ -#define QUICHE_COMMON_PLATFORM_DEFAULT_QUIC_TESTVALUE_IMPL_H_ +#ifndef QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUIC_TESTVALUE_IMPL_H_ +#define QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUIC_TESTVALUE_IMPL_H_ #include "absl/strings/string_view.h" @@ -10,4 +10,4 @@ void AdjustTestValueImpl(absl::string_view /*label*/, T* /*var*/) {} } // namespace quic -#endif // QUICHE_COMMON_PLATFORM_DEFAULT_QUIC_TESTVALUE_IMPL_H_ +#endif // QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUIC_TESTVALUE_IMPL_H_ diff --git a/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quiche_flag_utils_impl.h b/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quiche_flag_utils_impl.h new file mode 100644 index 00000000000..c38f75c67da --- /dev/null +++ b/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quiche_flag_utils_impl.h @@ -0,0 +1,29 @@ +// Copyright 2022 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_FLAG_UTILS_IMPL_H_ +#define QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUICHE_FLAG_UTILS_IMPL_H_ + +#define QUICHE_RELOADABLE_FLAG_COUNT_IMPL(flag) \ + do { \ + } while (0) +#define QUICHE_RELOADABLE_FLAG_COUNT_N_IMPL(flag, instance, total) \ + do { \ + } while (0) + +#define QUICHE_RESTART_FLAG_COUNT_IMPL(flag) \ + do { \ + } while (0) +#define QUICHE_RESTART_FLAG_COUNT_N_IMPL(flag, instance, total) \ + do { \ + } while (0) + +#define QUICHE_CODE_COUNT_IMPL(name) \ + do { \ + } while (0) +#define QUICHE_CODE_COUNT_N_IMPL(name, instance, total) \ + do { \ + } while (0) + +#endif // QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUICHE_FLAG_UTILS_IMPL_H_ diff --git a/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quiche_flags_impl.cc b/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quiche_flags_impl.cc new file mode 100644 index 00000000000..b68f93d4ded --- /dev/null +++ b/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quiche_flags_impl.cc @@ -0,0 +1,9 @@ +// Copyright 2020 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_flags_impl.h" + +#define QUIC_FLAG(flag, value) bool flag = value; +#include "quic/core/quic_flags_list.h" +#undef QUIC_FLAG diff --git a/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quiche_flags_impl.h b/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quiche_flags_impl.h new file mode 100644 index 00000000000..8d655edc35a --- /dev/null +++ b/chromium/net/third_party/quiche/src/common/platform/default/quiche_platform_impl/quiche_flags_impl.h @@ -0,0 +1,42 @@ +// Copyright 2020 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_FLAGS_IMPL_H_ +#define QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUICHE_FLAGS_IMPL_H_ + +#include <cstdint> +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "common/platform/api/quiche_export.h" + +#define QUIC_FLAG(flag, value) QUICHE_EXPORT_PRIVATE extern bool flag; +#include "quic/core/quic_flags_list.h" +#undef QUIC_FLAG + +inline bool GetQuicheFlagImpl(bool flag) { return flag; } +inline int32_t GetQuicheFlagImpl(int32_t flag) { return flag; } +inline int64_t GetQuicheFlagImpl(int64_t flag) { return flag; } +inline uint64_t GetQuicheFlagImpl(uint64_t flag) { return flag; } +inline double GetQuicheFlagImpl(double flag) { return flag; } +inline std::string GetQuicheFlagImpl(const std::string& flag) { return flag; } +#define SetQuicheFlagImpl(flag, value) ((flag) = (value)) + +// ------------------------------------------------------------------------ +// QUIC feature flags implementation. +// ------------------------------------------------------------------------ +#define QUICHE_RELOADABLE_FLAG(flag) FLAGS_quic_reloadable_flag_##flag +#define QUICHE_RESTART_FLAG(flag) FLAGS_quic_restart_flag_##flag +#define GetQuicheReloadableFlagImpl(module, flag) \ + GetQuicheFlag(QUICHE_RELOADABLE_FLAG(flag)) +#define SetQuicheReloadableFlagImpl(module, flag, value) \ + SetQuicheFlag(QUICHE_RELOADABLE_FLAG(flag), value) +#define GetQuicheRestartFlagImpl(module, flag) \ + GetQuicheFlag(QUICHE_RESTART_FLAG(flag)) +#define SetQuicheRestartFlagImpl(module, flag, value) \ + SetQuicheFlag(QUICHE_RESTART_FLAG(flag), value) + +#endif // QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUICHE_FLAGS_IMPL_H_ diff --git a/chromium/net/third_party/quiche/src/common/quiche_data_reader.cc b/chromium/net/third_party/quiche/src/common/quiche_data_reader.cc index f20c752bcce..1f948f1db8a 100644 --- a/chromium/net/third_party/quiche/src/common/quiche_data_reader.cc +++ b/chromium/net/third_party/quiche/src/common/quiche_data_reader.cc @@ -9,6 +9,7 @@ #include "absl/strings/numbers.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" +#include "common/platform/api/quiche_bug_tracker.h" #include "common/platform/api/quiche_logging.h" #include "common/quiche_endian.h" @@ -40,6 +41,21 @@ bool QuicheDataReader::ReadUInt16(uint16_t* result) { return true; } +bool QuicheDataReader::ReadUInt24(uint32_t* result) { + if (endianness_ != quiche::NETWORK_BYTE_ORDER) { + // TODO(b/214573190): Implement and test HOST_BYTE_ORDER case. + QUICHE_BUG(QuicheDataReader_ReadUInt24_NotImplemented); + return false; + } + + *result = 0; + if (!ReadBytes(reinterpret_cast<char*>(result) + 1, 3u)) { + return false; + } + *result = quiche::QuicheEndian::NetToHost32(*result); + return true; +} + bool QuicheDataReader::ReadUInt32(uint32_t* result) { if (!ReadBytes(result, sizeof(*result))) { return false; diff --git a/chromium/net/third_party/quiche/src/common/quiche_data_reader.h b/chromium/net/third_party/quiche/src/common/quiche_data_reader.h index 833198db879..ae8bbcf56db 100644 --- a/chromium/net/third_party/quiche/src/common/quiche_data_reader.h +++ b/chromium/net/third_party/quiche/src/common/quiche_data_reader.h @@ -45,11 +45,12 @@ class QUICHE_EXPORT_PRIVATE QuicheDataReader { // Empty destructor. ~QuicheDataReader() {} - // Reads an 8/16/32/64-bit unsigned integer into the given output + // Reads an 8/16/24/32/64-bit unsigned integer into the given output // parameter. Forwards the internal iterator on success. Returns true on // success, false otherwise. bool ReadUInt8(uint8_t* result); bool ReadUInt16(uint16_t* result); + bool ReadUInt24(uint32_t* result); bool ReadUInt32(uint32_t* result); bool ReadUInt64(uint64_t* result); diff --git a/chromium/net/third_party/quiche/src/common/quiche_data_reader_test.cc b/chromium/net/third_party/quiche/src/common/quiche_data_reader_test.cc new file mode 100644 index 00000000000..52ed292c936 --- /dev/null +++ b/chromium/net/third_party/quiche/src/common/quiche_data_reader_test.cc @@ -0,0 +1,187 @@ +// Copyright 2022 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 "common/quiche_data_reader.h" + +#include <cstdint> + +#include "common/platform/api/quiche_test.h" +#include "common/quiche_endian.h" + +namespace quiche { + +// TODO(b/214573190): Test Endianness::HOST_BYTE_ORDER. +// TODO(b/214573190): Test ReadUInt8, ReadUInt24, ReadUInt64, ReadBytesToUInt64, +// ReadStringPiece8, ReadStringPiece, ReadTag, etc. + +TEST(QuicheDataReaderTest, ReadUInt16) { + // Data in network byte order. + const uint16_t kData[] = { + QuicheEndian::HostToNet16(1), + QuicheEndian::HostToNet16(1 << 15), + }; + + QuicheDataReader reader(reinterpret_cast<const char*>(kData), sizeof(kData)); + EXPECT_FALSE(reader.IsDoneReading()); + + uint16_t uint16_val; + EXPECT_TRUE(reader.ReadUInt16(&uint16_val)); + EXPECT_FALSE(reader.IsDoneReading()); + EXPECT_EQ(1, uint16_val); + + EXPECT_TRUE(reader.ReadUInt16(&uint16_val)); + EXPECT_TRUE(reader.IsDoneReading()); + EXPECT_EQ(1 << 15, uint16_val); +} + +TEST(QuicheDataReaderTest, ReadUInt32) { + // Data in network byte order. + const uint32_t kData[] = { + QuicheEndian::HostToNet32(1), + QuicheEndian::HostToNet32(0x80000000), + }; + + QuicheDataReader reader(reinterpret_cast<const char*>(kData), + ABSL_ARRAYSIZE(kData) * sizeof(uint32_t)); + EXPECT_FALSE(reader.IsDoneReading()); + + uint32_t uint32_val; + EXPECT_TRUE(reader.ReadUInt32(&uint32_val)); + EXPECT_FALSE(reader.IsDoneReading()); + EXPECT_EQ(1u, uint32_val); + + EXPECT_TRUE(reader.ReadUInt32(&uint32_val)); + EXPECT_TRUE(reader.IsDoneReading()); + EXPECT_EQ(1u << 31, uint32_val); +} + +TEST(QuicheDataReaderTest, ReadStringPiece16) { + // Data in network byte order. + const char kData[] = { + 0x00, 0x02, // uint16_t(2) + 0x48, 0x69, // "Hi" + 0x00, 0x10, // uint16_t(16) + 0x54, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2c, + 0x20, 0x31, 0x2c, 0x20, 0x32, 0x2c, 0x20, 0x33, // "Testing, 1, 2, 3" + }; + + QuicheDataReader reader(kData, ABSL_ARRAYSIZE(kData)); + EXPECT_FALSE(reader.IsDoneReading()); + + absl::string_view stringpiece_val; + EXPECT_TRUE(reader.ReadStringPiece16(&stringpiece_val)); + EXPECT_FALSE(reader.IsDoneReading()); + EXPECT_EQ(0, stringpiece_val.compare("Hi")); + + EXPECT_TRUE(reader.ReadStringPiece16(&stringpiece_val)); + EXPECT_TRUE(reader.IsDoneReading()); + EXPECT_EQ(0, stringpiece_val.compare("Testing, 1, 2, 3")); +} + +TEST(QuicheDataReaderTest, ReadUInt16WithBufferTooSmall) { + // Data in network byte order. + const char kData[] = { + 0x00, // part of a uint16_t + }; + + QuicheDataReader reader(kData, ABSL_ARRAYSIZE(kData)); + EXPECT_FALSE(reader.IsDoneReading()); + + uint16_t uint16_val; + EXPECT_FALSE(reader.ReadUInt16(&uint16_val)); +} + +TEST(QuicheDataReaderTest, ReadUInt32WithBufferTooSmall) { + // Data in network byte order. + const char kData[] = { + 0x00, 0x00, 0x00, // part of a uint32_t + }; + + QuicheDataReader reader(kData, ABSL_ARRAYSIZE(kData)); + EXPECT_FALSE(reader.IsDoneReading()); + + uint32_t uint32_val; + EXPECT_FALSE(reader.ReadUInt32(&uint32_val)); + + // Also make sure that trying to read a uint16_t, which technically could + // work, fails immediately due to previously encountered failed read. + uint16_t uint16_val; + EXPECT_FALSE(reader.ReadUInt16(&uint16_val)); +} + +// Tests ReadStringPiece16() with a buffer too small to fit the entire string. +TEST(QuicheDataReaderTest, ReadStringPiece16WithBufferTooSmall) { + // Data in network byte order. + const char kData[] = { + 0x00, 0x03, // uint16_t(3) + 0x48, 0x69, // "Hi" + }; + + QuicheDataReader reader(kData, ABSL_ARRAYSIZE(kData)); + EXPECT_FALSE(reader.IsDoneReading()); + + absl::string_view stringpiece_val; + EXPECT_FALSE(reader.ReadStringPiece16(&stringpiece_val)); + + // Also make sure that trying to read a uint16_t, which technically could + // work, fails immediately due to previously encountered failed read. + uint16_t uint16_val; + EXPECT_FALSE(reader.ReadUInt16(&uint16_val)); +} + +// Tests ReadStringPiece16() with a buffer too small even to fit the length. +TEST(QuicheDataReaderTest, ReadStringPiece16WithBufferWayTooSmall) { + // Data in network byte order. + const char kData[] = { + 0x00, // part of a uint16_t + }; + + QuicheDataReader reader(kData, ABSL_ARRAYSIZE(kData)); + EXPECT_FALSE(reader.IsDoneReading()); + + absl::string_view stringpiece_val; + EXPECT_FALSE(reader.ReadStringPiece16(&stringpiece_val)); + + // Also make sure that trying to read a uint16_t, which technically could + // work, fails immediately due to previously encountered failed read. + uint16_t uint16_val; + EXPECT_FALSE(reader.ReadUInt16(&uint16_val)); +} + +TEST(QuicheDataReaderTest, ReadBytes) { + // Data in network byte order. + const char kData[] = { + 0x66, 0x6f, 0x6f, // "foo" + 0x48, 0x69, // "Hi" + }; + + QuicheDataReader reader(kData, ABSL_ARRAYSIZE(kData)); + EXPECT_FALSE(reader.IsDoneReading()); + + char dest1[3] = {}; + EXPECT_TRUE(reader.ReadBytes(&dest1, ABSL_ARRAYSIZE(dest1))); + EXPECT_FALSE(reader.IsDoneReading()); + EXPECT_EQ("foo", absl::string_view(dest1, ABSL_ARRAYSIZE(dest1))); + + char dest2[2] = {}; + EXPECT_TRUE(reader.ReadBytes(&dest2, ABSL_ARRAYSIZE(dest2))); + EXPECT_TRUE(reader.IsDoneReading()); + EXPECT_EQ("Hi", absl::string_view(dest2, ABSL_ARRAYSIZE(dest2))); +} + +TEST(QuicheDataReaderTest, ReadBytesWithBufferTooSmall) { + // Data in network byte order. + const char kData[] = { + 0x01, + }; + + QuicheDataReader reader(kData, ABSL_ARRAYSIZE(kData)); + EXPECT_FALSE(reader.IsDoneReading()); + + char dest[ABSL_ARRAYSIZE(kData) + 2] = {}; + EXPECT_FALSE(reader.ReadBytes(&dest, ABSL_ARRAYSIZE(kData) + 1)); + EXPECT_STREQ("", dest); +} + +} // namespace quiche diff --git a/chromium/net/third_party/quiche/src/epoll_server/simple_epoll_server.cc b/chromium/net/third_party/quiche/src/epoll_server/simple_epoll_server.cc index 28c301eb1d9..0092bb1e2c4 100644 --- a/chromium/net/third_party/quiche/src/epoll_server/simple_epoll_server.cc +++ b/chromium/net/third_party/quiche/src/epoll_server/simple_epoll_server.cc @@ -4,8 +4,9 @@ #include "epoll_server/simple_epoll_server.h" -#include <errno.h> // for errno and strerror_r +#include <errno.h> // for errno #include <stdlib.h> // for abort +#include <string.h> // for strerror_r #include <unistd.h> // For read, pipe, close and write. #include <algorithm> 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 dcdca642196..7d59c6d8d08 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 @@ -69,7 +69,7 @@ int64_t CallbackVisitor::OnReadyToSend(absl::string_view serialized) { } void CallbackVisitor::OnConnectionError(ConnectionError /*error*/) { - QUICHE_LOG(ERROR) << "OnConnectionError not implemented"; + QUICHE_VLOG(1) << "OnConnectionError not implemented"; } bool CallbackVisitor::OnFrameHeader(Http2StreamId stream_id, size_t length, @@ -201,12 +201,23 @@ bool CallbackVisitor::OnEndHeadersForStream(Http2StreamId /*stream_id*/) { return true; } +bool CallbackVisitor::OnDataPaddingLength(Http2StreamId /*stream_id*/, + size_t padding_length) { + QUICHE_DCHECK_GE(remaining_data_, padding_length); + current_frame_.data.padlen = padding_length; + remaining_data_ -= padding_length; + if (remaining_data_ == 0 && callbacks_->on_frame_recv_callback != nullptr) { + const int result = callbacks_->on_frame_recv_callback( + nullptr, ¤t_frame_, user_data_); + return result == 0; + } + return true; +} + bool 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 && callbacks_->on_frame_recv_callback) { + if (remaining_data_ == 0 && callbacks_->on_frame_recv_callback != nullptr) { const int result = callbacks_->on_frame_recv_callback( nullptr, ¤t_frame_, user_data_); return result == 0; @@ -251,6 +262,7 @@ void CallbackVisitor::OnCloseStream(Http2StreamId stream_id, callbacks_->on_stream_close_callback( nullptr, stream_id, static_cast<uint32_t>(error_code), user_data_); } + stream_map_.erase(stream_id); } void CallbackVisitor::OnPriorityForStream(Http2StreamId /*stream_id*/, 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 6d1915ce6f7..1f7452b1971 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 @@ -35,6 +35,8 @@ class QUICHE_EXPORT_PRIVATE CallbackVisitor : public Http2VisitorInterface { absl::string_view name, absl::string_view value) override; bool OnEndHeadersForStream(Http2StreamId stream_id) override; + bool OnDataPaddingLength(Http2StreamId stream_id, + size_t padding_length) override; bool OnBeginDataForStream(Http2StreamId stream_id, size_t payload_length) override; bool OnDataForStream(Http2StreamId stream_id, @@ -67,6 +69,8 @@ class QUICHE_EXPORT_PRIVATE CallbackVisitor : public Http2VisitorInterface { bool OnMetadataEndForStream(Http2StreamId stream_id) override; void OnErrorDebug(absl::string_view message) override; + size_t stream_map_size() const { return stream_map_.size(); } + private: struct QUICHE_EXPORT_PRIVATE StreamInfo { bool before_sent_headers = false; @@ -80,9 +84,12 @@ class QUICHE_EXPORT_PRIVATE CallbackVisitor : public Http2VisitorInterface { 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); + StreamInfoMap stream_map_; + Perspective perspective_; nghttp2_session_callbacks_unique_ptr callbacks_; void* user_data_; @@ -90,8 +97,6 @@ class QUICHE_EXPORT_PRIVATE CallbackVisitor : public Http2VisitorInterface { nghttp2_frame current_frame_; std::vector<nghttp2_settings_entry> settings_; size_t remaining_data_ = 0; - - 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 f7b982ef7b3..8f83da56f90 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 @@ -70,6 +70,8 @@ TEST(ClientCallbackVisitorUnitTest, ConnectionFrames) { EXPECT_CALL(callbacks, OnFrameRecv(IsGoAway(5, NGHTTP2_ENHANCE_YOUR_CALM, "calm down!!"))); visitor.OnGoAway(5, Http2ErrorCode::ENHANCE_YOUR_CALM, "calm down!!"); + + EXPECT_EQ(visitor.stream_map_size(), 0); } TEST(ClientCallbackVisitorUnitTest, StreamFrames) { @@ -79,6 +81,8 @@ TEST(ClientCallbackVisitorUnitTest, StreamFrames) { testing::InSequence seq; + EXPECT_EQ(visitor.stream_map_size(), 0); + // HEADERS on stream 1 EXPECT_CALL(callbacks, OnBeginFrame(HasFrameHeader(1, HEADERS, _))); visitor.OnFrameHeader(1, 23, HEADERS, 4); @@ -87,6 +91,8 @@ TEST(ClientCallbackVisitorUnitTest, StreamFrames) { OnBeginHeaders(IsHeaders(1, _, NGHTTP2_HCAT_RESPONSE))); visitor.OnBeginHeadersForStream(1); + EXPECT_EQ(visitor.stream_map_size(), 1); + EXPECT_CALL(callbacks, OnHeader(_, ":status", "200", _)); visitor.OnHeaderForStream(1, ":status", "200"); @@ -129,6 +135,9 @@ TEST(ClientCallbackVisitorUnitTest, StreamFrames) { EXPECT_CALL(callbacks, OnBeginFrame(HasFrameHeader(3, RST_STREAM, 0))); visitor.OnFrameHeader(3, 4, RST_STREAM, 0); + // No change in stream map size. + EXPECT_EQ(visitor.stream_map_size(), 1); + EXPECT_CALL(callbacks, OnFrameRecv(IsRstStream(3, NGHTTP2_INTERNAL_ERROR))); visitor.OnRstStream(3, Http2ErrorCode::INTERNAL_ERROR); @@ -147,6 +156,9 @@ TEST(ClientCallbackVisitorUnitTest, StreamFrames) { EXPECT_CALL(callbacks, OnStreamClose(1, NGHTTP2_NO_ERROR)); visitor.OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR); + // Stream map is empty again. + EXPECT_EQ(visitor.stream_map_size(), 0); + EXPECT_CALL(callbacks, OnBeginFrame(HasFrameHeader(5, RST_STREAM, _))); visitor.OnFrameHeader(5, 4, RST_STREAM, 0); @@ -155,6 +167,8 @@ TEST(ClientCallbackVisitorUnitTest, StreamFrames) { EXPECT_CALL(callbacks, OnStreamClose(5, NGHTTP2_REFUSED_STREAM)); visitor.OnCloseStream(5, Http2ErrorCode::REFUSED_STREAM); + + EXPECT_EQ(visitor.stream_map_size(), 0); } TEST(ClientCallbackVisitorUnitTest, HeadersWithContinuation) { @@ -228,6 +242,8 @@ TEST(ServerCallbackVisitorUnitTest, ConnectionFrames) { EXPECT_CALL(callbacks, OnFrameRecv(IsPingAck(247))); visitor.OnPing(247, true); + + EXPECT_EQ(visitor.stream_map_size(), 0); } TEST(ServerCallbackVisitorUnitTest, StreamFrames) { @@ -246,6 +262,8 @@ TEST(ServerCallbackVisitorUnitTest, StreamFrames) { NGHTTP2_HCAT_REQUEST))); visitor.OnBeginHeadersForStream(1); + EXPECT_EQ(visitor.stream_map_size(), 1); + EXPECT_CALL(callbacks, OnHeader(_, ":method", "POST", _)); visitor.OnHeaderForStream(1, ":method", "POST"); @@ -280,6 +298,8 @@ TEST(ServerCallbackVisitorUnitTest, StreamFrames) { EXPECT_CALL(callbacks, OnStreamClose(1, NGHTTP2_NO_ERROR)); visitor.OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR); + EXPECT_EQ(visitor.stream_map_size(), 0); + // RST_STREAM on stream 3 EXPECT_CALL(callbacks, OnBeginFrame(HasFrameHeader(3, RST_STREAM, 0))); visitor.OnFrameHeader(3, 4, RST_STREAM, 0); @@ -289,6 +309,74 @@ TEST(ServerCallbackVisitorUnitTest, StreamFrames) { EXPECT_CALL(callbacks, OnStreamClose(3, NGHTTP2_INTERNAL_ERROR)); visitor.OnCloseStream(3, Http2ErrorCode::INTERNAL_ERROR); + + EXPECT_EQ(visitor.stream_map_size(), 0); +} + +TEST(ServerCallbackVisitorUnitTest, DataWithPadding) { + testing::StrictMock<MockNghttp2Callbacks> callbacks; + CallbackVisitor visitor(Perspective::kServer, + *MockNghttp2Callbacks::GetCallbacks(), &callbacks); + + const size_t kPaddingLength = 39; + const uint8_t kFlags = NGHTTP2_FLAG_PADDED | NGHTTP2_FLAG_END_STREAM; + + testing::InSequence seq; + + // DATA on stream 1 + EXPECT_CALL(callbacks, OnBeginFrame(HasFrameHeader(1, DATA, kFlags))); + EXPECT_TRUE(visitor.OnFrameHeader(1, 25 + kPaddingLength, DATA, kFlags)); + + EXPECT_TRUE(visitor.OnBeginDataForStream(1, 25 + kPaddingLength)); + + // Padding before data. + EXPECT_TRUE(visitor.OnDataPaddingLength(1, kPaddingLength)); + + EXPECT_CALL(callbacks, + OnDataChunkRecv(kFlags, 1, "This is the request body.")); + EXPECT_CALL(callbacks, OnFrameRecv(IsData(1, _, kFlags, kPaddingLength))); + EXPECT_TRUE(visitor.OnDataForStream(1, "This is the request body.")); + visitor.OnEndStream(1); + + EXPECT_CALL(callbacks, OnStreamClose(1, NGHTTP2_NO_ERROR)); + visitor.OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR); + + // DATA on stream 3 + EXPECT_CALL(callbacks, OnBeginFrame(HasFrameHeader(3, DATA, kFlags))); + EXPECT_TRUE(visitor.OnFrameHeader(3, 25 + kPaddingLength, DATA, kFlags)); + + EXPECT_TRUE(visitor.OnBeginDataForStream(3, 25 + kPaddingLength)); + + // Data before padding. + EXPECT_CALL(callbacks, + OnDataChunkRecv(kFlags, 3, "This is the request body.")); + EXPECT_TRUE(visitor.OnDataForStream(3, "This is the request body.")); + + EXPECT_CALL(callbacks, OnFrameRecv(IsData(3, _, kFlags, kPaddingLength))); + EXPECT_TRUE(visitor.OnDataPaddingLength(3, kPaddingLength)); + visitor.OnEndStream(3); + + EXPECT_CALL(callbacks, OnStreamClose(3, NGHTTP2_NO_ERROR)); + visitor.OnCloseStream(3, Http2ErrorCode::HTTP2_NO_ERROR); + + // DATA on stream 5 + EXPECT_CALL(callbacks, OnBeginFrame(HasFrameHeader(5, DATA, kFlags))); + EXPECT_TRUE(visitor.OnFrameHeader(5, 25 + kPaddingLength, DATA, kFlags)); + + EXPECT_TRUE(visitor.OnBeginDataForStream(5, 25 + kPaddingLength)); + + // Error during padding. + EXPECT_CALL(callbacks, + OnDataChunkRecv(kFlags, 5, "This is the request body.")); + EXPECT_TRUE(visitor.OnDataForStream(5, "This is the request body.")); + + EXPECT_CALL(callbacks, OnFrameRecv(IsData(5, _, kFlags, kPaddingLength))) + .WillOnce(testing::Return(NGHTTP2_ERR_CALLBACK_FAILURE)); + EXPECT_FALSE(visitor.OnDataPaddingLength(5, kPaddingLength)); + visitor.OnEndStream(3); + + EXPECT_CALL(callbacks, OnStreamClose(5, NGHTTP2_NO_ERROR)); + visitor.OnCloseStream(5, Http2ErrorCode::HTTP2_NO_ERROR); } } // namespace 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 63448b3e56e..dd4601f48d9 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 @@ -48,6 +48,10 @@ class QUICHE_EXPORT_PRIVATE MetadataSource { // to indicate an error, as well as a boolean indicating whether the metadata // has been completely copied. virtual std::pair<int64_t, bool> Pack(uint8_t* dest, size_t dest_len) = 0; + + // This method is called when transmission of the metadata for this source + // fails in a non-recoverable way. + virtual void OnFailure() = 0; }; } // namespace adapter diff --git a/chromium/net/third_party/quiche/src/http2/adapter/header_validator.cc b/chromium/net/third_party/quiche/src/http2/adapter/header_validator.cc index c68ac3566e2..82424f658e4 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/header_validator.cc +++ b/chromium/net/third_party/quiche/src/http2/adapter/header_validator.cc @@ -1,6 +1,9 @@ #include "http2/adapter/header_validator.h" +#include <array> + #include "absl/strings/escaping.h" +#include "absl/strings/numbers.h" #include "common/platform/api/quiche_logging.h" namespace http2 { @@ -20,15 +23,92 @@ const absl::string_view kHttp2HeaderValueAllowedChars = const absl::string_view kHttp2StatusValueAllowedChars = "0123456789"; -// TODO(birenroy): Support websocket requests, which contain an extra -// `:protocol` pseudo-header. -bool ValidateRequestHeaders(const std::vector<std::string>& pseudo_headers) { +const absl::string_view kValidAuthorityChars = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~%!$&'()[" + "]*+,;=:"; + +using CharMap = std::array<bool, 256>; + +CharMap BuildValidCharMap(absl::string_view valid_chars) { + CharMap map; + map.fill(false); + for (char c : valid_chars) { + // Cast to uint8_t, guaranteed to have 8 bits. A char may have more, leading + // to possible indices above 256. + map[static_cast<uint8_t>(c)] = true; + } + return map; +} + +bool AllCharsInMap(absl::string_view str, const CharMap& map) { + for (char c : str) { + if (!map[static_cast<uint8_t>(c)]) { + return false; + } + } + return true; +} + +// Returns whether `authority` contains only characters from the `host` ABNF +// from RFC 3986 section 3.2.2. +bool IsValidAuthority(absl::string_view authority) { + static const CharMap valid_chars = BuildValidCharMap(kValidAuthorityChars); + return AllCharsInMap(authority, valid_chars); +} + +bool IsValidHeaderName(absl::string_view name) { + static const CharMap valid_chars = + BuildValidCharMap(kHttp2HeaderNameAllowedChars); + return AllCharsInMap(name, valid_chars); +} + +bool IsValidHeaderValue(absl::string_view value) { + static const CharMap valid_chars = + BuildValidCharMap(kHttp2HeaderValueAllowedChars); + return AllCharsInMap(value, valid_chars); +} + +bool IsValidStatus(absl::string_view status) { + static const CharMap valid_chars = + BuildValidCharMap(kHttp2StatusValueAllowedChars); + return AllCharsInMap(status, valid_chars); +} + +bool ValidateRequestHeaders(const std::vector<std::string>& pseudo_headers, + absl::string_view method, absl::string_view path, + bool allow_connect) { + QUICHE_VLOG(2) << "Request pseudo-headers: [" + << absl::StrJoin(pseudo_headers, ", ") + << "], allow_connect: " << allow_connect + << ", method: " << method << ", path: " << path; + if (allow_connect && method == "CONNECT") { + static const std::vector<std::string>* kConnectHeaders = + new std::vector<std::string>( + {":authority", ":method", ":path", ":protocol", ":scheme"}); + return pseudo_headers == *kConnectHeaders; + } + + if (path.empty()) { + return false; + } + if (path == "*") { + if (method != "OPTIONS") { + return false; + } + } else if (path[0] != '/') { + return false; + } + static const std::vector<std::string>* kRequiredHeaders = new std::vector<std::string>( {":authority", ":method", ":path", ":scheme"}); return pseudo_headers == *kRequiredHeaders; } +bool ValidateRequestTrailers(const std::vector<std::string>& pseudo_headers) { + return pseudo_headers.empty(); +} + bool ValidateResponseHeaders(const std::vector<std::string>& pseudo_headers) { static const std::vector<std::string>* kRequiredHeaders = new std::vector<std::string>({":status"}); @@ -44,42 +124,64 @@ bool ValidateResponseTrailers(const std::vector<std::string>& pseudo_headers) { void HeaderValidator::StartHeaderBlock() { pseudo_headers_.clear(); status_.clear(); + method_.clear(); + path_.clear(); + content_length_.reset(); } HeaderValidator::HeaderStatus HeaderValidator::ValidateSingleHeader( absl::string_view key, absl::string_view value) { if (key.empty()) { - return HEADER_NAME_EMPTY; + return HEADER_FIELD_INVALID; + } + if (max_field_size_.has_value() && + key.size() + value.size() > max_field_size_.value()) { + QUICHE_VLOG(2) << "Header field size is " << key.size() + value.size() + << ", exceeds max size of " << max_field_size_.value(); + return HEADER_FIELD_TOO_LONG; } const absl::string_view validated_key = key[0] == ':' ? key.substr(1) : key; - if (validated_key.find_first_not_of(kHttp2HeaderNameAllowedChars) != - absl::string_view::npos) { + if (!IsValidHeaderName(validated_key)) { QUICHE_VLOG(2) << "invalid chars in header name: [" << absl::CEscape(validated_key) << "]"; - return HEADER_NAME_INVALID_CHAR; + return HEADER_FIELD_INVALID; } - if (value.find_first_not_of(kHttp2HeaderValueAllowedChars) != - absl::string_view::npos) { + if (!IsValidHeaderValue(value)) { QUICHE_VLOG(2) << "invalid chars in header value: [" << absl::CEscape(value) << "]"; - return HEADER_VALUE_INVALID_CHAR; + return HEADER_FIELD_INVALID; } if (key[0] == ':') { if (key == ":status") { - if (value.size() != 3 || - value.find_first_not_of(kHttp2StatusValueAllowedChars) != - absl::string_view::npos) { + if (value.size() != 3 || !IsValidStatus(value)) { QUICHE_VLOG(2) << "malformed status value: [" << absl::CEscape(value) << "]"; - return HEADER_VALUE_INVALID_CHAR; + return HEADER_FIELD_INVALID; } if (value == "101") { // Switching protocols is not allowed on a HTTP/2 stream. - return HEADER_VALUE_INVALID_STATUS; + return HEADER_FIELD_INVALID; } status_ = std::string(value); + } else if (key == ":method") { + method_ = std::string(value); + } else if (key == ":authority" && !IsValidAuthority(value)) { + return HEADER_FIELD_INVALID; + } else if (key == ":path") { + if (value.empty()) { + // For now, reject an empty path regardless of scheme. + return HEADER_FIELD_INVALID; + } + path_ = std::string(value); } pseudo_headers_.push_back(std::string(key)); + } else if (key == "content-length") { + const bool success = HandleContentLength(value); + if (!success) { + return HEADER_FIELD_INVALID; + } + } else if (key == "te" && value != "trailers") { + return HEADER_FIELD_INVALID; } return HEADER_OK; } @@ -90,7 +192,10 @@ bool HeaderValidator::FinishHeaderBlock(HeaderType type) { std::sort(pseudo_headers_.begin(), pseudo_headers_.end()); switch (type) { case HeaderType::REQUEST: - return ValidateRequestHeaders(pseudo_headers_); + return ValidateRequestHeaders(pseudo_headers_, method_, path_, + allow_connect_); + case HeaderType::REQUEST_TRAILER: + return ValidateRequestTrailers(pseudo_headers_); case HeaderType::RESPONSE_100: case HeaderType::RESPONSE: return ValidateResponseHeaders(pseudo_headers_); @@ -100,5 +205,29 @@ bool HeaderValidator::FinishHeaderBlock(HeaderType type) { return false; } +bool HeaderValidator::HandleContentLength(absl::string_view value) { + if (value.empty()) { + return false; + } + + if (status_ == "204" && value != "0") { + // There should be no body in a "204 No Content" response. + return false; + } + if (!status_.empty() && status_[0] == '1' && value != "0") { + // There should also be no body in a 1xx response. + return false; + } + + size_t content_length = 0; + const bool valid = absl::SimpleAtoi(value, &content_length); + if (!valid) { + return false; + } + + content_length_ = content_length; + return true; +} + } // namespace adapter } // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/adapter/header_validator.h b/chromium/net/third_party/quiche/src/http2/adapter/header_validator.h index dd8ab592f01..30f16d52659 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/header_validator.h +++ b/chromium/net/third_party/quiche/src/http2/adapter/header_validator.h @@ -5,6 +5,7 @@ #include <vector> #include "absl/strings/string_view.h" +#include "absl/types/optional.h" #include "common/platform/api/quiche_export.h" namespace http2 { @@ -12,6 +13,7 @@ namespace adapter { enum class HeaderType : uint8_t { REQUEST, + REQUEST_TRAILER, RESPONSE_100, RESPONSE, RESPONSE_TRAILER, @@ -21,14 +23,18 @@ class QUICHE_EXPORT_PRIVATE HeaderValidator { public: HeaderValidator() {} + void SetMaxFieldSize(uint32_t field_size) { max_field_size_ = field_size; } + + // If called, this validator will allow the `:protocol` pseudo-header, as + // described in RFC 8441. + void AllowConnect() { allow_connect_ = true; } + void StartHeaderBlock(); enum HeaderStatus { HEADER_OK, - HEADER_NAME_EMPTY, - HEADER_NAME_INVALID_CHAR, - HEADER_VALUE_INVALID_CHAR, - HEADER_VALUE_INVALID_STATUS, + HEADER_FIELD_INVALID, + HEADER_FIELD_TOO_LONG, }; HeaderStatus ValidateSingleHeader(absl::string_view key, absl::string_view value); @@ -37,11 +43,21 @@ class QUICHE_EXPORT_PRIVATE HeaderValidator { // present for the given header type. bool FinishHeaderBlock(HeaderType type); + // For responses, returns the value of the ":status" header, if present. absl::string_view status_header() const { return status_; } + absl::optional<size_t> content_length() const { return content_length_; } + private: + bool HandleContentLength(absl::string_view value); + std::vector<std::string> pseudo_headers_; std::string status_; + std::string method_; + std::string path_; + absl::optional<size_t> max_field_size_; + absl::optional<size_t> content_length_; + bool allow_connect_ = false; }; } // namespace adapter diff --git a/chromium/net/third_party/quiche/src/http2/adapter/header_validator_test.cc b/chromium/net/third_party/quiche/src/http2/adapter/header_validator_test.cc index 01460c9a94b..52148df9850 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/header_validator_test.cc +++ b/chromium/net/third_party/quiche/src/http2/adapter/header_validator_test.cc @@ -1,16 +1,25 @@ #include "http2/adapter/header_validator.h" #include "absl/strings/str_cat.h" +#include "absl/types/optional.h" #include "common/platform/api/quiche_test.h" namespace http2 { namespace adapter { namespace test { +using ::testing::Optional; + +using Header = std::pair<absl::string_view, absl::string_view>; +constexpr Header kSampleRequestPseudoheaders[] = {{":authority", "www.foo.com"}, + {":method", "GET"}, + {":path", "/foo"}, + {":scheme", "https"}}; + TEST(HeaderValidatorTest, HeaderNameEmpty) { HeaderValidator v; HeaderValidator::HeaderStatus status = v.ValidateSingleHeader("", "value"); - EXPECT_EQ(HeaderValidator::HEADER_NAME_EMPTY, status); + EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, status); } TEST(HeaderValidatorTest, HeaderValueEmpty) { @@ -19,6 +28,18 @@ TEST(HeaderValidatorTest, HeaderValueEmpty) { EXPECT_EQ(HeaderValidator::HEADER_OK, status); } +TEST(HeaderValidatorTest, ExceedsMaxSize) { + HeaderValidator v; + v.SetMaxFieldSize(64u); + HeaderValidator::HeaderStatus status = + v.ValidateSingleHeader("name", "value"); + EXPECT_EQ(HeaderValidator::HEADER_OK, status); + status = v.ValidateSingleHeader( + "name2", + "Antidisestablishmentariansism is supercalifragilisticexpialodocious."); + EXPECT_EQ(HeaderValidator::HEADER_FIELD_TOO_LONG, status); +} + TEST(HeaderValidatorTest, NameHasInvalidChar) { HeaderValidator v; for (const bool is_pseudo_header : {true, false}) { @@ -37,13 +58,13 @@ TEST(HeaderValidatorTest, NameHasInvalidChar) { : absl::StrCat("na", c, "me"); HeaderValidator::HeaderStatus status = v.ValidateSingleHeader(name, "value"); - EXPECT_EQ(HeaderValidator::HEADER_NAME_INVALID_CHAR, status); + EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, status); } // Uppercase characters in header names should not be allowed. const std::string uc_name = is_pseudo_header ? ":Method" : "Name"; HeaderValidator::HeaderStatus status = v.ValidateSingleHeader(uc_name, "value"); - EXPECT_EQ(HeaderValidator::HEADER_NAME_INVALID_CHAR, status); + EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, status); } } @@ -60,7 +81,7 @@ TEST(HeaderValidatorTest, ValueHasInvalidChar) { for (const char* c : {"\r", "\n"}) { HeaderValidator::HeaderStatus status = v.ValidateSingleHeader("name", absl::StrCat("val", c, "ue")); - EXPECT_EQ(HeaderValidator::HEADER_VALUE_INVALID_CHAR, status); + EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, status); } } @@ -70,19 +91,19 @@ TEST(HeaderValidatorTest, StatusHasInvalidChar) { for (HeaderType type : {HeaderType::RESPONSE, HeaderType::RESPONSE_100}) { // When `:status` has a non-digit value, validation will fail. v.StartHeaderBlock(); - EXPECT_EQ(HeaderValidator::HEADER_VALUE_INVALID_CHAR, + EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, v.ValidateSingleHeader(":status", "bar")); EXPECT_FALSE(v.FinishHeaderBlock(type)); // When `:status` is too short, validation will fail. v.StartHeaderBlock(); - EXPECT_EQ(HeaderValidator::HEADER_VALUE_INVALID_CHAR, + EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, v.ValidateSingleHeader(":status", "10")); EXPECT_FALSE(v.FinishHeaderBlock(type)); // When `:status` is too long, validation will fail. v.StartHeaderBlock(); - EXPECT_EQ(HeaderValidator::HEADER_VALUE_INVALID_CHAR, + EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, v.ValidateSingleHeader(":status", "9000")); EXPECT_FALSE(v.FinishHeaderBlock(type)); @@ -94,16 +115,48 @@ TEST(HeaderValidatorTest, StatusHasInvalidChar) { } } +TEST(HeaderValidatorTest, AuthorityHasInvalidChar) { + HeaderValidator v; + v.StartHeaderBlock(); + + // These characters should be allowed. (Not exhaustive.) + for (const absl::string_view c : {"1", "-", "!", ":", "+", "=", ","}) { + HeaderValidator::HeaderStatus status = v.ValidateSingleHeader( + ":authority", absl::StrCat("ho", c, "st.example.com")); + EXPECT_EQ(HeaderValidator::HEADER_OK, status); + } + // These should not. + for (const absl::string_view c : {"\r", "\n", "|", "\\", "`"}) { + HeaderValidator::HeaderStatus status = v.ValidateSingleHeader( + ":authority", absl::StrCat("ho", c, "st.example.com")); + EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, status); + } + + // IPv4 example + HeaderValidator::HeaderStatus status = + v.ValidateSingleHeader(":authority", "123.45.67.89"); + EXPECT_EQ(HeaderValidator::HEADER_OK, status); + + // IPv6 examples + status = v.ValidateSingleHeader(":authority", + "2001:0db8:85a3:0000:0000:8a2e:0370:7334"); + EXPECT_EQ(HeaderValidator::HEADER_OK, status); + status = v.ValidateSingleHeader(":authority", "[::1]:80"); + EXPECT_EQ(HeaderValidator::HEADER_OK, status); + + // Empty field + status = v.ValidateSingleHeader(":authority", ""); + EXPECT_EQ(HeaderValidator::HEADER_OK, status); +} + TEST(HeaderValidatorTest, RequestPseudoHeaders) { HeaderValidator v; - const absl::string_view headers[] = {":authority", ":method", ":path", - ":scheme"}; - for (absl::string_view to_skip : headers) { + for (Header to_skip : kSampleRequestPseudoheaders) { v.StartHeaderBlock(); - for (absl::string_view to_add : headers) { + for (Header to_add : kSampleRequestPseudoheaders) { if (to_add != to_skip) { EXPECT_EQ(HeaderValidator::HEADER_OK, - v.ValidateSingleHeader(to_add, "foo")); + v.ValidateSingleHeader(to_add.first, to_add.second)); } } // When any pseudo-header is missing, final validation will fail. @@ -112,31 +165,31 @@ TEST(HeaderValidatorTest, RequestPseudoHeaders) { // When all pseudo-headers are present, final validation will succeed. v.StartHeaderBlock(); - for (absl::string_view to_add : headers) { + for (Header to_add : kSampleRequestPseudoheaders) { EXPECT_EQ(HeaderValidator::HEADER_OK, - v.ValidateSingleHeader(to_add, "foo")); + v.ValidateSingleHeader(to_add.first, to_add.second)); } EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::REQUEST)); // When an extra pseudo-header is present, final validation will fail. v.StartHeaderBlock(); - for (absl::string_view to_add : headers) { + for (Header to_add : kSampleRequestPseudoheaders) { EXPECT_EQ(HeaderValidator::HEADER_OK, - v.ValidateSingleHeader(to_add, "foo")); + v.ValidateSingleHeader(to_add.first, to_add.second)); } EXPECT_EQ(HeaderValidator::HEADER_OK, v.ValidateSingleHeader(":extra", "blah")); EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::REQUEST)); // When a required pseudo-header is repeated, final validation will fail. - for (absl::string_view to_repeat : headers) { + for (Header to_repeat : kSampleRequestPseudoheaders) { v.StartHeaderBlock(); - for (absl::string_view to_add : headers) { + for (Header to_add : kSampleRequestPseudoheaders) { EXPECT_EQ(HeaderValidator::HEADER_OK, - v.ValidateSingleHeader(to_add, "foo")); + v.ValidateSingleHeader(to_add.first, to_add.second)); if (to_add == to_repeat) { EXPECT_EQ(HeaderValidator::HEADER_OK, - v.ValidateSingleHeader(to_add, "foo")); + v.ValidateSingleHeader(to_add.first, to_add.second)); } } EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::REQUEST)); @@ -145,16 +198,107 @@ TEST(HeaderValidatorTest, RequestPseudoHeaders) { TEST(HeaderValidatorTest, WebsocketPseudoHeaders) { HeaderValidator v; - const absl::string_view headers[] = {":authority", ":method", ":path", - ":scheme"}; v.StartHeaderBlock(); - for (absl::string_view to_add : headers) { + for (Header to_add : kSampleRequestPseudoheaders) { + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, to_add.second)); + } + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":protocol", "websocket")); + // At this point, `:protocol` is treated as an extra pseudo-header. + EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::REQUEST)); + + // Future header blocks may send the `:protocol` pseudo-header for CONNECT + // requests. + v.AllowConnect(); + + v.StartHeaderBlock(); + for (Header to_add : kSampleRequestPseudoheaders) { EXPECT_EQ(HeaderValidator::HEADER_OK, - v.ValidateSingleHeader(to_add, "foo")); + v.ValidateSingleHeader(to_add.first, to_add.second)); } EXPECT_EQ(HeaderValidator::HEADER_OK, v.ValidateSingleHeader(":protocol", "websocket")); - // For now, `:protocol` is treated as an extra pseudo-header. + // The method is not "CONNECT", so `:protocol` is still treated as an extra + // pseudo-header. + EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::REQUEST)); + + v.StartHeaderBlock(); + for (Header to_add : kSampleRequestPseudoheaders) { + if (to_add.first == ":method") { + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, "CONNECT")); + } else { + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, to_add.second)); + } + } + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":protocol", "websocket")); + // After allowing the method, `:protocol` is acepted for CONNECT requests. + EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::REQUEST)); +} + +TEST(HeaderValidatorTest, AsteriskPathPseudoHeader) { + HeaderValidator v; + + // An asterisk :path should not be allowed for non-OPTIONS requests. + v.StartHeaderBlock(); + for (Header to_add : kSampleRequestPseudoheaders) { + if (to_add.first == ":path") { + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, "*")); + } else { + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, to_add.second)); + } + } + EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::REQUEST)); + + // An asterisk :path should be allowed for OPTIONS requests. + v.StartHeaderBlock(); + for (Header to_add : kSampleRequestPseudoheaders) { + if (to_add.first == ":path") { + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, "*")); + } else if (to_add.first == ":method") { + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, "OPTIONS")); + } else { + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, to_add.second)); + } + } + EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::REQUEST)); +} + +TEST(HeaderValidatorTest, InvalidPathPseudoHeader) { + HeaderValidator v; + + // An empty path should fail on single header validation and finish. + v.StartHeaderBlock(); + for (Header to_add : kSampleRequestPseudoheaders) { + if (to_add.first == ":path") { + EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, + v.ValidateSingleHeader(to_add.first, "")); + } else { + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, to_add.second)); + } + } + EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::REQUEST)); + + // A path that does not start with a slash should fail on finish. + v.StartHeaderBlock(); + for (Header to_add : kSampleRequestPseudoheaders) { + if (to_add.first == ":path") { + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, "shawarma")); + } else { + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, to_add.second)); + } + } EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::REQUEST)); } @@ -192,6 +336,78 @@ TEST(HeaderValidatorTest, ResponsePseudoHeaders) { } } +TEST(HeaderValidatorTest, Response204) { + HeaderValidator v; + + v.StartHeaderBlock(); + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":status", "204")); + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader("x-content", "is not present")); + EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::RESPONSE)); +} + +TEST(HeaderValidatorTest, Response204WithContentLengthZero) { + HeaderValidator v; + + v.StartHeaderBlock(); + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":status", "204")); + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader("x-content", "is not present")); + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader("content-length", "0")); + EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::RESPONSE)); +} + +TEST(HeaderValidatorTest, Response204WithContentLength) { + HeaderValidator v; + + v.StartHeaderBlock(); + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":status", "204")); + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader("x-content", "is not present")); + EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, + v.ValidateSingleHeader("content-length", "1")); +} + +TEST(HeaderValidatorTest, Response100) { + HeaderValidator v; + + v.StartHeaderBlock(); + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":status", "100")); + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader("x-content", "is not present")); + EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::RESPONSE)); +} + +TEST(HeaderValidatorTest, Response100WithContentLengthZero) { + HeaderValidator v; + + v.StartHeaderBlock(); + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":status", "100")); + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader("x-content", "is not present")); + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader("content-length", "0")); + EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::RESPONSE)); +} + +TEST(HeaderValidatorTest, Response100WithContentLength) { + HeaderValidator v; + + v.StartHeaderBlock(); + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":status", "100")); + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader("x-content", "is not present")); + EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, + v.ValidateSingleHeader("content-length", "1")); +} + TEST(HeaderValidatorTest, ResponseTrailerPseudoHeaders) { HeaderValidator v; @@ -208,6 +424,54 @@ TEST(HeaderValidatorTest, ResponseTrailerPseudoHeaders) { EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::RESPONSE_TRAILER)); } +TEST(HeaderValidatorTest, ValidContentLength) { + HeaderValidator v; + + v.StartHeaderBlock(); + EXPECT_EQ(v.content_length(), absl::nullopt); + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader("content-length", "41")); + EXPECT_THAT(v.content_length(), Optional(41)); + + v.StartHeaderBlock(); + EXPECT_EQ(v.content_length(), absl::nullopt); + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader("content-length", "42")); + EXPECT_THAT(v.content_length(), Optional(42)); +} + +TEST(HeaderValidatorTest, InvalidContentLength) { + HeaderValidator v; + + v.StartHeaderBlock(); + EXPECT_EQ(v.content_length(), absl::nullopt); + EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, + v.ValidateSingleHeader("content-length", "")); + EXPECT_EQ(v.content_length(), absl::nullopt); + EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, + v.ValidateSingleHeader("content-length", "nan")); + EXPECT_EQ(v.content_length(), absl::nullopt); + EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, + v.ValidateSingleHeader("content-length", "-42")); + EXPECT_EQ(v.content_length(), absl::nullopt); + // End on a positive note. + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader("content-length", "42")); + EXPECT_THAT(v.content_length(), Optional(42)); +} + +TEST(HeaderValidatorTest, TeHeader) { + HeaderValidator v; + + v.StartHeaderBlock(); + EXPECT_EQ(HeaderValidator::HEADER_OK, + v.ValidateSingleHeader("te", "trailers")); + + v.StartHeaderBlock(); + EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, + v.ValidateSingleHeader("te", "trailers, deflate")); +} + } // namespace test } // namespace adapter } // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/adapter/http2_util.cc b/chromium/net/third_party/quiche/src/http2/adapter/http2_util.cc index 553c0bd6a39..0419952202f 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/http2_util.cc +++ b/chromium/net/third_party/quiche/src/http2/adapter/http2_util.cc @@ -97,6 +97,8 @@ absl::string_view ConnectionErrorToString(ConnectionError error) { return "InvalidPushPromise"; case ConnectionError::kExceededMaxConcurrentStreams: return "ExceededMaxConcurrentStreams"; + case ConnectionError::kFlowControlError: + return "FlowControlError"; } return "UnknownConnectionError"; } 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 6d843a36253..db292d01b4a 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 @@ -78,6 +78,8 @@ class QUICHE_EXPORT_PRIVATE Http2VisitorInterface { kInvalidPushPromise, // The peer exceeded the max concurrent streams limit. kExceededMaxConcurrentStreams, + // The peer caused a flow control error. + kFlowControlError, }; virtual void OnConnectionError(ConnectionError error) = 0; @@ -121,9 +123,14 @@ class QUICHE_EXPORT_PRIVATE Http2VisitorInterface { HEADER_CONNECTION_ERROR, // The application rejects the header and requests the stream be reset. HEADER_RST_STREAM, - // The header is a violation of HTTP messaging semantics and will be reset + // The header field is invalid and will be reset with error code + // PROTOCOL_ERROR. + HEADER_FIELD_INVALID, + // The headers are a violation of HTTP messaging semantics and will be reset // with error code PROTOCOL_ERROR. HEADER_HTTP_MESSAGING, + // The headers caused a compression context error. + HEADER_COMPRESSION_ERROR, }; virtual OnHeaderResult OnHeaderForStream(Http2StreamId stream_id, absl::string_view key, @@ -139,6 +146,12 @@ class QUICHE_EXPORT_PRIVATE Http2VisitorInterface { virtual bool OnBeginDataForStream(Http2StreamId stream_id, size_t payload_length) = 0; + // Called when the optional padding length field is parsed as part of a DATA + // frame payload. `padding_length` represents the total amount of padding for + // this frame, including the length byte itself. + virtual bool OnDataPaddingLength(Http2StreamId stream_id, + size_t padding_length) = 0; + // Called when the connection receives some |data| (as part of a DATA frame // payload) for a stream. virtual bool OnDataForStream(Http2StreamId stream_id, 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 7454d2fad78..96f0978cee1 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 @@ -20,6 +20,7 @@ class QUICHE_NO_EXPORT MockHttp2Visitor : public Http2VisitorInterface { .WillByDefault(testing::Return(true)); ON_CALL(*this, OnHeaderForStream).WillByDefault(testing::Return(HEADER_OK)); ON_CALL(*this, OnEndHeadersForStream).WillByDefault(testing::Return(true)); + ON_CALL(*this, OnDataPaddingLength).WillByDefault(testing::Return(true)); ON_CALL(*this, OnBeginDataForStream).WillByDefault(testing::Return(true)); ON_CALL(*this, OnDataForStream).WillByDefault(testing::Return(true)); ON_CALL(*this, OnGoAway).WillByDefault(testing::Return(true)); @@ -50,6 +51,9 @@ class QUICHE_NO_EXPORT MockHttp2Visitor : public Http2VisitorInterface { MOCK_METHOD(bool, OnEndHeadersForStream, (Http2StreamId stream_id), (override)); + MOCK_METHOD(bool, OnDataPaddingLength, + (Http2StreamId strema_id, size_t padding_length), (override)); + MOCK_METHOD(bool, OnBeginDataForStream, (Http2StreamId stream_id, size_t payload_length), (override)); 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 821eb8cc748..a6ba0b27957 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 @@ -35,6 +35,11 @@ class SelfDeletingMetadataSource : public MetadataSource { return result; } + void OnFailure() override { + source_->OnFailure(); + delete this; + } + private: std::unique_ptr<MetadataSource> source_; }; 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 23322cb291d..5ff002007b5 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 @@ -17,6 +17,7 @@ namespace { using ConnectionError = Http2VisitorInterface::ConnectionError; +using spdy::SpdyFrameType; using testing::_; enum FrameType { @@ -100,23 +101,23 @@ TEST(NgHttp2AdapterTest, ClientHandlesFrames) { result = adapter->Send(); // Some bytes should have been serialized. EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::PING})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::PING})); visitor.Clear(); - const std::vector<const Header> headers1 = + const std::vector<Header> headers1 = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, {":path", "/this/is/request/one"}}); - const std::vector<const Header> headers2 = + const std::vector<Header> headers2 = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, {":path", "/this/is/request/two"}}); - const std::vector<const Header> headers3 = + const std::vector<Header> headers3 = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -151,9 +152,9 @@ TEST(NgHttp2AdapterTest, ClientHandlesFrames) { result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS, - spdy::SpdyFrameType::HEADERS, - spdy::SpdyFrameType::HEADERS})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::HEADERS, SpdyFrameType::HEADERS, + SpdyFrameType::HEADERS})); visitor.Clear(); // All streams are active and have not yet received any data, so the receive @@ -269,7 +270,7 @@ TEST(NgHttp2AdapterTest, ClientRejects100HeadersWithFin) { testing::InSequence s; - const std::vector<const Header> headers1 = + const std::vector<Header> headers1 = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -322,8 +323,316 @@ TEST(NgHttp2AdapterTest, ClientRejects100HeadersWithFin) { EXPECT_TRUE(adapter->want_write()); result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::RST_STREAM})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM})); +} + +TEST(NgHttp2AdapterTest, ClientRejects100HeadersWithContent) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); + + testing::InSequence s; + + const std::vector<Header> headers1 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + + const int32_t stream_id1 = adapter->SubmitRequest(headers1, nullptr, nullptr); + ASSERT_GT(stream_id1, 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); + visitor.Clear(); + + const std::string stream_frames = + TestFrameSequence() + .ServerPreface() + .Headers(1, {{":status", "100"}}, + /*fin=*/false) + .Data(1, "We needed the final headers before data, whoops") + .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", "100")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnFrameHeader(1, _, DATA, 0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, _)); + + const int64_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 1, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR)); + + EXPECT_TRUE(adapter->want_write()); + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM})); +} + +TEST(NgHttp2AdapterTest, ClientRejects100HeadersWithContentLength) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); + + testing::InSequence s; + + const std::vector<Header> headers1 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + + const int32_t stream_id1 = adapter->SubmitRequest(headers1, nullptr, nullptr); + ASSERT_GT(stream_id1, 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); + visitor.Clear(); + + const std::string stream_frames = + TestFrameSequence() + .ServerPreface() + .Headers(1, {{":status", "100"}, {"content-length", "42"}}, + /*fin=*/false) + .Headers(1, + {{":status", "200"}, + {"server", "my-fake-server"}, + {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}}, + /*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", "100")); + EXPECT_CALL( + visitor, + OnErrorDebug("Invalid HTTP header field was received: frame type: 1, " + "stream: 1, name: [content-length], value: [42]")); + EXPECT_CALL( + visitor, + OnInvalidFrame(1, Http2VisitorInterface::InvalidFrameError::kHttpHeader)); + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); + + const int64_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 1, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR)); + + EXPECT_TRUE(adapter->want_write()); + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM})); +} + +TEST(NgHttp2AdapterTest, ClientHandles204WithContent) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); + + testing::InSequence s; + + const std::vector<Header> headers1 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + + const int32_t stream_id1 = adapter->SubmitRequest(headers1, nullptr, nullptr); + ASSERT_GT(stream_id1, 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); + visitor.Clear(); + + const std::string stream_frames = + TestFrameSequence() + .ServerPreface() + .Headers(1, {{":status", "204"}, {"content-length", "2"}}, + /*fin=*/false) + .Data(1, "hi") + .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", "204")); + EXPECT_CALL( + visitor, + OnErrorDebug("Invalid HTTP header field was received: frame type: 1, " + "stream: 1, name: [content-length], value: [2]")); + EXPECT_CALL( + visitor, + OnInvalidFrame(1, Http2VisitorInterface::InvalidFrameError::kHttpHeader)); + + const int64_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 1, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR)); + + EXPECT_TRUE(adapter->want_write()); + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM})); +} + +TEST(NgHttp2AdapterTest, ClientHandles304WithContent) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); + + testing::InSequence s; + + const std::vector<Header> headers1 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + + const int32_t stream_id1 = adapter->SubmitRequest(headers1, nullptr, nullptr); + ASSERT_GT(stream_id1, 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); + visitor.Clear(); + + const std::string stream_frames = + TestFrameSequence() + .ServerPreface() + .Headers(1, {{":status", "304"}, {"content-length", "2"}}, + /*fin=*/false) + .Data(1, "hi") + .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", "304")); + EXPECT_CALL(visitor, OnHeaderForStream(1, "content-length", "2")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnFrameHeader(1, _, DATA, 0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 2)); + + const int64_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 1, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR)); + + EXPECT_TRUE(adapter->want_write()); + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM})); +} + +TEST(NgHttp2AdapterTest, ClientHandles304WithContentLength) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); + + testing::InSequence s; + + const std::vector<Header> headers = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + + const int32_t stream_id = adapter->SubmitRequest(headers, nullptr, nullptr); + ASSERT_GT(stream_id, 0); + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0)); + + int result = adapter->Send(); + EXPECT_EQ(0, result); + visitor.Clear(); + + const std::string stream_frames = + TestFrameSequence() + .ServerPreface() + .Headers(1, {{":status", "304"}, {"content-length", "2"}}, + /*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, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "304")); + EXPECT_CALL(visitor, OnHeaderForStream(1, "content-length", "2")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnEndStream(1)); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR)); + + const int64_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + + EXPECT_TRUE(adapter->want_write()); + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); } TEST(NgHttp2AdapterTest, ClientHandlesTrailers) { @@ -332,7 +641,7 @@ TEST(NgHttp2AdapterTest, ClientHandlesTrailers) { testing::InSequence s; - const std::vector<const Header> headers1 = + const std::vector<Header> headers1 = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -352,7 +661,7 @@ TEST(NgHttp2AdapterTest, ClientHandlesTrailers) { 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})); + EXPECT_THAT(data, EqualsFrames({SpdyFrameType::HEADERS})); visitor.Clear(); const std::string stream_frames = @@ -399,7 +708,55 @@ TEST(NgHttp2AdapterTest, ClientHandlesTrailers) { EXPECT_TRUE(adapter->want_write()); result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); +} + +TEST(NgHttp2AdapterTest, ClientSendsTrailers) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); + + testing::InSequence s; + + const std::vector<Header> headers1 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + + const std::string kBody = "This is an example request body."; + auto body1 = absl::make_unique<TestDataFrameSource>(visitor, false); + body1->AppendPayload(kBody); + // nghttp2 does not require that the data source indicate the end of data + // before trailers are enqueued. + + const int32_t stream_id1 = + adapter->SubmitRequest(headers1, std::move(body1), nullptr); + ASSERT_GT(stream_id1, 0); + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x4, 0)); + EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id1, _, 0x0, 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({SpdyFrameType::HEADERS, SpdyFrameType::DATA})); + visitor.Clear(); + + const std::vector<Header> trailers1 = + ToHeaders({{"extra-info", "Trailers are weird but good?"}}); + adapter->SubmitTrailer(stream_id1, trailers1); + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); + + result = adapter->Send(); + EXPECT_EQ(0, result); + data = visitor.data(); + EXPECT_THAT(data, EqualsFrames({SpdyFrameType::HEADERS})); } TEST(NgHttp2AdapterTest, ClientHandlesMetadata) { @@ -408,7 +765,7 @@ TEST(NgHttp2AdapterTest, ClientHandlesMetadata) { testing::InSequence s; - const std::vector<const Header> headers1 = + const std::vector<Header> headers1 = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -428,7 +785,7 @@ TEST(NgHttp2AdapterTest, ClientHandlesMetadata) { 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})); + EXPECT_THAT(data, EqualsFrames({SpdyFrameType::HEADERS})); visitor.Clear(); const std::string stream_frames = @@ -479,7 +836,67 @@ TEST(NgHttp2AdapterTest, ClientHandlesMetadata) { EXPECT_TRUE(adapter->want_write()); result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); +} + +TEST(NgHttp2AdapterTest, ClientHandlesMetadataWithEmptyPayload) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); + + testing::InSequence s; + + const std::vector<Header> headers1 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + + const int32_t stream_id = adapter->SubmitRequest(headers1, nullptr, nullptr); + ASSERT_GT(stream_id, 0); + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 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({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) + .Metadata(1, "") + .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(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(3); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnFrameHeader(1, _, kMetadataFrameType, 4)); + EXPECT_CALL(visitor, OnBeginMetadataForStream(1, _)); + EXPECT_CALL(visitor, OnMetadataEndForStream(1)); + EXPECT_CALL(visitor, OnFrameHeader(1, _, DATA, 1)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, _)); + EXPECT_CALL(visitor, OnDataForStream(1, "This is the response body.")); + EXPECT_CALL(visitor, OnEndStream(1)); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR)); + + const int64_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); } TEST(NgHttp2AdapterTest, ClientHandlesMetadataWithError) { @@ -488,7 +905,7 @@ TEST(NgHttp2AdapterTest, ClientHandlesMetadataWithError) { testing::InSequence s; - const std::vector<const Header> headers1 = + const std::vector<Header> headers1 = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -554,7 +971,7 @@ TEST(NgHttp2AdapterTest, ClientHandlesMetadataWithError) { EXPECT_TRUE(adapter->want_read()); // Even after an error. Why? result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); } TEST(NgHttp2AdapterTest, ClientHandlesHpackHeaderTableSetting) { @@ -563,7 +980,7 @@ TEST(NgHttp2AdapterTest, ClientHandlesHpackHeaderTableSetting) { testing::InSequence s; - const std::vector<const Header> headers1 = ToHeaders({ + const std::vector<Header> headers1 = ToHeaders({ {":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -607,7 +1024,7 @@ TEST(NgHttp2AdapterTest, ClientHandlesInvalidTrailers) { testing::InSequence s; - const std::vector<const Header> headers1 = + const std::vector<Header> headers1 = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -627,7 +1044,7 @@ TEST(NgHttp2AdapterTest, ClientHandlesInvalidTrailers) { 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})); + EXPECT_THAT(data, EqualsFrames({SpdyFrameType::HEADERS})); visitor.Clear(); const std::string stream_frames = @@ -683,8 +1100,8 @@ TEST(NgHttp2AdapterTest, ClientHandlesInvalidTrailers) { EXPECT_TRUE(adapter->want_write()); result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::RST_STREAM})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM})); } TEST(NgHttp2AdapterTest, ClientRstStreamWhileHandlingHeaders) { @@ -693,7 +1110,7 @@ TEST(NgHttp2AdapterTest, ClientRstStreamWhileHandlingHeaders) { testing::InSequence s; - const std::vector<const Header> headers1 = + const std::vector<Header> headers1 = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -713,7 +1130,7 @@ TEST(NgHttp2AdapterTest, ClientRstStreamWhileHandlingHeaders) { 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})); + EXPECT_THAT(data, EqualsFrames({SpdyFrameType::HEADERS})); visitor.Clear(); const std::string stream_frames = @@ -758,8 +1175,8 @@ TEST(NgHttp2AdapterTest, ClientRstStreamWhileHandlingHeaders) { EXPECT_TRUE(adapter->want_write()); result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::RST_STREAM})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM})); } TEST(NgHttp2AdapterTest, ClientConnectionErrorWhileHandlingHeaders) { @@ -768,7 +1185,7 @@ TEST(NgHttp2AdapterTest, ClientConnectionErrorWhileHandlingHeaders) { testing::InSequence s; - const std::vector<const Header> headers1 = + const std::vector<Header> headers1 = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -788,7 +1205,7 @@ TEST(NgHttp2AdapterTest, ClientConnectionErrorWhileHandlingHeaders) { 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})); + EXPECT_THAT(data, EqualsFrames({SpdyFrameType::HEADERS})); visitor.Clear(); const std::string stream_frames = @@ -827,7 +1244,7 @@ TEST(NgHttp2AdapterTest, ClientConnectionErrorWhileHandlingHeaders) { EXPECT_TRUE(adapter->want_write()); result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); } TEST(NgHttp2AdapterTest, ClientConnectionErrorWhileHandlingHeadersOnly) { @@ -836,7 +1253,7 @@ TEST(NgHttp2AdapterTest, ClientConnectionErrorWhileHandlingHeadersOnly) { testing::InSequence s; - const std::vector<const Header> headers1 = + const std::vector<Header> headers1 = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -856,7 +1273,7 @@ TEST(NgHttp2AdapterTest, ClientConnectionErrorWhileHandlingHeadersOnly) { 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})); + EXPECT_THAT(data, EqualsFrames({SpdyFrameType::HEADERS})); visitor.Clear(); const std::string stream_frames = @@ -894,7 +1311,7 @@ TEST(NgHttp2AdapterTest, ClientConnectionErrorWhileHandlingHeadersOnly) { EXPECT_TRUE(adapter->want_write()); result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); } TEST(NgHttp2AdapterTest, ClientRejectsHeaders) { @@ -903,7 +1320,7 @@ TEST(NgHttp2AdapterTest, ClientRejectsHeaders) { testing::InSequence s; - const std::vector<const Header> headers1 = + const std::vector<Header> headers1 = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -923,7 +1340,7 @@ TEST(NgHttp2AdapterTest, ClientRejectsHeaders) { 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})); + EXPECT_THAT(data, EqualsFrames({SpdyFrameType::HEADERS})); visitor.Clear(); const std::string stream_frames = @@ -957,7 +1374,23 @@ TEST(NgHttp2AdapterTest, ClientRejectsHeaders) { EXPECT_TRUE(adapter->want_write()); result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); +} + +TEST(NgHttp2AdapterTest, ClientStartsShutdown) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); + + EXPECT_FALSE(adapter->want_write()); + + // No-op for a client implementation. + adapter->SubmitShutdownNotice(); + EXPECT_FALSE(adapter->want_write()); + + int result = adapter->Send(); + EXPECT_EQ(0, result); + + EXPECT_EQ(visitor.data(), spdy::kHttp2ConnectionHeaderPrefix); } TEST(NgHttp2AdapterTest, ClientFailsOnGoAway) { @@ -966,7 +1399,7 @@ TEST(NgHttp2AdapterTest, ClientFailsOnGoAway) { testing::InSequence s; - const std::vector<const Header> headers1 = + const std::vector<Header> headers1 = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -986,7 +1419,7 @@ TEST(NgHttp2AdapterTest, ClientFailsOnGoAway) { 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})); + EXPECT_THAT(data, EqualsFrames({SpdyFrameType::HEADERS})); visitor.Clear(); const std::string stream_frames = @@ -1028,7 +1461,7 @@ TEST(NgHttp2AdapterTest, ClientFailsOnGoAway) { EXPECT_TRUE(adapter->want_write()); result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); } TEST(NgHttp2AdapterTest, ClientRejects101Response) { @@ -1037,7 +1470,7 @@ TEST(NgHttp2AdapterTest, ClientRejects101Response) { testing::InSequence s; - const std::vector<const Header> headers1 = + const std::vector<Header> headers1 = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -1055,7 +1488,7 @@ TEST(NgHttp2AdapterTest, ClientRejects101Response) { 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})); + EXPECT_THAT(data, EqualsFrames({SpdyFrameType::HEADERS})); visitor.Clear(); const std::string stream_frames = @@ -1098,8 +1531,8 @@ TEST(NgHttp2AdapterTest, ClientRejects101Response) { EXPECT_TRUE(adapter->want_write()); result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::RST_STREAM})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM})); } TEST(NgHttp2AdapterTest, ClientSubmitRequest) { @@ -1131,7 +1564,7 @@ TEST(NgHttp2AdapterTest, ClientSubmitRequest) { result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); visitor.Clear(); EXPECT_EQ(0, adapter->GetHpackEncoderDynamicTableSize()); @@ -1173,8 +1606,8 @@ TEST(NgHttp2AdapterTest, ClientSubmitRequest) { // 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(), + EqualsFrames({SpdyFrameType::HEADERS, SpdyFrameType::DATA})); EXPECT_THAT(visitor.data(), testing::HasSubstr(kBody)); visitor.Clear(); EXPECT_FALSE(adapter->want_write()); @@ -1196,7 +1629,7 @@ TEST(NgHttp2AdapterTest, ClientSubmitRequest) { result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::HEADERS})); EXPECT_EQ(kSentinel2, adapter->GetStreamUserData(stream_id)); @@ -1237,7 +1670,7 @@ TEST(NgHttp2AdapterTest, ClientSubmitRequestWithDataProvider) { result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); visitor.Clear(); EXPECT_FALSE(adapter->want_write()); @@ -1267,8 +1700,8 @@ TEST(NgHttp2AdapterTest, ClientSubmitRequestWithDataProvider) { result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS, - spdy::SpdyFrameType::DATA})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::HEADERS, SpdyFrameType::DATA})); EXPECT_THAT(visitor.data(), testing::HasSubstr(kBody)); EXPECT_FALSE(adapter->want_write()); } @@ -1310,7 +1743,7 @@ TEST(NgHttp2AdapterTest, ClientSubmitRequestWithDataProviderAndReadBlock) { EXPECT_THAT(serialized, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); - EXPECT_THAT(serialized, EqualsFrames({spdy::SpdyFrameType::HEADERS})); + EXPECT_THAT(serialized, EqualsFrames({SpdyFrameType::HEADERS})); visitor.Clear(); EXPECT_FALSE(adapter->want_write()); @@ -1323,7 +1756,7 @@ TEST(NgHttp2AdapterTest, ClientSubmitRequestWithDataProviderAndReadBlock) { result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::DATA})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::DATA})); EXPECT_FALSE(adapter->want_write()); // Stream data is done, so this stream cannot be resumed. @@ -1368,7 +1801,7 @@ TEST(NgHttp2AdapterTest, ClientSubmitRequestEmptyDataWithFin) { EXPECT_THAT(serialized, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); - EXPECT_THAT(serialized, EqualsFrames({spdy::SpdyFrameType::HEADERS})); + EXPECT_THAT(serialized, EqualsFrames({SpdyFrameType::HEADERS})); visitor.Clear(); EXPECT_FALSE(adapter->want_write()); @@ -1381,7 +1814,7 @@ TEST(NgHttp2AdapterTest, ClientSubmitRequestEmptyDataWithFin) { result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::DATA})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::DATA})); EXPECT_FALSE(adapter->want_write()); // Stream data is done, so this stream cannot be resumed. @@ -1434,8 +1867,8 @@ TEST(NgHttp2AdapterTest, ClientSubmitRequestWithDataProviderAndWriteBlock) { EXPECT_THAT(serialized, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); - EXPECT_THAT(serialized, EqualsFrames({spdy::SpdyFrameType::HEADERS, - spdy::SpdyFrameType::DATA})); + EXPECT_THAT(serialized, + EqualsFrames({SpdyFrameType::HEADERS, SpdyFrameType::DATA})); EXPECT_FALSE(adapter->want_write()); } @@ -1468,7 +1901,7 @@ TEST(NgHttp2AdapterTest, ClientReceivesDataOnClosedStream) { result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); visitor.Clear(); // Let the client open a stream with a request. @@ -1486,7 +1919,7 @@ TEST(NgHttp2AdapterTest, ClientReceivesDataOnClosedStream) { result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::HEADERS})); visitor.Clear(); // Let the client RST_STREAM the stream it opened. @@ -1499,7 +1932,7 @@ TEST(NgHttp2AdapterTest, ClientReceivesDataOnClosedStream) { result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::RST_STREAM})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::RST_STREAM})); visitor.Clear(); // Let the server send a response on the stream. (It might not have received @@ -1543,9 +1976,8 @@ TEST(NgHttp2AdapterTest, SubmitMetadata) { EXPECT_THAT(serialized, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); - EXPECT_THAT( - serialized, - EqualsFrames({static_cast<spdy::SpdyFrameType>(kMetadataFrameType)})); + EXPECT_THAT(serialized, + EqualsFrames({static_cast<SpdyFrameType>(kMetadataFrameType)})); EXPECT_FALSE(adapter->want_write()); } @@ -1575,12 +2007,11 @@ TEST(NgHttp2AdapterTest, SubmitMetadataMultipleFrames) { EXPECT_THAT(serialized, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); - EXPECT_THAT( - serialized, - EqualsFrames({static_cast<spdy::SpdyFrameType>(kMetadataFrameType), - static_cast<spdy::SpdyFrameType>(kMetadataFrameType), - static_cast<spdy::SpdyFrameType>(kMetadataFrameType), - static_cast<spdy::SpdyFrameType>(kMetadataFrameType)})); + EXPECT_THAT(serialized, + EqualsFrames({static_cast<SpdyFrameType>(kMetadataFrameType), + static_cast<SpdyFrameType>(kMetadataFrameType), + static_cast<SpdyFrameType>(kMetadataFrameType), + static_cast<SpdyFrameType>(kMetadataFrameType)})); EXPECT_FALSE(adapter->want_write()); } @@ -1602,9 +2033,8 @@ TEST(NgHttp2AdapterTest, SubmitConnectionMetadata) { EXPECT_THAT(serialized, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); - EXPECT_THAT( - serialized, - EqualsFrames({static_cast<spdy::SpdyFrameType>(kMetadataFrameType)})); + EXPECT_THAT(serialized, + EqualsFrames({static_cast<SpdyFrameType>(kMetadataFrameType)})); EXPECT_FALSE(adapter->want_write()); } @@ -1640,7 +2070,7 @@ TEST(NgHttp2AdapterTest, ClientObeysMaxConcurrentStreams) { result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); visitor.Clear(); EXPECT_FALSE(adapter->want_write()); @@ -1664,8 +2094,8 @@ TEST(NgHttp2AdapterTest, ClientObeysMaxConcurrentStreams) { result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS, - spdy::SpdyFrameType::DATA})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::HEADERS, SpdyFrameType::DATA})); EXPECT_THAT(visitor.data(), testing::HasSubstr(kBody)); visitor.Clear(); EXPECT_FALSE(adapter->want_write()); @@ -1720,11 +2150,267 @@ TEST(NgHttp2AdapterTest, ClientObeysMaxConcurrentStreams) { result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::HEADERS})); visitor.Clear(); EXPECT_FALSE(adapter->want_write()); } +TEST(NgHttp2AdapterTest, ClientReceivesInitialWindowSetting) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); + + const std::string initial_frames = + TestFrameSequence() + .Settings({{INITIAL_WINDOW_SIZE, 80000u}}) + .WindowUpdate(0, 65536) + .Serialize(); + // Server preface (SETTINGS with INITIAL_STREAM_WINDOW) + EXPECT_CALL(visitor, OnFrameHeader(0, 6, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSetting(Http2Setting{INITIAL_WINDOW_SIZE, 80000u})); + EXPECT_CALL(visitor, OnSettingsEnd()); + EXPECT_CALL(visitor, OnFrameHeader(0, 4, WINDOW_UPDATE, 0)); + EXPECT_CALL(visitor, OnWindowUpdate(0, 65536)); + + const int64_t initial_result = adapter->ProcessBytes(initial_frames); + EXPECT_EQ(initial_frames.size(), static_cast<size_t>(initial_result)); + + // Session will want to write a SETTINGS ack. + EXPECT_TRUE(adapter->want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + + int64_t result = adapter->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})); + visitor.Clear(); + + const std::string kLongBody = std::string(81000, 'c'); + auto body1 = absl::make_unique<TestDataFrameSource>(visitor, true); + body1->AppendPayload(kLongBody); + body1->EndData(); + const int stream_id = + adapter->SubmitRequest(ToHeaders({{":method", "POST"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}), + std::move(body1), nullptr); + EXPECT_GT(stream_id, 0); + EXPECT_TRUE(adapter->want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0)); + // The client can send more than 4 frames (65536 bytes) of data. + EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, 16384, 0x0, 0)).Times(4); + EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, 14464, 0x0, 0)); + + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::HEADERS, SpdyFrameType::DATA, + SpdyFrameType::DATA, SpdyFrameType::DATA, + SpdyFrameType::DATA, SpdyFrameType::DATA})); +} + +TEST(NgHttp2AdapterTest, ClientReceivesInitialWindowSettingAfterStreamStart) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); + + const std::string initial_frames = + TestFrameSequence().ServerPreface().WindowUpdate(0, 65536).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, 4, WINDOW_UPDATE, 0)); + EXPECT_CALL(visitor, OnWindowUpdate(0, 65536)); + + const int64_t initial_result = adapter->ProcessBytes(initial_frames); + EXPECT_EQ(initial_frames.size(), static_cast<size_t>(initial_result)); + + // Session will want to write a SETTINGS ack. + EXPECT_TRUE(adapter->want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + + int64_t result = adapter->Send(); + EXPECT_EQ(0, result); + visitor.Clear(); + + const std::string kLongBody = std::string(81000, 'c'); + auto body1 = absl::make_unique<TestDataFrameSource>(visitor, true); + body1->AppendPayload(kLongBody); + body1->EndData(); + const int stream_id = + adapter->SubmitRequest(ToHeaders({{":method", "POST"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}), + std::move(body1), nullptr); + EXPECT_GT(stream_id, 0); + EXPECT_TRUE(adapter->want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0)); + // The client can only send 65535 bytes of data, as the stream window has not + // yet been increased. + EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, 16384, 0x0, 0)).Times(3); + EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, 16383, 0x0, 0)); + + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::HEADERS, SpdyFrameType::DATA, + SpdyFrameType::DATA, SpdyFrameType::DATA, + SpdyFrameType::DATA})); + visitor.Clear(); + + // Can't write any more due to flow control. + EXPECT_FALSE(adapter->want_write()); + + const std::string settings_frame = + TestFrameSequence().Settings({{INITIAL_WINDOW_SIZE, 80000u}}).Serialize(); + // SETTINGS with INITIAL_STREAM_WINDOW + EXPECT_CALL(visitor, OnFrameHeader(0, 6, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSetting(Http2Setting{INITIAL_WINDOW_SIZE, 80000u})); + EXPECT_CALL(visitor, OnSettingsEnd()); + + const int64_t settings_result = adapter->ProcessBytes(settings_frame); + EXPECT_EQ(settings_frame.size(), static_cast<size_t>(settings_result)); + + EXPECT_TRUE(adapter->want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + // The client can write more after receiving the INITIAL_WINDOW_SIZE setting. + EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, 14465, 0x0, 0)); + + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::DATA})); +} + +TEST(NgHttp2AdapterTest, InvalidInitialWindowSetting) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); + + const uint32_t kTooLargeInitialWindow = 1u << 31; + const std::string initial_frames = + TestFrameSequence() + .Settings({{INITIAL_WINDOW_SIZE, kTooLargeInitialWindow}}) + .Serialize(); + // Server preface (SETTINGS with INITIAL_STREAM_WINDOW) + EXPECT_CALL(visitor, OnFrameHeader(0, 6, SETTINGS, 0)); + EXPECT_CALL(visitor, + OnInvalidFrame( + 0, Http2VisitorInterface::InvalidFrameError::kFlowControl)); + + const int64_t initial_result = adapter->ProcessBytes(initial_frames); + EXPECT_EQ(initial_frames.size(), static_cast<size_t>(initial_result)); + + // Session will want to write a GOAWAY. + EXPECT_TRUE(adapter->want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0)); + EXPECT_CALL( + visitor, + OnFrameSent(GOAWAY, 0, _, 0x0, + static_cast<int>(Http2ErrorCode::FLOW_CONTROL_ERROR))); + + int64_t result = adapter->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::GOAWAY})); + visitor.Clear(); +} + +TEST(NgHttp2AdapterTest, InitialWindowSettingCausesOverflow) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); + + testing::InSequence s; + + const std::vector<Header> headers = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + const int32_t stream_id = adapter->SubmitRequest(headers, nullptr, nullptr); + ASSERT_GT(stream_id, 0); + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0)); + int64_t write_result = adapter->Send(); + EXPECT_EQ(0, write_result); + absl::string_view data = visitor.data(); + EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); + data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); + EXPECT_THAT(data, EqualsFrames({SpdyFrameType::HEADERS})); + visitor.Clear(); + + const uint32_t kLargeInitialWindow = (1u << 31) - 1; + const std::string frames = + TestFrameSequence() + .ServerPreface() + .Headers(stream_id, {{":status", "200"}}, /*fin=*/false) + .WindowUpdate(stream_id, 65536u) + .Settings({{INITIAL_WINDOW_SIZE, kLargeInitialWindow}}) + .Serialize(); + + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + EXPECT_CALL(visitor, OnFrameHeader(stream_id, _, HEADERS, 0x4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(stream_id)); + EXPECT_CALL(visitor, OnHeaderForStream(stream_id, ":status", "200")); + EXPECT_CALL(visitor, OnEndHeadersForStream(stream_id)); + + EXPECT_CALL(visitor, OnFrameHeader(stream_id, 4, WINDOW_UPDATE, 0x0)); + EXPECT_CALL(visitor, OnWindowUpdate(stream_id, 65536)); + + EXPECT_CALL(visitor, OnFrameHeader(0, 6, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSetting(Http2Setting{INITIAL_WINDOW_SIZE, + kLargeInitialWindow})); + EXPECT_CALL(visitor, OnSettingsEnd()); + + const int64_t read_result = adapter->ProcessBytes(frames); + EXPECT_EQ(static_cast<size_t>(read_result), frames.size()); + EXPECT_TRUE(adapter->want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + + // The stream window update plus the SETTINGS frame with INITIAL_WINDOW_SIZE + // pushes the stream's flow control window outside of the acceptable range. + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, stream_id, 4, 0x0)); + EXPECT_CALL( + visitor, + OnFrameSent(RST_STREAM, stream_id, 4, 0x0, + static_cast<int>(Http2ErrorCode::FLOW_CONTROL_ERROR))); + EXPECT_CALL(visitor, + OnCloseStream(stream_id, Http2ErrorCode::FLOW_CONTROL_ERROR)); + + int result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM})); +} + TEST(NgHttp2AdapterTest, ClientForbidsPushPromise) { DataSavingVisitor visitor; auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); @@ -1740,11 +2426,11 @@ TEST(NgHttp2AdapterTest, ClientForbidsPushPromise) { 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})); + EXPECT_THAT(data, EqualsFrames({SpdyFrameType::SETTINGS})); visitor.Clear(); - const std::vector<const Header> headers = + const std::vector<Header> headers = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -1755,10 +2441,10 @@ TEST(NgHttp2AdapterTest, ClientForbidsPushPromise) { EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0)); write_result = adapter->Send(); EXPECT_EQ(0, write_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::HEADERS})); visitor.Clear(); - const std::vector<const Header> push_headers = + const std::vector<Header> push_headers = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -1812,11 +2498,11 @@ TEST(NgHttp2AdapterTest, ClientForbidsPushStream) { 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})); + EXPECT_THAT(data, EqualsFrames({SpdyFrameType::SETTINGS})); visitor.Clear(); - const std::vector<const Header> headers = + const std::vector<Header> headers = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -1827,7 +2513,7 @@ TEST(NgHttp2AdapterTest, ClientForbidsPushStream) { EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0)); write_result = adapter->Send(); EXPECT_EQ(0, write_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::HEADERS})); visitor.Clear(); const std::string frames = @@ -1880,6 +2566,118 @@ TEST(NgHttp2AdapterTest, FailureSendingConnectionPreface) { EXPECT_EQ(result, NGHTTP2_ERR_CALLBACK_FAILURE); } +TEST(NgHttp2AdapterTest, ConnectionErrorOnControlFrameSent) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + + const std::string frames = + TestFrameSequence().ClientPreface().Ping(42).Serialize(); + testing::InSequence s; + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + // PING + EXPECT_CALL(visitor, OnFrameHeader(0, _, PING, 0)); + EXPECT_CALL(visitor, OnPing(42, false)); + + const int64_t read_result = adapter->ProcessBytes(frames); + EXPECT_EQ(static_cast<size_t>(read_result), frames.size()); + + EXPECT_TRUE(adapter->want_write()); + + // SETTINGS ack + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)) + .WillOnce(testing::Return(-902)); + EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kSendError)); + + int send_result = adapter->Send(); + EXPECT_LT(send_result, 0); + + // Apparently nghttp2 retries sending the frames that had failed before. + EXPECT_TRUE(adapter->want_write()); + + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(PING, 0, _, 0x1, 0)); + send_result = adapter->Send(); + EXPECT_EQ(send_result, 0); + + EXPECT_FALSE(adapter->want_write()); +} + +TEST(NgHttp2AdapterTest, ConnectionErrorOnDataFrameSent) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + + 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, 0x5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnEndStream(1)); + + const int64_t read_result = adapter->ProcessBytes(frames); + EXPECT_EQ(static_cast<size_t>(read_result), frames.size()); + + auto body = absl::make_unique<TestDataFrameSource>(visitor, true); + body->AppendPayload("Here is some data, which will lead to a fatal error"); + TestDataFrameSource* body_ptr = body.get(); + int submit_result = adapter->SubmitResponse( + 1, ToHeaders({{":status", "200"}}), std::move(body)); + ASSERT_EQ(0, submit_result); + + EXPECT_TRUE(adapter->want_write()); + + // SETTINGS ack + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + // Stream 1, with doomed DATA + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0)); + EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0)) + .WillOnce(testing::Return(-902)); + EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kSendError)); + + int send_result = adapter->Send(); + EXPECT_LT(send_result, 0); + + // The test data source got a signal that the first chunk of data was sent + // successfully, so discarded that data internally. However, due to the send + // error, the next Send() from nghttp2 will try to send that exact same data + // again. Without this line appending the exact same data back to the data + // source, the test crashes. It is not clear how the data source would know to + // not discard the data, unless told by the session? This is not intuitive. + body_ptr->AppendPayload( + "Here is some data, which will lead to a fatal error"); + + // Apparently nghttp2 retries sending the frames that had failed before. + EXPECT_TRUE(adapter->want_write()); + EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0)); + + send_result = adapter->Send(); + EXPECT_EQ(send_result, 0); + + EXPECT_FALSE(adapter->want_write()); +} + TEST(NgHttp2AdapterTest, ServerConstruction) { testing::StrictMock<MockHttp2Visitor> visitor; auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); @@ -1998,9 +2796,66 @@ TEST(NgHttp2AdapterTest, ServerHandlesFrames) { // Some bytes should have been serialized. EXPECT_EQ(0, send_result); // SETTINGS ack, two PING acks. - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::PING, - spdy::SpdyFrameType::PING})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::PING, + SpdyFrameType::PING})); +} + +TEST(NgHttp2AdapterTest, ServerHandlesDataWithPadding) { + 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) + .Data(1, "This is the request body.", + /*fin=*/true, /*padding_length=*/39) + .Headers(3, + {{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/two"}}, + /*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()); + + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnFrameHeader(1, 25 + 39, DATA, 0x9)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 25 + 39)); + EXPECT_CALL(visitor, OnDataForStream(1, "This is the request body.")); + // Note: nghttp2 passes padding information after the actual data. + EXPECT_CALL(visitor, OnDataPaddingLength(1, 39)); + EXPECT_CALL(visitor, OnEndStream(1)); + EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(3)); + EXPECT_CALL(visitor, OnHeaderForStream(3, _, _)).Times(4); + EXPECT_CALL(visitor, OnEndHeadersForStream(3)); + EXPECT_CALL(visitor, OnEndStream(3)); + + const int64_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(frames.size(), result); + + EXPECT_TRUE(adapter->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({SpdyFrameType::SETTINGS})); } // Tests the case where the response body is in the progress of being sent while @@ -2088,7 +2943,7 @@ TEST(NgHttp2AdapterTest, ServerSubmitsTrailersWhileDataDeferred) { send_result = adapter->Send(); EXPECT_EQ(0, send_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::HEADERS})); visitor.Clear(); // Resuming the stream results in the library wanting to write again. @@ -2105,6 +2960,147 @@ TEST(NgHttp2AdapterTest, ServerSubmitsTrailersWhileDataDeferred) { EXPECT_FALSE(adapter->want_write()); } +TEST(NgHttp2AdapterTest, ClientDisobeysConnectionFlowControl) { + 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) + // 70000 bytes of data + .Data(1, std::string(16384, 'a')) + .Data(1, std::string(16384, 'a')) + .Data(1, std::string(16384, 'a')) + .Data(1, std::string(16384, 'a')) + .Data(1, std::string(4464, 'a')) + .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).Times(5); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnFrameHeader(1, 16384, DATA, 0x0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 16384)); + EXPECT_CALL(visitor, OnDataForStream(1, _)); + EXPECT_CALL(visitor, OnFrameHeader(1, 16384, DATA, 0x0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 16384)); + EXPECT_CALL(visitor, OnDataForStream(1, _)); + EXPECT_CALL(visitor, OnFrameHeader(1, 16384, DATA, 0x0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 16384)); + EXPECT_CALL(visitor, OnDataForStream(1, _)); + EXPECT_CALL(visitor, OnFrameHeader(1, 16384, DATA, 0x0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 16384)); + // No further frame data or headers are delivered. + + const int64_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(frames.size(), static_cast<size_t>(result)); + + EXPECT_TRUE(adapter->want_write()); + + // No SETTINGS ack is written. + EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0)); + EXPECT_CALL( + visitor, + OnFrameSent(GOAWAY, 0, _, 0x0, + static_cast<int>(Http2ErrorCode::FLOW_CONTROL_ERROR))); + + int send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::GOAWAY})); +} + +TEST(NgHttp2AdapterTest, ClientDisobeysStreamFlowControl) { + 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) + .Serialize(); + const std::string more_frames = TestFrameSequence() + // 70000 bytes of data + .Data(1, std::string(16384, 'a')) + .Data(1, std::string(16384, 'a')) + .Data(1, std::string(16384, 'a')) + .Data(1, std::string(16384, 'a')) + .Data(1, std::string(4464, 'a')) + .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).Times(5); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + + int64_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(frames.size(), static_cast<size_t>(result)); + + adapter->SubmitWindowUpdate(0, 20000); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(WINDOW_UPDATE, 0, 4, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(WINDOW_UPDATE, 0, 4, 0x0, 0)); + + int send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS, + SpdyFrameType::WINDOW_UPDATE})); + visitor.Clear(); + + EXPECT_CALL(visitor, OnFrameHeader(1, 16384, DATA, 0x0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 16384)); + EXPECT_CALL(visitor, OnDataForStream(1, _)); + EXPECT_CALL(visitor, OnFrameHeader(1, 16384, DATA, 0x0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 16384)); + EXPECT_CALL(visitor, OnDataForStream(1, _)); + EXPECT_CALL(visitor, OnFrameHeader(1, 16384, DATA, 0x0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 16384)); + EXPECT_CALL(visitor, OnDataForStream(1, _)); + EXPECT_CALL(visitor, OnFrameHeader(1, 16384, DATA, 0x0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 16384)); + EXPECT_CALL(visitor, OnDataForStream(1, _)); + // No further frame data or headers for stream 1 are delivered. + + result = adapter->ProcessBytes(more_frames); + EXPECT_EQ(more_frames.size(), static_cast<size_t>(result)); + + EXPECT_TRUE(adapter->want_write()); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, 4, 0x0)); + EXPECT_CALL( + visitor, + OnFrameSent(RST_STREAM, 1, 4, 0x0, + static_cast<int>(Http2ErrorCode::FLOW_CONTROL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::FLOW_CONTROL_ERROR)); + + send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::RST_STREAM})); +} + TEST(NgHttp2AdapterTest, ServerErrorWhileHandlingHeaders) { DataSavingVisitor visitor; auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); @@ -2160,8 +3156,103 @@ TEST(NgHttp2AdapterTest, ServerErrorWhileHandlingHeaders) { // 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})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM})); +} + +TEST(NgHttp2AdapterTest, ServerErrorWhileHandlingHeadersDropsFrames) { + 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.") + .Metadata(1, "This is the request metadata.") + .RstStream(1, Http2ErrorCode::CANCEL) + .WindowUpdate(0, 2000) + .Headers(3, + {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/two"}}, + /*fin=*/false) + .Metadata(3, "This is the request metadata.", + /*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()); + + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4); + EXPECT_CALL(visitor, OnHeaderForStream(1, "accept", "some bogus value!")) + .WillOnce(testing::Return(Http2VisitorInterface::HEADER_RST_STREAM)); + // For the RST_STREAM-marked stream, the control frames and METADATA frame but + // not the DATA frame are delivered to the visitor. + EXPECT_CALL(visitor, OnFrameHeader(1, 4, WINDOW_UPDATE, 0)); + EXPECT_CALL(visitor, OnWindowUpdate(1, 2000)); + 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, 4, RST_STREAM, 0)); + EXPECT_CALL(visitor, OnRstStream(1, Http2ErrorCode::CANCEL)); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::CANCEL)); + EXPECT_CALL(visitor, OnFrameHeader(0, 4, WINDOW_UPDATE, 0)); + EXPECT_CALL(visitor, OnWindowUpdate(0, 2000)); + EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(3)); + EXPECT_CALL(visitor, OnHeaderForStream(3, _, _)).Times(4); + EXPECT_CALL(visitor, OnEndHeadersForStream(3)); + EXPECT_CALL(visitor, OnFrameHeader(3, _, kMetadataFrameType, 0)); + EXPECT_CALL(visitor, OnBeginMetadataForStream(3, _)); + EXPECT_CALL(visitor, OnMetadataForStream(3, "This is the re")) + .WillOnce(testing::DoAll(testing::InvokeWithoutArgs([&adapter]() { + adapter->SubmitRst( + 3, Http2ErrorCode::REFUSED_STREAM); + }), + testing::Return(true))); + // The rest of the metadata is still delivered to the visitor. + EXPECT_CALL(visitor, OnFrameHeader(3, _, kMetadataFrameType, 4)); + EXPECT_CALL(visitor, OnBeginMetadataForStream(3, _)); + EXPECT_CALL(visitor, OnMetadataForStream(3, "quest metadata.")); + EXPECT_CALL(visitor, OnMetadataEndForStream(3)); + + const int64_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(frames.size(), static_cast<size_t>(result)); + + EXPECT_TRUE(adapter->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, OnBeforeFrameSent(RST_STREAM, 3, 4, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 3, 4, 0x0, + static_cast<int>(Http2ErrorCode::REFUSED_STREAM))); + EXPECT_CALL(visitor, OnCloseStream(3, Http2ErrorCode::REFUSED_STREAM)); + + int send_result = adapter->Send(); + // Some bytes should have been serialized. + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::RST_STREAM, + SpdyFrameType::RST_STREAM})); } TEST(NgHttp2AdapterTest, ServerConnectionErrorWhileHandlingHeaders) { @@ -2219,8 +3310,8 @@ TEST(NgHttp2AdapterTest, ServerConnectionErrorWhileHandlingHeaders) { // Some bytes should have been serialized. EXPECT_EQ(0, send_result); // SETTINGS ack and RST_STREAM - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::RST_STREAM})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM})); } TEST(NgHttp2AdapterTest, ServerErrorAfterHandlingHeaders) { @@ -2268,7 +3359,7 @@ TEST(NgHttp2AdapterTest, ServerErrorAfterHandlingHeaders) { // Some bytes should have been serialized. EXPECT_EQ(0, send_result); // SETTINGS ack - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); } // Exercises the case when a visitor chooses to reject a frame based solely on @@ -2313,7 +3404,7 @@ TEST(NgHttp2AdapterTest, ServerRejectsFrameHeader) { // Some bytes should have been serialized. EXPECT_EQ(0, send_result); // SETTINGS ack - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); } TEST(NgHttp2AdapterTest, ServerRejectsBeginningOfData) { @@ -2369,7 +3460,7 @@ TEST(NgHttp2AdapterTest, ServerRejectsBeginningOfData) { // Some bytes should have been serialized. EXPECT_EQ(0, send_result); // SETTINGS ack. - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); } TEST(NgHttp2AdapterTest, ServerRejectsStreamData) { @@ -2425,7 +3516,109 @@ TEST(NgHttp2AdapterTest, ServerRejectsStreamData) { // Some bytes should have been serialized. EXPECT_EQ(0, send_result); // SETTINGS ack. - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); +} + +TEST(NgHttp2AdapterTest, ServerReceivesTooLargeHeader) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + + // nghttp2 will accept a maximum of 64kB of huffman encoded data per header + // field. + const std::string too_large_value = std::string(80 * 1024, 'q'); + const std::string frames = TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":method", "POST"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}, + {"x-toobig", too_large_value}}, + /*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, 0)); + 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")); + // Further header processing is skipped, as the header field is too large. + + const int64_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(frames.size(), result); + + EXPECT_TRUE(adapter->want_write()); + + // Since nghttp2 opted not to process the header, it generates a GOAWAY with + // error code COMPRESSION_ERROR. + EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, 8, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(GOAWAY, 0, 8, 0x0, + static_cast<int>(Http2ErrorCode::COMPRESSION_ERROR))); + + int send_result = adapter->Send(); + // Some bytes should have been serialized. + EXPECT_EQ(0, send_result); + // GOAWAY. + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::GOAWAY})); +} + +TEST(NgHttp2AdapterTest, ServerReceivesInvalidAuthority) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + + const std::string frames = TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":method", "POST"}, + {":scheme", "https"}, + {":authority", "ex|ample.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, + OnErrorDebug("Invalid HTTP header field was received: frame type: 1, " + "stream: 1, name: [:authority], value: [ex|ample.com]")); + EXPECT_CALL( + visitor, + OnInvalidFrame(1, Http2VisitorInterface::InvalidFrameError::kHttpHeader)); + + const int64_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(frames.size(), result); + + EXPECT_TRUE(adapter->want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0x0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, 4, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 1, 4, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR)); + + int send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM})); } TEST(NgHttp2AdapterTest, ServerSubmitResponse) { @@ -2477,7 +3670,7 @@ TEST(NgHttp2AdapterTest, ServerSubmitResponse) { int send_result = adapter->Send(); EXPECT_EQ(0, send_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); visitor.Clear(); EXPECT_EQ(0, adapter->GetHpackEncoderDynamicTableSize()); @@ -2508,8 +3701,8 @@ TEST(NgHttp2AdapterTest, ServerSubmitResponse) { send_result = adapter->Send(); EXPECT_EQ(0, send_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS, - spdy::SpdyFrameType::DATA})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::HEADERS, SpdyFrameType::DATA})); EXPECT_THAT(visitor.data(), testing::HasSubstr(kBody)); EXPECT_FALSE(adapter->want_write()); @@ -2567,8 +3760,8 @@ TEST(NgHttp2AdapterTest, ServerSendsShutdown) { int send_result = adapter->Send(); EXPECT_EQ(0, send_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::GOAWAY})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::GOAWAY})); } TEST(NgHttp2AdapterTest, ServerSendsTrailers) { @@ -2612,7 +3805,7 @@ TEST(NgHttp2AdapterTest, ServerSendsTrailers) { int send_result = adapter->Send(); EXPECT_EQ(0, send_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); visitor.Clear(); EXPECT_FALSE(adapter->want_write()); @@ -2635,8 +3828,8 @@ TEST(NgHttp2AdapterTest, ServerSendsTrailers) { send_result = adapter->Send(); EXPECT_EQ(0, send_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS, - spdy::SpdyFrameType::DATA})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::HEADERS, SpdyFrameType::DATA})); EXPECT_THAT(visitor.data(), testing::HasSubstr(kBody)); visitor.Clear(); EXPECT_FALSE(adapter->want_write()); @@ -2654,7 +3847,7 @@ TEST(NgHttp2AdapterTest, ServerSendsTrailers) { send_result = adapter->Send(); EXPECT_EQ(0, send_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::HEADERS})); } TEST(NgHttp2AdapterTest, ClientSendsContinuation) { @@ -2748,14 +3941,154 @@ TEST(NgHttp2AdapterTest, ClientSendsMetadataWithContinuation) { const int64_t result = adapter->ProcessBytes(frames); EXPECT_EQ(frames.size(), result); - EXPECT_EQ(TestFrameSequence::MetadataBlockForPayload( - "Example connection metadata in multiple frames"), + EXPECT_EQ("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"), + EXPECT_EQ("Some stream metadata that's also sent in multiple frames", absl::StrJoin(visitor.GetMetadata(1), "")); } +TEST(NgHttp2AdapterTest, RepeatedHeaderNames) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + EXPECT_FALSE(adapter->want_write()); + + const std::string frames = TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}, + {"accept", "text/plain"}, + {"accept", "text/html"}}, + /*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, OnHeaderForStream(1, "accept", "text/plain")); + EXPECT_CALL(visitor, OnHeaderForStream(1, "accept", "text/html")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnEndStream(1)); + + int64_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(frames.size(), static_cast<size_t>(result)); + + const std::vector<Header> headers1 = ToHeaders( + {{":status", "200"}, {"content-length", "10"}, {"content-length", "10"}}); + auto body1 = absl::make_unique<TestDataFrameSource>(visitor, true); + body1->AppendPayload("perfection"); + body1->EndData(); + + int submit_result = adapter->SubmitResponse(1, headers1, std::move(body1)); + ASSERT_EQ(0, submit_result); + + 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, 10, 0x1, 0)); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR)); + + int send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::HEADERS, + SpdyFrameType::DATA})); +} + +TEST(NgHttp2AdapterTest, ServerRespondsToRequestWithTrailers) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + EXPECT_FALSE(adapter->want_write()); + + const std::string frames = + TestFrameSequence() + .ClientPreface() + .Headers(1, {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}) + .Data(1, "Example data, woohoo.") + .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, 4)); + 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, OnFrameHeader(1, _, DATA, 0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, _)); + EXPECT_CALL(visitor, OnDataForStream(1, _)); + + int64_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(frames.size(), static_cast<size_t>(result)); + + const std::vector<Header> headers1 = ToHeaders({{":status", "200"}}); + auto body1 = absl::make_unique<TestDataFrameSource>(visitor, true); + TestDataFrameSource* body1_ptr = body1.get(); + + int submit_result = adapter->SubmitResponse(1, headers1, std::move(body1)); + ASSERT_EQ(0, submit_result); + + 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)); + + int send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::HEADERS})); + visitor.Clear(); + + const std::string more_frames = + TestFrameSequence() + .Headers(1, {{"extra-info", "Trailers are weird but good?"}}, + /*fin=*/true) + .Serialize(); + + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, "extra-info", + "Trailers are weird but good?")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnEndStream(1)); + + result = adapter->ProcessBytes(more_frames); + EXPECT_EQ(more_frames.size(), static_cast<size_t>(result)); + + body1_ptr->EndData(); + EXPECT_EQ(true, adapter->ResumeStream(1)); + + EXPECT_CALL(visitor, OnFrameSent(DATA, 1, 0, 0x1, 0)); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR)); + + send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::DATA})); +} + TEST(NgHttp2AdapterTest, ServerSubmitsResponseWithDataSourceError) { DataSavingVisitor visitor; auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); @@ -2807,9 +4140,9 @@ TEST(NgHttp2AdapterTest, ServerSubmitsResponseWithDataSourceError) { int send_result = adapter->Send(); EXPECT_EQ(0, send_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::HEADERS, - spdy::SpdyFrameType::RST_STREAM})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::HEADERS, + SpdyFrameType::RST_STREAM})); visitor.Clear(); EXPECT_FALSE(adapter->want_write()); @@ -2868,8 +4201,8 @@ TEST(NgHttp2AdapterTest, CompleteRequestWithServerResponse) { int send_result = adapter->Send(); EXPECT_EQ(0, send_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::HEADERS})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::HEADERS})); EXPECT_FALSE(adapter->want_write()); } @@ -2915,8 +4248,8 @@ TEST(NgHttp2AdapterTest, IncompleteRequestWithServerResponse) { int send_result = adapter->Send(); EXPECT_EQ(0, send_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::HEADERS})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::HEADERS})); EXPECT_FALSE(adapter->want_write()); } @@ -2974,9 +4307,9 @@ TEST(NgHttp2AdapterTest, ServerSendsInvalidTrailers) { 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(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::HEADERS, + SpdyFrameType::DATA})); EXPECT_THAT(visitor.data(), testing::HasSubstr(kBody)); visitor.Clear(); EXPECT_FALSE(adapter->want_write()); @@ -2993,7 +4326,7 @@ TEST(NgHttp2AdapterTest, ServerSendsInvalidTrailers) { send_result = adapter->Send(); EXPECT_EQ(0, send_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::HEADERS})); } TEST(NgHttp2AdapterTest, ServerDropsNewStreamBelowWatermark) { @@ -3056,7 +4389,61 @@ TEST(NgHttp2AdapterTest, ServerDropsNewStreamBelowWatermark) { // Some bytes should have been serialized. EXPECT_EQ(0, send_result); // SETTINGS ack - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); +} + +TEST(NgHttp2AdapterInteractionTest, + ClientServerInteractionRepeatedHeaderNames) { + DataSavingVisitor client_visitor; + auto client_adapter = NgHttp2Adapter::CreateClientAdapter(client_visitor); + + client_adapter->SubmitSettings({}); + + const std::vector<Header> headers1 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}, + {"accept", "text/plain"}, + {"accept", "text/html"}}); + + const int32_t stream_id1 = + client_adapter->SubmitRequest(headers1, nullptr, nullptr); + ASSERT_GT(stream_id1, 0); + + EXPECT_CALL(client_visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(client_visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(client_visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); + EXPECT_CALL(client_visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); + int send_result = client_adapter->Send(); + EXPECT_EQ(0, send_result); + + DataSavingVisitor server_visitor; + auto server_adapter = NgHttp2Adapter::CreateServerAdapter(server_visitor); + + testing::InSequence s; + + // Client preface (empty SETTINGS) + EXPECT_CALL(server_visitor, OnFrameHeader(0, _, SETTINGS, 0)); + EXPECT_CALL(server_visitor, OnSettingsStart()); + EXPECT_CALL(server_visitor, OnSetting(_)).Times(testing::AnyNumber()); + EXPECT_CALL(server_visitor, OnSettingsEnd()); + // Stream 1 + EXPECT_CALL(server_visitor, OnFrameHeader(1, _, HEADERS, 5)); + EXPECT_CALL(server_visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(server_visitor, OnHeaderForStream(1, ":method", "GET")); + EXPECT_CALL(server_visitor, OnHeaderForStream(1, ":scheme", "http")); + EXPECT_CALL(server_visitor, + OnHeaderForStream(1, ":authority", "example.com")); + EXPECT_CALL(server_visitor, + OnHeaderForStream(1, ":path", "/this/is/request/one")); + EXPECT_CALL(server_visitor, OnHeaderForStream(1, "accept", "text/plain")); + EXPECT_CALL(server_visitor, OnHeaderForStream(1, "accept", "text/html")); + EXPECT_CALL(server_visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(server_visitor, OnEndStream(1)); + + int64_t result = server_adapter->ProcessBytes(client_visitor.data()); + EXPECT_EQ(client_visitor.data().size(), static_cast<size_t>(result)); } TEST(NgHttp2AdapterTest, ServerForbidsWindowUpdateOnIdleStream) { @@ -3094,7 +4481,7 @@ TEST(NgHttp2AdapterTest, ServerForbidsWindowUpdateOnIdleStream) { // Some bytes should have been serialized. EXPECT_EQ(0, send_result); // The GOAWAY apparently causes the SETTINGS ack to be dropped. - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::GOAWAY})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::GOAWAY})); } TEST(NgHttp2AdapterTest, ServerForbidsDataOnIdleStream) { @@ -3133,7 +4520,7 @@ TEST(NgHttp2AdapterTest, ServerForbidsDataOnIdleStream) { // Some bytes should have been serialized. EXPECT_EQ(0, send_result); // The GOAWAY apparently causes the SETTINGS ack to be dropped. - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::GOAWAY})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::GOAWAY})); } TEST(NgHttp2AdapterTest, ServerForbidsRstStreamOnIdleStream) { @@ -3174,7 +4561,7 @@ TEST(NgHttp2AdapterTest, ServerForbidsRstStreamOnIdleStream) { // Some bytes should have been serialized. EXPECT_EQ(0, send_result); // The GOAWAY apparently causes the SETTINGS ack to be dropped. - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::GOAWAY})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::GOAWAY})); } TEST(NgHttp2AdapterTest, ServerForbidsNewStreamAboveStreamLimit) { @@ -3205,8 +4592,8 @@ TEST(NgHttp2AdapterTest, ServerForbidsNewStreamAboveStreamLimit) { int send_result = adapter->Send(); EXPECT_EQ(0, send_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS})); visitor.Clear(); // Let the client send a SETTINGS ack and then attempt to open more than the @@ -3253,7 +4640,7 @@ TEST(NgHttp2AdapterTest, ServerForbidsNewStreamAboveStreamLimit) { send_result = adapter->Send(); EXPECT_EQ(0, send_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::GOAWAY})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::GOAWAY})); } TEST(NgHttp2AdapterTest, ServerRstStreamsNewStreamAboveStreamLimitBeforeAck) { @@ -3284,8 +4671,8 @@ TEST(NgHttp2AdapterTest, ServerRstStreamsNewStreamAboveStreamLimitBeforeAck) { int send_result = adapter->Send(); EXPECT_EQ(0, send_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS})); visitor.Clear(); // Let the client avoid sending a SETTINGS ack and attempt to open more than @@ -3329,7 +4716,7 @@ TEST(NgHttp2AdapterTest, ServerRstStreamsNewStreamAboveStreamLimitBeforeAck) { send_result = adapter->Send(); EXPECT_EQ(0, send_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::RST_STREAM})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::RST_STREAM})); } TEST(NgHttp2AdapterTest, AutomaticSettingsAndPingAcks) { @@ -3362,8 +4749,8 @@ TEST(NgHttp2AdapterTest, AutomaticSettingsAndPingAcks) { int send_result = adapter->Send(); EXPECT_EQ(0, send_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::PING})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::PING})); } TEST(NgHttp2AdapterTest, AutomaticPingAcksDisabled) { @@ -3399,7 +4786,773 @@ TEST(NgHttp2AdapterTest, AutomaticPingAcksDisabled) { int send_result = adapter->Send(); EXPECT_EQ(0, send_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); +} + +TEST(NgHttp2AdapterTest, ServerForbidsProtocolPseudoheaderBeforeAck) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + + const std::string initial_frames = + TestFrameSequence().ClientPreface().Serialize(); + + testing::InSequence s; + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + const int64_t initial_result = adapter->ProcessBytes(initial_frames); + EXPECT_EQ(static_cast<size_t>(initial_result), initial_frames.size()); + + // The client attempts to send a CONNECT request with the `:protocol` + // pseudoheader before receiving the server's SETTINGS frame. + const std::string stream1_frames = + TestFrameSequence() + .Headers(1, + {{":method", "CONNECT"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}, + {":protocol", "websocket"}}, + /*fin=*/true) + .Serialize(); + + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 0x5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4); + EXPECT_CALL( + visitor, + OnErrorDebug("Invalid HTTP header field was received: frame type: 1, " + "stream: 1, name: [:protocol], value: [websocket]")); + EXPECT_CALL( + visitor, + OnInvalidFrame(1, Http2VisitorInterface::InvalidFrameError::kHttpHeader)); + + int64_t stream_result = adapter->ProcessBytes(stream1_frames); + EXPECT_EQ(static_cast<size_t>(stream_result), stream1_frames.size()); + + // Server sends a SETTINGS ack and initial SETTINGS (with + // ENABLE_CONNECT_PROTOCOL). + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 6, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 6, 0x0, 0)); + + // The server sends a RST_STREAM for the offending stream. + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 1, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR)); + + adapter->SubmitSettings({{ENABLE_CONNECT_PROTOCOL, 1}}); + int send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM})); + visitor.Clear(); + + // The client attempts to send a CONNECT request with the `:protocol` + // pseudoheader before acking the server's SETTINGS frame. + const std::string stream3_frames = + TestFrameSequence() + .Headers(3, + {{":method", "CONNECT"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/two"}, + {":protocol", "websocket"}}, + /*fin=*/true) + .Serialize(); + + // Surprisingly, nghttp2 is okay with this. + EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 0x5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(3)); + EXPECT_CALL(visitor, OnHeaderForStream(3, _, _)).Times(5); + EXPECT_CALL(visitor, OnEndHeadersForStream(3)); + EXPECT_CALL(visitor, OnEndStream(3)); + + stream_result = adapter->ProcessBytes(stream3_frames); + EXPECT_EQ(static_cast<size_t>(stream_result), stream3_frames.size()); + + EXPECT_FALSE(adapter->want_write()); +} + +TEST(NgHttp2AdapterTest, ServerAllowsProtocolPseudoheaderAfterAck) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + adapter->SubmitSettings({{ENABLE_CONNECT_PROTOCOL, 1}}); + + const std::string initial_frames = + TestFrameSequence().ClientPreface().Serialize(); + + testing::InSequence s; + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + const int64_t initial_result = adapter->ProcessBytes(initial_frames); + EXPECT_EQ(static_cast<size_t>(initial_result), initial_frames.size()); + + // Server initial SETTINGS (with ENABLE_CONNECT_PROTOCOL) and SETTINGS ack. + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 6, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 6, 0x0, 0)); + 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); + visitor.Clear(); + + // The client attempts to send a CONNECT request with the `:protocol` + // pseudoheader after acking the server's SETTINGS frame. + const std::string stream_frames = + TestFrameSequence() + .SettingsAck() + .Headers(1, + {{":method", "CONNECT"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}, + {":protocol", "websocket"}}, + /*fin=*/true) + .Serialize(); + + EXPECT_CALL(visitor, OnFrameHeader(0, _, SETTINGS, 0x1)); + EXPECT_CALL(visitor, OnSettingsAck()); + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 0x5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(5); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnEndStream(1)); + + const int64_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(static_cast<size_t>(stream_result), stream_frames.size()); + + EXPECT_FALSE(adapter->want_write()); +} + +TEST(NgHttp2AdapterTest, SkipsSendingFramesForRejectedStream) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + + const std::string initial_frames = + TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":method", "GET"}, + {":scheme", "http"}, + {":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()); + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 0x5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnEndStream(1)); + + const int64_t initial_result = adapter->ProcessBytes(initial_frames); + EXPECT_EQ(static_cast<size_t>(initial_result), initial_frames.size()); + + auto body = absl::make_unique<TestDataFrameSource>(visitor, true); + body->AppendPayload("Here is some data, which will be completely ignored!"); + + int submit_result = adapter->SubmitResponse( + 1, ToHeaders({{":status", "200"}}), std::move(body)); + ASSERT_EQ(0, submit_result); + + auto source = absl::make_unique<TestMetadataSource>(ToHeaderBlock(ToHeaders( + {{"query-cost", "is too darn high"}, {"secret-sauce", "hollandaise"}}))); + adapter->SubmitMetadata(1, 16384u, std::move(source)); + + adapter->SubmitWindowUpdate(1, 1024); + adapter->SubmitRst(1, Http2ErrorCode::INTERNAL_ERROR); + + // Server initial SETTINGS and SETTINGS ack. + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + + // nghttp2 apparently allows extension frames to be sent on reset streams. + EXPECT_CALL(visitor, OnBeforeFrameSent(kMetadataFrameType, 1, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(kMetadataFrameType, 1, _, 0x4, 0)); + + // The server sends a RST_STREAM for the offending stream. + // The response HEADERS, DATA and WINDOW_UPDATE are all ignored. + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 1, _, 0x0, + static_cast<int>(Http2ErrorCode::INTERNAL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::INTERNAL_ERROR)); + + int send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, + static_cast<SpdyFrameType>(kMetadataFrameType), + SpdyFrameType::RST_STREAM})); +} + +TEST(NgHttp2AdapterTest, ServerStartsShutdown) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + + EXPECT_FALSE(adapter->want_write()); + + adapter->SubmitShutdownNotice(); + EXPECT_TRUE(adapter->want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(GOAWAY, 0, _, 0x0, 0)); + + int result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::GOAWAY})); +} + +TEST(NgHttp2AdapterTest, ServerStartsShutdownAfterGoaway) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + + EXPECT_FALSE(adapter->want_write()); + + adapter->SubmitGoAway(1, Http2ErrorCode::HTTP2_NO_ERROR, + "and don't come back!"); + EXPECT_TRUE(adapter->want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(GOAWAY, 0, _, 0x0, 0)); + + int result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::GOAWAY})); + + // No-op, since a GOAWAY has previously been enqueued. + adapter->SubmitShutdownNotice(); + EXPECT_FALSE(adapter->want_write()); +} + +// Verifies that a connection-level processing error results in repeatedly +// returning a positive value for ProcessBytes() to mark all data as consumed. +TEST(NgHttp2AdapterTest, ConnectionErrorWithBlackholeSinkingData) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + + const std::string frames = + TestFrameSequence().ClientPreface().WindowUpdate(1, 42).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, 4, WINDOW_UPDATE, 0)); + EXPECT_CALL(visitor, OnInvalidFrame(1, _)); + + const int64_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(static_cast<size_t>(result), frames.size()); + + // Ask the connection to process more bytes. Because the option is enabled, + // the data should be marked as consumed. + const std::string next_frame = TestFrameSequence().Ping(42).Serialize(); + const int64_t next_result = adapter->ProcessBytes(next_frame); + EXPECT_EQ(static_cast<size_t>(next_result), next_frame.size()); +} + +TEST(NgHttp2AdapterTest, ServerDoesNotSendFramesAfterImmediateGoAway) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + + // Submit a custom initial SETTINGS frame with one setting. + adapter->SubmitSettings({{HEADER_TABLE_SIZE, 100u}}); + + 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, 0x5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnEndStream(1)); + + const int64_t read_result = adapter->ProcessBytes(frames); + EXPECT_EQ(static_cast<size_t>(read_result), frames.size()); + + // Submit a response for the stream. + auto body = absl::make_unique<TestDataFrameSource>(visitor, true); + body->AppendPayload("This data is doomed to never be written."); + int submit_result = adapter->SubmitResponse( + 1, ToHeaders({{":status", "200"}}), std::move(body)); + ASSERT_EQ(0, submit_result); + + // Submit a WINDOW_UPDATE frame. + adapter->SubmitWindowUpdate(kConnectionStreamId, 42); + + // Submit another SETTINGS frame. + adapter->SubmitSettings({}); + + // Submit some metadata. + auto source = absl::make_unique<TestMetadataSource>(ToHeaderBlock(ToHeaders( + {{"query-cost", "is too darn high"}, {"secret-sauce", "hollandaise"}}))); + adapter->SubmitMetadata(1, 16384u, std::move(source)); + + EXPECT_TRUE(adapter->want_write()); + + // Trigger a connection error. Only the response headers will be written. + const std::string connection_error_frames = + TestFrameSequence().WindowUpdate(3, 42).Serialize(); + + EXPECT_CALL(visitor, OnFrameHeader(3, 4, WINDOW_UPDATE, 0)); + EXPECT_CALL(visitor, OnInvalidFrame(3, _)); + + const int64_t result = adapter->ProcessBytes(connection_error_frames); + EXPECT_EQ(static_cast<size_t>(result), connection_error_frames.size()); + + EXPECT_TRUE(adapter->want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 6, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 6, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(GOAWAY, 0, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + + int send_result = adapter->Send(); + // Some bytes should have been serialized. + EXPECT_EQ(0, send_result); + // The GOAWAY apparently causes the other frames to be dropped except for the + // non-ack SETTINGS frames; nghttp2 sends non-ack SETTINGS frames because they + // could be the initial SETTINGS frame. However, nghttp2 still allows sending + // multiple non-ack SETTINGS, which feels non-ideal. + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS, + SpdyFrameType::GOAWAY})); + visitor.Clear(); + + // Try to submit more frames for writing. They should not be written. + adapter->SubmitPing(42); + EXPECT_FALSE(adapter->want_write()); + send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), testing::IsEmpty()); +} + +TEST(NgHttp2AdapterTest, ServerHandlesContentLength) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + + testing::InSequence s; + + const std::string stream_frames = + TestFrameSequence() + .ClientPreface() + .Headers(1, {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}, + {"content-length", "2"}}) + .Data(1, "hi", /*fin=*/true) + .Headers(3, + {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/two"}, + {"content-length", "nan"}}, + /*fin=*/true) + .Serialize(); + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + // Stream 1: content-length is correct + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(5); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnFrameHeader(1, _, DATA, 1)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 2)); + EXPECT_CALL(visitor, OnDataForStream(1, "hi")); + EXPECT_CALL(visitor, OnEndStream(1)); + + // Stream 3: content-length is not a number + EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(3)); + EXPECT_CALL(visitor, OnHeaderForStream(3, _, _)).Times(4); + EXPECT_CALL( + visitor, + OnErrorDebug("Invalid HTTP header field was received: frame type: 1, " + "stream: 3, name: [content-length], value: [nan]")); + EXPECT_CALL( + visitor, + OnInvalidFrame(3, Http2VisitorInterface::InvalidFrameError::kHttpHeader)); + + const int64_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 3, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 3, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(3, Http2ErrorCode::PROTOCOL_ERROR)); + + EXPECT_TRUE(adapter->want_write()); + int result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM})); +} + +TEST(NgHttp2AdapterTest, ServerHandlesContentLengthMismatch) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + + testing::InSequence s; + + const std::string stream_frames = + TestFrameSequence() + .ClientPreface() + .Headers(1, {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/two"}, + {"content-length", "2"}}) + .Data(1, "h", /*fin=*/true) + .Headers(3, {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/three"}, + {"content-length", "2"}}) + .Data(3, "howdy", /*fin=*/true) + .Headers(5, + {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/four"}, + {"content-length", "2"}}, + /*fin=*/true) + .Headers(7, + {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/four"}, + {"content-length", "2"}}, + /*fin=*/false) + .Data(7, "h", /*fin=*/false) + .Headers(7, {{"extra-info", "Trailers with content-length mismatch"}}, + /*fin=*/true) + .Serialize(); + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + // Stream 1: content-length is larger than actual data + // All data is delivered to the visitor, but OnInvalidFrame() is not. + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(5); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnFrameHeader(1, _, DATA, 1)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 1)); + EXPECT_CALL(visitor, OnDataForStream(1, "h")); + + // Stream 3: content-length is smaller than actual data + // The beginning of data is delivered to the visitor, but not the actual data, + // and neither is OnInvalidFrame(). + EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(3)); + EXPECT_CALL(visitor, OnHeaderForStream(3, _, _)).Times(5); + EXPECT_CALL(visitor, OnEndHeadersForStream(3)); + EXPECT_CALL(visitor, OnFrameHeader(3, _, DATA, 1)); + EXPECT_CALL(visitor, OnBeginDataForStream(3, 5)); + + // Stream 5: content-length is invalid and HEADERS ends the stream + // When the stream ends with HEADERS, nghttp2 invokes OnInvalidFrame(). + EXPECT_CALL(visitor, OnFrameHeader(5, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(5)); + EXPECT_CALL(visitor, OnHeaderForStream(5, _, _)).Times(5); + EXPECT_CALL(visitor, + OnInvalidFrame( + 5, Http2VisitorInterface::InvalidFrameError::kHttpMessaging)); + + // Stream 7: content-length is invalid and trailers end the stream + // When the stream ends with trailers, nghttp2 invokes OnInvalidFrame(). + EXPECT_CALL(visitor, OnFrameHeader(7, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(7)); + EXPECT_CALL(visitor, OnHeaderForStream(7, _, _)).Times(5); + EXPECT_CALL(visitor, OnEndHeadersForStream(7)); + EXPECT_CALL(visitor, OnFrameHeader(7, _, DATA, 0)); + EXPECT_CALL(visitor, OnBeginDataForStream(7, 1)); + EXPECT_CALL(visitor, OnDataForStream(7, "h")); + EXPECT_CALL(visitor, OnFrameHeader(7, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(7)); + EXPECT_CALL(visitor, OnHeaderForStream(7, _, _)); + EXPECT_CALL(visitor, + OnInvalidFrame( + 7, Http2VisitorInterface::InvalidFrameError::kHttpMessaging)); + + const int64_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 1, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 3, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 3, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(3, Http2ErrorCode::PROTOCOL_ERROR)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 5, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 5, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(5, Http2ErrorCode::PROTOCOL_ERROR)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 7, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 7, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(7, Http2ErrorCode::PROTOCOL_ERROR)); + + EXPECT_TRUE(adapter->want_write()); + int result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT( + visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::RST_STREAM, + SpdyFrameType::RST_STREAM, SpdyFrameType::RST_STREAM, + SpdyFrameType::RST_STREAM})); +} + +TEST(NgHttp2AdapterTest, ServerHandlesAsteriskPathForOptions) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + + testing::InSequence s; + + const std::string stream_frames = TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":scheme", "https"}, + {":authority", "example.com"}, + {":path", "*"}, + {":method", "OPTIONS"}}, + /*fin=*/true) + .Serialize(); + + // 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, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnEndStream(1)); + + const int64_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + + EXPECT_TRUE(adapter->want_write()); + int result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); +} + +TEST(NgHttp2AdapterTest, ServerHandlesInvalidPath) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + + testing::InSequence s; + + const std::string stream_frames = + TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":scheme", "https"}, + {":authority", "example.com"}, + {":path", "*"}, + {":method", "GET"}}, + /*fin=*/true) + .Headers(3, + {{":scheme", "https"}, + {":authority", "example.com"}, + {":path", "other/non/slash/starter"}, + {":method", "GET"}}, + /*fin=*/true) + .Headers(5, + {{":scheme", "https"}, + {":authority", "example.com"}, + {":path", ""}, + {":method", "GET"}}, + /*fin=*/true) + .Serialize(); + + // 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, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4); + EXPECT_CALL(visitor, + OnInvalidFrame( + 1, Http2VisitorInterface::InvalidFrameError::kHttpMessaging)); + + EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(3)); + EXPECT_CALL(visitor, OnHeaderForStream(3, _, _)).Times(4); + EXPECT_CALL(visitor, + OnInvalidFrame( + 3, Http2VisitorInterface::InvalidFrameError::kHttpMessaging)); + + EXPECT_CALL(visitor, OnFrameHeader(5, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(5)); + EXPECT_CALL(visitor, OnHeaderForStream(5, _, _)).Times(2); + EXPECT_CALL( + visitor, + OnErrorDebug("Invalid HTTP header field was received: frame type: 1, " + "stream: 5, name: [:path], value: []")); + EXPECT_CALL( + visitor, + OnInvalidFrame(5, Http2VisitorInterface::InvalidFrameError::kHttpHeader)); + + const int64_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 1, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 3, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 3, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(3, Http2ErrorCode::PROTOCOL_ERROR)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 5, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 5, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(5, Http2ErrorCode::PROTOCOL_ERROR)); + + EXPECT_TRUE(adapter->want_write()); + int result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT( + visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::RST_STREAM, + SpdyFrameType::RST_STREAM, SpdyFrameType::RST_STREAM})); +} + +TEST(NgHttp2AdapterTest, ServerHandlesTeHeader) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + + testing::InSequence s; + + const std::string stream_frames = TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/"}, + {":method", "GET"}, + {"te", "trailers"}}, + /*fin=*/true) + .Headers(3, + {{":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/"}, + {":method", "GET"}, + {"te", "trailers, deflate"}}, + /*fin=*/true) + .Serialize(); + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + // Stream 1: TE: trailers should be allowed. + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(5); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnEndStream(1)); + + // Stream 3: TE: <non-trailers> should be rejected. + EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(3)); + EXPECT_CALL(visitor, OnHeaderForStream(3, _, _)).Times(4); + EXPECT_CALL( + visitor, + OnErrorDebug("Invalid HTTP header field was received: frame type: 1, " + "stream: 3, name: [te], value: [trailers, deflate]")); + EXPECT_CALL( + visitor, + OnInvalidFrame(3, Http2VisitorInterface::InvalidFrameError::kHttpHeader)); + + const int64_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 3, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 3, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(3, Http2ErrorCode::PROTOCOL_ERROR)); + + EXPECT_TRUE(adapter->want_write()); + int result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM})); } } // 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 cb15a21b835..fee8ffbd485 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 @@ -63,6 +63,9 @@ int OnFrameReceived(nghttp2_session* /* session */, // callbacks. This callback handles the point at which the entire logical // frame has been received and processed. case NGHTTP2_DATA: + if ((frame->hd.flags & NGHTTP2_FLAG_PADDED) != 0) { + visitor->OnDataPaddingLength(stream_id, frame->data.padlen); + } if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { visitor->OnEndStream(stream_id); } @@ -174,8 +177,10 @@ int OnHeader(nghttp2_session* /* session */, const nghttp2_frame* frame, case Http2VisitorInterface::HEADER_OK: return 0; case Http2VisitorInterface::HEADER_CONNECTION_ERROR: + case Http2VisitorInterface::HEADER_COMPRESSION_ERROR: return NGHTTP2_ERR_CALLBACK_FAILURE; case Http2VisitorInterface::HEADER_RST_STREAM: + case Http2VisitorInterface::HEADER_FIELD_INVALID: case Http2VisitorInterface::HEADER_HTTP_MESSAGING: return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; } @@ -206,6 +211,21 @@ int OnFrameSent(nghttp2_session* /* session */, const nghttp2_frame* frame, frame->hd.length, frame->hd.flags, error_code); } +int OnFrameNotSent(nghttp2_session* /* session */, const nghttp2_frame* frame, + int /* lib_error_code */, void* /* user_data */) { + if (frame->hd.type == kMetadataFrameType) { + auto* source = static_cast<MetadataSource*>(frame->ext.payload); + if (source == nullptr) { + QUICHE_BUG(not_sent_payload_is_nullptr) + << "Extension frame payload for stream " << frame->hd.stream_id + << " is null!"; + } else { + source->OnFailure(); + } + } + return 0; +} + int OnInvalidFrameReceived(nghttp2_session* /* session */, const nghttp2_frame* frame, int lib_error_code, void* user_data) { @@ -312,10 +332,11 @@ nghttp2_session_callbacks_unique_ptr Create() { 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_frame_not_send_callback(callbacks, + &OnFrameNotSent); 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( 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 293c6d6e2e8..82b7cf1a806 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 @@ -48,6 +48,10 @@ int OnBeforeFrameSent(nghttp2_session* session, const nghttp2_frame* frame, int OnFrameSent(nghttp2_session* session, const nghttp2_frame* frame, void* user_data); +// Invoked when a non-DATA frame is not sent because of an error. +int OnFrameNotSent(nghttp2_session* session, const nghttp2_frame* frame, + int lib_error_code, 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); 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 13867bd978d..6db6e4ad663 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 @@ -25,9 +25,10 @@ NgHttp2Session::~NgHttp2Session() { // Can't invoke want_read() or want_write(), as they are virtual methods. const bool pending_reads = nghttp2_session_want_read(session_.get()) != 0; const bool pending_writes = nghttp2_session_want_write(session_.get()) != 0; - QUICHE_LOG_IF(WARNING, pending_reads || pending_writes) - << "Shutting down connection with pending reads: " << pending_reads - << " or pending writes: " << pending_writes; + if (pending_reads || pending_writes) { + QUICHE_VLOG(1) << "Shutting down connection with pending reads: " + << pending_reads << " or pending writes: " << pending_writes; + } } int64_t NgHttp2Session::ProcessBytes(absl::string_view bytes) { 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 5f5ee0f0041..036c1f2d2e7 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 @@ -99,21 +99,21 @@ TEST_F(NgHttp2SessionTest, ClientHandlesFrames) { spdy::SpdyFrameType::PING})); visitor_.Clear(); - const std::vector<const Header> headers1 = + const std::vector<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 = + const std::vector<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 = + const std::vector<Header> headers3 = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, 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 index d9a04f27772..6e4b5b27eeb 100644 --- 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 @@ -101,8 +101,12 @@ 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) {} + const testing::Matcher<int> flags, + const testing::Matcher<size_t> padding) + : stream_id_(stream_id), + length_(length), + flags_(flags), + padding_(padding) {} bool MatchAndExplain(const nghttp2_frame* frame, testing::MatchResultListener* listener) const override { @@ -121,6 +125,9 @@ class DataMatcher : public testing::MatcherInterface<const nghttp2_frame*> { if (!flags_.MatchAndExplain(frame->hd.flags, listener)) { matched = false; } + if (!padding_.MatchAndExplain(frame->data.padlen, listener)) { + matched = false; + } return matched; } @@ -142,6 +149,7 @@ class DataMatcher : public testing::MatcherInterface<const nghttp2_frame*> { const testing::Matcher<uint32_t> stream_id_; const testing::Matcher<size_t> length_; const testing::Matcher<int> flags_; + const testing::Matcher<size_t> padding_; }; class HeadersMatcher : public testing::MatcherInterface<const nghttp2_frame*> { @@ -405,8 +413,9 @@ testing::Matcher<const nghttp2_frame_hd&> HasFrameHeaderRef( 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)); + const testing::Matcher<size_t> length, const testing::Matcher<int> flags, + const testing::Matcher<size_t> padding) { + return MakeMatcher(new DataMatcher(stream_id, length, flags, padding)); } testing::Matcher<const nghttp2_frame*> IsHeaders( 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 index 92632dc7afb..24493d7e164 100644 --- 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 @@ -66,7 +66,8 @@ testing::Matcher<const nghttp2_frame_hd&> HasFrameHeaderRef( testing::Matcher<const nghttp2_frame*> IsData( const testing::Matcher<uint32_t> stream_id, - const testing::Matcher<size_t> length, const testing::Matcher<int> flags); + const testing::Matcher<size_t> length, const testing::Matcher<int> flags, + const testing::Matcher<size_t> padding = testing::_); testing::Matcher<const nghttp2_frame*> IsHeaders( const testing::Matcher<uint32_t> stream_id, 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 ea6eaf3556f..2e3788093b5 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 @@ -19,6 +19,7 @@ namespace { using ConnectionError = Http2VisitorInterface::ConnectionError; +using spdy::SpdyFrameType; using testing::_; enum FrameType { @@ -34,55 +35,59 @@ enum FrameType { CONTINUATION, }; -using spdy::SpdyFrameType; - -class OgHttp2AdapterTest : public testing::Test { - protected: - void SetUp() override { - OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; - adapter_ = OgHttp2Adapter::Create(http2_visitor_, options); - } - - DataSavingVisitor http2_visitor_; - std::unique_ptr<OgHttp2Adapter> adapter_; +// TODO(birenroy): replace numeric flag values with named constants +enum FrameFlag { + END_STREAM = 0x01, + ACK = END_STREAM, + END_HEADERS = 0x04, }; -TEST_F(OgHttp2AdapterTest, IsServerSession) { - EXPECT_TRUE(adapter_->IsServerSession()); +TEST(OgHttp2AdapterTest, IsServerSession) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + EXPECT_TRUE(adapter->IsServerSession()); } -TEST_F(OgHttp2AdapterTest, ProcessBytes) { +TEST(OgHttp2AdapterTest, ProcessBytes) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + testing::InSequence seq; - EXPECT_CALL(http2_visitor_, OnFrameHeader(0, 0, 4, 0)); - EXPECT_CALL(http2_visitor_, OnSettingsStart()); - EXPECT_CALL(http2_visitor_, OnSettingsEnd()); - EXPECT_CALL(http2_visitor_, OnFrameHeader(0, 8, 6, 0)); - EXPECT_CALL(http2_visitor_, OnPing(17, false)); - adapter_->ProcessBytes( + EXPECT_CALL(visitor, OnFrameHeader(0, 0, 4, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + EXPECT_CALL(visitor, OnFrameHeader(0, 8, 6, 0)); + EXPECT_CALL(visitor, OnPing(17, false)); + adapter->ProcessBytes( TestFrameSequence().ClientPreface().Ping(17).Serialize()); } -TEST_F(OgHttp2AdapterTest, InitialSettings) { +TEST(OgHttp2AdapterTest, InitialSettingsNoExtendedConnect) { DataSavingVisitor client_visitor; - OgHttp2Adapter::Options client_options{.perspective = Perspective::kClient}; + OgHttp2Adapter::Options client_options{.perspective = Perspective::kClient, + .max_header_list_bytes = 42, + .allow_extended_connect = false}; auto client_adapter = OgHttp2Adapter::Create(client_visitor, client_options); DataSavingVisitor server_visitor; - OgHttp2Adapter::Options server_options{.perspective = Perspective::kServer}; + OgHttp2Adapter::Options server_options{.perspective = Perspective::kServer, + .allow_extended_connect = false}; auto server_adapter = OgHttp2Adapter::Create(server_visitor, server_options); testing::InSequence s; // Client sends the connection preface, including the initial SETTINGS. - EXPECT_CALL(client_visitor, OnBeforeFrameSent(SETTINGS, 0, 6, 0x0)); - EXPECT_CALL(client_visitor, OnFrameSent(SETTINGS, 0, 6, 0x0, 0)); + EXPECT_CALL(client_visitor, OnBeforeFrameSent(SETTINGS, 0, 12, 0x0)); + EXPECT_CALL(client_visitor, OnFrameSent(SETTINGS, 0, 12, 0x0, 0)); { int result = client_adapter->Send(); EXPECT_EQ(0, result); absl::string_view data = client_visitor.data(); EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); - EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(data, EqualsFrames({SpdyFrameType::SETTINGS})); } // Server sends the connection preface, including the initial SETTINGS. @@ -92,11 +97,11 @@ TEST_F(OgHttp2AdapterTest, InitialSettings) { int result = server_adapter->Send(); EXPECT_EQ(0, result); absl::string_view data = server_visitor.data(); - EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(data, EqualsFrames({SpdyFrameType::SETTINGS})); } // Client processes the server's initial bytes, including initial SETTINGS. - EXPECT_CALL(client_visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(client_visitor, OnFrameHeader(0, 0, SETTINGS, 0x0)); EXPECT_CALL(client_visitor, OnSettingsStart()); EXPECT_CALL(client_visitor, OnSettingsEnd()); { @@ -105,13 +110,14 @@ TEST_F(OgHttp2AdapterTest, InitialSettings) { } // Server processes the client's initial bytes, including initial SETTINGS. - EXPECT_CALL(server_visitor, OnFrameHeader(0, 6, SETTINGS, 0)); + EXPECT_CALL(server_visitor, OnFrameHeader(0, 12, SETTINGS, 0x0)); EXPECT_CALL(server_visitor, OnSettingsStart()); + EXPECT_CALL(server_visitor, + OnSetting(Http2Setting{Http2KnownSettingsId::ENABLE_PUSH, 0u})) + .Times(2); EXPECT_CALL( server_visitor, - OnSetting(testing::AllOf( - testing::Field(&Http2Setting::id, Http2KnownSettingsId::ENABLE_PUSH), - testing::Field(&Http2Setting::value, 0)))) + OnSetting(Http2Setting{Http2KnownSettingsId::MAX_HEADER_LIST_SIZE, 42u})) .Times(2); EXPECT_CALL(server_visitor, OnSettingsEnd()); { @@ -120,43 +126,112 @@ TEST_F(OgHttp2AdapterTest, InitialSettings) { } } -TEST_F(OgHttp2AdapterTest, AutomaticSettingsAndPingAcks) { +TEST(OgHttp2AdapterTest, InitialSettings) { + DataSavingVisitor client_visitor; + OgHttp2Adapter::Options client_options{.perspective = Perspective::kClient, + .max_header_list_bytes = 42}; + ASSERT_TRUE(client_options.allow_extended_connect); + auto client_adapter = OgHttp2Adapter::Create(client_visitor, client_options); + + DataSavingVisitor server_visitor; + OgHttp2Adapter::Options server_options{.perspective = Perspective::kServer}; + ASSERT_TRUE(server_options.allow_extended_connect); + auto server_adapter = OgHttp2Adapter::Create(server_visitor, server_options); + + testing::InSequence s; + + // Client sends the connection preface, including the initial SETTINGS. + EXPECT_CALL(client_visitor, OnBeforeFrameSent(SETTINGS, 0, 12, 0x0)); + EXPECT_CALL(client_visitor, OnFrameSent(SETTINGS, 0, 12, 0x0, 0)); + { + int result = client_adapter->Send(); + EXPECT_EQ(0, result); + absl::string_view data = client_visitor.data(); + EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); + data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); + EXPECT_THAT(data, EqualsFrames({SpdyFrameType::SETTINGS})); + } + + // Server sends the connection preface, including the initial SETTINGS. + EXPECT_CALL(server_visitor, OnBeforeFrameSent(SETTINGS, 0, 6, 0x0)); + EXPECT_CALL(server_visitor, OnFrameSent(SETTINGS, 0, 6, 0x0, 0)); + { + int result = server_adapter->Send(); + EXPECT_EQ(0, result); + absl::string_view data = server_visitor.data(); + EXPECT_THAT(data, EqualsFrames({SpdyFrameType::SETTINGS})); + } + + // Client processes the server's initial bytes, including initial SETTINGS. + EXPECT_CALL(client_visitor, OnFrameHeader(0, 6, SETTINGS, 0x0)); + EXPECT_CALL(client_visitor, OnSettingsStart()); + EXPECT_CALL(client_visitor, + OnSetting(Http2Setting{ + Http2KnownSettingsId::ENABLE_CONNECT_PROTOCOL, 1u})) + .Times(2); + EXPECT_CALL(client_visitor, OnSettingsEnd()); + { + const int64_t result = client_adapter->ProcessBytes(server_visitor.data()); + EXPECT_EQ(server_visitor.data().size(), static_cast<size_t>(result)); + } + + // Server processes the client's initial bytes, including initial SETTINGS. + EXPECT_CALL(server_visitor, OnFrameHeader(0, 12, SETTINGS, 0x0)); + EXPECT_CALL(server_visitor, OnSettingsStart()); + EXPECT_CALL(server_visitor, + OnSetting(Http2Setting{Http2KnownSettingsId::ENABLE_PUSH, 0u})) + .Times(2); + EXPECT_CALL( + server_visitor, + OnSetting(Http2Setting{Http2KnownSettingsId::MAX_HEADER_LIST_SIZE, 42u})) + .Times(2); + EXPECT_CALL(server_visitor, OnSettingsEnd()); + { + const int64_t result = server_adapter->ProcessBytes(client_visitor.data()); + EXPECT_EQ(client_visitor.data().size(), static_cast<size_t>(result)); + } +} + +TEST(OgHttp2AdapterTest, AutomaticSettingsAndPingAcks) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + const std::string frames = TestFrameSequence().ClientPreface().Ping(42).Serialize(); testing::InSequence s; // Client preface (empty SETTINGS) - EXPECT_CALL(http2_visitor_, OnFrameHeader(0, 0, SETTINGS, 0)); - EXPECT_CALL(http2_visitor_, OnSettingsStart()); - EXPECT_CALL(http2_visitor_, OnSettingsEnd()); + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); // PING - EXPECT_CALL(http2_visitor_, OnFrameHeader(0, _, PING, 0)); - EXPECT_CALL(http2_visitor_, OnPing(42, false)); + EXPECT_CALL(visitor, OnFrameHeader(0, _, PING, 0)); + EXPECT_CALL(visitor, OnPing(42, false)); - const int64_t read_result = adapter_->ProcessBytes(frames); + const int64_t read_result = adapter->ProcessBytes(frames); EXPECT_EQ(static_cast<size_t>(read_result), frames.size()); - EXPECT_TRUE(adapter_->want_write()); + EXPECT_TRUE(adapter->want_write()); // Server preface (SETTINGS) - EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); - EXPECT_CALL(http2_visitor_, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); // SETTINGS ack - EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); - EXPECT_CALL(http2_visitor_, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); // PING ack - EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(PING, 0, _, 0x1)); - EXPECT_CALL(http2_visitor_, OnFrameSent(PING, 0, _, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(PING, 0, _, 0x1, 0)); - int send_result = adapter_->Send(); + int send_result = adapter->Send(); EXPECT_EQ(0, send_result); - EXPECT_THAT( - http2_visitor_.data(), - EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::SETTINGS, spdy::SpdyFrameType::PING})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS, + SpdyFrameType::PING})); } -TEST_F(OgHttp2AdapterTest, AutomaticPingAcksDisabled) { +TEST(OgHttp2AdapterTest, AutomaticPingAcksDisabled) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kServer, .auto_ping_ack = false}; @@ -189,18 +264,18 @@ TEST_F(OgHttp2AdapterTest, AutomaticPingAcksDisabled) { int send_result = adapter->Send(); EXPECT_EQ(0, send_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS})); } -TEST(OgHttp2AdapterClientTest, ClientHandles100Headers) { +TEST(OgHttp2AdapterTest, ClientHandles100Headers) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; auto adapter = OgHttp2Adapter::Create(visitor, options); testing::InSequence s; - const std::vector<const Header> headers1 = + const std::vector<Header> headers1 = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -266,18 +341,18 @@ TEST(OgHttp2AdapterClientTest, ClientHandles100Headers) { EXPECT_TRUE(adapter->want_write()); result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::PING})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::PING})); } -TEST(OgHttp2AdapterClientTest, ClientRejects100HeadersWithFin) { +TEST(OgHttp2AdapterTest, ClientRejects100HeadersWithFin) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; auto adapter = OgHttp2Adapter::Create(visitor, options); testing::InSequence s; - const std::vector<const Header> headers1 = + const std::vector<Header> headers1 = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -319,8 +394,6 @@ TEST(OgHttp2AdapterClientTest, ClientRejects100HeadersWithFin) { EXPECT_CALL(visitor, OnInvalidFrame( 1, Http2VisitorInterface::InvalidFrameError::kHttpMessaging)); - // NOTE: nghttp2 does not deliver the OnEndStream event. - EXPECT_CALL(visitor, OnEndStream(1)); const int64_t stream_result = adapter->ProcessBytes(stream_frames); EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); @@ -334,18 +407,332 @@ TEST(OgHttp2AdapterClientTest, ClientRejects100HeadersWithFin) { EXPECT_TRUE(adapter->want_write()); result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::RST_STREAM})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM})); +} + +TEST(OgHttp2AdapterTest, ClientRejects100HeadersWithContent) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + testing::InSequence s; + + const std::vector<Header> headers1 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + + const int32_t stream_id1 = adapter->SubmitRequest(headers1, nullptr, nullptr); + ASSERT_GT(stream_id1, 0); + + 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); + visitor.Clear(); + + const std::string stream_frames = + TestFrameSequence() + .ServerPreface() + .Headers(1, {{":status", "100"}}, + /*fin=*/false) + .Data(1, "We needed the final headers before data, whoops") + .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", "100")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnFrameHeader(1, _, DATA, 0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, _)); + + const int64_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 1, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR)); + + EXPECT_TRUE(adapter->want_write()); + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM})); +} + +TEST(OgHttp2AdapterTest, ClientRejects100HeadersWithContentLength) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + testing::InSequence s; + + const std::vector<Header> headers1 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + + const int32_t stream_id1 = adapter->SubmitRequest(headers1, nullptr, nullptr); + ASSERT_GT(stream_id1, 0); + + 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); + visitor.Clear(); + + const std::string stream_frames = + TestFrameSequence() + .ServerPreface() + .Headers(1, {{":status", "100"}, {"content-length", "42"}}, + /*fin=*/false) + .Headers(1, + {{":status", "200"}, + {"server", "my-fake-server"}, + {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}}, + /*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", "100")); + EXPECT_CALL( + visitor, + OnInvalidFrame(1, Http2VisitorInterface::InvalidFrameError::kHttpHeader)); + + const int64_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 1, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR)); + + EXPECT_TRUE(adapter->want_write()); + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM})); } -TEST(OgHttp2AdapterClientTest, ClientHandlesTrailers) { +TEST(OgHttp2AdapterTest, ClientHandles204WithContent) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; auto adapter = OgHttp2Adapter::Create(visitor, options); testing::InSequence s; - const std::vector<const Header> headers1 = + const std::vector<Header> headers1 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + + const int32_t stream_id1 = adapter->SubmitRequest(headers1, nullptr, nullptr); + ASSERT_GT(stream_id1, 0); + + 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); + visitor.Clear(); + + const std::string stream_frames = + TestFrameSequence() + .ServerPreface() + .Headers(1, {{":status", "204"}, {"content-length", "2"}}, + /*fin=*/false) + .Data(1, "hi") + .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", "204")); + EXPECT_CALL( + visitor, + OnInvalidFrame(1, Http2VisitorInterface::InvalidFrameError::kHttpHeader)); + + const int64_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 1, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR)); + + EXPECT_TRUE(adapter->want_write()); + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM})); +} + +TEST(OgHttp2AdapterTest, ClientHandles304WithContent) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + testing::InSequence s; + + const std::vector<Header> headers1 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + + const int32_t stream_id1 = adapter->SubmitRequest(headers1, nullptr, nullptr); + ASSERT_GT(stream_id1, 0); + + 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); + visitor.Clear(); + + const std::string stream_frames = + TestFrameSequence() + .ServerPreface() + .Headers(1, {{":status", "304"}, {"content-length", "2"}}, + /*fin=*/false) + .Data(1, "hi") + .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", "304")); + EXPECT_CALL(visitor, OnHeaderForStream(1, "content-length", "2")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnFrameHeader(1, _, DATA, 0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 2)); + + const int64_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 1, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR)); + + EXPECT_TRUE(adapter->want_write()); + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM})); +} + +TEST(OgHttp2AdapterTest, ClientHandles304WithContentLength) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + testing::InSequence s; + + const std::vector<Header> headers = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + + const int32_t stream_id = adapter->SubmitRequest(headers, nullptr, nullptr); + ASSERT_GT(stream_id, 0); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0)); + + int result = adapter->Send(); + EXPECT_EQ(0, result); + visitor.Clear(); + + const std::string stream_frames = + TestFrameSequence() + .ServerPreface() + .Headers(1, {{":status", "304"}, {"content-length", "2"}}, + /*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, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "304")); + EXPECT_CALL(visitor, OnHeaderForStream(1, "content-length", "2")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnEndStream(1)); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR)); + + const int64_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + + EXPECT_TRUE(adapter->want_write()); + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); +} + +TEST(OgHttp2AdapterTest, ClientHandlesTrailers) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + testing::InSequence s; + + const std::vector<Header> headers1 = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -367,8 +754,8 @@ TEST(OgHttp2AdapterClientTest, ClientHandlesTrailers) { 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})); + EXPECT_THAT(data, + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::HEADERS})); visitor.Clear(); const std::string stream_frames = @@ -415,17 +802,68 @@ TEST(OgHttp2AdapterClientTest, ClientHandlesTrailers) { EXPECT_TRUE(adapter->want_write()); result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); +} + +TEST(OgHttp2AdapterTest, ClientSendsTrailers) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + testing::InSequence s; + + const std::vector<Header> headers1 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + + const std::string kBody = "This is an example request body."; + auto body1 = absl::make_unique<TestDataFrameSource>(visitor, false); + body1->AppendPayload(kBody); + body1->EndData(); + + const int32_t stream_id1 = + adapter->SubmitRequest(headers1, std::move(body1), nullptr); + ASSERT_GT(stream_id1, 0); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x4, 0)); + EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id1, _, 0x0, 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({SpdyFrameType::SETTINGS, SpdyFrameType::HEADERS, + SpdyFrameType::DATA})); + visitor.Clear(); + + const std::vector<Header> trailers1 = + ToHeaders({{"extra-info", "Trailers are weird but good?"}}); + adapter->SubmitTrailer(stream_id1, trailers1); + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); + + result = adapter->Send(); + EXPECT_EQ(0, result); + data = visitor.data(); + EXPECT_THAT(data, EqualsFrames({SpdyFrameType::HEADERS})); } -TEST(OgHttp2AdapterClientTest, ClientHandlesMetadata) { +TEST(OgHttp2AdapterTest, ClientHandlesMetadata) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; auto adapter = OgHttp2Adapter::Create(visitor, options); testing::InSequence s; - const std::vector<const Header> headers1 = + const std::vector<Header> headers1 = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -447,8 +885,8 @@ TEST(OgHttp2AdapterClientTest, ClientHandlesMetadata) { 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})); + EXPECT_THAT(data, + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::HEADERS})); visitor.Clear(); const std::string stream_frames = @@ -499,47 +937,48 @@ TEST(OgHttp2AdapterClientTest, ClientHandlesMetadata) { EXPECT_TRUE(adapter->want_write()); result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); } -TEST(OgHttp2AdapterClientTest, ClientHandlesMetadataWithError) { +TEST(OgHttp2AdapterTest, ClientHandlesMetadataWithEmptyPayload) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; auto adapter = OgHttp2Adapter::Create(visitor, options); testing::InSequence s; - const std::vector<const Header> headers1 = + const std::vector<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; + const int32_t stream_id = adapter->SubmitRequest(headers1, nullptr, nullptr); + ASSERT_GT(stream_id, 0); 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)); + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 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({SpdyFrameType::SETTINGS, 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") + .Metadata(1, "") .Data(1, "This is the response body.", true) .Serialize(); @@ -548,20 +987,77 @@ TEST(OgHttp2AdapterClientTest, ClientHandlesMetadataWithError) { 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, OnHeaderForStream(1, _, _)).Times(3); 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, _, DATA, 1)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, _)); + EXPECT_CALL(visitor, OnDataForStream(1, "This is the response body.")); + EXPECT_CALL(visitor, OnEndStream(1)); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR)); + + const int64_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); +} + +TEST(OgHttp2AdapterTest, ClientHandlesMetadataWithPayloadError) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + testing::InSequence s; + + const std::vector<Header> headers = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + + const int32_t stream_id = adapter->SubmitRequest(headers, nullptr, nullptr); + ASSERT_GT(stream_id, 0); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0)); + + int result = adapter->Send(); + EXPECT_EQ(0, result); + visitor.Clear(); + + const std::string stream_frames = + TestFrameSequence() + .ServerPreface() + .Metadata(0, "Example connection metadata") + .Headers(stream_id, + {{":status", "200"}, + {"server", "my-fake-server"}, + {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}}, + /*fin=*/false) + .Metadata(stream_id, "Example stream metadata") + .Data(stream_id, "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(stream_id, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(stream_id)); + EXPECT_CALL(visitor, OnHeaderForStream(stream_id, _, _)).Times(3); + EXPECT_CALL(visitor, OnEndHeadersForStream(stream_id)); + EXPECT_CALL(visitor, OnFrameHeader(stream_id, _, kMetadataFrameType, 4)); + EXPECT_CALL(visitor, OnBeginMetadataForStream(stream_id, _)); + EXPECT_CALL(visitor, OnMetadataForStream(stream_id, _)) .WillOnce(testing::Return(false)); // Remaining frames are not processed due to the error. EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kParseError)); @@ -577,17 +1073,90 @@ TEST(OgHttp2AdapterClientTest, ClientHandlesMetadataWithError) { EXPECT_TRUE(adapter->want_write()); result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); } -TEST(OgHttp2AdapterClientTest, ClientRstStreamWhileHandlingHeaders) { +TEST(OgHttp2AdapterTest, ClientHandlesMetadataWithCompletionError) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; auto adapter = OgHttp2Adapter::Create(visitor, options); testing::InSequence s; - const std::vector<const Header> headers1 = + const std::vector<Header> headers = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + + const int32_t stream_id = adapter->SubmitRequest(headers, nullptr, nullptr); + ASSERT_GT(stream_id, 0); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0)); + + int result = adapter->Send(); + EXPECT_EQ(0, result); + visitor.Clear(); + + const std::string stream_frames = + TestFrameSequence() + .ServerPreface() + .Metadata(0, "Example connection metadata") + .Headers(stream_id, + {{":status", "200"}, + {"server", "my-fake-server"}, + {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}}, + /*fin=*/false) + .Metadata(stream_id, "Example stream metadata") + .Data(stream_id, "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(stream_id, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(stream_id)); + EXPECT_CALL(visitor, OnHeaderForStream(stream_id, _, _)).Times(3); + EXPECT_CALL(visitor, OnEndHeadersForStream(stream_id)); + EXPECT_CALL(visitor, OnFrameHeader(stream_id, _, kMetadataFrameType, 4)); + EXPECT_CALL(visitor, OnBeginMetadataForStream(stream_id, _)); + EXPECT_CALL(visitor, OnMetadataForStream(stream_id, _)); + EXPECT_CALL(visitor, OnMetadataEndForStream(stream_id)) + .WillOnce(testing::Return(false)); + // Remaining frames are not processed due to the error. + EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kParseError)); + + const int64_t stream_result = adapter->ProcessBytes(stream_frames); + // Negative integer returned to indicate an error. + EXPECT_LT(stream_result, 0); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + + EXPECT_FALSE(adapter->want_read()); + EXPECT_TRUE(adapter->want_write()); + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); +} + +TEST(OgHttp2AdapterTest, ClientRstStreamWhileHandlingHeaders) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + testing::InSequence s; + + const std::vector<Header> headers1 = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -609,8 +1178,8 @@ TEST(OgHttp2AdapterClientTest, ClientRstStreamWhileHandlingHeaders) { 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})); + EXPECT_THAT(data, + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::HEADERS})); visitor.Clear(); const std::string stream_frames = @@ -640,9 +1209,6 @@ TEST(OgHttp2AdapterClientTest, ClientRstStreamWhileHandlingHeaders) { 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 int64_t stream_result = adapter->ProcessBytes(stream_frames); EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); @@ -658,18 +1224,18 @@ TEST(OgHttp2AdapterClientTest, ClientRstStreamWhileHandlingHeaders) { EXPECT_TRUE(adapter->want_write()); result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::RST_STREAM})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM})); } -TEST(OgHttp2AdapterClientTest, ClientConnectionErrorWhileHandlingHeaders) { +TEST(OgHttp2AdapterTest, ClientConnectionErrorWhileHandlingHeaders) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; auto adapter = OgHttp2Adapter::Create(visitor, options); testing::InSequence s; - const std::vector<const Header> headers1 = + const std::vector<Header> headers1 = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -691,8 +1257,8 @@ TEST(OgHttp2AdapterClientTest, ClientConnectionErrorWhileHandlingHeaders) { 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})); + EXPECT_THAT(data, + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::HEADERS})); visitor.Clear(); const std::string stream_frames = @@ -730,17 +1296,17 @@ TEST(OgHttp2AdapterClientTest, ClientConnectionErrorWhileHandlingHeaders) { EXPECT_TRUE(adapter->want_write()); result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); } -TEST(OgHttp2AdapterClientTest, ClientConnectionErrorWhileHandlingHeadersOnly) { +TEST(OgHttp2AdapterTest, ClientConnectionErrorWhileHandlingHeadersOnly) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; auto adapter = OgHttp2Adapter::Create(visitor, options); testing::InSequence s; - const std::vector<const Header> headers1 = + const std::vector<Header> headers1 = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -762,8 +1328,8 @@ TEST(OgHttp2AdapterClientTest, ClientConnectionErrorWhileHandlingHeadersOnly) { 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})); + EXPECT_THAT(data, + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::HEADERS})); visitor.Clear(); const std::string stream_frames = @@ -800,17 +1366,17 @@ TEST(OgHttp2AdapterClientTest, ClientConnectionErrorWhileHandlingHeadersOnly) { EXPECT_TRUE(adapter->want_write()); result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); } -TEST(OgHttp2AdapterClientTest, ClientRejectsHeaders) { +TEST(OgHttp2AdapterTest, ClientRejectsHeaders) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; auto adapter = OgHttp2Adapter::Create(visitor, options); testing::InSequence s; - const std::vector<const Header> headers1 = + const std::vector<Header> headers1 = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -832,8 +1398,8 @@ TEST(OgHttp2AdapterClientTest, ClientRejectsHeaders) { 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})); + EXPECT_THAT(data, + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::HEADERS})); visitor.Clear(); const std::string stream_frames = @@ -867,17 +1433,17 @@ TEST(OgHttp2AdapterClientTest, ClientRejectsHeaders) { EXPECT_TRUE(adapter->want_write()); result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); } -TEST(OgHttp2AdapterClientTest, ClientHandlesSmallerHpackHeaderTableSetting) { +TEST(OgHttp2AdapterTest, ClientHandlesSmallerHpackHeaderTableSetting) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; auto adapter = OgHttp2Adapter::Create(visitor, options); testing::InSequence s; - const std::vector<const Header> headers1 = ToHeaders({ + const std::vector<Header> headers1 = ToHeaders({ {":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -919,7 +1485,7 @@ TEST(OgHttp2AdapterClientTest, ClientHandlesSmallerHpackHeaderTableSetting) { EXPECT_LE(adapter->GetHpackEncoderDynamicTableSize(), 100); } -TEST(OgHttp2AdapterClientTest, ClientHandlesLargerHpackHeaderTableSetting) { +TEST(OgHttp2AdapterTest, ClientHandlesLargerHpackHeaderTableSetting) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; auto adapter = OgHttp2Adapter::Create(visitor, options); @@ -962,14 +1528,14 @@ TEST(OgHttp2AdapterClientTest, ClientHandlesLargerHpackHeaderTableSetting) { EXPECT_EQ(adapter->GetHpackEncoderDynamicTableCapacity(), 40960); } -TEST(OgHttp2AdapterClientTest, ClientSendsHpackHeaderTableSetting) { +TEST(OgHttp2AdapterTest, ClientSendsHpackHeaderTableSetting) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; auto adapter = OgHttp2Adapter::Create(visitor, options); testing::InSequence s; - const std::vector<const Header> headers1 = ToHeaders({ + const std::vector<Header> headers1 = ToHeaders({ {":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -1042,7 +1608,7 @@ TEST(OgHttp2AdapterClientTest, ClientSendsHpackHeaderTableSetting) { EXPECT_EQ(0, result); visitor.Clear(); - const std::vector<const Header> headers2 = ToHeaders({ + const std::vector<Header> headers2 = ToHeaders({ {":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -1097,14 +1663,14 @@ TEST(OgHttp2AdapterClientTest, ClientSendsHpackHeaderTableSetting) { // 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) { +TEST(OgHttp2AdapterTest, 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 = + const std::vector<Header> headers1 = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -1124,8 +1690,8 @@ TEST(OgHttp2AdapterClientTest, DISABLED_ClientHandlesInvalidTrailers) { 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})); + EXPECT_THAT(data, + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::HEADERS})); visitor.Clear(); const std::string stream_frames = @@ -1174,18 +1740,42 @@ TEST(OgHttp2AdapterClientTest, DISABLED_ClientHandlesInvalidTrailers) { EXPECT_TRUE(adapter->want_write()); result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::RST_STREAM})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM})); } -TEST(OgHttp2AdapterClientTest, ClientFailsOnGoAway) { +TEST(OgHttp2AdapterTest, ClientStartsShutdown) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + EXPECT_FALSE(adapter->want_write()); + + // No-op (except for logging) for a client implementation. + adapter->SubmitShutdownNotice(); + EXPECT_FALSE(adapter->want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + + int result = adapter->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(OgHttp2AdapterTest, ClientFailsOnGoAway) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; auto adapter = OgHttp2Adapter::Create(visitor, options); testing::InSequence s; - const std::vector<const Header> headers1 = + const std::vector<Header> headers1 = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -1207,8 +1797,8 @@ TEST(OgHttp2AdapterClientTest, ClientFailsOnGoAway) { 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})); + EXPECT_THAT(data, + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::HEADERS})); visitor.Clear(); const std::string stream_frames = @@ -1250,17 +1840,17 @@ TEST(OgHttp2AdapterClientTest, ClientFailsOnGoAway) { EXPECT_TRUE(adapter->want_write()); result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); } -TEST(OgHttp2AdapterClientTest, ClientRejects101Response) { +TEST(OgHttp2AdapterTest, ClientRejects101Response) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; auto adapter = OgHttp2Adapter::Create(visitor, options); testing::InSequence s; - const std::vector<const Header> headers1 = + const std::vector<Header> headers1 = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -1280,8 +1870,8 @@ TEST(OgHttp2AdapterClientTest, ClientRejects101Response) { 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})); + EXPECT_THAT(data, + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::HEADERS})); visitor.Clear(); const std::string stream_frames = @@ -1301,9 +1891,9 @@ TEST(OgHttp2AdapterClientTest, ClientRejects101Response) { EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); - EXPECT_CALL(visitor, - OnInvalidFrame( - 1, Http2VisitorInterface::InvalidFrameError::kHttpMessaging)); + EXPECT_CALL( + visitor, + OnInvalidFrame(1, Http2VisitorInterface::InvalidFrameError::kHttpHeader)); const int64_t stream_result = adapter->ProcessBytes(stream_frames); EXPECT_EQ(static_cast<int64_t>(stream_frames.size()), stream_result); @@ -1320,11 +1910,11 @@ TEST(OgHttp2AdapterClientTest, ClientRejects101Response) { EXPECT_TRUE(adapter->want_write()); result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::RST_STREAM})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM})); } -TEST(OgHttp2AdapterClientTest, ClientObeysMaxConcurrentStreams) { +TEST(OgHttp2AdapterTest, ClientObeysMaxConcurrentStreams) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; auto adapter = OgHttp2Adapter::Create(visitor, options); @@ -1394,8 +1984,8 @@ TEST(OgHttp2AdapterClientTest, ClientObeysMaxConcurrentStreams) { result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS, - spdy::SpdyFrameType::DATA})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::HEADERS, SpdyFrameType::DATA})); EXPECT_THAT(visitor.data(), testing::HasSubstr(kBody)); visitor.Clear(); EXPECT_FALSE(adapter->want_write()); @@ -1450,12 +2040,295 @@ TEST(OgHttp2AdapterClientTest, ClientObeysMaxConcurrentStreams) { result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::HEADERS})); visitor.Clear(); EXPECT_FALSE(adapter->want_write()); } -TEST(OgHttp2AdapterClientTest, FailureSendingConnectionPreface) { +TEST(OgHttp2AdapterTest, ClientReceivesInitialWindowSetting) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + const std::string initial_frames = + TestFrameSequence() + .Settings({{INITIAL_WINDOW_SIZE, 80000u}}) + .WindowUpdate(0, 65536) + .Serialize(); + // Server preface (SETTINGS with INITIAL_STREAM_WINDOW) + EXPECT_CALL(visitor, OnFrameHeader(0, 6, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + // TODO(diannahu): Remove the duplicate call with a separate + // ExtensionVisitorInterface implementation. + EXPECT_CALL(visitor, OnSetting(Http2Setting{INITIAL_WINDOW_SIZE, 80000u})) + .Times(2); + EXPECT_CALL(visitor, OnSettingsEnd()); + EXPECT_CALL(visitor, OnFrameHeader(0, 4, WINDOW_UPDATE, 0)); + EXPECT_CALL(visitor, OnWindowUpdate(0, 65536)); + + const int64_t initial_result = adapter->ProcessBytes(initial_frames); + EXPECT_EQ(initial_frames.size(), static_cast<size_t>(initial_result)); + + // Session will want to write a SETTINGS ack. + EXPECT_TRUE(adapter->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)); + + int64_t result = adapter->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::SETTINGS})); + visitor.Clear(); + + const std::string kLongBody = std::string(81000, 'c'); + auto body1 = absl::make_unique<TestDataFrameSource>(visitor, true); + body1->AppendPayload(kLongBody); + body1->EndData(); + const int stream_id = + adapter->SubmitRequest(ToHeaders({{":method", "POST"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}), + std::move(body1), nullptr); + EXPECT_GT(stream_id, 0); + EXPECT_TRUE(adapter->want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0)); + // The client can send more than 4 frames (65536 bytes) of data. + EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, 16384, 0x0, 0)).Times(4); + EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, 14464, 0x0, 0)); + + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::HEADERS, SpdyFrameType::DATA, + SpdyFrameType::DATA, SpdyFrameType::DATA, + SpdyFrameType::DATA, SpdyFrameType::DATA})); +} + +TEST(OgHttp2AdapterTest, ClientReceivesInitialWindowSettingAfterStreamStart) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + const std::string initial_frames = + TestFrameSequence().ServerPreface().WindowUpdate(0, 65536).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, 4, WINDOW_UPDATE, 0)); + EXPECT_CALL(visitor, OnWindowUpdate(0, 65536)); + + const int64_t initial_result = adapter->ProcessBytes(initial_frames); + EXPECT_EQ(initial_frames.size(), static_cast<size_t>(initial_result)); + + // Session will want to write a SETTINGS ack. + EXPECT_TRUE(adapter->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)); + + int64_t result = adapter->Send(); + EXPECT_EQ(0, result); + visitor.Clear(); + + const std::string kLongBody = std::string(81000, 'c'); + auto body1 = absl::make_unique<TestDataFrameSource>(visitor, true); + body1->AppendPayload(kLongBody); + body1->EndData(); + const int stream_id = + adapter->SubmitRequest(ToHeaders({{":method", "POST"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}), + std::move(body1), nullptr); + EXPECT_GT(stream_id, 0); + EXPECT_TRUE(adapter->want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0)); + // The client can only send 65535 bytes of data, as the stream window has not + // yet been increased. + EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, 16384, 0x0, 0)).Times(3); + EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, 16383, 0x0, 0)); + + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::HEADERS, SpdyFrameType::DATA, + SpdyFrameType::DATA, SpdyFrameType::DATA, + SpdyFrameType::DATA})); + visitor.Clear(); + + // Can't write any more due to flow control. + EXPECT_FALSE(adapter->want_write()); + + const std::string settings_frame = + TestFrameSequence().Settings({{INITIAL_WINDOW_SIZE, 80000u}}).Serialize(); + // SETTINGS with INITIAL_STREAM_WINDOW + EXPECT_CALL(visitor, OnFrameHeader(0, 6, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + // TODO(diannahu): Remove the duplicate call with a separate + // ExtensionVisitorInterface implementation. + EXPECT_CALL(visitor, OnSetting(Http2Setting{INITIAL_WINDOW_SIZE, 80000u})) + .Times(2); + EXPECT_CALL(visitor, OnSettingsEnd()); + + const int64_t settings_result = adapter->ProcessBytes(settings_frame); + EXPECT_EQ(settings_frame.size(), static_cast<size_t>(settings_result)); + + EXPECT_TRUE(adapter->want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + // The client can write more after receiving the INITIAL_WINDOW_SIZE setting. + EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, 14465, 0x0, 0)); + + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::DATA})); +} + +TEST(OgHttp2AdapterTest, InvalidInitialWindowSetting) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + const uint32_t kTooLargeInitialWindow = 1u << 31; + const std::string initial_frames = + TestFrameSequence() + .Settings({{INITIAL_WINDOW_SIZE, kTooLargeInitialWindow}}) + .Serialize(); + // Server preface (SETTINGS with INITIAL_STREAM_WINDOW) + EXPECT_CALL(visitor, OnFrameHeader(0, 6, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + // TODO(diannahu): Remove the duplicate call with a separate + // ExtensionVisitorInterface implementation. + EXPECT_CALL( + visitor, + OnInvalidFrame(0, Http2VisitorInterface::InvalidFrameError::kFlowControl)) + .Times(2); + EXPECT_CALL(visitor, + OnConnectionError( + Http2VisitorInterface::ConnectionError::kFlowControlError)); + + const int64_t initial_result = adapter->ProcessBytes(initial_frames); + EXPECT_EQ(initial_frames.size(), static_cast<size_t>(initial_result)); + + // Session will want to write a GOAWAY. + EXPECT_TRUE(adapter->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)); + + int64_t result = adapter->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::SETTINGS})); + visitor.Clear(); +} + +TEST(OggHttp2AdapterClientTest, InitialWindowSettingCausesOverflow) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + testing::InSequence s; + + const std::vector<Header> headers = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + const int32_t stream_id = adapter->SubmitRequest(headers, nullptr, nullptr); + ASSERT_GT(stream_id, 0); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0)); + int64_t write_result = adapter->Send(); + EXPECT_EQ(0, write_result); + absl::string_view data = visitor.data(); + EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); + data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); + EXPECT_THAT(data, + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::HEADERS})); + visitor.Clear(); + + const uint32_t kLargeInitialWindow = (1u << 31) - 1; + const std::string frames = + TestFrameSequence() + .ServerPreface() + .Headers(stream_id, {{":status", "200"}}, /*fin=*/false) + .WindowUpdate(stream_id, 65536u) + .Settings({{INITIAL_WINDOW_SIZE, kLargeInitialWindow}}) + .Serialize(); + + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + EXPECT_CALL(visitor, OnFrameHeader(stream_id, _, HEADERS, 0x4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(stream_id)); + EXPECT_CALL(visitor, OnHeaderForStream(stream_id, ":status", "200")); + EXPECT_CALL(visitor, OnEndHeadersForStream(stream_id)); + + EXPECT_CALL(visitor, OnFrameHeader(stream_id, 4, WINDOW_UPDATE, 0x0)); + EXPECT_CALL(visitor, OnWindowUpdate(stream_id, 65536)); + + EXPECT_CALL(visitor, OnFrameHeader(0, 6, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, + OnSetting(Http2Setting{INITIAL_WINDOW_SIZE, kLargeInitialWindow})) + .Times(2); + EXPECT_CALL(visitor, OnSettingsEnd()); + + const int64_t read_result = adapter->ProcessBytes(frames); + EXPECT_EQ(static_cast<size_t>(read_result), frames.size()); + EXPECT_TRUE(adapter->want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + + // The stream window update plus the SETTINGS frame with INITIAL_WINDOW_SIZE + // pushes the stream's flow control window outside of the acceptable range. + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, stream_id, 4, 0x0)); + EXPECT_CALL( + visitor, + OnFrameSent(RST_STREAM, stream_id, 4, 0x0, + static_cast<int>(Http2ErrorCode::FLOW_CONTROL_ERROR))); + EXPECT_CALL(visitor, + OnCloseStream(stream_id, Http2ErrorCode::HTTP2_NO_ERROR)); + + int result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM})); +} + +TEST(OgHttp2AdapterTest, FailureSendingConnectionPreface) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; auto adapter = OgHttp2Adapter::Create(visitor, options); @@ -1464,10 +2337,10 @@ TEST(OgHttp2AdapterClientTest, FailureSendingConnectionPreface) { EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kSendError)); int result = adapter->Send(); - EXPECT_EQ(result, Http2VisitorInterface::kSendError); + EXPECT_LT(result, 0); } -TEST(OgHttp2AdapterClientTest, ClientForbidsPushPromise) { +TEST(OgHttp2AdapterTest, ClientForbidsPushPromise) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; auto adapter = OgHttp2Adapter::Create(visitor, options); @@ -1482,11 +2355,11 @@ TEST(OgHttp2AdapterClientTest, ClientForbidsPushPromise) { 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})); + EXPECT_THAT(data, EqualsFrames({SpdyFrameType::SETTINGS})); visitor.Clear(); - const std::vector<const Header> headers = + const std::vector<Header> headers = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -1497,10 +2370,10 @@ TEST(OgHttp2AdapterClientTest, ClientForbidsPushPromise) { EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0)); write_result = adapter->Send(); EXPECT_EQ(0, write_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::HEADERS})); visitor.Clear(); - const std::vector<const Header> push_headers = + const std::vector<Header> push_headers = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -1526,7 +2399,7 @@ TEST(OgHttp2AdapterClientTest, ClientForbidsPushPromise) { EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kInvalidPushPromise)); const int64_t read_result = adapter->ProcessBytes(frames); - EXPECT_LT(read_result, 0); + EXPECT_EQ(static_cast<size_t>(read_result), frames.size()); EXPECT_TRUE(adapter->want_write()); @@ -1536,10 +2409,10 @@ TEST(OgHttp2AdapterClientTest, ClientForbidsPushPromise) { int result = adapter->Send(); EXPECT_EQ(0, result); // SETTINGS ack. - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); } -TEST(OgHttp2AdapterClientTest, ClientForbidsPushStream) { +TEST(OgHttp2AdapterTest, ClientForbidsPushStream) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; auto adapter = OgHttp2Adapter::Create(visitor, options); @@ -1554,11 +2427,11 @@ TEST(OgHttp2AdapterClientTest, ClientForbidsPushStream) { 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})); + EXPECT_THAT(data, EqualsFrames({SpdyFrameType::SETTINGS})); visitor.Clear(); - const std::vector<const Header> headers = + const std::vector<Header> headers = ToHeaders({{":method", "GET"}, {":scheme", "http"}, {":authority", "example.com"}, @@ -1569,7 +2442,7 @@ TEST(OgHttp2AdapterClientTest, ClientForbidsPushStream) { EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0)); write_result = adapter->Send(); EXPECT_EQ(0, write_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::HEADERS})); visitor.Clear(); const std::string frames = @@ -1598,7 +2471,7 @@ TEST(OgHttp2AdapterClientTest, ClientForbidsPushStream) { EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kInvalidNewStreamId)); const int64_t read_result = adapter->ProcessBytes(frames); - EXPECT_LT(read_result, 0); + EXPECT_EQ(static_cast<size_t>(read_result), frames.size()); EXPECT_TRUE(adapter->want_write()); @@ -1608,10 +2481,10 @@ TEST(OgHttp2AdapterClientTest, ClientForbidsPushStream) { int result = adapter->Send(); EXPECT_EQ(0, result); // SETTINGS ack. - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); } -TEST(OgHttp2AdapterClientTest, ClientReceivesDataOnClosedStream) { +TEST(OgHttp2AdapterTest, ClientReceivesDataOnClosedStream) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; auto adapter = OgHttp2Adapter::Create(visitor, options); @@ -1623,7 +2496,7 @@ TEST(OgHttp2AdapterClientTest, ClientReceivesDataOnClosedStream) { 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})); + EXPECT_THAT(data, EqualsFrames({SpdyFrameType::SETTINGS})); visitor.Clear(); const std::string initial_frames = @@ -1645,7 +2518,7 @@ TEST(OgHttp2AdapterClientTest, ClientReceivesDataOnClosedStream) { result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); visitor.Clear(); // Let the client open a stream with a request. @@ -1663,7 +2536,7 @@ TEST(OgHttp2AdapterClientTest, ClientReceivesDataOnClosedStream) { result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::HEADERS})); visitor.Clear(); // Let the client RST_STREAM the stream it opened. @@ -1677,7 +2550,7 @@ TEST(OgHttp2AdapterClientTest, ClientReceivesDataOnClosedStream) { result = adapter->Send(); EXPECT_EQ(0, result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::RST_STREAM})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::RST_STREAM})); visitor.Clear(); // Let the server send a response on the stream. (It might not have received @@ -1703,158 +2576,479 @@ TEST(OgHttp2AdapterClientTest, ClientReceivesDataOnClosedStream) { EXPECT_FALSE(adapter->want_write()); } -TEST_F(OgHttp2AdapterTest, SubmitMetadata) { +TEST(OgHttp2AdapterTest, ClientEncountersFlowControlBlock) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + testing::InSequence s; + + const std::vector<Header> headers1 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + + const std::string kBody = std::string(100 * 1024, 'a'); + auto body1 = absl::make_unique<TestDataFrameSource>(visitor, false); + body1->AppendPayload(kBody); + body1->EndData(); + + const int32_t stream_id1 = + adapter->SubmitRequest(headers1, std::move(body1), nullptr); + ASSERT_GT(stream_id1, 0); + + const std::vector<Header> headers2 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/two"}}); + + auto body2 = absl::make_unique<TestDataFrameSource>(visitor, false); + body2->AppendPayload(kBody); + body2->EndData(); + + const int32_t stream_id2 = + adapter->SubmitRequest(headers2, std::move(body2), nullptr); + ASSERT_GT(stream_id2, 0); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x4, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id2, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id2, _, 0x4, 0)); + // 4 DATA frames should saturate the default 64kB stream/connection flow + // control window. + EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id1, _, 0x0, 0)).Times(4); + + int result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_EQ(0, adapter->GetSendWindowSize()); + + const std::string stream_frames = TestFrameSequence() + .ServerPreface() + .WindowUpdate(0, 80000) + .WindowUpdate(stream_id1, 20000) + .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, 4, WINDOW_UPDATE, 0)); + EXPECT_CALL(visitor, OnWindowUpdate(0, 80000)); + EXPECT_CALL(visitor, OnFrameHeader(1, 4, WINDOW_UPDATE, 0)); + EXPECT_CALL(visitor, OnWindowUpdate(1, 20000)); + + const int64_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); + + EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id2, _, 0x0, 0)) + .Times(testing::AtLeast(1)); + EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id1, _, 0x0, 0)) + .Times(testing::AtLeast(1)); + + EXPECT_TRUE(adapter->want_write()); + result = adapter->Send(); + EXPECT_EQ(0, result); +} + +TEST(OgHttp2AdapterTest, ClientSendsTrailersAfterFlowControlBlock) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + testing::InSequence s; + + const std::vector<Header> headers1 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + + auto body1 = absl::make_unique<TestDataFrameSource>(visitor, false); + body1->AppendPayload("Really small body."); + body1->EndData(); + + const int32_t stream_id1 = + adapter->SubmitRequest(headers1, std::move(body1), nullptr); + ASSERT_GT(stream_id1, 0); + + const std::vector<Header> headers2 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/two"}}); + + const std::string kBody = std::string(100 * 1024, 'a'); + auto body2 = absl::make_unique<TestDataFrameSource>(visitor, false); + body2->AppendPayload(kBody); + body2->EndData(); + + const int32_t stream_id2 = + adapter->SubmitRequest(headers2, std::move(body2), nullptr); + ASSERT_GT(stream_id2, 0); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x4, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id2, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id2, _, 0x4, 0)); + EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id1, _, 0x0, 0)).Times(1); + // 4 DATA frames should saturate the default 64kB stream/connection flow + // control window. + EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id2, _, 0x0, 0)).Times(4); + + int result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_FALSE(adapter->want_write()); + EXPECT_EQ(0, adapter->GetSendWindowSize()); + + const std::vector<Header> trailers1 = + ToHeaders({{"extra-info", "Trailers are weird but good?"}}); + adapter->SubmitTrailer(stream_id1, trailers1); + + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); + + result = adapter->Send(); + EXPECT_EQ(0, result); +} + +TEST(OgHttp2AdapterTest, ClientSendsMetadataAfterFlowControlBlock) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kClient}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + testing::InSequence s; + + const std::vector<Header> headers1 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}); + + const std::string kBody = std::string(100 * 1024, 'a'); + auto body1 = absl::make_unique<TestDataFrameSource>(visitor, false); + body1->AppendPayload(kBody); + body1->EndData(); + + const int32_t stream_id1 = + adapter->SubmitRequest(headers1, std::move(body1), nullptr); + ASSERT_GT(stream_id1, 0); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x4, 0)); + // 4 DATA frames should saturate the default 64kB stream/connection flow + // control window. + EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id1, _, 0x0, 0)).Times(4); + + int result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_FALSE(adapter->want_write()); + EXPECT_EQ(0, adapter->GetSendWindowSize()); + + auto source = absl::make_unique<TestMetadataSource>(ToHeaderBlock(ToHeaders( + {{"query-cost", "is too darn high"}, {"secret-sauce", "hollandaise"}}))); + adapter->SubmitMetadata(1, 16384u, std::move(source)); + EXPECT_CALL(visitor, OnBeforeFrameSent(kMetadataFrameType, 1, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(kMetadataFrameType, 1, _, 0x4, 0)); + + result = adapter->Send(); + EXPECT_EQ(0, result); +} + +TEST(OgHttp2AdapterTest, SubmitMetadata) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + auto source = absl::make_unique<TestMetadataSource>(ToHeaderBlock(ToHeaders( {{"query-cost", "is too darn high"}, {"secret-sauce", "hollandaise"}}))); - adapter_->SubmitMetadata(1, 16384u, std::move(source)); - EXPECT_TRUE(adapter_->want_write()); + adapter->SubmitMetadata(1, 16384u, std::move(source)); + EXPECT_TRUE(adapter->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)); + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(kMetadataFrameType, 1, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(kMetadataFrameType, 1, _, 0x4, 0)); - int result = adapter_->Send(); + 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_->want_write()); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, + static_cast<SpdyFrameType>(kMetadataFrameType)})); + EXPECT_FALSE(adapter->want_write()); } -TEST_F(OgHttp2AdapterTest, SubmitMetadataMultipleFrames) { +TEST(OgHttp2AdapterTest, SubmitMetadataMultipleFrames) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + const auto kLargeValue = std::string(63 * 1024, 'a'); auto source = absl::make_unique<TestMetadataSource>( ToHeaderBlock(ToHeaders({{"large-value", kLargeValue}}))); - adapter_->SubmitMetadata(1, 16384u, std::move(source)); - EXPECT_TRUE(adapter_->want_write()); + adapter->SubmitMetadata(1, 16384u, std::move(source)); + EXPECT_TRUE(adapter->want_write()); testing::InSequence seq; - EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); - EXPECT_CALL(http2_visitor_, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); - EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(kMetadataFrameType, 1, _, 0x0)); - EXPECT_CALL(http2_visitor_, OnFrameSent(kMetadataFrameType, 1, _, 0x0, 0)); - EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(kMetadataFrameType, 1, _, 0x0)); - EXPECT_CALL(http2_visitor_, OnFrameSent(kMetadataFrameType, 1, _, 0x0, 0)); - EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(kMetadataFrameType, 1, _, 0x0)); - EXPECT_CALL(http2_visitor_, OnFrameSent(kMetadataFrameType, 1, _, 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); - absl::string_view serialized = http2_visitor_.data(); - EXPECT_THAT( - serialized, - EqualsFrames({spdy::SpdyFrameType::SETTINGS, - static_cast<spdy::SpdyFrameType>(kMetadataFrameType), - static_cast<spdy::SpdyFrameType>(kMetadataFrameType), - static_cast<spdy::SpdyFrameType>(kMetadataFrameType), - static_cast<spdy::SpdyFrameType>(kMetadataFrameType)})); - EXPECT_FALSE(adapter_->want_write()); + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(kMetadataFrameType, 1, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(kMetadataFrameType, 1, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(kMetadataFrameType, 1, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(kMetadataFrameType, 1, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(kMetadataFrameType, 1, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(kMetadataFrameType, 1, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(kMetadataFrameType, 1, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(kMetadataFrameType, 1, _, 0x4, 0)); + + int result = adapter->Send(); + EXPECT_EQ(0, result); + absl::string_view serialized = visitor.data(); + EXPECT_THAT(serialized, + EqualsFrames({SpdyFrameType::SETTINGS, + static_cast<SpdyFrameType>(kMetadataFrameType), + static_cast<SpdyFrameType>(kMetadataFrameType), + static_cast<SpdyFrameType>(kMetadataFrameType), + static_cast<SpdyFrameType>(kMetadataFrameType)})); + EXPECT_FALSE(adapter->want_write()); } -TEST_F(OgHttp2AdapterTest, SubmitConnectionMetadata) { +TEST(OgHttp2AdapterTest, SubmitConnectionMetadata) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + auto source = absl::make_unique<TestMetadataSource>(ToHeaderBlock(ToHeaders( {{"query-cost", "is too darn high"}, {"secret-sauce", "hollandaise"}}))); - adapter_->SubmitMetadata(0, 16384u, std::move(source)); - EXPECT_TRUE(adapter_->want_write()); + adapter->SubmitMetadata(0, 16384u, std::move(source)); + EXPECT_TRUE(adapter->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)); + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(kMetadataFrameType, 0, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(kMetadataFrameType, 0, _, 0x4, 0)); - int result = adapter_->Send(); + 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_->want_write()); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, + static_cast<SpdyFrameType>(kMetadataFrameType)})); + EXPECT_FALSE(adapter->want_write()); } -TEST_F(OgHttp2AdapterTest, GetSendWindowSize) { - const int peer_window = adapter_->GetSendWindowSize(); +TEST(OgHttp2AdapterTest, GetSendWindowSize) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + const int peer_window = adapter->GetSendWindowSize(); EXPECT_EQ(peer_window, kInitialFlowControlWindowSize); } -TEST_F(OgHttp2AdapterTest, MarkDataConsumedForStream) { - EXPECT_QUICHE_BUG(adapter_->MarkDataConsumedForStream(1, 11), +TEST(OgHttp2AdapterTest, MarkDataConsumedForStream) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + EXPECT_QUICHE_BUG(adapter->MarkDataConsumedForStream(1, 11), "Stream 1 not found"); } -TEST_F(OgHttp2AdapterTest, TestSerialize) { - EXPECT_TRUE(adapter_->want_read()); - EXPECT_FALSE(adapter_->want_write()); +TEST(OgHttp2AdapterTest, TestSerialize) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + EXPECT_TRUE(adapter->want_read()); + EXPECT_FALSE(adapter->want_write()); - adapter_->SubmitSettings( + adapter->SubmitSettings( {{HEADER_TABLE_SIZE, 128}, {MAX_FRAME_SIZE, 128 << 10}}); - EXPECT_TRUE(adapter_->want_write()); - - adapter_->SubmitPriorityForStream(3, 1, 255, true); - adapter_->SubmitRst(3, Http2ErrorCode::CANCEL); - adapter_->SubmitPing(42); - adapter_->SubmitGoAway(13, Http2ErrorCode::HTTP2_NO_ERROR, ""); - adapter_->SubmitWindowUpdate(3, 127); - EXPECT_TRUE(adapter_->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_, 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_TRUE(adapter->want_write()); + + const Http2StreamId accepted_stream = 3; + const Http2StreamId rejected_stream = 7; + adapter->SubmitPriorityForStream(accepted_stream, 1, 255, true); + adapter->SubmitRst(rejected_stream, Http2ErrorCode::CANCEL); + adapter->SubmitPing(42); + adapter->SubmitGoAway(13, Http2ErrorCode::HTTP2_NO_ERROR, ""); + adapter->SubmitWindowUpdate(accepted_stream, 127); + EXPECT_TRUE(adapter->want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(PRIORITY, accepted_stream, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(PRIORITY, accepted_stream, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, rejected_stream, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(RST_STREAM, rejected_stream, _, 0x0, 0x8)); + EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(PING, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(GOAWAY, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, + OnBeforeFrameSent(WINDOW_UPDATE, accepted_stream, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(WINDOW_UPDATE, accepted_stream, _, 0x0, 0)); + + int result = adapter->Send(); EXPECT_EQ(0, result); EXPECT_THAT( - http2_visitor_.data(), + visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::PRIORITY, SpdyFrameType::RST_STREAM, SpdyFrameType::PING, SpdyFrameType::GOAWAY, SpdyFrameType::WINDOW_UPDATE})); - EXPECT_FALSE(adapter_->want_write()); + EXPECT_FALSE(adapter->want_write()); } -TEST_F(OgHttp2AdapterTest, TestPartialSerialize) { - EXPECT_FALSE(adapter_->want_write()); +TEST(OgHttp2AdapterTest, TestPartialSerialize) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + EXPECT_FALSE(adapter->want_write()); - adapter_->SubmitSettings( + adapter->SubmitSettings( {{HEADER_TABLE_SIZE, 128}, {MAX_FRAME_SIZE, 128 << 10}}); - adapter_->SubmitGoAway(13, Http2ErrorCode::HTTP2_NO_ERROR, - "And don't come back!"); - adapter_->SubmitPing(42); - EXPECT_TRUE(adapter_->want_write()); - - 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_->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_->want_write()); - 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_->want_write()); - EXPECT_THAT(http2_visitor_.data(), + adapter->SubmitGoAway(13, Http2ErrorCode::HTTP2_NO_ERROR, + "And don't come back!"); + adapter->SubmitPing(42); + EXPECT_TRUE(adapter->want_write()); + + visitor.set_send_limit(20); + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + int result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_TRUE(adapter->want_write()); + EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(GOAWAY, 0, _, 0x0, 0)); + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_TRUE(adapter->want_write()); + EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(PING, 0, _, 0x0, 0)); + result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_FALSE(adapter->want_write()); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::GOAWAY, SpdyFrameType::PING})); } -TEST(OgHttp2AdapterServerTest, ClientSendsContinuation) { +TEST(OgHttp2AdapterTest, ConnectionErrorOnControlFrameSent) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + const std::string frames = + TestFrameSequence().ClientPreface().Ping(42).Serialize(); + testing::InSequence s; + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + // PING + EXPECT_CALL(visitor, OnFrameHeader(0, _, PING, 0)); + EXPECT_CALL(visitor, OnPing(42, false)); + + const int64_t read_result = adapter->ProcessBytes(frames); + EXPECT_EQ(static_cast<size_t>(read_result), frames.size()); + + EXPECT_TRUE(adapter->want_write()); + + // Server preface (SETTINGS) + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + // SETTINGS ack + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)) + .WillOnce(testing::Return(-902)); + EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kSendError)); + + int send_result = adapter->Send(); + EXPECT_LT(send_result, 0); + + EXPECT_FALSE(adapter->want_write()); + + send_result = adapter->Send(); + EXPECT_LT(send_result, 0); +} + +TEST(OgHttp2AdapterTest, ConnectionErrorOnDataFrameSent) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + 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, 0x5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnEndStream(1)); + + const int64_t read_result = adapter->ProcessBytes(frames); + EXPECT_EQ(static_cast<size_t>(read_result), frames.size()); + + auto body = absl::make_unique<TestDataFrameSource>(visitor, true); + body->AppendPayload("Here is some data, which will lead to a fatal error"); + TestDataFrameSource* body_ptr = body.get(); + int submit_result = adapter->SubmitResponse( + 1, ToHeaders({{":status", "200"}}), std::move(body)); + ASSERT_EQ(0, submit_result); + + EXPECT_TRUE(adapter->want_write()); + + // Server preface (SETTINGS) + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + // SETTINGS ack + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + // Stream 1, with doomed DATA + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0)); + EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0)) + .WillOnce(testing::Return(-902)); + EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kSendError)); + + int send_result = adapter->Send(); + EXPECT_LT(send_result, 0); + + body_ptr->AppendPayload("After the fatal error, data will be sent no more"); + + EXPECT_FALSE(adapter->want_write()); + + send_result = adapter->Send(); + EXPECT_LT(send_result, 0); +} + +TEST(OgHttp2AdapterTest, ClientSendsContinuation) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; auto adapter = OgHttp2Adapter::Create(visitor, options); @@ -1891,7 +3085,7 @@ TEST(OgHttp2AdapterServerTest, ClientSendsContinuation) { EXPECT_EQ(frames.size(), static_cast<size_t>(result)); } -TEST(OgHttp2AdapterServerTest, ClientSendsMetadataWithContinuation) { +TEST(OgHttp2AdapterTest, ClientSendsMetadataWithContinuation) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; auto adapter = OgHttp2Adapter::Create(visitor, options); @@ -1947,15 +3141,214 @@ TEST(OgHttp2AdapterServerTest, ClientSendsMetadataWithContinuation) { const int64_t result = adapter->ProcessBytes(frames); EXPECT_EQ(frames.size(), static_cast<size_t>(result)); - EXPECT_EQ(TestFrameSequence::MetadataBlockForPayload( - "Example connection metadata in multiple frames"), + EXPECT_EQ("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"), + EXPECT_EQ("Some stream metadata that's also sent in multiple frames", absl::StrJoin(visitor.GetMetadata(1), "")); } -TEST(OgHttp2AdapterServerTest, ServerSubmitsResponseWithDataSourceError) { +TEST(OgHttp2AdapterTest, RepeatedHeaderNames) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + EXPECT_FALSE(adapter->want_write()); + + const std::string frames = TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}, + {"accept", "text/plain"}, + {"accept", "text/html"}}, + /*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, OnHeaderForStream(1, "accept", "text/plain")); + EXPECT_CALL(visitor, OnHeaderForStream(1, "accept", "text/html")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnEndStream(1)); + + int64_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(frames.size(), static_cast<size_t>(result)); + + const std::vector<Header> headers1 = ToHeaders( + {{":status", "200"}, {"content-length", "10"}, {"content-length", "10"}}); + auto body1 = absl::make_unique<TestDataFrameSource>(visitor, true); + body1->AppendPayload("perfection"); + body1->EndData(); + + int submit_result = adapter->SubmitResponse(1, headers1, std::move(body1)); + ASSERT_EQ(0, submit_result); + + 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, 10, 0x1, 0)); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR)); + + int send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS, + SpdyFrameType::HEADERS, SpdyFrameType::DATA})); +} + +TEST(OgHttp2AdapterTest, ServerRespondsToRequestWithTrailers) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + EXPECT_FALSE(adapter->want_write()); + + const std::string frames = + TestFrameSequence() + .ClientPreface() + .Headers(1, {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}) + .Data(1, "Example data, woohoo.") + .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, 4)); + 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, OnFrameHeader(1, _, DATA, 0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, _)); + EXPECT_CALL(visitor, OnDataForStream(1, _)); + + int64_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(frames.size(), static_cast<size_t>(result)); + + const std::vector<Header> headers1 = ToHeaders({{":status", "200"}}); + auto body1 = absl::make_unique<TestDataFrameSource>(visitor, true); + TestDataFrameSource* body1_ptr = body1.get(); + + int submit_result = adapter->SubmitResponse(1, headers1, std::move(body1)); + ASSERT_EQ(0, submit_result); + + 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)); + + int send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS, + SpdyFrameType::HEADERS})); + visitor.Clear(); + + const std::string more_frames = + TestFrameSequence() + .Headers(1, {{"extra-info", "Trailers are weird but good?"}}, + /*fin=*/true) + .Serialize(); + + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, "extra-info", + "Trailers are weird but good?")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnEndStream(1)); + + result = adapter->ProcessBytes(more_frames); + EXPECT_EQ(more_frames.size(), static_cast<size_t>(result)); + + body1_ptr->EndData(); + EXPECT_EQ(true, adapter->ResumeStream(1)); + + EXPECT_CALL(visitor, OnFrameSent(DATA, 1, 0, 0x1, 0)); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR)); + + send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::DATA})); +} + +TEST(OgHttp2AdapterTest, ServerReceivesMoreHeaderBytesThanConfigured) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer, + .max_header_list_bytes = 42}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + EXPECT_FALSE(adapter->want_write()); + + const std::string frames = + TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}, + {"from-douglas-de-fermat", + "I have discovered a truly marvelous answer to the life, " + "the universe, and everything that the header setting is " + "too narrow to contain."}}, + /*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, 0x5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kParseError)); + + int64_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(static_cast<size_t>(result), frames.size()); + + EXPECT_TRUE(adapter->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, + static_cast<int>(Http2ErrorCode::COMPRESSION_ERROR))); + + int send_result = adapter->Send(); + // Some bytes should have been serialized. + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::GOAWAY})); +} + +TEST(OgHttp2AdapterTest, ServerSubmitsResponseWithDataSourceError) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; auto adapter = OgHttp2Adapter::Create(visitor, options); @@ -2008,9 +3401,9 @@ TEST(OgHttp2AdapterServerTest, ServerSubmitsResponseWithDataSourceError) { int send_result = adapter->Send(); EXPECT_EQ(0, send_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::HEADERS})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS, + SpdyFrameType::HEADERS})); visitor.Clear(); EXPECT_FALSE(adapter->want_write()); @@ -2022,7 +3415,7 @@ TEST(OgHttp2AdapterServerTest, ServerSubmitsResponseWithDataSourceError) { EXPECT_FALSE(adapter->want_write()); } -TEST(OgHttp2AdapterServerTest, CompleteRequestWithServerResponse) { +TEST(OgHttp2AdapterTest, CompleteRequestWithServerResponse) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; auto adapter = OgHttp2Adapter::Create(visitor, options); @@ -2073,13 +3466,13 @@ TEST(OgHttp2AdapterServerTest, CompleteRequestWithServerResponse) { int send_result = adapter->Send(); EXPECT_EQ(0, send_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::HEADERS})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS, + SpdyFrameType::HEADERS})); EXPECT_FALSE(adapter->want_write()); } -TEST(OgHttp2AdapterServerTest, IncompleteRequestWithServerResponse) { +TEST(OgHttp2AdapterTest, IncompleteRequestWithServerResponse) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; auto adapter = OgHttp2Adapter::Create(visitor, options); @@ -2124,14 +3517,13 @@ TEST(OgHttp2AdapterServerTest, IncompleteRequestWithServerResponse) { int send_result = adapter->Send(); EXPECT_EQ(0, send_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::HEADERS})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS, + SpdyFrameType::HEADERS})); EXPECT_FALSE(adapter->want_write()); } -TEST(OgHttp2AdapterServerTest, - IncompleteRequestWithServerResponseRstStreamEnabled) { +TEST(OgHttp2AdapterTest, IncompleteRequestWithServerResponseRstStreamEnabled) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kServer, .rst_stream_no_error_when_incomplete = true}; @@ -2179,14 +3571,14 @@ TEST(OgHttp2AdapterServerTest, 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::RST_STREAM})); + EXPECT_THAT( + visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS, + SpdyFrameType::HEADERS, SpdyFrameType::RST_STREAM})); EXPECT_FALSE(adapter->want_write()); } -TEST(OgHttp2AdapterServerTest, ServerSendsInvalidTrailers) { +TEST(OgHttp2AdapterTest, ServerSendsInvalidTrailers) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; auto adapter = OgHttp2Adapter::Create(visitor, options); @@ -2244,9 +3636,8 @@ TEST(OgHttp2AdapterServerTest, ServerSendsInvalidTrailers) { 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})); + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS, + SpdyFrameType::HEADERS, SpdyFrameType::DATA})); EXPECT_THAT(visitor.data(), testing::HasSubstr(kBody)); visitor.Clear(); EXPECT_FALSE(adapter->want_write()); @@ -2263,12 +3654,73 @@ TEST(OgHttp2AdapterServerTest, ServerSendsInvalidTrailers) { send_result = adapter->Send(); EXPECT_EQ(0, send_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::HEADERS})); +} + +TEST(OgHttp2AdapterTest, ServerHandlesDataWithPadding) { + 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"}}, + /*fin=*/false) + .Data(1, "This is the request body.", + /*fin=*/true, /*padding_length=*/39) + .Headers(3, + {{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/two"}}, + /*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()); + + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnFrameHeader(1, 25 + 39, DATA, 0x9)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 25 + 39)); + // Note: oghttp2 passes padding information before the actual data. + EXPECT_CALL(visitor, OnDataPaddingLength(1, 39)); + EXPECT_CALL(visitor, OnDataForStream(1, "This is the request body.")); + EXPECT_CALL(visitor, OnEndStream(1)); + EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(3)); + EXPECT_CALL(visitor, OnHeaderForStream(3, _, _)).Times(4); + EXPECT_CALL(visitor, OnEndHeadersForStream(3)); + EXPECT_CALL(visitor, OnEndStream(3)); + + const int64_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(static_cast<int64_t>(frames.size()), result); + + EXPECT_TRUE(adapter->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)); + + int send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS})); } // Tests the case where the response body is in the progress of being sent while // trailers are queued. -TEST(OgHttp2AdapterServerTest, ServerSubmitsTrailersWhileDataDeferred) { +TEST(OgHttp2AdapterTest, ServerSubmitsTrailersWhileDataDeferred) { DataSavingVisitor visitor; for (const bool queue_trailers : {true, false}) { OgHttp2Adapter::Options options{ @@ -2370,12 +3822,151 @@ TEST(OgHttp2AdapterServerTest, ServerSubmitsTrailersWhileDataDeferred) { send_result = adapter->Send(); EXPECT_EQ(0, send_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::HEADERS})); } } } -TEST(OgHttp2AdapterServerTest, ServerErrorWhileHandlingHeaders) { +TEST(OgHttp2AdapterTest, ClientDisobeysConnectionFlowControl) { + 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) + // 70000 bytes of data + .Data(1, std::string(16384, 'a')) + .Data(1, std::string(16384, 'a')) + .Data(1, std::string(16384, 'a')) + .Data(1, std::string(16384, 'a')) + .Data(1, std::string(4464, 'a')) + .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).Times(5); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnFrameHeader(1, 16384, DATA, 0x0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 16384)); + EXPECT_CALL(visitor, OnDataForStream(1, _)); + EXPECT_CALL(visitor, OnFrameHeader(1, 16384, DATA, 0x0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 16384)); + EXPECT_CALL(visitor, OnDataForStream(1, _)); + EXPECT_CALL(visitor, OnFrameHeader(1, 16384, DATA, 0x0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 16384)); + EXPECT_CALL(visitor, OnDataForStream(1, _)); + EXPECT_CALL(visitor, OnFrameHeader(1, 16384, DATA, 0x0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 16384)); + EXPECT_CALL(visitor, OnDataForStream(1, _)); + EXPECT_CALL(visitor, OnFrameHeader(1, 4464, DATA, 0x0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 4464)); + EXPECT_CALL(visitor, OnDataForStream(1, _)); + + const int64_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(frames.size(), static_cast<size_t>(result)); + + EXPECT_TRUE(adapter->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)); + + int send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS})); +} + +TEST(OgHttp2AdapterTest, ClientDisobeysStreamFlowControl) { + 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) + .Serialize(); + const std::string more_frames = TestFrameSequence() + // 70000 bytes of data + .Data(1, std::string(16384, 'a')) + .Data(1, std::string(16384, 'a')) + .Data(1, std::string(16384, 'a')) + .Data(1, std::string(16384, 'a')) + .Data(1, std::string(4464, 'a')) + .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).Times(5); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + + int64_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(frames.size(), static_cast<size_t>(result)); + + adapter->SubmitWindowUpdate(0, 20000); + + 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(WINDOW_UPDATE, 0, 4, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(WINDOW_UPDATE, 0, 4, 0x0, 0)); + + int send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS, + SpdyFrameType::WINDOW_UPDATE})); + + EXPECT_CALL(visitor, OnFrameHeader(1, 16384, DATA, 0x0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 16384)); + EXPECT_CALL(visitor, OnDataForStream(1, _)); + EXPECT_CALL(visitor, OnFrameHeader(1, 16384, DATA, 0x0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 16384)); + EXPECT_CALL(visitor, OnDataForStream(1, _)); + EXPECT_CALL(visitor, OnFrameHeader(1, 16384, DATA, 0x0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 16384)); + EXPECT_CALL(visitor, OnDataForStream(1, _)); + EXPECT_CALL(visitor, OnFrameHeader(1, 16384, DATA, 0x0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 16384)); + EXPECT_CALL(visitor, OnDataForStream(1, _)); + EXPECT_CALL(visitor, OnFrameHeader(1, 4464, DATA, 0x0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 4464)); + EXPECT_CALL(visitor, OnDataForStream(1, _)); + + result = adapter->ProcessBytes(more_frames); + EXPECT_EQ(more_frames.size(), static_cast<size_t>(result)); + + EXPECT_FALSE(adapter->want_write()); +} + +TEST(OgHttp2AdapterTest, ServerErrorWhileHandlingHeaders) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; auto adapter = OgHttp2Adapter::Create(visitor, options); @@ -2408,14 +3999,7 @@ TEST(OgHttp2AdapterServerTest, ServerErrorWhileHandlingHeaders) { 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, - OnInvalidFrame(1, Http2VisitorInterface::InvalidFrameError::kHttpHeader)); - 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.")); + // Stream WINDOW_UPDATE and DATA frames are not delivered to the visitor. EXPECT_CALL(visitor, OnFrameHeader(0, 4, WINDOW_UPDATE, 0)); EXPECT_CALL(visitor, OnWindowUpdate(0, 2000)); @@ -2438,12 +4022,100 @@ TEST(OgHttp2AdapterServerTest, ServerErrorWhileHandlingHeaders) { // 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})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM})); } -TEST(OgHttp2AdapterServerTest, ServerConnectionErrorWhileHandlingHeaders) { +TEST(OgHttp2AdapterTest, ServerErrorWhileHandlingHeadersDropsFrames) { + 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.") + .Metadata(1, "This is the request metadata.") + .RstStream(1, Http2ErrorCode::CANCEL) + .WindowUpdate(0, 2000) + .Headers(3, + {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/two"}}, + /*fin=*/false) + .Metadata(3, "This is the request metadata.", + /*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()); + + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4); + EXPECT_CALL(visitor, OnHeaderForStream(1, "accept", "some bogus value!")) + .WillOnce(testing::Return(Http2VisitorInterface::HEADER_RST_STREAM)); + // Frames for the RST_STREAM-marked stream are not delivered to the visitor. + // Note: nghttp2 still delivers control frames and metadata for the stream. + EXPECT_CALL(visitor, OnFrameHeader(0, 4, WINDOW_UPDATE, 0)); + EXPECT_CALL(visitor, OnWindowUpdate(0, 2000)); + EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(3)); + EXPECT_CALL(visitor, OnHeaderForStream(3, _, _)).Times(4); + EXPECT_CALL(visitor, OnEndHeadersForStream(3)); + EXPECT_CALL(visitor, OnFrameHeader(3, _, kMetadataFrameType, 0)); + EXPECT_CALL(visitor, OnBeginMetadataForStream(3, _)); + EXPECT_CALL(visitor, OnMetadataForStream(3, "This is the re")) + .WillOnce(testing::DoAll(testing::InvokeWithoutArgs([&adapter]() { + adapter->SubmitRst( + 3, Http2ErrorCode::REFUSED_STREAM); + }), + testing::Return(true))); + // The rest of the metadata is not delivered to the visitor. + + const int64_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(frames.size(), static_cast<size_t>(result)); + + EXPECT_TRUE(adapter->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::HTTP2_NO_ERROR)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 3, 4, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 3, 4, 0x0, + static_cast<int>(Http2ErrorCode::REFUSED_STREAM))); + EXPECT_CALL(visitor, OnCloseStream(3, Http2ErrorCode::HTTP2_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({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM, SpdyFrameType::RST_STREAM})); +} + +TEST(OgHttp2AdapterTest, ServerConnectionErrorWhileHandlingHeaders) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; auto adapter = OgHttp2Adapter::Create(visitor, options); @@ -2487,29 +4159,25 @@ TEST(OgHttp2AdapterServerTest, ServerConnectionErrorWhileHandlingHeaders) { 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))); + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR)); EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0)); EXPECT_CALL(visitor, OnFrameSent(GOAWAY, 0, _, 0x0, - static_cast<int>(Http2ErrorCode::INTERNAL_ERROR))); + static_cast<int>(Http2ErrorCode::PROTOCOL_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, - spdy::SpdyFrameType::GOAWAY})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::RST_STREAM, + SpdyFrameType::GOAWAY})); } -TEST(OgHttp2AdapterServerTest, ServerErrorAfterHandlingHeaders) { +TEST(OgHttp2AdapterTest, ServerErrorAfterHandlingHeaders) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; auto adapter = OgHttp2Adapter::Create(visitor, options); @@ -2550,8 +4218,6 @@ TEST(OgHttp2AdapterServerTest, ServerErrorAfterHandlingHeaders) { 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(GOAWAY, 0, _, 0x0)); EXPECT_CALL(visitor, OnFrameSent(GOAWAY, 0, _, 0x0, @@ -2560,15 +4226,13 @@ TEST(OgHttp2AdapterServerTest, ServerErrorAfterHandlingHeaders) { int send_result = adapter->Send(); // Some bytes should have been serialized. EXPECT_EQ(0, send_result); - // SETTINGS, SETTINGS ack, and GOAWAY - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::GOAWAY})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::GOAWAY})); } // Exercises the case when a visitor chooses to reject a frame based solely on // the frame header, which is a fatal error for the connection. -TEST(OgHttp2AdapterServerTest, ServerRejectsFrameHeader) { +TEST(OgHttp2AdapterTest, ServerRejectsFrameHeader) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; auto adapter = OgHttp2Adapter::Create(visitor, options); @@ -2604,8 +4268,6 @@ TEST(OgHttp2AdapterServerTest, ServerRejectsFrameHeader) { 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(GOAWAY, 0, _, 0x0)); EXPECT_CALL(visitor, OnFrameSent(GOAWAY, 0, _, 0x0, @@ -2614,13 +4276,11 @@ TEST(OgHttp2AdapterServerTest, ServerRejectsFrameHeader) { int send_result = adapter->Send(); // Some bytes should have been serialized. EXPECT_EQ(0, send_result); - // SETTINGS, SETTINGS ack, and GOAWAY - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::GOAWAY})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::GOAWAY})); } -TEST(OgHttp2AdapterServerTest, ServerRejectsBeginningOfData) { +TEST(OgHttp2AdapterTest, ServerRejectsBeginningOfData) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; auto adapter = OgHttp2Adapter::Create(visitor, options); @@ -2669,8 +4329,6 @@ TEST(OgHttp2AdapterServerTest, ServerRejectsBeginningOfData) { 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(GOAWAY, 0, _, 0x0)); EXPECT_CALL(visitor, OnFrameSent(GOAWAY, 0, _, 0x0, @@ -2679,13 +4337,133 @@ TEST(OgHttp2AdapterServerTest, ServerRejectsBeginningOfData) { int send_result = adapter->Send(); // Some bytes should have been serialized. EXPECT_EQ(0, send_result); - // SETTINGS, SETTINGS ack, and GOAWAY. - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::GOAWAY})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::GOAWAY})); } -TEST(OgHttp2AdapterServerTest, ServerRejectsStreamData) { +TEST(OgHttp2AdapterTest, ServerReceivesTooLargeHeader) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer, + .max_header_list_bytes = 64 * 1024, + .max_header_field_size = 64 * 1024}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + // Due to configuration, the library will accept a maximum of 64kB of huffman + // encoded data per header field. + const std::string too_large_value = std::string(80 * 1024, 'q'); + const std::string frames = TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":method", "POST"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}, + {"x-toobig", too_large_value}}, + /*fin=*/true) + .Headers(3, + {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/two"}}, + /*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()); + + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, END_STREAM)); + 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, OnFrameHeader(1, _, CONTINUATION, 0)).Times(3); + EXPECT_CALL(visitor, OnFrameHeader(1, _, CONTINUATION, END_HEADERS)); + // Further header processing is skipped, as the header field is too large. + + EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, END_STREAM | END_HEADERS)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(3)); + EXPECT_CALL(visitor, OnHeaderForStream(3, _, _)).Times(4); + EXPECT_CALL(visitor, OnEndHeadersForStream(3)); + EXPECT_CALL(visitor, OnEndStream(3)); + + const int64_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(static_cast<int64_t>(frames.size()), result); + + EXPECT_TRUE(adapter->want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, ACK)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, ACK, 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::HTTP2_NO_ERROR)); + + int send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM})); +} + +TEST(OgHttp2AdapterTest, ServerReceivesInvalidAuthority) { + 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", "ex|ample.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, + OnInvalidFrame(1, Http2VisitorInterface::InvalidFrameError::kHttpHeader)); + + const int64_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(static_cast<int64_t>(frames.size()), result); + + EXPECT_TRUE(adapter->want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0x0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0x0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, 4, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 1, 4, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR)); + + int send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM})); +} + +TEST(OgHttp2AdapterTest, ServerRejectsStreamData) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; auto adapter = OgHttp2Adapter::Create(visitor, options); @@ -2734,8 +4512,6 @@ TEST(OgHttp2AdapterServerTest, ServerRejectsStreamData) { 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(GOAWAY, 0, _, 0x0)); EXPECT_CALL(visitor, OnFrameSent(GOAWAY, 0, _, 0x0, @@ -2744,10 +4520,8 @@ TEST(OgHttp2AdapterServerTest, ServerRejectsStreamData) { int send_result = adapter->Send(); // Some bytes should have been serialized. EXPECT_EQ(0, send_result); - // SETTINGS, SETTINGS ack, and GOAWAY. - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::GOAWAY})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::GOAWAY})); } // Exercises a naive mutually recursive test client and server. This test fails @@ -2812,7 +4586,61 @@ TEST(OgHttp2AdapterInteractionTest, ClientServerInteractionTest) { client_adapter->Send(); } -TEST(OgHttp2AdapterServerTest, ServerForbidsNewStreamBelowWatermark) { +TEST(OgHttp2AdapterInteractionTest, + ClientServerInteractionRepeatedHeaderNames) { + DataSavingVisitor client_visitor; + OgHttp2Adapter::Options client_options{.perspective = Perspective::kClient}; + auto client_adapter = OgHttp2Adapter::Create(client_visitor, client_options); + + const std::vector<Header> headers1 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}, + {"accept", "text/plain"}, + {"accept", "text/html"}}); + + const int32_t stream_id1 = + client_adapter->SubmitRequest(headers1, nullptr, nullptr); + ASSERT_GT(stream_id1, 0); + + EXPECT_CALL(client_visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(client_visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(client_visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); + EXPECT_CALL(client_visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); + int send_result = client_adapter->Send(); + EXPECT_EQ(0, send_result); + + DataSavingVisitor server_visitor; + OgHttp2Adapter::Options server_options{.perspective = Perspective::kServer}; + auto server_adapter = OgHttp2Adapter::Create(server_visitor, server_options); + + testing::InSequence s; + + // Client preface (empty SETTINGS) + EXPECT_CALL(server_visitor, OnFrameHeader(0, _, SETTINGS, 0)); + EXPECT_CALL(server_visitor, OnSettingsStart()); + EXPECT_CALL(server_visitor, OnSetting(_)).Times(testing::AnyNumber()); + EXPECT_CALL(server_visitor, OnSettingsEnd()); + // Stream 1 + EXPECT_CALL(server_visitor, OnFrameHeader(1, _, HEADERS, 5)); + EXPECT_CALL(server_visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(server_visitor, OnHeaderForStream(1, ":method", "GET")); + EXPECT_CALL(server_visitor, OnHeaderForStream(1, ":scheme", "http")); + EXPECT_CALL(server_visitor, + OnHeaderForStream(1, ":authority", "example.com")); + EXPECT_CALL(server_visitor, + OnHeaderForStream(1, ":path", "/this/is/request/one")); + EXPECT_CALL(server_visitor, OnHeaderForStream(1, "accept", "text/plain")); + EXPECT_CALL(server_visitor, OnHeaderForStream(1, "accept", "text/html")); + EXPECT_CALL(server_visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(server_visitor, OnEndStream(1)); + + int64_t result = server_adapter->ProcessBytes(client_visitor.data()); + EXPECT_EQ(client_visitor.data().size(), static_cast<size_t>(result)); +} + +TEST(OgHttp2AdapterTest, ServerForbidsNewStreamBelowWatermark) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; auto adapter = OgHttp2Adapter::Create(visitor, options); @@ -2856,7 +4684,7 @@ TEST(OgHttp2AdapterServerTest, ServerForbidsNewStreamBelowWatermark) { EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kInvalidNewStreamId)); const int64_t result = adapter->ProcessBytes(frames); - EXPECT_LT(result, 0); + EXPECT_EQ(static_cast<size_t>(result), frames.size()); EXPECT_EQ(3, adapter->GetHighestReceivedStreamId()); @@ -2864,8 +4692,6 @@ TEST(OgHttp2AdapterServerTest, ServerForbidsNewStreamBelowWatermark) { 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(GOAWAY, 0, _, 0x0)); EXPECT_CALL(visitor, OnFrameSent(GOAWAY, 0, _, 0x0, @@ -2874,13 +4700,11 @@ TEST(OgHttp2AdapterServerTest, ServerForbidsNewStreamBelowWatermark) { int send_result = adapter->Send(); // Some bytes should have been serialized. EXPECT_EQ(0, send_result); - // SETTINGS, SETTINGS ack, and GOAWAY. - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::GOAWAY})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::GOAWAY})); } -TEST(OgHttp2AdapterServerTest, ServerForbidsWindowUpdateOnIdleStream) { +TEST(OgHttp2AdapterTest, ServerForbidsWindowUpdateOnIdleStream) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; auto adapter = OgHttp2Adapter::Create(visitor, options); @@ -2901,16 +4725,14 @@ TEST(OgHttp2AdapterServerTest, ServerForbidsWindowUpdateOnIdleStream) { EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kWrongFrameSequence)); const int64_t result = adapter->ProcessBytes(frames); - EXPECT_LT(result, 0); + EXPECT_EQ(static_cast<size_t>(result), frames.size()); EXPECT_EQ(1, adapter->GetHighestReceivedStreamId()); EXPECT_TRUE(adapter->want_write()); - EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x0)); - EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x0, 0)); - EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); - EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + 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, @@ -2919,13 +4741,11 @@ TEST(OgHttp2AdapterServerTest, ServerForbidsWindowUpdateOnIdleStream) { int send_result = adapter->Send(); // Some bytes should have been serialized. EXPECT_EQ(0, send_result); - // SETTINGS, SETTINGS ack, and GOAWAY. - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::GOAWAY})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::GOAWAY})); } -TEST(OgHttp2AdapterServerTest, ServerForbidsDataOnIdleStream) { +TEST(OgHttp2AdapterTest, ServerForbidsDataOnIdleStream) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; auto adapter = OgHttp2Adapter::Create(visitor, options); @@ -2948,16 +4768,14 @@ TEST(OgHttp2AdapterServerTest, ServerForbidsDataOnIdleStream) { EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kWrongFrameSequence)); const int64_t result = adapter->ProcessBytes(frames); - EXPECT_LT(result, 0); + EXPECT_EQ(static_cast<size_t>(result), frames.size()); EXPECT_EQ(1, adapter->GetHighestReceivedStreamId()); EXPECT_TRUE(adapter->want_write()); - EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x0)); - EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x0, 0)); - EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); - EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + 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, @@ -2966,13 +4784,11 @@ TEST(OgHttp2AdapterServerTest, ServerForbidsDataOnIdleStream) { int send_result = adapter->Send(); // Some bytes should have been serialized. EXPECT_EQ(0, send_result); - // SETTINGS, SETTINGS ack, and GOAWAY. - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::GOAWAY})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::GOAWAY})); } -TEST(OgHttp2AdapterServerTest, ServerForbidsRstStreamOnIdleStream) { +TEST(OgHttp2AdapterTest, ServerForbidsRstStreamOnIdleStream) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; auto adapter = OgHttp2Adapter::Create(visitor, options); @@ -2996,16 +4812,14 @@ TEST(OgHttp2AdapterServerTest, ServerForbidsRstStreamOnIdleStream) { EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kWrongFrameSequence)); const int64_t result = adapter->ProcessBytes(frames); - EXPECT_LT(result, 0); + EXPECT_EQ(static_cast<size_t>(result), frames.size()); EXPECT_EQ(1, adapter->GetHighestReceivedStreamId()); EXPECT_TRUE(adapter->want_write()); - EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x0)); - EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x0, 0)); - EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); - EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + 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, @@ -3014,13 +4828,11 @@ TEST(OgHttp2AdapterServerTest, ServerForbidsRstStreamOnIdleStream) { int send_result = adapter->Send(); // Some bytes should have been serialized. EXPECT_EQ(0, send_result); - // SETTINGS, SETTINGS ack, and GOAWAY. - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::GOAWAY})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::GOAWAY})); } -TEST(OgHttp2AdapterServerTest, ServerForbidsNewStreamAboveStreamLimit) { +TEST(OgHttp2AdapterTest, ServerForbidsNewStreamAboveStreamLimit) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; auto adapter = OgHttp2Adapter::Create(visitor, options); @@ -3049,8 +4861,8 @@ TEST(OgHttp2AdapterServerTest, ServerForbidsNewStreamAboveStreamLimit) { int send_result = adapter->Send(); EXPECT_EQ(0, send_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS})); visitor.Clear(); // Let the client send a SETTINGS ack and then attempt to open more than the @@ -3083,14 +4895,13 @@ TEST(OgHttp2AdapterServerTest, ServerForbidsNewStreamAboveStreamLimit) { EXPECT_CALL( visitor, OnInvalidFrame(3, Http2VisitorInterface::InvalidFrameError::kProtocol)); - // The oghttp2 stack also signals the connection error via OnConnectionError() - // and a negative ProcessBytes() return value. + // The oghttp2 stack also signals the error via OnConnectionError(). EXPECT_CALL(visitor, OnConnectionError(Http2VisitorInterface::ConnectionError:: kExceededMaxConcurrentStreams)); const int64_t stream_result = adapter->ProcessBytes(stream_frames); - EXPECT_LT(stream_result, 0); + EXPECT_EQ(static_cast<size_t>(stream_result), stream_frames.size()); // The server should send a GOAWAY for this error, even though // OnInvalidFrame() returns true. @@ -3102,11 +4913,10 @@ TEST(OgHttp2AdapterServerTest, ServerForbidsNewStreamAboveStreamLimit) { send_result = adapter->Send(); EXPECT_EQ(0, send_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::GOAWAY})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::GOAWAY})); } -TEST(OgHttp2AdapterServerTest, - ServerRstStreamsNewStreamAboveStreamLimitBeforeAck) { +TEST(OgHttp2AdapterTest, ServerRstStreamsNewStreamAboveStreamLimitBeforeAck) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; auto adapter = OgHttp2Adapter::Create(visitor, options); @@ -3135,8 +4945,8 @@ TEST(OgHttp2AdapterServerTest, int send_result = adapter->Send(); EXPECT_EQ(0, send_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, - spdy::SpdyFrameType::SETTINGS})); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS})); visitor.Clear(); // Let the client avoid sending a SETTINGS ack and attempt to open more than @@ -3180,7 +4990,819 @@ TEST(OgHttp2AdapterServerTest, send_result = adapter->Send(); EXPECT_EQ(0, send_result); - EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::RST_STREAM})); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::RST_STREAM})); +} + +TEST(OgHttp2AdapterTest, ServerForbidsProtocolPseudoheaderBeforeAck) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer, + .allow_extended_connect = false}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + const std::string initial_frames = + TestFrameSequence().ClientPreface().Serialize(); + + testing::InSequence s; + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + const int64_t initial_result = adapter->ProcessBytes(initial_frames); + EXPECT_EQ(static_cast<size_t>(initial_result), initial_frames.size()); + + // The client attempts to send a CONNECT request with the `:protocol` + // pseudoheader before receiving the server's SETTINGS frame. + const std::string stream1_frames = + TestFrameSequence() + .Headers(1, + {{":method", "CONNECT"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}, + {":protocol", "websocket"}}, + /*fin=*/true) + .Serialize(); + + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 0x5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(5); + EXPECT_CALL(visitor, + OnInvalidFrame( + 1, Http2VisitorInterface::InvalidFrameError::kHttpMessaging)); + + int64_t stream_result = adapter->ProcessBytes(stream1_frames); + EXPECT_EQ(static_cast<size_t>(stream_result), stream1_frames.size()); + + // Server initial SETTINGS and SETTINGS ack. + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); + + // The server sends a RST_STREAM for the offending stream. + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 1, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR)); + + // Server settings with ENABLE_CONNECT_PROTOCOL. + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 6, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 6, 0x0, 0)); + + adapter->SubmitSettings({{ENABLE_CONNECT_PROTOCOL, 1}}); + int send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT( + visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM, SpdyFrameType::SETTINGS})); + visitor.Clear(); + + // The client attempts to send a CONNECT request with the `:protocol` + // pseudoheader before acking the server's SETTINGS frame. + const std::string stream3_frames = + TestFrameSequence() + .Headers(3, + {{":method", "CONNECT"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/two"}, + {":protocol", "websocket"}}, + /*fin=*/true) + .Serialize(); + + // After sending SETTINGS with `ENABLE_CONNECT_PROTOCOL`, oghttp2 matches + // nghttp2 in allowing this, even though the `allow_extended_connect` option + // is false. + EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 0x5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(3)); + EXPECT_CALL(visitor, OnHeaderForStream(3, _, _)).Times(5); + EXPECT_CALL(visitor, OnEndHeadersForStream(3)); + EXPECT_CALL(visitor, OnEndStream(3)); + + stream_result = adapter->ProcessBytes(stream3_frames); + EXPECT_EQ(static_cast<size_t>(stream_result), stream3_frames.size()); + + EXPECT_FALSE(adapter->want_write()); +} + +TEST(OgHttp2AdapterTest, ServerAllowsProtocolPseudoheaderAfterAck) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + adapter->SubmitSettings({{ENABLE_CONNECT_PROTOCOL, 1}}); + + const std::string initial_frames = + TestFrameSequence().ClientPreface().Serialize(); + + testing::InSequence s; + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + const int64_t initial_result = adapter->ProcessBytes(initial_frames); + EXPECT_EQ(static_cast<size_t>(initial_result), initial_frames.size()); + + // Server initial SETTINGS (with ENABLE_CONNECT_PROTOCOL) and SETTINGS ack. + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 6, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 6, 0x0, 0)); + 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); + visitor.Clear(); + + // The client attempts to send a CONNECT request with the `:protocol` + // pseudoheader after acking the server's SETTINGS frame. + const std::string stream_frames = + TestFrameSequence() + .SettingsAck() + .Headers(1, + {{":method", "CONNECT"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}, + {":protocol", "websocket"}}, + /*fin=*/true) + .Serialize(); + + EXPECT_CALL(visitor, OnFrameHeader(0, _, SETTINGS, 0x1)); + EXPECT_CALL(visitor, OnSettingsAck()); + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 0x5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(5); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnEndStream(1)); + + const int64_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(static_cast<size_t>(stream_result), stream_frames.size()); + + EXPECT_FALSE(adapter->want_write()); +} + +TEST(OgHttp2AdapterTest, SkipsSendingFramesForRejectedStream) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + const std::string initial_frames = + TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":method", "GET"}, + {":scheme", "http"}, + {":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()); + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 0x5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnEndStream(1)); + + const int64_t initial_result = adapter->ProcessBytes(initial_frames); + EXPECT_EQ(static_cast<size_t>(initial_result), initial_frames.size()); + + auto body = absl::make_unique<TestDataFrameSource>(visitor, true); + body->AppendPayload("Here is some data, which will be completely ignored!"); + + int submit_result = adapter->SubmitResponse( + 1, ToHeaders({{":status", "200"}}), std::move(body)); + ASSERT_EQ(0, submit_result); + + auto source = absl::make_unique<TestMetadataSource>(ToHeaderBlock(ToHeaders( + {{"query-cost", "is too darn high"}, {"secret-sauce", "hollandaise"}}))); + adapter->SubmitMetadata(1, 16384u, std::move(source)); + + adapter->SubmitWindowUpdate(1, 1024); + adapter->SubmitRst(1, Http2ErrorCode::INTERNAL_ERROR); + + // Server initial SETTINGS and SETTINGS ack. + 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)); + + // The server sends a RST_STREAM for the offending stream. + // The response HEADERS, DATA and WINDOW_UPDATE are all ignored. + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 1, _, 0x0, + static_cast<int>(Http2ErrorCode::INTERNAL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR)); + + int send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM})); +} + +TEST(OgHttpAdapterServerTest, ServerStartsShutdown) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + EXPECT_FALSE(adapter->want_write()); + + adapter->SubmitShutdownNotice(); + EXPECT_TRUE(adapter->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 = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::GOAWAY})); +} + +TEST(OgHttp2AdapterTest, ServerStartsShutdownAfterGoaway) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + EXPECT_FALSE(adapter->want_write()); + + adapter->SubmitGoAway(1, Http2ErrorCode::HTTP2_NO_ERROR, + "and don't come back!"); + EXPECT_TRUE(adapter->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 = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::GOAWAY})); + + // No-op, since a GOAWAY has previously been enqueued. + adapter->SubmitShutdownNotice(); + EXPECT_FALSE(adapter->want_write()); +} + +// Verifies that a connection-level processing error results in repeatedly +// returning a positive value for ProcessBytes() to mark all data as consumed +// when the blackhole option is enabled. +TEST(OgHttp2AdapterTest, ConnectionErrorWithBlackholingData) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer, + .blackhole_data_on_connection_error = true}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + const std::string frames = + TestFrameSequence().ClientPreface().WindowUpdate(1, 42).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, 4, WINDOW_UPDATE, 0)); + EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kWrongFrameSequence)); + + const int64_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(static_cast<size_t>(result), frames.size()); + + // Ask the connection to process more bytes. Because the option is enabled, + // the data should be marked as consumed. + const std::string next_frame = TestFrameSequence().Ping(42).Serialize(); + const int64_t next_result = adapter->ProcessBytes(next_frame); + EXPECT_EQ(static_cast<size_t>(next_result), next_frame.size()); +} + +// Verifies that a connection-level processing error results in returning a +// negative value for ProcessBytes() when the blackhole option is disabled. +TEST(OgHttp2AdapterTest, ConnectionErrorWithoutBlackholingData) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer, + .blackhole_data_on_connection_error = false}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + const std::string frames = + TestFrameSequence().ClientPreface().WindowUpdate(1, 42).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, 4, WINDOW_UPDATE, 0)); + EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kWrongFrameSequence)); + + const int64_t result = adapter->ProcessBytes(frames); + EXPECT_LT(result, 0); + + // Ask the connection to process more bytes. Because the option is disabled, + // ProcessBytes() should continue to return an error. + const std::string next_frame = TestFrameSequence().Ping(42).Serialize(); + const int64_t next_result = adapter->ProcessBytes(next_frame); + EXPECT_LT(next_result, 0); +} + +TEST(OgHttp2AdapterTest, ServerDoesNotSendFramesAfterImmediateGoAway) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + // Submit a custom initial SETTINGS frame with one setting. + adapter->SubmitSettings({{HEADER_TABLE_SIZE, 100u}}); + + 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, 0x5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnEndStream(1)); + + const int64_t read_result = adapter->ProcessBytes(frames); + EXPECT_EQ(static_cast<size_t>(read_result), frames.size()); + + // Submit a response for the stream. + auto body = absl::make_unique<TestDataFrameSource>(visitor, true); + body->AppendPayload("This data is doomed to never be written."); + int submit_result = adapter->SubmitResponse( + 1, ToHeaders({{":status", "200"}}), std::move(body)); + ASSERT_EQ(0, submit_result); + + // Submit a WINDOW_UPDATE frame. + adapter->SubmitWindowUpdate(kConnectionStreamId, 42); + + // Submit another SETTINGS frame. + adapter->SubmitSettings({}); + + // Submit some metadata. + auto source = absl::make_unique<TestMetadataSource>(ToHeaderBlock(ToHeaders( + {{"query-cost", "is too darn high"}, {"secret-sauce", "hollandaise"}}))); + adapter->SubmitMetadata(1, 16384u, std::move(source)); + + EXPECT_TRUE(adapter->want_write()); + + // Trigger a connection error. Only the response headers will be written. + const std::string connection_error_frames = + TestFrameSequence().WindowUpdate(3, 42).Serialize(); + + EXPECT_CALL(visitor, OnFrameHeader(3, 4, WINDOW_UPDATE, 0)); + EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kWrongFrameSequence)); + + const int64_t result = adapter->ProcessBytes(connection_error_frames); + EXPECT_EQ(static_cast<size_t>(result), connection_error_frames.size()); + + EXPECT_TRUE(adapter->want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 6, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 6, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(GOAWAY, 0, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + + int send_result = adapter->Send(); + // Some bytes should have been serialized. + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::GOAWAY})); + visitor.Clear(); + + // Try to submit more frames for writing. They should not be written. + adapter->SubmitPing(42); + // TODO(diannahu): Enable the below expectation. + // EXPECT_FALSE(adapter->want_write()); + send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), testing::IsEmpty()); +} + +TEST(OgHttp2AdapterTest, ServerHandlesContentLength) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + testing::InSequence s; + + const std::string stream_frames = + TestFrameSequence() + .ClientPreface() + .Headers(1, {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}, + {"content-length", "2"}}) + .Data(1, "hi", /*fin=*/true) + .Headers(3, + {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/three"}, + {"content-length", "nan"}}, + /*fin=*/true) + .Serialize(); + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + // Stream 1: content-length is correct + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(5); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnFrameHeader(1, _, DATA, 1)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 2)); + EXPECT_CALL(visitor, OnDataForStream(1, "hi")); + EXPECT_CALL(visitor, OnEndStream(1)); + + // Stream 3: content-length is not a number + EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(3)); + EXPECT_CALL(visitor, OnHeaderForStream(3, _, _)).Times(4); + EXPECT_CALL( + visitor, + OnInvalidFrame(3, Http2VisitorInterface::InvalidFrameError::kHttpHeader)); + + const int64_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); + + 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(RST_STREAM, 3, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 3, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(3, Http2ErrorCode::HTTP2_NO_ERROR)); + + EXPECT_TRUE(adapter->want_write()); + int result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM})); +} + +TEST(OgHttp2AdapterTest, ServerHandlesContentLengthMismatch) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + testing::InSequence s; + + const std::string stream_frames = + TestFrameSequence() + .ClientPreface() + .Headers(1, {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/two"}, + {"content-length", "2"}}) + .Data(1, "h", /*fin=*/true) + .Headers(3, {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/three"}, + {"content-length", "2"}}) + .Data(3, "howdy", /*fin=*/true) + .Headers(5, + {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/four"}, + {"content-length", "2"}}, + /*fin=*/true) + .Headers(7, + {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/four"}, + {"content-length", "2"}}, + /*fin=*/false) + .Data(7, "h", /*fin=*/false) + .Headers(7, {{"extra-info", "Trailers with content-length mismatch"}}, + /*fin=*/true) + .Serialize(); + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + // Stream 1: content-length is larger than actual data + // All data is delivered to the visitor. Note that neither oghttp2 nor + // nghttp2 delivers OnInvalidFrame(). + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(5); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnFrameHeader(1, _, DATA, 1)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 1)); + EXPECT_CALL(visitor, OnDataForStream(1, "h")); + + // Stream 3: content-length is smaller than actual data + // The beginning of data is delivered to the visitor, but not the actual data. + // Again, neither oghttp2 nor nghttp2 delivers OnInvalidFrame(). + EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(3)); + EXPECT_CALL(visitor, OnHeaderForStream(3, _, _)).Times(5); + EXPECT_CALL(visitor, OnEndHeadersForStream(3)); + EXPECT_CALL(visitor, OnFrameHeader(3, _, DATA, 1)); + EXPECT_CALL(visitor, OnBeginDataForStream(3, 5)); + + // Stream 5: content-length is invalid and HEADERS ends the stream + // Only oghttp2 invokes OnEndHeadersForStream(). Only nghttp2 invokes + // OnInvalidFrame(). + EXPECT_CALL(visitor, OnFrameHeader(5, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(5)); + EXPECT_CALL(visitor, OnHeaderForStream(5, _, _)).Times(5); + EXPECT_CALL(visitor, OnEndHeadersForStream(5)); + + // Stream 7: content-length is invalid and trailers end the stream + // Only oghttp2 invokes OnEndHeadersForStream(). Only nghttp2 invokes + // OnInvalidFrame(). + EXPECT_CALL(visitor, OnFrameHeader(7, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(7)); + EXPECT_CALL(visitor, OnHeaderForStream(7, _, _)).Times(5); + EXPECT_CALL(visitor, OnEndHeadersForStream(7)); + EXPECT_CALL(visitor, OnFrameHeader(7, _, DATA, 0)); + EXPECT_CALL(visitor, OnBeginDataForStream(7, 1)); + EXPECT_CALL(visitor, OnDataForStream(7, "h")); + EXPECT_CALL(visitor, OnFrameHeader(7, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(7)); + EXPECT_CALL(visitor, OnHeaderForStream(7, _, _)); + EXPECT_CALL(visitor, OnEndHeadersForStream(7)); + + const int64_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); + + 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(RST_STREAM, 1, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 1, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 3, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 3, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(3, Http2ErrorCode::HTTP2_NO_ERROR)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 5, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 5, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(5, Http2ErrorCode::HTTP2_NO_ERROR)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 7, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 7, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(7, Http2ErrorCode::HTTP2_NO_ERROR)); + + EXPECT_TRUE(adapter->want_write()); + int result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT( + visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM, SpdyFrameType::RST_STREAM, + SpdyFrameType::RST_STREAM, SpdyFrameType::RST_STREAM})); +} + +TEST(OgHttp2AdapterTest, ServerHandlesAsteriskPathForOptions) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + testing::InSequence s; + + const std::string stream_frames = TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":scheme", "https"}, + {":authority", "example.com"}, + {":path", "*"}, + {":method", "OPTIONS"}}, + /*fin=*/true) + .Serialize(); + + // 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, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnEndStream(1)); + + const int64_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); + + 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_TRUE(adapter->want_write()); + int result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS})); +} + +TEST(OgHttp2AdapterTest, ServerHandlesInvalidPath) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + testing::InSequence s; + + const std::string stream_frames = + TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":scheme", "https"}, + {":authority", "example.com"}, + {":path", "*"}, + {":method", "GET"}}, + /*fin=*/true) + .Headers(3, + {{":scheme", "https"}, + {":authority", "example.com"}, + {":path", "other/non/slash/starter"}, + {":method", "GET"}}, + /*fin=*/true) + .Headers(5, + {{":scheme", "https"}, + {":authority", "example.com"}, + {":path", ""}, + {":method", "GET"}}, + /*fin=*/true) + .Serialize(); + + // 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, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4); + EXPECT_CALL(visitor, + OnInvalidFrame( + 1, Http2VisitorInterface::InvalidFrameError::kHttpMessaging)); + + EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(3)); + EXPECT_CALL(visitor, OnHeaderForStream(3, _, _)).Times(4); + EXPECT_CALL(visitor, + OnInvalidFrame( + 3, Http2VisitorInterface::InvalidFrameError::kHttpMessaging)); + + EXPECT_CALL(visitor, OnFrameHeader(5, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(5)); + EXPECT_CALL(visitor, OnHeaderForStream(5, _, _)).Times(2); + EXPECT_CALL( + visitor, + OnInvalidFrame(5, Http2VisitorInterface::InvalidFrameError::kHttpHeader)); + + const int64_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); + + 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(RST_STREAM, 1, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 1, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 3, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 3, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(3, Http2ErrorCode::HTTP2_NO_ERROR)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 5, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 5, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(5, Http2ErrorCode::HTTP2_NO_ERROR)); + + EXPECT_TRUE(adapter->want_write()); + int result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT( + visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS, + SpdyFrameType::RST_STREAM, SpdyFrameType::RST_STREAM, + SpdyFrameType::RST_STREAM})); +} + +TEST(OgHttp2AdapterTest, ServerHandlesTeHeader) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + testing::InSequence s; + + const std::string stream_frames = TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/"}, + {":method", "GET"}, + {"te", "trailers"}}, + /*fin=*/true) + .Headers(3, + {{":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/"}, + {":method", "GET"}, + {"te", "trailers, deflate"}}, + /*fin=*/true) + .Serialize(); + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + // Stream 1: TE: trailers should be allowed. + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(5); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnEndStream(1)); + + // Stream 3: TE: <non-trailers> should be rejected. + EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(3)); + EXPECT_CALL(visitor, OnHeaderForStream(3, _, _)).Times(4); + EXPECT_CALL( + visitor, + OnInvalidFrame(3, Http2VisitorInterface::InvalidFrameError::kHttpHeader)); + + const int64_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); + + 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(RST_STREAM, 3, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 3, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(3, Http2ErrorCode::HTTP2_NO_ERROR)); + + EXPECT_TRUE(adapter->want_write()); + int result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS, + 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 645a38d9c78..773199ca161 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,10 +1,11 @@ #include "http2/adapter/oghttp2_session.h" -#include <tuple> +#include <cstdint> #include <utility> #include "absl/memory/memory.h" #include "absl/strings/escaping.h" +#include "http2/adapter/header_validator.h" #include "http2/adapter/http2_protocol.h" #include "http2/adapter/http2_util.h" #include "http2/adapter/http2_visitor_interface.h" @@ -33,6 +34,9 @@ const uint32_t kMaxAllowedMetadataFrameSize = 65536u; const uint32_t kDefaultHpackTableCapacity = 4096u; const uint32_t kMaximumHpackTableCapacity = 65536u; +// Corresponds to NGHTTP2_ERR_CALLBACK_FAILURE. +const int kSendError = -902; + // TODO(birenroy): Consider incorporating spdy::FlagsSerializionVisitor here. class FrameAttributeCollector : public spdy::SpdyFrameVisitor { public: @@ -204,17 +208,37 @@ uint32_t HpackCapacityBound(const OgHttp2Session::Options& o) { kMaximumHpackTableCapacity); } +bool IsNonAckSettings(const spdy::SpdyFrameIR& frame) { + return frame.frame_type() == spdy::SpdyFrameType::SETTINGS && + !reinterpret_cast<const SpdySettingsIR&>(frame).is_ack(); +} + } // namespace void OgHttp2Session::PassthroughHeadersHandler::OnHeaderBlockStart() { result_ = Http2VisitorInterface::HEADER_OK; const bool status = visitor_.OnBeginHeadersForStream(stream_id_); if (!status) { + QUICHE_VLOG(1) + << "Visitor rejected header block, returning HEADER_CONNECTION_ERROR"; result_ = Http2VisitorInterface::HEADER_CONNECTION_ERROR; } validator_.StartHeaderBlock(); } +Http2VisitorInterface::OnHeaderResult InterpretHeaderStatus( + HeaderValidator::HeaderStatus status) { + switch (status) { + case HeaderValidator::HEADER_OK: + return Http2VisitorInterface::HEADER_OK; + case HeaderValidator::HEADER_FIELD_INVALID: + return Http2VisitorInterface::HEADER_FIELD_INVALID; + case HeaderValidator::HEADER_FIELD_TOO_LONG: + return Http2VisitorInterface::HEADER_RST_STREAM; + } + return Http2VisitorInterface::HEADER_CONNECTION_ERROR; +} + void OgHttp2Session::PassthroughHeadersHandler::OnHeader( absl::string_view key, absl::string_view value) { @@ -222,15 +246,12 @@ void OgHttp2Session::PassthroughHeadersHandler::OnHeader( QUICHE_VLOG(2) << "Early return; status not HEADER_OK"; return; } - const auto validation_result = validator_.ValidateSingleHeader(key, value); - if (validation_result == HeaderValidator::HEADER_VALUE_INVALID_STATUS) { - QUICHE_VLOG(2) << "RST_STREAM: invalid status found"; - result_ = Http2VisitorInterface::HEADER_HTTP_MESSAGING; - return; - } else if (validation_result != HeaderValidator::HEADER_OK) { - QUICHE_VLOG(2) << "RST_STREAM: invalid header found"; - // TODO(birenroy): consider updating this to return HEADER_HTTP_MESSAGING. - result_ = Http2VisitorInterface::HEADER_RST_STREAM; + const HeaderValidator::HeaderStatus validation_result = + validator_.ValidateSingleHeader(key, value); + if (validation_result != HeaderValidator::HEADER_OK) { + QUICHE_VLOG(2) << "Header validation failed with result " + << static_cast<int>(validation_result); + result_ = InterpretHeaderStatus(validation_result); return; } result_ = visitor_.OnHeaderForStream(stream_id_, key, value); @@ -241,17 +262,20 @@ void OgHttp2Session::PassthroughHeadersHandler::OnHeaderBlockEnd( size_t /* compressed_header_bytes */) { if (result_ == Http2VisitorInterface::HEADER_OK) { if (!validator_.FinishHeaderBlock(type_)) { - result_ = Http2VisitorInterface::HEADER_RST_STREAM; + QUICHE_VLOG(1) << "FinishHeaderBlock returned false; returning " + "HEADER_HTTP_MESSAGING"; + result_ = Http2VisitorInterface::HEADER_HTTP_MESSAGING; } } if (frame_contains_fin_ && IsResponse(type_) && StatusIs1xx(status_header())) { - // Unexpected end of stream without final headers. + QUICHE_VLOG(1) << "Unexpected end of stream without final headers"; result_ = Http2VisitorInterface::HEADER_HTTP_MESSAGING; } if (result_ == Http2VisitorInterface::HEADER_OK) { const bool result = visitor_.OnEndHeadersForStream(stream_id_); if (!result) { + session_.fatal_visitor_callback_failure_ = true; session_.decoder_.StopProcessing(); } } else { @@ -260,6 +284,40 @@ void OgHttp2Session::PassthroughHeadersHandler::OnHeaderBlockEnd( frame_contains_fin_ = false; } +// TODO(diannahu): Add checks for other response codes and request methods. +bool OgHttp2Session::PassthroughHeadersHandler::CanReceiveBody() const { + switch (header_type()) { + case HeaderType::REQUEST_TRAILER: + case HeaderType::RESPONSE_TRAILER: + case HeaderType::RESPONSE_100: + return false; + case HeaderType::RESPONSE: + // 304 responses should not have a body: + // https://httpwg.org/specs/rfc7230.html#rfc.section.3.3.2 + return status_header() != "304"; + case HeaderType::REQUEST: + return true; + } + return true; +} + +// A visitor that extracts an int64_t from each type of a ProcessBytesResult. +struct OgHttp2Session::ProcessBytesResultVisitor { + int64_t operator()(const int64_t bytes) const { return bytes; } + + int64_t operator()(const ProcessBytesError error) const { + switch (error) { + case ProcessBytesError::kUnspecified: + return -1; + case ProcessBytesError::kInvalidConnectionPreface: + return -903; // NGHTTP2_ERR_BAD_CLIENT_MAGIC + case ProcessBytesError::kVisitorCallbackFailed: + return -902; // NGHTTP2_ERR_CALLBACK_FAILURE + } + return -1; + } +}; + OgHttp2Session::OgHttp2Session(Http2VisitorInterface& visitor, Options options) : visitor_(visitor), event_forwarder_([this]() { return !latched_error_; }, *this), @@ -279,10 +337,21 @@ OgHttp2Session::OgHttp2Session(Http2VisitorInterface& visitor, Options options) options_(options) { decoder_.set_visitor(&receive_logger_); decoder_.set_extension_visitor(this); - if (options_.perspective == Perspective::kServer) { + if (options_.max_header_list_bytes) { + // Limit buffering of encoded HPACK data to 2x the decoded limit. + decoder_.GetHpackDecoder()->set_max_decode_buffer_size_bytes( + 2 * *options_.max_header_list_bytes); + // Limit the total bytes accepted for HPACK decoding to 4x the limit. + decoder_.GetHpackDecoder()->set_max_header_block_bytes( + 4 * *options_.max_header_list_bytes); + } + if (IsServerSession()) { remaining_preface_ = {spdy::kHttp2ConnectionHeaderPrefix, spdy::kHttp2ConnectionHeaderPrefixSize}; } + if (options_.max_header_field_size.has_value()) { + headers_handler_.SetMaxFieldSize(options_.max_header_field_size.value()); + } } OgHttp2Session::~OgHttp2Session() {} @@ -366,6 +435,11 @@ int OgHttp2Session::GetHpackDecoderSizeLimit() const { int64_t OgHttp2Session::ProcessBytes(absl::string_view bytes) { QUICHE_VLOG(2) << TracePerspectiveAsString(options_.perspective) << " processing [" << absl::CEscape(bytes) << "]"; + return absl::visit(ProcessBytesResultVisitor(), ProcessBytesImpl(bytes)); +} + +absl::variant<int64_t, OgHttp2Session::ProcessBytesError> +OgHttp2Session::ProcessBytesImpl(absl::string_view bytes) { if (processing_bytes_) { QUICHE_VLOG(1) << "Returning early; already processing bytes."; return 0; @@ -373,6 +447,10 @@ int64_t OgHttp2Session::ProcessBytes(absl::string_view bytes) { processing_bytes_ = true; RunOnExit r{[this]() { processing_bytes_ = false; }}; + if (options_.blackhole_data_on_connection_error && latched_error_) { + return static_cast<int64_t>(bytes.size()); + } + int64_t preface_consumed = 0; if (!remaining_preface_.empty()) { QUICHE_VLOG(2) << "Preface bytes remaining: " << remaining_preface_.size(); @@ -385,25 +463,33 @@ int64_t OgHttp2Session::ProcessBytes(absl::string_view bytes) { << absl::CEscape(bytes) << "]"; LatchErrorAndNotify(Http2ErrorCode::PROTOCOL_ERROR, ConnectionError::kInvalidConnectionPreface); - return -1; + return ProcessBytesError::kInvalidConnectionPreface; } remaining_preface_.remove_prefix(min_size); bytes.remove_prefix(min_size); if (!remaining_preface_.empty()) { QUICHE_VLOG(2) << "Preface bytes remaining: " << remaining_preface_.size(); - return min_size; + return static_cast<int64_t>(min_size); } preface_consumed = min_size; } int64_t result = decoder_.ProcessInput(bytes.data(), bytes.size()); - if (latched_error_) { + QUICHE_VLOG(2) << "ProcessBytes result: " << result; + if (fatal_visitor_callback_failure_) { + QUICHE_DCHECK(latched_error_); + QUICHE_VLOG(2) << "Visitor callback failed while processing bytes."; + return ProcessBytesError::kVisitorCallbackFailed; + } + if (latched_error_ || result < 0) { QUICHE_VLOG(2) << "ProcessBytes encountered an error."; - return -1; + if (options_.blackhole_data_on_connection_error) { + return static_cast<int64_t>(bytes.size() + preface_consumed); + } else { + return ProcessBytesError::kUnspecified; + } } - const int64_t ret = result < 0 ? result : result + preface_consumed; - QUICHE_VLOG(2) << "ProcessBytes returning: " << ret; - return ret; + return result + preface_consumed; } int OgHttp2Session::Consume(Http2StreamId stream_id, size_t num_bytes) { @@ -420,7 +506,7 @@ int OgHttp2Session::Consume(Http2StreamId stream_id, size_t num_bytes) { } void OgHttp2Session::StartGracefulShutdown() { - if (options_.perspective == Perspective::kServer) { + if (IsServerSession()) { if (!queued_goaway_) { EnqueueFrame(absl::make_unique<spdy::SpdyGoAwayIR>( std::numeric_limits<int32_t>::max(), spdy::ERROR_CODE_NO_ERROR, @@ -432,9 +518,17 @@ void OgHttp2Session::StartGracefulShutdown() { } void OgHttp2Session::EnqueueFrame(std::unique_ptr<spdy::SpdyFrameIR> frame) { + if (queued_immediate_goaway_) { + // Do not allow additional frames to be enqueued after the GOAWAY. + return; + } + RunOnExit r; if (frame->frame_type() == spdy::SpdyFrameType::GOAWAY) { queued_goaway_ = true; + if (latched_error_) { + PrepareForImmediateGoAway(); + } } else if (frame->fin() || frame->frame_type() == spdy::SpdyFrameType::RST_STREAM) { auto iter = stream_map_.find(frame->stream_id()); @@ -442,10 +536,8 @@ void OgHttp2Session::EnqueueFrame(std::unique_ptr<spdy::SpdyFrameIR> frame) { iter->second.half_closed_local = true; } if (frame->frame_type() == spdy::SpdyFrameType::RST_STREAM) { + // TODO(diannahu): Condition on existence in the stream map? streams_reset_.insert(frame->stream_id()); - } else if (iter != stream_map_.end()) { - // Enqueue RST_STREAM NO_ERROR if appropriate. - r.emplace([this, iter]() { MaybeFinWithRstStream(iter); }); } } if (frame->stream_id() != 0) { @@ -466,17 +558,26 @@ int OgHttp2Session::Send() { sending_ = true; RunOnExit r{[this]() { sending_ = false; }}; + if (fatal_send_error_) { + return kSendError; + } + MaybeSetupPreface(); SendResult continue_writing = SendQueuedFrames(); + if (queued_immediate_goaway_) { + // If an immediate GOAWAY was queued, then the above flush either sent the + // GOAWAY or buffered it to be sent on the next successful flush. In either + // case, return early here to avoid sending other frames. + return InterpretSendResult(continue_writing); + } while (continue_writing == SendResult::SEND_OK && !connection_metadata_.empty()) { continue_writing = SendMetadata(0, connection_metadata_); } // Wake streams for writes. - while (continue_writing == SendResult::SEND_OK && - write_scheduler_.HasReadyStreams() && connection_send_window_ > 0) { - const Http2StreamId stream_id = write_scheduler_.PopNextReadyStream(); + while (continue_writing == SendResult::SEND_OK && HasReadyStream()) { + const Http2StreamId stream_id = GetNextReadyStream(); // TODO(birenroy): Add a return value to indicate write blockage, so streams // aren't woken unnecessarily. QUICHE_VLOG(1) << "Waking stream " << stream_id << " for writes."; @@ -485,7 +586,38 @@ int OgHttp2Session::Send() { if (continue_writing == SendResult::SEND_OK) { continue_writing = SendQueuedFrames(); } - return continue_writing == SendResult::SEND_ERROR ? -1 : 0; + return InterpretSendResult(continue_writing); +} + +int OgHttp2Session::InterpretSendResult(SendResult result) { + if (result == SendResult::SEND_ERROR) { + fatal_send_error_ = true; + return kSendError; + } else { + return 0; + } +} + +bool OgHttp2Session::HasReadyStream() const { + return !metadata_ready_.empty() || !trailers_ready_.empty() || + (write_scheduler_.HasReadyStreams() && connection_send_window_ > 0); +} + +Http2StreamId OgHttp2Session::GetNextReadyStream() { + QUICHE_DCHECK(HasReadyStream()); + if (!metadata_ready_.empty()) { + const Http2StreamId stream_id = *metadata_ready_.begin(); + // WriteForStream() will re-mark the stream as ready, if necessary. + write_scheduler_.MarkStreamNotReady(stream_id); + return stream_id; + } + if (!trailers_ready_.empty()) { + const Http2StreamId stream_id = *trailers_ready_.begin(); + // WriteForStream() will re-mark the stream as ready, if necessary. + write_scheduler_.MarkStreamNotReady(stream_id); + return stream_id; + } + return write_scheduler_.PopNextReadyStream(); } OgHttp2Session::SendResult OgHttp2Session::MaybeSendBufferedData() { @@ -516,6 +648,21 @@ OgHttp2Session::SendResult OgHttp2Session::SendQueuedFrames() { const auto& frame_ptr = frames_.front(); FrameAttributeCollector c; frame_ptr->Visit(&c); + + // DATA frames should never be queued. + QUICHE_DCHECK_NE(c.frame_type(), 0); + + const bool stream_reset = + c.stream_id() != 0 && streams_reset_.count(c.stream_id()) > 0; + if (stream_reset && + c.frame_type() != static_cast<uint8_t>(FrameType::RST_STREAM)) { + // The stream has been reset, so any other remaining frames can be + // skipped. + // TODO(birenroy): inform the visitor of frames that are skipped. + DecrementQueuedFrameCount(c.stream_id(), c.frame_type()); + frames_.pop_front(); + continue; + } // Frames can't accurately report their own length; the actual serialized // length must be used instead. spdy::SpdySerializedFrame frame = framer_.SerializeFrame(*frame_ptr); @@ -532,10 +679,16 @@ OgHttp2Session::SendResult OgHttp2Session::SendQueuedFrames() { // Write blocked. return SendResult::SEND_BLOCKED; } else { - AfterFrameSent(c.frame_type(), c.stream_id(), frame_payload_length, - c.flags(), c.error_code()); - frames_.pop_front(); + + const bool ok = + AfterFrameSent(c.frame_type(), c.stream_id(), frame_payload_length, + c.flags(), c.error_code()); + if (!ok) { + LatchErrorAndNotify(Http2ErrorCode::INTERNAL_ERROR, + ConnectionError::kSendError); + return SendResult::SEND_ERROR; + } if (static_cast<size_t>(result) < frame.size()) { // The frame was partially written, so the rest must be buffered. buffered_data_.append(frame.data() + result, frame.size() - result); @@ -546,30 +699,50 @@ OgHttp2Session::SendResult OgHttp2Session::SendQueuedFrames() { return SendResult::SEND_OK; } -void OgHttp2Session::AfterFrameSent(uint8_t frame_type, uint32_t stream_id, +bool OgHttp2Session::AfterFrameSent(uint8_t frame_type_int, uint32_t stream_id, size_t payload_length, uint8_t flags, uint32_t error_code) { - visitor_.OnFrameSent(frame_type, stream_id, payload_length, flags, - error_code); + const FrameType frame_type = static_cast<FrameType>(frame_type_int); + int result = visitor_.OnFrameSent(frame_type_int, stream_id, payload_length, + flags, error_code); + if (result < 0) { + return false; + } if (stream_id == 0) { - const bool is_settings_ack = - static_cast<FrameType>(frame_type) == FrameType::SETTINGS && - (flags & 0x01); - if (is_settings_ack && encoder_header_table_capacity_when_acking_) { - framer_.UpdateHeaderEncoderTableSize( - encoder_header_table_capacity_when_acking_.value()); - encoder_header_table_capacity_when_acking_ = absl::nullopt; + if (frame_type == FrameType::SETTINGS) { + const bool is_settings_ack = (flags & 0x01); + if (is_settings_ack && encoder_header_table_capacity_when_acking_) { + framer_.UpdateHeaderEncoderTableSize( + encoder_header_table_capacity_when_acking_.value()); + encoder_header_table_capacity_when_acking_ = absl::nullopt; + } else if (!is_settings_ack) { + sent_non_ack_settings_ = true; + } } - return; - } - auto iter = queued_frames_.find(stream_id); - if (frame_type != 0) { - --iter->second; + return true; } - if (iter->second == 0) { - // TODO(birenroy): Consider passing through `error_code` here. - CloseStreamIfReady(frame_type, stream_id); + + const bool contains_fin = + (frame_type == FrameType::DATA || frame_type == FrameType::HEADERS) && + (flags & 0x01) == 0x01; + auto it = stream_map_.find(stream_id); + const bool still_open_remote = + it != stream_map_.end() && !it->second.half_closed_remote; + if (contains_fin && still_open_remote && + options_.rst_stream_no_error_when_incomplete && IsServerSession()) { + // Since the peer has not yet ended the stream, this endpoint should + // send a RST_STREAM NO_ERROR. See RFC 7540 Section 8.1. + frames_.push_front(absl::make_unique<spdy::SpdyRstStreamIR>( + stream_id, spdy::SpdyErrorCode::ERROR_CODE_NO_ERROR)); + auto queued_result = queued_frames_.insert({stream_id, 1}); + if (!queued_result.second) { + ++(queued_result.first->second); + } + it->second.half_closed_remote = true; } + + DecrementQueuedFrameCount(stream_id, frame_type_int); + return true; } OgHttp2Session::SendResult OgHttp2Session::WriteForStream( @@ -581,14 +754,28 @@ OgHttp2Session::SendResult OgHttp2Session::WriteForStream( return SendResult::SEND_OK; } StreamState& state = it->second; + auto reset_it = streams_reset_.find(stream_id); + if (reset_it != streams_reset_.end()) { + // The stream has been reset; there's no point in sending DATA or trailing + // HEADERS. + state.outbound_body = nullptr; + state.trailers = nullptr; + state.outbound_metadata.clear(); + return SendResult::SEND_OK; + } SendResult connection_can_write = SendResult::SEND_OK; if (!state.outbound_metadata.empty()) { connection_can_write = SendMetadata(stream_id, state.outbound_metadata); } - if (state.outbound_body == nullptr) { + if (state.outbound_body == nullptr || + (!options_.trailers_require_end_data && state.data_deferred)) { // No data to send, but there might be trailers. if (state.trailers != nullptr) { + // Trailers will include END_STREAM, so the data source can be discarded. + // Since data_deferred is true, there is no data waiting to be flushed for + // this stream. + state.outbound_body = nullptr; auto block_ptr = std::move(state.trailers); if (state.half_closed_local) { QUICHE_LOG(ERROR) << "Sent fin; can't send trailers."; @@ -603,9 +790,7 @@ OgHttp2Session::SendResult OgHttp2Session::WriteForStream( static_cast<int32_t>(max_frame_payload_)}); while (connection_can_write == SendResult::SEND_OK && available_window > 0 && state.outbound_body != nullptr && !state.data_deferred) { - int64_t length; - bool end_data; - std::tie(length, end_data) = + auto [length, end_data] = state.outbound_body->SelectPayloadLength(available_window); QUICHE_VLOG(2) << "WriteForStream | length: " << length << " end_data: " << end_data @@ -646,7 +831,13 @@ OgHttp2Session::SendResult OgHttp2Session::WriteForStream( state.half_closed_local = true; MaybeFinWithRstStream(it); } - AfterFrameSent(/* DATA */ 0, stream_id, length, fin ? 0x1 : 0x0, 0); + const bool ok = + AfterFrameSent(/* DATA */ 0, stream_id, length, fin ? 0x1 : 0x0, 0); + if (!ok) { + LatchErrorAndNotify(Http2ErrorCode::INTERNAL_ERROR, + ConnectionError::kSendError); + return SendResult::SEND_ERROR; + } if (!stream_map_.contains(stream_id)) { // Note: the stream may have been closed if `fin` is true. break; @@ -679,7 +870,8 @@ OgHttp2Session::SendResult OgHttp2Session::WriteForStream( if (connection_can_write != SendResult::SEND_OK) { return connection_can_write; } - return available_window <= 0 ? SendResult::SEND_BLOCKED : SendResult::SEND_OK; + return connection_send_window_ <= 0 ? SendResult::SEND_BLOCKED + : SendResult::SEND_OK; } OgHttp2Session::SendResult OgHttp2Session::SendMetadata( @@ -690,9 +882,7 @@ OgHttp2Session::SendResult OgHttp2Session::SendMetadata( while (!sequence.empty()) { MetadataSource& source = *sequence.front(); - int64_t written; - bool end_metadata; - std::tie(written, end_metadata) = + auto [written, end_metadata] = source.Pack(payload_buffer.get(), max_payload_size); if (written < 0) { // Did not touch the connection, so perhaps writes are still possible. @@ -706,6 +896,7 @@ OgHttp2Session::SendResult OgHttp2Session::SendMetadata( std::string(payload))); if (end_metadata) { sequence.erase(sequence.begin()); + metadata_ready_.erase(stream_id); } } return SendQueuedFrames(); @@ -775,11 +966,8 @@ int OgHttp2Session::SubmitTrailer(Http2StreamId stream_id, // Save trailers so they can be written once data is done. state.trailers = absl::make_unique<spdy::SpdyHeaderBlock>(ToHeaderBlock(trailers)); - if (!options_.trailers_require_end_data) { - iter->second.data_deferred = false; - } - if (!iter->second.data_deferred) { - write_scheduler_.MarkStreamReady(stream_id, false); + if (!options_.trailers_require_end_data || !iter->second.data_deferred) { + trailers_ready_.insert(stream_id); } } return 0; @@ -792,12 +980,14 @@ void OgHttp2Session::SubmitMetadata(Http2StreamId stream_id, } else { auto iter = CreateStream(stream_id); iter->second.outbound_metadata.push_back(std::move(source)); - write_scheduler_.MarkStreamReady(stream_id, false); + metadata_ready_.insert(stream_id); } } void OgHttp2Session::SubmitSettings(absl::Span<const Http2Setting> settings) { - EnqueueFrame(PrepareSettingsFrame(settings)); + auto frame = PrepareSettingsFrame(settings); + HandleOutboundSettings(*frame); + EnqueueFrame(std::move(frame)); } void OgHttp2Session::OnError(SpdyFramerError error, @@ -815,15 +1005,20 @@ void OgHttp2Session::OnCommonHeader(spdy::SpdyStreamId stream_id, uint8_t flags) { highest_received_stream_id_ = std::max(static_cast<Http2StreamId>(stream_id), highest_received_stream_id_); + if (streams_reset_.contains(stream_id)) { + return; + } const bool result = visitor_.OnFrameHeader(stream_id, length, type, flags); if (!result) { + fatal_visitor_callback_failure_ = true; decoder_.StopProcessing(); } } void OgHttp2Session::OnDataFrameHeader(spdy::SpdyStreamId stream_id, size_t length, bool /*fin*/) { - if (!stream_map_.contains(stream_id)) { + auto iter = stream_map_.find(stream_id); + if (iter == stream_map_.end() || streams_reset_.contains(stream_id)) { // The stream does not exist; it could be an error or a benign close, e.g., // getting data for a stream this connection recently closed. if (static_cast<Http2StreamId>(stream_id) > highest_processed_stream_id_) { @@ -836,8 +1031,25 @@ void OgHttp2Session::OnDataFrameHeader(spdy::SpdyStreamId stream_id, const bool result = visitor_.OnBeginDataForStream(stream_id, length); if (!result) { + fatal_visitor_callback_failure_ = true; decoder_.StopProcessing(); } + + if (!iter->second.can_receive_body && length > 0) { + EnqueueFrame(absl::make_unique<spdy::SpdyRstStreamIR>( + stream_id, spdy::ERROR_CODE_PROTOCOL_ERROR)); + return; + } + + // Validate against the content-length if it exists. + if (iter->second.remaining_content_length.has_value()) { + if (length > *iter->second.remaining_content_length) { + HandleContentLengthError(stream_id); + iter->second.remaining_content_length.reset(); + } else { + *iter->second.remaining_content_length -= length; + } + } } void OgHttp2Session::OnStreamFrameData(spdy::SpdyStreamId stream_id, @@ -846,7 +1058,7 @@ void OgHttp2Session::OnStreamFrameData(spdy::SpdyStreamId stream_id, // Count the data against flow control, even if the stream is unknown. MarkDataBuffered(stream_id, len); - if (!stream_map_.contains(stream_id)) { + if (!stream_map_.contains(stream_id) || streams_reset_.contains(stream_id)) { // If the stream was unknown due to a protocol error, the visitor was // informed in OnDataFrameHeader(). return; @@ -855,6 +1067,7 @@ void OgHttp2Session::OnStreamFrameData(spdy::SpdyStreamId stream_id, const bool result = visitor_.OnDataForStream(stream_id, absl::string_view(data, len)); if (!result) { + fatal_visitor_callback_failure_ = true; decoder_.StopProcessing(); } } @@ -863,13 +1076,25 @@ 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; + if (streams_reset_.contains(stream_id)) { + return; + } + + // Validate against the content-length if it exists. + if (iter->second.remaining_content_length.has_value() && + *iter->second.remaining_content_length != 0) { + HandleContentLengthError(stream_id); + return; + } + visitor_.OnEndStream(stream_id); } + auto queued_frames_iter = queued_frames_.find(stream_id); const bool no_queued_frames = queued_frames_iter == queued_frames_.end() || queued_frames_iter->second == 0; if (iter != stream_map_.end() && iter->second.half_closed_local && - options_.perspective == Perspective::kClient && no_queued_frames) { + !IsServerSession() && no_queued_frames) { // From the client's perspective, the stream can be closed if it's already // half_closed_local. CloseStream(stream_id, Http2ErrorCode::HTTP2_NO_ERROR); @@ -878,8 +1103,12 @@ void OgHttp2Session::OnStreamEnd(spdy::SpdyStreamId stream_id) { void OgHttp2Session::OnStreamPadLength(spdy::SpdyStreamId stream_id, size_t value) { + bool result = visitor_.OnDataPaddingLength(stream_id, 1 + value); + if (!result) { + fatal_visitor_callback_failure_ = true; + decoder_.StopProcessing(); + } MarkDataBuffered(stream_id, 1 + value); - // TODO(181586191): Pass padding to the visitor? } void OgHttp2Session::OnStreamPadding(spdy::SpdyStreamId /*stream_id*/, size_t @@ -891,7 +1120,7 @@ void OgHttp2Session::OnStreamPadding(spdy::SpdyStreamId /*stream_id*/, size_t spdy::SpdyHeadersHandlerInterface* OgHttp2Session::OnHeaderFrameStart( spdy::SpdyStreamId stream_id) { auto it = stream_map_.find(stream_id); - if (it != stream_map_.end()) { + if (it != stream_map_.end() && !streams_reset_.contains(stream_id)) { headers_handler_.set_stream_id(stream_id); headers_handler_.set_header_type( NextHeaderType(it->second.received_header_type)); @@ -909,10 +1138,16 @@ void OgHttp2Session::OnHeaderFrameEnd(spdy::SpdyStreamId stream_id) { headers_handler_.status_header()[0] == '1') { // If response headers carried a 1xx response code, final response headers // should still be forthcoming. - it->second.received_header_type = HeaderType::RESPONSE_100; - } else { - it->second.received_header_type = headers_handler_.header_type(); + headers_handler_.set_header_type(HeaderType::RESPONSE_100); } + it->second.received_header_type = headers_handler_.header_type(); + + // Track the content-length if the headers indicate that a body can follow. + it->second.can_receive_body = headers_handler_.CanReceiveBody(); + if (it->second.can_receive_body) { + it->second.remaining_content_length = headers_handler_.content_length(); + } + headers_handler_.set_stream_id(0); } } @@ -930,6 +1165,9 @@ void OgHttp2Session::OnRstStream(spdy::SpdyStreamId stream_id, ConnectionError::kWrongFrameSequence); return; } + if (streams_reset_.contains(stream_id)) { + return; + } visitor_.OnRstStream(stream_id, TranslateErrorCode(error_code)); // TODO(birenroy): Consider whether there are outbound frames queued for the // stream. @@ -944,28 +1182,50 @@ void OgHttp2Session::OnSettings() { } void OgHttp2Session::OnSetting(spdy::SpdySettingsId id, uint32_t value) { - visitor_.OnSetting({id, value}); - if (id == kMetadataExtensionId) { - peer_supports_metadata_ = (value != 0); - } else if (id == MAX_FRAME_SIZE) { - max_frame_payload_ = value; - } else if (id == MAX_CONCURRENT_STREAMS) { - max_outbound_concurrent_streams_ = value; - } else if (id == HEADER_TABLE_SIZE) { - value = std::min(value, HpackCapacityBound(options_)); - if (value < framer_.GetHpackEncoder()->CurrentHeaderTableSizeSetting()) { - // Safe to apply a smaller table capacity immediately. - QUICHE_VLOG(2) << TracePerspectiveAsString(options_.perspective) - << " applying encoder table capacity " << value; - framer_.GetHpackEncoder()->ApplyHeaderTableSizeSetting(value); - } else { - QUICHE_VLOG(2) - << TracePerspectiveAsString(options_.perspective) - << " NOT applying encoder table capacity until writing ack: " - << value; - encoder_header_table_capacity_when_acking_ = value; - } + switch (id) { + case MAX_FRAME_SIZE: + max_frame_payload_ = value; + break; + case MAX_CONCURRENT_STREAMS: + max_outbound_concurrent_streams_ = value; + break; + case HEADER_TABLE_SIZE: + value = std::min(value, HpackCapacityBound(options_)); + if (value < framer_.GetHpackEncoder()->CurrentHeaderTableSizeSetting()) { + // Safe to apply a smaller table capacity immediately. + QUICHE_VLOG(2) << TracePerspectiveAsString(options_.perspective) + << " applying encoder table capacity " << value; + framer_.GetHpackEncoder()->ApplyHeaderTableSizeSetting(value); + } else { + QUICHE_VLOG(2) + << TracePerspectiveAsString(options_.perspective) + << " NOT applying encoder table capacity until writing ack: " + << value; + encoder_header_table_capacity_when_acking_ = value; + } + break; + case INITIAL_WINDOW_SIZE: + if (value > spdy::kSpdyMaximumWindowSize) { + visitor_.OnInvalidFrame( + 0, Http2VisitorInterface::InvalidFrameError::kFlowControl); + // The specification says this is a connection-level flow control error. + LatchErrorAndNotify( + Http2ErrorCode::FLOW_CONTROL_ERROR, + Http2VisitorInterface::ConnectionError::kFlowControlError); + return; + } else { + UpdateInitialWindowSize(value); + } + break; + default: + // TODO(bnc): See if C++17 inline constants are allowed in QUICHE. + if (id == kMetadataExtensionId) { + peer_supports_metadata_ = (value != 0); + } else { + QUICHE_VLOG(1) << "Unimplemented SETTING id: " << id; + } } + visitor_.OnSetting({id, value}); } void OgHttp2Session::OnSettingsEnd() { @@ -997,6 +1257,7 @@ void OgHttp2Session::OnGoAway(spdy::SpdyStreamId last_accepted_stream_id, const bool result = visitor_.OnGoAway(last_accepted_stream_id, TranslateErrorCode(error_code), ""); if (!result) { + fatal_visitor_callback_failure_ = true; decoder_.StopProcessing(); } } @@ -1020,8 +1281,12 @@ void OgHttp2Session::OnHeaders(spdy::SpdyStreamId stream_id, if (fin) { headers_handler_.set_frame_contains_fin(); } - if (options_.perspective == Perspective::kServer) { + if (IsServerSession()) { const auto new_stream_id = static_cast<Http2StreamId>(stream_id); + if (stream_map_.find(new_stream_id) != stream_map_.end() && fin) { + // Not a new stream, must be trailers. + return; + } if (new_stream_id <= highest_processed_stream_id_) { // A new stream ID lower than the watermark is a connection error. LatchErrorAndNotify(Http2ErrorCode::PROTOCOL_ERROR, @@ -1033,8 +1298,11 @@ void OgHttp2Session::OnHeaders(spdy::SpdyStreamId stream_id, // The new stream would exceed our advertised and acknowledged // MAX_CONCURRENT_STREAMS. For parity with nghttp2, treat this error as a // connection-level PROTOCOL_ERROR. - visitor_.OnInvalidFrame( + bool ok = visitor_.OnInvalidFrame( stream_id, Http2VisitorInterface::InvalidFrameError::kProtocol); + if (!ok) { + fatal_visitor_callback_failure_ = true; + } LatchErrorAndNotify(Http2ErrorCode::PROTOCOL_ERROR, ConnectionError::kExceededMaxConcurrentStreams); return; @@ -1047,6 +1315,7 @@ void OgHttp2Session::OnHeaders(spdy::SpdyStreamId stream_id, const bool ok = visitor_.OnInvalidFrame( stream_id, Http2VisitorInterface::InvalidFrameError::kRefusedStream); if (!ok) { + fatal_visitor_callback_failure_ = true; LatchErrorAndNotify(Http2ErrorCode::REFUSED_STREAM, ConnectionError::kExceededMaxConcurrentStreams); } @@ -1073,8 +1342,12 @@ void OgHttp2Session::OnWindowUpdate(spdy::SpdyStreamId stream_id, return; } } else { + if (streams_reset_.contains(stream_id)) { + return; + } if (it->second.send_window == 0) { // The stream was blocked on flow control. + QUICHE_VLOG(1) << "Marking stream " << stream_id << " ready to write."; write_scheduler_.MarkStreamReady(stream_id, false); } it->second.send_window += delta_window_size; @@ -1116,8 +1389,11 @@ bool OgHttp2Session::OnUnknownFrame(spdy::SpdyStreamId /*stream_id*/, void OgHttp2Session::OnHeaderStatus( Http2StreamId stream_id, Http2VisitorInterface::OnHeaderResult result) { QUICHE_DCHECK_NE(result, Http2VisitorInterface::HEADER_OK); + QUICHE_VLOG(1) << "OnHeaderStatus(stream_id=" << stream_id + << ", result=" << result << ")"; const bool should_reset_stream = result == Http2VisitorInterface::HEADER_RST_STREAM || + result == Http2VisitorInterface::HEADER_FIELD_INVALID || result == Http2VisitorInterface::HEADER_HTTP_MESSAGING; if (should_reset_stream) { const Http2ErrorCode error_code = @@ -1126,7 +1402,8 @@ void OgHttp2Session::OnHeaderStatus( : Http2ErrorCode::PROTOCOL_ERROR; const spdy::SpdyErrorCode spdy_error_code = TranslateErrorCode(error_code); const Http2VisitorInterface::InvalidFrameError frame_error = - (result == Http2VisitorInterface::HEADER_RST_STREAM) + (result == Http2VisitorInterface::HEADER_RST_STREAM || + result == Http2VisitorInterface::HEADER_FIELD_INVALID) ? Http2VisitorInterface::InvalidFrameError::kHttpHeader : Http2VisitorInterface::InvalidFrameError::kHttpMessaging; auto it = streams_reset_.find(stream_id); @@ -1134,25 +1411,41 @@ void OgHttp2Session::OnHeaderStatus( EnqueueFrame( absl::make_unique<spdy::SpdyRstStreamIR>(stream_id, spdy_error_code)); - const bool ok = visitor_.OnInvalidFrame(stream_id, frame_error); - if (!ok) { - LatchErrorAndNotify(error_code, ConnectionError::kHeaderError); + if (result == Http2VisitorInterface::HEADER_FIELD_INVALID || + result == Http2VisitorInterface::HEADER_HTTP_MESSAGING) { + const bool ok = visitor_.OnInvalidFrame(stream_id, frame_error); + if (!ok) { + fatal_visitor_callback_failure_ = true; + LatchErrorAndNotify(error_code, ConnectionError::kHeaderError); + } } } } else if (result == Http2VisitorInterface::HEADER_CONNECTION_ERROR) { + fatal_visitor_callback_failure_ = true; LatchErrorAndNotify(Http2ErrorCode::INTERNAL_ERROR, ConnectionError::kHeaderError); + } else if (result == Http2VisitorInterface::HEADER_COMPRESSION_ERROR) { + LatchErrorAndNotify(Http2ErrorCode::COMPRESSION_ERROR, + ConnectionError::kHeaderError); } } bool OgHttp2Session::OnFrameHeader(spdy::SpdyStreamId stream_id, size_t length, uint8_t type, uint8_t flags) { + if (streams_reset_.contains(stream_id)) { + return false; + } 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; + + // Empty metadata payloads will not trigger OnFramePayload(), so handle + // that possibility here. + MaybeHandleMetadataEndForStream(metadata_stream_id_); + return true; } else { QUICHE_DLOG(INFO) << "Unexpected frame type " << static_cast<int>(type) @@ -1162,18 +1455,18 @@ bool OgHttp2Session::OnFrameHeader(spdy::SpdyStreamId stream_id, size_t length, } void OgHttp2Session::OnFramePayload(const char* data, size_t len) { + if (streams_reset_.contains(metadata_stream_id_)) { + return; + } if (metadata_length_ > 0) { QUICHE_DCHECK_LE(len, metadata_length_); - const bool success = visitor_.OnMetadataForStream( + const bool payload_success = visitor_.OnMetadataForStream( metadata_stream_id_, absl::string_view(data, len)); - if (success) { + if (payload_success) { metadata_length_ -= len; - if (metadata_length_ == 0 && end_metadata_) { - visitor_.OnMetadataEndForStream(metadata_stream_id_); - metadata_stream_id_ = 0; - end_metadata_ = false; - } + MaybeHandleMetadataEndForStream(metadata_stream_id_); } else { + fatal_visitor_callback_failure_ = true; decoder_.StopProcessing(); } } else { @@ -1184,15 +1477,15 @@ void OgHttp2Session::OnFramePayload(const char* data, size_t len) { void OgHttp2Session::MaybeSetupPreface() { if (!queued_preface_) { - if (options_.perspective == Perspective::kClient) { + if (!IsServerSession()) { buffered_data_.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<SpdySettingsIR&>(*frames_.front()).is_ack()) { - frames_.push_front(PrepareSettingsFrame(GetInitialSettings())); + if (frames_.empty() || !IsNonAckSettings(*frames_.front())) { + auto frame = PrepareSettingsFrame(GetInitialSettings()); + HandleOutboundSettings(*frame); + frames_.push_front(std::move(frame)); } queued_preface_ = true; } @@ -1206,6 +1499,13 @@ std::vector<Http2Setting> OgHttp2Session::GetInitialSettings() const { // TODO(diannahu): Consider applying server push disabling on SETTINGS ack. settings.push_back({Http2KnownSettingsId::ENABLE_PUSH, 0}); } + if (options_.max_header_list_bytes) { + settings.push_back({Http2KnownSettingsId::MAX_HEADER_LIST_SIZE, + *options_.max_header_list_bytes}); + } + if (options_.allow_extended_connect && IsServerSession()) { + settings.push_back({Http2KnownSettingsId::ENABLE_CONNECT_PROTOCOL, 1u}); + } return settings; } @@ -1214,26 +1514,60 @@ std::unique_ptr<SpdySettingsIR> OgHttp2Session::PrepareSettingsFrame( auto settings_ir = absl::make_unique<SpdySettingsIR>(); for (const Http2Setting& setting : settings) { settings_ir->AddSetting(setting.id, setting.value); + } + return settings_ir; +} - if (setting.id == Http2KnownSettingsId::MAX_CONCURRENT_STREAMS) { - pending_max_inbound_concurrent_streams_ = setting.value; +void OgHttp2Session::HandleOutboundSettings( + const spdy::SpdySettingsIR& settings_frame) { + for (const auto& [id, value] : settings_frame.values()) { + switch (static_cast<Http2KnownSettingsId>(id)) { + case MAX_CONCURRENT_STREAMS: + pending_max_inbound_concurrent_streams_ = value; + break; + case ENABLE_CONNECT_PROTOCOL: + if (value == 1u && IsServerSession()) { + // Allow extended CONNECT semantics even before SETTINGS are acked, to + // make things easier for clients. + headers_handler_.AllowConnect(); + } + break; + case HEADER_TABLE_SIZE: + case ENABLE_PUSH: + case INITIAL_WINDOW_SIZE: + case MAX_FRAME_SIZE: + case MAX_HEADER_LIST_SIZE: + QUICHE_VLOG(2) + << "Not adjusting internal state for outbound setting with id " + << id; + break; } } // Copy the (small) map of settings we are about to send so that we can set // values in the SETTINGS ack callback. settings_ack_callbacks_.push_back( - [this, settings_map = settings_ir->values()]() { - for (const auto id_and_value : settings_map) { - if (id_and_value.first == spdy::SETTINGS_MAX_CONCURRENT_STREAMS) { - max_inbound_concurrent_streams_ = id_and_value.second; - } else if (id_and_value.first == spdy::SETTINGS_HEADER_TABLE_SIZE) { - decoder_.GetHpackDecoder()->ApplyHeaderTableSizeSetting( - id_and_value.second); + [this, settings_map = settings_frame.values()]() { + for (const auto& [id, value] : settings_map) { + switch (static_cast<Http2KnownSettingsId>(id)) { + case MAX_CONCURRENT_STREAMS: + max_inbound_concurrent_streams_ = value; + break; + case HEADER_TABLE_SIZE: + decoder_.GetHpackDecoder()->ApplyHeaderTableSizeSetting(value); + break; + case ENABLE_PUSH: + case INITIAL_WINDOW_SIZE: + case MAX_FRAME_SIZE: + case MAX_HEADER_LIST_SIZE: + case ENABLE_CONNECT_PROTOCOL: + QUICHE_VLOG(2) + << "No action required in ack for outbound setting with id " + << id; + break; } } }); - return settings_ir; } void OgHttp2Session::SendWindowUpdate(Http2StreamId stream_id, @@ -1257,13 +1591,13 @@ void OgHttp2Session::SendTrailers(Http2StreamId stream_id, absl::make_unique<spdy::SpdyHeadersIR>(stream_id, std::move(trailers)); frame->set_fin(true); EnqueueFrame(std::move(frame)); + trailers_ready_.erase(stream_id); } void OgHttp2Session::MaybeFinWithRstStream(StreamStateMap::iterator iter) { QUICHE_DCHECK(iter != stream_map_.end() && iter->second.half_closed_local); - if (options_.rst_stream_no_error_when_incomplete && - options_.perspective == Perspective::kServer && + if (options_.rst_stream_no_error_when_incomplete && IsServerSession() && !iter->second.half_closed_remote) { // Since the peer has not yet ended the stream, this endpoint should // send a RST_STREAM NO_ERROR. See RFC 7540 Section 8.1. @@ -1275,8 +1609,7 @@ void OgHttp2Session::MaybeFinWithRstStream(StreamStateMap::iterator iter) { 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()) { + if (auto it = stream_map_.find(stream_id); it != stream_map_.end()) { it->second.window_manager.MarkDataBuffered(bytes); } } @@ -1287,11 +1620,9 @@ OgHttp2Session::StreamStateMap::iterator OgHttp2Session::CreateStream( [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))); + auto [iter, inserted] = stream_map_.try_emplace( + stream_id, StreamState(initial_stream_receive_window_, + initial_stream_send_window_, std::move(listener))); if (inserted) { // Add the stream to the write scheduler. const WriteScheduler::StreamPrecedenceType precedence(3); @@ -1321,6 +1652,11 @@ void OgHttp2Session::CloseStream(Http2StreamId stream_id, Http2ErrorCode error_code) { visitor_.OnCloseStream(stream_id, error_code); stream_map_.erase(stream_id); + trailers_ready_.erase(stream_id); + metadata_ready_.erase(stream_id); + streams_reset_.erase(stream_id); + queued_frames_.erase(stream_id); + stream_map_.erase(stream_id); if (write_scheduler_.StreamRegistered(stream_id)) { write_scheduler_.UnregisterStream(stream_id); } @@ -1341,7 +1677,12 @@ bool OgHttp2Session::CanCreateStream() const { HeaderType OgHttp2Session::NextHeaderType( absl::optional<HeaderType> current_type) { if (IsServerSession()) { - return HeaderType::REQUEST; + if (!current_type) { + return HeaderType::REQUEST; + } else { + QUICHE_DCHECK(current_type == HeaderType::REQUEST); + return HeaderType::REQUEST_TRAILER; + } } else if (!current_type || current_type.value() == HeaderType::RESPONSE_100) { return HeaderType::RESPONSE; @@ -1380,5 +1721,85 @@ void OgHttp2Session::CloseStreamIfReady(uint8_t frame_type, } } +void OgHttp2Session::PrepareForImmediateGoAway() { + queued_immediate_goaway_ = true; + + // Keep the initial SETTINGS frame if the session has SETTINGS at the front of + // the queue but has not sent SETTINGS yet. The session should send initial + // SETTINGS before GOAWAY. + std::unique_ptr<spdy::SpdyFrameIR> initial_settings; + if (!sent_non_ack_settings_ && !frames_.empty() && + IsNonAckSettings(*frames_.front())) { + initial_settings = std::move(frames_.front()); + frames_.pop_front(); + } + + // Remove all pending frames except for RST_STREAMs. It is important to send + // RST_STREAMs so the peer knows of errors below the GOAWAY last stream ID. + // TODO(diannahu): Consider informing the visitor of dropped frames. This may + // mean keeping the frames and invoking a frame-not-sent callback, similar to + // nghttp2. Could add a closure to each frame in the frames queue. + frames_.remove_if([](const auto& frame) { + return frame->frame_type() != spdy::SpdyFrameType::RST_STREAM; + }); + + if (initial_settings != nullptr) { + frames_.push_front(std::move(initial_settings)); + } +} + +void OgHttp2Session::MaybeHandleMetadataEndForStream(Http2StreamId stream_id) { + if (metadata_length_ == 0 && end_metadata_) { + const bool completion_success = visitor_.OnMetadataEndForStream(stream_id); + if (!completion_success) { + fatal_visitor_callback_failure_ = true; + decoder_.StopProcessing(); + } + metadata_stream_id_ = 0; + end_metadata_ = false; + } +} + +void OgHttp2Session::DecrementQueuedFrameCount(uint32_t stream_id, + uint8_t frame_type) { + auto iter = queued_frames_.find(stream_id); + if (iter == queued_frames_.end()) { + QUICHE_LOG(ERROR) << "Unable to find a queued frame count for stream " + << stream_id; + return; + } + if (static_cast<FrameType>(frame_type) != FrameType::DATA) { + --iter->second; + } + if (iter->second == 0) { + // TODO(birenroy): Consider passing through `error_code` here. + CloseStreamIfReady(frame_type, stream_id); + } +} + +void OgHttp2Session::HandleContentLengthError(Http2StreamId stream_id) { + EnqueueFrame(absl::make_unique<spdy::SpdyRstStreamIR>( + stream_id, spdy::ERROR_CODE_PROTOCOL_ERROR)); +} + +void OgHttp2Session::UpdateInitialWindowSize(uint32_t new_value) { + const int32_t delta = + static_cast<int32_t>(new_value) - initial_stream_send_window_; + initial_stream_send_window_ = new_value; + for (auto& [stream_id, stream_state] : stream_map_) { + const int64_t current_window_size = stream_state.send_window; + const int64_t new_window_size = current_window_size + delta; + if (new_window_size > spdy::kSpdyMaximumWindowSize) { + EnqueueFrame(absl::make_unique<spdy::SpdyRstStreamIR>( + stream_id, spdy::ERROR_CODE_FLOW_CONTROL_ERROR)); + } else { + stream_state.send_window += delta; + } + if (current_window_size <= 0 && new_window_size > 0) { + write_scheduler_.MarkStreamReady(stream_id, false); + } + } +} + } // 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 670b2367f45..29bff34245d 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 @@ -9,6 +9,7 @@ #include "absl/strings/string_view.h" #include "absl/types/optional.h" +#include "absl/types/variant.h" #include "http2/adapter/data_source.h" #include "http2/adapter/event_forwarder.h" #include "http2/adapter/header_validator.h" @@ -40,6 +41,10 @@ class QUICHE_EXPORT_PRIVATE OgHttp2Session Perspective perspective = Perspective::kClient; // The maximum HPACK table size to use. absl::optional<size_t> max_hpack_encoding_table_capacity = absl::nullopt; + // The maximum number of decoded header bytes that a stream can receive. + absl::optional<uint32_t> max_header_list_bytes = absl::nullopt; + // The maximum size of an individual header field, including name and value. + absl::optional<uint32_t> max_header_field_size = absl::nullopt; // Whether to automatically send PING acks when receiving a PING. bool auto_ping_ack = true; // Whether (as server) to send a RST_STREAM NO_ERROR when sending a fin on @@ -49,6 +54,14 @@ class QUICHE_EXPORT_PRIVATE OgHttp2Session // has indicated the end of data. If false, the server will assume that // submitting trailers indicates the end of data. bool trailers_require_end_data = false; + // Whether to mark all input data as consumed upon encountering a connection + // error while processing bytes. If true, subsequent processing will also + // mark all input data as consumed. + bool blackhole_data_on_connection_error = true; + // Whether to advertise support for the extended CONNECT semantics described + // in RFC 8441. If true, this endpoint will send the appropriate setting in + // initial SETTINGS. + bool allow_extended_connect = true; }; OgHttp2Session(Http2VisitorInterface& visitor, Options options); @@ -122,8 +135,9 @@ class QUICHE_EXPORT_PRIVATE OgHttp2Session return !received_goaway_ && !decoder_.HasError(); } bool want_write() const override { - return !frames_.empty() || !buffered_data_.empty() || - write_scheduler_.HasReadyStreams() || !connection_metadata_.empty(); + return !fatal_send_error_ && + (!frames_.empty() || !buffered_data_.empty() || + !connection_metadata_.empty() || HasReadyStream()); } int GetRemoteWindowSize() const override { return connection_send_window_; } @@ -198,21 +212,24 @@ class QUICHE_EXPORT_PRIVATE OgHttp2Session using MetadataSequence = std::vector<std::unique_ptr<MetadataSource>>; struct QUICHE_EXPORT_PRIVATE StreamState { - StreamState(int32_t stream_receive_window, + StreamState(int32_t stream_receive_window, int32_t stream_send_window, WindowManager::WindowUpdateListener listener) - : window_manager(stream_receive_window, std::move(listener)) {} + : window_manager(stream_receive_window, std::move(listener)), + send_window(stream_send_window) {} WindowManager window_manager; std::unique_ptr<DataFrameSource> outbound_body; MetadataSequence outbound_metadata; std::unique_ptr<spdy::SpdyHeaderBlock> trailers; void* user_data = nullptr; - int32_t send_window = kInitialFlowControlWindowSize; + int32_t send_window; absl::optional<HeaderType> received_header_type; + absl::optional<size_t> remaining_content_length; bool half_closed_local = false; bool half_closed_remote = false; // Indicates that `outbound_body` temporarily cannot produce data. bool data_deferred = false; + bool can_receive_body = true; }; using StreamStateMap = absl::flat_hash_map<Http2StreamId, StreamState>; @@ -243,11 +260,19 @@ class QUICHE_EXPORT_PRIVATE OgHttp2Session void OnHeader(absl::string_view key, absl::string_view value) override; void OnHeaderBlockEnd(size_t /* uncompressed_header_bytes */, size_t /* compressed_header_bytes */) override; - absl::string_view status_header() { + absl::string_view status_header() const { QUICHE_DCHECK(type_ == HeaderType::RESPONSE || type_ == HeaderType::RESPONSE_100); return validator_.status_header(); } + absl::optional<size_t> content_length() const { + return validator_.content_length(); + } + void AllowConnect() { validator_.AllowConnect(); } + void SetMaxFieldSize(uint32_t field_size) { + validator_.SetMaxFieldSize(field_size); + } + bool CanReceiveBody() const; private: OgHttp2Session& session_; @@ -261,6 +286,8 @@ class QUICHE_EXPORT_PRIVATE OgHttp2Session bool frame_contains_fin_ = false; }; + struct QUICHE_EXPORT_PRIVATE ProcessBytesResultVisitor; + // Queues the connection preface, if not already done. void MaybeSetupPreface(); @@ -272,6 +299,9 @@ class QUICHE_EXPORT_PRIVATE OgHttp2Session std::unique_ptr<spdy::SpdySettingsIR> PrepareSettingsFrame( absl::Span<const Http2Setting> settings); + // Updates internal state to match the SETTINGS advertised to the peer. + void HandleOutboundSettings(const spdy::SpdySettingsIR& settings_frame); + void SendWindowUpdate(Http2StreamId stream_id, size_t update_delta); enum class SendResult { @@ -283,13 +313,38 @@ class QUICHE_EXPORT_PRIVATE OgHttp2Session SEND_ERROR, }; + // Returns the int corresponding to the `result`, updating state as needed. + int InterpretSendResult(SendResult result); + + enum class ProcessBytesError { + // A general, unspecified error. + kUnspecified, + // The (server-side) session received an invalid client connection preface. + kInvalidConnectionPreface, + // A user/visitor callback failed with a fatal error. + kVisitorCallbackFailed, + }; + using ProcessBytesResult = absl::variant<int64_t, ProcessBytesError>; + + // Attempts to process `bytes` and returns the number of bytes proccessed on + // success or the processing error on failure. + ProcessBytesResult ProcessBytesImpl(absl::string_view bytes); + + // Returns true if at least one stream has data or control frames to write. + bool HasReadyStream() const; + + // Returns the next stream that has something to write. If there are no such + // streams, returns zero. + Http2StreamId GetNextReadyStream(); + // Sends the buffered connection preface or serialized frame data, if any. SendResult MaybeSendBufferedData(); // Serializes and sends queued frames. SendResult SendQueuedFrames(); - void AfterFrameSent(uint8_t frame_type, uint32_t stream_id, + // Returns false if a fatal connection error occurred. + bool AfterFrameSent(uint8_t frame_type_int, uint32_t stream_id, size_t payload_length, uint8_t flags, uint32_t error_code); @@ -336,6 +391,18 @@ class QUICHE_EXPORT_PRIVATE OgHttp2Session void CloseStreamIfReady(uint8_t frame_type, uint32_t stream_id); + // Updates internal state to prepare for sending an immediate GOAWAY. + void PrepareForImmediateGoAway(); + + // Handles the potential end of received metadata for the given `stream_id`. + void MaybeHandleMetadataEndForStream(Http2StreamId stream_id); + + void DecrementQueuedFrameCount(uint32_t stream_id, uint8_t frame_type); + + void HandleContentLengthError(Http2StreamId stream_id); + + void UpdateInitialWindowSize(uint32_t new_value); + // Receives events when inbound frames are parsed. Http2VisitorInterface& visitor_; @@ -388,8 +455,16 @@ class QUICHE_EXPORT_PRIVATE OgHttp2Session WindowManager connection_window_manager_; + // Tracks the streams that have been marked for reset. A stream is removed + // from this set once it is closed. absl::flat_hash_set<Http2StreamId> streams_reset_; + + // The number of frames currently queued per stream. absl::flat_hash_map<Http2StreamId, int> queued_frames_; + // Includes streams that are currently ready to write trailers. + absl::flat_hash_set<Http2StreamId> trailers_ready_; + // Includes streams that are currently ready to write metadata. + absl::flat_hash_set<Http2StreamId> metadata_ready_; MetadataSequence connection_metadata_; @@ -403,7 +478,9 @@ class QUICHE_EXPORT_PRIVATE OgHttp2Session size_t metadata_length_ = 0; int32_t connection_send_window_ = kInitialFlowControlWindowSize; // The initial flow control receive window size for any newly created streams. - int32_t stream_receive_window_limit_ = kInitialFlowControlWindowSize; + int32_t initial_stream_receive_window_ = kInitialFlowControlWindowSize; + // The initial flow control send window size for any newly created streams. + int32_t initial_stream_send_window_ = kInitialFlowControlWindowSize; uint32_t max_frame_payload_ = 16384u; // The maximum number of concurrent streams that this connection can open to // its peer and allow from its peer, respectively. Although the initial value @@ -415,7 +492,7 @@ class QUICHE_EXPORT_PRIVATE OgHttp2Session std::numeric_limits<uint32_t>::max(); uint32_t max_inbound_concurrent_streams_ = std::numeric_limits<uint32_t>::max(); - Options options_; + const Options options_; // The HPACK encoder header table capacity that will be applied when // acking SETTINGS from the peer. Only contains a value if the peer advertises @@ -427,6 +504,7 @@ class QUICHE_EXPORT_PRIVATE OgHttp2Session bool queued_preface_ = false; bool peer_supports_metadata_ = false; bool end_metadata_ = false; + bool sent_non_ack_settings_ = false; // Recursion guard for ProcessBytes(). bool processing_bytes_ = false; @@ -435,7 +513,14 @@ class QUICHE_EXPORT_PRIVATE OgHttp2Session // Replace this with a stream ID, for multiple GOAWAY support. bool queued_goaway_ = false; + bool queued_immediate_goaway_ = false; bool latched_error_ = false; + + // True if a fatal sending error has occurred. + bool fatal_send_error_ = false; + + // True if a fatal processing visitor callback failed. + bool fatal_visitor_callback_failure_ = 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 28a143aac81..83dd424cc66 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 @@ -473,30 +473,6 @@ TEST(OgHttp2SessionTest, ClientSubmitRequestWithWriteBlock) { 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) { testing::StrictMock<MockHttp2Visitor> visitor; OgHttp2Session session( @@ -762,54 +738,6 @@ TEST(OgHttp2SessionTest, ServerSubmitResponse) { 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) { diff --git a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_util.cc b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_util.cc index 3ed13dcf46e..ed0e6f618b3 100644 --- a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_util.cc +++ b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_util.cc @@ -8,7 +8,7 @@ spdy::SpdyHeaderBlock ToHeaderBlock(absl::Span<const Header> headers) { for (const Header& header : headers) { absl::string_view name = GetStringView(header.first).first; absl::string_view value = GetStringView(header.second).first; - block[name] = value; + block.AppendValueOrAddHeader(name, value); } return block; } diff --git a/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_util_test.cc b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_util_test.cc new file mode 100644 index 00000000000..6f29b26eef5 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/adapter/oghttp2_util_test.cc @@ -0,0 +1,83 @@ +#include "http2/adapter/oghttp2_util.h" + +#include <utility> +#include <vector> + +#include "http2/adapter/http2_protocol.h" +#include "http2/adapter/test_frame_sequence.h" +#include "common/platform/api/quiche_test.h" + +namespace http2 { +namespace adapter { +namespace test { +namespace { + +using HeaderPair = std::pair<absl::string_view, absl::string_view>; + +TEST(ToHeaderBlock, EmptySpan) { + spdy::SpdyHeaderBlock block = ToHeaderBlock({}); + EXPECT_TRUE(block.empty()); +} + +TEST(ToHeaderBlock, ExampleRequestHeaders) { + const std::vector<HeaderPair> pairs = {{":authority", "example.com"}, + {":method", "GET"}, + {":path", "/example.html"}, + {":scheme", "http"}, + {"accept", "text/plain, text/html"}}; + const std::vector<Header> headers = ToHeaders(pairs); + spdy::SpdyHeaderBlock block = ToHeaderBlock(headers); + EXPECT_THAT(block, testing::ElementsAreArray(pairs)); +} + +TEST(ToHeaderBlock, ExampleResponseHeaders) { + const std::vector<HeaderPair> pairs = { + {":status", "403"}, + {"content-length", "1023"}, + {"x-extra-info", "humblest apologies"}}; + const std::vector<Header> headers = ToHeaders(pairs); + spdy::SpdyHeaderBlock block = ToHeaderBlock(headers); + EXPECT_THAT(block, testing::ElementsAreArray(pairs)); +} + +TEST(ToHeaderBlock, RepeatedRequestHeaderNames) { + const std::vector<HeaderPair> pairs = { + {":authority", "example.com"}, {":method", "GET"}, + {":path", "/example.html"}, {":scheme", "http"}, + {"cookie", "chocolate_chips=yes"}, {"accept", "text/plain, text/html"}, + {"cookie", "raisins=no"}}; + const std::vector<HeaderPair> expected = { + {":authority", "example.com"}, + {":method", "GET"}, + {":path", "/example.html"}, + {":scheme", "http"}, + {"cookie", "chocolate_chips=yes; raisins=no"}, + {"accept", "text/plain, text/html"}}; + const std::vector<Header> headers = ToHeaders(pairs); + spdy::SpdyHeaderBlock block = ToHeaderBlock(headers); + EXPECT_THAT(block, testing::ElementsAreArray(expected)); +} + +TEST(ToHeaderBlock, RepeatedResponseHeaderNames) { + const std::vector<HeaderPair> pairs = { + {":status", "403"}, {"x-extra-info", "sorry"}, + {"content-length", "1023"}, {"x-extra-info", "humblest apologies"}, + {"content-length", "1024"}, {"set-cookie", "chocolate_chips=yes"}, + {"set-cookie", "raisins=no"}}; + const std::vector<HeaderPair> expected = { + {":status", "403"}, + {"x-extra-info", absl::string_view("sorry\0humblest apologies", 24)}, + {"content-length", absl::string_view("1023" + "\0" + "1024", + 9)}, + {"set-cookie", absl::string_view("chocolate_chips=yes\0raisins=no", 30)}}; + const std::vector<Header> headers = ToHeaders(pairs); + spdy::SpdyHeaderBlock block = ToHeaderBlock(headers); + EXPECT_THAT(block, testing::ElementsAreArray(expected)); +} + +} // namespace +} // namespace test +} // 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 74db309bea3..b9cbcfbd51b 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 @@ -60,6 +60,13 @@ bool RecordingHttp2Visitor::OnEndHeadersForStream(Http2StreamId stream_id) { return true; } +bool RecordingHttp2Visitor::OnDataPaddingLength(Http2StreamId stream_id, + size_t padding_length) { + events_.push_back( + absl::StrFormat("OnDataPaddingLength %d %d", stream_id, padding_length)); + return true; +} + bool RecordingHttp2Visitor::OnBeginDataForStream(Http2StreamId stream_id, size_t payload_length) { events_.push_back( 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 6008725bd0b..3ded946a65f 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 @@ -33,6 +33,8 @@ class QUICHE_NO_EXPORT RecordingHttp2Visitor : public Http2VisitorInterface { absl::string_view name, absl::string_view value) override; bool OnEndHeadersForStream(Http2StreamId stream_id) override; + bool OnDataPaddingLength(Http2StreamId stream_id, + size_t padding_length) override; bool OnBeginDataForStream(Http2StreamId stream_id, size_t payload_length) override; bool OnDataForStream(Http2StreamId stream_id, 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 8cad139ee95..ed9018acf4b 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 @@ -9,12 +9,11 @@ namespace http2 { namespace adapter { namespace test { -std::vector<const Header> ToHeaders( +std::vector<Header> ToHeaders( absl::Span<const std::pair<absl::string_view, absl::string_view>> headers) { - std::vector<const Header> out; - for (const auto& header : headers) { - out.push_back( - std::make_pair(HeaderRep(header.first), HeaderRep(header.second))); + std::vector<Header> out; + for (auto [name, value] : headers) { + out.push_back(std::make_pair(HeaderRep(name), HeaderRep(value))); } return out; } @@ -157,18 +156,16 @@ TestFrameSequence& TestFrameSequence::Priority(Http2StreamId stream_id, 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; + const size_t pos = payload.size() / 2; frames_.push_back(absl::make_unique<spdy::SpdyUnknownIR>( - stream_id, kMetadataFrameType, 0, encoded_payload.substr(0, pos))); + stream_id, kMetadataFrameType, 0, std::string(payload.substr(0, pos)))); frames_.push_back(absl::make_unique<spdy::SpdyUnknownIR>( stream_id, kMetadataFrameType, kMetadataEndFlag, - encoded_payload.substr(pos))); + std::string(payload.substr(pos)))); } else { frames_.push_back(absl::make_unique<spdy::SpdyUnknownIR>( - stream_id, kMetadataFrameType, kMetadataEndFlag, - std::move(encoded_payload))); + stream_id, kMetadataFrameType, kMetadataEndFlag, std::string(payload))); } return *this; } @@ -186,16 +183,6 @@ 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(); - return encoder.EncodeHeaderBlock(block); -} - } // 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 9b630c042b2..0eddf418b92 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 @@ -14,7 +14,7 @@ namespace http2 { namespace adapter { namespace test { -std::vector<const Header> QUICHE_NO_EXPORT ToHeaders( +std::vector<Header> QUICHE_NO_EXPORT ToHeaders( absl::Span<const std::pair<absl::string_view, absl::string_view>> headers); class QUICHE_NO_EXPORT TestFrameSequence { @@ -61,8 +61,6 @@ class QUICHE_NO_EXPORT TestFrameSequence { 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 210cb665913..d56acda34ee 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 @@ -4,9 +4,8 @@ #include "absl/strings/str_format.h" #include "http2/adapter/http2_visitor_interface.h" -#include "common/quiche_endian.h" +#include "common/quiche_data_reader.h" #include "spdy/core/hpack/hpack_encoder.h" -#include "spdy/core/spdy_frame_reader.h" namespace http2 { namespace adapter { @@ -137,7 +136,7 @@ class SpdyControlFrameMatcher bool MatchAndExplain(absl::string_view s, testing::MatchResultListener* listener) const override { - spdy::SpdyFrameReader reader(s.data(), s.size()); + quiche::QuicheDataReader reader(s.data(), s.size()); for (TypeAndOptionalLength expected : expected_types_and_lengths_) { if (!MatchAndExplainOneFrame(expected.first, expected.second, &reader, @@ -146,8 +145,7 @@ class SpdyControlFrameMatcher } } if (!reader.IsDoneReading()) { - size_t bytes_remaining = s.size() - reader.GetBytesConsumed(); - *listener << "; " << bytes_remaining << " bytes left to read!"; + *listener << "; " << reader.BytesRemaining() << " bytes left to read!"; return false; } return true; @@ -155,7 +153,7 @@ class SpdyControlFrameMatcher bool MatchAndExplainOneFrame(spdy::SpdyFrameType expected_type, absl::optional<size_t> expected_length, - spdy::SpdyFrameReader* reader, + quiche::QuicheDataReader* reader, testing::MatchResultListener* listener) const { uint32_t payload_length; if (!reader->ReadUInt24(&payload_length)) { 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 886d0bf9167..be53ba8cab5 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 @@ -110,6 +110,7 @@ class QUICHE_NO_EXPORT TestMetadataSource : public MetadataSource { return (encoded_entries_.size() + max_frame_size - 1) / max_frame_size; } std::pair<int64_t, bool> Pack(uint8_t* dest, size_t dest_len) override; + void OnFailure() override {} private: const std::string encoded_entries_; diff --git a/chromium/net/third_party/quiche/src/http2/core/http2_trace_logging.cc b/chromium/net/third_party/quiche/src/http2/core/http2_trace_logging.cc index 2c1bee38a08..f8f54b68f78 100644 --- a/chromium/net/third_party/quiche/src/http2/core/http2_trace_logging.cc +++ b/chromium/net/third_party/quiche/src/http2/core/http2_trace_logging.cc @@ -189,9 +189,13 @@ spdy::SpdyHeadersHandlerInterface* Http2TraceLogger::OnHeaderFrameStart( << FORMAT_ARG(stream_id); spdy::SpdyHeadersHandlerInterface* result = wrapped_->OnHeaderFrameStart(stream_id); - recording_headers_handler_ = - absl::make_unique<spdy::RecordingHeadersHandler>(result); - result = recording_headers_handler_.get(); + if (is_enabled_()) { + recording_headers_handler_ = + absl::make_unique<spdy::RecordingHeadersHandler>(result); + result = recording_headers_handler_.get(); + } else { + recording_headers_handler_ = nullptr; + } return result; } @@ -328,7 +332,8 @@ bool Http2TraceLogger::OnUnknownFrame(SpdyStreamId stream_id, void Http2TraceLogger::LogReceivedHeaders() const { if (recording_headers_handler_ == nullptr) { - QUICHE_BUG(bug_2794_1) << "Cannot log headers before creating handler."; + // Trace logging was not enabled when the start of the header block was + // received. return; } HTTP2_TRACE_LOG(perspective_, is_enabled_) diff --git a/chromium/net/third_party/quiche/src/http2/decoder/decode_buffer.h b/chromium/net/third_party/quiche/src/http2/decoder/decode_buffer.h index 4757c061a03..3d0f5d5fddf 100644 --- a/chromium/net/third_party/quiche/src/http2/decoder/decode_buffer.h +++ b/chromium/net/third_party/quiche/src/http2/decoder/decode_buffer.h @@ -26,13 +26,15 @@ class DecodeBufferSubset; class QUICHE_EXPORT_PRIVATE DecodeBuffer { public: + // We assume the decode buffers will typically be modest in size (i.e. often a + // few KB, perhaps as high as 100KB). Let's make sure during testing that we + // don't go very high, with 32MB selected rather arbitrarily. This is exposed + // to support testing. + static constexpr size_t kMaxDecodeBufferLength = 1 << 25; + DecodeBuffer(const char* buffer, size_t len) : buffer_(buffer), cursor_(buffer), beyond_(buffer + len) { QUICHE_DCHECK(buffer != nullptr); - // We assume the decode buffers will typically be modest in size (i.e. often - // a few KB, perhaps as high as 100KB). Let's make sure during testing that - // we don't go very high, with 32MB selected rather arbitrarily. - const size_t kMaxDecodeBufferLength = 1 << 25; QUICHE_DCHECK_LE(len, kMaxDecodeBufferLength); } explicit DecodeBuffer(absl::string_view s) diff --git a/chromium/net/third_party/quiche/src/http2/decoder/decode_buffer_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/decode_buffer_test.cc index 685107a6236..54c0fea371b 100644 --- a/chromium/net/third_party/quiche/src/http2/decoder/decode_buffer_test.cc +++ b/chromium/net/third_party/quiche/src/http2/decoder/decode_buffer_test.cc @@ -132,9 +132,14 @@ TEST(DecodeBufferDeathTest, NonNullBufferRequired) { TEST(DecodeBufferDeathTest, ModestBufferSizeRequired) { EXPECT_DEBUG_DEATH( { - const char data[] = "abc"; - size_t len = 0; - DecodeBuffer b(data, ~len); + // This depends on being able to allocate a fairly large array on the + // stack. If that fails, we can instead do this: + // + // std::string data(DecodeBuffer::kMaxDecodeBufferLength + 1, ' '); + // DecodeBuffer b(data.data(), data.size()); + + const char data[DecodeBuffer::kMaxDecodeBufferLength + 1] = {}; + DecodeBuffer b(data, sizeof data); }, "Max.*Length"); } diff --git a/chromium/net/third_party/quiche/src/http2/decoder/decode_http2_structures_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/decode_http2_structures_test.cc index 39b11e99323..89833e58ec9 100644 --- a/chromium/net/third_party/quiche/src/http2/decoder/decode_http2_structures_test.cc +++ b/chromium/net/third_party/quiche/src/http2/decoder/decode_http2_structures_test.cc @@ -22,8 +22,6 @@ #include "http2/tools/http2_frame_builder.h" #include "common/platform/api/quiche_test.h" -using ::testing::AssertionResult; - namespace http2 { namespace test { namespace { diff --git a/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder.cc index 55dea39cdb2..ec2034bb1c4 100644 --- a/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder.cc +++ b/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder.cc @@ -10,7 +10,7 @@ #include "http2/platform/api/http2_bug_tracker.h" #include "http2/platform/api/http2_flag_utils.h" #include "http2/platform/api/http2_flags.h" -#include "http2/platform/api/http2_macros.h" +#include "common/platform/api/quiche_logging.h" namespace http2 { @@ -72,7 +72,7 @@ DecodeStatus Http2FrameDecoder::DecodeFrame(DecodeBuffer* db) { return DiscardPayload(db); } - HTTP2_UNREACHABLE(); + QUICHE_NOTREACHED(); return DecodeStatus::kDecodeError; } diff --git a/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_test.cc index b09a5d086c5..b3fd7233329 100644 --- a/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_test.cc +++ b/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_test.cc @@ -19,7 +19,6 @@ #include "http2/test_tools/http2_random.h" #include "http2/tools/random_decoder_test.h" -using ::testing::AssertionResult; using ::testing::AssertionSuccess; namespace http2 { diff --git a/chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder_test.cc index 7db9ba06cbc..afec9d4a909 100644 --- a/chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder_test.cc +++ b/chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder_test.cc @@ -33,8 +33,6 @@ #include "http2/tools/http2_frame_builder.h" #include "http2/tools/random_decoder_test.h" -using ::testing::AssertionFailure; -using ::testing::AssertionResult; using ::testing::AssertionSuccess; namespace http2 { diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder.cc index 39d3fc9efbd..f95417c6fe8 100644 --- a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder.cc +++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder.cc @@ -6,13 +6,13 @@ #include <stddef.h> +#include "absl/base/macros.h" #include "http2/decoder/decode_buffer.h" #include "http2/decoder/http2_frame_decoder_listener.h" #include "http2/http2_constants.h" #include "http2/http2_structures.h" #include "http2/platform/api/http2_bug_tracker.h" #include "http2/platform/api/http2_logging.h" -#include "http2/platform/api/http2_macros.h" namespace http2 { @@ -72,7 +72,7 @@ DecodeStatus AltSvcPayloadDecoder::ResumeDecodingPayload( switch (payload_state_) { case PayloadState::kStartDecodingStruct: status = state->StartDecodingStructureInPayload(&altsvc_fields_, db); - HTTP2_FALLTHROUGH; + ABSL_FALLTHROUGH_INTENDED; case PayloadState::kMaybeDecodedStruct: if (status == DecodeStatus::kDecodeDone && @@ -95,7 +95,7 @@ DecodeStatus AltSvcPayloadDecoder::ResumeDecodingPayload( state->remaining_payload()); return state->ReportFrameSizeError(); } - HTTP2_FALLTHROUGH; + ABSL_FALLTHROUGH_INTENDED; case PayloadState::kDecodingStrings: return DecodeStrings(state, db); diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder.cc index 5868d43fe35..523ead1d5b1 100644 --- a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder.cc +++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder.cc @@ -6,13 +6,13 @@ #include <stddef.h> +#include "absl/base/macros.h" #include "http2/decoder/decode_buffer.h" #include "http2/decoder/http2_frame_decoder_listener.h" #include "http2/http2_constants.h" #include "http2/http2_structures.h" #include "http2/platform/api/http2_bug_tracker.h" #include "http2/platform/api/http2_logging.h" -#include "http2/platform/api/http2_macros.h" namespace http2 { @@ -97,7 +97,7 @@ DecodeStatus DataPayloadDecoder::ResumeDecodingPayload(FrameDecoderState* state, if (status != DecodeStatus::kDecodeDone) { return status; } - HTTP2_FALLTHROUGH; + ABSL_FALLTHROUGH_INTENDED; case PayloadState::kReadPayload: avail = state->AvailablePayload(db); @@ -110,7 +110,7 @@ DecodeStatus DataPayloadDecoder::ResumeDecodingPayload(FrameDecoderState* state, payload_state_ = PayloadState::kReadPayload; return DecodeStatus::kDecodeInProgress; } - HTTP2_FALLTHROUGH; + ABSL_FALLTHROUGH_INTENDED; case PayloadState::kSkipPadding: // SkipPadding handles the OnPadding callback. diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder_test.cc index c74503bdf37..aa7b5e848c7 100644 --- a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder_test.cc +++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder_test.cc @@ -22,8 +22,6 @@ #include "http2/tools/random_decoder_test.h" #include "common/platform/api/quiche_test.h" -using ::testing::AssertionResult; - namespace http2 { namespace test { diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder.cc index a2cfd09673b..3231feb5de8 100644 --- a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder.cc +++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder.cc @@ -6,13 +6,13 @@ #include <stddef.h> +#include "absl/base/macros.h" #include "http2/decoder/decode_buffer.h" #include "http2/decoder/http2_frame_decoder_listener.h" #include "http2/http2_constants.h" #include "http2/http2_structures.h" #include "http2/platform/api/http2_bug_tracker.h" #include "http2/platform/api/http2_logging.h" -#include "http2/platform/api/http2_macros.h" namespace http2 { @@ -74,7 +74,7 @@ DecodeStatus GoAwayPayloadDecoder::ResumeDecodingPayload( switch (payload_state_) { case PayloadState::kStartDecodingFixedFields: status = state->StartDecodingStructureInPayload(&goaway_fields_, db); - HTTP2_FALLTHROUGH; + ABSL_FALLTHROUGH_INTENDED; case PayloadState::kHandleFixedFieldsStatus: if (status == DecodeStatus::kDecodeDone) { @@ -92,7 +92,7 @@ DecodeStatus GoAwayPayloadDecoder::ResumeDecodingPayload( payload_state_ = PayloadState::kResumeDecodingFixedFields; return status; } - HTTP2_FALLTHROUGH; + ABSL_FALLTHROUGH_INTENDED; case PayloadState::kReadOpaqueData: // The opaque data is all the remains to be decoded, so anything left diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder.cc index a312fb944b6..5ae1f996cfd 100644 --- a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder.cc +++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder.cc @@ -6,13 +6,13 @@ #include <stddef.h> +#include "absl/base/macros.h" #include "http2/decoder/decode_buffer.h" #include "http2/decoder/http2_frame_decoder_listener.h" #include "http2/http2_constants.h" #include "http2/http2_structures.h" #include "http2/platform/api/http2_bug_tracker.h" #include "http2/platform/api/http2_logging.h" -#include "http2/platform/api/http2_macros.h" namespace http2 { @@ -129,7 +129,7 @@ DecodeStatus HeadersPayloadDecoder::ResumeDecodingPayload( payload_state_ = PayloadState::kReadPayload; continue; } - HTTP2_FALLTHROUGH; + ABSL_FALLTHROUGH_INTENDED; case PayloadState::kStartDecodingPriorityFields: status = state->StartDecodingStructureInPayload(&priority_fields_, db); @@ -138,7 +138,7 @@ DecodeStatus HeadersPayloadDecoder::ResumeDecodingPayload( return status; } state->listener()->OnHeadersPriority(priority_fields_); - HTTP2_FALLTHROUGH; + ABSL_FALLTHROUGH_INTENDED; case PayloadState::kReadPayload: avail = state->AvailablePayload(db); @@ -151,7 +151,7 @@ DecodeStatus HeadersPayloadDecoder::ResumeDecodingPayload( payload_state_ = PayloadState::kReadPayload; return DecodeStatus::kDecodeInProgress; } - HTTP2_FALLTHROUGH; + ABSL_FALLTHROUGH_INTENDED; case PayloadState::kSkipPadding: // SkipPadding handles the OnPadding callback. diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/priority_update_payload_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/priority_update_payload_decoder.cc index bf7821625cf..ff6758c3729 100644 --- a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/priority_update_payload_decoder.cc +++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/priority_update_payload_decoder.cc @@ -6,13 +6,13 @@ #include <stddef.h> +#include "absl/base/macros.h" #include "http2/decoder/decode_buffer.h" #include "http2/decoder/http2_frame_decoder_listener.h" #include "http2/http2_constants.h" #include "http2/http2_structures.h" #include "http2/platform/api/http2_bug_tracker.h" #include "http2/platform/api/http2_logging.h" -#include "http2/platform/api/http2_macros.h" namespace http2 { @@ -76,7 +76,7 @@ DecodeStatus PriorityUpdatePayloadDecoder::ResumeDecodingPayload( case PayloadState::kStartDecodingFixedFields: status = state->StartDecodingStructureInPayload( &priority_update_fields_, db); - HTTP2_FALLTHROUGH; + ABSL_FALLTHROUGH_INTENDED; case PayloadState::kHandleFixedFieldsStatus: if (status == DecodeStatus::kDecodeDone) { @@ -95,7 +95,7 @@ DecodeStatus PriorityUpdatePayloadDecoder::ResumeDecodingPayload( payload_state_ = PayloadState::kResumeDecodingFixedFields; return status; } - HTTP2_FALLTHROUGH; + ABSL_FALLTHROUGH_INTENDED; case PayloadState::kReadPriorityFieldValue: // Anything left in the decode buffer is the Priority Field Value. diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder.cc index 95fb5f5e662..8dff0b9fe38 100644 --- a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder.cc +++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder.cc @@ -6,13 +6,13 @@ #include <stddef.h> +#include "absl/base/macros.h" #include "http2/decoder/decode_buffer.h" #include "http2/decoder/http2_frame_decoder_listener.h" #include "http2/http2_constants.h" #include "http2/http2_structures.h" #include "http2/platform/api/http2_bug_tracker.h" #include "http2/platform/api/http2_logging.h" -#include "http2/platform/api/http2_macros.h" namespace http2 { @@ -95,7 +95,7 @@ DecodeStatus PushPromisePayloadDecoder::ResumeDecodingPayload( payload_state_ = PayloadState::kReadPadLength; return status; } - HTTP2_FALLTHROUGH; + ABSL_FALLTHROUGH_INTENDED; case PayloadState::kStartDecodingPushPromiseFields: status = @@ -107,7 +107,7 @@ DecodeStatus PushPromisePayloadDecoder::ResumeDecodingPayload( // Finished decoding the Promised Stream ID. Can now tell the listener // that we're starting to decode a PUSH_PROMISE frame. ReportPushPromise(state); - HTTP2_FALLTHROUGH; + ABSL_FALLTHROUGH_INTENDED; case PayloadState::kReadPayload: QUICHE_DCHECK_LT(state->remaining_payload(), @@ -131,7 +131,7 @@ DecodeStatus PushPromisePayloadDecoder::ResumeDecodingPayload( payload_state_ = PayloadState::kReadPayload; return DecodeStatus::kDecodeInProgress; } - HTTP2_FALLTHROUGH; + ABSL_FALLTHROUGH_INTENDED; case PayloadState::kSkipPadding: // SkipPadding handles the OnPadding callback. diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder_test.cc index 1f2ff39088d..216eba4d78c 100644 --- a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder_test.cc +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder_test.cc @@ -20,8 +20,6 @@ #include "http2/tools/random_decoder_test.h" #include "common/platform/api/quiche_test.h" -using ::testing::AssertionSuccess; - namespace http2 { namespace test { namespace { diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.cc index 3140a19e5f5..92c7b25bb39 100644 --- a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.cc +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.cc @@ -8,7 +8,7 @@ #include "http2/http2_constants.h" #include "http2/platform/api/http2_logging.h" -#include "http2/platform/api/http2_macros.h" +#include "common/platform/api/quiche_logging.h" namespace http2 { namespace { @@ -26,13 +26,16 @@ std::string ExtractString(HpackDecoderStringBuffer* string_buffer) { } // namespace HpackDecoderState::HpackDecoderState(HpackDecoderListener* listener) - : listener_(HTTP2_DIE_IF_NULL(listener)), + : listener_(listener), final_header_table_size_(Http2SettingsInfo::DefaultHeaderTableSize()), lowest_header_table_size_(final_header_table_size_), require_dynamic_table_size_update_(false), allow_dynamic_table_size_update_(true), saw_dynamic_table_size_update_(false), - error_(HpackDecodingError::kOk) {} + error_(HpackDecodingError::kOk) { + QUICHE_CHECK(listener_); +} + HpackDecoderState::~HpackDecoderState() = default; void HpackDecoderState::ApplyHeaderTableSizeSetting( diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_test.cc index c160bfe30bf..c1439f9bc6b 100644 --- a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_test.cc +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_test.cc @@ -25,7 +25,6 @@ #include "http2/tools/random_util.h" #include "common/platform/api/quiche_test.h" -using ::testing::AssertionFailure; using ::testing::AssertionResult; using ::testing::AssertionSuccess; using ::testing::ElementsAreArray; diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.cc index deb07a6dcdc..13022484937 100644 --- a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.cc +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.cc @@ -8,11 +8,11 @@ #include <cstdint> +#include "absl/base/macros.h" #include "http2/platform/api/http2_bug_tracker.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" namespace http2 { namespace { @@ -112,7 +112,7 @@ DecodeStatus HpackEntryDecoder::Resume(DecodeBuffer* db, return status; } state_ = EntryDecoderState::kDecodedType; - HTTP2_FALLTHROUGH; + ABSL_FALLTHROUGH_INTENDED; case EntryDecoderState::kDecodedType: // entry_type_decoder_ returned kDecodeDone, now need to decide how @@ -144,7 +144,7 @@ DecodeStatus HpackEntryDecoder::Resume(DecodeBuffer* db, return status; } state_ = EntryDecoderState::kStartDecodingValue; - HTTP2_FALLTHROUGH; + ABSL_FALLTHROUGH_INTENDED; case EntryDecoderState::kStartDecodingValue: HTTP2_DVLOG(1) << "kStartDecodingValue: db->Remaining=" diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_test.cc index b885b1891e7..32eeaf412e2 100644 --- a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_test.cc +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_test.cc @@ -15,8 +15,6 @@ #include "http2/tools/random_decoder_test.h" #include "common/platform/api/quiche_test.h" -using ::testing::AssertionResult; - namespace http2 { namespace test { namespace { diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder_test.cc index 88520649bab..8babd809e82 100644 --- a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder_test.cc +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder_test.cc @@ -12,8 +12,6 @@ #include "http2/tools/random_decoder_test.h" #include "common/platform/api/quiche_test.h" -using ::testing::AssertionFailure; -using ::testing::AssertionResult; using ::testing::AssertionSuccess; namespace http2 { diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.h index b861d23e63f..9cddc67f9b4 100644 --- a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.h +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.h @@ -15,11 +15,11 @@ #include <cstdint> #include <string> +#include "absl/base/macros.h" #include "http2/decoder/decode_buffer.h" #include "http2/decoder/decode_status.h" #include "http2/hpack/varint/hpack_varint_decoder.h" #include "http2/platform/api/http2_logging.h" -#include "http2/platform/api/http2_macros.h" #include "common/platform/api/quiche_export.h" namespace http2 { @@ -96,7 +96,7 @@ class QUICHE_EXPORT_PRIVATE HpackStringDecoder { // buffer, and hence this fall through skips another trip through the // switch above and more importantly skips setting the state_ variable // again in those cases where we don't need it. - HTTP2_FALLTHROUGH; + ABSL_FALLTHROUGH_INTENDED; case kDecodingString: HTTP2_DVLOG(2) << "kDecodingString: db->Remaining=" << db->Remaining() diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_test.cc index 0571f8dc56c..6ff332f92a6 100644 --- a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_test.cc +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_test.cc @@ -15,8 +15,6 @@ #include "http2/tools/random_decoder_test.h" #include "common/platform/api/quiche_test.h" -using ::testing::AssertionResult; - namespace http2 { namespace test { namespace { 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 5f3e63e2315..1752ba524a2 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 @@ -8,7 +8,7 @@ #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 "common/platform/api/quiche_logging.h" #include "common/quiche_text_utils.h" namespace http2 { @@ -21,7 +21,8 @@ HpackWholeEntryBuffer::HpackWholeEntryBuffer(HpackWholeEntryListener* listener, HpackWholeEntryBuffer::~HpackWholeEntryBuffer() = default; void HpackWholeEntryBuffer::set_listener(HpackWholeEntryListener* listener) { - listener_ = HTTP2_DIE_IF_NULL(listener); + QUICHE_CHECK(listener); + listener_ = listener; } void HpackWholeEntryBuffer::set_max_string_size_bytes( 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 395706f219c..1e160862b22 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 @@ -16,8 +16,6 @@ #include "http2/tools/random_decoder_test.h" #include "common/platform/api/quiche_test.h" -using ::testing::AssertionResult; - namespace http2 { namespace test { namespace { 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 98a0478d2c5..671eb4f33a0 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 @@ -15,7 +15,6 @@ #include "common/platform/api/quiche_test.h" #include "common/quiche_text_utils.h" -using ::testing::AssertionResult; using ::testing::AssertionSuccess; using ::testing::Combine; using ::testing::Range; 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 aff6af3730e..d204b333966 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 @@ -15,20 +15,19 @@ #include "http2/tools/random_decoder_test.h" #include "common/platform/api/quiche_test.h" -using ::testing::AssertionFailure; using ::testing::AssertionSuccess; namespace http2 { namespace test { namespace { -class HpackVarintDecoderTest : public RandomDecoderTest, - public ::testing::WithParamInterface< - ::testing::tuple<uint8_t, const char*>> { +class HpackVarintDecoderTest + : public RandomDecoderTest, + public ::testing::WithParamInterface<std::tuple<uint8_t, const char*>> { protected: HpackVarintDecoderTest() - : high_bits_(::testing::get<0>(GetParam())), - suffix_(absl::HexStringToBytes(::testing::get<1>(GetParam()))), + : high_bits_(std::get<0>(GetParam())), + suffix_(absl::HexStringToBytes(std::get<1>(GetParam()))), prefix_length_(0) {} void DecodeExpectSuccess(absl::string_view data, diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_macros.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_macros.h deleted file mode 100644 index 0be5e89b4ec..00000000000 --- a/chromium/net/third_party/quiche/src/http2/platform/api/http2_macros.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_MACROS_H_ -#define QUICHE_HTTP2_PLATFORM_API_HTTP2_MACROS_H_ - -#include "net/http2/platform/impl/http2_macros_impl.h" - -#define HTTP2_FALLTHROUGH HTTP2_FALLTHROUGH_IMPL -#define HTTP2_UNREACHABLE() HTTP2_UNREACHABLE_IMPL() -#define HTTP2_DIE_IF_NULL(ptr) HTTP2_DIE_IF_NULL_IMPL(ptr) - -#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_MACROS_H_ diff --git a/chromium/net/third_party/quiche/src/http2/tools/random_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/tools/random_decoder_test.cc index 08f7a60e09a..a49e97f933b 100644 --- a/chromium/net/third_party/quiche/src/http2/tools/random_decoder_test.cc +++ b/chromium/net/third_party/quiche/src/http2/tools/random_decoder_test.cc @@ -16,9 +16,7 @@ #include "http2/platform/api/http2_test_helpers.h" #include "common/platform/api/quiche_test.h" -using ::testing::AssertionFailure; using ::testing::AssertionResult; -using ::testing::AssertionSuccess; namespace http2 { namespace test { 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 fbf1343f58b..c634db5d4a3 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 @@ -29,15 +29,14 @@ #include "quic/test_tools/simulator/simulator.h" #include "quic/test_tools/simulator/switch.h" #include "quic/test_tools/simulator/traffic_policer.h" +#include "common/platform/api/quiche_command_line_flags.h" using testing::AllOf; using testing::Ge; using testing::Le; -DEFINE_QUIC_COMMAND_LINE_FLAG( - std::string, - quic_bbr2_test_regression_mode, - "", +DEFINE_QUICHE_COMMAND_LINE_FLAG( + std::string, quic_bbr2_test_regression_mode, "", "One of a) 'record' to record test result (one file per test), or " "b) 'regress' to regress against recorded results, or " "c) <anything else> for non-regression mode."); diff --git a/chromium/net/third_party/quiche/src/quic/core/congestion_control/bbr_sender_test.cc b/chromium/net/third_party/quiche/src/quic/core/congestion_control/bbr_sender_test.cc index 8973f94728a..caba9b1c7f4 100644 --- a/chromium/net/third_party/quiche/src/quic/core/congestion_control/bbr_sender_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/congestion_control/bbr_sender_test.cc @@ -28,15 +28,14 @@ #include "quic/test_tools/simulator/quic_endpoint.h" #include "quic/test_tools/simulator/simulator.h" #include "quic/test_tools/simulator/switch.h" +#include "common/platform/api/quiche_command_line_flags.h" using testing::AllOf; using testing::Ge; using testing::Le; -DEFINE_QUIC_COMMAND_LINE_FLAG( - std::string, - quic_bbr_test_regression_mode, - "", +DEFINE_QUICHE_COMMAND_LINE_FLAG( + std::string, quic_bbr_test_regression_mode, "", "One of a) 'record' to record test result (one file per test), or " "b) 'regress' to regress against recorded results, or " "c) <anything else> for non-regression mode."); diff --git a/chromium/net/third_party/quiche/src/quic/core/congestion_control/pacing_sender.h b/chromium/net/third_party/quiche/src/quic/core/congestion_control/pacing_sender.h index d473a38cd91..b2401a2b6c7 100644 --- a/chromium/net/third_party/quiche/src/quic/core/congestion_control/pacing_sender.h +++ b/chromium/net/third_party/quiche/src/quic/core/congestion_control/pacing_sender.h @@ -79,6 +79,8 @@ class QUIC_EXPORT_PRIVATE PacingSender { return {ideal_next_packet_send_time_, allow_burst}; } + uint32_t initial_burst_size() const { return initial_burst_size_; } + protected: uint32_t lumpy_tokens() const { return lumpy_tokens_; } diff --git a/chromium/net/third_party/quiche/src/quic/core/congestion_control/send_algorithm_interface.h b/chromium/net/third_party/quiche/src/quic/core/congestion_control/send_algorithm_interface.h index 2fa1217a9ea..42716033c6d 100644 --- a/chromium/net/third_party/quiche/src/quic/core/congestion_control/send_algorithm_interface.h +++ b/chromium/net/third_party/quiche/src/quic/core/congestion_control/send_algorithm_interface.h @@ -33,27 +33,28 @@ class QUIC_EXPORT_PRIVATE SendAlgorithmInterface { public: // Network Params for AdjustNetworkParameters. struct QUIC_NO_EXPORT NetworkParams { - NetworkParams() - : NetworkParams(QuicBandwidth::Zero(), QuicTime::Delta::Zero(), false) { - } + NetworkParams() = default; NetworkParams(const QuicBandwidth& bandwidth, const QuicTime::Delta& rtt, bool allow_cwnd_to_decrease) : bandwidth(bandwidth), rtt(rtt), allow_cwnd_to_decrease(allow_cwnd_to_decrease) {} + explicit NetworkParams(int burst_token) : burst_token(burst_token) {} bool operator==(const NetworkParams& other) const { return bandwidth == other.bandwidth && rtt == other.rtt && max_initial_congestion_window == other.max_initial_congestion_window && + burst_token == other.burst_token && allow_cwnd_to_decrease == other.allow_cwnd_to_decrease; } - QuicBandwidth bandwidth; - QuicTime::Delta rtt; + QuicBandwidth bandwidth = QuicBandwidth::Zero(); + QuicTime::Delta rtt = QuicTime::Delta::Zero(); int max_initial_congestion_window = 0; - bool allow_cwnd_to_decrease; + int burst_token = 0; + bool allow_cwnd_to_decrease = false; }; static SendAlgorithmInterface* Create( diff --git a/chromium/net/third_party/quiche/src/quic/core/congestion_control/tcp_cubic_sender_bytes.h b/chromium/net/third_party/quiche/src/quic/core/congestion_control/tcp_cubic_sender_bytes.h index cf2a532e463..be4d2b2ad8d 100644 --- a/chromium/net/third_party/quiche/src/quic/core/congestion_control/tcp_cubic_sender_bytes.h +++ b/chromium/net/third_party/quiche/src/quic/core/congestion_control/tcp_cubic_sender_bytes.h @@ -96,8 +96,7 @@ class QUIC_EXPORT_PRIVATE TcpCubicSenderBytes : public SendAlgorithmInterface { QuicTime::Delta rtt); void SetMinCongestionWindowInPackets(QuicPacketCount congestion_window); void ExitSlowstart(); - void OnPacketLost(QuicPacketNumber largest_loss, - QuicByteCount lost_bytes, + void OnPacketLost(QuicPacketNumber packet_number, QuicByteCount lost_bytes, QuicByteCount prior_in_flight); void MaybeIncreaseCwnd(QuicPacketNumber acked_packet_number, QuicByteCount acked_bytes, diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/boring_utils.h b/chromium/net/third_party/quiche/src/quic/core/crypto/boring_utils.h index 2927b891f98..d623fa25af5 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/boring_utils.h +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/boring_utils.h @@ -23,6 +23,12 @@ inline QUIC_EXPORT_PRIVATE CBS StringPieceToCbs(absl::string_view piece) { return result; } +inline QUIC_EXPORT_PRIVATE bool AddStringToCbb(CBB* cbb, + absl::string_view piece) { + return 1 == CBB_add_bytes(cbb, reinterpret_cast<const uint8_t*>(piece.data()), + piece.size()); +} + } // namespace quic #endif // QUICHE_QUIC_CORE_CRYPTO_BORING_UTILS_H_ diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/certificate_util.cc b/chromium/net/third_party/quiche/src/quic/core/crypto/certificate_util.cc new file mode 100644 index 00000000000..550adffa582 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/certificate_util.cc @@ -0,0 +1,280 @@ +// 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/crypto/certificate_util.h" + +#include "absl/strings/str_format.h" +#include "absl/strings/str_split.h" +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/bn.h" +#include "third_party/boringssl/src/include/openssl/bytestring.h" +#include "third_party/boringssl/src/include/openssl/digest.h" +#include "third_party/boringssl/src/include/openssl/ec_key.h" +#include "third_party/boringssl/src/include/openssl/mem.h" +#include "third_party/boringssl/src/include/openssl/pkcs7.h" +#include "third_party/boringssl/src/include/openssl/pool.h" +#include "third_party/boringssl/src/include/openssl/rsa.h" +#include "third_party/boringssl/src/include/openssl/stack.h" +#include "quic/core/crypto/boring_utils.h" +#include "quic/platform/api/quic_logging.h" + +namespace quic { +namespace { +bool AddEcdsa256SignatureAlgorithm(CBB* cbb) { + // See RFC 5758. This is the encoding of OID 1.2.840.10045.4.3.2. + static const uint8_t kEcdsaWithSha256[] = {0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x04, 0x03, 0x02}; + + // An AlgorithmIdentifier is described in RFC 5280, 4.1.1.2. + CBB sequence, oid; + if (!CBB_add_asn1(cbb, &sequence, CBS_ASN1_SEQUENCE) || + !CBB_add_asn1(&sequence, &oid, CBS_ASN1_OBJECT)) { + return false; + } + + if (!CBB_add_bytes(&oid, kEcdsaWithSha256, sizeof(kEcdsaWithSha256))) { + return false; + } + + // RFC 5758, section 3.2: ecdsa-with-sha256 MUST omit the parameters field. + return CBB_flush(cbb); +} + +// Adds an X.509 Name with the specified distinguished name to |cbb|. +bool AddName(CBB* cbb, absl::string_view name) { + // See RFC 4519. + static const uint8_t kCommonName[] = {0x55, 0x04, 0x03}; + static const uint8_t kCountryName[] = {0x55, 0x04, 0x06}; + static const uint8_t kOrganizationName[] = {0x55, 0x04, 0x0a}; + static const uint8_t kOrganizationalUnitName[] = {0x55, 0x04, 0x0b}; + + std::vector<std::string> attributes = + absl::StrSplit(name, ',', absl::SkipEmpty()); + + if (attributes.empty()) { + QUIC_LOG(ERROR) << "Missing DN or wrong format"; + return false; + } + + // See RFC 5280, section 4.1.2.4. + CBB rdns; + if (!CBB_add_asn1(cbb, &rdns, CBS_ASN1_SEQUENCE)) { + return false; + } + + for (const std::string& attribute : attributes) { + std::vector<std::string> parts = + absl::StrSplit(absl::StripAsciiWhitespace(attribute), '='); + if (parts.size() != 2) { + QUIC_LOG(ERROR) << "Wrong DN format at " + attribute; + return false; + } + + const std::string& type_string = parts[0]; + const std::string& value_string = parts[1]; + absl::Span<const uint8_t> type_bytes; + if (type_string == "CN") { + type_bytes = kCommonName; + } else if (type_string == "C") { + type_bytes = kCountryName; + } else if (type_string == "O") { + type_bytes = kOrganizationName; + } else if (type_string == "OU") { + type_bytes = kOrganizationalUnitName; + } else { + QUIC_LOG(ERROR) << "Unrecognized type " + type_string; + return false; + } + + CBB rdn, attr, type, value; + if (!CBB_add_asn1(&rdns, &rdn, CBS_ASN1_SET) || + !CBB_add_asn1(&rdn, &attr, CBS_ASN1_SEQUENCE) || + !CBB_add_asn1(&attr, &type, CBS_ASN1_OBJECT) || + !CBB_add_bytes(&type, type_bytes.data(), type_bytes.size()) || + !CBB_add_asn1(&attr, &value, + type_string == "C" ? CBS_ASN1_PRINTABLESTRING + : CBS_ASN1_UTF8STRING) || + !AddStringToCbb(&value, value_string) || !CBB_flush(&rdns)) { + return false; + } + } + if (!CBB_flush(cbb)) { + return false; + } + return true; +} + +bool CBBAddTime(CBB* cbb, const CertificateTimestamp& timestamp) { + CBB child; + std::string formatted_time; + + // Per RFC 5280, 4.1.2.5, times which fit in UTCTime must be encoded as + // UTCTime rather than GeneralizedTime. + const bool is_utc_time = (1950 <= timestamp.year && timestamp.year < 2050); + if (is_utc_time) { + uint16_t year = timestamp.year - 1900; + if (year >= 100) { + year -= 100; + } + formatted_time = absl::StrFormat("%02d", year); + if (!CBB_add_asn1(cbb, &child, CBS_ASN1_UTCTIME)) { + return false; + } + } else { + formatted_time = absl::StrFormat("%04d", timestamp.year); + if (!CBB_add_asn1(cbb, &child, CBS_ASN1_GENERALIZEDTIME)) { + return false; + } + } + + absl::StrAppendFormat(&formatted_time, "%02d%02d%02d%02d%02dZ", + timestamp.month, timestamp.day, timestamp.hour, + timestamp.minute, timestamp.second); + + static const size_t kGeneralizedTimeLength = 15; + static const size_t kUTCTimeLength = 13; + QUICHE_DCHECK_EQ(formatted_time.size(), + is_utc_time ? kUTCTimeLength : kGeneralizedTimeLength); + + return AddStringToCbb(&child, formatted_time) && CBB_flush(cbb); +} + +bool CBBAddExtension(CBB* extensions, absl::Span<const uint8_t> oid, + bool critical, absl::Span<const uint8_t> contents) { + CBB extension, cbb_oid, cbb_contents; + if (!CBB_add_asn1(extensions, &extension, CBS_ASN1_SEQUENCE) || + !CBB_add_asn1(&extension, &cbb_oid, CBS_ASN1_OBJECT) || + !CBB_add_bytes(&cbb_oid, oid.data(), oid.size()) || + (critical && !CBB_add_asn1_bool(&extension, 1)) || + !CBB_add_asn1(&extension, &cbb_contents, CBS_ASN1_OCTETSTRING) || + !CBB_add_bytes(&cbb_contents, contents.data(), contents.size()) || + !CBB_flush(extensions)) { + return false; + } + + return true; +} + +bool IsEcdsa256Key(const EVP_PKEY& evp_key) { + if (EVP_PKEY_id(&evp_key) != EVP_PKEY_EC) { + return false; + } + const EC_KEY* key = EVP_PKEY_get0_EC_KEY(&evp_key); + if (key == nullptr) { + return false; + } + const EC_GROUP* group = EC_KEY_get0_group(key); + if (group == nullptr) { + return false; + } + return EC_GROUP_get_curve_name(group) == NID_X9_62_prime256v1; +} + +} // namespace + +bssl::UniquePtr<EVP_PKEY> MakeKeyPairForSelfSignedCertificate() { + bssl::UniquePtr<EVP_PKEY_CTX> context( + EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr)); + if (!context) { + return nullptr; + } + if (EVP_PKEY_keygen_init(context.get()) != 1) { + return nullptr; + } + if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(context.get(), + NID_X9_62_prime256v1) != 1) { + return nullptr; + } + EVP_PKEY* raw_key = nullptr; + if (EVP_PKEY_keygen(context.get(), &raw_key) != 1) { + return nullptr; + } + return bssl::UniquePtr<EVP_PKEY>(raw_key); +} + +std::string CreateSelfSignedCertificate(EVP_PKEY& key, + const CertificateOptions& options) { + std::string error; + if (!IsEcdsa256Key(key)) { + QUIC_LOG(ERROR) << "CreateSelfSignedCert only accepts ECDSA P-256 keys"; + return error; + } + + // See RFC 5280, section 4.1. First, construct the TBSCertificate. + bssl::ScopedCBB cbb; + CBB tbs_cert, version, validity; + uint8_t* tbs_cert_bytes; + size_t tbs_cert_len; + + if (!CBB_init(cbb.get(), 64) || + !CBB_add_asn1(cbb.get(), &tbs_cert, CBS_ASN1_SEQUENCE) || + !CBB_add_asn1(&tbs_cert, &version, + CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) || + !CBB_add_asn1_uint64(&version, 2) || // X.509 version 3 + !CBB_add_asn1_uint64(&tbs_cert, options.serial_number) || + !AddEcdsa256SignatureAlgorithm(&tbs_cert) || // signature algorithm + !AddName(&tbs_cert, options.subject) || // issuer + !CBB_add_asn1(&tbs_cert, &validity, CBS_ASN1_SEQUENCE) || + !CBBAddTime(&validity, options.validity_start) || + !CBBAddTime(&validity, options.validity_end) || + !AddName(&tbs_cert, options.subject) || // subject + !EVP_marshal_public_key(&tbs_cert, &key)) { // subjectPublicKeyInfo + return error; + } + + CBB outer_extensions, extensions; + if (!CBB_add_asn1(&tbs_cert, &outer_extensions, + 3 | CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED) || + !CBB_add_asn1(&outer_extensions, &extensions, CBS_ASN1_SEQUENCE)) { + return error; + } + + // Key Usage + constexpr uint8_t kKeyUsageOid[] = {0x55, 0x1d, 0x0f}; + constexpr uint8_t kKeyUsageContent[] = { + 0x3, // BIT STRING + 0x2, // Length + 0x0, // Unused bits + 0x80, // bit(0): digitalSignature + }; + CBBAddExtension(&extensions, kKeyUsageOid, true, kKeyUsageContent); + + // TODO(wub): Add more extensions here if needed. + + if (!CBB_finish(cbb.get(), &tbs_cert_bytes, &tbs_cert_len)) { + return error; + } + + bssl::UniquePtr<uint8_t> delete_tbs_cert_bytes(tbs_cert_bytes); + + // Sign the TBSCertificate and write the entire certificate. + CBB cert, signature; + bssl::ScopedEVP_MD_CTX ctx; + uint8_t* sig_out; + size_t sig_len; + uint8_t* cert_bytes; + size_t cert_len; + if (!CBB_init(cbb.get(), tbs_cert_len) || + !CBB_add_asn1(cbb.get(), &cert, CBS_ASN1_SEQUENCE) || + !CBB_add_bytes(&cert, tbs_cert_bytes, tbs_cert_len) || + !AddEcdsa256SignatureAlgorithm(&cert) || + !CBB_add_asn1(&cert, &signature, CBS_ASN1_BITSTRING) || + !CBB_add_u8(&signature, 0 /* no unused bits */) || + !EVP_DigestSignInit(ctx.get(), nullptr, EVP_sha256(), nullptr, &key) || + // Compute the maximum signature length. + !EVP_DigestSign(ctx.get(), nullptr, &sig_len, tbs_cert_bytes, + tbs_cert_len) || + !CBB_reserve(&signature, &sig_out, sig_len) || + // Actually sign the TBSCertificate. + !EVP_DigestSign(ctx.get(), sig_out, &sig_len, tbs_cert_bytes, + tbs_cert_len) || + !CBB_did_write(&signature, sig_len) || + !CBB_finish(cbb.get(), &cert_bytes, &cert_len)) { + return error; + } + bssl::UniquePtr<uint8_t> delete_cert_bytes(cert_bytes); + return std::string(reinterpret_cast<char*>(cert_bytes), cert_len); +} + +} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/certificate_util.h b/chromium/net/third_party/quiche/src/quic/core/crypto/certificate_util.h new file mode 100644 index 00000000000..ebc1cf48d6e --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/certificate_util.h @@ -0,0 +1,46 @@ +// 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_CRYPTO_CERTIFICATE_UTIL_H_ +#define QUICHE_QUIC_CORE_CRYPTO_CERTIFICATE_UTIL_H_ + +#include <string> + +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/evp.h" +#include "quic/core/quic_time.h" +#include "quic/platform/api/quic_export.h" + +namespace quic { + +struct QUIC_NO_EXPORT CertificateTimestamp { + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; +}; + +struct QUIC_NO_EXPORT CertificateOptions { + absl::string_view subject; + uint64_t serial_number; + CertificateTimestamp validity_start; // a.k.a not_valid_before + CertificateTimestamp validity_end; // a.k.a not_valid_after +}; + +// Creates a ECDSA P-256 key pair. +QUIC_EXPORT_PRIVATE bssl::UniquePtr<EVP_PKEY> +MakeKeyPairForSelfSignedCertificate(); + +// Creates a self-signed, DER-encoded X.509 certificate. +// |key| must be a ECDSA P-256 key. +// This is mostly stolen from Chromium's net/cert/x509_util.h, with +// modifications to make it work in QUICHE. +QUIC_EXPORT_PRIVATE std::string CreateSelfSignedCertificate( + EVP_PKEY& key, const CertificateOptions& options); + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_CERTIFICATE_UTIL_H_ diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/certificate_util_test.cc b/chromium/net/third_party/quiche/src/quic/core/crypto/certificate_util_test.cc new file mode 100644 index 00000000000..1ffe6f551ee --- /dev/null +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/certificate_util_test.cc @@ -0,0 +1,49 @@ +// 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/crypto/certificate_util.h" + +#include "third_party/boringssl/src/include/openssl/ssl.h" +#include "quic/core/crypto/certificate_view.h" +#include "quic/platform/api/quic_test.h" +#include "quic/platform/api/quic_test_output.h" + +namespace quic { +namespace test { +namespace { + +TEST(CertificateUtilTest, CreateSelfSignedCertificate) { + bssl::UniquePtr<EVP_PKEY> key = MakeKeyPairForSelfSignedCertificate(); + ASSERT_NE(key, nullptr); + + CertificatePrivateKey cert_key(std::move(key)); + + CertificateOptions options; + options.subject = "CN=subject"; + options.serial_number = 0x12345678; + options.validity_start = {2020, 1, 1, 0, 0, 0}; + options.validity_end = {2049, 12, 31, 0, 0, 0}; + std::string der_cert = + CreateSelfSignedCertificate(*cert_key.private_key(), options); + ASSERT_FALSE(der_cert.empty()); + + QuicSaveTestOutput("CertificateUtilTest_CreateSelfSignedCert.crt", der_cert); + + std::unique_ptr<CertificateView> cert_view = + CertificateView::ParseSingleCertificate(der_cert); + ASSERT_NE(cert_view, nullptr); + EXPECT_EQ(cert_view->public_key_type(), PublicKeyType::kP256); + + absl::optional<std::string> subject = cert_view->GetHumanReadableSubject(); + ASSERT_TRUE(subject.has_value()); + EXPECT_EQ(*subject, options.subject); + + EXPECT_TRUE( + cert_key.ValidForSignatureAlgorithm(SSL_SIGN_ECDSA_SECP256R1_SHA256)); + EXPECT_TRUE(cert_key.MatchesPublicKey(*cert_view)); +} + +} // namespace +} // namespace test +} // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/certificate_view.cc b/chromium/net/third_party/quiche/src/quic/core/crypto/certificate_view.cc index f0b1c18a195..12866670783 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/certificate_view.cc +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/certificate_view.cc @@ -49,14 +49,6 @@ constexpr uint8_t kX509Version[] = {0x02, 0x01, 0x02}; // 2.5.29.17 constexpr uint8_t kSubjectAltNameOid[] = {0x55, 0x1d, 0x11}; -enum class PublicKeyType { - kRsa, - kP256, - kP384, - kEd25519, - kUnknown, -}; - PublicKeyType PublicKeyTypeFromKey(EVP_PKEY* public_key) { switch (EVP_PKEY_id(public_key)) { case EVP_PKEY_RSA: @@ -176,6 +168,22 @@ absl::optional<std::string> DistinguishedNameToString(CBS input) { } // namespace +std::string PublicKeyTypeToString(PublicKeyType type) { + switch (type) { + case PublicKeyType::kRsa: + return "RSA"; + case PublicKeyType::kP256: + return "ECDSA P-256"; + case PublicKeyType::kP384: + return "ECDSA P-384"; + case PublicKeyType::kEd25519: + return "Ed25519"; + case PublicKeyType::kUnknown: + return "unknown"; + } + return ""; +} + absl::optional<quic::QuicWallTime> ParseDerTime(unsigned tag, absl::string_view payload) { if (tag != CBS_ASN1_GENERALIZEDTIME && tag != CBS_ASN1_UTCTIME) { @@ -469,11 +477,13 @@ std::vector<std::string> CertificateView::LoadPemFromStream( } } +PublicKeyType CertificateView::public_key_type() const { + return PublicKeyTypeFromKey(public_key_.get()); +} + bool CertificateView::ValidatePublicKeyParameters() { - // The profile here affects what certificates can be used: - // (1) when QUIC is used as a server library without any custom certificate - // provider logic, - // (2) when QuicTransport is handling self-signed certificates. + // The profile here affects what certificates can be used when QUIC is used as + // a server library without any custom certificate provider logic. // The goal is to allow at minimum any certificate that would be allowed on a // regular Web session over TLS 1.3 while ensuring we do not expose any // algorithms we don't want to support long-term. diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/certificate_view.h b/chromium/net/third_party/quiche/src/quic/core/crypto/certificate_view.h index f2826cdd668..5b3c8c762c6 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/certificate_view.h +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/certificate_view.h @@ -34,6 +34,16 @@ struct QUIC_EXPORT_PRIVATE PemReadResult { // Reads |input| line-by-line and returns the next available PEM message. QUIC_EXPORT_PRIVATE PemReadResult ReadNextPemMessage(std::istream* input); +// Cryptograhpic algorithms recognized in X.509. +enum class PublicKeyType { + kRsa, + kP256, + kP384, + kEd25519, + kUnknown, +}; +QUIC_EXPORT_PRIVATE std::string PublicKeyTypeToString(PublicKeyType type); + // CertificateView represents a parsed version of a single X.509 certificate. As // the word "view" implies, it does not take ownership of the underlying strings // and consists primarily of pointers into the certificate that is passed into @@ -69,6 +79,9 @@ class QUIC_EXPORT_PRIVATE CertificateView { absl::string_view signature, uint16_t signature_algorithm) const; + // Returns the type of the key used in the certificate's SPKI. + PublicKeyType public_key_type() const; + private: CertificateView() = default; diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/certificate_view_test.cc b/chromium/net/third_party/quiche/src/quic/core/crypto/certificate_view_test.cc index 5d743b2114c..fbca9159c82 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/certificate_view_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/certificate_view_test.cc @@ -58,6 +58,8 @@ TEST(CertificateViewTest, Parse) { const QuicWallTime validity_end = QuicWallTime::FromUNIXSeconds( *quiche::QuicheUtcDateTimeToUnixSeconds(2020, 2, 2, 18, 13, 59)); EXPECT_EQ(view->validity_end(), validity_end); + EXPECT_EQ(view->public_key_type(), PublicKeyType::kRsa); + EXPECT_EQ(PublicKeyTypeToString(view->public_key_type()), "RSA"); EXPECT_EQ("C=US,ST=California,L=Mountain View,O=QUIC Server,CN=127.0.0.1", view->GetHumanReadableSubject()); diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_message_printer_bin.cc b/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_message_printer_bin.cc index 68e3d9cbe19..0159e3004e5 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_message_printer_bin.cc +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_message_printer_bin.cc @@ -13,6 +13,7 @@ #include "absl/strings/escaping.h" #include "quic/core/crypto/crypto_framer.h" #include "quic/core/quic_utils.h" +#include "common/platform/api/quiche_command_line_flags.h" using std::cerr; using std::cout; @@ -37,9 +38,9 @@ class CryptoMessagePrinter : public ::quic::CryptoFramerVisitorInterface { int main(int argc, char* argv[]) { const char* usage = "Usage: crypto_message_printer <hex>"; std::vector<std::string> messages = - quic::QuicParseCommandLineFlags(usage, argc, argv); + quiche::QuicheParseCommandLineFlags(usage, argc, argv); if (messages.size() != 1) { - quic::QuicPrintCommandLineFlagHelp(usage); + quiche::QuichePrintCommandLineFlagHelp(usage); exit(0); } 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 5a3003861e1..0ebe0e430b8 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 @@ -121,9 +121,9 @@ const QuicTag kBBQ7 = TAG('B', 'B', 'Q', '7'); // Reduce bw_lo by const QuicTag kBBQ8 = TAG('B', 'B', 'Q', '8'); // Reduce bw_lo by // bw_lo * bytes_lost/inflight const QuicTag kBBQ9 = TAG('B', 'B', 'Q', '9'); // Reduce bw_lo by + // bw_lo * bytes_lost/cwnd const QuicTag kBBQ0 = TAG('B', 'B', 'Q', '0'); // Increase bytes_acked in // PROBE_UP when app limited. - // bw_lo * bytes_lost/cwnd const QuicTag kRENO = TAG('R', 'E', 'N', 'O'); // Reno Congestion Control const QuicTag kTPCC = TAG('P', 'C', 'C', '\0'); // Performance-Oriented // Congestion Control @@ -434,6 +434,7 @@ const QuicTag kSRWP = TAG('S', 'R', 'W', 'P'); // Enable retransmittable on // Client Hints triggers. const QuicTag kGWCH = TAG('G', 'W', 'C', 'H'); const QuicTag kYTCH = TAG('Y', 'T', 'C', 'H'); +const QuicTag kACH0 = TAG('A', 'C', 'H', '0'); // Rejection tags const QuicTag kRREJ = TAG('R', 'R', 'E', 'J'); // Reasons for server sending diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_secret_boxer.cc b/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_secret_boxer.cc index 1826212d83c..a4a7a2a615d 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_secret_boxer.cc +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_secret_boxer.cc @@ -44,8 +44,11 @@ size_t CryptoSecretBoxer::GetKeySize() { // kAEAD is the AEAD used for boxing: AES-256-GCM-SIV. static const EVP_AEAD* (*const kAEAD)() = EVP_aead_aes_256_gcm_siv; -void CryptoSecretBoxer::SetKeys(const std::vector<std::string>& keys) { - QUICHE_DCHECK(!keys.empty()); +bool CryptoSecretBoxer::SetKeys(const std::vector<std::string>& keys) { + if (keys.empty()) { + QUIC_LOG(DFATAL) << "No keys supplied!"; + return false; + } const EVP_AEAD* const aead = kAEAD(); std::unique_ptr<State> new_state(new State); @@ -57,7 +60,7 @@ void CryptoSecretBoxer::SetKeys(const std::vector<std::string>& keys) { if (!ctx) { ERR_clear_error(); QUIC_LOG(DFATAL) << "EVP_AEAD_CTX_init failed"; - return; + return false; } new_state->ctxs.push_back(std::move(ctx)); @@ -65,6 +68,7 @@ void CryptoSecretBoxer::SetKeys(const std::vector<std::string>& keys) { QuicWriterMutexLock l(&lock_); state_ = std::move(new_state); + return true; } std::string CryptoSecretBoxer::Box(QuicRandom* rand, diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_secret_boxer.h b/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_secret_boxer.h index 5dd5cb24985..e2cd0377786 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_secret_boxer.h +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_secret_boxer.h @@ -34,13 +34,15 @@ class QUIC_EXPORT_PRIVATE CryptoSecretBoxer { // SetKeys sets a list of encryption keys. The first key in the list will be // used by |Box|, but all supplied keys will be tried by |Unbox|, to handle // key skew across the fleet. This must be called before |Box| or |Unbox|. - // Keys must be |GetKeySize()| bytes long. - void SetKeys(const std::vector<std::string>& keys); + // Keys must be |GetKeySize()| bytes long. No change is made if any key is + // invalid, or if there are no keys supplied. + bool SetKeys(const std::vector<std::string>& keys); // Box encrypts |plaintext| using a random nonce generated from |rand| and // returns the resulting ciphertext. Since an authenticator and nonce are // included, the result will be slightly larger than |plaintext|. The first - // key in the vector supplied to |SetKeys| will be used. + // key in the vector supplied to |SetKeys| will be used. |SetKeys| must be + // called before calling this method. std::string Box(QuicRandom* rand, absl::string_view plaintext) const; // Unbox takes the result of a previous call to |Box| in |ciphertext| and diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_secret_boxer_test.cc b/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_secret_boxer_test.cc index 225bfc8b14e..7b07d96d11d 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_secret_boxer_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_secret_boxer_test.cc @@ -56,9 +56,9 @@ TEST_F(CryptoSecretBoxerTest, MultipleKeys) { std::string key_12(CryptoSecretBoxer::GetKeySize(), 0x12); CryptoSecretBoxer boxer_11, boxer_12, boxer; - boxer_11.SetKeys({key_11}); - boxer_12.SetKeys({key_12}); - boxer.SetKeys({key_12, key_11}); + EXPECT_TRUE(boxer_11.SetKeys({key_11})); + EXPECT_TRUE(boxer_12.SetKeys({key_12})); + EXPECT_TRUE(boxer.SetKeys({key_12, key_11})); // Neither single-key boxer can decode the other's tokens. EXPECT_FALSE(CanDecode(boxer_11, boxer_12)); @@ -74,7 +74,7 @@ TEST_F(CryptoSecretBoxerTest, MultipleKeys) { // After we flush key_11 from |boxer|, it can no longer decode tokens from // |boxer_11|. - boxer.SetKeys({key_12}); + EXPECT_TRUE(boxer.SetKeys({key_12})); EXPECT_FALSE(CanDecode(boxer, boxer_11)); } 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 50bbfefa4dc..05bbdfe6321 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 @@ -57,8 +57,7 @@ namespace { // |out_len|, respectively. The resulting expanded secret is returned. std::vector<uint8_t> HkdfExpandLabel(const EVP_MD* prf, const std::vector<uint8_t>& secret, - const std::string& label, - size_t out_len) { + const std::string& label, size_t out_len) { bssl::ScopedCBB quic_hkdf_label; CBB inner_label; const char label_prefix[] = "tls13 "; @@ -91,13 +90,23 @@ std::vector<uint8_t> HkdfExpandLabel(const EVP_MD* prf, } // namespace +const std::string getLabelForVersion(const ParsedQuicVersion& version, + const absl::string_view& predicate) { + static_assert(SupportedVersions().size() == 6u, + "Supported versions out of sync with HKDF labels"); + if (version == ParsedQuicVersion::V2Draft01()) { + return absl::StrCat("quicv2 ", predicate); + } else { + return absl::StrCat("quic ", predicate); + } +} + void CryptoUtils::InitializeCrypterSecrets( - const EVP_MD* prf, - const std::vector<uint8_t>& pp_secret, - QuicCrypter* crypter) { - SetKeyAndIV(prf, pp_secret, crypter); - std::vector<uint8_t> header_protection_key = - GenerateHeaderProtectionKey(prf, pp_secret, crypter->GetKeySize()); + const EVP_MD* prf, const std::vector<uint8_t>& pp_secret, + const ParsedQuicVersion& version, QuicCrypter* crypter) { + SetKeyAndIV(prf, pp_secret, version, crypter); + std::vector<uint8_t> header_protection_key = GenerateHeaderProtectionKey( + prf, pp_secret, version, crypter->GetKeySize()); crypter->SetHeaderProtectionKey( absl::string_view(reinterpret_cast<char*>(header_protection_key.data()), header_protection_key.size())); @@ -105,11 +114,13 @@ void CryptoUtils::InitializeCrypterSecrets( void CryptoUtils::SetKeyAndIV(const EVP_MD* prf, const std::vector<uint8_t>& pp_secret, + const ParsedQuicVersion& version, QuicCrypter* crypter) { std::vector<uint8_t> key = - HkdfExpandLabel(prf, pp_secret, "quic key", crypter->GetKeySize()); - std::vector<uint8_t> iv = - HkdfExpandLabel(prf, pp_secret, "quic iv", crypter->GetIVSize()); + HkdfExpandLabel(prf, pp_secret, getLabelForVersion(version, "key"), + crypter->GetKeySize()); + std::vector<uint8_t> iv = HkdfExpandLabel( + prf, pp_secret, getLabelForVersion(version, "iv"), crypter->GetIVSize()); crypter->SetKey( absl::string_view(reinterpret_cast<char*>(key.data()), key.size())); crypter->SetIV( @@ -117,16 +128,17 @@ void CryptoUtils::SetKeyAndIV(const EVP_MD* prf, } std::vector<uint8_t> CryptoUtils::GenerateHeaderProtectionKey( - const EVP_MD* prf, - const std::vector<uint8_t>& pp_secret, - size_t out_len) { - return HkdfExpandLabel(prf, pp_secret, "quic hp", out_len); + const EVP_MD* prf, const std::vector<uint8_t>& pp_secret, + const ParsedQuicVersion& version, size_t out_len) { + return HkdfExpandLabel(prf, pp_secret, getLabelForVersion(version, "hp"), + out_len); } std::vector<uint8_t> CryptoUtils::GenerateNextKeyPhaseSecret( - const EVP_MD* prf, + const EVP_MD* prf, const ParsedQuicVersion& version, const std::vector<uint8_t>& current_secret) { - return HkdfExpandLabel(prf, current_secret, "quic ku", current_secret.size()); + return HkdfExpandLabel(prf, current_secret, getLabelForVersion(version, "ku"), + current_secret.size()); } namespace { @@ -138,6 +150,9 @@ const uint8_t kDraft29InitialSalt[] = {0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, const uint8_t kRFCv1InitialSalt[] = {0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a}; +const uint8_t kV2Draft01InitialSalt[] = { + 0xa7, 0x07, 0xc2, 0x03, 0xa5, 0x9b, 0x47, 0x18, 0x4a, 0x1d, + 0x62, 0xca, 0x57, 0x04, 0x06, 0xea, 0x7a, 0xe3, 0xe5, 0xd3}; // Salts used by deployed versions of QUIC. When introducing a new version, // generate a new salt by running `openssl rand -hex 20`. @@ -154,9 +169,12 @@ const uint8_t kReservedForNegotiationSalt[] = { const uint8_t* InitialSaltForVersion(const ParsedQuicVersion& version, size_t* out_len) { - static_assert(SupportedVersions().size() == 5u, + static_assert(SupportedVersions().size() == 6u, "Supported versions out of sync with initial encryption salts"); - if (version == ParsedQuicVersion::RFCv1()) { + if (version == ParsedQuicVersion::V2Draft01()) { + *out_len = ABSL_ARRAYSIZE(kV2Draft01InitialSalt); + return kV2Draft01InitialSalt; + } else if (version == ParsedQuicVersion::RFCv1()) { *out_len = ABSL_ARRAYSIZE(kRFCv1InitialSalt); return kRFCv1InitialSalt; } else if (version == ParsedQuicVersion::Draft29()) { @@ -191,6 +209,11 @@ const uint8_t kRFCv1RetryIntegrityKey[] = {0xbe, 0x0c, 0x69, 0x0b, 0x9f, 0x66, 0xe3, 0x68, 0xc8, 0x4e}; const uint8_t kRFCv1RetryIntegrityNonce[] = { 0x46, 0x15, 0x99, 0xd3, 0x5d, 0x63, 0x2b, 0xf2, 0x23, 0x98, 0x25, 0xbb}; +const uint8_t kV2Draft01RetryIntegrityKey[] = { + 0xba, 0x85, 0x8d, 0xc7, 0xb4, 0x3d, 0xe5, 0xdb, + 0xf8, 0x76, 0x17, 0xff, 0x4a, 0xb2, 0x53, 0xdb}; +const uint8_t kV2Draft01RetryIntegrityNonce[] = { + 0x14, 0x1b, 0x99, 0xc2, 0x39, 0xb0, 0x3e, 0x78, 0x5d, 0x6a, 0x2e, 0x9f}; // Retry integrity key used by ParsedQuicVersion::ReservedForNegotiation(). const uint8_t kReservedForNegotiationRetryIntegrityKey[] = { 0xf2, 0xcd, 0x8f, 0xe0, 0x36, 0xd0, 0x25, 0x35, @@ -204,13 +227,21 @@ const uint8_t kReservedForNegotiationRetryIntegrityNonce[] = { bool RetryIntegrityKeysForVersion(const ParsedQuicVersion& version, absl::string_view* key, absl::string_view* nonce) { - static_assert(SupportedVersions().size() == 5u, + static_assert(SupportedVersions().size() == 6u, "Supported versions out of sync with retry integrity keys"); if (!version.UsesTls()) { QUIC_BUG(quic_bug_10699_2) << "Attempted to get retry integrity keys for invalid version " << version; return false; + } else if (version == ParsedQuicVersion::V2Draft01()) { + *key = absl::string_view( + reinterpret_cast<const char*>(kV2Draft01RetryIntegrityKey), + ABSL_ARRAYSIZE(kV2Draft01RetryIntegrityKey)); + *nonce = absl::string_view( + reinterpret_cast<const char*>(kV2Draft01RetryIntegrityNonce), + ABSL_ARRAYSIZE(kV2Draft01RetryIntegrityNonce)); + return true; } else if (version == ParsedQuicVersion::RFCv1()) { *key = absl::string_view( reinterpret_cast<const char*>(kRFCv1RetryIntegrityKey), @@ -291,20 +322,20 @@ void CryptoUtils::CreateInitialObfuscators(Perspective perspective, std::vector<uint8_t> encryption_secret = HkdfExpandLabel( hash, handshake_secret, encryption_label, EVP_MD_size(hash)); crypters->encrypter = std::make_unique<Aes128GcmEncrypter>(); - InitializeCrypterSecrets(hash, encryption_secret, crypters->encrypter.get()); + InitializeCrypterSecrets(hash, encryption_secret, version, + crypters->encrypter.get()); std::vector<uint8_t> decryption_secret = HkdfExpandLabel( hash, handshake_secret, decryption_label, EVP_MD_size(hash)); crypters->decrypter = std::make_unique<Aes128GcmDecrypter>(); - InitializeCrypterSecrets(hash, decryption_secret, crypters->decrypter.get()); + InitializeCrypterSecrets(hash, decryption_secret, version, + crypters->decrypter.get()); } // static bool CryptoUtils::ValidateRetryIntegrityTag( - ParsedQuicVersion version, - QuicConnectionId original_connection_id, - absl::string_view retry_without_tag, - absl::string_view integrity_tag) { + ParsedQuicVersion version, QuicConnectionId original_connection_id, + absl::string_view retry_without_tag, absl::string_view integrity_tag) { unsigned char computed_integrity_tag[kRetryIntegrityTagLength]; if (integrity_tag.length() != ABSL_ARRAYSIZE(computed_integrity_tag)) { QUIC_BUG(quic_bug_10699_4) @@ -348,10 +379,8 @@ bool CryptoUtils::ValidateRetryIntegrityTag( } // static -void CryptoUtils::GenerateNonce(QuicWallTime now, - QuicRandom* random_generator, - absl::string_view orbit, - std::string* nonce) { +void CryptoUtils::GenerateNonce(QuicWallTime now, QuicRandom* random_generator, + absl::string_view orbit, std::string* nonce) { // a 4-byte timestamp + 28 random bytes. nonce->reserve(kNonceSize); nonce->resize(kNonceSize); @@ -375,17 +404,13 @@ void CryptoUtils::GenerateNonce(QuicWallTime now, } // static -bool CryptoUtils::DeriveKeys(const ParsedQuicVersion& version, - absl::string_view premaster_secret, - QuicTag aead, - absl::string_view client_nonce, - absl::string_view server_nonce, - absl::string_view pre_shared_key, - const std::string& hkdf_input, - Perspective perspective, - Diversification diversification, - CrypterPair* crypters, - std::string* subkey_secret) { +bool CryptoUtils::DeriveKeys( + const ParsedQuicVersion& version, absl::string_view premaster_secret, + QuicTag aead, absl::string_view client_nonce, + absl::string_view server_nonce, absl::string_view pre_shared_key, + const std::string& hkdf_input, Perspective perspective, + Diversification diversification, CrypterPair* crypters, + std::string* subkey_secret) { // If the connection is using PSK, concatenate it with the pre-master secret. std::unique_ptr<char[]> psk_premaster_secret; if (!pre_shared_key.empty()) { @@ -573,8 +598,7 @@ QuicErrorCode CryptoUtils::ValidateServerHelloVersions( } QuicErrorCode CryptoUtils::ValidateClientHello( - const CryptoHandshakeMessage& client_hello, - ParsedQuicVersion version, + const CryptoHandshakeMessage& client_hello, ParsedQuicVersion version, const ParsedQuicVersionVector& supported_versions, std::string* error_details) { if (client_hello.tag() != kCHLO) { @@ -597,8 +621,7 @@ QuicErrorCode CryptoUtils::ValidateClientHello( } QuicErrorCode CryptoUtils::ValidateClientHelloVersion( - QuicVersionLabel client_version, - ParsedQuicVersion connection_version, + QuicVersionLabel client_version, ParsedQuicVersion connection_version, const ParsedQuicVersionVector& supported_versions, std::string* error_details) { if (client_version != CreateQuicVersionLabel(connection_version)) { @@ -736,8 +759,7 @@ std::string CryptoUtils::EarlyDataReasonToString( // static std::string CryptoUtils::HashHandshakeMessage( - const CryptoHandshakeMessage& message, - Perspective /*perspective*/) { + const CryptoHandshakeMessage& message, Perspective /*perspective*/) { std::string output; const QuicData& serialized = message.GetSerialized(); uint8_t digest[SHA256_DIGEST_LENGTH]; diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_utils.h b/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_utils.h index 8884f6fa096..fce62018823 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_utils.h +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_utils.h @@ -79,9 +79,11 @@ class QUIC_EXPORT_PRIVATE CryptoUtils { // on the given QuicCrypter |*crypter|. // This follows the derivation described in section 7.3 of RFC 8446, except // with the label prefix in HKDF-Expand-Label changed from "tls13 " to "quic " - // as described in draft-ietf-quic-tls-14, section 5.1. + // as described in draft-ietf-quic-tls-14, section 5.1, or "quicv2 " as + // described in draft-ietf-quic-v2-01. static void InitializeCrypterSecrets(const EVP_MD* prf, const std::vector<uint8_t>& pp_secret, + const ParsedQuicVersion& version, QuicCrypter* crypter); // Derives the key and IV from the packet protection secret and sets those @@ -90,17 +92,17 @@ class QUIC_EXPORT_PRIVATE CryptoUtils { // called before using |crypter|. static void SetKeyAndIV(const EVP_MD* prf, const std::vector<uint8_t>& pp_secret, + const ParsedQuicVersion& version, QuicCrypter* crypter); // Derives the header protection key from the packet protection secret. static std::vector<uint8_t> GenerateHeaderProtectionKey( - const EVP_MD* prf, - const std::vector<uint8_t>& pp_secret, - size_t out_len); + const EVP_MD* prf, const std::vector<uint8_t>& pp_secret, + const ParsedQuicVersion& version, size_t out_len); // Given a secret for key phase n, return the secret for phase n+1. static std::vector<uint8_t> GenerateNextKeyPhaseSecret( - const EVP_MD* prf, + const EVP_MD* prf, const ParsedQuicVersion& version, const std::vector<uint8_t>& current_secret); // IETF QUIC encrypts ENCRYPTION_INITIAL messages with a version-specific key @@ -130,10 +132,8 @@ class QUIC_EXPORT_PRIVATE CryptoUtils { // <4 bytes> current time // <8 bytes> |orbit| (or random if |orbit| is empty) // <20 bytes> random - static void GenerateNonce(QuicWallTime now, - QuicRandom* random_generator, - absl::string_view orbit, - std::string* nonce); + static void GenerateNonce(QuicWallTime now, QuicRandom* random_generator, + absl::string_view orbit, std::string* nonce); // DeriveKeys populates |crypters->encrypter|, |crypters->decrypter|, and // |subkey_secret| (optional -- may be null) given the contents of @@ -155,15 +155,12 @@ class QUIC_EXPORT_PRIVATE CryptoUtils { // |SetDiversificationNonce| with a diversification nonce will be needed to // complete keying. static bool DeriveKeys(const ParsedQuicVersion& version, - absl::string_view premaster_secret, - QuicTag aead, + absl::string_view premaster_secret, QuicTag aead, absl::string_view client_nonce, absl::string_view server_nonce, absl::string_view pre_shared_key, - const std::string& hkdf_input, - Perspective perspective, - Diversification diversification, - CrypterPair* crypters, + const std::string& hkdf_input, Perspective perspective, + Diversification diversification, CrypterPair* crypters, std::string* subkey_secret); // Computes the FNV-1a hash of the provided DER-encoded cert for use in the @@ -199,8 +196,7 @@ class QUIC_EXPORT_PRIVATE CryptoUtils { // Returns QUIC_NO_ERROR if this is the case or returns the appropriate error // code and sets |error_details|. static QuicErrorCode ValidateClientHello( - const CryptoHandshakeMessage& client_hello, - ParsedQuicVersion version, + const CryptoHandshakeMessage& client_hello, ParsedQuicVersion version, const ParsedQuicVersionVector& supported_versions, std::string* error_details); @@ -212,8 +208,7 @@ class QUIC_EXPORT_PRIVATE CryptoUtils { // Returns QUIC_NO_ERROR if this is the case or returns the appropriate error // code and sets |error_details|. static QuicErrorCode ValidateClientHelloVersion( - QuicVersionLabel client_version, - ParsedQuicVersion connection_version, + QuicVersionLabel client_version, ParsedQuicVersion connection_version, const ParsedQuicVersionVector& supported_versions, std::string* error_details); diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_utils_test.cc b/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_utils_test.cc index 251f136ec5d..f0d944b4fea 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_utils_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/crypto_utils_test.cc @@ -8,6 +8,7 @@ #include "absl/base/macros.h" #include "absl/strings/escaping.h" +#include "absl/strings/string_view.h" #include "quic/core/quic_utils.h" #include "quic/platform/api/quic_test.h" #include "quic/test_tools/quic_test_utils.h" @@ -165,6 +166,97 @@ TEST_F(CryptoUtilsTest, ValidateServerVersionsWithDowngrade) { EXPECT_FALSE(error_details.empty()); } +// Test that the library is using the correct labels for each version, and +// therefore generating correct obfuscators, using the test vectors in appendix +// A of each RFC or internet-draft. +TEST_F(CryptoUtilsTest, ValidateCryptoLabels) { + // if the number of HTTP/3 QUIC versions has changed, we need to change the + // expected_keys hardcoded into this test. Regrettably, this is not a + // compile-time constant. + EXPECT_EQ(AllSupportedVersionsWithTls().size(), 3u); + const char draft_29_key[] = {// test vector from draft-ietf-quic-tls-29, A.1 + 0x14, + static_cast<char>(0x9d), + 0x0b, + 0x16, + 0x62, + static_cast<char>(0xab), + static_cast<char>(0x87), + 0x1f, + static_cast<char>(0xbe), + 0x63, + static_cast<char>(0xc4), + static_cast<char>(0x9b), + 0x5e, + 0x65, + 0x5a, + 0x5d}; + const char v1_key[] = {// test vector from RFC 9001, A.1 + static_cast<char>(0xcf), + 0x3a, + 0x53, + 0x31, + 0x65, + 0x3c, + 0x36, + 0x4c, + static_cast<char>(0x88), + static_cast<char>(0xf0), + static_cast<char>(0xf3), + 0x79, + static_cast<char>(0xb6), + 0x06, + 0x7e, + 0x37}; + const char v2_01_key[] = {// test vector from draft-ietf-quic-v2-01 + 0x15, + static_cast<char>(0xd5), + static_cast<char>(0xb4), + static_cast<char>(0xd9), + static_cast<char>(0xa2), + static_cast<char>(0xb8), + static_cast<char>(0x91), + 0x6a, + static_cast<char>(0xa3), + static_cast<char>(0x9b), + 0x1b, + static_cast<char>(0xfe), + 0x57, + 0x4d, + 0x2a, + static_cast<char>(0xad)}; + const char connection_id[] = // test vector from both docs + {static_cast<char>(0x83), + static_cast<char>(0x94), + static_cast<char>(0xc8), + static_cast<char>(0xf0), + 0x3e, + 0x51, + 0x57, + 0x08}; + const QuicConnectionId cid(connection_id, sizeof(connection_id)); + const char* key_str; + size_t key_size; + for (const ParsedQuicVersion& version : AllSupportedVersionsWithTls()) { + if (version == ParsedQuicVersion::Draft29()) { + key_str = draft_29_key; + key_size = sizeof(draft_29_key); + } else if (version == ParsedQuicVersion::RFCv1()) { + key_str = v1_key; + key_size = sizeof(v1_key); + } else { // draft-ietf-quic-v2-01 + key_str = v2_01_key; + key_size = sizeof(v2_01_key); + } + const absl::string_view expected_key{key_str, key_size}; + + CrypterPair crypters; + CryptoUtils::CreateInitialObfuscators(Perspective::IS_SERVER, version, cid, + &crypters); + EXPECT_EQ(crypters.encrypter->GetKey(), expected_key); + } +} + } // namespace } // namespace test } // namespace quic 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 e4989bc302a..1b1790adc75 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 @@ -834,9 +834,8 @@ void QuicCryptoClientConfig::AddCanonicalSuffix(const std::string& suffix) { } bool QuicCryptoClientConfig::PopulateFromCanonicalConfig( - const QuicServerId& server_id, - CachedState* server_state) { - QUICHE_DCHECK(server_state->IsEmpty()); + const QuicServerId& server_id, CachedState* cached) { + QUICHE_DCHECK(cached->IsEmpty()); size_t i = 0; for (; i < canonical_suffixes_.size(); ++i) { if (absl::EndsWithIgnoreCase(server_id.host(), canonical_suffixes_[i])) { @@ -867,7 +866,7 @@ bool QuicCryptoClientConfig::PopulateFromCanonicalConfig( // Update canonical version to point at the "most recent" entry. it->second = server_id; - server_state->InitializeFrom(*canonical_state); + cached->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 13517299087..9c71acafa08 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 @@ -332,16 +332,14 @@ class QUIC_EXPORT_PRIVATE QuicCryptoClientConfig : public QuicCryptoConfig { QuicReferenceCountedPointer<QuicCryptoNegotiatedParameters> out_params, std::string* error_details); - // Processes the message in |server_update|, updating the cached source + // Processes the message in |server_config_update|, updating the cached source // address token, and server config. - // If |server_update| is invalid then |error_details| will contain an error - // message, and an error code will be returned. If all has gone well + // If |server_config_update| is invalid then |error_details| will contain an + // error message, and an error code will be returned. If all has gone well // QUIC_NO_ERROR is returned. QuicErrorCode ProcessServerConfigUpdate( - const CryptoHandshakeMessage& server_update, - QuicWallTime now, - const QuicTransportVersion version, - absl::string_view chlo_hash, + const CryptoHandshakeMessage& server_config_update, QuicWallTime now, + const QuicTransportVersion version, absl::string_view chlo_hash, CachedState* cached, QuicReferenceCountedPointer<QuicCryptoNegotiatedParameters> out_params, std::string* error_details); diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/quic_crypto_server_config.cc b/chromium/net/third_party/quiche/src/quic/core/crypto/quic_crypto_server_config.cc index 16a53fa920c..71c177552a0 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/quic_crypto_server_config.cc +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/quic_crypto_server_config.cc @@ -512,15 +512,17 @@ bool QuicCryptoServerConfig::SetConfigs( void QuicCryptoServerConfig::SetSourceAddressTokenKeys( const std::vector<std::string>& keys) { + // TODO(b/208866709) source_address_token_boxer_.SetKeys(keys); } -void QuicCryptoServerConfig::GetConfigIds( - std::vector<std::string>* scids) const { +std::vector<std::string> QuicCryptoServerConfig::GetConfigIds() const { QuicReaderMutexLock locked(&configs_lock_); + std::vector<std::string> scids; for (auto it = configs_.begin(); it != configs_.end(); ++it) { - scids->push_back(it->first); + scids.push_back(it->first); } + return scids; } void QuicCryptoServerConfig::ValidateClientHello( @@ -538,6 +540,10 @@ void QuicCryptoServerConfig::ValidateClientHello( client_hello, client_address.host(), now)); absl::string_view requested_scid; + // We ignore here the return value from GetStringPiece. If there is no SCID + // tag, EvaluateClientHello will discover that because GetCurrentConfigs will + // not have found the requested config (i.e. because none of the configs will + // have an empty string as its id). client_hello.GetStringPiece(kSCID, &requested_scid); Configs configs; if (!GetCurrentConfigs(now, requested_scid, @@ -1575,9 +1581,14 @@ QuicCryptoServerConfig::ParseConfigProtobuf( std::unique_ptr<CryptoHandshakeMessage> msg = CryptoFramer::ParseMessage(protobuf.config()); + if (!msg) { + QUIC_LOG(WARNING) << "Failed to parse server config message"; + return nullptr; + } + if (msg->tag() != kSCFG) { QUIC_LOG(WARNING) << "Server config message has tag " << msg->tag() - << " expected " << kSCFG; + << ", but expected " << kSCFG; return nullptr; } @@ -1597,6 +1608,7 @@ QuicCryptoServerConfig::ParseConfigProtobuf( QUIC_LOG(WARNING) << "Server config message is missing SCID"; return nullptr; } + QUICHE_DCHECK(!scid.empty()); config->id = std::string(scid); if (msg->GetTaglist(kAEAD, &config->aead) != QUIC_NO_ERROR) { 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 ca6dbe5e32f..799058d70ff 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 @@ -200,7 +200,7 @@ class QUIC_EXPORT_PRIVATE QuicCryptoServerConfig { // p256 determines whether a P-256 public key will be included in the // server config. Note that this breaks deterministic server-config // generation since P-256 key generation doesn't use the QuicRandom given - // to DefaultConfig(). + // to GenerateConfig(). bool p256; }; @@ -238,8 +238,8 @@ class QUIC_EXPORT_PRIVATE QuicCryptoServerConfig { const QuicServerConfigProtobuf& protobuf, QuicWallTime now); - // AddDefaultConfig calls DefaultConfig to create a config and then calls - // AddConfig to add it. See the comment for |DefaultConfig| for details of + // AddDefaultConfig calls GenerateConfig to create a config and then calls + // AddConfig to add it. See the comment for |GenerateConfig| for details of // the arguments. std::unique_ptr<CryptoHandshakeMessage> AddDefaultConfig( QuicRandom* rand, @@ -264,7 +264,7 @@ class QUIC_EXPORT_PRIVATE QuicCryptoServerConfig { void SetSourceAddressTokenKeys(const std::vector<std::string>& keys); // Get the server config ids for all known configs. - void GetConfigIds(std::vector<std::string>* scids) const; + std::vector<std::string> GetConfigIds() const; // Checks |client_hello| for gross errors and determines whether it can be // shown to be fresh (i.e. not a replay). The result of the validation step @@ -282,9 +282,9 @@ class QUIC_EXPORT_PRIVATE QuicCryptoServerConfig { // port may be used for certificate selection. // version: protocol version used for this connection. // clock: used to validate client nonces and ephemeral keys. - // crypto_proof: in/out parameter to which will be written the crypto proof - // used in reply to a proof demand. The pointed-to-object must - // live until the callback is invoked. + // signed_config: in/out parameter to which will be written the crypto proof + // used in reply to a proof demand. The pointed-to-object must live until + // the callback is invoked. // done_cb: single-use callback that accepts an opaque // ValidatedClientHelloMsg token that holds information about // the client hello. The callback will always be called exactly @@ -293,10 +293,9 @@ class QUIC_EXPORT_PRIVATE QuicCryptoServerConfig { void ValidateClientHello( const CryptoHandshakeMessage& client_hello, const QuicSocketAddress& client_address, - const QuicSocketAddress& server_address, - QuicTransportVersion version, + const QuicSocketAddress& server_address, QuicTransportVersion version, const QuicClock* clock, - QuicReferenceCountedPointer<QuicSignedServerConfig> crypto_proof, + QuicReferenceCountedPointer<QuicSignedServerConfig> signed_config, std::unique_ptr<ValidateClientHelloResultCallback> done_cb) const; // ProcessClientHello processes |client_hello| and decides whether to accept @@ -323,27 +322,22 @@ class QUIC_EXPORT_PRIVATE QuicCryptoServerConfig { // certs. Owned by QuicDispatcher. // params: the state of the handshake. This may be updated with a server // nonce when we send a rejection. - // crypto_proof: output structure containing the crypto proof used in reply to - // a proof demand. + // signed_config: output structure containing the crypto proof used in reply + // to a proof demand. // total_framing_overhead: the total per-packet overhead for a stream frame // chlo_packet_size: the size, in bytes, of the CHLO packet // done_cb: the callback invoked on completion void ProcessClientHello( QuicReferenceCountedPointer<ValidateClientHelloResultCallback::Result> validate_chlo_result, - bool reject_only, - QuicConnectionId connection_id, + bool reject_only, QuicConnectionId connection_id, const QuicSocketAddress& server_address, - const QuicSocketAddress& client_address, - ParsedQuicVersion version, - const ParsedQuicVersionVector& supported_versions, - const QuicClock* clock, - QuicRandom* rand, - QuicCompressedCertsCache* compressed_certs_cache, + const QuicSocketAddress& client_address, ParsedQuicVersion version, + const ParsedQuicVersionVector& supported_versions, const QuicClock* clock, + QuicRandom* rand, QuicCompressedCertsCache* compressed_certs_cache, QuicReferenceCountedPointer<QuicCryptoNegotiatedParameters> params, - QuicReferenceCountedPointer<QuicSignedServerConfig> crypto_proof, - QuicByteCount total_framing_overhead, - QuicByteCount chlo_packet_size, + QuicReferenceCountedPointer<QuicSignedServerConfig> signed_config, + QuicByteCount total_framing_overhead, QuicByteCount chlo_packet_size, std::unique_ptr<ProcessClientHelloResultCallback> done_cb) const; // BuildServerConfigUpdateMessage invokes |cb| with a SCUP message containing 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 fa9be976f69..901ae66e3c1 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 @@ -106,8 +106,8 @@ TlsConnection::TlsConnection(SSL_CTX* ssl_ctx, ssl(), ssl_config_.signing_algorithm_prefs->data(), ssl_config_.signing_algorithm_prefs->size()); } - if (ssl_config.disable_ticket_support.has_value()) { - if (*ssl_config.disable_ticket_support) { + if (ssl_config_.disable_ticket_support.has_value()) { + if (*ssl_config_.disable_ticket_support) { SSL_set_options(ssl(), SSL_OP_NO_TICKET); } } diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/transport_parameters.cc b/chromium/net/third_party/quiche/src/quic/core/crypto/transport_parameters.cc index 61e8f7c8720..0d828db2d2b 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/transport_parameters.cc +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/transport_parameters.cc @@ -54,9 +54,9 @@ enum TransportParameters::TransportParameterId : uint64_t { kInitialRoundTripTime = 0x3127, kGoogleConnectionOptions = 0x3128, - kGoogleUserAgentId = 0x3129, + // 0x3129 was used to convey the user agent string. // 0x312A was used only in T050 to indicate support for HANDSHAKE_DONE. - kGoogleKeyUpdateNotYetSupported = 0x312B, + // 0x312B was used to indicate that QUIC+TLS key updates were not supported. // 0x4751 was used for non-standard Google-specific parameters encoded as a // Google QUIC_CRYPTO CHLO, it has been replaced by individual parameters. kGoogleQuicVersion = @@ -122,10 +122,6 @@ std::string TransportParameterIdToString( return "initial_round_trip_time"; case TransportParameters::kGoogleConnectionOptions: return "google_connection_options"; - case TransportParameters::kGoogleUserAgentId: - return "user_agent_id"; - case TransportParameters::kGoogleKeyUpdateNotYetSupported: - return "key_update_not_yet_supported"; case TransportParameters::kGoogleQuicVersion: return "google-version"; case TransportParameters::kMinAckDelay: @@ -164,10 +160,6 @@ bool TransportParameterIdIsKnown( return true; case TransportParameters::kVersionInformation: return GetQuicReloadableFlag(quic_version_information); - case TransportParameters::kGoogleUserAgentId: - return !GetQuicReloadableFlag(quic_ignore_user_agent_transport_parameter); - case TransportParameters::kGoogleKeyUpdateNotYetSupported: - return !GetQuicReloadableFlag(quic_ignore_key_update_not_yet_supported); } return false; } @@ -447,13 +439,6 @@ std::string TransportParameters::ToString() const { rv += QuicTagToString(connection_option); } } - if (user_agent_id.has_value()) { - rv += " " + TransportParameterIdToString(kGoogleUserAgentId) + " \"" + - user_agent_id.value() + "\""; - } - if (key_update_not_yet_supported) { - rv += " " + TransportParameterIdToString(kGoogleKeyUpdateNotYetSupported); - } for (const auto& kv : custom_parameters) { absl::StrAppend(&rv, " 0x", absl::Hex(static_cast<uint32_t>(kv.first)), "="); @@ -493,8 +478,7 @@ TransportParameters::TransportParameters() kMinActiveConnectionIdLimitTransportParam, kVarInt62MaxValue), max_datagram_frame_size(kMaxDatagramFrameSize), - initial_round_trip_time_us(kInitialRoundTripTime), - key_update_not_yet_supported(false) + initial_round_trip_time_us(kInitialRoundTripTime) // Important note: any new transport parameters must be added // to TransportParameters::AreValid, SerializeTransportParameters and // ParseTransportParameters, TransportParameters's custom copy constructor, the @@ -528,8 +512,6 @@ TransportParameters::TransportParameters(const TransportParameters& other) max_datagram_frame_size(other.max_datagram_frame_size), initial_round_trip_time_us(other.initial_round_trip_time_us), google_connection_options(other.google_connection_options), - user_agent_id(other.user_agent_id), - key_update_not_yet_supported(other.key_update_not_yet_supported), custom_parameters(other.custom_parameters) { if (other.preferred_address) { preferred_address = std::make_unique<TransportParameters::PreferredAddress>( @@ -570,8 +552,6 @@ bool TransportParameters::operator==(const TransportParameters& rhs) const { initial_round_trip_time_us.value() == rhs.initial_round_trip_time_us.value() && google_connection_options == rhs.google_connection_options && - user_agent_id == rhs.user_agent_id && - key_update_not_yet_supported == rhs.key_update_not_yet_supported && custom_parameters == rhs.custom_parameters)) { return false; } @@ -646,10 +626,6 @@ bool TransportParameters::AreValid(std::string* error_details) const { *error_details = "Server cannot send initial round trip time"; return false; } - if (perspective == Perspective::IS_SERVER && user_agent_id.has_value()) { - *error_details = "Server cannot send user agent ID"; - return false; - } if (version_information.has_value()) { const QuicVersionLabel& chosen_version = version_information.value().chosen_version; @@ -758,8 +734,6 @@ bool SerializeTransportParameters(ParsedQuicVersion /*version*/, kIntegerParameterLength + // max_datagram_frame_size kIntegerParameterLength + // initial_round_trip_time_us kTypeAndValueLength + // google_connection_options - kTypeAndValueLength + // user_agent_id - kTypeAndValueLength + // key_update_not_yet_supported kTypeAndValueLength; // google-version std::vector<TransportParameters::TransportParameterId> parameter_ids = { @@ -784,8 +758,6 @@ bool SerializeTransportParameters(ParsedQuicVersion /*version*/, TransportParameters::kInitialSourceConnectionId, TransportParameters::kRetrySourceConnectionId, TransportParameters::kGoogleConnectionOptions, - TransportParameters::kGoogleUserAgentId, - TransportParameters::kGoogleKeyUpdateNotYetSupported, TransportParameters::kGoogleQuicVersion, TransportParameters::kVersionInformation, }; @@ -796,10 +768,6 @@ bool SerializeTransportParameters(ParsedQuicVersion /*version*/, max_transport_param_length += in.google_connection_options.value().size() * sizeof(QuicTag); } - // user_agent_id. - if (in.user_agent_id.has_value()) { - max_transport_param_length += in.user_agent_id.value().length(); - } // Google-specific version extension. if (in.legacy_version_information.has_value()) { max_transport_param_length += @@ -1124,30 +1092,6 @@ bool SerializeTransportParameters(ParsedQuicVersion /*version*/, } } } break; - // Google-specific user agent identifier. - case TransportParameters::kGoogleUserAgentId: { - if (in.user_agent_id.has_value()) { - if (!writer.WriteVarInt62(TransportParameters::kGoogleUserAgentId) || - !writer.WriteStringPieceVarInt62(in.user_agent_id.value())) { - QUIC_BUG(Failed to write Google user agent ID) - << "Failed to write Google user agent ID \"" - << in.user_agent_id.value() << "\" for " << in; - return false; - } - } - } break; - // Google-specific indicator for key update not yet supported. - case TransportParameters::kGoogleKeyUpdateNotYetSupported: { - if (in.key_update_not_yet_supported) { - if (!writer.WriteVarInt62( - TransportParameters::kGoogleKeyUpdateNotYetSupported) || - !writer.WriteVarInt62(/* transport parameter length */ 0)) { - QUIC_BUG(Failed to write key_update_not_yet_supported) - << "Failed to write key_update_not_yet_supported for " << in; - return false; - } - } - } break; // Google-specific version extension. case TransportParameters::kGoogleQuicVersion: { if (!in.legacy_version_information.has_value()) { @@ -1470,54 +1414,6 @@ bool ParseTransportParameters(ParsedQuicVersion version, out->google_connection_options.value().push_back(connection_option); } } break; - case TransportParameters::kGoogleUserAgentId: - if (GetQuicReloadableFlag(quic_ignore_user_agent_transport_parameter)) { - QUIC_RELOADABLE_FLAG_COUNT( - quic_ignore_user_agent_transport_parameter); - // This is a copy of the default switch statement below. - // TODO(dschinazi) remove this case entirely when deprecating the - // quic_ignore_user_agent_transport_parameter flag. - if (out->custom_parameters.find(param_id) != - out->custom_parameters.end()) { - *error_details = "Received a second unknown parameter" + - TransportParameterIdToString(param_id); - return false; - } - out->custom_parameters[param_id] = - std::string(value_reader.ReadRemainingPayload()); - break; - } - if (out->user_agent_id.has_value()) { - *error_details = "Received a second user_agent_id"; - return false; - } - out->user_agent_id = std::string(value_reader.ReadRemainingPayload()); - break; - case TransportParameters::kGoogleKeyUpdateNotYetSupported: - if (GetQuicReloadableFlag(quic_ignore_key_update_not_yet_supported)) { - QUIC_RELOADABLE_FLAG_COUNT_N(quic_ignore_key_update_not_yet_supported, - 1, 2); - QUIC_CODE_COUNT(quic_ignore_key_update_not_yet_supported_ignored); - // This is a copy of the default switch statement below. - // TODO(dschinazi) remove this case entirely when deprecating the - // quic_ignore_key_update_not_yet_supported flag. - if (out->custom_parameters.find(param_id) != - out->custom_parameters.end()) { - *error_details = "Received a second unknown parameter" + - TransportParameterIdToString(param_id); - return false; - } - out->custom_parameters[param_id] = - std::string(value_reader.ReadRemainingPayload()); - break; - } - QUIC_CODE_COUNT(quic_ignore_key_update_not_yet_supported_received); - if (out->key_update_not_yet_supported) { - *error_details = "Received a second key_update_not_yet_supported"; - return false; - } - out->key_update_not_yet_supported = true; - break; case TransportParameters::kGoogleQuicVersion: { if (!out->legacy_version_information.has_value()) { out->legacy_version_information = diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/transport_parameters.h b/chromium/net/third_party/quiche/src/quic/core/crypto/transport_parameters.h index fa349359571..1b6f51684fa 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/transport_parameters.h +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/transport_parameters.h @@ -260,13 +260,6 @@ struct QUIC_EXPORT_PRIVATE TransportParameters { // Google-specific connection options. absl::optional<QuicTagVector> google_connection_options; - // Google-specific user agent identifier. - absl::optional<std::string> user_agent_id; - - // Google-specific mechanism to indicate that IETF QUIC Key Update has not - // yet been implemented. This will be removed once we implement it. - bool key_update_not_yet_supported; - // Validates whether transport parameters are valid according to // the specification. If the transport parameters are not valid, this method // will write a human-readable error message to |error_details|. diff --git a/chromium/net/third_party/quiche/src/quic/core/crypto/transport_parameters_test.cc b/chromium/net/third_party/quiche/src/quic/core/crypto/transport_parameters_test.cc index d9b55730454..c9df18a3d8a 100644 --- a/chromium/net/third_party/quiche/src/quic/core/crypto/transport_parameters_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/crypto/transport_parameters_test.cc @@ -37,7 +37,6 @@ const uint64_t kFakeInitialRoundTripTime = 53; const uint8_t kFakePreferredStatelessResetTokenData[16] = { 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F}; -const bool kFakeKeyUpdateNotYetSupported = true; const auto kCustomParameter1 = static_cast<TransportParameters::TransportParameterId>(0xffcd); @@ -128,10 +127,6 @@ QuicTagVector CreateFakeGoogleConnectionOptions() { MakeQuicTag('H', 'I', 'J', 0xff)}; } -std::string CreateFakeUserAgentId() { - return "FakeUAID"; -} - void RemoveGreaseParameters(TransportParameters* params) { std::vector<TransportParameters::TransportParameterId> grease_params; for (const auto& kv : params->custom_parameters) { @@ -291,8 +286,6 @@ TEST_P(TransportParametersTest, CopyConstructor) { orig_params.retry_source_connection_id = CreateFakeRetrySourceConnectionId(); orig_params.initial_round_trip_time_us.set_value(kFakeInitialRoundTripTime); orig_params.google_connection_options = CreateFakeGoogleConnectionOptions(); - orig_params.user_agent_id = CreateFakeUserAgentId(); - orig_params.key_update_not_yet_supported = kFakeKeyUpdateNotYetSupported; orig_params.custom_parameters[kCustomParameter1] = kCustomParameter1Value; orig_params.custom_parameters[kCustomParameter2] = kCustomParameter2Value; @@ -329,12 +322,6 @@ TEST_P(TransportParametersTest, RoundTripClient) { CreateFakeInitialSourceConnectionId(); orig_params.initial_round_trip_time_us.set_value(kFakeInitialRoundTripTime); orig_params.google_connection_options = CreateFakeGoogleConnectionOptions(); - if (!GetQuicReloadableFlag(quic_ignore_user_agent_transport_parameter)) { - orig_params.user_agent_id = CreateFakeUserAgentId(); - } - if (!GetQuicReloadableFlag(quic_ignore_key_update_not_yet_supported)) { - orig_params.key_update_not_yet_supported = kFakeKeyUpdateNotYetSupported; - } orig_params.custom_parameters[kCustomParameter1] = kCustomParameter1Value; orig_params.custom_parameters[kCustomParameter2] = kCustomParameter2Value; @@ -578,13 +565,6 @@ TEST_P(TransportParametersTest, ParseClientParams) { 'A', 'L', 'P', 'N', // value 'E', 'F', 'G', 0x00, 'H', 'I', 'J', 0xff, - // user_agent_id - 0x71, 0x29, // parameter id - 0x08, // length - 'F', 'a', 'k', 'e', 'U', 'A', 'I', 'D', // value - // key_update_not_yet_supported - 0x71, 0x2B, // parameter id - 0x00, // length // Google version extension 0x80, 0x00, 0x47, 0x52, // parameter id 0x04, // length @@ -649,15 +629,6 @@ TEST_P(TransportParametersTest, ParseClientParams) { ASSERT_TRUE(new_params.google_connection_options.has_value()); EXPECT_EQ(CreateFakeGoogleConnectionOptions(), new_params.google_connection_options.value()); - if (!GetQuicReloadableFlag(quic_ignore_user_agent_transport_parameter)) { - ASSERT_TRUE(new_params.user_agent_id.has_value()); - EXPECT_EQ(CreateFakeUserAgentId(), new_params.user_agent_id.value()); - } else { - EXPECT_FALSE(new_params.user_agent_id.has_value()); - } - if (!GetQuicReloadableFlag(quic_ignore_key_update_not_yet_supported)) { - EXPECT_TRUE(new_params.key_update_not_yet_supported); - } } TEST_P(TransportParametersTest, @@ -844,9 +815,6 @@ TEST_P(TransportParametersTest, ParseServerParams) { 'A', 'L', 'P', 'N', // value 'E', 'F', 'G', 0x00, 'H', 'I', 'J', 0xff, - // key_update_not_yet_supported - 0x71, 0x2B, // parameter id - 0x00, // length // Google version extension 0x80, 0x00, 0x47, 0x52, // parameter id 0x0d, // length @@ -933,10 +901,6 @@ TEST_P(TransportParametersTest, ParseServerParams) { ASSERT_TRUE(new_params.google_connection_options.has_value()); EXPECT_EQ(CreateFakeGoogleConnectionOptions(), new_params.google_connection_options.value()); - EXPECT_FALSE(new_params.user_agent_id.has_value()); - if (!GetQuicReloadableFlag(quic_ignore_key_update_not_yet_supported)) { - EXPECT_TRUE(new_params.key_update_not_yet_supported); - } } TEST_P(TransportParametersTest, ParseServerParametersRepeated) { @@ -1054,8 +1018,6 @@ TEST_P(TransportParametersTest, SerializationOrderIsRandom) { CreateFakeInitialSourceConnectionId(); orig_params.initial_round_trip_time_us.set_value(kFakeInitialRoundTripTime); orig_params.google_connection_options = CreateFakeGoogleConnectionOptions(); - orig_params.user_agent_id = CreateFakeUserAgentId(); - orig_params.key_update_not_yet_supported = kFakeKeyUpdateNotYetSupported; orig_params.custom_parameters[kCustomParameter1] = kCustomParameter1Value; orig_params.custom_parameters[kCustomParameter2] = kCustomParameter2Value; diff --git a/chromium/net/third_party/quiche/src/quic/core/frames/quic_ack_frame.h b/chromium/net/third_party/quiche/src/quic/core/frames/quic_ack_frame.h index 8a5c7e6e12e..c9c46d41d3c 100644 --- a/chromium/net/third_party/quiche/src/quic/core/frames/quic_ack_frame.h +++ b/chromium/net/third_party/quiche/src/quic/core/frames/quic_ack_frame.h @@ -111,6 +111,8 @@ struct QUIC_EXPORT_PRIVATE QuicAckFrame { QuicTime::Delta ack_delay_time = QuicTime::Delta::Infinite(); // Vector of <packet_number, time> for when packets arrived. + // For IETF versions, packet numbers and timestamps in this vector are both in + // ascending orders. Packets received out of order are not saved here. PacketTimeVector received_packet_times; // Set of packets. diff --git a/chromium/net/third_party/quiche/src/quic/core/handshaker_delegate_interface.h b/chromium/net/third_party/quiche/src/quic/core/handshaker_delegate_interface.h index 1e47d16a5f1..7fbc5ae33db 100644 --- a/chromium/net/third_party/quiche/src/quic/core/handshaker_delegate_interface.h +++ b/chromium/net/third_party/quiche/src/quic/core/handshaker_delegate_interface.h @@ -7,6 +7,7 @@ #include "quic/core/crypto/transport_parameters.h" #include "quic/core/quic_types.h" +#include "quic/core/quic_versions.h" namespace quic { @@ -21,15 +22,12 @@ class QUIC_EXPORT_PRIVATE HandshakerDelegateInterface { // Called when new decryption key of |level| is available. Returns true if // decrypter is set successfully, otherwise, returns false. virtual bool OnNewDecryptionKeyAvailable( - EncryptionLevel level, - std::unique_ptr<QuicDecrypter> decrypter, - bool set_alternative_decrypter, - bool latch_once_used) = 0; + EncryptionLevel level, std::unique_ptr<QuicDecrypter> decrypter, + bool set_alternative_decrypter, bool latch_once_used) = 0; // Called when new encryption key of |level| is available. virtual void OnNewEncryptionKeyAvailable( - EncryptionLevel level, - std::unique_ptr<QuicEncrypter> encrypter) = 0; + EncryptionLevel level, std::unique_ptr<QuicEncrypter> encrypter) = 0; // Called to set default encryption level to |level|. Only used in QUIC // crypto. @@ -68,8 +66,7 @@ class QUIC_EXPORT_PRIVATE HandshakerDelegateInterface { // On failure, returns a QuicErrorCode and saves a detailed error in // |error_details|. virtual QuicErrorCode ProcessTransportParameters( - const TransportParameters& params, - bool is_resumption, + const TransportParameters& params, bool is_resumption, std::string* error_details) = 0; // Called at the end of an handshake operation callback. @@ -77,6 +74,10 @@ class QUIC_EXPORT_PRIVATE HandshakerDelegateInterface { // Whether a packet flusher is currently attached. virtual bool PacketFlusherAttached() const = 0; + + // Get the QUIC version currently in use. tls_handshaker needs this to pass + // to crypto_utils to apply version-dependent HKDF labels. + virtual ParsedQuicVersion parsed_version() const = 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 29e6377280b..cad9cacb022 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 @@ -485,8 +485,7 @@ class EndToEndTest : public QuicTestWithParam<TestParams> { } } - void AddToCache(absl::string_view path, - int response_code, + void AddToCache(absl::string_view path, int response_code, absl::string_view body) { memory_cache_backend_.AddSimpleResponse(server_hostname_, path, response_code, body); @@ -553,12 +552,6 @@ class EndToEndTest : public QuicTestWithParam<TestParams> { EXPECT_EQ(0u, server_stats.packets_lost); } EXPECT_EQ(0u, server_stats.packets_discarded); - if (!GetQuicReloadableFlag( - quic_ignore_user_agent_transport_parameter)) { - EXPECT_EQ( - server_session->user_agent_id().value_or("MissingUserAgent"), - kTestUserAgentId); - } } else { ADD_FAILURE() << "Missing server connection"; } @@ -657,16 +650,14 @@ class EndToEndTest : public QuicTestWithParam<TestParams> { } bool SendSynchronousRequestAndCheckResponse( - QuicTestClient* client, - const std::string& request, + QuicTestClient* client, const std::string& request, const std::string& expected_response) { std::string received_response = client->SendSynchronousRequest(request); return CheckResponse(client, received_response, expected_response); } bool SendSynchronousRequestAndCheckResponse( - const std::string& request, - const std::string& expected_response) { + const std::string& request, const std::string& expected_response) { return SendSynchronousRequestAndCheckResponse(client_.get(), request, expected_response); } @@ -844,8 +835,7 @@ class EndToEndTest : public QuicTestWithParam<TestParams> { }; // Run all end to end tests with all supported versions. -INSTANTIATE_TEST_SUITE_P(EndToEndTests, - EndToEndTest, +INSTANTIATE_TEST_SUITE_P(EndToEndTests, EndToEndTest, ::testing::ValuesIn(GetTestParams()), ::testing::PrintToStringParamName()); @@ -3210,8 +3200,7 @@ TEST_P(EndToEndTest, ConnectionMigrationNewTokenForNewIp) { class DuplicatePacketWithSpoofedSelfAddressWriter : public QuicPacketWriterWrapper { public: - WriteResult WritePacket(const char* buffer, - size_t buf_len, + WriteResult WritePacket(const char* buffer, size_t buf_len, const QuicIpAddress& self_address, const QuicSocketAddress& peer_address, PerPacketOptions* options) override { @@ -3246,7 +3235,8 @@ TEST_P(EndToEndTest, ClientAddressSpoofedForSomePeriod) { ASSERT_TRUE(QuicConnectionPeer::HasUnusedPeerIssuedConnectionId( GetClientConnection())); - QuicIpAddress real_host = TestLoopback(1); + QuicIpAddress real_host = + client_->client()->session()->connection()->self_address().host(); ASSERT_TRUE(client_->MigrateSocket(real_host)); SendSynchronousFooRequestAndCheckResponse(); EXPECT_EQ( @@ -4384,13 +4374,10 @@ TEST_P(EndToEndTest, CanceledStreamDoesNotBecomeZombie) { class ServerStreamWithErrorResponseBody : public QuicSimpleServerStream { public: ServerStreamWithErrorResponseBody( - QuicStreamId id, - QuicSpdySession* session, + QuicStreamId id, QuicSpdySession* session, QuicSimpleServerBackend* quic_simple_server_backend, std::string response_body) - : QuicSimpleServerStream(id, - session, - BIDIRECTIONAL, + : QuicSimpleServerStream(id, session, BIDIRECTIONAL, quic_simple_server_backend), response_body_(std::move(response_body)) {} @@ -4419,8 +4406,7 @@ class StreamWithErrorFactory : public QuicTestServer::StreamFactory { ~StreamWithErrorFactory() override = default; QuicSimpleServerStream* CreateStream( - QuicStreamId id, - QuicSpdySession* session, + QuicStreamId id, QuicSpdySession* session, QuicSimpleServerBackend* quic_simple_server_backend) override { return new ServerStreamWithErrorResponseBody( id, session, quic_simple_server_backend, response_body_); @@ -4433,12 +4419,9 @@ class StreamWithErrorFactory : public QuicTestServer::StreamFactory { // A test server stream that drops all received body. class ServerStreamThatDropsBody : public QuicSimpleServerStream { public: - ServerStreamThatDropsBody(QuicStreamId id, - QuicSpdySession* session, + ServerStreamThatDropsBody(QuicStreamId id, QuicSpdySession* session, QuicSimpleServerBackend* quic_simple_server_backend) - : QuicSimpleServerStream(id, - session, - BIDIRECTIONAL, + : QuicSimpleServerStream(id, session, BIDIRECTIONAL, quic_simple_server_backend) {} ~ServerStreamThatDropsBody() override = default; @@ -4480,8 +4463,7 @@ class ServerStreamThatDropsBodyFactory : public QuicTestServer::StreamFactory { ~ServerStreamThatDropsBodyFactory() override = default; QuicSimpleServerStream* CreateStream( - QuicStreamId id, - QuicSpdySession* session, + QuicStreamId id, QuicSpdySession* session, QuicSimpleServerBackend* quic_simple_server_backend) override { return new ServerStreamThatDropsBody(id, session, quic_simple_server_backend); @@ -4492,13 +4474,9 @@ class ServerStreamThatDropsBodyFactory : public QuicTestServer::StreamFactory { class ServerStreamThatSendsHugeResponse : public QuicSimpleServerStream { public: ServerStreamThatSendsHugeResponse( - QuicStreamId id, - QuicSpdySession* session, - QuicSimpleServerBackend* quic_simple_server_backend, - int64_t body_bytes) - : QuicSimpleServerStream(id, - session, - BIDIRECTIONAL, + QuicStreamId id, QuicSpdySession* session, + QuicSimpleServerBackend* quic_simple_server_backend, int64_t body_bytes) + : QuicSimpleServerStream(id, session, BIDIRECTIONAL, quic_simple_server_backend), body_bytes_(body_bytes) {} @@ -4528,8 +4506,7 @@ class ServerStreamThatSendsHugeResponseFactory ~ServerStreamThatSendsHugeResponseFactory() override = default; QuicSimpleServerStream* CreateStream( - QuicStreamId id, - QuicSpdySession* session, + QuicStreamId id, QuicSpdySession* session, QuicSimpleServerBackend* quic_simple_server_backend) override { return new ServerStreamThatSendsHugeResponse( id, session, quic_simple_server_backend, body_bytes_); @@ -5254,8 +5231,7 @@ TEST_P(EndToEndPacketReorderingTest, ReorderedConnectivityProbing) { // called. class PacketHoldingWriter : public QuicPacketWriterWrapper { public: - WriteResult WritePacket(const char* buffer, - size_t buf_len, + WriteResult WritePacket(const char* buffer, size_t buf_len, const QuicIpAddress& self_address, const QuicSocketAddress& peer_address, PerPacketOptions* options) override { @@ -5533,11 +5509,13 @@ TEST_P(EndToEndPacketReorderingTest, MigrateAgainAfterPathValidationFailure) { } TEST_P(EndToEndPacketReorderingTest, - MigrateAgainAfterPathValidationFailureWithNonZeroClientConnectionId) { + MigrateAgainAfterPathValidationFailureWithNonZeroClientCid) { if (!version_.SupportsClientConnectionIds()) { ASSERT_TRUE(Initialize()); return; } + SetQuicReloadableFlag(quic_retire_cid_on_reverse_path_validation_failure, + true); override_client_connection_id_length_ = kQuicDefaultConnectionIdLength; ASSERT_TRUE(Initialize()); if (!GetClientConnection()->connection_migration_use_new_cid()) { @@ -5707,8 +5685,7 @@ class BadShloPacketWriter : public QuicPacketWriterWrapper { : error_returned_(false), version_(version) {} ~BadShloPacketWriter() override {} - WriteResult WritePacket(const char* buffer, - size_t buf_len, + WriteResult WritePacket(const char* buffer, size_t buf_len, const QuicIpAddress& self_address, const QuicSocketAddress& peer_address, quic::PerPacketOptions* options) override { @@ -5725,6 +5702,9 @@ class BadShloPacketWriter : public QuicPacketWriterWrapper { } bool TypeByteIsServerHello(uint8_t type_byte) { + if (version_.UsesV2PacketTypes()) { + return ((type_byte & 0x30) >> 4) == 3; + } if (version_.UsesQuicCrypto()) { // ENCRYPTION_ZERO_RTT packet. return ((type_byte & 0x30) >> 4) == 1; @@ -5781,21 +5761,23 @@ TEST_P(EndToEndTest, ConnectionCloseBeforeHandshakeComplete) { class BadShloPacketWriter2 : public QuicPacketWriterWrapper { public: - BadShloPacketWriter2() : error_returned_(false) {} + BadShloPacketWriter2(ParsedQuicVersion version) + : error_returned_(false), version_(version) {} ~BadShloPacketWriter2() override {} - WriteResult WritePacket(const char* buffer, - size_t buf_len, + WriteResult WritePacket(const char* buffer, size_t buf_len, const QuicIpAddress& self_address, const QuicSocketAddress& peer_address, quic::PerPacketOptions* options) override { const uint8_t type_byte = buffer[0]; - if ((type_byte & FLAGS_LONG_HEADER) && - (((type_byte & 0x30) >> 4) == 1 || (type_byte & 0x7F) == 0x7C)) { - QUIC_DVLOG(1) << "Dropping ZERO_RTT_PACKET packet"; - return WriteResult(WRITE_STATUS_OK, buf_len); - } - if (!error_returned_ && !(type_byte & FLAGS_LONG_HEADER)) { + + if (type_byte & FLAGS_LONG_HEADER) { + if (((type_byte & 0x30 >> 4) == (version_.UsesV2PacketTypes() ? 2 : 1)) || + ((type_byte & 0x7F) == 0x7C)) { + QUIC_DVLOG(1) << "Dropping ZERO_RTT_PACKET packet"; + return WriteResult(WRITE_STATUS_OK, buf_len); + } + } else if (!error_returned_) { QUIC_DVLOG(1) << "Return write error for short header packet"; error_returned_ = true; return WriteResult(WRITE_STATUS_ERROR, QUIC_EMSGSIZE); @@ -5806,6 +5788,7 @@ class BadShloPacketWriter2 : public QuicPacketWriterWrapper { private: bool error_returned_; + ParsedQuicVersion version_; }; TEST_P(EndToEndTest, ForwardSecureConnectionClose) { @@ -5836,7 +5819,7 @@ TEST_P(EndToEndTest, ForwardSecureConnectionClose) { dispatcher, // This causes the all server sent ZERO_RTT_PROTECTED packets to be // dropped, and first short header packet causes write error. - new BadShloPacketWriter2()); + new BadShloPacketWriter2(version_)); server_thread_->Resume(); client_.reset(CreateQuicClient(client_writer_)); EXPECT_EQ("", client_->SendSynchronousRequest("/foo")); @@ -5913,10 +5896,6 @@ TEST_P(EndToEndTest, CustomTransportParameters) { QuicConfig* server_config = nullptr; if (server_session != nullptr) { server_config = server_session->config(); - if (!GetQuicReloadableFlag(quic_ignore_user_agent_transport_parameter)) { - EXPECT_EQ(server_session->user_agent_id().value_or("MissingUserAgent"), - kTestUserAgentId); - } } else { ADD_FAILURE() << "Missing server session"; } @@ -6025,8 +6004,7 @@ class CopyingPacketWriter : public PacketDroppingTestWriter { public: explicit CopyingPacketWriter(int num_packets_to_copy) : num_packets_to_copy_(num_packets_to_copy) {} - WriteResult WritePacket(const char* buffer, - size_t buf_len, + WriteResult WritePacket(const char* buffer, size_t buf_len, const QuicIpAddress& self_address, const QuicSocketAddress& peer_address, PerPacketOptions* options) override { diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_headers_stream_test.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_headers_stream_test.cc index 42fef63c978..3a44f79a1d1 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/quic_headers_stream_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_headers_stream_test.cc @@ -50,7 +50,6 @@ using spdy::SpdyGoAwayIR; using spdy::SpdyHeaderBlock; using spdy::SpdyHeadersHandlerInterface; using spdy::SpdyHeadersIR; -using spdy::SpdyKnownSettingsId; using spdy::SpdyPingId; using spdy::SpdyPingIR; using spdy::SpdyPriority; diff --git a/chromium/net/third_party/quiche/src/quic/core/http/quic_server_session_base_test.cc b/chromium/net/third_party/quiche/src/quic/core/http/quic_server_session_base_test.cc index 30c4178feca..2d144d2465c 100644 --- a/chromium/net/third_party/quiche/src/quic/core/http/quic_server_session_base_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/http/quic_server_session_base_test.cc @@ -45,7 +45,6 @@ using testing::_; using testing::StrictMock; using testing::AtLeast; -using testing::Return; namespace quic { namespace test { 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 5a859271e6e..cfc47296417 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 @@ -43,7 +43,6 @@ using spdy::SpdyFrameType; using spdy::SpdyHeaderBlock; using spdy::SpdyHeadersHandlerInterface; using spdy::SpdyHeadersIR; -using spdy::SpdyKnownSettingsId; using spdy::SpdyPingId; using spdy::SpdyPriority; using spdy::SpdyPriorityIR; 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 5db1aab4e91..45506211f9d 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 @@ -153,7 +153,6 @@ class TestCryptoStream : public QuicCryptoStream, public QuicCryptoHandshaker { } void SetServerApplicationStateForResumption( std::unique_ptr<ApplicationState> /*application_state*/) override {} - bool KeyUpdateSupportedLocally() const override { return false; } std::unique_ptr<QuicDecrypter> AdvanceKeysAndCreateCurrentOneRttDecrypter() override { return nullptr; 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 8147ce00dcc..5317137d7e0 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 @@ -185,6 +185,8 @@ QuicSpdyStream::QuicSpdyStream(QuicStreamId id, QuicSpdySession* spdy_session, headers_payload_length_(0), trailers_decompressed_(false), trailers_consumed_(false), + qpack_decoded_headers_accumulator_reset_reason_( + QpackDecodedHeadersAccumulatorResetReason::kUnSet), http_decoder_visitor_(std::make_unique<HttpDecoderVisitor>(this)), decoder_(http_decoder_visitor_.get(), HttpDecoderOptionsForBidiStream(spdy_session)), @@ -225,6 +227,8 @@ QuicSpdyStream::QuicSpdyStream(PendingStream* pending, headers_payload_length_(0), trailers_decompressed_(false), trailers_consumed_(false), + qpack_decoded_headers_accumulator_reset_reason_( + QpackDecodedHeadersAccumulatorResetReason::kUnSet), http_decoder_visitor_(std::make_unique<HttpDecoderVisitor>(this)), decoder_(http_decoder_visitor_.get()), sequencer_offset_(sequencer()->NumBytesConsumed()), @@ -563,6 +567,8 @@ void QuicSpdyStream::OnHeadersDecoded(QuicHeaderList headers, bool header_list_size_limit_exceeded) { header_list_size_limit_exceeded_ = header_list_size_limit_exceeded; qpack_decoded_headers_accumulator_.reset(); + qpack_decoded_headers_accumulator_reset_reason_ = + QpackDecodedHeadersAccumulatorResetReason::kResetInOnHeadersDecoded; QuicSpdySession::LogHeaderCompressionRatioHistogram( /* using_qpack = */ true, @@ -592,6 +598,8 @@ void QuicSpdyStream::OnHeadersDecoded(QuicHeaderList headers, void QuicSpdyStream::OnHeaderDecodingError(QuicErrorCode error_code, absl::string_view error_message) { qpack_decoded_headers_accumulator_.reset(); + qpack_decoded_headers_accumulator_reset_reason_ = + QpackDecodedHeadersAccumulatorResetReason::kResetInOnHeaderDecodingError; std::string connection_close_error_message = absl::StrCat( "Error decoding ", headers_decompressed_ ? "trailers" : "headers", @@ -756,6 +764,8 @@ void QuicSpdyStream::OnStreamReset(const QuicRstStreamFrame& frame) { if (GetQuicReloadableFlag(quic_abort_qpack_on_stream_reset)) { QUIC_RELOADABLE_FLAG_COUNT_N(quic_abort_qpack_on_stream_reset, 1, 2); qpack_decoded_headers_accumulator_.reset(); + qpack_decoded_headers_accumulator_reset_reason_ = + QpackDecodedHeadersAccumulatorResetReason::kResetInOnStreamReset1; } } @@ -769,6 +779,8 @@ void QuicSpdyStream::OnStreamReset(const QuicRstStreamFrame& frame) { if (!fin_received() && spdy_session_->qpack_decoder()) { spdy_session_->qpack_decoder()->OnStreamReset(id()); qpack_decoded_headers_accumulator_.reset(); + qpack_decoded_headers_accumulator_reset_reason_ = + QpackDecodedHeadersAccumulatorResetReason::kResetInOnStreamReset2; } QuicStream::OnStreamReset(frame); @@ -791,6 +803,8 @@ void QuicSpdyStream::ResetWithError(QuicResetStreamError error) { if (GetQuicReloadableFlag(quic_abort_qpack_on_stream_reset)) { QUIC_RELOADABLE_FLAG_COUNT_N(quic_abort_qpack_on_stream_reset, 2, 2); qpack_decoded_headers_accumulator_.reset(); + qpack_decoded_headers_accumulator_reset_reason_ = + QpackDecodedHeadersAccumulatorResetReason::kResetInResetWithError; } } @@ -893,6 +907,8 @@ void QuicSpdyStream::OnClose() { QuicStream::OnClose(); qpack_decoded_headers_accumulator_.reset(); + qpack_decoded_headers_accumulator_reset_reason_ = + QpackDecodedHeadersAccumulatorResetReason::kResetInOnClose; if (visitor_) { Visitor* visitor = visitor_; @@ -1088,7 +1104,14 @@ bool QuicSpdyStream::OnHeadersFrameStart(QuicByteCount header_length, bool QuicSpdyStream::OnHeadersFramePayload(absl::string_view payload) { QUICHE_DCHECK(VersionUsesHttp3(transport_version())); - QUICHE_DCHECK(qpack_decoded_headers_accumulator_); + + if (!qpack_decoded_headers_accumulator_) { + QUIC_BUG(b215142466_OnHeadersFramePayload) + << static_cast<int>(qpack_decoded_headers_accumulator_reset_reason_); + OnHeaderDecodingError(QUIC_INTERNAL_ERROR, + "qpack_decoded_headers_accumulator_ is nullptr"); + return false; + } qpack_decoded_headers_accumulator_->Decode(payload); @@ -1103,7 +1126,14 @@ bool QuicSpdyStream::OnHeadersFramePayload(absl::string_view payload) { bool QuicSpdyStream::OnHeadersFrameEnd() { QUICHE_DCHECK(VersionUsesHttp3(transport_version())); - QUICHE_DCHECK(qpack_decoded_headers_accumulator_); + + if (!qpack_decoded_headers_accumulator_) { + QUIC_BUG(b215142466_OnHeadersFrameEnd) + << static_cast<int>(qpack_decoded_headers_accumulator_reset_reason_); + OnHeaderDecodingError(QUIC_INTERNAL_ERROR, + "qpack_decoded_headers_accumulator_ is nullptr"); + return false; + } qpack_decoded_headers_accumulator_->EndHeaderBlock(); @@ -1244,9 +1274,7 @@ void QuicSpdyStream::MaybeProcessReceivedWebTransportHeaders() { std::string protocol; absl::optional<QuicDatagramStreamId> flow_id; bool version_indicated = false; - for (const auto& header : header_list_) { - const std::string& header_name = header.first; - const std::string& header_value = header.second; + for (const auto& [header_name, header_value] : header_list_) { if (header_name == ":method") { if (!method.empty() || header_value.empty()) { return; 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 8ec56d58f06..14ac2f2f585 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 @@ -391,6 +391,20 @@ class QUIC_EXPORT_PRIVATE QuicSpdyStream WebTransportStreamAdapter adapter; }; + // Reason codes for `qpack_decoded_headers_accumulator_` being nullptr. + enum class QpackDecodedHeadersAccumulatorResetReason { + // `qpack_decoded_headers_accumulator_` was default constructed to nullptr. + kUnSet = 0, + // `qpack_decoded_headers_accumulator_` was reset in the corresponding + // method. + kResetInOnHeadersDecoded = 1, + kResetInOnHeaderDecodingError = 2, + kResetInOnStreamReset1 = 3, + kResetInOnStreamReset2 = 4, + kResetInResetWithError = 5, + kResetInOnClose = 6, + }; + // Called by HttpDecoderVisitor. bool OnDataFrameStart(QuicByteCount header_length, QuicByteCount payload_length); @@ -462,6 +476,9 @@ class QUIC_EXPORT_PRIVATE QuicSpdyStream // Headers accumulator for decoding HEADERS frame payload. std::unique_ptr<QpackDecodedHeadersAccumulator> qpack_decoded_headers_accumulator_; + // Reason for `qpack_decoded_headers_accumulator_` being nullptr. + QpackDecodedHeadersAccumulatorResetReason + qpack_decoded_headers_accumulator_reset_reason_; // Visitor of the HttpDecoder. std::unique_ptr<HttpDecoderVisitor> http_decoder_visitor_; // HttpDecoder for processing raw incoming stream frames. 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 3bad6efe6ee..c8146e1c5a1 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 @@ -136,7 +136,6 @@ class TestCryptoStream : public QuicCryptoStream, public QuicCryptoHandshaker { } void SetServerApplicationStateForResumption( std::unique_ptr<ApplicationState> /*application_state*/) override {} - bool KeyUpdateSupportedLocally() const override { return true; } std::unique_ptr<QuicDecrypter> AdvanceKeysAndCreateCurrentOneRttDecrypter() override { return nullptr; @@ -3424,6 +3423,32 @@ TEST_P(QuicSpdyStreamTest, GetMaxDatagramSize) { EXPECT_EQ(size - 1, size_with_context); } +TEST_P(QuicSpdyStreamTest, + QUIC_TEST_DISABLED_IN_CHROME(HeadersAccumulatorNullptr)) { + if (!UsesHttp3()) { + return; + } + + Initialize(kShouldProcessData); + + // Creates QpackDecodedHeadersAccumulator in + // `qpack_decoded_headers_accumulator_`. + std::string headers = HeadersFrame({std::make_pair("foo", "bar")}); + stream_->OnStreamFrame(QuicStreamFrame(stream_->id(), false, 0, headers)); + + // Resets `qpack_decoded_headers_accumulator_`. + stream_->OnHeadersDecoded({}, false); + + // This private method should never be called when + // `qpack_decoded_headers_accumulator_` is nullptr. The number 1 identifies + // the site where `qpack_decoded_headers_accumulator_` was last reset. + EXPECT_CALL(*connection_, CloseConnection(_, _, _)); + bool result = true; + EXPECT_QUIC_BUG(result = QuicSpdyStreamPeer::OnHeadersFrameEnd(stream_), + "b215142466_OnHeadersFrameEnd.: 1$"); + EXPECT_FALSE(result); +} + } // 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 74c9c53a72d..5b744077402 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 @@ -13,6 +13,7 @@ #include "absl/strings/str_split.h" #include "absl/strings/string_view.h" #include "absl/types/optional.h" +#include "quic/core/quic_versions.h" #include "quic/platform/api/quic_flag_utils.h" #include "quic/platform/api/quic_flags.h" #include "quic/platform/api/quic_logging.h" @@ -194,4 +195,23 @@ void SpdyUtils::AddDatagramFlowIdHeader(spdy::SpdyHeaderBlock* headers, (*headers)["datagram-flow-id"] = absl::StrCat(flow_id); } +// static +ParsedQuicVersion SpdyUtils::ExtractQuicVersionFromAltSvcEntry( + const spdy::SpdyAltSvcWireFormat::AlternativeService& + alternative_service_entry, + const ParsedQuicVersionVector& supported_versions) { + for (const ParsedQuicVersion& version : supported_versions) { + if (version.AlpnDeferToRFCv1()) { + // Versions with share an ALPN with v1 are currently unable to be + // advertised with Alt-Svc. + continue; + } + if (AlpnForVersion(version) == alternative_service_entry.protocol_id) { + return version; + } + } + + return ParsedQuicVersion::Unsupported(); +} + } // namespace quic 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 01fae4f17a7..85099ab15e7 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 @@ -14,6 +14,7 @@ #include "quic/core/http/quic_header_list.h" #include "quic/core/quic_packets.h" #include "quic/platform/api/quic_export.h" +#include "spdy/core/spdy_alt_svc_wire_format.h" #include "spdy/core/spdy_header_block.h" namespace quic { @@ -61,6 +62,14 @@ class QUIC_EXPORT_PRIVATE SpdyUtils { // Adds the "datagram-flow-id" header. static void AddDatagramFlowIdHeader(spdy::SpdyHeaderBlock* headers, QuicDatagramStreamId flow_id); + + // Returns the advertised QUIC version from the specified alternative service + // advertisement, or ParsedQuicVersion::Unsupported() if no supported version + // is advertised. + static ParsedQuicVersion ExtractQuicVersionFromAltSvcEntry( + const spdy::SpdyAltSvcWireFormat::AlternativeService& + alternative_service_entry, + const ParsedQuicVersionVector& supported_versions); }; } // 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 918739d6f90..85c5ceccd57 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 @@ -2,12 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "quic/core/http/spdy_utils.h" + #include <memory> #include <string> #include "absl/base/macros.h" #include "absl/strings/string_view.h" -#include "quic/core/http/spdy_utils.h" +#include "quic/core/quic_versions.h" #include "quic/platform/api/quic_test.h" using spdy::SpdyHeaderBlock; @@ -408,5 +410,33 @@ TEST_F(DatagramFlowIdTest, DatagramFlowId) { ValidateDatagramFlowId("44; ecn-ect0, 42, 48; ecn-ce, 46; ecn-ect1", 42); } +using ExtractQuicVersionFromAltSvcEntry = QuicTest; + +TEST_F(ExtractQuicVersionFromAltSvcEntry, SupportedVersion) { + ParsedQuicVersionVector supported_versions = AllSupportedVersions(); + spdy::SpdyAltSvcWireFormat::AlternativeService entry; + for (const ParsedQuicVersion& version : supported_versions) { + entry.protocol_id = AlpnForVersion(version); + ParsedQuicVersion expected_version = version; + // Versions with share an ALPN with v1 are currently unable to be + // advertised with Alt-Svc. + if (entry.protocol_id == AlpnForVersion(ParsedQuicVersion::RFCv1()) && + version != ParsedQuicVersion::RFCv1()) { + expected_version = ParsedQuicVersion::RFCv1(); + } + EXPECT_EQ(expected_version, SpdyUtils::ExtractQuicVersionFromAltSvcEntry( + entry, supported_versions)) + << "version: " << version; + } +} + +TEST_F(ExtractQuicVersionFromAltSvcEntry, UnsupportedVersion) { + spdy::SpdyAltSvcWireFormat::AlternativeService entry; + entry.protocol_id = "quic"; + EXPECT_EQ(ParsedQuicVersion::Unsupported(), + SpdyUtils::ExtractQuicVersionFromAltSvcEntry( + entry, AllSupportedVersions())); +} + } // namespace test } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoded_headers_accumulator.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoded_headers_accumulator.cc index 4e50e728ee4..934cc5f0d4d 100644 --- a/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoded_headers_accumulator.cc +++ b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_decoded_headers_accumulator.cc @@ -7,6 +7,7 @@ #include "absl/strings/string_view.h" #include "quic/core/qpack/qpack_decoder.h" #include "quic/core/qpack/qpack_header_table.h" +#include "quic/platform/api/quic_bug_tracker.h" namespace quic { @@ -88,6 +89,11 @@ void QpackDecodedHeadersAccumulator::EndHeaderBlock() { QUICHE_DCHECK(!error_detected_); QUICHE_DCHECK(!headers_decoded_); + if (!decoder_) { + QUIC_BUG(b215142466_EndHeaderBlock); + return; + } + // Might destroy |this|. decoder_->EndHeaderBlock(); } diff --git a/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder.cc index 3ae944152c0..974f7b2274f 100644 --- a/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder.cc +++ b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder.cc @@ -87,6 +87,12 @@ QpackEncoder::Representations QpackEncoder::FirstPassEncode( const QuicByteCount initial_encoder_stream_buffered_byte_count = encoder_stream_sender_.BufferedByteCount(); + bool can_write_to_encoder_stream = true; + if (GetQuicReloadableFlag(quic_limit_encoder_stream_buffering)) { + QUIC_RELOADABLE_FLAG_COUNT(quic_limit_encoder_stream_buffering); + can_write_to_encoder_stream = encoder_stream_sender_.CanWrite(); + } + Representations representations; representations.reserve(header_list.size()); @@ -152,17 +158,20 @@ QpackEncoder::Representations QpackEncoder::FirstPassEncode( std::min(smallest_blocking_index, index))) { dynamic_table_insertion_blocked = true; } else { - // If allowed, duplicate entry and refer to it. - encoder_stream_sender_.SendDuplicate( - QpackAbsoluteIndexToEncoderStreamRelativeIndex( - index, header_table_.inserted_entry_count())); - uint64_t new_index = header_table_.InsertEntry(name, value); - representations.push_back(EncodeIndexedHeaderField( - is_static, new_index, referred_indices)); - smallest_blocking_index = std::min(smallest_blocking_index, index); - header_table_.set_dynamic_table_entry_referenced(); - - break; + if (can_write_to_encoder_stream) { + // If allowed, duplicate entry and refer to it. + encoder_stream_sender_.SendDuplicate( + QpackAbsoluteIndexToEncoderStreamRelativeIndex( + index, header_table_.inserted_entry_count())); + uint64_t new_index = header_table_.InsertEntry(name, value); + representations.push_back(EncodeIndexedHeaderField( + is_static, new_index, referred_indices)); + smallest_blocking_index = + std::min(smallest_blocking_index, index); + header_table_.set_dynamic_table_entry_referenced(); + + break; + } } } @@ -182,15 +191,17 @@ QpackEncoder::Representations QpackEncoder::FirstPassEncode( header_table_.MaxInsertSizeWithoutEvictingGivenEntry( smallest_blocking_index)) { // If allowed, insert entry into dynamic table and refer to it. - encoder_stream_sender_.SendInsertWithNameReference(is_static, index, - value); - uint64_t new_index = header_table_.InsertEntry(name, value); - representations.push_back(EncodeIndexedHeaderField( - /* is_static = */ false, new_index, referred_indices)); - smallest_blocking_index = - std::min<uint64_t>(smallest_blocking_index, new_index); - - break; + if (can_write_to_encoder_stream) { + encoder_stream_sender_.SendInsertWithNameReference(is_static, + index, value); + uint64_t new_index = header_table_.InsertEntry(name, value); + representations.push_back(EncodeIndexedHeaderField( + /* is_static = */ false, new_index, referred_indices)); + smallest_blocking_index = + std::min<uint64_t>(smallest_blocking_index, new_index); + + break; + } } // Emit literal field with name reference. @@ -208,18 +219,20 @@ QpackEncoder::Representations QpackEncoder::FirstPassEncode( dynamic_table_insertion_blocked = true; } else { // If allowed, insert entry with name reference and refer to it. - encoder_stream_sender_.SendInsertWithNameReference( - is_static, - QpackAbsoluteIndexToEncoderStreamRelativeIndex( - index, header_table_.inserted_entry_count()), - value); - uint64_t new_index = header_table_.InsertEntry(name, value); - representations.push_back( - EncodeIndexedHeaderField(is_static, new_index, referred_indices)); - smallest_blocking_index = std::min(smallest_blocking_index, index); - header_table_.set_dynamic_table_entry_referenced(); + if (can_write_to_encoder_stream) { + encoder_stream_sender_.SendInsertWithNameReference( + is_static, + QpackAbsoluteIndexToEncoderStreamRelativeIndex( + index, header_table_.inserted_entry_count()), + value); + uint64_t new_index = header_table_.InsertEntry(name, value); + representations.push_back(EncodeIndexedHeaderField( + is_static, new_index, referred_indices)); + smallest_blocking_index = std::min(smallest_blocking_index, index); + header_table_.set_dynamic_table_entry_referenced(); - break; + break; + } } if ((blocking_allowed || index < known_received_count) && @@ -251,14 +264,16 @@ QpackEncoder::Representations QpackEncoder::FirstPassEncode( smallest_blocking_index)) { dynamic_table_insertion_blocked = true; } else { - encoder_stream_sender_.SendInsertWithoutNameReference(name, value); - uint64_t new_index = header_table_.InsertEntry(name, value); - representations.push_back(EncodeIndexedHeaderField( - /* is_static = */ false, new_index, referred_indices)); - smallest_blocking_index = - std::min<uint64_t>(smallest_blocking_index, new_index); + if (can_write_to_encoder_stream) { + encoder_stream_sender_.SendInsertWithoutNameReference(name, value); + uint64_t new_index = header_table_.InsertEntry(name, value); + representations.push_back(EncodeIndexedHeaderField( + /* is_static = */ false, new_index, referred_indices)); + smallest_blocking_index = + std::min<uint64_t>(smallest_blocking_index, new_index); - break; + break; + } } // Encode entry as string literals. @@ -275,12 +290,18 @@ QpackEncoder::Representations QpackEncoder::FirstPassEncode( encoder_stream_sender_.BufferedByteCount(); QUICHE_DCHECK_GE(encoder_stream_buffered_byte_count, initial_encoder_stream_buffered_byte_count); + if (encoder_stream_sent_byte_count) { *encoder_stream_sent_byte_count = encoder_stream_buffered_byte_count - initial_encoder_stream_buffered_byte_count; } - encoder_stream_sender_.Flush(); + if (can_write_to_encoder_stream) { + encoder_stream_sender_.Flush(); + } else { + QUICHE_DCHECK_EQ(encoder_stream_buffered_byte_count, + initial_encoder_stream_buffered_byte_count); + } ++header_list_count_; diff --git a/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_sender.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_sender.cc index 4079ba45a85..8b6f887cf3c 100644 --- a/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_sender.cc +++ b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_sender.cc @@ -14,6 +14,14 @@ namespace quic { +namespace { + +// If QUIC stream bufferes more that this number of bytes, +// CanWrite() will return false. +constexpr uint64_t kMaxBytesBufferedByStream = 64 * 1024; + +} // anonymous namespace + QpackEncoderStreamSender::QpackEncoderStreamSender() : delegate_(nullptr) {} void QpackEncoderStreamSender::SendInsertWithNameReference( @@ -44,6 +52,11 @@ void QpackEncoderStreamSender::SendSetDynamicTableCapacity(uint64_t capacity) { QpackInstructionWithValues::SetDynamicTableCapacity(capacity), &buffer_); } +bool QpackEncoderStreamSender::CanWrite() const { + return delegate_ && delegate_->NumBytesBuffered() + buffer_.size() <= + kMaxBytesBufferedByStream; +} + void QpackEncoderStreamSender::Flush() { if (buffer_.empty()) { return; diff --git a/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_sender.h b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_sender.h index 44d777d8d90..d8c64306a78 100644 --- a/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_sender.h +++ b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_sender.h @@ -38,9 +38,18 @@ class QUIC_EXPORT_PRIVATE QpackEncoderStreamSender { // 5.2.4. Set Dynamic Table Capacity void SendSetDynamicTableCapacity(uint64_t capacity); - // Returns number of buffered bytes. + // Returns number of bytes buffered by this object. + // There is no limit on how much data this object is willing to buffer. QuicByteCount BufferedByteCount() const { return buffer_.size(); } + // Returns whether writing to the encoder stream is allowed. Writing is + // disallowed if the amount of data buffered by the underlying stream exceeds + // a hardcoded limit, in order to limit memory consumption in case the encoder + // stream is blocked. CanWrite() returning true does not mean that the + // encoder stream is not blocked, it just means the blocked data does not + // exceed the threshold. + bool CanWrite() const; + // Writes all buffered instructions on the encoder stream. void Flush(); diff --git a/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_test.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_test.cc index 21397ed8571..30f578e9378 100644 --- a/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_encoder_test.cc @@ -18,12 +18,19 @@ using ::testing::_; using ::testing::Eq; +using ::testing::Return; using ::testing::StrictMock; namespace quic { namespace test { namespace { +// A number larger than kMaxBytesBufferedByStream in +// qpack_encoder_stream_sender.cc. Returning this value from NumBytesBuffered() +// will instruct QpackEncoder not to generate any instructions for the encoder +// stream. +constexpr uint64_t kTooManyBytesBuffered = 1024 * 1024; + class QpackEncoderTest : public QuicTest { protected: QpackEncoderTest() @@ -47,6 +54,8 @@ class QpackEncoderTest : public QuicTest { }; TEST_F(QpackEncoderTest, Empty) { + EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered()) + .WillRepeatedly(Return(0)); spdy::Http2HeaderBlock header_list; std::string output = Encode(header_list); @@ -54,6 +63,8 @@ TEST_F(QpackEncoderTest, Empty) { } TEST_F(QpackEncoderTest, EmptyName) { + EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered()) + .WillRepeatedly(Return(0)); spdy::Http2HeaderBlock header_list; header_list[""] = "foo"; std::string output = Encode(header_list); @@ -62,6 +73,8 @@ TEST_F(QpackEncoderTest, EmptyName) { } TEST_F(QpackEncoderTest, EmptyValue) { + EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered()) + .WillRepeatedly(Return(0)); spdy::Http2HeaderBlock header_list; header_list["foo"] = ""; std::string output = Encode(header_list); @@ -70,6 +83,8 @@ TEST_F(QpackEncoderTest, EmptyValue) { } TEST_F(QpackEncoderTest, EmptyNameAndValue) { + EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered()) + .WillRepeatedly(Return(0)); spdy::Http2HeaderBlock header_list; header_list[""] = ""; std::string output = Encode(header_list); @@ -78,6 +93,8 @@ TEST_F(QpackEncoderTest, EmptyNameAndValue) { } TEST_F(QpackEncoderTest, Simple) { + EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered()) + .WillRepeatedly(Return(0)); spdy::Http2HeaderBlock header_list; header_list["foo"] = "bar"; std::string output = Encode(header_list); @@ -86,6 +103,8 @@ TEST_F(QpackEncoderTest, Simple) { } TEST_F(QpackEncoderTest, Multiple) { + EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered()) + .WillRepeatedly(Return(0)); spdy::Http2HeaderBlock header_list; header_list["foo"] = "bar"; // 'Z' would be Huffman encoded to 8 bits, so no Huffman encoding is used. @@ -108,6 +127,8 @@ TEST_F(QpackEncoderTest, Multiple) { } TEST_F(QpackEncoderTest, StaticTable) { + EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered()) + .WillRepeatedly(Return(0)); { spdy::Http2HeaderBlock header_list; header_list[":method"] = "GET"; @@ -149,6 +170,8 @@ TEST_F(QpackEncoderTest, DecoderStreamError) { } TEST_F(QpackEncoderTest, SplitAlongNullCharacter) { + EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered()) + .WillRepeatedly(Return(0)); spdy::Http2HeaderBlock header_list; header_list["foo"] = absl::string_view("bar\0bar\0baz", 11); std::string output = Encode(header_list); @@ -217,6 +240,8 @@ TEST_F(QpackEncoderTest, InvalidHeaderAcknowledgement) { } TEST_F(QpackEncoderTest, DynamicTable) { + EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered()) + .WillRepeatedly(Return(0)); encoder_.SetMaximumBlockedStreams(1); encoder_.SetMaximumDynamicTableCapacity(4096); encoder_.SetDynamicTableCapacity(4096); @@ -252,6 +277,8 @@ TEST_F(QpackEncoderTest, DynamicTable) { // There is no room in the dynamic table after inserting the first entry. TEST_F(QpackEncoderTest, SmallDynamicTable) { + EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered()) + .WillRepeatedly(Return(0)); encoder_.SetMaximumBlockedStreams(1); encoder_.SetMaximumDynamicTableCapacity(QpackEntry::Size("foo", "bar")); encoder_.SetDynamicTableCapacity(QpackEntry::Size("foo", "bar")); @@ -288,6 +315,8 @@ TEST_F(QpackEncoderTest, SmallDynamicTable) { } TEST_F(QpackEncoderTest, BlockedStream) { + EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered()) + .WillRepeatedly(Return(0)); encoder_.SetMaximumBlockedStreams(1); encoder_.SetMaximumDynamicTableCapacity(4096); encoder_.SetDynamicTableCapacity(4096); @@ -395,6 +424,8 @@ TEST_F(QpackEncoderTest, BlockedStream) { } TEST_F(QpackEncoderTest, Draining) { + EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered()) + .WillRepeatedly(Return(0)); spdy::Http2HeaderBlock header_list1; header_list1["one"] = "foo"; header_list1["two"] = "foo"; @@ -467,6 +498,135 @@ TEST_F(QpackEncoderTest, DynamicTableCapacityLessThanMaximum) { EXPECT_EQ(30u, header_table->dynamic_table_capacity()); } +TEST_F(QpackEncoderTest, EncoderStreamWritesDisallowedThenAllowed) { + if (!GetQuicReloadableFlag(quic_limit_encoder_stream_buffering)) { + return; + } + + EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered()) + .WillRepeatedly(Return(kTooManyBytesBuffered)); + encoder_.SetMaximumBlockedStreams(1); + encoder_.SetMaximumDynamicTableCapacity(4096); + encoder_.SetDynamicTableCapacity(4096); + + spdy::Http2HeaderBlock header_list1; + header_list1["foo"] = "bar"; + header_list1.AppendValueOrAddHeader("foo", "baz"); + header_list1["cookie"] = "baz"; // name matches static entry + + // Encoder is not allowed to write on the encoder stream. + // No Set Dynamic Table Capacity or Insert instructions are sent. + // Headers are encoded as string literals. + EXPECT_EQ(absl::HexStringToBytes("0000" // prefix + "2a94e7" // literal name "foo" + "03626172" // with literal value "bar" + "2a94e7" // literal name "foo" + "0362617a" // with literal value "baz" + "55" // name of static entry 5 + "0362617a"), // with literal value "baz" + Encode(header_list1)); + + EXPECT_EQ(0u, encoder_stream_sent_byte_count_); + + // If number of bytes buffered by encoder stream goes under the threshold, + // then QpackEncoder will resume emitting encoder stream instructions. + ::testing::Mock::VerifyAndClearExpectations(&encoder_stream_sender_delegate_); + EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered()) + .WillRepeatedly(Return(0)); + + spdy::Http2HeaderBlock header_list2; + header_list2["foo"] = "bar"; + header_list2.AppendValueOrAddHeader("foo", + "baz"); // name matches dynamic entry + header_list2["cookie"] = "baz"; // name matches static entry + + // Set Dynamic Table Capacity instruction. + std::string set_dyanamic_table_capacity = absl::HexStringToBytes("3fe11f"); + // Insert three entries into the dynamic table. + std::string insert_entries = absl::HexStringToBytes( + "62" // insert without name reference + "94e7" // Huffman-encoded name "foo" + "03626172" // value "bar" + "80" // insert with name reference, dynamic index 0 + "0362617a" // value "baz" + "c5" // insert with name reference, static index 5 + "0362617a"); // value "baz" + EXPECT_CALL(encoder_stream_sender_delegate_, + WriteStreamData(Eq( + absl::StrCat(set_dyanamic_table_capacity, insert_entries)))); + + EXPECT_EQ(absl::HexStringToBytes( + "0400" // prefix + "828180"), // dynamic entries with relative index 0, 1, and 2 + Encode(header_list2)); + + EXPECT_EQ(insert_entries.size(), encoder_stream_sent_byte_count_); +} + +TEST_F(QpackEncoderTest, EncoderStreamWritesAllowedThenDisallowed) { + if (!GetQuicReloadableFlag(quic_limit_encoder_stream_buffering)) { + return; + } + + EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered()) + .WillRepeatedly(Return(0)); + encoder_.SetMaximumBlockedStreams(1); + encoder_.SetMaximumDynamicTableCapacity(4096); + encoder_.SetDynamicTableCapacity(4096); + + spdy::Http2HeaderBlock header_list1; + header_list1["foo"] = "bar"; + header_list1.AppendValueOrAddHeader("foo", + "baz"); // name matches dynamic entry + header_list1["cookie"] = "baz"; // name matches static entry + + // Set Dynamic Table Capacity instruction. + std::string set_dyanamic_table_capacity = absl::HexStringToBytes("3fe11f"); + // Insert three entries into the dynamic table. + std::string insert_entries = absl::HexStringToBytes( + "62" // insert without name reference + "94e7" // Huffman-encoded name "foo" + "03626172" // value "bar" + "80" // insert with name reference, dynamic index 0 + "0362617a" // value "baz" + "c5" // insert with name reference, static index 5 + "0362617a"); // value "baz" + EXPECT_CALL(encoder_stream_sender_delegate_, + WriteStreamData(Eq( + absl::StrCat(set_dyanamic_table_capacity, insert_entries)))); + + EXPECT_EQ(absl::HexStringToBytes( + "0400" // prefix + "828180"), // dynamic entries with relative index 0, 1, and 2 + Encode(header_list1)); + + EXPECT_EQ(insert_entries.size(), encoder_stream_sent_byte_count_); + + // If number of bytes buffered by encoder stream goes over the threshold, + // then QpackEncoder will stop emitting encoder stream instructions. + ::testing::Mock::VerifyAndClearExpectations(&encoder_stream_sender_delegate_); + EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered()) + .WillRepeatedly(Return(kTooManyBytesBuffered)); + + spdy::Http2HeaderBlock header_list2; + header_list2["foo"] = "bar"; // matches previously inserted dynamic entry + header_list2["bar"] = "baz"; + header_list2["cookie"] = "baz"; // name matches static entry + + // Encoder is not allowed to write on the encoder stream. + // No Set Dynamic Table Capacity or Insert instructions are sent. + // Headers are encoded as string literals. + EXPECT_EQ( + absl::HexStringToBytes("0400" // prefix + "82" // dynamic entry with relative index 0 + "23626172" // literal name "bar" + "0362617a" // with literal value "baz" + "80"), // dynamic entry with relative index 2 + Encode(header_list2)); + + EXPECT_EQ(0u, encoder_stream_sent_byte_count_); +} + } // namespace } // namespace test } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_offline_decoder_bin.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_offline_decoder_bin.cc index 8cd1108a458..20f7a62731b 100644 --- a/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_offline_decoder_bin.cc +++ b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_offline_decoder_bin.cc @@ -9,16 +9,17 @@ #include "quic/platform/api/quic_flags.h" #include "quic/platform/api/quic_logging.h" #include "quic/test_tools/qpack/qpack_offline_decoder.h" +#include "common/platform/api/quiche_command_line_flags.h" int main(int argc, char* argv[]) { const char* usage = "Usage: qpack_offline_decoder input_filename expected_headers_filename " "...."; std::vector<std::string> args = - quic::QuicParseCommandLineFlags(usage, argc, argv); + quiche::QuicheParseCommandLineFlags(usage, argc, argv); if (args.size() < 2 || args.size() % 2 != 0) { - quic::QuicPrintCommandLineFlagHelp(usage); + quiche::QuichePrintCommandLineFlagHelp(usage); return 1; } diff --git a/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_send_stream.cc b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_send_stream.cc index 626d0639bf5..3e921d860f6 100644 --- a/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_send_stream.cc +++ b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_send_stream.cc @@ -34,6 +34,10 @@ void QpackSendStream::WriteStreamData(absl::string_view data) { WriteOrBufferData(data, false, nullptr); } +uint64_t QpackSendStream::NumBytesBuffered() const { + return QuicStream::BufferedDataBytes(); +} + void QpackSendStream::MaybeSendStreamType() { if (!stream_type_sent_) { char type[sizeof(http3_stream_type_)]; diff --git a/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_send_stream.h b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_send_stream.h index 086ad638d46..72dc677ecc7 100644 --- a/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_send_stream.h +++ b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_send_stream.h @@ -43,6 +43,9 @@ class QUIC_EXPORT_PRIVATE QpackSendStream : public QuicStream, // before the first instruction so that the peer can open an qpack stream. void WriteStreamData(absl::string_view data) override; + // Return the number of bytes buffered due to underlying stream being blocked. + uint64_t NumBytesBuffered() const override; + // TODO(b/112770235): Remove this method once QuicStreamIdManager supports // creating HTTP/3 unidirectional streams dynamically. void MaybeSendStreamType(); diff --git a/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_stream_sender_delegate.h b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_stream_sender_delegate.h index 4bd1a3ae72d..04f533d3084 100644 --- a/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_stream_sender_delegate.h +++ b/chromium/net/third_party/quiche/src/quic/core/qpack/qpack_stream_sender_delegate.h @@ -17,6 +17,9 @@ class QUIC_EXPORT_PRIVATE QpackStreamSenderDelegate { // Write data on the unidirectional stream. virtual void WriteStreamData(absl::string_view data) = 0; + + // Return the number of bytes buffered due to underlying stream being blocked. + virtual uint64_t NumBytesBuffered() const = 0; }; } // namespace quic 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 3206b373dd7..9607054bd38 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 @@ -90,8 +90,7 @@ class QUIC_NO_EXPORT QuicBufferedPacketStore { BufferedPacketList early_arrived_packets) = 0; }; - QuicBufferedPacketStore(VisitorInterface* vistor, - const QuicClock* clock, + QuicBufferedPacketStore(VisitorInterface* visitor, const QuicClock* clock, QuicAlarmFactory* alarm_factory); QuicBufferedPacketStore(const QuicBufferedPacketStore&) = delete; diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_config.cc b/chromium/net/third_party/quiche/src/quic/core/quic_config.cc index 796c641051b..8313ef76fbd 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_config.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_config.cc @@ -456,8 +456,6 @@ QuicConfig::QuicConfig() initial_stream_flow_control_window_bytes_(kSFCW, PRESENCE_OPTIONAL), initial_session_flow_control_window_bytes_(kCFCW, PRESENCE_OPTIONAL), connection_migration_disabled_(kNCMR, PRESENCE_OPTIONAL), - key_update_supported_remotely_(false), - key_update_supported_locally_(false), alternate_server_address_ipv6_(kASAD, PRESENCE_OPTIONAL), alternate_server_address_ipv4_(kASAD, PRESENCE_OPTIONAL), stateless_reset_token_(kSRST, PRESENCE_OPTIONAL), @@ -863,22 +861,6 @@ bool QuicConfig::DisableConnectionMigration() const { return connection_migration_disabled_.HasReceivedValue(); } -void QuicConfig::SetKeyUpdateSupportedLocally() { - key_update_supported_locally_ = true; -} - -bool QuicConfig::KeyUpdateSupportedForConnection() const { - return KeyUpdateSupportedRemotely() && KeyUpdateSupportedLocally(); -} - -bool QuicConfig::KeyUpdateSupportedLocally() const { - return key_update_supported_locally_; -} - -bool QuicConfig::KeyUpdateSupportedRemotely() const { - return key_update_supported_remotely_; -} - void QuicConfig::SetIPv6AlternateServerAddressToSend( const QuicSocketAddress& alternate_server_address_ipv6) { if (!alternate_server_address_ipv6.host().IsIPv6()) { @@ -1297,10 +1279,6 @@ bool QuicConfig::FillTransportParameters(TransportParameters* params) const { params->google_connection_options = connection_options_.GetSendValues(); } - if (!KeyUpdateSupportedLocally()) { - params->key_update_not_yet_supported = true; - } - params->custom_parameters = custom_transport_parameters_to_send_; return true; @@ -1410,9 +1388,6 @@ QuicErrorCode QuicConfig::ProcessTransportParameters( if (params.disable_active_migration) { connection_migration_disabled_.SetReceivedValue(1u); } - if (!is_resumption && !params.key_update_not_yet_supported) { - key_update_supported_remotely_ = true; - } active_connection_id_limit_.SetReceivedValue( params.active_connection_id_limit.value()); diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_config.h b/chromium/net/third_party/quiche/src/quic/core/quic_config.h index d122be7ff29..cb8b4ecdf1e 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_config.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_config.h @@ -67,7 +67,7 @@ class QUIC_EXPORT_PRIVATE QuicConfigValue { // Stores uint32_t from CHLO or SHLO messages that are not negotiated. class QUIC_EXPORT_PRIVATE QuicFixedUint32 : public QuicConfigValue { public: - QuicFixedUint32(QuicTag name, QuicConfigPresence presence); + QuicFixedUint32(QuicTag tag, QuicConfigPresence presence); ~QuicFixedUint32() override; bool HasSendValue() const; @@ -384,12 +384,6 @@ class QUIC_EXPORT_PRIVATE QuicConfig { void SetDisableConnectionMigration(); bool DisableConnectionMigration() const; - // Key update support. - void SetKeyUpdateSupportedLocally(); - bool KeyUpdateSupportedForConnection() const; - bool KeyUpdateSupportedLocally() const; - bool KeyUpdateSupportedRemotely() const; - // IPv6 alternate server address. void SetIPv6AlternateServerAddressToSend( const QuicSocketAddress& alternate_server_address_ipv6); @@ -593,13 +587,6 @@ class QUIC_EXPORT_PRIVATE QuicConfig { // Uses the disable_active_migration transport parameter in IETF QUIC. QuicFixedUint32 connection_migration_disabled_; - // Whether key update is supported by the peer. Uses key_update_not_yet - // supported transport parameter in IETF QUIC. - bool key_update_supported_remotely_; - - // Whether key update is supported locally. - bool key_update_supported_locally_; - // Alternate server addresses the client could connect to. // Uses the preferred_address transport parameter in IETF QUIC. // Note that when QUIC_CRYPTO is in use, only one of the addresses is sent. diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_config_test.cc b/chromium/net/third_party/quiche/src/quic/core/quic_config_test.cc index 5124a975bf8..e53d429030e 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_config_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_config_test.cc @@ -56,9 +56,6 @@ TEST_P(QuicConfigTest, SetDefaults) { EXPECT_FALSE(config_.HasReceivedInitialMaxStreamDataBytesUnidirectional()); EXPECT_EQ(kMaxIncomingPacketSize, config_.GetMaxPacketSizeToSend()); EXPECT_FALSE(config_.HasReceivedMaxPacketSize()); - EXPECT_FALSE(config_.KeyUpdateSupportedForConnection()); - EXPECT_FALSE(config_.KeyUpdateSupportedLocally()); - EXPECT_FALSE(config_.KeyUpdateSupportedRemotely()); } TEST_P(QuicConfigTest, AutoSetIetfFlowControl) { @@ -519,7 +516,6 @@ TEST_P(QuicConfigTest, FillTransportParams) { EXPECT_EQ( static_cast<uint64_t>(kDefaultMinAckDelayTimeMs) * kNumMicrosPerMilli, params.min_ack_delay_us.value()); - EXPECT_TRUE(params.key_update_not_yet_supported); EXPECT_EQ(params.preferred_address->ipv4_socket_address, kTestServerAddress); EXPECT_EQ(*reinterpret_cast<StatelessResetToken*>( @@ -685,83 +681,6 @@ TEST_P(QuicConfigTest, DisableMigrationTransportParameter) { EXPECT_TRUE(config_.DisableConnectionMigration()); } -TEST_P(QuicConfigTest, KeyUpdateNotYetSupportedTransportParameterNorLocally) { - if (!version_.UsesTls()) { - // TransportParameters are only used for QUIC+TLS. - return; - } - EXPECT_FALSE(config_.KeyUpdateSupportedForConnection()); - EXPECT_FALSE(config_.KeyUpdateSupportedLocally()); - EXPECT_FALSE(config_.KeyUpdateSupportedRemotely()); - TransportParameters params; - params.key_update_not_yet_supported = true; - std::string error_details; - EXPECT_THAT(config_.ProcessTransportParameters( - params, /* is_resumption = */ false, &error_details), - IsQuicNoError()); - EXPECT_FALSE(config_.KeyUpdateSupportedForConnection()); - EXPECT_FALSE(config_.KeyUpdateSupportedLocally()); - EXPECT_FALSE(config_.KeyUpdateSupportedRemotely()); -} - -TEST_P(QuicConfigTest, KeyUpdateNotYetSupportedTransportParameter) { - if (!version_.UsesTls()) { - // TransportParameters are only used for QUIC+TLS. - return; - } - config_.SetKeyUpdateSupportedLocally(); - EXPECT_FALSE(config_.KeyUpdateSupportedForConnection()); - EXPECT_TRUE(config_.KeyUpdateSupportedLocally()); - - TransportParameters params; - params.key_update_not_yet_supported = true; - std::string error_details; - EXPECT_THAT(config_.ProcessTransportParameters( - params, /* is_resumption = */ false, &error_details), - IsQuicNoError()); - EXPECT_FALSE(config_.KeyUpdateSupportedForConnection()); - EXPECT_TRUE(config_.KeyUpdateSupportedLocally()); -} - -TEST_P(QuicConfigTest, KeyUpdateSupportedRemotelyButNotLocally) { - if (!version_.UsesTls()) { - // TransportParameters are only used for QUIC+TLS. - return; - } - EXPECT_FALSE(config_.KeyUpdateSupportedLocally()); - EXPECT_FALSE(config_.KeyUpdateSupportedForConnection()); - - TransportParameters params; - params.key_update_not_yet_supported = false; - std::string error_details; - EXPECT_THAT(config_.ProcessTransportParameters( - params, /* is_resumption = */ false, &error_details), - IsQuicNoError()); - EXPECT_FALSE(config_.KeyUpdateSupportedForConnection()); - EXPECT_FALSE(config_.KeyUpdateSupportedLocally()); - EXPECT_TRUE(config_.KeyUpdateSupportedRemotely()); -} - -TEST_P(QuicConfigTest, KeyUpdateSupported) { - if (!version_.UsesTls()) { - // TransportParameters are only used for QUIC+TLS. - return; - } - config_.SetKeyUpdateSupportedLocally(); - EXPECT_TRUE(config_.KeyUpdateSupportedLocally()); - EXPECT_FALSE(config_.KeyUpdateSupportedForConnection()); - - TransportParameters params; - params.key_update_not_yet_supported = false; - std::string error_details; - EXPECT_THAT(config_.ProcessTransportParameters( - params, /* is_resumption = */ false, &error_details), - IsQuicNoError()); - EXPECT_TRUE(config_.KeyUpdateSupportedForConnection()); - EXPECT_TRUE(config_.KeyUpdateSupportedLocally()); - EXPECT_TRUE(config_.KeyUpdateSupportedRemotely()); -} - TEST_P(QuicConfigTest, SendPreferredIPv4Address) { if (!version_.UsesTls()) { // TransportParameters are only used for QUIC+TLS. 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 e31408a3116..2fe7af38717 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 @@ -505,8 +505,7 @@ void QuicConnection::SetFromConfig(const QuicConfig& config) { if (!ValidateConfigConnectionIds(config)) { return; } - support_key_update_for_connection_ = - config.KeyUpdateSupportedForConnection(); + support_key_update_for_connection_ = version().UsesTls(); framer_.SetKeyUpdateSupportForConnection( support_key_update_for_connection_); } else { @@ -641,7 +640,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_ = validate_client_addresses_ && - GetQuicReloadableFlag(quic_drop_unsent_path_response) && GetQuicReloadableFlag(quic_connection_migration_use_new_cid_v2); if (config.HasReceivedMaxPacketSize()) { peer_max_packet_size_ = config.ReceivedMaxPacketSize(); @@ -1742,19 +1740,10 @@ 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_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); - } - if (!success) { + if (!SendPathResponse(frame.data_buffer, + last_received_packet_info_.source_address, + current_effective_peer_address)) { QUIC_CODE_COUNT(quic_failed_to_send_path_response); - if (!GetQuicReloadableFlag(quic_drop_unsent_path_response)) { - // Queue the payloads to re-try later. - pending_path_challenge_payloads_.push_back( - {frame.data_buffer, last_received_packet_info_.source_address}); - } } // TODO(b/150095588): change the stats to // num_valid_path_challenge_received. @@ -2870,23 +2859,6 @@ void QuicConnection::OnCanWrite() { } } - // TODO(danzh) PATH_RESPONSE is of more interest to the peer than ACK, - // evaluate if it's worth to send them before sending ACKs. - while (!pending_path_challenge_payloads_.empty()) { - QUIC_RELOADABLE_FLAG_COUNT_N(quic_send_path_response2, 4, 5); - const PendingPathChallenge& pending_path_challenge = - pending_path_challenge_payloads_.front(); - // Note connection_migration_use_cid_ will depends on - // quic_drop_unsent_path_response flag eventually, and hence the empty - // effective_peer_address here will not be used. - if (!SendPathResponse(pending_path_challenge.received_path_challenge, - pending_path_challenge.peer_address, - /*effective_peer_address=*/QuicSocketAddress())) { - break; - } - pending_path_challenge_payloads_.pop_front(); - } - // Sending queued packets may have caused the socket to become write blocked, // or the congestion manager to prohibit sending. if (!CanWrite(HAS_RETRANSMITTABLE_DATA)) { @@ -3273,11 +3245,9 @@ bool QuicConnection::CanWrite(HasRetransmittableData retransmittable) { return false; } - if (GetQuicReloadableFlag(quic_suppress_write_mid_packet_processing) && - version().CanSendCoalescedPackets() && + if (version().CanSendCoalescedPackets() && framer_.HasEncrypterOfEncryptionLevel(ENCRYPTION_INITIAL) && framer_.is_processing_packet()) { - QUIC_RELOADABLE_FLAG_COUNT(quic_suppress_write_mid_packet_processing); // While we still have initial keys, suppress sending in mid of packet // processing. // TODO(fayang): always suppress sending while in the mid of packet @@ -3726,15 +3696,8 @@ bool QuicConnection::WritePacket(SerializedPacket* packet) { return true; } } - 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()) { + if (ShouldSetRetransmissionAlarmOnPacketSent(in_flight, + packet->encryption_level)) { SetRetransmissionAlarm(); } SetPingAlarm(); @@ -6765,6 +6728,21 @@ void QuicConnection::RetirePeerIssuedConnectionIdsNoLongerOnPath() { } } +void QuicConnection::RetirePeerIssuedConnectionIdsOnPathValidationFailure() { + // 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. + if (GetQuicReloadableFlag( + quic_retire_cid_on_reverse_path_validation_failure) || + perspective_ == Perspective::IS_CLIENT) { + QUIC_RELOADABLE_FLAG_COUNT( + quic_retire_cid_on_reverse_path_validation_failure); + RetirePeerIssuedConnectionIdsNoLongerOnPath(); + } +} + bool QuicConnection::MigratePath(const QuicSocketAddress& self_address, const QuicSocketAddress& peer_address, QuicPacketWriter* writer, @@ -6816,12 +6794,7 @@ 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(); + RetirePeerIssuedConnectionIdsOnPathValidationFailure(); } QuicConnectionId QuicConnection::GetOneActiveServerConnectionId() const { @@ -7075,6 +7048,7 @@ void QuicConnection::ReversePathValidationResultDelegate:: QUIC_CODE_COUNT_N(quic_kick_off_client_address_validation, 6, 6); connection_->alternative_path_.Clear(); } + connection_->RetirePeerIssuedConnectionIdsOnPathValidationFailure(); } QuicConnection::ScopedRetransmissionTimeoutIndicator:: 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 3b310c8f838..954eb6a6d0f 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 @@ -1564,6 +1564,10 @@ class QUIC_EXPORT_PRIVATE QuicConnection // any path. void RetirePeerIssuedConnectionIdsNoLongerOnPath(); + // When path validation fails, proactively retire peer issued connection IDs + // no longer used on any path. + void RetirePeerIssuedConnectionIdsOnPathValidationFailure(); + // Writes the given packet to socket, encrypted with packet's // encryption_level. Returns true on successful write, and false if the writer // was blocked and the write needs to be tried again. Notifies the @@ -2165,13 +2169,6 @@ class QUIC_EXPORT_PRIVATE QuicConnection quiche::QuicheCircularDeque<QuicPathFrameBuffer> received_path_challenge_payloads_; - // Buffer outstanding PATH_CHALLENGEs if socket write is blocked, future - // OnCanWrite will attempt to respond with PATH_RESPONSEs using the retained - // payload and peer addresses. - // TODO(fayang): remove this when deprecating quic_drop_unsent_path_response. - quiche::QuicheCircularDeque<PendingPathChallenge> - pending_path_challenge_payloads_; - // 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 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 89fb70bacb1..fba066caf68 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 @@ -184,19 +184,12 @@ class TestConnection : public QuicConnection { TestConnection(QuicConnectionId connection_id, QuicSocketAddress initial_self_address, QuicSocketAddress initial_peer_address, - TestConnectionHelper* helper, - TestAlarmFactory* alarm_factory, - TestPacketWriter* writer, - Perspective perspective, + TestConnectionHelper* helper, TestAlarmFactory* alarm_factory, + TestPacketWriter* writer, Perspective perspective, ParsedQuicVersion version) - : QuicConnection(connection_id, - initial_self_address, - initial_peer_address, - helper, - alarm_factory, - writer, - /* owns_writer= */ false, - perspective, + : QuicConnection(connection_id, initial_self_address, + initial_peer_address, helper, alarm_factory, writer, + /* owns_writer= */ false, perspective, SupportedVersions(version)), notifier_(nullptr) { writer->set_perspective(perspective); @@ -215,11 +208,9 @@ class TestConnection : public QuicConnection { QuicConnectionPeer::SetLossAlgorithm(this, loss_algorithm); } - void SendPacket(EncryptionLevel /*level*/, - uint64_t packet_number, + void SendPacket(EncryptionLevel /*level*/, uint64_t packet_number, std::unique_ptr<QuicPacket> packet, - HasRetransmittableData retransmittable, - bool has_ack, + HasRetransmittableData retransmittable, bool has_ack, bool has_pending_frames) { ScopedPacketFlusher flusher(this); char buffer[kMaxOutgoingPacketSize]; @@ -239,8 +230,7 @@ class TestConnection : public QuicConnection { } QuicConsumedData SaveAndSendStreamData(QuicStreamId id, - const struct iovec* iov, - int iov_count, + const struct iovec* iov, int iov_count, size_t total_length, QuicStreamOffset offset, StreamSendingState state) { @@ -555,8 +545,7 @@ enum class AckResponse { kDefer, kImmediate }; // Run tests with combinations of {ParsedQuicVersion, AckResponse}. struct TestParams { - TestParams(ParsedQuicVersion version, - AckResponse ack_response, + TestParams(ParsedQuicVersion version, AckResponse ack_response, bool no_stop_waiting) : version(version), ack_response(ack_response), @@ -609,30 +598,20 @@ class QuicConnectionTest : public QuicTestWithParam<TestParams> { protected: QuicConnectionTest() : connection_id_(TestConnectionId()), - framer_(SupportedVersions(version()), - QuicTime::Zero(), - Perspective::IS_CLIENT, - connection_id_.length()), + framer_(SupportedVersions(version()), QuicTime::Zero(), + Perspective::IS_CLIENT, connection_id_.length()), send_algorithm_(new StrictMock<MockSendAlgorithm>), loss_algorithm_(new MockLossAlgorithm()), helper_(new TestConnectionHelper(&clock_, &random_generator_)), alarm_factory_(new TestAlarmFactory()), - peer_framer_(SupportedVersions(version()), - QuicTime::Zero(), - Perspective::IS_SERVER, - connection_id_.length()), - peer_creator_(connection_id_, - &peer_framer_, + peer_framer_(SupportedVersions(version()), QuicTime::Zero(), + Perspective::IS_SERVER, connection_id_.length()), + peer_creator_(connection_id_, &peer_framer_, /*delegate=*/nullptr), writer_( new TestPacketWriter(version(), &clock_, Perspective::IS_CLIENT)), - connection_(connection_id_, - kSelfAddress, - kPeerAddress, - helper_.get(), - alarm_factory_.get(), - writer_.get(), - Perspective::IS_CLIENT, + connection_(connection_id_, kSelfAddress, kPeerAddress, helper_.get(), + alarm_factory_.get(), writer_.get(), Perspective::IS_CLIENT, version()), creator_(QuicConnectionPeer::GetPacketCreator(&connection_)), manager_(QuicConnectionPeer::GetSentPacketManager(&connection_)), @@ -872,16 +851,14 @@ class QuicConnectionTest : public QuicTestWithParam<TestParams> { QuicReceivedPacket(encrypted_buffer, encrypted_length, clock_.Now())); } - size_t ProcessFramePacketAtLevel(uint64_t number, - QuicFrame frame, + size_t ProcessFramePacketAtLevel(uint64_t number, QuicFrame frame, EncryptionLevel level) { QuicFrames frames; frames.push_back(frame); return ProcessFramesPacketAtLevel(number, frames, level); } - size_t ProcessFramesPacketAtLevel(uint64_t number, - const QuicFrames& frames, + size_t ProcessFramesPacketAtLevel(uint64_t number, const QuicFrames& frames, EncryptionLevel level) { QuicPacketHeader header = ConstructPacketHeader(number, level); // Set the correct encryption level and encrypter on peer_creator and @@ -1025,8 +1002,7 @@ class QuicConnectionTest : public QuicTestWithParam<TestParams> { return encrypted_length; } - size_t ProcessDataPacketAtLevel(uint64_t number, - bool has_stop_waiting, + size_t ProcessDataPacketAtLevel(uint64_t number, bool has_stop_waiting, EncryptionLevel level) { std::unique_ptr<QuicPacket> packet( ConstructDataPacket(number, has_stop_waiting, level)); @@ -1055,8 +1031,7 @@ class QuicConnectionTest : public QuicTestWithParam<TestParams> { QuicReceivedPacket(buffer, encrypted_length, QuicTime::Zero(), false)); } - QuicByteCount SendStreamDataToPeer(QuicStreamId id, - absl::string_view data, + QuicByteCount SendStreamDataToPeer(QuicStreamId id, absl::string_view data, QuicStreamOffset offset, StreamSendingState state, QuicPacketNumber* last_packet) { @@ -1084,8 +1059,7 @@ class QuicConnectionTest : public QuicTestWithParam<TestParams> { .Times(AnyNumber()); } - void SendRstStream(QuicStreamId id, - QuicRstStreamErrorCode error, + void SendRstStream(QuicStreamId id, QuicRstStreamErrorCode error, QuicStreamOffset bytes_written) { notifier_.WriteOrBufferRstStream(id, error, bytes_written); connection_.OnStreamReset(id, error); @@ -4543,179 +4517,6 @@ TEST_P(QuicConnectionTest, TLP) { EXPECT_EQ(QuicPacketNumber(1u), stop_waiting()->least_unacked); } -TEST_P(QuicConnectionTest, TailLossProbeDelayForStreamDataInTLPR) { - if (connection_.PtoEnabled() || GetQuicReloadableFlag(quic_deprecate_tlpr)) { - return; - } - - // Set TLPR from QuicConfig. - EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); - QuicConfig config; - QuicTagVector options; - options.push_back(kTLPR); - config.SetConnectionOptionsToSend(options); - connection_.SetFromConfig(config); - connection_.SetMaxTailLossProbes(1); - - SendStreamDataToPeer(3, "foo", 0, NO_FIN, nullptr); - EXPECT_EQ(QuicPacketNumber(1u), stop_waiting()->least_unacked); - - QuicTime retransmission_time = - connection_.GetRetransmissionAlarm()->deadline(); - EXPECT_NE(QuicTime::Zero(), retransmission_time); - QuicTime::Delta expected_tlp_delay = - 0.5 * manager_->GetRttStats()->SmoothedOrInitialRtt(); - EXPECT_EQ(expected_tlp_delay, retransmission_time - clock_.Now()); - - EXPECT_EQ(QuicPacketNumber(1u), writer_->header().packet_number); - // Simulate firing of the retransmission alarm and retransmit the packet. - EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, QuicPacketNumber(2), _, _)); - clock_.AdvanceTime(retransmission_time - clock_.Now()); - connection_.GetRetransmissionAlarm()->Fire(); - EXPECT_EQ(QuicPacketNumber(2u), writer_->header().packet_number); - - // We do not raise the high water mark yet. - EXPECT_EQ(QuicPacketNumber(1u), stop_waiting()->least_unacked); -} - -TEST_P(QuicConnectionTest, TailLossProbeDelayForNonStreamDataInTLPR) { - if (connection_.PtoEnabled() || GetQuicReloadableFlag(quic_deprecate_tlpr)) { - return; - } - - // Set TLPR from QuicConfig. - EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); - QuicConfig config; - QuicTagVector options; - options.push_back(kTLPR); - config.SetConnectionOptionsToSend(options); - QuicConfigPeer::SetNegotiated(&config, true); - if (connection_.version().UsesTls()) { - QuicConfigPeer::SetReceivedOriginalConnectionId( - &config, connection_.connection_id()); - QuicConfigPeer::SetReceivedInitialSourceConnectionId( - &config, connection_.connection_id()); - } - connection_.SetFromConfig(config); - connection_.SetMaxTailLossProbes(1); - - // Sets retransmittable on wire. - const QuicTime::Delta retransmittable_on_wire_timeout = - QuicTime::Delta::FromMilliseconds(50); - connection_.set_initial_retransmittable_on_wire_timeout( - retransmittable_on_wire_timeout); - - EXPECT_TRUE(connection_.connected()); - EXPECT_CALL(visitor_, ShouldKeepConnectionAlive()) - .WillRepeatedly(Return(true)); - EXPECT_FALSE(connection_.PathDegradingDetectionInProgress()); - EXPECT_FALSE(connection_.IsPathDegrading()); - EXPECT_FALSE(connection_.GetPingAlarm()->IsSet()); - - const char data[] = "data"; - size_t data_size = strlen(data); - QuicStreamOffset offset = 0; - - // Send a data packet. - connection_.SendStreamDataWithString(1, data, offset, NO_FIN); - offset += data_size; - - // Path degrading alarm should be set when there is a retransmittable packet - // on the wire. - EXPECT_TRUE(connection_.PathDegradingDetectionInProgress()); - - // Verify the path degrading delay. - // First TLP with stream data. - QuicTime::Delta srtt = manager_->GetRttStats()->SmoothedOrInitialRtt(); - QuicTime::Delta expected_delay = 0.5 * srtt; - // Add 1st RTO. - QuicTime::Delta retransmission_delay = - QuicTime::Delta::FromMilliseconds(kDefaultRetransmissionTimeMs); - expected_delay = expected_delay + retransmission_delay; - // Add 2nd RTO. - expected_delay = expected_delay + retransmission_delay * 2; - EXPECT_EQ(expected_delay, - QuicConnectionPeer::GetSentPacketManager(&connection_) - ->GetPathDegradingDelay()); - ASSERT_TRUE(connection_.sent_packet_manager().HasInFlightPackets()); - - // The ping alarm is set for the ping timeout, not the shorter - // retransmittable_on_wire_timeout. - EXPECT_TRUE(connection_.GetPingAlarm()->IsSet()); - EXPECT_EQ(connection_.ping_timeout(), - connection_.GetPingAlarm()->deadline() - clock_.ApproximateNow()); - - // Receive an ACK for the data packet. - clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); - EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); - EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _)); - QuicAckFrame frame = - InitAckFrame({{QuicPacketNumber(1), QuicPacketNumber(2)}}); - ProcessAckPacket(&frame); - - // Path degrading alarm should be cancelled as there is no more - // reretransmittable packets on the wire. - EXPECT_FALSE(connection_.PathDegradingDetectionInProgress()); - // The ping alarm should be set to the retransmittable_on_wire_timeout. - EXPECT_TRUE(connection_.GetPingAlarm()->IsSet()); - EXPECT_EQ(retransmittable_on_wire_timeout, - connection_.GetPingAlarm()->deadline() - clock_.ApproximateNow()); - - // Simulate firing of the retransmittable on wire and send a PING. - clock_.AdvanceTime(retransmittable_on_wire_timeout); - connection_.GetPingAlarm()->Fire(); - - // The retransmission alarm and the path degrading alarm should be set as - // there is a retransmittable packet (PING) on the wire, - EXPECT_TRUE(connection_.GetRetransmissionAlarm()->IsSet()); - EXPECT_TRUE(connection_.PathDegradingDetectionInProgress()); - - // Verify the retransmission delay. - QuicTime::Delta min_rto_timeout = - QuicTime::Delta::FromMilliseconds(kMinRetransmissionTimeMs); - srtt = manager_->GetRttStats()->SmoothedOrInitialRtt(); - - // Arm RTO mode since there is only PING in flight. - expected_delay = manager_->GetPtoDelay(); - EXPECT_EQ(expected_delay, - connection_.GetRetransmissionAlarm()->deadline() - clock_.Now()); - - // Verify the path degrading delay = TLP delay + 1st RTO + 2nd RTO. - // Add 1st RTO. - expected_delay = std::max(2 * srtt, 1.5 * srtt + 0.5 * min_rto_timeout); - retransmission_delay = - std::max(manager_->GetRttStats()->smoothed_rtt() + - 4 * manager_->GetRttStats()->mean_deviation(), - min_rto_timeout); - expected_delay = expected_delay + retransmission_delay; - // Add 2nd RTO. - expected_delay = expected_delay + retransmission_delay * 2; - EXPECT_EQ(expected_delay, - QuicConnectionPeer::GetSentPacketManager(&connection_) - ->GetPathDegradingDelay()); - - // The ping alarm is set for the ping timeout, not the shorter - // retransmittable_on_wire_timeout. - EXPECT_TRUE(connection_.GetPingAlarm()->IsSet()); - EXPECT_EQ(connection_.ping_timeout(), - connection_.GetPingAlarm()->deadline() - clock_.ApproximateNow()); - - // Advance a small period of time: 5ms. And receive a retransmitted ACK. - // This will update the retransmission alarm, verify the retransmission delay - // is correct. - clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); - QuicAckFrame ack = InitAckFrame({{QuicPacketNumber(1), QuicPacketNumber(2)}}); - ProcessAckPacket(&ack); - - // Verify the retransmission delay. - // First TLP without unacked stream data will no longer use TLPR. - // Arm RTO mode since there is only PING in flight. - expected_delay = manager_->GetPtoDelay(); - expected_delay = expected_delay - QuicTime::Delta::FromMilliseconds(5); - EXPECT_EQ(expected_delay, - connection_.GetRetransmissionAlarm()->deadline() - clock_.Now()); -} - TEST_P(QuicConnectionTest, RTO) { if (connection_.PtoEnabled()) { return; @@ -10672,7 +10473,12 @@ void QuicConnectionTest::TestClientRetryHandling( return; } - // These values come from draft-ietf-quic-tls Appendix A.4. + // These values come from draft-ietf-quic-v2 Appendix A.4. + uint8_t retry_packet_rfcv2[] = { + 0xcf, 0x70, 0x9a, 0x50, 0xc4, 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, + 0x42, 0x62, 0xb5, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x1d, 0xc7, 0x11, 0x30, + 0xcd, 0x1e, 0xd3, 0x9d, 0x6e, 0xfc, 0xee, 0x5c, 0x85, 0x80, 0x65, 0x01}; + // These values come from RFC9001 Appendix A.4. 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, @@ -10684,7 +10490,10 @@ void QuicConnectionTest::TestClientRetryHandling( uint8_t* retry_packet; size_t retry_packet_length; - if (version() == ParsedQuicVersion::RFCv1()) { + if (version() == ParsedQuicVersion::V2Draft01()) { + retry_packet = retry_packet_rfcv2; + retry_packet_length = ABSL_ARRAYSIZE(retry_packet_rfcv2); + } else if (version() == ParsedQuicVersion::RFCv1()) { retry_packet = retry_packet_rfcv1; retry_packet_length = ABSL_ARRAYSIZE(retry_packet_rfcv1); } else if (version() == ParsedQuicVersion::Draft29()) { @@ -12719,30 +12528,6 @@ TEST_P(QuicConnectionTest, FailToWritePathResponse) { writer_->SetWriteBlocked(); ProcessFramesPacketWithAddresses(frames, kSelfAddress, kNewPeerAddress, ENCRYPTION_FORWARD_SECURE); - - if (GetQuicReloadableFlag(quic_drop_unsent_path_response)) { - EXPECT_EQ(0u, QuicConnectionPeer::NumPendingPathChallengesToResponse( - &connection_)); - return; - } - ASSERT_EQ( - 1u, QuicConnectionPeer::NumPendingPathChallengesToResponse(&connection_)); - - EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(AtLeast(1)); - writer_->SetWritable(); - connection_.OnCanWrite(); - EXPECT_EQ(1u, writer_->path_response_frames().size()); - // The final check is to ensure that the random data in the response - // matches the random data from the challenge. - EXPECT_EQ(0, memcmp(path_frame_buffer.data(), - &(writer_->path_response_frames().front().data_buffer), - sizeof(path_frame_buffer))); - EXPECT_EQ(1u, writer_->padding_frames().size()); - // PATH_RESPONSE should be sent in another packet to a different peer - // address. - EXPECT_EQ(kNewPeerAddress, writer_->last_write_peer_address()); - EXPECT_EQ( - 0u, QuicConnectionPeer::NumPendingPathChallengesToResponse(&connection_)); } // Regression test for b/168101557. @@ -12949,13 +12734,11 @@ TEST_P(QuicConnectionTest, InitiateKeyUpdate) { } TransportParameters params; - params.key_update_not_yet_supported = false; QuicConfig config; std::string error_details; EXPECT_THAT(config.ProcessTransportParameters( params, /* is_resumption = */ false, &error_details), IsQuicNoError()); - config.SetKeyUpdateSupportedLocally(); QuicConfigPeer::SetNegotiated(&config, true); if (connection_.version().UsesTls()) { QuicConfigPeer::SetReceivedOriginalConnectionId( @@ -13111,12 +12894,10 @@ TEST_P(QuicConnectionTest, InitiateKeyUpdateApproachingConfidentialityLimit) { std::string error_details; TransportParameters params; // Key update is enabled. - params.key_update_not_yet_supported = false; QuicConfig config; EXPECT_THAT(config.ProcessTransportParameters( params, /* is_resumption = */ false, &error_details), IsQuicNoError()); - config.SetKeyUpdateSupportedLocally(); QuicConfigPeer::SetNegotiated(&config, true); if (connection_.version().UsesTls()) { QuicConfigPeer::SetReceivedOriginalConnectionId( @@ -13209,12 +12990,10 @@ TEST_P(QuicConnectionTest, std::string error_details; TransportParameters params; // Key update is enabled. - params.key_update_not_yet_supported = false; QuicConfig config; EXPECT_THAT(config.ProcessTransportParameters( params, /* is_resumption = */ false, &error_details), IsQuicNoError()); - config.SetKeyUpdateSupportedLocally(); QuicConfigPeer::SetNegotiated(&config, true); if (connection_.version().UsesTls()) { QuicConfigPeer::SetReceivedOriginalConnectionId( @@ -13250,137 +13029,6 @@ TEST_P(QuicConnectionTest, TestConnectionCloseQuicErrorCode(QUIC_AEAD_LIMIT_REACHED); } -TEST_P(QuicConnectionTest, - CloseConnectionOnConfidentialityLimitKeyUpdateNotSupportedByPeer) { - if (!connection_.version().UsesTls()) { - return; - } - - // Set key update confidentiality limit to 1 packet. - SetQuicFlag(FLAGS_quic_key_update_confidentiality_limit, 1U); - // Use confidentiality limit for connection close of 3 packets. - constexpr size_t kConfidentialityLimit = 3U; - - std::string error_details; - TransportParameters params; - // Key update not enabled for this connection as peer doesn't support it. - params.key_update_not_yet_supported = true; - QuicConfig config; - EXPECT_THAT(config.ProcessTransportParameters( - params, /* is_resumption = */ false, &error_details), - IsQuicNoError()); - // Key update is supported locally. - config.SetKeyUpdateSupportedLocally(); - QuicConfigPeer::SetNegotiated(&config, true); - if (connection_.version().UsesTls()) { - QuicConfigPeer::SetReceivedOriginalConnectionId( - &config, connection_.connection_id()); - QuicConfigPeer::SetReceivedInitialSourceConnectionId( - &config, connection_.connection_id()); - } - EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); - connection_.SetFromConfig(config); - - connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); - connection_.SetEncrypter( - ENCRYPTION_FORWARD_SECURE, - std::make_unique<NullEncrypterWithConfidentialityLimit>( - Perspective::IS_CLIENT, kConfidentialityLimit)); - EXPECT_CALL(visitor_, GetHandshakeState()) - .WillRepeatedly(Return(HANDSHAKE_CONFIRMED)); - connection_.OnHandshakeComplete(); - - QuicPacketNumber last_packet; - // Send 3 packets and receive acks for them. Since key update is not enabled - // the confidentiality limit should be reached, forcing the connection to be - // closed. - SendStreamDataToPeer(1, "foo", 0, NO_FIN, &last_packet); - EXPECT_TRUE(connection_.connected()); - // Receive ack for packet. - EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _)); - QuicAckFrame frame1 = InitAckFrame(1); - ProcessAckPacket(&frame1); - - SendStreamDataToPeer(2, "foo", 0, NO_FIN, &last_packet); - EXPECT_TRUE(connection_.connected()); - // Receive ack for packet. - EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _)); - QuicAckFrame frame2 = InitAckFrame(2); - ProcessAckPacket(&frame2); - - EXPECT_CALL(visitor_, OnConnectionClosed(_, _)); - SendStreamDataToPeer(3, "foo", 0, NO_FIN, &last_packet); - EXPECT_FALSE(connection_.connected()); - const QuicConnectionStats& stats = connection_.GetStats(); - EXPECT_EQ(0U, stats.key_update_count); - TestConnectionCloseQuicErrorCode(QUIC_AEAD_LIMIT_REACHED); -} - -TEST_P(QuicConnectionTest, - CloseConnectionOnConfidentialityLimitKeyUpdateNotEnabledLocally) { - if (!connection_.version().UsesTls()) { - return; - } - - // Set key update confidentiality limit to 1 packet. - SetQuicFlag(FLAGS_quic_key_update_confidentiality_limit, 1U); - // Use confidentiality limit for connection close of 3 packets. - constexpr size_t kConfidentialityLimit = 3U; - - std::string error_details; - TransportParameters params; - // Key update is supported by peer but not locally - // (config.SetKeyUpdateSupportedLocally is not called.) - params.key_update_not_yet_supported = false; - QuicConfig config; - EXPECT_THAT(config.ProcessTransportParameters( - params, /* is_resumption = */ false, &error_details), - IsQuicNoError()); - QuicConfigPeer::SetNegotiated(&config, true); - if (connection_.version().UsesTls()) { - QuicConfigPeer::SetReceivedOriginalConnectionId( - &config, connection_.connection_id()); - QuicConfigPeer::SetReceivedInitialSourceConnectionId( - &config, connection_.connection_id()); - } - EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); - connection_.SetFromConfig(config); - - connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); - connection_.SetEncrypter( - ENCRYPTION_FORWARD_SECURE, - std::make_unique<NullEncrypterWithConfidentialityLimit>( - Perspective::IS_CLIENT, kConfidentialityLimit)); - EXPECT_CALL(visitor_, GetHandshakeState()) - .WillRepeatedly(Return(HANDSHAKE_CONFIRMED)); - connection_.OnHandshakeComplete(); - - QuicPacketNumber last_packet; - // Send 3 packets and receive acks for them. Since key update is not enabled - // the confidentiality limit should be reached, forcing the connection to be - // closed. - SendStreamDataToPeer(1, "foo", 0, NO_FIN, &last_packet); - EXPECT_TRUE(connection_.connected()); - // Receive ack for packet. - EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _)); - QuicAckFrame frame1 = InitAckFrame(1); - ProcessAckPacket(&frame1); - - SendStreamDataToPeer(2, "foo", 0, NO_FIN, &last_packet); - EXPECT_TRUE(connection_.connected()); - // Receive ack for packet. - EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _)); - QuicAckFrame frame2 = InitAckFrame(2); - ProcessAckPacket(&frame2); - - EXPECT_CALL(visitor_, OnConnectionClosed(_, _)); - SendStreamDataToPeer(3, "foo", 0, NO_FIN, &last_packet); - EXPECT_FALSE(connection_.connected()); - const QuicConnectionStats& stats = connection_.GetStats(); - EXPECT_EQ(0U, stats.key_update_count); - TestConnectionCloseQuicErrorCode(QUIC_AEAD_LIMIT_REACHED); -} - TEST_P(QuicConnectionTest, CloseConnectionOnIntegrityLimitDuringHandshake) { if (!connection_.version().UsesTls()) { return; @@ -13536,13 +13184,11 @@ TEST_P(QuicConnectionTest, CloseConnectionOnIntegrityLimitAcrossKeyPhases) { constexpr QuicPacketCount kIntegrityLimit = 4; TransportParameters params; - params.key_update_not_yet_supported = false; QuicConfig config; std::string error_details; EXPECT_THAT(config.ProcessTransportParameters( params, /* is_resumption = */ false, &error_details), IsQuicNoError()); - config.SetKeyUpdateSupportedLocally(); QuicConfigPeer::SetNegotiated(&config, true); if (connection_.version().UsesTls()) { QuicConfigPeer::SetReceivedOriginalConnectionId( @@ -13987,12 +13633,10 @@ TEST_P(QuicConnectionTest, // kept for key update, so enable key update for the test. std::string error_details; TransportParameters params; - params.key_update_not_yet_supported = false; QuicConfig config; EXPECT_THAT(config.ProcessTransportParameters( params, /* is_resumption = */ false, &error_details), IsQuicNoError()); - config.SetKeyUpdateSupportedLocally(); QuicConfigPeer::SetNegotiated(&config, true); QuicConfigPeer::SetReceivedOriginalConnectionId(&config, connection_.connection_id()); @@ -15265,14 +14909,8 @@ TEST_P(QuicConnectionTest, SendingZeroRttPacketsDoesNotPostponePTO) { 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 { - // PTO gets re-armed. - EXPECT_NE(pto_deadline, connection_.GetRetransmissionAlarm()->deadline()); - } + // PTO deadline should be unchanged. + EXPECT_EQ(pto_deadline, connection_.GetRetransmissionAlarm()->deadline()); } TEST_P(QuicConnectionTest, QueueingUndecryptablePacketsDoesntPostponePTO) { @@ -15547,6 +15185,25 @@ TEST_P(QuicConnectionTest, AckElicitingFrames) { } } +TEST_P(QuicConnectionTest, ReceivedChloAndAck) { + if (!version().HasIetfQuicFrames()) { + return; + } + set_perspective(Perspective::IS_SERVER); + QuicFrames frames; + QuicAckFrame ack_frame = InitAckFrame(1); + frames.push_back(MakeCryptoFrame()); + frames.push_back(QuicFrame(&ack_frame)); + + EXPECT_CALL(visitor_, OnCryptoFrame(_)) + .WillOnce(IgnoreResult(InvokeWithoutArgs( + &connection_, &TestConnection::SendCryptoStreamData))); + EXPECT_CALL(visitor_, BeforeConnectionCloseSent()); + EXPECT_CALL(visitor_, OnConnectionClosed(_, _)); + ProcessFramesPacketWithAddresses(frames, kSelfAddress, kPeerAddress, + ENCRYPTION_INITIAL); +} + // Regression test for b/201643321. TEST_P(QuicConnectionTest, FailedToRetransmitShlo) { if (!version().HasIetfQuicFrames()) { diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_crypto_client_handshaker.cc b/chromium/net/third_party/quiche/src/quic/core/quic_crypto_client_handshaker.cc index e9a99167f46..872b050bf9c 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_crypto_client_handshaker.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_crypto_client_handshaker.cc @@ -180,10 +180,6 @@ size_t QuicCryptoClientHandshaker::BufferSizeLimitForLevel( return QuicCryptoHandshaker::BufferSizeLimitForLevel(level); } -bool QuicCryptoClientHandshaker::KeyUpdateSupportedLocally() const { - return false; -} - std::unique_ptr<QuicDecrypter> QuicCryptoClientHandshaker::AdvanceKeysAndCreateCurrentOneRttDecrypter() { // Key update is only defined in QUIC+TLS. diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_crypto_client_handshaker.h b/chromium/net/third_party/quiche/src/quic/core/quic_crypto_client_handshaker.h index d9f10350553..c0c94b94cd8 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_crypto_client_handshaker.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_crypto_client_handshaker.h @@ -51,7 +51,6 @@ class QUIC_EXPORT_PRIVATE QuicCryptoClientHandshaker CryptoMessageParser* crypto_message_parser() override; HandshakeState GetHandshakeState() const override; size_t BufferSizeLimitForLevel(EncryptionLevel level) const override; - bool KeyUpdateSupportedLocally() const override; std::unique_ptr<QuicDecrypter> AdvanceKeysAndCreateCurrentOneRttDecrypter() override; std::unique_ptr<QuicEncrypter> CreateCurrentOneRttEncrypter() override; diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_crypto_client_stream.cc b/chromium/net/third_party/quiche/src/quic/core/quic_crypto_client_stream.cc index 31d96a37794..27a2a469ace 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_crypto_client_stream.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_crypto_client_stream.cc @@ -114,10 +114,6 @@ size_t QuicCryptoClientStream::BufferSizeLimitForLevel( return handshaker_->BufferSizeLimitForLevel(level); } -bool QuicCryptoClientStream::KeyUpdateSupportedLocally() const { - return handshaker_->KeyUpdateSupportedLocally(); -} - std::unique_ptr<QuicDecrypter> QuicCryptoClientStream::AdvanceKeysAndCreateCurrentOneRttDecrypter() { return handshaker_->AdvanceKeysAndCreateCurrentOneRttDecrypter(); diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_crypto_client_stream.h b/chromium/net/third_party/quiche/src/quic/core/quic_crypto_client_stream.h index e37a5707ba5..34928b61f81 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_crypto_client_stream.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_crypto_client_stream.h @@ -183,9 +183,6 @@ class QUIC_EXPORT_PRIVATE QuicCryptoClientStream // buffered at each encryption level. virtual size_t BufferSizeLimitForLevel(EncryptionLevel level) const = 0; - // Returns whether the implementation supports key update. - virtual bool KeyUpdateSupportedLocally() const = 0; - // Called to generate a decrypter for the next key phase. Each call should // generate the key for phase n+1. virtual std::unique_ptr<QuicDecrypter> @@ -283,7 +280,6 @@ class QUIC_EXPORT_PRIVATE QuicCryptoClientStream void SetServerApplicationStateForResumption( std::unique_ptr<ApplicationState> application_state) override; size_t BufferSizeLimitForLevel(EncryptionLevel level) const override; - bool KeyUpdateSupportedLocally() const override; std::unique_ptr<QuicDecrypter> AdvanceKeysAndCreateCurrentOneRttDecrypter() override; std::unique_ptr<QuicEncrypter> CreateCurrentOneRttEncrypter() override; 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 28bfe4add34..55b7bf1a703 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 @@ -435,10 +435,6 @@ size_t QuicCryptoServerStream::BufferSizeLimitForLevel( return QuicCryptoHandshaker::BufferSizeLimitForLevel(level); } -bool QuicCryptoServerStream::KeyUpdateSupportedLocally() const { - return false; -} - std::unique_ptr<QuicDecrypter> QuicCryptoServerStream::AdvanceKeysAndCreateCurrentOneRttDecrypter() { // Key update is only defined in QUIC+TLS. diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_crypto_server_stream.h b/chromium/net/third_party/quiche/src/quic/core/quic_crypto_server_stream.h index cf4f6e00489..aca9e9a1170 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_crypto_server_stream.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_crypto_server_stream.h @@ -66,7 +66,6 @@ class QUIC_EXPORT_PRIVATE QuicCryptoServerStream void SetServerApplicationStateForResumption( std::unique_ptr<ApplicationState> state) override; size_t BufferSizeLimitForLevel(EncryptionLevel level) const override; - bool KeyUpdateSupportedLocally() const override; std::unique_ptr<QuicDecrypter> AdvanceKeysAndCreateCurrentOneRttDecrypter() override; std::unique_ptr<QuicEncrypter> CreateCurrentOneRttEncrypter() override; diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_crypto_stream.h b/chromium/net/third_party/quiche/src/quic/core/quic_crypto_stream.h index 86b691309bc..8197a5f4d75 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_crypto_stream.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_crypto_stream.h @@ -149,9 +149,6 @@ class QUIC_EXPORT_PRIVATE QuicCryptoStream : public QuicStream { // encryption level |level|. virtual size_t BufferSizeLimitForLevel(EncryptionLevel level) const; - // Returns whether the implementation supports key update. - virtual bool KeyUpdateSupportedLocally() const = 0; - // Called to generate a decrypter for the next key phase. Each call should // generate the key for phase n+1. virtual std::unique_ptr<QuicDecrypter> @@ -245,6 +242,10 @@ class QUIC_EXPORT_PRIVATE QuicCryptoStream : public QuicStream { virtual void OnDataAvailableInSequencer(QuicStreamSequencer* sequencer, EncryptionLevel level); + QuicStreamSequencer* GetStreamSequencerForLevel(EncryptionLevel level) { + return &substreams_[level].sequencer; + } + private: // Data sent and received in CRYPTO frames is sent at multiple encryption // levels. Some of the state for the single logical crypto stream is split diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_crypto_stream_test.cc b/chromium/net/third_party/quiche/src/quic/core/quic_crypto_stream_test.cc index 1c18c7b7886..2c9ec0db082 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_crypto_stream_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_crypto_stream_test.cc @@ -80,7 +80,6 @@ class MockQuicCryptoStream : public QuicCryptoStream, HandshakeState GetHandshakeState() const override { return HANDSHAKE_START; } void SetServerApplicationStateForResumption( std::unique_ptr<ApplicationState> /*application_state*/) override {} - bool KeyUpdateSupportedLocally() const override { return false; } std::unique_ptr<QuicDecrypter> AdvanceKeysAndCreateCurrentOneRttDecrypter() override { return nullptr; 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 4221d4fd9d0..0da608c6900 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 @@ -367,7 +367,7 @@ QuicDispatcher::~QuicDispatcher() { clear_stateless_reset_addresses_alarm_->PermanentCancel(); } reference_counted_session_map_.clear(); - closed_ref_counted_session_list_.clear(); + closed_session_list_.clear(); num_sessions_in_session_map_ = 0; } @@ -968,7 +968,7 @@ std::unique_ptr<QuicPerPacketContext> QuicDispatcher::GetPerPacketContext() void QuicDispatcher::DeleteSessions() { if (!write_blocked_list_.empty()) { - for (const auto& session : closed_ref_counted_session_list_) { + for (const auto& session : closed_session_list_) { if (write_blocked_list_.erase(session->connection()) != 0) { QUIC_BUG(quic_bug_12724_2) << "QuicConnection was in WriteBlockedList before destruction " @@ -976,7 +976,7 @@ void QuicDispatcher::DeleteSessions() { } } } - closed_ref_counted_session_list_.clear(); + closed_session_list_.clear(); } void QuicDispatcher::ClearStatelessResetAddresses() { @@ -1051,11 +1051,11 @@ void QuicDispatcher::OnConnectionClosed(QuicConnectionId server_connection_id, if (ShouldDestroySessionAsynchronously()) { // Set up alarm to fire immediately to bring destruction of this session // out of current call stack. - if (closed_ref_counted_session_list_.empty()) { + if (closed_session_list_.empty()) { delete_sessions_alarm_->Update(helper()->GetClock()->ApproximateNow(), QuicTime::Delta::Zero()); } - closed_ref_counted_session_list_.push_back(std::move(it->second)); + closed_session_list_.push_back(std::move(it->second)); } CleanUpSession(it->first, connection, error, error_details, source); for (const QuicConnectionId& cid : @@ -1377,11 +1377,6 @@ const ParsedQuicVersionVector& QuicDispatcher::GetSupportedVersions() { return version_manager_->GetSupportedVersions(); } -const ParsedQuicVersionVector& -QuicDispatcher::GetSupportedVersionsWithQuicCrypto() { - return version_manager_->GetSupportedVersionsWithQuicCrypto(); -} - void QuicDispatcher::DeliverPacketsToSession( const std::list<BufferedPacket>& packets, QuicSession* session) { 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 eab326a9679..f22d58aa4c9 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 @@ -256,8 +256,6 @@ class QUIC_NO_EXPORT QuicDispatcher const ParsedQuicVersionVector& GetSupportedVersions(); - const ParsedQuicVersionVector& GetSupportedVersionsWithQuicCrypto(); - const QuicConfig& config() const { return *config_; } const QuicCryptoServerConfig* crypto_config() const { return crypto_config_; } @@ -414,8 +412,7 @@ class QUIC_NO_EXPORT QuicDispatcher std::unique_ptr<QuicTimeWaitListManager> time_wait_list_manager_; // The list of closed but not-yet-deleted sessions. - std::vector<std::unique_ptr<QuicSession>> closed_session_list_; - std::vector<std::shared_ptr<QuicSession>> closed_ref_counted_session_list_; + std::vector<std::shared_ptr<QuicSession>> closed_session_list_; // The helper used for all connections. std::unique_ptr<QuicConnectionHelperInterface> helper_; 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 ca4ae483af8..add5164d674 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 @@ -1521,7 +1521,7 @@ TEST_P(QuicDispatcherTestOneVersion, dispatcher_->ProcessPacket(server_address_, client_address, received_packet); } -static_assert(quic::SupportedVersions().size() == 5u, +static_assert(quic::SupportedVersions().size() == 6u, "Please add new RejectDeprecatedVersion tests above this assert " "when deprecating versions"); diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_epoll_alarm_factory.h b/chromium/net/third_party/quiche/src/quic/core/quic_epoll_alarm_factory.h index 6fb1e02ed4e..986b842ae8d 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_epoll_alarm_factory.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_epoll_alarm_factory.h @@ -15,7 +15,7 @@ namespace quic { // Creates alarms that use the supplied EpollServer for timing and firing. class QUIC_EXPORT_PRIVATE QuicEpollAlarmFactory : public QuicAlarmFactory { public: - explicit QuicEpollAlarmFactory(QuicEpollServer* eps); + explicit QuicEpollAlarmFactory(QuicEpollServer* epoll_server); QuicEpollAlarmFactory(const QuicEpollAlarmFactory&) = delete; QuicEpollAlarmFactory& operator=(const QuicEpollAlarmFactory&) = delete; ~QuicEpollAlarmFactory() override; diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_epoll_connection_helper.cc b/chromium/net/third_party/quiche/src/quic/core/quic_epoll_connection_helper.cc index 8f532562aee..42d2e0230cc 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_epoll_connection_helper.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_epoll_connection_helper.cc @@ -12,11 +12,10 @@ namespace quic { QuicEpollConnectionHelper::QuicEpollConnectionHelper( - QuicEpollServer* epoll_server, - QuicAllocator type) + QuicEpollServer* epoll_server, QuicAllocator allocator_type) : clock_(epoll_server), random_generator_(QuicRandom::GetInstance()), - allocator_type_(type) {} + allocator_type_(allocator_type) {} QuicEpollConnectionHelper::~QuicEpollConnectionHelper() = default; diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_epoll_connection_helper.h b/chromium/net/third_party/quiche/src/quic/core/quic_epoll_connection_helper.h index 0db6ed2cd0a..d363d6bc97f 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_epoll_connection_helper.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_epoll_connection_helper.h @@ -30,7 +30,8 @@ enum class QuicAllocator { SIMPLE, BUFFER_POOL }; class QUIC_EXPORT_PRIVATE QuicEpollConnectionHelper : public QuicConnectionHelperInterface { public: - QuicEpollConnectionHelper(QuicEpollServer* eps, QuicAllocator allocator); + QuicEpollConnectionHelper(QuicEpollServer* epoll_server, + QuicAllocator allocator_type); QuicEpollConnectionHelper(const QuicEpollConnectionHelper&) = delete; QuicEpollConnectionHelper& operator=(const QuicEpollConnectionHelper&) = delete; 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 74922c37721..c7da0ae17ba 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 @@ -279,6 +279,7 @@ const char* QuicErrorCodeToString(QuicErrorCode error) { RETURN_STRING_LITERAL(QUIC_TLS_UNEXPECTED_KEYING_MATERIAL_EXPORT_LABEL); RETURN_STRING_LITERAL(QUIC_TLS_KEYING_MATERIAL_EXPORTS_MISMATCH); RETURN_STRING_LITERAL(QUIC_TLS_KEYING_MATERIAL_EXPORT_NOT_AVAILABLE); + RETURN_STRING_LITERAL(QUIC_UNEXPECTED_DATA_BEFORE_ENCRYPTION_ESTABLISHED); RETURN_STRING_LITERAL(QUIC_LAST_ERROR); // Intentionally have no default case, so we'll break the build @@ -784,6 +785,8 @@ QuicErrorCodeToIetfMapping QuicErrorCodeToTransportErrorCode( return {true, static_cast<uint64_t>(PROTOCOL_VIOLATION)}; case QUIC_TLS_KEYING_MATERIAL_EXPORT_NOT_AVAILABLE: return {true, static_cast<uint64_t>(PROTOCOL_VIOLATION)}; + case QUIC_UNEXPECTED_DATA_BEFORE_ENCRYPTION_ESTABLISHED: + return {true, static_cast<uint64_t>(PROTOCOL_VIOLATION)}; case QUIC_LAST_ERROR: return {false, static_cast<uint64_t>(QUIC_LAST_ERROR)}; } 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 218b12ff1ea..0ecef013ff9 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 @@ -608,9 +608,10 @@ enum QuicErrorCode { QUIC_TLS_UNEXPECTED_KEYING_MATERIAL_EXPORT_LABEL = 208, QUIC_TLS_KEYING_MATERIAL_EXPORTS_MISMATCH = 209, QUIC_TLS_KEYING_MATERIAL_EXPORT_NOT_AVAILABLE = 210, + QUIC_UNEXPECTED_DATA_BEFORE_ENCRYPTION_ESTABLISHED = 211, // No error. Used as bound while iterating. - QUIC_LAST_ERROR = 211, + QUIC_LAST_ERROR = 212, }; // 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 b7b7f155cb4..069ba10b822 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 @@ -1,4 +1,4 @@ -// Copyright (c) 2021 The Chromium Authors. All rights reserved. +// Copyright (c) 2022 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. @@ -6,6 +6,8 @@ #ifdef QUIC_FLAG +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_set_burst_token, false) + QUIC_FLAG(FLAGS_quic_restart_flag_quic_offload_pacing_to_usps2, false) // A testonly reloadable flag that will always default to false. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_testonly_default_false, false) @@ -16,15 +18,15 @@ 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) // Donot check amplification limit if there is available pending_timer_transmission_count. -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_donot_check_amplification_limit_with_pending_timer_credit, false) +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_donot_check_amplification_limit_with_pending_timer_credit, true) // If bytes in flight has dipped below 1.25*MaxBW in the last round, do not exit PROBE_UP due to excess queue buildup. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_no_probe_up_exit_if_no_queue, true) // If true, 1) NEW_TOKENs sent from a IETF QUIC session will include the cached network parameters proto, 2) A min_rtt received from a validated token will be used to set the initial rtt, 3) Enable bandwidth resumption for IETF QUIC when connection options BWRE or BWMX exists. -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_add_cached_network_parameters_to_address_token2, false) +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_add_cached_network_parameters_to_address_token2, true) // 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, QUIC won\'t honor the connection option TLPR -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_deprecate_tlpr, true) +// If true, QpackEncoder will stop emitting instructions on the encoder stream if amount of buffered data exceeds a hardcoded limit. +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_limit_encoder_stream_buffering, 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, TlsServerHandshaker will be able to 1) request client cert, and 2) verify the client cert in the virtual method TlsServerHandshaker::VerifyCertChain. @@ -32,7 +34,7 @@ QUIC_FLAG(FLAGS_quic_restart_flag_quic_tls_server_support_client_cert, 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, accept empty crypto frame. -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_accept_empty_crypto_frame, false) +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_accept_empty_crypto_frame, 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, allow client to enable BBRv2 on server via connection option \'B2ON\'. @@ -69,12 +71,8 @@ QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_single_ack_in_packet2, false) QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_tls_no_select_cert_if_disconnected, true) // 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 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, do not use the gQUIC common certificate set for certificate compression. QUIC_FLAG(FLAGS_quic_restart_flag_quic_no_common_cert_set, 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 server retransmittable on wire PING. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_enable_server_on_wire_ping, true) // If true, flush pending frames as well as pending padding bytes on connection migration. @@ -99,12 +97,12 @@ QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_require_handshake_confirmation, false) 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, server proactively retires client issued connection ID on reverse path validation failure. +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_retire_cid_on_reverse_path_validation_failure, false) // If true, set burst token to 2 in cwnd bootstrapping experiment. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_conservative_bursts, false) // 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, suppress crypto data write in mid of packet processing. -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_suppress_write_mid_packet_processing, 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 max(max_bw, send_rate) as the estimated bandwidth in QUIC\'s MaxAckHeightTracker. @@ -117,22 +115,18 @@ QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_conservative_cwnd_and_pacing_gains, fa QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_server_reverse_validate_new_path3, true) // If true, when client attempts TLS resumption, use token in session_cache_ instead of cached_states_ in QuicCryptoClientConfig. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_tls_use_token_in_session_cache, true) -// When receiving STOP_SENDING, send a RESET_STREAM with a matching error code. -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_match_ietf_reset_code, true) // When the flag is true, exit STARTUP after the same number of loss events as PROBE_UP. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_startup_probe_up_loss_events, true) -// When true, QUIC server will ignore received key_update_not_yet_supported transport parameter. -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_ignore_key_update_not_yet_supported, true) -// When true, QUIC server will ignore received user agent transport parameter and rely on getting that information from HTTP headers. -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_ignore_user_agent_transport_parameter, true) // When true, QUIC will both send and validate the version_information transport parameter. -QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_version_information, false) +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_version_information, true) // When true, defaults to BBR congestion control instead of Cubic. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_default_to_bbr, false) // 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) +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_pacing_sender_bursts, true) // 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, true) +// When true, support draft-ietf-quic-v2-01 +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_enable_version_2_draft_01, false) // When true, the B203 connection option causes the Bbr2Sender to ignore inflight_hi during PROBE_UP and increase it when the bytes delivered without loss are higher. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_ignore_inflight_hi_in_probe_up, true) // When true, the B205 connection option enables extra acked in STARTUP, and B204 adds new logic to decrease it whenever max bandwidth increases. 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 79ace5a642d..da28e4c5b5c 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 @@ -170,8 +170,7 @@ QuicPacketNumberLength ReadSequenceNumberLength(uint8_t flags) { } } -QuicPacketNumberLength ReadAckPacketNumberLength( - uint8_t flags) { +QuicPacketNumberLength ReadAckPacketNumberLength(uint8_t flags) { switch (flags & PACKET_FLAGS_8BYTE_PACKET) { case PACKET_FLAGS_8BYTE_PACKET: return PACKET_6BYTE_PACKET_NUMBER; @@ -197,16 +196,17 @@ QuicPacketNumberLength GetShortHeaderPacketNumberLength(uint8_t type) { return static_cast<QuicPacketNumberLength>((type & 0x03) + 1); } -uint8_t LongHeaderTypeToOnWireValue(QuicLongHeaderType type) { +uint8_t LongHeaderTypeToOnWireValue(QuicLongHeaderType type, + const ParsedQuicVersion& version) { switch (type) { case INITIAL: - return 0; + return version.UsesV2PacketTypes() ? (1 << 4) : 0; case ZERO_RTT_PROTECTED: - return 1 << 4; + return version.UsesV2PacketTypes() ? (2 << 4) : (1 << 4); case HANDSHAKE: - return 2 << 4; + return version.UsesV2PacketTypes() ? (3 << 4) : (2 << 4); case RETRY: - return 3 << 4; + return version.UsesV2PacketTypes() ? 0 : (3 << 4); case VERSION_NEGOTIATION: return 0xF0; // Value does not matter default: @@ -215,27 +215,22 @@ uint8_t LongHeaderTypeToOnWireValue(QuicLongHeaderType type) { } } -bool GetLongHeaderType(uint8_t type, QuicLongHeaderType* long_header_type) { +QuicLongHeaderType GetLongHeaderType(uint8_t type, + const ParsedQuicVersion& version) { QUICHE_DCHECK((type & FLAGS_LONG_HEADER)); switch ((type & 0x30) >> 4) { case 0: - *long_header_type = INITIAL; - break; + return version.UsesV2PacketTypes() ? RETRY : INITIAL; case 1: - *long_header_type = ZERO_RTT_PROTECTED; - break; + return version.UsesV2PacketTypes() ? INITIAL : ZERO_RTT_PROTECTED; case 2: - *long_header_type = HANDSHAKE; - break; + return version.UsesV2PacketTypes() ? ZERO_RTT_PROTECTED : HANDSHAKE; case 3: - *long_header_type = RETRY; - break; + return version.UsesV2PacketTypes() ? HANDSHAKE : RETRY; default: QUIC_BUG(quic_bug_10850_4) << "Unreachable statement"; - *long_header_type = INVALID_PACKET_TYPE; - return false; + return INVALID_PACKET_TYPE; } - return true; } QuicPacketNumberLength GetLongHeaderPacketNumberLength(uint8_t type) { @@ -331,8 +326,7 @@ bool IsValidFullPacketNumber(uint64_t full_packet_number, return full_packet_number > 0 || version.HasIetfQuicFrames(); } -bool AppendIetfConnectionIds(bool version_flag, - bool use_length_prefix, +bool AppendIetfConnectionIds(bool version_flag, bool use_length_prefix, QuicConnectionId destination_connection_id, QuicConnectionId source_connection_id, QuicDataWriter* writer) { @@ -412,6 +406,7 @@ QuicFramer::QuicFramer(const ParsedQuicVersionVector& supported_versions, perspective_(perspective), validate_flags_(true), process_timestamps_(false), + max_receive_timestamps_per_ack_(std::numeric_limits<uint32_t>::max()), receive_timestamps_exponent_(0), creation_time_(creation_time), last_timestamp_(QuicTime::Delta::Zero()), @@ -476,13 +471,14 @@ size_t QuicFramer::GetMessageFrameSize(QuicTransportVersion version, } // static -size_t QuicFramer::GetMinAckFrameSize(QuicTransportVersion version, - const QuicAckFrame& ack_frame, - uint32_t local_ack_delay_exponent) { +size_t QuicFramer::GetMinAckFrameSize( + QuicTransportVersion version, const QuicAckFrame& ack_frame, + uint32_t local_ack_delay_exponent, + bool use_ietf_ack_with_receive_timestamp) { if (VersionHasIetfQuicFrames(version)) { // The minimal ack frame consists of the following fields: Largest - // Acknowledged, ACK Delay, 0 ACK Block Count, First ACK Block and ECN - // counts. + // Acknowledged, ACK Delay, 0 ACK Block Count, First ACK Block and either 0 + // Timestamp Range Count or ECN counts. // Type byte + largest acked. size_t min_size = kQuicFrameTypeSize + @@ -496,10 +492,14 @@ size_t QuicFramer::GetMinAckFrameSize(QuicTransportVersion version, min_size += QuicDataWriter::GetVarInt62Len( ack_frame.packets.Empty() ? 0 : ack_frame.packets.rbegin()->Length() - 1); - // ECN counts. - if (ack_frame.ecn_counters_populated && - (ack_frame.ect_0_count || ack_frame.ect_1_count || - ack_frame.ecn_ce_count)) { + + if (use_ietf_ack_with_receive_timestamp) { + // 0 Timestamp Range Count. + min_size += QuicDataWriter::GetVarInt62Len(0); + } else if (ack_frame.ecn_counters_populated && + (ack_frame.ect_0_count || ack_frame.ect_1_count || + ack_frame.ecn_ce_count)) { + // ECN counts. min_size += (QuicDataWriter::GetVarInt62Len(ack_frame.ect_0_count) + QuicDataWriter::GetVarInt62Len(ack_frame.ect_1_count) + QuicDataWriter::GetVarInt62Len(ack_frame.ecn_ce_count)); @@ -533,8 +533,7 @@ size_t QuicFramer::GetRstStreamFrameSize(QuicTransportVersion version, // static size_t QuicFramer::GetConnectionCloseFrameSize( - QuicTransportVersion version, - const QuicConnectionCloseFrame& frame) { + QuicTransportVersion version, const QuicConnectionCloseFrame& frame) { if (!VersionHasIetfQuicFrames(version)) { // Not IETF QUIC, return Google QUIC CONNECTION CLOSE frame size. return kQuicFrameTypeSize + kQuicErrorCodeSize + @@ -569,8 +568,7 @@ size_t QuicFramer::GetMinGoAwayFrameSize() { // static size_t QuicFramer::GetWindowUpdateFrameSize( - QuicTransportVersion version, - const QuicWindowUpdateFrame& frame) { + QuicTransportVersion version, const QuicWindowUpdateFrame& frame) { if (!VersionHasIetfQuicFrames(version)) { return kQuicFrameTypeSize + kQuicMaxStreamIdSize + kQuicMaxStreamOffsetSize; } @@ -599,8 +597,7 @@ size_t QuicFramer::GetMaxStreamsFrameSize(QuicTransportVersion version, // static size_t QuicFramer::GetStreamsBlockedFrameSize( - QuicTransportVersion version, - const QuicStreamsBlockedFrame& frame) { + QuicTransportVersion version, const QuicStreamsBlockedFrame& frame) { if (!VersionHasIetfQuicFrames(version)) { QUIC_BUG(quic_bug_10850_10) << "In version " << version @@ -658,8 +655,7 @@ size_t QuicFramer::GetPathResponseFrameSize( // static size_t QuicFramer::GetRetransmittableControlFrameSize( - QuicTransportVersion version, - const QuicFrame& frame) { + QuicTransportVersion version, const QuicFrame& frame) { switch (frame.type) { case PING_FRAME: // Ping has no payload. @@ -793,11 +789,8 @@ bool QuicFramer::IsSupportedVersion(const ParsedQuicVersion version) const { } size_t QuicFramer::GetSerializedFrameLength( - const QuicFrame& frame, - size_t free_bytes, - bool first_frame, - bool last_frame, - QuicPacketNumberLength packet_number_length) { + const QuicFrame& frame, size_t free_bytes, bool first_frame, + bool last_frame, QuicPacketNumberLength packet_number_length) { // Prevent a rare crash reported in b/19458523. if (frame.type == ACK_FRAME && frame.ack_frame == nullptr) { QUIC_BUG(quic_bug_10850_13) @@ -835,9 +828,10 @@ size_t QuicFramer::GetSerializedFrameLength( } bool can_truncate = frame.type == ACK_FRAME && - free_bytes >= GetMinAckFrameSize(version_.transport_version, - *frame.ack_frame, - local_ack_delay_exponent_); + free_bytes >= + GetMinAckFrameSize(version_.transport_version, *frame.ack_frame, + local_ack_delay_exponent_, + UseIetfAckWithReceiveTimestamp(*frame.ack_frame)); if (can_truncate) { // Truncate the frame so the packet will not exceed kMaxOutgoingPacketSize. // Note that we may not use every byte of the writer in this case. @@ -887,8 +881,7 @@ bool QuicFramer::WriteIetfLongHeaderLength(const QuicPacketHeader& header, } size_t QuicFramer::BuildDataPacket(const QuicPacketHeader& header, - const QuicFrames& frames, - char* buffer, + const QuicFrames& frames, char* buffer, size_t packet_length, EncryptionLevel level) { QUIC_BUG_IF(quic_bug_12975_2, @@ -1345,10 +1338,8 @@ std::unique_ptr<QuicEncryptedPacket> QuicFramer::BuildIetfStatelessResetPacket( // static std::unique_ptr<QuicEncryptedPacket> QuicFramer::BuildVersionNegotiationPacket( QuicConnectionId server_connection_id, - QuicConnectionId client_connection_id, - bool ietf_quic, - bool use_length_prefix, - const ParsedQuicVersionVector& versions) { + QuicConnectionId client_connection_id, bool ietf_quic, + bool use_length_prefix, const ParsedQuicVersionVector& versions) { QUIC_CODE_COUNT(quic_build_version_negotiation); if (use_length_prefix) { QUICHE_DCHECK(ietf_quic); @@ -1420,8 +1411,7 @@ std::unique_ptr<QuicEncryptedPacket> QuicFramer::BuildVersionNegotiationPacket( // static std::unique_ptr<QuicEncryptedPacket> QuicFramer::BuildIetfVersionNegotiationPacket( - bool use_length_prefix, - QuicConnectionId server_connection_id, + bool use_length_prefix, QuicConnectionId server_connection_id, QuicConnectionId client_connection_id, const ParsedQuicVersionVector& versions) { QUIC_DVLOG(1) << "Building IETF version negotiation packet with" @@ -1566,8 +1556,7 @@ bool QuicFramer::ProcessPacketInternal(const QuicEncryptedPacket& packet) { } bool QuicFramer::ProcessVersionNegotiationPacket( - QuicDataReader* reader, - const QuicPacketHeader& header) { + QuicDataReader* reader, const QuicPacketHeader& header) { QUICHE_DCHECK_EQ(Perspective::IS_CLIENT, perspective_); QuicVersionNegotiationPacket packet( @@ -1667,8 +1656,7 @@ bool QuicFramer::ProcessRetryPacket(QuicDataReader* reader, // If the IETF length field only spans part of the outer packet, // then there is a coalesced packet after this one. void QuicFramer::MaybeProcessCoalescedPacket( - const QuicDataReader& encrypted_reader, - uint64_t remaining_bytes_length, + const QuicDataReader& encrypted_reader, uint64_t remaining_bytes_length, const QuicPacketHeader& header) { if (header.remaining_packet_length >= remaining_bytes_length) { // There is no coalesced packet. @@ -2221,7 +2209,7 @@ bool QuicFramer::AppendIetfHeaderTypeByte(const QuicPacketHeader& header, if (header.version_flag) { type = static_cast<uint8_t>( FLAGS_LONG_HEADER | FLAGS_FIXED_BIT | - LongHeaderTypeToOnWireValue(header.long_packet_type) | + LongHeaderTypeToOnWireValue(header.long_packet_type, version_) | PacketNumberLengthToOnWireValue(header.packet_number_length)); } else { type = static_cast<uint8_t>( @@ -2357,8 +2345,7 @@ const QuicTime::Delta QuicFramer::CalculateTimestampFromWire( uint64_t QuicFramer::CalculatePacketNumberFromWire( QuicPacketNumberLength packet_number_length, - QuicPacketNumber base_packet_number, - uint64_t packet_number) const { + QuicPacketNumber base_packet_number, uint64_t packet_number) const { // The new packet number might have wrapped to the next epoch, or // it might have reverse wrapped to the previous epoch, or it might // remain in the same epoch. Select the packet number closest to the @@ -2632,21 +2619,27 @@ bool QuicFramer::ProcessIetfHeaderTypeByte(QuicDataReader* reader, set_detailed_error("Fixed bit is 0 in long header."); return false; } - if (!GetLongHeaderType(type, &header->long_packet_type)) { - set_detailed_error("Illegal long header type value."); - return false; - } - if (header->long_packet_type == RETRY) { - if (!version().SupportsRetry()) { - set_detailed_error("RETRY not supported in this version."); + header->long_packet_type = GetLongHeaderType(type, header->version); + switch (header->long_packet_type) { + case INVALID_PACKET_TYPE: + set_detailed_error("Illegal long header type value."); return false; - } - if (perspective_ == Perspective::IS_SERVER) { - set_detailed_error("Client-initiated RETRY is invalid."); - return false; - } - } else if (!header->version.HasHeaderProtection()) { - header->packet_number_length = GetLongHeaderPacketNumberLength(type); + case RETRY: + if (!version().SupportsRetry()) { + set_detailed_error("RETRY not supported in this version."); + return false; + } + if (perspective_ == Perspective::IS_SERVER) { + set_detailed_error("Client-initiated RETRY is invalid."); + return false; + } + break; + default: + if (!header->version.HasHeaderProtection()) { + header->packet_number_length = + GetLongHeaderPacketNumberLength(type); + } + break; } } } @@ -2690,14 +2683,11 @@ bool QuicFramer::ProcessVersionLabel(QuicDataReader* reader, // static bool QuicFramer::ProcessAndValidateIetfConnectionIdLength( - QuicDataReader* reader, - ParsedQuicVersion version, - Perspective perspective, + QuicDataReader* reader, ParsedQuicVersion version, Perspective perspective, bool should_update_expected_server_connection_id_length, uint8_t* expected_server_connection_id_length, uint8_t* destination_connection_id_length, - uint8_t* source_connection_id_length, - std::string* detailed_error) { + uint8_t* source_connection_id_length, std::string* detailed_error) { uint8_t connection_id_lengths_byte; if (!reader->ReadBytes(&connection_id_lengths_byte, 1)) { *detailed_error = "Unable to read ConnectionId length."; @@ -2880,10 +2870,8 @@ bool QuicFramer::ProcessIetfPacketHeader(QuicDataReader* reader, } bool QuicFramer::ProcessAndCalculatePacketNumber( - QuicDataReader* reader, - QuicPacketNumberLength packet_number_length, - QuicPacketNumber base_packet_number, - uint64_t* packet_number) { + QuicDataReader* reader, QuicPacketNumberLength packet_number_length, + QuicPacketNumber base_packet_number, uint64_t* packet_number) { uint64_t wire_packet_number; if (!reader->ReadBytesToUInt64(packet_number_length, &wire_packet_number)) { return false; @@ -3131,8 +3119,7 @@ bool QuicFramer::ProcessFrameData(QuicDataReader* reader, // static bool QuicFramer::IsIetfFrameTypeExpectedForEncryptionLevel( - uint64_t frame_type, - EncryptionLevel level) { + uint64_t frame_type, EncryptionLevel level) { switch (level) { case ENCRYPTION_INITIAL: case ENCRYPTION_HANDSHAKE: @@ -3560,8 +3547,7 @@ void SetBit(uint8_t* flags, bool val, uint8_t offset) { } } // namespace -bool QuicFramer::ProcessStreamFrame(QuicDataReader* reader, - uint8_t frame_type, +bool QuicFramer::ProcessStreamFrame(QuicDataReader* reader, uint8_t frame_type, QuicStreamFrame* frame) { uint8_t stream_flags = frame_type; @@ -3748,12 +3734,12 @@ bool QuicFramer::ProcessAckFrame(QuicDataReader* reader, uint8_t frame_type) { // Determine the two lengths from the frame type: largest acked length, // ack block length. - const QuicPacketNumberLength ack_block_length = ReadAckPacketNumberLength( - ExtractBits(frame_type, kQuicSequenceNumberLengthNumBits, - kActBlockLengthOffset)); - const QuicPacketNumberLength largest_acked_length = ReadAckPacketNumberLength( - ExtractBits(frame_type, kQuicSequenceNumberLengthNumBits, - kLargestAckedOffset)); + const QuicPacketNumberLength ack_block_length = + ReadAckPacketNumberLength(ExtractBits( + frame_type, kQuicSequenceNumberLengthNumBits, kActBlockLengthOffset)); + const QuicPacketNumberLength largest_acked_length = + ReadAckPacketNumberLength(ExtractBits( + frame_type, kQuicSequenceNumberLengthNumBits, kLargestAckedOffset)); uint64_t largest_acked; if (!reader->ReadBytesToUInt64(largest_acked_length, &largest_acked)) { @@ -4179,7 +4165,7 @@ bool QuicFramer::ProcessIetfTimestampsInAckFrame(QuicPacketNumber largest_acked, // packet order. timestamp_delta = timestamp_delta << receive_timestamps_exponent_; if (i == 0 && j == 0) { - last_timestamp_ = CalculateTimestampFromWire(timestamp_delta); + last_timestamp_ = QuicTime::Delta::FromMicroseconds(timestamp_delta); } else { last_timestamp_ = last_timestamp_ - QuicTime::Delta::FromMicroseconds(timestamp_delta); @@ -4188,10 +4174,10 @@ bool QuicFramer::ProcessIetfTimestampsInAckFrame(QuicPacketNumber largest_acked, return false; } } - visitor_->OnAckTimestamp(packet_number - j, - creation_time_ + last_timestamp_); + visitor_->OnAckTimestamp(packet_number, creation_time_ + last_timestamp_); + packet_number--; } - packet_number = packet_number - (timestamp_count - 1); + packet_number--; } return true; } @@ -4364,11 +4350,9 @@ bool QuicFramer::ProcessMessageFrame(QuicDataReader* reader, // static absl::string_view QuicFramer::GetAssociatedDataFromEncryptedPacket( - QuicTransportVersion version, - const QuicEncryptedPacket& encrypted, + QuicTransportVersion version, const QuicEncryptedPacket& encrypted, QuicConnectionIdLength destination_connection_id_length, - QuicConnectionIdLength source_connection_id_length, - bool includes_version, + QuicConnectionIdLength source_connection_id_length, bool includes_version, bool includes_diversification_nonce, QuicPacketNumberLength packet_number_length, QuicVariableLengthIntegerLength retry_token_length_length, @@ -4397,8 +4381,7 @@ void QuicFramer::SetDecrypter(EncryptionLevel level, } void QuicFramer::SetAlternativeDecrypter( - EncryptionLevel level, - std::unique_ptr<QuicDecrypter> decrypter, + EncryptionLevel level, std::unique_ptr<QuicDecrypter> decrypter, bool latch_once_used) { QUICHE_DCHECK_NE(level, decrypter_level_); QUICHE_DCHECK(!version_.KnowsWhichDecrypterToUse()); @@ -4523,10 +4506,8 @@ void QuicFramer::SetInitialObfuscators(QuicConnectionId connection_id) { } size_t QuicFramer::EncryptInPlace(EncryptionLevel level, - QuicPacketNumber packet_number, - size_t ad_len, - size_t total_len, - size_t buffer_len, + QuicPacketNumber packet_number, size_t ad_len, + size_t total_len, size_t buffer_len, char* buffer) { QUICHE_DCHECK(packet_number.IsInitialized()); if (encrypter_[level] == nullptr) { @@ -4568,10 +4549,8 @@ constexpr bool IsLongHeader(uint8_t type_byte) { } // namespace -bool QuicFramer::ApplyHeaderProtection(EncryptionLevel level, - char* buffer, - size_t buffer_len, - size_t ad_len) { +bool QuicFramer::ApplyHeaderProtection(EncryptionLevel level, char* buffer, + size_t buffer_len, size_t ad_len) { QuicDataReader buffer_reader(buffer, buffer_len); QuicDataWriter buffer_writer(buffer_len, buffer); // The sample starts 4 bytes after the start of the packet number. @@ -4615,7 +4594,8 @@ bool QuicFramer::ApplyHeaderProtection(EncryptionLevel level, QuicLongHeaderType header_type; if (IsLongHeader(type_byte)) { bitmask = 0x0f; - if (!GetLongHeaderType(type_byte, &header_type)) { + header_type = GetLongHeaderType(type_byte, version_); + if (header_type == INVALID_PACKET_TYPE) { return false; } } @@ -4781,8 +4761,7 @@ bool QuicFramer::RemoveHeaderProtection(QuicDataReader* reader, size_t QuicFramer::EncryptPayload(EncryptionLevel level, QuicPacketNumber packet_number, - const QuicPacket& packet, - char* buffer, + const QuicPacket& packet, char* buffer, size_t buffer_len) { QUICHE_DCHECK(packet_number.IsInitialized()); if (encrypter_[level] == nullptr) { @@ -4867,8 +4846,7 @@ bool QuicFramer::DecryptPayload(size_t udp_packet_length, absl::string_view encrypted, absl::string_view associated_data, const QuicPacketHeader& header, - char* decrypted_buffer, - size_t buffer_length, + char* decrypted_buffer, size_t buffer_length, size_t* decrypted_length, EncryptionLevel* decrypted_level) { if (!EncryptionLevelIsValid(decrypter_level_)) { @@ -5088,9 +5066,11 @@ size_t QuicFramer::GetIetfAckFrameSize(const QuicAckFrame& frame) { previous_smallest = iter->min(); } - // ECN counts. - if (frame.ecn_counters_populated && - (frame.ect_0_count || frame.ect_1_count || frame.ecn_ce_count)) { + if (UseIetfAckWithReceiveTimestamp(frame)) { + ack_frame_size += GetIetfAckFrameTimestampSize(frame); + } else if (frame.ecn_counters_populated && + (frame.ect_0_count || frame.ect_1_count || frame.ecn_ce_count)) { + // ECN counts. ack_frame_size += QuicDataWriter::GetVarInt62Len(frame.ect_0_count); ack_frame_size += QuicDataWriter::GetVarInt62Len(frame.ect_1_count); ack_frame_size += QuicDataWriter::GetVarInt62Len(frame.ecn_ce_count); @@ -5099,9 +5079,22 @@ size_t QuicFramer::GetIetfAckFrameSize(const QuicAckFrame& frame) { return ack_frame_size; } +size_t QuicFramer::GetIetfAckFrameTimestampSize(const QuicAckFrame& ack) { + QUICHE_DCHECK(!ack.received_packet_times.empty()); + std::string detailed_error; + absl::InlinedVector<AckTimestampRange, 2> timestamp_ranges = + GetAckTimestampRanges(ack, detailed_error); + if (!detailed_error.empty()) { + return 0; + } + + int64_t size = + FrameAckTimestampRanges(ack, timestamp_ranges, /*writer=*/nullptr); + return std::max<int64_t>(0, size); +} + size_t QuicFramer::GetAckFrameSize( - const QuicAckFrame& ack, - QuicPacketNumberLength /*packet_number_length*/) { + const QuicAckFrame& ack, QuicPacketNumberLength /*packet_number_length*/) { QUICHE_DCHECK(!ack.packets.Empty()); size_t ack_size = 0; @@ -5113,7 +5106,8 @@ size_t QuicFramer::GetAckFrameSize( GetMinPacketNumberLength(QuicPacketNumber(ack_info.max_block_length)); ack_size = GetMinAckFrameSize(version_.transport_version, ack, - local_ack_delay_exponent_); + local_ack_delay_exponent_, + UseIetfAckWithReceiveTimestamp(ack)); // First ack block length. ack_size += ack_block_length; if (ack_info.num_ack_blocks != 0) { @@ -5141,8 +5135,7 @@ size_t QuicFramer::GetAckFrameTimeStampSize(const QuicAckFrame& ack) { } size_t QuicFramer::ComputeFrameLength( - const QuicFrame& frame, - bool last_frame_in_packet, + const QuicFrame& frame, bool last_frame_in_packet, QuicPacketNumberLength packet_number_length) { switch (frame.type) { case STREAM_FRAME: @@ -5370,8 +5363,7 @@ bool QuicFramer::AppendPacketNumber(QuicPacketNumberLength packet_number_length, } // static -bool QuicFramer::AppendStreamId(size_t stream_id_length, - QuicStreamId stream_id, +bool QuicFramer::AppendStreamId(size_t stream_id_length, QuicStreamId stream_id, QuicDataWriter* writer) { if (stream_id_length == 0 || stream_id_length > 4) { QUIC_BUG(quic_bug_10850_77) @@ -5397,8 +5389,7 @@ bool QuicFramer::AppendStreamOffset(size_t offset_length, // static bool QuicFramer::AppendAckBlock(uint8_t gap, QuicPacketNumberLength length_length, - uint64_t length, - QuicDataWriter* writer) { + uint64_t length, QuicDataWriter* writer) { if (length == 0) { if (!IsValidPacketNumberLength(length_length)) { QUIC_BUG(quic_bug_10850_79) @@ -5613,7 +5604,8 @@ bool QuicFramer::AppendAckFrameAndTypeByte(const QuicAckFrame& frame, int32_t available_timestamp_and_ack_block_bytes = writer->capacity() - writer->length() - ack_block_length - GetMinAckFrameSize(version_.transport_version, frame, - local_ack_delay_exponent_) - + local_ack_delay_exponent_, + UseIetfAckWithReceiveTimestamp(frame)) - (new_ack_info.num_ack_blocks != 0 ? kNumberOfAckBlocksSize : 0); QUICHE_DCHECK_LE(0, available_timestamp_and_ack_block_bytes); @@ -5808,6 +5800,172 @@ bool QuicFramer::AppendTimestampsToAckFrame(const QuicAckFrame& frame, return true; } +absl::InlinedVector<QuicFramer::AckTimestampRange, 2> +QuicFramer::GetAckTimestampRanges(const QuicAckFrame& frame, + std::string& detailed_error) const { + detailed_error = ""; + if (frame.received_packet_times.empty()) { + return {}; + } + + absl::InlinedVector<AckTimestampRange, 2> timestamp_ranges; + + for (size_t r = 0; r < std::min<size_t>(max_receive_timestamps_per_ack_, + frame.received_packet_times.size()); + ++r) { + const size_t i = frame.received_packet_times.size() - 1 - r; + const QuicPacketNumber packet_number = frame.received_packet_times[i].first; + const QuicTime receive_timestamp = frame.received_packet_times[i].second; + + if (timestamp_ranges.empty()) { + if (receive_timestamp < creation_time_ || + LargestAcked(frame) < packet_number) { + detailed_error = + "The first packet is either received earlier than framer creation " + "time, or larger than largest acked packet."; + QUIC_BUG(quic_framer_ack_ts_first_packet_bad) + << detailed_error << " receive_timestamp:" << receive_timestamp + << ", framer_creation_time:" << creation_time_ + << ", packet_number:" << packet_number + << ", largest_acked:" << LargestAcked(frame); + return {}; + } + timestamp_ranges.push_back(AckTimestampRange()); + timestamp_ranges.back().gap = LargestAcked(frame) - packet_number; + timestamp_ranges.back().range_begin = i; + timestamp_ranges.back().range_end = i; + continue; + } + + const size_t prev_i = timestamp_ranges.back().range_end; + const QuicPacketNumber prev_packet_number = + frame.received_packet_times[prev_i].first; + const QuicTime prev_receive_timestamp = + frame.received_packet_times[prev_i].second; + + QUIC_DVLOG(3) << "prev_packet_number:" << prev_packet_number + << ", packet_number:" << packet_number; + if (prev_receive_timestamp < receive_timestamp || + prev_packet_number <= packet_number) { + detailed_error = "Packet number and/or receive time not in order."; + QUIC_BUG(quic_framer_ack_ts_packet_out_of_order) + << detailed_error << " packet_number:" << packet_number + << ", receive_timestamp:" << receive_timestamp + << ", prev_packet_number:" << prev_packet_number + << ", prev_receive_timestamp:" << prev_receive_timestamp; + return {}; + } + + if (prev_packet_number == packet_number + 1) { + timestamp_ranges.back().range_end = i; + } else { + timestamp_ranges.push_back(AckTimestampRange()); + timestamp_ranges.back().gap = prev_packet_number - 2 - packet_number; + timestamp_ranges.back().range_begin = i; + timestamp_ranges.back().range_end = i; + } + } + + return timestamp_ranges; +} + +int64_t QuicFramer::FrameAckTimestampRanges( + const QuicAckFrame& frame, + const absl::InlinedVector<AckTimestampRange, 2>& timestamp_ranges, + QuicDataWriter* writer) const { + int64_t size = 0; + auto maybe_write_var_int62 = [&](uint64_t value) { + size += QuicDataWriter::GetVarInt62Len(value); + if (writer != nullptr && !writer->WriteVarInt62(value)) { + return false; + } + return true; + }; + + if (!maybe_write_var_int62(timestamp_ranges.size())) { + return -1; + } + + // |effective_prev_time| is the exponent-encoded timestamp of the previous + // packet. + absl::optional<QuicTime> effective_prev_time; + for (const AckTimestampRange& range : timestamp_ranges) { + QUIC_DVLOG(3) << "Range: gap:" << range.gap << ", beg:" << range.range_begin + << ", end:" << range.range_end; + if (!maybe_write_var_int62(range.gap)) { + return -1; + } + + if (!maybe_write_var_int62(range.range_begin - range.range_end + 1)) { + return -1; + } + + for (int64_t i = range.range_begin; i >= range.range_end; --i) { + const QuicTime receive_timestamp = frame.received_packet_times[i].second; + uint64_t time_delta; + if (effective_prev_time.has_value()) { + time_delta = + (*effective_prev_time - receive_timestamp).ToMicroseconds(); + QUIC_DVLOG(3) << "time_delta:" << time_delta + << ", exponent:" << receive_timestamps_exponent_ + << ", effective_prev_time:" << *effective_prev_time + << ", recv_time:" << receive_timestamp; + time_delta = time_delta >> receive_timestamps_exponent_; + effective_prev_time = effective_prev_time.value() - + QuicTime::Delta::FromMicroseconds( + time_delta << receive_timestamps_exponent_); + } else { + // The first delta is from framer creation to the current receive + // timestamp (forward in time), whereas in the common case subsequent + // deltas move backwards in time. + time_delta = (receive_timestamp - creation_time_).ToMicroseconds(); + QUIC_DVLOG(3) << "First time_delta:" << time_delta + << ", exponent:" << receive_timestamps_exponent_ + << ", recv_time:" << receive_timestamp + << ", creation_time:" << creation_time_; + // Round up the first exponent-encoded time delta so that the next + // receive timestamp is guaranteed to be decreasing. + time_delta = ((time_delta - 1) >> receive_timestamps_exponent_) + 1; + effective_prev_time = + creation_time_ + QuicTime::Delta::FromMicroseconds( + time_delta << receive_timestamps_exponent_); + } + + if (!maybe_write_var_int62(time_delta)) { + return -1; + } + } + } + + return size; +} + +bool QuicFramer::AppendIetfTimestampsToAckFrame(const QuicAckFrame& frame, + QuicDataWriter* writer) { + QUICHE_DCHECK(!frame.received_packet_times.empty()); + std::string detailed_error; + const absl::InlinedVector<AckTimestampRange, 2> timestamp_ranges = + GetAckTimestampRanges(frame, detailed_error); + if (!detailed_error.empty()) { + set_detailed_error(std::move(detailed_error)); + return false; + } + + // Compute the size first using a null writer. + int64_t size = + FrameAckTimestampRanges(frame, timestamp_ranges, /*writer=*/nullptr); + if (size > static_cast<int64_t>(writer->capacity() - writer->length())) { + QUIC_DVLOG(1) << "Insufficient room to write IETF ack receive timestamps. " + "size_remain:" + << (writer->capacity() - writer->length()) + << ", size_needed:" << size; + // Write a Timestamp Range Count of 0. + return writer->WriteVarInt62(0); + } + + return FrameAckTimestampRanges(frame, timestamp_ranges, writer) > 0; +} + bool QuicFramer::AppendStopWaitingFrame(const QuicPacketHeader& header, const QuicStopWaitingFrame& frame, QuicDataWriter* writer) { @@ -5845,8 +6003,10 @@ bool QuicFramer::AppendIetfAckFrameAndTypeByte(const QuicAckFrame& frame, QuicDataWriter* writer) { uint8_t type = IETF_ACK; uint64_t ecn_size = 0; - if (frame.ecn_counters_populated && - (frame.ect_0_count || frame.ect_1_count || frame.ecn_ce_count)) { + if (UseIetfAckWithReceiveTimestamp(frame)) { + type = IETF_ACK_RECEIVE_TIMESTAMPS; + } else if (frame.ecn_counters_populated && + (frame.ect_0_count || frame.ect_1_count || frame.ecn_ce_count)) { // Change frame type to ACK_ECN if any ECN count is available. type = IETF_ACK_ECN; ecn_size = (QuicDataWriter::GetVarInt62Len(frame.ect_0_count) + @@ -5904,10 +6064,19 @@ bool QuicFramer::AppendIetfAckFrameAndTypeByte(const QuicAckFrame& frame, const uint64_t gap = previous_smallest - iter->max() - 1; const uint64_t ack_range = iter->Length() - 1; - if (writer->remaining() < ecn_size || - writer->remaining() - ecn_size < + if (type == IETF_ACK_RECEIVE_TIMESTAMPS && + writer->remaining() < static_cast<size_t>(QuicDataWriter::GetVarInt62Len(gap) + - QuicDataWriter::GetVarInt62Len(ack_range))) { + QuicDataWriter::GetVarInt62Len(ack_range) + + QuicDataWriter::GetVarInt62Len(0))) { + // If we write this ACK range we won't have space for a timestamp range + // count of 0. + break; + } else if (writer->remaining() < ecn_size || + writer->remaining() - ecn_size < + static_cast<size_t>( + QuicDataWriter::GetVarInt62Len(gap) + + QuicDataWriter::GetVarInt62Len(ack_range))) { // ACK range does not fit, truncate it. break; } @@ -5951,6 +6120,12 @@ bool QuicFramer::AppendIetfAckFrameAndTypeByte(const QuicAckFrame& frame, } } + if (type == IETF_ACK_RECEIVE_TIMESTAMPS) { + if (!AppendIetfTimestampsToAckFrame(frame, writer)) { + return false; + } + } + return true; } @@ -5976,8 +6151,7 @@ bool QuicFramer::AppendRstStreamFrame(const QuicRstStreamFrame& frame, } bool QuicFramer::AppendConnectionCloseFrame( - const QuicConnectionCloseFrame& frame, - QuicDataWriter* writer) { + const QuicConnectionCloseFrame& frame, QuicDataWriter* writer) { if (VersionHasIetfQuicFrames(version_.transport_version)) { return AppendIetfConnectionCloseFrame(frame, writer); } @@ -6084,8 +6258,7 @@ bool QuicFramer::RaiseError(QuicErrorCode error) { } bool QuicFramer::IsVersionNegotiation( - const QuicPacketHeader& header, - bool packet_has_ietf_packet_header) const { + const QuicPacketHeader& header, bool packet_has_ietf_packet_header) const { if (!packet_has_ietf_packet_header && perspective_ == Perspective::IS_CLIENT) { return header.version_flag; @@ -6097,8 +6270,7 @@ bool QuicFramer::IsVersionNegotiation( } bool QuicFramer::AppendIetfConnectionCloseFrame( - const QuicConnectionCloseFrame& frame, - QuicDataWriter* writer) { + const QuicConnectionCloseFrame& frame, QuicDataWriter* writer) { if (frame.close_type != IETF_QUIC_TRANSPORT_CONNECTION_CLOSE && frame.close_type != IETF_QUIC_APPLICATION_CONNECTION_CLOSE) { QUIC_BUG(quic_bug_10850_90) @@ -6135,8 +6307,7 @@ bool QuicFramer::AppendIetfConnectionCloseFrame( } bool QuicFramer::ProcessIetfConnectionCloseFrame( - QuicDataReader* reader, - QuicConnectionCloseType type, + QuicDataReader* reader, QuicConnectionCloseType type, QuicConnectionCloseFrame* frame) { frame->close_type = type; @@ -6263,8 +6434,7 @@ bool QuicFramer::ProcessIetfResetStreamFrame(QuicDataReader* reader, } bool QuicFramer::ProcessStopSendingFrame( - QuicDataReader* reader, - QuicStopSendingFrame* stop_sending_frame) { + QuicDataReader* reader, QuicStopSendingFrame* stop_sending_frame) { if (!ReadUint32FromVarint62(reader, IETF_STOP_SENDING, &stop_sending_frame->stream_id)) { return false; @@ -6281,8 +6451,7 @@ bool QuicFramer::ProcessStopSendingFrame( } bool QuicFramer::AppendStopSendingFrame( - const QuicStopSendingFrame& stop_sending_frame, - QuicDataWriter* writer) { + const QuicStopSendingFrame& stop_sending_frame, QuicDataWriter* writer) { if (!writer->WriteVarInt62(stop_sending_frame.stream_id)) { set_detailed_error("Can not write stop sending stream id"); return false; @@ -6438,8 +6607,7 @@ bool QuicFramer::ProcessStreamsBlockedFrame(QuicDataReader* reader, } bool QuicFramer::AppendNewConnectionIdFrame( - const QuicNewConnectionIdFrame& frame, - QuicDataWriter* writer) { + const QuicNewConnectionIdFrame& frame, QuicDataWriter* writer) { if (!writer->WriteVarInt62(frame.sequence_number)) { set_detailed_error("Can not write New Connection ID sequence number"); return false; @@ -6500,8 +6668,7 @@ bool QuicFramer::ProcessNewConnectionIdFrame(QuicDataReader* reader, } bool QuicFramer::AppendRetireConnectionIdFrame( - const QuicRetireConnectionIdFrame& frame, - QuicDataWriter* writer) { + const QuicRetireConnectionIdFrame& frame, QuicDataWriter* writer) { if (!writer->WriteVarInt62(frame.sequence_number)) { set_detailed_error("Can not write Retire Connection ID sequence number"); return false; @@ -6510,8 +6677,7 @@ bool QuicFramer::AppendRetireConnectionIdFrame( } bool QuicFramer::ProcessRetireConnectionIdFrame( - QuicDataReader* reader, - QuicRetireConnectionIdFrame* frame) { + QuicDataReader* reader, QuicRetireConnectionIdFrame* frame) { if (!reader->ReadVarInt62(&frame->sequence_number)) { set_detailed_error( "Unable to read retire connection ID frame sequence number."); @@ -6567,8 +6733,7 @@ uint8_t QuicFramer::GetStreamFrameTypeByte(const QuicStreamFrame& frame, } uint8_t QuicFramer::GetIetfStreamFrameTypeByte( - const QuicStreamFrame& frame, - bool last_frame_in_packet) const { + const QuicStreamFrame& frame, bool last_frame_in_packet) const { QUICHE_DCHECK(VersionHasIetfQuicFrames(version_.transport_version)); uint8_t type_byte = IETF_STREAM; if (!last_frame_in_packet) { @@ -6662,14 +6827,10 @@ QuicErrorCode QuicFramer::ParsePublicHeaderDispatcher( // static QuicErrorCode QuicFramer::ParsePublicHeaderGoogleQuic( - QuicDataReader* reader, - uint8_t* first_byte, - PacketHeaderFormat* format, - bool* version_present, - QuicVersionLabel* version_label, + QuicDataReader* reader, uint8_t* first_byte, PacketHeaderFormat* format, + bool* version_present, QuicVersionLabel* version_label, ParsedQuicVersion* parsed_version, - QuicConnectionId* destination_connection_id, - std::string* detailed_error) { + QuicConnectionId* destination_connection_id, std::string* detailed_error) { *format = GOOGLE_QUIC_PACKET; *version_present = (*first_byte & PACKET_PUBLIC_FLAGS_VERSION) != 0; uint8_t destination_connection_id_length = 0; @@ -6696,10 +6857,8 @@ namespace { const QuicVersionLabel kProxVersionLabel = 0x50524F58; // "PROX" inline bool PacketHasLengthPrefixedConnectionIds( - const QuicDataReader& reader, - ParsedQuicVersion parsed_version, - QuicVersionLabel version_label, - uint8_t first_byte) { + const QuicDataReader& reader, ParsedQuicVersion parsed_version, + QuicVersionLabel version_label, uint8_t first_byte) { if (parsed_version.IsKnown()) { return parsed_version.HasLengthPrefixedConnectionIds(); } @@ -6733,18 +6892,15 @@ inline bool PacketHasLengthPrefixedConnectionIds( } inline bool ParseLongHeaderConnectionIds( - QuicDataReader* reader, - bool has_length_prefix, - QuicVersionLabel version_label, - QuicConnectionId* destination_connection_id, - QuicConnectionId* source_connection_id, - std::string* detailed_error) { + QuicDataReader& reader, bool has_length_prefix, + QuicVersionLabel version_label, QuicConnectionId& destination_connection_id, + QuicConnectionId& source_connection_id, std::string& detailed_error) { if (has_length_prefix) { - if (!reader->ReadLengthPrefixedConnectionId(destination_connection_id)) { - *detailed_error = "Unable to read destination connection ID."; + if (!reader.ReadLengthPrefixedConnectionId(&destination_connection_id)) { + detailed_error = "Unable to read destination connection ID."; return false; } - if (!reader->ReadLengthPrefixedConnectionId(source_connection_id)) { + if (!reader.ReadLengthPrefixedConnectionId(&source_connection_id)) { if (version_label == kProxVersionLabel) { // The "PROX" version does not follow the length-prefixed invariants, // and can therefore attempt to read a payload byte and interpret it @@ -6753,14 +6909,14 @@ inline bool ParseLongHeaderConnectionIds( // parsing as successful. return true; } - *detailed_error = "Unable to read source connection ID."; + detailed_error = "Unable to read source connection ID."; return false; } } else { // Parse connection ID lengths. uint8_t connection_id_lengths_byte; - if (!reader->ReadUInt8(&connection_id_lengths_byte)) { - *detailed_error = "Unable to read connection ID lengths."; + if (!reader.ReadUInt8(&connection_id_lengths_byte)) { + detailed_error = "Unable to read connection ID lengths."; return false; } uint8_t destination_connection_id_length = @@ -6775,16 +6931,16 @@ inline bool ParseLongHeaderConnectionIds( } // Read destination connection ID. - if (!reader->ReadConnectionId(destination_connection_id, - destination_connection_id_length)) { - *detailed_error = "Unable to read destination connection ID."; + if (!reader.ReadConnectionId(&destination_connection_id, + destination_connection_id_length)) { + detailed_error = "Unable to read destination connection ID."; return false; } // Read source connection ID. - if (!reader->ReadConnectionId(source_connection_id, - source_connection_id_length)) { - *detailed_error = "Unable to read source connection ID."; + if (!reader.ReadConnectionId(&source_connection_id, + source_connection_id_length)) { + detailed_error = "Unable to read source connection ID."; return false; } } @@ -6795,21 +6951,15 @@ inline bool ParseLongHeaderConnectionIds( // static QuicErrorCode QuicFramer::ParsePublicHeader( - QuicDataReader* reader, - uint8_t expected_destination_connection_id_length, - bool ietf_format, - uint8_t* first_byte, - PacketHeaderFormat* format, - bool* version_present, - bool* has_length_prefix, - QuicVersionLabel* version_label, - ParsedQuicVersion* parsed_version, + QuicDataReader* reader, uint8_t expected_destination_connection_id_length, + bool ietf_format, uint8_t* first_byte, PacketHeaderFormat* format, + bool* version_present, bool* has_length_prefix, + QuicVersionLabel* version_label, ParsedQuicVersion* parsed_version, QuicConnectionId* destination_connection_id, QuicConnectionId* source_connection_id, QuicLongHeaderType* long_packet_type, QuicVariableLengthIntegerLength* retry_token_length_length, - absl::string_view* retry_token, - std::string* detailed_error) { + absl::string_view* retry_token, std::string* detailed_error) { *version_present = false; *has_length_prefix = false; *version_label = 0; @@ -6863,9 +7013,9 @@ QuicErrorCode QuicFramer::ParsePublicHeader( *reader, *parsed_version, *version_label, *first_byte); // Parse connection IDs. - if (!ParseLongHeaderConnectionIds(reader, *has_length_prefix, *version_label, - destination_connection_id, - source_connection_id, detailed_error)) { + if (!ParseLongHeaderConnectionIds(*reader, *has_length_prefix, *version_label, + *destination_connection_id, + *source_connection_id, *detailed_error)) { return QUIC_INVALID_PACKET_HEADER; } @@ -6875,14 +7025,20 @@ QuicErrorCode QuicFramer::ParsePublicHeader( } // Parse long packet type. - if (!GetLongHeaderType(*first_byte, long_packet_type)) { - *detailed_error = "Unable to parse long packet type."; - return QUIC_INVALID_PACKET_HEADER; - } + *long_packet_type = GetLongHeaderType(*first_byte, *parsed_version); - if (!parsed_version->SupportsRetry() || *long_packet_type != INITIAL) { - // Retry token is only present on initial packets for some versions. - return QUIC_NO_ERROR; + switch (*long_packet_type) { + case INVALID_PACKET_TYPE: + *detailed_error = "Unable to parse long packet type."; + return QUIC_INVALID_PACKET_HEADER; + case INITIAL: + if (!parsed_version->SupportsRetry()) { + // Retry token is only present on initial packets for some versions. + return QUIC_NO_ERROR; + } + break; + default: + return QUIC_NO_ERROR; } *retry_token_length_length = reader->PeekVarInt62Length(); @@ -6903,8 +7059,7 @@ QuicErrorCode QuicFramer::ParsePublicHeader( // static bool QuicFramer::WriteClientVersionNegotiationProbePacket( - char* packet_bytes, - QuicByteCount packet_length, + char* packet_bytes, QuicByteCount packet_length, const char* destination_connection_id_bytes, uint8_t destination_connection_id_length) { if (packet_bytes == nullptr) { @@ -6981,10 +7136,8 @@ bool QuicFramer::WriteClientVersionNegotiationProbePacket( // static bool QuicFramer::ParseServerVersionNegotiationProbeResponse( - const char* packet_bytes, - QuicByteCount packet_length, - char* source_connection_id_bytes, - uint8_t* source_connection_id_length_out, + const char* packet_bytes, QuicByteCount packet_length, + char* source_connection_id_bytes, uint8_t* source_connection_id_length_out, std::string* detailed_error) { if (detailed_error == nullptr) { QUIC_BUG(quic_bug_10850_101) << "Invalid error_details"; 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 287888850b4..306f0491d58 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 @@ -315,8 +315,15 @@ class QUIC_EXPORT_PRIVATE QuicFramer { process_timestamps_ = process_timestamps; } + // Sets the max number of receive timestamps to send per ACK frame. + // TODO(wub): Remove the const once timestamps are negotiated via + // transport params. + void set_max_receive_timestamps_per_ack(uint32_t max_timestamps) const { + max_receive_timestamps_per_ack_ = max_timestamps; + } + // Sets the exponent to use when writing/reading ACK receive timestamps. - void set_receive_timestamps_exponent(uint32_t exponent) { + void set_receive_timestamps_exponent(uint32_t exponent) const { receive_timestamps_exponent_ = exponent; } @@ -347,7 +354,8 @@ class QUIC_EXPORT_PRIVATE QuicFramer { // blocks. static size_t GetMinAckFrameSize(QuicTransportVersion version, const QuicAckFrame& ack_frame, - uint32_t local_ack_delay_exponent); + uint32_t local_ack_delay_exponent, + bool use_ietf_ack_with_receive_timestamp); // Size in bytes of a stop waiting frame. static size_t GetStopWaitingFrameSize( QuicPacketNumberLength packet_number_length); @@ -521,8 +529,7 @@ class QUIC_EXPORT_PRIVATE QuicFramer { QuicDataWriter* writer); size_t AppendIetfFrames(const QuicFrames& frames, QuicDataWriter* writer); bool AppendStreamFrame(const QuicStreamFrame& frame, - bool last_frame_in_packet, - QuicDataWriter* writer); + bool no_stream_frame_length, QuicDataWriter* writer); bool AppendCryptoFrame(const QuicCryptoFrame& frame, QuicDataWriter* writer); bool AppendAckFrequencyFrame(const QuicAckFrequencyFrame& frame, QuicDataWriter* writer); @@ -728,6 +735,23 @@ class QUIC_EXPORT_PRIVATE QuicFramer { using NackRangeMap = std::map<QuicPacketNumber, uint8_t>; + // AckTimestampRange is a data structure derived from a QuicAckFrame. It is + // used to serialize timestamps in a IETF_ACK_RECEIVE_TIMESTAMPS frame. + struct QUIC_EXPORT_PRIVATE AckTimestampRange { + QuicPacketCount gap; + // |range_begin| and |range_end| are index(es) in + // QuicAckFrame.received_packet_times, representing a continuous range of + // packet numbers in descending order. |range_begin| >= |range_end|. + int64_t range_begin; // Inclusive + int64_t range_end; // Inclusive + }; + absl::InlinedVector<AckTimestampRange, 2> GetAckTimestampRanges( + const QuicAckFrame& frame, std::string& detailed_error) const; + int64_t FrameAckTimestampRanges( + const QuicAckFrame& frame, + const absl::InlinedVector<AckTimestampRange, 2>& timestamp_ranges, + QuicDataWriter* writer) const; + struct QUIC_EXPORT_PRIVATE AckFrameInfo { AckFrameInfo(); AckFrameInfo(const AckFrameInfo& other); @@ -896,6 +920,7 @@ class QUIC_EXPORT_PRIVATE QuicFramer { // Computes the wire size in bytes of time stamps in |ack|. size_t GetAckFrameTimeStampSize(const QuicAckFrame& ack); + size_t GetIetfAckFrameTimestampSize(const QuicAckFrame& ack); // Computes the wire size in bytes of the |ack| frame. size_t GetAckFrameSize(const QuicAckFrame& ack, @@ -949,7 +974,7 @@ class QUIC_EXPORT_PRIVATE QuicFramer { // |writer|, and return true if successful. bool AppendAckFrameAndTypeByte(const QuicAckFrame& frame, - QuicDataWriter* builder); + QuicDataWriter* writer); bool AppendTimestampsToAckFrame(const QuicAckFrame& frame, QuicDataWriter* writer); @@ -959,14 +984,16 @@ class QUIC_EXPORT_PRIVATE QuicFramer { // of the frame. bool AppendIetfAckFrameAndTypeByte(const QuicAckFrame& frame, QuicDataWriter* writer); + bool AppendIetfTimestampsToAckFrame(const QuicAckFrame& frame, + QuicDataWriter* writer); bool AppendStopWaitingFrame(const QuicPacketHeader& header, const QuicStopWaitingFrame& frame, - QuicDataWriter* builder); + QuicDataWriter* writer); bool AppendRstStreamFrame(const QuicRstStreamFrame& frame, - QuicDataWriter* builder); + QuicDataWriter* writer); bool AppendConnectionCloseFrame(const QuicConnectionCloseFrame& frame, - QuicDataWriter* builder); + QuicDataWriter* writer); bool AppendGoAwayFrame(const QuicGoAwayFrame& frame, QuicDataWriter* writer); bool AppendWindowUpdateFrame(const QuicWindowUpdateFrame& frame, QuicDataWriter* writer); @@ -1081,6 +1108,15 @@ class QUIC_EXPORT_PRIVATE QuicFramer { bool ProcessPacketInternal(const QuicEncryptedPacket& packet); + // Determine whether the given QuicAckFrame should be serialized with a + // IETF_ACK_RECEIVE_TIMESTAMPS frame type. + bool UseIetfAckWithReceiveTimestamp(const QuicAckFrame& frame) const { + return VersionHasIetfQuicFrames(version_.transport_version) && + process_timestamps_ && + std::min<uint64_t>(max_receive_timestamps_per_ack_, + frame.received_packet_times.size()) > 0; + } + std::string detailed_error_; QuicFramerVisitorInterface* visitor_; QuicErrorCode error_; @@ -1122,10 +1158,13 @@ class QUIC_EXPORT_PRIVATE QuicFramer { // The diversification nonce from the last received packet. DiversificationNonce last_nonce_; // If true, send and process timestamps in the ACK frame. - // TODO(ianswett): Remove the mutable once set_process_timestamps isn't const. + // TODO(ianswett): Remove the mutables once set_process_timestamps and + // set_receive_timestamp_exponent_ aren't const. mutable bool process_timestamps_; + // The max number of receive timestamps to send per ACK frame. + mutable uint32_t max_receive_timestamps_per_ack_; // The exponent to use when writing/reading ACK receive timestamps. - uint32_t receive_timestamps_exponent_; + mutable uint32_t receive_timestamps_exponent_; // The creation time of the connection, used to calculate timestamps. QuicTime creation_time_; // The last timestamp received if process_timestamps_ is true. 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 28b7c20345a..d24664dbded 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 @@ -38,6 +38,8 @@ using testing::_; using testing::ContainerEq; +using testing::Eq; +using testing::IsNull; using testing::Return; namespace quic { @@ -105,10 +107,8 @@ class TestEncrypter : public QuicEncrypter { bool SetHeaderProtectionKey(absl::string_view /*key*/) override { return true; } - bool EncryptPacket(uint64_t packet_number, - absl::string_view associated_data, - absl::string_view plaintext, - char* output, + bool EncryptPacket(uint64_t packet_number, absl::string_view associated_data, + absl::string_view plaintext, char* output, size_t* output_length, size_t /*max_output_length*/) override { packet_number_ = QuicPacketNumber(packet_number); @@ -162,10 +162,8 @@ class TestDecrypter : public QuicDecrypter { bool SetDiversificationNonce(const DiversificationNonce& /*key*/) override { return true; } - bool DecryptPacket(uint64_t packet_number, - absl::string_view associated_data, - absl::string_view ciphertext, - char* output, + bool DecryptPacket(uint64_t packet_number, absl::string_view associated_data, + absl::string_view ciphertext, char* output, size_t* output_length, size_t /*max_output_length*/) override { packet_number_ = QuicPacketNumber(packet_number); @@ -197,9 +195,7 @@ class TestDecrypter : public QuicDecrypter { }; std::unique_ptr<QuicEncryptedPacket> EncryptPacketWithTagAndPhase( - const QuicPacket& packet, - uint8_t tag, - bool phase) { + const QuicPacket& packet, uint8_t tag, bool phase) { std::string packet_data = std::string(packet.AsStringPiece()); if (phase) { packet_data[0] |= FLAGS_KEY_PHASE_BIT; @@ -692,9 +688,7 @@ class QuicFramerTest : public QuicTestWithParam<ParsedQuicVersion> { decrypter_(new test::TestDecrypter()), version_(GetParam()), start_(QuicTime::Zero() + QuicTime::Delta::FromMicroseconds(0x10)), - framer_(AllSupportedVersions(), - start_, - Perspective::IS_SERVER, + framer_(AllSupportedVersions(), start_, Perspective::IS_SERVER, kQuicDefaultConnectionIdLength) { framer_.set_version(version_); if (framer_.version().KnowsWhichDecrypterToUse()) { @@ -726,6 +720,19 @@ class QuicFramerTest : public QuicTestWithParam<ParsedQuicVersion> { return (CreateQuicVersionLabel(version_) >> 8 * (3 - pos)) & 0xff; } + // Helper functions to take a v1 long header packet and make it v2. These are + // not needed for short header packets, but if sent, this function will exit + // cleanly. It needs to be called twice for coalesced packets (see references + // to length_of_first_coalesced_packet below for examples of how to do this). + inline void ReviseFirstByteByVersion(unsigned char packet_ietf[]) { + if (version_.UsesV2PacketTypes() && (packet_ietf[0] >= 0x80)) { + packet_ietf[0] = (packet_ietf[0] + 0x10) | 0xc0; + } + } + inline void ReviseFirstByteByVersion(PacketFragments& packet_ietf) { + ReviseFirstByteByVersion(&packet_ietf[0].fragment[0]); + } + bool CheckEncryption(QuicPacketNumber packet_number, QuicPacket* packet) { if (packet_number != encrypter_->packet_number_) { QUIC_LOG(ERROR) << "Encrypted incorrect packet number. expected " @@ -762,8 +769,7 @@ class QuicFramerTest : public QuicTestWithParam<ParsedQuicVersion> { } bool CheckDecryption( - const QuicEncryptedPacket& encrypted, - bool includes_version, + const QuicEncryptedPacket& encrypted, bool includes_version, bool includes_diversification_nonce, QuicConnectionIdLength destination_connection_id_length, QuicConnectionIdLength source_connection_id_length, @@ -838,15 +844,13 @@ class QuicFramerTest : public QuicTestWithParam<ParsedQuicVersion> { } } - if (expected_error.empty()) - continue; + if (expected_error.empty()) continue; CheckProcessingFails(*packet, i, expected_error, error_code); } } - void CheckProcessingFails(const QuicEncryptedPacket& packet, - size_t len, + void CheckProcessingFails(const QuicEncryptedPacket& packet, size_t len, std::string expected_error, QuicErrorCode error_code) { QuicEncryptedPacket encrypted(packet.data(), len, false); @@ -855,8 +859,7 @@ class QuicFramerTest : public QuicTestWithParam<ParsedQuicVersion> { EXPECT_EQ(error_code, framer_.error()) << "len: " << len; } - void CheckProcessingFails(unsigned char* packet, - size_t len, + void CheckProcessingFails(unsigned char* packet, size_t len, std::string expected_error, QuicErrorCode error_code) { QuicEncryptedPacket encrypted(AsChars(packet), len, false); @@ -894,8 +897,7 @@ class QuicFramerTest : public QuicTestWithParam<ParsedQuicVersion> { // N starts at 1. QuicStreamId GetNthStreamid(QuicTransportVersion transport_version, - Perspective perspective, - bool bidirectional, + Perspective perspective, bool bidirectional, int n) { if (bidirectional) { return QuicUtils::GetFirstBidirectionalStreamId(transport_version, @@ -931,8 +933,7 @@ class QuicFramerTest : public QuicTestWithParam<ParsedQuicVersion> { GetQuicVersionByte(3) // Run all framer tests with all supported versions of QUIC. -INSTANTIATE_TEST_SUITE_P(QuicFramerTests, - QuicFramerTest, +INSTANTIATE_TEST_SUITE_P(QuicFramerTests, QuicFramerTest, ::testing::ValuesIn(AllSupportedVersions()), ::testing::PrintToStringParamName()); @@ -1273,6 +1274,7 @@ TEST_P(QuicFramerTest, LongPacketHeaderWithBothConnectionIds) { unsigned char* p = packet; size_t p_length = ABSL_ARRAYSIZE(packet); if (framer_.version().HasLongHeaderLengths()) { + ReviseFirstByteByVersion(packet49); p = packet49; p_length = ABSL_ARRAYSIZE(packet49); } @@ -1379,6 +1381,7 @@ TEST_P(QuicFramerTest, ParsePublicHeader) { unsigned char* p = packet; size_t p_length = ABSL_ARRAYSIZE(packet); if (framer_.version().HasLongHeaderLengths()) { + ReviseFirstByteByVersion(packet49); p = packet49; p_length = ABSL_ARRAYSIZE(packet49); } else if (framer_.version().HasIetfInvariantHeader()) { @@ -1669,6 +1672,7 @@ TEST_P(QuicFramerTest, PacketHeaderWithVersionFlag) { }; // clang-format on + ReviseFirstByteByVersion(packet49); PacketFragments& fragments = framer_.version().HasLongHeaderLengths() ? packet49 @@ -2970,6 +2974,7 @@ TEST_P(QuicFramerTest, StreamFrameWithVersion) { ? VARIABLE_LENGTH_INTEGER_LENGTH_1 : VARIABLE_LENGTH_INTEGER_LENGTH_0; + ReviseFirstByteByVersion(packet_ietf); PacketFragments& fragments = VersionHasIetfQuicFrames(framer_.transport_version()) ? packet_ietf @@ -4113,7 +4118,7 @@ TEST_P(QuicFramerTest, AckFrameMultipleReceiveTimestampRanges) { // Timestamp range 2 (one packet). { "Unable to read receive timestamp gap.", - { kVarInt62OneByte + 0x07 }}, + { kVarInt62OneByte + 0x05 }}, { "Unable to read receive timestamp count.", { kVarInt62OneByte + 0x01 }}, { "Unable to read receive timestamp delta.", @@ -4121,7 +4126,7 @@ TEST_P(QuicFramerTest, AckFrameMultipleReceiveTimestampRanges) { // Timestamp range 3 (two packets). { "Unable to read receive timestamp gap.", - { kVarInt62OneByte + 0x0a }}, + { kVarInt62OneByte + 0x08 }}, { "Unable to read receive timestamp count.", { kVarInt62OneByte + 0x02 }}, { "Unable to read receive timestamp delta.", @@ -6420,6 +6425,7 @@ TEST_P(QuicFramerTest, ParseIetfRetryPacket) { unsigned char* p = packet; size_t p_length = ABSL_ARRAYSIZE(packet); if (framer_.version().UsesTls()) { + ReviseFirstByteByVersion(packet_with_tag); p = packet_with_tag; p_length = ABSL_ARRAYSIZE(packet_with_tag); } else if (framer_.version().HasLongHeaderLengths()) { @@ -7115,6 +7121,7 @@ TEST_P(QuicFramerTest, BuildStreamFramePacketWithVersionFlag) { unsigned char* p = packet; size_t p_size = ABSL_ARRAYSIZE(packet); if (VersionHasIetfQuicFrames(framer_.transport_version())) { + ReviseFirstByteByVersion(packet_ietf); p = packet_ietf; p_size = ABSL_ARRAYSIZE(packet_ietf); } else if (framer_.version().HasLongHeaderLengths()) { @@ -7483,6 +7490,706 @@ TEST_P(QuicFramerTest, BuildAckFramePacketOneAckBlock) { "constructed packet", data->data(), data->length(), AsChars(p), p_size); } +TEST_P(QuicFramerTest, BuildAckReceiveTimestampsFrameMultipleRanges) { + if (!VersionHasIetfQuicFrames(framer_.transport_version())) { + return; + } + + QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT); + QuicPacketHeader header; + header.destination_connection_id = FramerTestConnectionId(); + header.reset_flag = false; + header.version_flag = false; + header.packet_number = kPacketNumber; + + QuicAckFrame ack_frame = InitAckFrame(kSmallLargestObserved); + ack_frame.received_packet_times = PacketTimeVector{ + // Timestamp Range 3. + {kSmallLargestObserved - 22, CreationTimePlus(0x29ffdddd)}, + {kSmallLargestObserved - 21, CreationTimePlus(0x29ffdedd)}, + // Timestamp Range 2. + {kSmallLargestObserved - 11, CreationTimePlus(0x29ffdeed)}, + // Timestamp Range 1. + {kSmallLargestObserved - 4, CreationTimePlus(0x29ffeeed)}, + {kSmallLargestObserved - 3, CreationTimePlus(0x29ffeeee)}, + {kSmallLargestObserved - 2, CreationTimePlus(0x29ffffff)}, + }; + ack_frame.ack_delay_time = QuicTime::Delta::Zero(); + QuicFrames frames = {QuicFrame(&ack_frame)}; + + unsigned char packet_ietf[] = { + // type (short header, 4 byte packet number) + 0x43, + // connection_id + 0xFE, + 0xDC, + 0xBA, + 0x98, + 0x76, + 0x54, + 0x32, + 0x10, + // packet number + 0x12, + 0x34, + 0x56, + 0x78, + + // frame type (IETF_ACK_RECEIVE_TIMESTAMPS frame) + 0x22, + // largest acked + kVarInt62TwoBytes + 0x12, + 0x34, // = 4660 + // Zero delta time. + kVarInt62OneByte + 0x00, + // number of additional ack blocks + kVarInt62OneByte + 0x00, + // first ack block length. + kVarInt62TwoBytes + 0x12, + 0x33, + + // Receive Timestamps. + + // Timestamp Range Count + kVarInt62OneByte + 0x03, + + // Timestamp range 1 (three packets). + // Gap + kVarInt62OneByte + 0x02, + // Timestamp Range Count + kVarInt62OneByte + 0x03, + // Timestamp Delta + kVarInt62FourBytes + 0x29, + 0xff, + 0xff, + 0xff, + // Timestamp Delta + kVarInt62TwoBytes + 0x11, + 0x11, + // Timestamp Delta + kVarInt62OneByte + 0x01, + + // Timestamp range 2 (one packet). + // Gap + kVarInt62OneByte + 0x05, + // Timestamp Range Count + kVarInt62OneByte + 0x01, + // Timestamp Delta + kVarInt62TwoBytes + 0x10, + 0x00, + + // Timestamp range 3 (two packets). + // Gap + kVarInt62OneByte + 0x08, + // Timestamp Range Count + kVarInt62OneByte + 0x02, + // Timestamp Delta + kVarInt62OneByte + 0x10, + // Timestamp Delta + kVarInt62TwoBytes + 0x01, + 0x00, + }; + // clang-format on + + framer_.set_process_timestamps(true); + framer_.set_max_receive_timestamps_per_ack(8); + std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames)); + ASSERT_TRUE(data != nullptr); + quiche::test::CompareCharArraysWithHexError( + "constructed packet", data->data(), data->length(), AsChars(packet_ietf), + ABSL_ARRAYSIZE(packet_ietf)); +} + +TEST_P(QuicFramerTest, BuildAckReceiveTimestampsFrameExceedsMaxTimestamps) { + if (!VersionHasIetfQuicFrames(framer_.transport_version())) { + return; + } + + QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT); + QuicPacketHeader header; + header.destination_connection_id = FramerTestConnectionId(); + header.reset_flag = false; + header.version_flag = false; + header.packet_number = kPacketNumber; + + QuicAckFrame ack_frame = InitAckFrame(kSmallLargestObserved); + ack_frame.received_packet_times = PacketTimeVector{ + // Timestamp Range 3 (not included because max receive timestamps = 4). + {kSmallLargestObserved - 20, CreationTimePlus(0x29ffdddd)}, + // Timestamp Range 2. + {kSmallLargestObserved - 10, CreationTimePlus(0x29ffdedd)}, + {kSmallLargestObserved - 9, CreationTimePlus(0x29ffdeed)}, + // Timestamp Range 1. + {kSmallLargestObserved - 2, CreationTimePlus(0x29ffeeed)}, + {kSmallLargestObserved - 1, CreationTimePlus(0x29ffeeee)}, + {kSmallLargestObserved, CreationTimePlus(0x29ffffff)}, + }; + ack_frame.ack_delay_time = QuicTime::Delta::Zero(); + QuicFrames frames = {QuicFrame(&ack_frame)}; + + unsigned char packet_ietf[] = { + // type (short header, 4 byte packet number) + 0x43, + // connection_id + 0xFE, + 0xDC, + 0xBA, + 0x98, + 0x76, + 0x54, + 0x32, + 0x10, + // packet number + 0x12, + 0x34, + 0x56, + 0x78, + + // frame type (IETF_ACK_RECEIVE_TIMESTAMPS frame) + 0x22, + // largest acked + kVarInt62TwoBytes + 0x12, + 0x34, // = 4660 + // Zero delta time. + kVarInt62OneByte + 0x00, + // number of additional ack blocks + kVarInt62OneByte + 0x00, + // first ack block length. + kVarInt62TwoBytes + 0x12, + 0x33, + + // Receive Timestamps. + + // Timestamp Range Count + kVarInt62OneByte + 0x02, + + // Timestamp range 1 (three packets). + // Gap + kVarInt62OneByte + 0x00, + // Timestamp Range Count + kVarInt62OneByte + 0x03, + // Timestamp Delta + kVarInt62FourBytes + 0x29, + 0xff, + 0xff, + 0xff, + // Timestamp Delta + kVarInt62TwoBytes + 0x11, + 0x11, + // Timestamp Delta + kVarInt62OneByte + 0x01, + + // Timestamp range 2 (one packet). + // Gap + kVarInt62OneByte + 0x05, + // Timestamp Range Count + kVarInt62OneByte + 0x01, + // Timestamp Delta + kVarInt62TwoBytes + 0x10, + 0x00, + }; + // clang-format on + + framer_.set_process_timestamps(true); + framer_.set_max_receive_timestamps_per_ack(4); + std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames)); + ASSERT_TRUE(data != nullptr); + quiche::test::CompareCharArraysWithHexError( + "constructed packet", data->data(), data->length(), AsChars(packet_ietf), + ABSL_ARRAYSIZE(packet_ietf)); +} + +TEST_P(QuicFramerTest, BuildAckReceiveTimestampsFrameWithExponentEncoding) { + if (!VersionHasIetfQuicFrames(framer_.transport_version())) { + return; + } + + QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT); + QuicPacketHeader header; + header.destination_connection_id = FramerTestConnectionId(); + header.reset_flag = false; + header.version_flag = false; + header.packet_number = kPacketNumber; + + QuicAckFrame ack_frame = InitAckFrame(kSmallLargestObserved); + ack_frame.received_packet_times = PacketTimeVector{ + // Timestamp Range 2. + {kSmallLargestObserved - 12, CreationTimePlus((0x06c00 << 3) + 0x03)}, + {kSmallLargestObserved - 11, CreationTimePlus((0x28e00 << 3) + 0x00)}, + // Timestamp Range 1. + {kSmallLargestObserved - 5, CreationTimePlus((0x29f00 << 3) + 0x00)}, + {kSmallLargestObserved - 4, CreationTimePlus((0x29f00 << 3) + 0x01)}, + {kSmallLargestObserved - 3, CreationTimePlus((0x29f00 << 3) + 0x02)}, + {kSmallLargestObserved - 2, CreationTimePlus((0x29f00 << 3) + 0x03)}, + }; + ack_frame.ack_delay_time = QuicTime::Delta::Zero(); + + QuicFrames frames = {QuicFrame(&ack_frame)}; + + unsigned char packet_ietf[] = { + // type (short header, 4 byte packet number) + 0x43, + // connection_id + 0xFE, + 0xDC, + 0xBA, + 0x98, + 0x76, + 0x54, + 0x32, + 0x10, + // packet number + 0x12, + 0x34, + 0x56, + 0x78, + + // frame type (IETF_ACK_RECEIVE_TIMESTAMPS frame) + 0x22, + // largest acked + kVarInt62TwoBytes + 0x12, + 0x34, // = 4660 + // Zero delta time. + kVarInt62OneByte + 0x00, + // number of additional ack blocks + kVarInt62OneByte + 0x00, + // first ack block length. + kVarInt62TwoBytes + 0x12, + 0x33, + + // Receive Timestamps. + + // Timestamp Range Count + kVarInt62OneByte + 0x02, + + // Timestamp range 1 (three packets). + // Gap + kVarInt62OneByte + 0x02, + // Timestamp Range Count + kVarInt62OneByte + 0x04, + // Timestamp Delta + kVarInt62FourBytes + 0x00, + 0x02, + 0x9f, + 0x01, // round up + // Timestamp Delta + kVarInt62OneByte + 0x00, + // Timestamp Delta + kVarInt62OneByte + 0x00, + // Timestamp Delta + kVarInt62OneByte + 0x01, + + // Timestamp range 2 (one packet). + // Gap + kVarInt62OneByte + 0x04, + // Timestamp Range Count + kVarInt62OneByte + 0x02, + // Timestamp Delta + kVarInt62TwoBytes + 0x11, + 0x00, + // Timestamp Delta + kVarInt62FourBytes + 0x00, + 0x02, + 0x21, + 0xff, + }; + // clang-format on + + framer_.set_process_timestamps(true); + framer_.set_max_receive_timestamps_per_ack(8); + framer_.set_receive_timestamps_exponent(3); + std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames)); + ASSERT_TRUE(data != nullptr); + quiche::test::CompareCharArraysWithHexError( + "constructed packet", data->data(), data->length(), AsChars(packet_ietf), + ABSL_ARRAYSIZE(packet_ietf)); +} + +TEST_P(QuicFramerTest, BuildAndProcessAckReceiveTimestampsWithMultipleRanges) { + if (!VersionHasIetfQuicFrames(framer_.transport_version())) { + return; + } + framer_.InstallDecrypter(ENCRYPTION_FORWARD_SECURE, + std::make_unique<StrictTaggingDecrypter>(/*key=*/0)); + framer_.SetKeyUpdateSupportForConnection(true); + framer_.set_process_timestamps(true); + framer_.set_max_receive_timestamps_per_ack(8); + + QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT); + QuicPacketHeader header; + header.destination_connection_id = FramerTestConnectionId(); + header.reset_flag = false; + header.version_flag = false; + header.packet_number = kPacketNumber; + + QuicAckFrame ack_frame = InitAckFrame(kSmallLargestObserved); + ack_frame.received_packet_times = PacketTimeVector{ + {kSmallLargestObserved - 1201, CreationTimePlus(0x8bcaef234)}, + {kSmallLargestObserved - 1200, CreationTimePlus(0x8bcdef123)}, + {kSmallLargestObserved - 1000, CreationTimePlus(0xaacdef123)}, + {kSmallLargestObserved - 4, CreationTimePlus(0xabcdea125)}, + {kSmallLargestObserved - 2, CreationTimePlus(0xabcdee124)}, + {kSmallLargestObserved - 1, CreationTimePlus(0xabcdef123)}, + {kSmallLargestObserved, CreationTimePlus(0xabcdef123)}, + }; + ack_frame.ack_delay_time = QuicTime::Delta::Zero(); + QuicFrames frames = {QuicFrame(&ack_frame)}; + + std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames)); + ASSERT_TRUE(data != nullptr); + std::unique_ptr<QuicEncryptedPacket> encrypted( + EncryptPacketWithTagAndPhase(*data, 0, false)); + ASSERT_TRUE(encrypted); + QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_SERVER); + EXPECT_TRUE(framer_.ProcessPacket(*encrypted)); + EXPECT_THAT(framer_.error(), IsQuicNoError()); + + const QuicAckFrame& frame = *visitor_.ack_frames_[0]; + EXPECT_THAT(frame.received_packet_times, + ContainerEq(PacketTimeVector{ + {kSmallLargestObserved, CreationTimePlus(0xabcdef123)}, + {kSmallLargestObserved - 1, CreationTimePlus(0xabcdef123)}, + {kSmallLargestObserved - 2, CreationTimePlus(0xabcdee124)}, + {kSmallLargestObserved - 4, CreationTimePlus(0xabcdea125)}, + {kSmallLargestObserved - 1000, CreationTimePlus(0xaacdef123)}, + {kSmallLargestObserved - 1200, CreationTimePlus(0x8bcdef123)}, + {kSmallLargestObserved - 1201, CreationTimePlus(0x8bcaef234)}, + })); +} + +TEST_P(QuicFramerTest, + BuildAndProcessAckReceiveTimestampsExceedsMaxTimestamps) { + if (!VersionHasIetfQuicFrames(framer_.transport_version())) { + return; + } + framer_.InstallDecrypter(ENCRYPTION_FORWARD_SECURE, + std::make_unique<StrictTaggingDecrypter>(/*key=*/0)); + framer_.SetKeyUpdateSupportForConnection(true); + framer_.set_process_timestamps(true); + framer_.set_max_receive_timestamps_per_ack(2); + + QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT); + QuicPacketHeader header; + header.destination_connection_id = FramerTestConnectionId(); + header.reset_flag = false; + header.version_flag = false; + header.packet_number = kPacketNumber; + + QuicAckFrame ack_frame = InitAckFrame(kSmallLargestObserved); + ack_frame.received_packet_times = PacketTimeVector{ + {kSmallLargestObserved - 1201, CreationTimePlus(0x8bcaef234)}, + {kSmallLargestObserved - 1200, CreationTimePlus(0x8bcdef123)}, + {kSmallLargestObserved - 1000, CreationTimePlus(0xaacdef123)}, + {kSmallLargestObserved - 5, CreationTimePlus(0xabcdea125)}, + {kSmallLargestObserved - 3, CreationTimePlus(0xabcded124)}, + {kSmallLargestObserved - 2, CreationTimePlus(0xabcdee124)}, + {kSmallLargestObserved - 1, CreationTimePlus(0xabcdef123)}, + }; + ack_frame.ack_delay_time = QuicTime::Delta::Zero(); + QuicFrames frames = {QuicFrame(&ack_frame)}; + + std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames)); + ASSERT_TRUE(data != nullptr); + std::unique_ptr<QuicEncryptedPacket> encrypted( + EncryptPacketWithTagAndPhase(*data, 0, false)); + ASSERT_TRUE(encrypted); + QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_SERVER); + EXPECT_TRUE(framer_.ProcessPacket(*encrypted)); + EXPECT_THAT(framer_.error(), IsQuicNoError()); + + const QuicAckFrame& frame = *visitor_.ack_frames_[0]; + EXPECT_THAT(frame.received_packet_times, + ContainerEq(PacketTimeVector{ + {kSmallLargestObserved - 1, CreationTimePlus(0xabcdef123)}, + {kSmallLargestObserved - 2, CreationTimePlus(0xabcdee124)}, + })); +} + +TEST_P(QuicFramerTest, + BuildAndProcessAckReceiveTimestampsWithExponentNoTruncation) { + if (!VersionHasIetfQuicFrames(framer_.transport_version())) { + return; + } + framer_.InstallDecrypter(ENCRYPTION_FORWARD_SECURE, + std::make_unique<StrictTaggingDecrypter>(/*key=*/0)); + framer_.SetKeyUpdateSupportForConnection(true); + framer_.set_process_timestamps(true); + framer_.set_max_receive_timestamps_per_ack(8); + framer_.set_receive_timestamps_exponent(3); + + QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT); + QuicPacketHeader header; + header.destination_connection_id = FramerTestConnectionId(); + header.reset_flag = false; + header.version_flag = false; + header.packet_number = kPacketNumber; + + QuicAckFrame ack_frame = InitAckFrame(kSmallLargestObserved); + ack_frame.received_packet_times = PacketTimeVector{ + {kSmallLargestObserved - 8, CreationTimePlus(0x1add << 3)}, + {kSmallLargestObserved - 7, CreationTimePlus(0x29ed << 3)}, + {kSmallLargestObserved - 3, CreationTimePlus(0x29fe << 3)}, + {kSmallLargestObserved - 2, CreationTimePlus(0x29ff << 3)}, + }; + ack_frame.ack_delay_time = QuicTime::Delta::Zero(); + QuicFrames frames = {QuicFrame(&ack_frame)}; + + std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames)); + ASSERT_TRUE(data != nullptr); + std::unique_ptr<QuicEncryptedPacket> encrypted( + EncryptPacketWithTagAndPhase(*data, 0, false)); + ASSERT_TRUE(encrypted); + QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_SERVER); + EXPECT_TRUE(framer_.ProcessPacket(*encrypted)); + EXPECT_THAT(framer_.error(), IsQuicNoError()); + + const QuicAckFrame& frame = *visitor_.ack_frames_[0]; + EXPECT_THAT(frame.received_packet_times, + ContainerEq(PacketTimeVector{ + {kSmallLargestObserved - 2, CreationTimePlus(0x29ff << 3)}, + {kSmallLargestObserved - 3, CreationTimePlus(0x29fe << 3)}, + {kSmallLargestObserved - 7, CreationTimePlus(0x29ed << 3)}, + {kSmallLargestObserved - 8, CreationTimePlus(0x1add << 3)}, + })); +} + +TEST_P(QuicFramerTest, + BuildAndProcessAckReceiveTimestampsWithExponentTruncation) { + if (!VersionHasIetfQuicFrames(framer_.transport_version())) { + return; + } + framer_.InstallDecrypter(ENCRYPTION_FORWARD_SECURE, + std::make_unique<StrictTaggingDecrypter>(/*key=*/0)); + framer_.SetKeyUpdateSupportForConnection(true); + framer_.set_process_timestamps(true); + framer_.set_max_receive_timestamps_per_ack(8); + framer_.set_receive_timestamps_exponent(3); + + QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT); + QuicPacketHeader header; + header.destination_connection_id = FramerTestConnectionId(); + header.reset_flag = false; + header.version_flag = false; + header.packet_number = kPacketNumber; + + QuicAckFrame ack_frame = InitAckFrame(kSmallLargestObserved); + ack_frame.received_packet_times = PacketTimeVector{ + {kSmallLargestObserved - 10, CreationTimePlus((0x1001 << 3) + 1)}, + {kSmallLargestObserved - 9, CreationTimePlus((0x2995 << 3) - 1)}, + {kSmallLargestObserved - 8, CreationTimePlus((0x2995 << 3) + 0)}, + {kSmallLargestObserved - 7, CreationTimePlus((0x2995 << 3) + 1)}, + {kSmallLargestObserved - 6, CreationTimePlus((0x2995 << 3) + 2)}, + {kSmallLargestObserved - 3, CreationTimePlus((0x2995 << 3) + 3)}, + {kSmallLargestObserved - 2, CreationTimePlus((0x2995 << 3) + 4)}, + }; + ack_frame.ack_delay_time = QuicTime::Delta::Zero(); + QuicFrames frames = {QuicFrame(&ack_frame)}; + + std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames)); + ASSERT_TRUE(data != nullptr); + std::unique_ptr<QuicEncryptedPacket> encrypted( + EncryptPacketWithTagAndPhase(*data, 0, false)); + ASSERT_TRUE(encrypted); + QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_SERVER); + EXPECT_TRUE(framer_.ProcessPacket(*encrypted)); + EXPECT_THAT(framer_.error(), IsQuicNoError()); + + const QuicAckFrame& frame = *visitor_.ack_frames_[0]; + EXPECT_THAT(frame.received_packet_times, + ContainerEq(PacketTimeVector{ + {kSmallLargestObserved - 2, CreationTimePlus(0x2996 << 3)}, + {kSmallLargestObserved - 3, CreationTimePlus(0x2996 << 3)}, + {kSmallLargestObserved - 6, CreationTimePlus(0x2996 << 3)}, + {kSmallLargestObserved - 7, CreationTimePlus(0x2996 << 3)}, + {kSmallLargestObserved - 8, CreationTimePlus(0x2995 << 3)}, + {kSmallLargestObserved - 9, CreationTimePlus(0x2995 << 3)}, + {kSmallLargestObserved - 10, CreationTimePlus(0x1002 << 3)}, + })); +} + +TEST_P(QuicFramerTest, AckReceiveTimestamps) { + if (!VersionHasIetfQuicFrames(framer_.transport_version())) { + return; + } + framer_.InstallDecrypter(ENCRYPTION_FORWARD_SECURE, + std::make_unique<StrictTaggingDecrypter>(/*key=*/0)); + framer_.SetKeyUpdateSupportForConnection(true); + framer_.set_process_timestamps(true); + framer_.set_max_receive_timestamps_per_ack(8); + framer_.set_receive_timestamps_exponent(3); + + QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT); + QuicPacketHeader header; + header.destination_connection_id = FramerTestConnectionId(); + header.reset_flag = false; + header.version_flag = false; + header.packet_number = kPacketNumber; + + // Use kSmallLargestObserved to make this test finished in a short time. + QuicAckFrame ack_frame = InitAckFrame(kSmallLargestObserved); + ack_frame.received_packet_times = PacketTimeVector{ + {kSmallLargestObserved - 5, CreationTimePlus((0x29ff << 3))}, + {kSmallLargestObserved - 4, CreationTimePlus((0x29ff << 3))}, + {kSmallLargestObserved - 3, CreationTimePlus((0x29ff << 3))}, + {kSmallLargestObserved - 2, CreationTimePlus((0x29ff << 3))}, + }; + ack_frame.ack_delay_time = QuicTime::Delta::Zero(); + QuicFrames frames = {QuicFrame(&ack_frame)}; + + std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames)); + ASSERT_TRUE(data != nullptr); + std::unique_ptr<QuicEncryptedPacket> encrypted( + EncryptPacketWithTagAndPhase(*data, 0, false)); + ASSERT_TRUE(encrypted); + QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_SERVER); + EXPECT_TRUE(framer_.ProcessPacket(*encrypted)); + EXPECT_THAT(framer_.error(), IsQuicNoError()); + + const QuicAckFrame& frame = *visitor_.ack_frames_[0]; + EXPECT_THAT(frame.received_packet_times, + ContainerEq(PacketTimeVector{ + {kSmallLargestObserved - 2, CreationTimePlus(0x29ff << 3)}, + {kSmallLargestObserved - 3, CreationTimePlus(0x29ff << 3)}, + {kSmallLargestObserved - 4, CreationTimePlus(0x29ff << 3)}, + {kSmallLargestObserved - 5, CreationTimePlus(0x29ff << 3)}, + })); +} + +TEST_P(QuicFramerTest, AckReceiveTimestampsPacketOutOfOrder) { + if (!VersionHasIetfQuicFrames(framer_.transport_version())) { + return; + } + framer_.InstallDecrypter(ENCRYPTION_FORWARD_SECURE, + std::make_unique<StrictTaggingDecrypter>(/*key=*/0)); + framer_.SetKeyUpdateSupportForConnection(true); + framer_.set_process_timestamps(true); + framer_.set_max_receive_timestamps_per_ack(8); + framer_.set_receive_timestamps_exponent(3); + + QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT); + QuicPacketHeader header; + header.destination_connection_id = FramerTestConnectionId(); + header.reset_flag = false; + header.version_flag = false; + header.packet_number = kPacketNumber; + + // Use kSmallLargestObserved to make this test finished in a short time. + QuicAckFrame ack_frame = InitAckFrame(kSmallLargestObserved); + + // The packet numbers below are out of order, this is impossible because we + // don't record out of order packets in received_packet_times. The test is + // intended to ensure this error is raised when it happens. + ack_frame.received_packet_times = PacketTimeVector{ + {kSmallLargestObserved - 5, CreationTimePlus((0x29ff << 3))}, + {kSmallLargestObserved - 2, CreationTimePlus((0x29ff << 3))}, + {kSmallLargestObserved - 4, CreationTimePlus((0x29ff << 3))}, + {kSmallLargestObserved - 3, CreationTimePlus((0x29ff << 3))}, + }; + ack_frame.ack_delay_time = QuicTime::Delta::Zero(); + QuicFrames frames = {QuicFrame(&ack_frame)}; + + EXPECT_QUIC_BUG(BuildDataPacket(header, frames), + "Packet number and/or receive time not in order."); +} + +// If there's insufficient room for IETF ack receive timestamps, don't write any +// timestamp ranges. +TEST_P(QuicFramerTest, IetfAckReceiveTimestampsTruncate) { + if (!VersionHasIetfQuicFrames(framer_.transport_version())) { + return; + } + framer_.InstallDecrypter(ENCRYPTION_FORWARD_SECURE, + std::make_unique<StrictTaggingDecrypter>(/*key=*/0)); + framer_.SetKeyUpdateSupportForConnection(true); + framer_.set_process_timestamps(true); + framer_.set_max_receive_timestamps_per_ack(8192); + framer_.set_receive_timestamps_exponent(3); + + QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT); + QuicPacketHeader header; + header.destination_connection_id = FramerTestConnectionId(); + header.reset_flag = false; + header.version_flag = false; + header.packet_number = kPacketNumber; + + // Use kSmallLargestObserved to make this test finished in a short time. + QuicAckFrame ack_frame = InitAckFrame(kSmallLargestObserved); + for (QuicPacketNumber i(1); i <= kSmallLargestObserved; i += 2) { + ack_frame.received_packet_times.push_back( + {i, CreationTimePlus((0x29ff << 3))}); + } + + ack_frame.ack_delay_time = QuicTime::Delta::Zero(); + QuicFrames frames = {QuicFrame(&ack_frame)}; + + std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames)); + ASSERT_TRUE(data != nullptr); + std::unique_ptr<QuicEncryptedPacket> encrypted( + EncryptPacketWithTagAndPhase(*data, 0, false)); + ASSERT_TRUE(encrypted); + QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_SERVER); + EXPECT_TRUE(framer_.ProcessPacket(*encrypted)); + EXPECT_THAT(framer_.error(), IsQuicNoError()); + + const QuicAckFrame& frame = *visitor_.ack_frames_[0]; + EXPECT_TRUE(frame.received_packet_times.empty()); +} + +// If there are too many ack ranges, they will be truncated to make room for a +// timestamp range count of 0. +TEST_P(QuicFramerTest, IetfAckReceiveTimestampsAckRangeTruncation) { + if (!VersionHasIetfQuicFrames(framer_.transport_version())) { + return; + } + SetDecrypterLevel(ENCRYPTION_FORWARD_SECURE); + framer_.set_process_timestamps(true); + framer_.set_max_receive_timestamps_per_ack(8); + framer_.set_receive_timestamps_exponent(3); + + QuicPacketHeader header; + header.destination_connection_id = FramerTestConnectionId(); + header.reset_flag = false; + header.version_flag = false; + header.packet_number = kPacketNumber; + + QuicAckFrame ack_frame; + // Create a packet with just the ack. + ack_frame = MakeAckFrameWithGaps(/*gap_size=*/0xffffffff, + /*max_num_gaps=*/200, + /*largest_acked=*/kMaxIetfVarInt); + ack_frame.received_packet_times = PacketTimeVector{ + {QuicPacketNumber(kMaxIetfVarInt) - 2, CreationTimePlus((0x29ff << 3))}, + }; + QuicFrames frames = {QuicFrame(&ack_frame)}; + // Build an ACK packet. + QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT); + std::unique_ptr<QuicPacket> raw_ack_packet(BuildDataPacket(header, frames)); + ASSERT_TRUE(raw_ack_packet != nullptr); + char buffer[kMaxOutgoingPacketSize]; + size_t encrypted_length = + framer_.EncryptPayload(ENCRYPTION_INITIAL, header.packet_number, + *raw_ack_packet, buffer, kMaxOutgoingPacketSize); + ASSERT_NE(0u, encrypted_length); + // Now make sure we can turn our ack packet back into an ack frame. + QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_SERVER); + ASSERT_TRUE(framer_.ProcessPacket( + QuicEncryptedPacket(buffer, encrypted_length, false))); + ASSERT_EQ(1u, visitor_.ack_frames_.size()); + QuicAckFrame& processed_ack_frame = *visitor_.ack_frames_[0]; + EXPECT_EQ(QuicPacketNumber(kMaxIetfVarInt), + LargestAcked(processed_ack_frame)); + // Verify ACK ranges in the frame gets truncated. + ASSERT_LT(processed_ack_frame.packets.NumPacketsSlow(), + ack_frame.packets.NumIntervals()); + EXPECT_EQ(158u, processed_ack_frame.packets.NumPacketsSlow()); + EXPECT_LT(processed_ack_frame.packets.NumIntervals(), + ack_frame.packets.NumIntervals()); + EXPECT_EQ(QuicPacketNumber(kMaxIetfVarInt), + processed_ack_frame.packets.Max()); + // But the receive timestamps are not truncated because they are small. + EXPECT_FALSE(processed_ack_frame.received_packet_times.empty()); +} + TEST_P(QuicFramerTest, BuildAckFramePacketOneAckBlockMaxLength) { QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT); QuicPacketHeader header; @@ -13034,10 +13741,16 @@ TEST_P(QuicFramerTest, CoalescedPacket) { 'R', 'L', 'D', '?', }; // clang-format on + const size_t first_packet_ietf_size = 46; + // If the first packet changes, the attempt to fix the first byte of the + // second packet will fail. + EXPECT_EQ(packet_ietf[first_packet_ietf_size], 0xD3); unsigned char* p = packet; size_t p_length = ABSL_ARRAYSIZE(packet); if (framer_.version().HasIetfQuicFrames()) { + ReviseFirstByteByVersion(packet_ietf); + ReviseFirstByteByVersion(&packet_ietf[first_packet_ietf_size]); p = packet_ietf; p_length = ABSL_ARRAYSIZE(packet_ietf); } @@ -13157,6 +13870,7 @@ TEST_P(QuicFramerTest, CoalescedPacketWithUdpPadding) { unsigned char* p = packet; size_t p_length = ABSL_ARRAYSIZE(packet); if (framer_.version().HasIetfQuicFrames()) { + ReviseFirstByteByVersion(packet_ietf); p = packet_ietf; p_length = ABSL_ARRAYSIZE(packet_ietf); } @@ -13304,10 +14018,16 @@ TEST_P(QuicFramerTest, CoalescedPacketWithDifferentVersion) { 'R', 'L', 'D', '?', }; // clang-format on + const size_t first_packet_ietf_size = 46; + // If the first packet changes, the attempt to fix the first byte of the + // second packet will fail. + EXPECT_EQ(packet_ietf[first_packet_ietf_size], 0xD3); unsigned char* p = packet; size_t p_length = ABSL_ARRAYSIZE(packet); if (framer_.version().HasIetfQuicFrames()) { + ReviseFirstByteByVersion(packet_ietf); + ReviseFirstByteByVersion(&packet_ietf[first_packet_ietf_size]); p = packet_ietf; p_length = ABSL_ARRAYSIZE(packet_ietf); } @@ -13415,6 +14135,7 @@ TEST_P(QuicFramerTest, UndecryptablePacketWithoutDecrypter) { unsigned char* p = packet; size_t p_length = ABSL_ARRAYSIZE(packet); if (framer_.version().HasLongHeaderLengths()) { + ReviseFirstByteByVersion(packet49); p = packet49; p_length = ABSL_ARRAYSIZE(packet49); } else if (framer_.version().HasIetfInvariantHeader()) { @@ -13517,6 +14238,7 @@ TEST_P(QuicFramerTest, UndecryptablePacketWithDecrypter) { unsigned char* p = packet; size_t p_length = ABSL_ARRAYSIZE(packet); if (framer_.version().HasLongHeaderLengths()) { + ReviseFirstByteByVersion(packet49); p = packet49; p_length = ABSL_ARRAYSIZE(packet49); } else if (framer_.version().HasIetfInvariantHeader()) { @@ -13677,10 +14399,15 @@ TEST_P(QuicFramerTest, UndecryptableCoalescedPacket) { }; // clang-format on const size_t length_of_first_coalesced_packet = 46; + // If the first packet changes, the attempt to fix the first byte of the + // second packet will fail. + EXPECT_EQ(packet_ietf[length_of_first_coalesced_packet], 0xD3); unsigned char* p = packet; size_t p_length = ABSL_ARRAYSIZE(packet); if (framer_.version().HasIetfQuicFrames()) { + ReviseFirstByteByVersion(packet_ietf); + ReviseFirstByteByVersion(&packet_ietf[length_of_first_coalesced_packet]); p = packet_ietf; p_length = ABSL_ARRAYSIZE(packet_ietf); } @@ -13844,10 +14571,16 @@ TEST_P(QuicFramerTest, MismatchedCoalescedPacket) { 'R', 'L', 'D', '?', }; // clang-format on + const size_t length_of_first_coalesced_packet = 46; + // If the first packet changes, the attempt to fix the first byte of the + // second packet will fail. + EXPECT_EQ(packet_ietf[length_of_first_coalesced_packet], 0xD3); unsigned char* p = packet; size_t p_length = ABSL_ARRAYSIZE(packet); if (framer_.version().HasIetfQuicFrames()) { + ReviseFirstByteByVersion(packet_ietf); + ReviseFirstByteByVersion(&packet_ietf[length_of_first_coalesced_packet]); p = packet_ietf; p_length = ABSL_ARRAYSIZE(packet_ietf); } @@ -13949,10 +14682,16 @@ TEST_P(QuicFramerTest, InvalidCoalescedPacket) { // version would be here but we cut off the invalid coalesced header. }; // clang-format on + const size_t length_of_first_coalesced_packet = 46; + // If the first packet changes, the attempt to fix the first byte of the + // second packet will fail. + EXPECT_EQ(packet_ietf[length_of_first_coalesced_packet], 0xD3); unsigned char* p = packet; size_t p_length = ABSL_ARRAYSIZE(packet); if (framer_.version().HasIetfQuicFrames()) { + ReviseFirstByteByVersion(packet_ietf); + ReviseFirstByteByVersion(&packet_ietf[length_of_first_coalesced_packet]); p = packet_ietf; p_length = ABSL_ARRAYSIZE(packet_ietf); } @@ -14180,6 +14919,7 @@ TEST_P(QuicFramerTest, MultiplePacketNumberSpaces) { QuicEncryptedPacket(AsChars(long_header_packet), ABSL_ARRAYSIZE(long_header_packet), false))); } else { + ReviseFirstByteByVersion(long_header_packet_ietf); EXPECT_TRUE(framer_.ProcessPacket( QuicEncryptedPacket(AsChars(long_header_packet_ietf), ABSL_ARRAYSIZE(long_header_packet_ietf), false))); @@ -14708,9 +15448,11 @@ TEST_P(QuicFramerTest, ClientConnectionIdFromLongHeaderToClient) { 0x00, }; // clang-format on + unsigned char* p = packet; size_t p_length = ABSL_ARRAYSIZE(packet); if (framer_.version().HasLongHeaderLengths()) { + ReviseFirstByteByVersion(packet49); p = packet49; p_length = ABSL_ARRAYSIZE(packet49); } @@ -14776,6 +15518,7 @@ TEST_P(QuicFramerTest, ClientConnectionIdFromLongHeaderToServer) { unsigned char* p = packet; size_t p_length = ABSL_ARRAYSIZE(packet); if (framer_.version().HasLongHeaderLengths()) { + ReviseFirstByteByVersion(packet49); p = packet49; p_length = ABSL_ARRAYSIZE(packet49); } @@ -15615,6 +16358,7 @@ TEST_P(QuicFramerTest, ErrorWhenUnexpectedFrameTypeEncountered) { }; // clang-format on + ReviseFirstByteByVersion(packet); QuicEncryptedPacket encrypted(AsChars(packet), ABSL_ARRAYSIZE(packet), false); EXPECT_FALSE(framer_.ProcessPacket(encrypted)); diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_received_packet_manager.cc b/chromium/net/third_party/quiche/src/quic/core/quic_received_packet_manager.cc index 079778cd5d8..d0f1578fa79 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_received_packet_manager.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_received_packet_manager.cc @@ -38,6 +38,7 @@ QuicReceivedPacketManager::QuicReceivedPacketManager(QuicConnectionStats* stats) max_ack_ranges_(0), time_largest_observed_(QuicTime::Zero()), save_timestamps_(false), + save_timestamps_for_in_order_packets_(false), stats_(stats), num_retransmittable_packets_received_since_last_ack_sent_(0), min_received_before_ack_decimation_(kMinReceivedBeforeAckDecimation), @@ -80,9 +81,12 @@ void QuicReceivedPacketManager::RecordPacketReceived( } ack_frame_updated_ = true; + // Whether |packet_number| is received out of order. + bool packet_reordered = false; if (LargestAcked(ack_frame_).IsInitialized() && LargestAcked(ack_frame_) > packet_number) { // Record how out of order stats. + packet_reordered = true; ++stats_->packets_reordered; stats_->max_sequence_reordering = std::max(stats_->max_sequence_reordering, @@ -101,8 +105,11 @@ void QuicReceivedPacketManager::RecordPacketReceived( if (save_timestamps_) { // The timestamp format only handles packets in time order. - if (!ack_frame_.received_packet_times.empty() && - ack_frame_.received_packet_times.back().second > receipt_time) { + if (save_timestamps_for_in_order_packets_ && packet_reordered) { + QUIC_DLOG(WARNING) << "Not saving receive timestamp for packet " + << packet_number; + } else if (!ack_frame_.received_packet_times.empty() && + ack_frame_.received_packet_times.back().second > receipt_time) { QUIC_LOG(WARNING) << "Receive time went backwards from: " << ack_frame_.received_packet_times.back().second.ToDebuggingValue() diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_received_packet_manager.h b/chromium/net/third_party/quiche/src/quic/core/quic_received_packet_manager.h index 822725db6aa..21f7f045d4a 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_received_packet_manager.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_received_packet_manager.h @@ -103,8 +103,9 @@ class QUIC_EXPORT_PRIVATE QuicReceivedPacketManager { max_ack_ranges_ = max_ack_ranges; } - void set_save_timestamps(bool save_timestamps) { + void set_save_timestamps(bool save_timestamps, bool in_order_packets_only) { save_timestamps_ = save_timestamps; + save_timestamps_for_in_order_packets_ = in_order_packets_only; } size_t min_received_before_ack_decimation() const { @@ -167,6 +168,10 @@ class QUIC_EXPORT_PRIVATE QuicReceivedPacketManager { // If true, save timestamps in the ack_frame_. bool save_timestamps_; + // If true and |save_timestamps_|, only save timestamps for packets that are + // received in order. + bool save_timestamps_for_in_order_packets_; + // Least packet number received from peer. QuicPacketNumber least_received_packet_number_; diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_received_packet_manager_test.cc b/chromium/net/third_party/quiche/src/quic/core/quic_received_packet_manager_test.cc index 9d6b6644421..cc0438140fe 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_received_packet_manager_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_received_packet_manager_test.cc @@ -46,7 +46,7 @@ class QuicReceivedPacketManagerTest : public QuicTest { QuicReceivedPacketManagerTest() : received_manager_(&stats_) { clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1)); rtt_stats_.UpdateRtt(kMinRttMs, QuicTime::Delta::Zero(), QuicTime::Zero()); - received_manager_.set_save_timestamps(true); + received_manager_.set_save_timestamps(true, false); } void RecordPacketReceipt(uint64_t packet_number) { @@ -189,6 +189,21 @@ TEST_F(QuicReceivedPacketManagerTest, IgnoreOutOfOrderTimestamps) { EXPECT_EQ(2u, received_manager_.ack_frame().received_packet_times.size()); } +TEST_F(QuicReceivedPacketManagerTest, IgnoreOutOfOrderPackets) { + received_manager_.set_save_timestamps(true, true); + EXPECT_FALSE(received_manager_.ack_frame_updated()); + RecordPacketReceipt(1, QuicTime::Zero()); + EXPECT_TRUE(received_manager_.ack_frame_updated()); + EXPECT_EQ(1u, received_manager_.ack_frame().received_packet_times.size()); + RecordPacketReceipt(4, + QuicTime::Zero() + QuicTime::Delta::FromMilliseconds(1)); + EXPECT_EQ(2u, received_manager_.ack_frame().received_packet_times.size()); + + RecordPacketReceipt(3, + QuicTime::Zero() + QuicTime::Delta::FromMilliseconds(3)); + EXPECT_EQ(2u, received_manager_.ack_frame().received_packet_times.size()); +} + TEST_F(QuicReceivedPacketManagerTest, HasMissingPackets) { EXPECT_QUIC_BUG(received_manager_.PeerFirstSendingPacketNumber(), "No packets have been received yet"); 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 4c5026ca974..55dbe057887 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 @@ -87,7 +87,6 @@ QuicSentPacketManager::QuicSentPacketManager( pending_timer_transmission_count_(0), max_tail_loss_probes_(kDefaultMaxTailLossProbes), max_rto_packets_(kMaxRetransmissionsOnTimeout), - enable_half_rtt_tail_loss_probe_(false), using_pacing_(false), use_new_rto_(false), conservative_handshake_retransmits_(false), @@ -209,12 +208,7 @@ void QuicSentPacketManager::SetFromConfig(const QuicConfig& config) { QUIC_CODE_COUNT(two_aggressive_ptos); num_tlp_timeout_ptos_ = 2; } - if (GetQuicReloadableFlag(quic_deprecate_tlpr)) { - QUIC_RELOADABLE_FLAG_COUNT_N(quic_deprecate_tlpr, 2, 2); - } - if (config.HasClientSentConnectionOption(kPLE1, perspective) || - (config.HasClientSentConnectionOption(kTLPR, perspective) && - !GetQuicReloadableFlag(quic_deprecate_tlpr))) { + if (config.HasClientSentConnectionOption(kPLE1, perspective)) { first_pto_srtt_multiplier_ = 0.5; } else if (config.HasClientSentConnectionOption(kPLE2, perspective)) { first_pto_srtt_multiplier_ = 1.5; @@ -304,17 +298,6 @@ void QuicSentPacketManager::SetFromConfig(const QuicConfig& config) { if (config.HasClientSentConnectionOption(k1RTO, perspective)) { max_rto_packets_ = 1; } - if (GetQuicReloadableFlag(quic_deprecate_tlpr)) { - QUIC_RELOADABLE_FLAG_COUNT_N(quic_deprecate_tlpr, 1, 2); - } - if (config.HasClientSentConnectionOption(kTLPR, perspective) && - !GetQuicReloadableFlag(quic_deprecate_tlpr)) { - enable_half_rtt_tail_loss_probe_ = true; - } - if (config.HasClientRequestedIndependentOption(kTLPR, perspective) && - !GetQuicReloadableFlag(quic_deprecate_tlpr)) { - enable_half_rtt_tail_loss_probe_ = true; - } if (config.HasClientSentConnectionOption(kNRTO, perspective)) { use_new_rto_ = true; } @@ -352,6 +335,15 @@ void QuicSentPacketManager::SetFromConfig(const QuicConfig& config) { if (network_change_visitor_ != nullptr) { network_change_visitor_->OnCongestionChange(); } + + if (debug_delegate_ != nullptr) { + DebugDelegate::SendParameters parameters; + parameters.congestion_control_type = + send_algorithm_->GetCongestionControlType(); + parameters.use_pacing = using_pacing_; + parameters.initial_congestion_window = initial_congestion_window_; + debug_delegate_->OnConfigProcessed(parameters); + } } void QuicSentPacketManager::ApplyConnectionOptions( @@ -392,6 +384,18 @@ void QuicSentPacketManager::ResumeConnectionState( void QuicSentPacketManager::AdjustNetworkParameters( const SendAlgorithmInterface::NetworkParams& params) { + if (params.burst_token != 0) { + if (using_pacing_) { + QUIC_RELOADABLE_FLAG_COUNT(quic_set_burst_token); + int old_burst_size = pacing_sender_.initial_burst_size(); + pacing_sender_.SetBurstTokens(params.burst_token); + if (debug_delegate_ != nullptr) { + debug_delegate_->OnAdjustBurstSize(old_burst_size, + pacing_sender_.initial_burst_size()); + } + } + return; + } const QuicBandwidth& bandwidth = params.bandwidth; const QuicTime::Delta& rtt = params.rtt; if (!rtt.IsZero()) { @@ -1393,12 +1397,6 @@ const QuicTime::Delta QuicSentPacketManager::GetCryptoRetransmissionDelay() const QuicTime::Delta QuicSentPacketManager::GetTailLossProbeDelay() const { QuicTime::Delta srtt = rtt_stats_.SmoothedOrInitialRtt(); - if (enable_half_rtt_tail_loss_probe_ && consecutive_tlp_count_ == 0u) { - if (unacked_packets().HasUnackedStreamData()) { - // Enable TLPR if there are pending data packets. - return std::max(min_tlp_timeout_, srtt * 0.5); - } - } if (!unacked_packets_.HasMultipleInFlightPackets()) { // This expression really should be using the delayed ack time, but in TCP // MinRTO was traditionally set to 2x the delayed ack timer and this @@ -1444,10 +1442,6 @@ const QuicTime::Delta QuicSentPacketManager::GetProbeTimeoutDelay( QuicTime::Delta::FromMilliseconds(kMinHandshakeTimeoutMs)) * (1 << consecutive_pto_count_); } - if (enable_half_rtt_tail_loss_probe_ && consecutive_pto_count_ == 0 && - handshake_finished_) { - return std::max(min_tlp_timeout_, rtt_stats_.smoothed_rtt() * 0.5); - } const QuicTime::Delta rtt_var = use_standard_deviation_for_pto_ ? rtt_stats_.GetStandardOrMeanDeviation() : rtt_stats_.mean_deviation(); @@ -1769,18 +1763,11 @@ QuicSentPacketManager::GetNConsecutiveRetransmissionTimeoutDelay( std::min(num_timeouts, static_cast<int>(max_tail_loss_probes_)); num_timeouts -= num_tlps; if (num_tlps > 0) { - if (enable_half_rtt_tail_loss_probe_ && - unacked_packets().HasUnackedStreamData()) { - total_delay = total_delay + std::max(min_tlp_timeout_, srtt * 0.5); - --num_tlps; - } - if (num_tlps > 0) { - const QuicTime::Delta tlp_delay = - std::max(2 * srtt, unacked_packets_.HasMultipleInFlightPackets() - ? min_tlp_timeout_ - : (1.5 * srtt + (min_rto_timeout_ * 0.5))); - total_delay = total_delay + num_tlps * tlp_delay; - } + const QuicTime::Delta tlp_delay = + std::max(2 * srtt, unacked_packets_.HasMultipleInFlightPackets() + ? min_tlp_timeout_ + : (1.5 * srtt + (min_rto_timeout_ * 0.5))); + total_delay = total_delay + num_tlps * tlp_delay; } if (num_timeouts == 0) { return total_delay; 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 e4a7036e432..dac8ccf08d8 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 @@ -52,6 +52,12 @@ class QUIC_EXPORT_PRIVATE QuicSentPacketManager { // the packet manager or connection as a result of these callbacks. class QUIC_EXPORT_PRIVATE DebugDelegate { public: + struct QUIC_EXPORT_PRIVATE SendParameters { + CongestionControlType congestion_control_type; + bool use_pacing; + QuicPacketCount initial_congestion_window; + }; + virtual ~DebugDelegate() {} // Called when a spurious retransmission is detected. @@ -80,7 +86,12 @@ class QUIC_EXPORT_PRIVATE QuicSentPacketManager { QuicByteCount /*old_cwnd*/, QuicByteCount /*new_cwnd*/) {} + virtual void OnAdjustBurstSize(int /*old_burst_size*/, + int /*new_burst_size*/) {} + virtual void OnOvershootingDetected() {} + + virtual void OnConfigProcessed(const SendParameters& /*parameters*/) {} }; // Interface which gets callbacks from the QuicSentPacketManager when @@ -622,8 +633,6 @@ class QUIC_EXPORT_PRIVATE QuicSentPacketManager { // Maximum number of packets to send upon RTO. QuicPacketCount max_rto_packets_; // If true, send the TLP at 0.5 RTT. - // TODO(renjietang): remove it once quic_deprecate_tlpr flag is deprecated. - bool enable_half_rtt_tail_loss_probe_; bool using_pacing_; // If true, use the new RTO with loss based CWND reduction instead of the send // algorithms's OnRetransmissionTimeout to reduce the congestion window. 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 a09afc6b7b8..1ce3da8e396 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 @@ -1463,112 +1463,6 @@ TEST_F(QuicSentPacketManagerTest, GetTransmissionTimeTailLossProbe) { EXPECT_EQ(expected_time, manager_.GetRetransmissionTime()); } -TEST_F(QuicSentPacketManagerTest, TLPRWithPendingStreamData) { - if (GetQuicReloadableFlag(quic_default_on_pto) || - GetQuicReloadableFlag(quic_deprecate_tlpr)) { - return; - } - QuicConfig config; - QuicTagVector options; - - options.push_back(kTLPR); - QuicConfigPeer::SetReceivedConnectionOptions(&config, options); - EXPECT_CALL(*network_change_visitor_, OnCongestionChange()); - EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); - EXPECT_CALL(*send_algorithm_, PacingRate(_)) - .WillRepeatedly(Return(QuicBandwidth::Zero())); - EXPECT_CALL(*send_algorithm_, GetCongestionWindow()) - .WillOnce(Return(10 * kDefaultTCPMSS)); - manager_.SetFromConfig(config); - EXPECT_TRUE( - QuicSentPacketManagerPeer::GetEnableHalfRttTailLossProbe(&manager_)); - - QuicSentPacketManagerPeer::SetMaxTailLossProbes(&manager_, 2); - - SendDataPacket(1); - SendDataPacket(2); - - // Test with a standard smoothed RTT. - RttStats* rtt_stats = const_cast<RttStats*>(manager_.GetRttStats()); - rtt_stats->set_initial_rtt(QuicTime::Delta::FromMilliseconds(100)); - QuicTime::Delta srtt = rtt_stats->initial_rtt(); - // With pending stream data, TLPR is used. - QuicTime::Delta expected_tlp_delay = 0.5 * srtt; - EXPECT_CALL(notifier_, HasUnackedStreamData()).WillRepeatedly(Return(true)); - - EXPECT_EQ(expected_tlp_delay, - manager_.GetRetransmissionTime() - clock_.Now()); - - // Retransmit the packet by invoking the retransmission timeout. - clock_.AdvanceTime(expected_tlp_delay); - manager_.OnRetransmissionTimeout(); - EXPECT_EQ(QuicTime::Delta::Zero(), manager_.TimeUntilSend(clock_.Now())); - EXPECT_CALL(notifier_, RetransmitFrames(_, _)) - .WillOnce(WithArgs<1>(Invoke( - [this](TransmissionType type) { RetransmitDataPacket(3, type); }))); - EXPECT_TRUE(manager_.MaybeRetransmitTailLossProbe()); - - EXPECT_CALL(*send_algorithm_, CanSend(_)).WillOnce(Return(false)); - EXPECT_EQ(QuicTime::Delta::Infinite(), manager_.TimeUntilSend(clock_.Now())); - - // 2nd TLP. - expected_tlp_delay = 2 * srtt; - EXPECT_EQ(expected_tlp_delay, - manager_.GetRetransmissionTime() - clock_.Now()); -} - -TEST_F(QuicSentPacketManagerTest, TLPRWithoutPendingStreamData) { - if (GetQuicReloadableFlag(quic_default_on_pto) || - GetQuicReloadableFlag(quic_deprecate_tlpr)) { - return; - } - QuicConfig config; - QuicTagVector options; - - options.push_back(kTLPR); - QuicConfigPeer::SetReceivedConnectionOptions(&config, options); - EXPECT_CALL(*network_change_visitor_, OnCongestionChange()); - EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); - EXPECT_CALL(*send_algorithm_, PacingRate(_)) - .WillRepeatedly(Return(QuicBandwidth::Zero())); - EXPECT_CALL(*send_algorithm_, GetCongestionWindow()) - .WillOnce(Return(10 * kDefaultTCPMSS)); - manager_.SetFromConfig(config); - EXPECT_TRUE( - QuicSentPacketManagerPeer::GetEnableHalfRttTailLossProbe(&manager_)); - QuicSentPacketManagerPeer::SetMaxTailLossProbes(&manager_, 2); - - SendPingPacket(1, ENCRYPTION_INITIAL); - SendPingPacket(2, ENCRYPTION_INITIAL); - - // Test with a standard smoothed RTT. - RttStats* rtt_stats = const_cast<RttStats*>(manager_.GetRttStats()); - rtt_stats->set_initial_rtt(QuicTime::Delta::FromMilliseconds(100)); - QuicTime::Delta srtt = rtt_stats->initial_rtt(); - QuicTime::Delta expected_tlp_delay = 0.5 * srtt; - // With no pending stream data, TLPR is ignored. - expected_tlp_delay = 2 * srtt; - EXPECT_CALL(notifier_, HasUnackedStreamData()).WillRepeatedly(Return(false)); - EXPECT_EQ(expected_tlp_delay, - manager_.GetRetransmissionTime() - clock_.Now()); - - // Retransmit the packet by invoking the retransmission timeout. - clock_.AdvanceTime(expected_tlp_delay); - manager_.OnRetransmissionTimeout(); - EXPECT_EQ(QuicTime::Delta::Zero(), manager_.TimeUntilSend(clock_.Now())); - EXPECT_CALL(notifier_, RetransmitFrames(_, _)) - .WillOnce(WithArgs<1>(Invoke( - [this](TransmissionType type) { RetransmitDataPacket(3, type); }))); - EXPECT_TRUE(manager_.MaybeRetransmitTailLossProbe()); - EXPECT_CALL(*send_algorithm_, CanSend(_)).WillOnce(Return(false)); - EXPECT_EQ(QuicTime::Delta::Infinite(), manager_.TimeUntilSend(clock_.Now())); - - // 2nd TLP. - expected_tlp_delay = 2 * srtt; - EXPECT_EQ(expected_tlp_delay, - manager_.GetRetransmissionTime() - clock_.Now()); -} - TEST_F(QuicSentPacketManagerTest, GetTransmissionTimeSpuriousRTO) { if (GetQuicReloadableFlag(quic_default_on_pto)) { return; @@ -2125,41 +2019,6 @@ TEST_F(QuicSentPacketManagerTest, Negotiate1TLPFromOptionsAtClient) { EXPECT_EQ(1u, QuicSentPacketManagerPeer::GetMaxTailLossProbes(&manager_)); } -TEST_F(QuicSentPacketManagerTest, NegotiateTLPRttFromOptionsAtServer) { - if (GetQuicReloadableFlag(quic_default_on_pto) || - GetQuicReloadableFlag(quic_deprecate_tlpr)) { - return; - } - QuicConfig config; - QuicTagVector options; - - options.push_back(kTLPR); - QuicConfigPeer::SetReceivedConnectionOptions(&config, options); - EXPECT_CALL(*network_change_visitor_, OnCongestionChange()); - EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); - manager_.SetFromConfig(config); - EXPECT_TRUE( - QuicSentPacketManagerPeer::GetEnableHalfRttTailLossProbe(&manager_)); -} - -TEST_F(QuicSentPacketManagerTest, NegotiateTLPRttFromOptionsAtClient) { - if (GetQuicReloadableFlag(quic_default_on_pto) || - GetQuicReloadableFlag(quic_deprecate_tlpr)) { - return; - } - QuicConfig client_config; - QuicTagVector options; - - options.push_back(kTLPR); - QuicSentPacketManagerPeer::SetPerspective(&manager_, Perspective::IS_CLIENT); - client_config.SetConnectionOptionsToSend(options); - EXPECT_CALL(*network_change_visitor_, OnCongestionChange()); - EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); - manager_.SetFromConfig(client_config); - EXPECT_TRUE( - QuicSentPacketManagerPeer::GetEnableHalfRttTailLossProbe(&manager_)); -} - TEST_F(QuicSentPacketManagerTest, NegotiateNewRTOFromOptionsAtServer) { if (GetQuicReloadableFlag(quic_default_on_pto)) { return; @@ -4371,90 +4230,6 @@ TEST_F(QuicSentPacketManagerTest, manager_.GetRetransmissionTime()); } -TEST_F(QuicSentPacketManagerTest, ClientOnlyTLPRServer) { - if (GetQuicReloadableFlag(quic_deprecate_tlpr)) { - return; - } - QuicConfig config; - QuicTagVector options; - - options.push_back(kTLPR); - config.SetClientConnectionOptions(options); - EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); - EXPECT_CALL(*network_change_visitor_, OnCongestionChange()); - manager_.SetFromConfig(config); - // No change if the server receives client options. - EXPECT_FALSE( - QuicSentPacketManagerPeer::GetEnableHalfRttTailLossProbe(&manager_)); -} - -TEST_F(QuicSentPacketManagerTest, ClientOnlyTLPR) { - if (GetQuicReloadableFlag(quic_deprecate_tlpr)) { - return; - } - QuicSentPacketManagerPeer::SetPerspective(&manager_, Perspective::IS_CLIENT); - QuicConfig config; - QuicTagVector options; - - options.push_back(kTLPR); - config.SetClientConnectionOptions(options); - EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); - EXPECT_CALL(*network_change_visitor_, OnCongestionChange()); - manager_.SetFromConfig(config); - EXPECT_TRUE( - QuicSentPacketManagerPeer::GetEnableHalfRttTailLossProbe(&manager_)); -} - -TEST_F(QuicSentPacketManagerTest, PtoWithTlpr) { - if (GetQuicReloadableFlag(quic_deprecate_tlpr)) { - return; - } - QuicConfig config; - QuicTagVector options; - - options.push_back(kTLPR); - options.push_back(k1PTO); - options.push_back(kPTOS); - QuicConfigPeer::SetReceivedConnectionOptions(&config, options); - EXPECT_CALL(*network_change_visitor_, OnCongestionChange()); - EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); - EXPECT_CALL(*send_algorithm_, PacingRate(_)) - .WillRepeatedly(Return(QuicBandwidth::Zero())); - EXPECT_CALL(*send_algorithm_, GetCongestionWindow()) - .WillOnce(Return(10 * kDefaultTCPMSS)); - manager_.SetFromConfig(config); - EXPECT_TRUE( - QuicSentPacketManagerPeer::GetEnableHalfRttTailLossProbe(&manager_)); - RttStats* rtt_stats = const_cast<RttStats*>(manager_.GetRttStats()); - rtt_stats->UpdateRtt(QuicTime::Delta::FromMilliseconds(100), - QuicTime::Delta::Zero(), QuicTime::Zero()); - QuicTime::Delta srtt = rtt_stats->smoothed_rtt(); - manager_.SetHandshakeConfirmed(); - - SendDataPacket(1, ENCRYPTION_FORWARD_SECURE); - // Verify PTO is correctly set. - QuicTime::Delta expected_pto_delay = 0.5 * srtt; - QuicTime deadline = clock_.Now() + expected_pto_delay; - EXPECT_EQ(deadline, manager_.GetRetransmissionTime()); - - // Invoke PTO. - clock_.AdvanceTime(deadline - clock_.Now()); - manager_.OnRetransmissionTimeout(); - EXPECT_CALL(notifier_, RetransmitFrames(_, _)) - .WillOnce(WithArgs<1>(Invoke([this](TransmissionType type) { - RetransmitDataPacket(3, type, ENCRYPTION_FORWARD_SECURE); - }))); - manager_.MaybeSendProbePackets(); - - // Verify PTO period gets set correctly. - expected_pto_delay = - srtt + GetPtoRttvarMultiplier() * rtt_stats->mean_deviation() + - QuicTime::Delta::FromMilliseconds(kDefaultDelayedAckTimeMs); - QuicTime sent_time = clock_.Now(); - EXPECT_EQ(sent_time + expected_pto_delay * 2, - manager_.GetRetransmissionTime()); -} - TEST_F(QuicSentPacketManagerTest, SendPathChallengeAndGetAck) { QuicPacketNumber packet_number(1); EXPECT_CALL(*send_algorithm_, 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 14d549a3888..202bff43e1f 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 @@ -147,10 +147,6 @@ void QuicSession::Initialize() { connection_->OnSuccessfulVersionNegotiation(); } - if (GetMutableCryptoStream()->KeyUpdateSupportedLocally()) { - config_.SetKeyUpdateSupportedLocally(); - } - if (QuicVersionUsesCryptoFrames(transport_version())) { return; } 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 d06e1fe43a4..88ae992880d 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 @@ -312,6 +312,7 @@ class QUIC_EXPORT_PRIVATE QuicSession std::string* error_details) override; void OnHandshakeCallbackDone() override; bool PacketFlusherAttached() const override; + ParsedQuicVersion parsed_version() const override { return version(); } // Implement StreamDelegateInterface. void OnStreamError(QuicErrorCode error_code, 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 8d5e477237a..256865d9e7b 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 @@ -159,7 +159,6 @@ class TestCryptoStream : public QuicCryptoStream, public QuicCryptoHandshaker { } void SetServerApplicationStateForResumption( std::unique_ptr<ApplicationState> /*application_state*/) override {} - MOCK_METHOD(bool, KeyUpdateSupportedLocally, (), (const, override)); MOCK_METHOD(std::unique_ptr<QuicDecrypter>, AdvanceKeysAndCreateCurrentOneRttDecrypter, (), @@ -235,8 +234,6 @@ class TestSession : public QuicSession { writev_consumes_all_data_(false), uses_pending_streams_(false), num_incoming_streams_created_(0) { - EXPECT_CALL(*GetMutableCryptoStream(), KeyUpdateSupportedLocally()) - .WillRepeatedly(Return(false)); Initialize(); this->connection()->SetEncrypter( ENCRYPTION_FORWARD_SECURE, @@ -2294,20 +2291,6 @@ TEST_P(QuicSessionTestClient, MinAckDelaySetOnTheClientQuicConfig) { ASSERT_TRUE(session_.connection()->can_receive_ack_frequency_frame()); } -TEST_P(QuicSessionTestClient, KeyUpdateNotSupportedLocally) { - EXPECT_CALL(*session_.GetMutableCryptoStream(), KeyUpdateSupportedLocally()) - .WillOnce(Return(false)); - session_.Initialize(); - EXPECT_FALSE(session_.config()->KeyUpdateSupportedLocally()); -} - -TEST_P(QuicSessionTestClient, KeyUpdateSupportedLocally) { - EXPECT_CALL(*session_.GetMutableCryptoStream(), KeyUpdateSupportedLocally()) - .WillOnce(Return(true)); - session_.Initialize(); - EXPECT_TRUE(session_.config()->KeyUpdateSupportedLocally()); -} - TEST_P(QuicSessionTestClient, FailedToCreateStreamIfTooCloseToIdleTimeout) { connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); EXPECT_TRUE(session_.CanOpenNextOutgoingBidirectionalStream()); 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 f055bdf2e9f..6237dae2f62 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 @@ -506,13 +506,7 @@ bool QuicStream::OnStopSending(QuicResetStreamError error) { } stream_error_ = error; - if (GetQuicReloadableFlag(quic_match_ietf_reset_code)) { - QUIC_RELOADABLE_FLAG_COUNT(quic_match_ietf_reset_code); - MaybeSendRstStream(error); - } else { - MaybeSendRstStream( - QuicResetStreamError::FromInternal(error.internal_code())); - } + MaybeSendRstStream(error); return true; } diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_tag.cc b/chromium/net/third_party/quiche/src/quic/core/quic_tag.cc index 32b7bf9cd76..b5f64f1a775 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_tag.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_tag.cc @@ -66,7 +66,7 @@ std::string QuicTagToString(QuicTag tag) { reinterpret_cast<const char*>(&orig_tag), sizeof(orig_tag))); } -uint32_t MakeQuicTag(char a, char b, char c, char d) { +uint32_t MakeQuicTag(uint8_t a, uint8_t b, uint8_t c, uint8_t d) { return static_cast<uint32_t>(a) | static_cast<uint32_t>(b) << 8 | static_cast<uint32_t>(c) << 16 | static_cast<uint32_t>(d) << 24; } diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_tag.h b/chromium/net/third_party/quiche/src/quic/core/quic_tag.h index c77219eb321..1dfc754f870 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_tag.h +++ b/chromium/net/third_party/quiche/src/quic/core/quic_tag.h @@ -5,6 +5,7 @@ #ifndef QUICHE_QUIC_CORE_QUIC_TAG_H_ #define QUICHE_QUIC_CORE_QUIC_TAG_H_ +#include <cstdint> #include <map> #include <string> #include <vector> @@ -28,7 +29,8 @@ using QuicTagVector = std::vector<QuicTag>; // MakeQuicTag returns a value given the four bytes. For example: // MakeQuicTag('C', 'H', 'L', 'O'); -QUIC_EXPORT_PRIVATE QuicTag MakeQuicTag(char a, char b, char c, char d); +QUIC_EXPORT_PRIVATE QuicTag MakeQuicTag(uint8_t a, uint8_t b, uint8_t c, + uint8_t d); // Returns true if |tag_vector| contains |tag|. QUIC_EXPORT_PRIVATE bool ContainsQuicTag(const QuicTagVector& tag_vector, 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 200e843f826..a437c9ae731 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 @@ -13,11 +13,12 @@ #include <vector> #include "absl/container/inlined_vector.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" #include "quic/core/quic_connection_id.h" #include "quic/core/quic_error_codes.h" #include "quic/core/quic_packet_number.h" #include "quic/core/quic_time.h" -#include "quic/platform/api/quic_containers.h" #include "quic/platform/api/quic_export.h" #include "quic/platform/api/quic_flags.h" 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 3aa821c1cd4..13094564258 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 @@ -30,21 +30,17 @@ QuicVersionManager::GetSupportedVersionsWithOnlyHttp3() { return filtered_supported_versions_with_http3_; } -const ParsedQuicVersionVector& -QuicVersionManager::GetSupportedVersionsWithQuicCrypto() { - MaybeRefilterSupportedVersions(); - return filtered_supported_versions_with_quic_crypto_; -} - const std::vector<std::string>& QuicVersionManager::GetSupportedAlpns() { MaybeRefilterSupportedVersions(); return filtered_supported_alpns_; } void QuicVersionManager::MaybeRefilterSupportedVersions() { - static_assert(SupportedVersions().size() == 5u, + static_assert(SupportedVersions().size() == 6u, "Supported versions out of sync"); - if (disable_version_rfcv1_ != + if (enable_version_2_draft_01_ != + GetQuicReloadableFlag(quic_enable_version_2_draft_01) || + disable_version_rfcv1_ != GetQuicReloadableFlag(quic_disable_version_rfcv1) || disable_version_draft_29_ != GetQuicReloadableFlag(quic_disable_version_draft_29) || @@ -54,6 +50,8 @@ void QuicVersionManager::MaybeRefilterSupportedVersions() { GetQuicReloadableFlag(quic_disable_version_q046) || disable_version_q043_ != GetQuicReloadableFlag(quic_disable_version_q043)) { + enable_version_2_draft_01_ = + GetQuicReloadableFlag(quic_enable_version_2_draft_01); disable_version_rfcv1_ = GetQuicReloadableFlag(quic_disable_version_rfcv1); disable_version_draft_29_ = GetQuicReloadableFlag(quic_disable_version_draft_29); @@ -69,7 +67,6 @@ void QuicVersionManager::RefilterSupportedVersions() { filtered_supported_versions_ = FilterSupportedVersions(allowed_supported_versions_); filtered_supported_versions_with_http3_.clear(); - filtered_supported_versions_with_quic_crypto_.clear(); filtered_transport_versions_.clear(); filtered_supported_alpns_.clear(); for (const ParsedQuicVersion& version : filtered_supported_versions_) { @@ -82,10 +79,11 @@ void QuicVersionManager::RefilterSupportedVersions() { if (version.UsesHttp3()) { filtered_supported_versions_with_http3_.push_back(version); } - if (version.handshake_protocol == PROTOCOL_QUIC_CRYPTO) { - filtered_supported_versions_with_quic_crypto_.push_back(version); + if (std::find(filtered_supported_alpns_.begin(), + filtered_supported_alpns_.end(), + AlpnForVersion(version)) == filtered_supported_alpns_.end()) { + filtered_supported_alpns_.emplace_back(AlpnForVersion(version)); } - filtered_supported_alpns_.emplace_back(AlpnForVersion(version)); } } 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 a3ae7194ad4..44e134ac363 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 @@ -25,9 +25,6 @@ class QUIC_EXPORT_PRIVATE QuicVersionManager { // Returns currently supported versions using HTTP/3. const ParsedQuicVersionVector& GetSupportedVersionsWithOnlyHttp3(); - // Returns currently supported versions using QUIC crypto. - const ParsedQuicVersionVector& GetSupportedVersionsWithQuicCrypto(); - // Returns the list of supported ALPNs, based on the current supported // versions and any custom additions by subclasses. const std::vector<std::string>& GetSupportedAlpns(); @@ -58,6 +55,8 @@ class QUIC_EXPORT_PRIVATE QuicVersionManager { private: // Cached value of reloadable flags. + // quic_enable_version_2_draft_01 flag + bool enable_version_2_draft_01_ = false; // quic_disable_version_rfcv1 flag bool disable_version_rfcv1_ = true; // quic_disable_version_draft_29 flag @@ -82,8 +81,6 @@ class QUIC_EXPORT_PRIVATE QuicVersionManager { ParsedQuicVersionVector filtered_supported_versions_; // Currently supported versions using HTTP/3. ParsedQuicVersionVector filtered_supported_versions_with_http3_; - // Currently supported versions using QUIC crypto. - ParsedQuicVersionVector filtered_supported_versions_with_quic_crypto_; // This vector contains the transport versions from // |filtered_supported_versions_|. No guarantees are made that the same // transport version isn't repeated. diff --git a/chromium/net/third_party/quiche/src/quic/core/quic_version_manager_test.cc b/chromium/net/third_party/quiche/src/quic/core/quic_version_manager_test.cc index 8960bced5c0..da87c4a1d53 100644 --- a/chromium/net/third_party/quiche/src/quic/core/quic_version_manager_test.cc +++ b/chromium/net/third_party/quiche/src/quic/core/quic_version_manager_test.cc @@ -18,11 +18,12 @@ namespace { class QuicVersionManagerTest : public QuicTest {}; TEST_F(QuicVersionManagerTest, QuicVersionManager) { - static_assert(SupportedVersions().size() == 5u, + static_assert(SupportedVersions().size() == 6u, "Supported versions out of sync"); for (const ParsedQuicVersion& version : AllSupportedVersions()) { QuicEnableVersion(version); } + QuicDisableVersion(ParsedQuicVersion::V2Draft01()); QuicDisableVersion(ParsedQuicVersion::RFCv1()); QuicDisableVersion(ParsedQuicVersion::Draft29()); QuicVersionManager manager(AllSupportedVersions()); @@ -37,8 +38,6 @@ TEST_F(QuicVersionManagerTest, QuicVersionManager) { EXPECT_EQ(FilterSupportedVersions(AllSupportedVersions()), manager.GetSupportedVersions()); EXPECT_TRUE(manager.GetSupportedVersionsWithOnlyHttp3().empty()); - EXPECT_EQ(CurrentSupportedVersionsWithQuicCrypto(), - manager.GetSupportedVersionsWithQuicCrypto()); EXPECT_THAT(manager.GetSupportedAlpns(), ElementsAre("h3-Q050", "h3-Q046", "h3-Q043")); @@ -47,15 +46,11 @@ TEST_F(QuicVersionManagerTest, QuicVersionManager) { expected_parsed_versions.insert(expected_parsed_versions.begin(), ParsedQuicVersion::Draft29()); EXPECT_EQ(expected_parsed_versions, manager.GetSupportedVersions()); - EXPECT_EQ(expected_parsed_versions.size() - 1 - offset, - manager.GetSupportedVersionsWithQuicCrypto().size()); EXPECT_EQ(FilterSupportedVersions(AllSupportedVersions()), manager.GetSupportedVersions()); EXPECT_EQ(1u, manager.GetSupportedVersionsWithOnlyHttp3().size()); EXPECT_EQ(CurrentSupportedHttp3Versions(), manager.GetSupportedVersionsWithOnlyHttp3()); - EXPECT_EQ(CurrentSupportedVersionsWithQuicCrypto(), - manager.GetSupportedVersionsWithQuicCrypto()); EXPECT_THAT(manager.GetSupportedAlpns(), ElementsAre("h3-29", "h3-Q050", "h3-Q046", "h3-Q043")); @@ -64,17 +59,27 @@ TEST_F(QuicVersionManagerTest, QuicVersionManager) { expected_parsed_versions.insert(expected_parsed_versions.begin(), ParsedQuicVersion::RFCv1()); EXPECT_EQ(expected_parsed_versions, manager.GetSupportedVersions()); - EXPECT_EQ(expected_parsed_versions.size() - 1 - offset, - manager.GetSupportedVersionsWithQuicCrypto().size()); EXPECT_EQ(FilterSupportedVersions(AllSupportedVersions()), manager.GetSupportedVersions()); EXPECT_EQ(2u, manager.GetSupportedVersionsWithOnlyHttp3().size()); EXPECT_EQ(CurrentSupportedHttp3Versions(), manager.GetSupportedVersionsWithOnlyHttp3()); - EXPECT_EQ(CurrentSupportedVersionsWithQuicCrypto(), - manager.GetSupportedVersionsWithQuicCrypto()); EXPECT_THAT(manager.GetSupportedAlpns(), ElementsAre("h3", "h3-29", "h3-Q050", "h3-Q046", "h3-Q043")); + + offset++; + QuicEnableVersion(ParsedQuicVersion::V2Draft01()); + expected_parsed_versions.insert(expected_parsed_versions.begin(), + ParsedQuicVersion::V2Draft01()); + EXPECT_EQ(expected_parsed_versions, manager.GetSupportedVersions()); + EXPECT_EQ(FilterSupportedVersions(AllSupportedVersions()), + manager.GetSupportedVersions()); + EXPECT_EQ(3u, manager.GetSupportedVersionsWithOnlyHttp3().size()); + EXPECT_EQ(CurrentSupportedHttp3Versions(), + manager.GetSupportedVersionsWithOnlyHttp3()); + EXPECT_THAT( + manager.GetSupportedAlpns(), + ElementsAre("h3", "h3-29", "h3-Q050", "h3-Q046", "h3-Q043")); } } // namespace 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 4c9e661fa41..21ec163807c 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 @@ -23,12 +23,6 @@ namespace quic { namespace { -// Constructs a version label from the 4 bytes such that the on-the-wire -// order will be: d, c, b, a. -QuicVersionLabel MakeVersionLabel(char a, char b, char c, char d) { - return MakeQuicTag(d, c, b, a); -} - QuicVersionLabel CreateRandomVersionLabelForNegotiation() { QuicVersionLabel result; if (!GetQuicFlag(FLAGS_quic_disable_version_negotiation_grease_randomness)) { @@ -42,11 +36,13 @@ QuicVersionLabel CreateRandomVersionLabelForNegotiation() { } void SetVersionFlag(const ParsedQuicVersion& version, bool should_enable) { - static_assert(SupportedVersions().size() == 5u, + static_assert(SupportedVersions().size() == 6u, "Supported versions out of sync"); const bool enable = should_enable; const bool disable = !should_enable; - if (version == ParsedQuicVersion::RFCv1()) { + if (version == ParsedQuicVersion::V2Draft01()) { + SetQuicReloadableFlag(quic_enable_version_2_draft_01, enable); + } else if (version == ParsedQuicVersion::RFCv1()) { SetQuicReloadableFlag(quic_disable_version_rfcv1, disable); } else if (version == ParsedQuicVersion::Draft29()) { SetQuicReloadableFlag(quic_disable_version_draft_29, disable); @@ -182,6 +178,16 @@ bool ParsedQuicVersion::UsesQuicCrypto() const { return handshake_protocol == PROTOCOL_QUIC_CRYPTO; } +bool ParsedQuicVersion::UsesV2PacketTypes() const { + QUICHE_DCHECK(IsKnown()); + return transport_version == QUIC_VERSION_IETF_2_DRAFT_01; +} + +bool ParsedQuicVersion::AlpnDeferToRFCv1() const { + QUICHE_DCHECK(IsKnown()); + return transport_version == QUIC_VERSION_IETF_2_DRAFT_01; +} + bool VersionHasLengthPrefixedConnectionIds( QuicTransportVersion transport_version) { QUICHE_DCHECK(transport_version != QUIC_VERSION_UNSUPPORTED); @@ -200,6 +206,10 @@ std::ostream& operator<<(std::ostream& os, return os; } +QuicVersionLabel MakeVersionLabel(uint8_t a, uint8_t b, uint8_t c, uint8_t d) { + return MakeQuicTag(d, c, b, a); +} + std::ostream& operator<<(std::ostream& os, const QuicVersionLabelVector& version_labels) { os << QuicVersionLabelVectorToString(version_labels); @@ -213,9 +223,11 @@ std::ostream& operator<<(std::ostream& os, } QuicVersionLabel CreateQuicVersionLabel(ParsedQuicVersion parsed_version) { - static_assert(SupportedVersions().size() == 5u, + static_assert(SupportedVersions().size() == 6u, "Supported versions out of sync"); - if (parsed_version == ParsedQuicVersion::RFCv1()) { + if (parsed_version == ParsedQuicVersion::V2Draft01()) { + return MakeVersionLabel(0x70, 0x9a, 0x50, 0xc4); + } else if (parsed_version == ParsedQuicVersion::RFCv1()) { return MakeVersionLabel(0x00, 0x00, 0x00, 0x01); } else if (parsed_version == ParsedQuicVersion::Draft29()) { return MakeVersionLabel(0xff, 0x00, 0x00, 29); @@ -333,25 +345,11 @@ ParsedQuicVersion ParseQuicVersionString(absl::string_view version_string) { if (version_string.empty()) { return UnsupportedQuicVersion(); } - int quic_version_number = 0; const ParsedQuicVersionVector supported_versions = AllSupportedVersions(); - if (absl::SimpleAtoi(version_string, &quic_version_number) && - quic_version_number > 0) { - QuicTransportVersion transport_version = - static_cast<QuicTransportVersion>(quic_version_number); - if (!ParsedQuicVersionIsValid(PROTOCOL_QUIC_CRYPTO, transport_version)) { - return UnsupportedQuicVersion(); - } - ParsedQuicVersion version(PROTOCOL_QUIC_CRYPTO, transport_version); - if (std::find(supported_versions.begin(), supported_versions.end(), - version) != supported_versions.end()) { - return version; - } - return UnsupportedQuicVersion(); - } for (const ParsedQuicVersion& version : supported_versions) { if (version_string == ParsedQuicVersionToString(version) || - version_string == AlpnForVersion(version) || + (version_string == AlpnForVersion(version) && + !version.AlpnDeferToRFCv1()) || (version.handshake_protocol == PROTOCOL_QUIC_CRYPTO && version_string == QuicVersionToString(version.transport_version))) { return version; @@ -364,6 +362,21 @@ ParsedQuicVersion ParseQuicVersionString(absl::string_view version_string) { return version; } } + int quic_version_number = 0; + if (absl::SimpleAtoi(version_string, &quic_version_number) && + quic_version_number > 0) { + QuicTransportVersion transport_version = + static_cast<QuicTransportVersion>(quic_version_number); + if (!ParsedQuicVersionIsValid(PROTOCOL_QUIC_CRYPTO, transport_version)) { + return UnsupportedQuicVersion(); + } + ParsedQuicVersion version(PROTOCOL_QUIC_CRYPTO, transport_version); + if (std::find(supported_versions.begin(), supported_versions.end(), + version) != supported_versions.end()) { + return version; + } + return UnsupportedQuicVersion(); + } // Reading from the client so this should not be considered an ERROR. QUIC_DLOG(INFO) << "Unsupported QUIC version string: \"" << version_string << "\"."; @@ -411,10 +424,16 @@ ParsedQuicVersionVector CurrentSupportedVersions() { ParsedQuicVersionVector FilterSupportedVersions( ParsedQuicVersionVector versions) { + static_assert(SupportedVersions().size() == 6u, + "Supported versions out of sync"); ParsedQuicVersionVector filtered_versions; filtered_versions.reserve(versions.size()); for (const ParsedQuicVersion& version : versions) { - if (version == ParsedQuicVersion::RFCv1()) { + if (version == ParsedQuicVersion::V2Draft01()) { + if (GetQuicReloadableFlag(quic_enable_version_2_draft_01)) { + filtered_versions.push_back(version); + } + } else if (version == ParsedQuicVersion::RFCv1()) { if (!GetQuicReloadableFlag(quic_disable_version_rfcv1)) { filtered_versions.push_back(version); } @@ -444,8 +463,7 @@ ParsedQuicVersionVector FilterSupportedVersions( } ParsedQuicVersionVector ParsedVersionOfIndex( - const ParsedQuicVersionVector& versions, - int index) { + const ParsedQuicVersionVector& versions, int index) { ParsedQuicVersionVector version; int version_count = versions.size(); if (index >= 0 && index < version_count) { @@ -460,9 +478,20 @@ std::string QuicVersionLabelToString(QuicVersionLabel version_label) { return QuicTagToString(quiche::QuicheEndian::HostToNet32(version_label)); } +ParsedQuicVersion ParseQuicVersionLabelString( + absl::string_view version_label_string) { + const ParsedQuicVersionVector supported_versions = AllSupportedVersions(); + for (const ParsedQuicVersion& version : supported_versions) { + if (version_label_string == + QuicVersionLabelToString(CreateQuicVersionLabel(version))) { + return version; + } + } + return UnsupportedQuicVersion(); +} + std::string QuicVersionLabelVectorToString( - const QuicVersionLabelVector& version_labels, - const std::string& separator, + const QuicVersionLabelVector& version_labels, const std::string& separator, size_t skip_after_nth_version) { std::string result; for (size_t i = 0; i < version_labels.size(); ++i) { @@ -490,6 +519,7 @@ std::string QuicVersionToString(QuicTransportVersion transport_version) { RETURN_STRING_LITERAL(QUIC_VERSION_50); RETURN_STRING_LITERAL(QUIC_VERSION_IETF_DRAFT_29); RETURN_STRING_LITERAL(QUIC_VERSION_IETF_RFC_V1); + RETURN_STRING_LITERAL(QUIC_VERSION_IETF_2_DRAFT_01); RETURN_STRING_LITERAL(QUIC_VERSION_UNSUPPORTED); RETURN_STRING_LITERAL(QUIC_VERSION_RESERVED_FOR_NEGOTIATION); } @@ -508,10 +538,13 @@ std::string HandshakeProtocolToString(HandshakeProtocol handshake_protocol) { } std::string ParsedQuicVersionToString(ParsedQuicVersion version) { - static_assert(SupportedVersions().size() == 5u, + static_assert(SupportedVersions().size() == 6u, "Supported versions out of sync"); if (version == UnsupportedQuicVersion()) { return "0"; + } else if (version == ParsedQuicVersion::V2Draft01()) { + QUICHE_DCHECK(version.UsesHttp3()); + return "v2draft01"; } else if (version == ParsedQuicVersion::RFCv1()) { QUICHE_DCHECK(version.UsesHttp3()); return "RFCv1"; @@ -536,8 +569,7 @@ std::string QuicTransportVersionVectorToString( } std::string ParsedQuicVersionVectorToString( - const ParsedQuicVersionVector& versions, - const std::string& separator, + const ParsedQuicVersionVector& versions, const std::string& separator, size_t skip_after_nth_version) { std::string result; for (size_t i = 0; i < versions.size(); ++i) { @@ -604,7 +636,9 @@ ParsedQuicVersion LegacyVersionForEncapsulation() { } std::string AlpnForVersion(ParsedQuicVersion parsed_version) { - if (parsed_version == ParsedQuicVersion::RFCv1()) { + if (parsed_version == ParsedQuicVersion::V2Draft01()) { + return "h3"; + } else if (parsed_version == ParsedQuicVersion::RFCv1()) { return "h3"; } else if (parsed_version == ParsedQuicVersion::Draft29()) { return "h3-29"; 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 331a99c9759..59bf7532f3a 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 @@ -23,6 +23,7 @@ #ifndef QUICHE_QUIC_CORE_QUIC_VERSIONS_H_ #define QUICHE_QUIC_CORE_QUIC_VERSIONS_H_ +#include <cstdint> #include <string> #include <vector> @@ -122,8 +123,9 @@ enum QuicTransportVersion { // Number 70 used to represent draft-ietf-quic-transport-25. // Number 71 used to represent draft-ietf-quic-transport-27. // Number 72 used to represent draft-ietf-quic-transport-28. - QUIC_VERSION_IETF_DRAFT_29 = 73, // draft-ietf-quic-transport-29. - QUIC_VERSION_IETF_RFC_V1 = 80, // RFC 9000. + QUIC_VERSION_IETF_DRAFT_29 = 73, // draft-ietf-quic-transport-29. + QUIC_VERSION_IETF_RFC_V1 = 80, // RFC 9000. + QUIC_VERSION_IETF_2_DRAFT_01 = 81, // draft-ietf-quic-v2-01. // Version 99 was a dumping ground for IETF QUIC changes which were not yet // ready for production between 2018-02 and 2020-02. @@ -171,6 +173,7 @@ QUIC_EXPORT_PRIVATE constexpr bool ParsedQuicVersionIsValid( QuicTransportVersion transport_version) { bool transport_version_is_valid = false; constexpr QuicTransportVersion valid_transport_versions[] = { + QUIC_VERSION_IETF_2_DRAFT_01, QUIC_VERSION_IETF_RFC_V1, QUIC_VERSION_IETF_DRAFT_29, QUIC_VERSION_50, @@ -195,7 +198,8 @@ QUIC_EXPORT_PRIVATE constexpr bool ParsedQuicVersionIsValid( return transport_version != QUIC_VERSION_UNSUPPORTED && transport_version != QUIC_VERSION_RESERVED_FOR_NEGOTIATION && transport_version != QUIC_VERSION_IETF_DRAFT_29 && - transport_version != QUIC_VERSION_IETF_RFC_V1; + transport_version != QUIC_VERSION_IETF_RFC_V1 && + transport_version != QUIC_VERSION_IETF_2_DRAFT_01; case PROTOCOL_TLS1_3: return transport_version != QUIC_VERSION_UNSUPPORTED && transport_version != QUIC_VERSION_50 && @@ -245,6 +249,10 @@ struct QUIC_EXPORT_PRIVATE ParsedQuicVersion { transport_version != other.transport_version; } + static constexpr ParsedQuicVersion V2Draft01() { + return ParsedQuicVersion(PROTOCOL_TLS1_3, QUIC_VERSION_IETF_2_DRAFT_01); + } + static constexpr ParsedQuicVersion RFCv1() { return ParsedQuicVersion(PROTOCOL_TLS1_3, QUIC_VERSION_IETF_RFC_V1); } @@ -361,6 +369,14 @@ struct QUIC_EXPORT_PRIVATE ParsedQuicVersion { // Returns whether this version uses PROTOCOL_QUIC_CRYPTO. bool UsesQuicCrypto() const; + + // Returns whether this version uses the QUICv2 Long Header Packet Types. + bool UsesV2PacketTypes() const; + + // Returns true if this shares ALPN codes with RFCv1, and endpoints should + // choose RFCv1 when presented with a v1 ALPN. Note that this is false for + // RFCv1. + bool AlpnDeferToRFCv1() const; }; QUIC_EXPORT_PRIVATE ParsedQuicVersion UnsupportedQuicVersion(); @@ -377,36 +393,38 @@ QUIC_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os, using ParsedQuicVersionVector = std::vector<ParsedQuicVersion>; QUIC_EXPORT_PRIVATE std::ostream& operator<<( - std::ostream& os, - const ParsedQuicVersionVector& versions); + std::ostream& os, const ParsedQuicVersionVector& versions); // Representation of the on-the-wire QUIC version number. Will be written/read // to the wire in network-byte-order. using QuicVersionLabel = uint32_t; using QuicVersionLabelVector = std::vector<QuicVersionLabel>; +// Constructs a version label from the 4 bytes such that the on-the-wire +// order will be: d, c, b, a. +QUIC_EXPORT_PRIVATE QuicVersionLabel MakeVersionLabel(uint8_t a, uint8_t b, + uint8_t c, uint8_t d); + QUIC_EXPORT_PRIVATE std::ostream& operator<<( - std::ostream& os, - const QuicVersionLabelVector& version_labels); + std::ostream& os, const QuicVersionLabelVector& version_labels); // This vector contains all crypto handshake protocols that are supported. constexpr std::array<HandshakeProtocol, 2> SupportedHandshakeProtocols() { return {PROTOCOL_TLS1_3, PROTOCOL_QUIC_CRYPTO}; } -constexpr std::array<ParsedQuicVersion, 5> SupportedVersions() { +constexpr std::array<ParsedQuicVersion, 6> SupportedVersions() { return { - ParsedQuicVersion::RFCv1(), ParsedQuicVersion::Draft29(), - ParsedQuicVersion::Q050(), ParsedQuicVersion::Q046(), - ParsedQuicVersion::Q043(), + ParsedQuicVersion::V2Draft01(), ParsedQuicVersion::RFCv1(), + ParsedQuicVersion::Draft29(), ParsedQuicVersion::Q050(), + ParsedQuicVersion::Q046(), ParsedQuicVersion::Q043(), }; } using QuicTransportVersionVector = std::vector<QuicTransportVersion>; QUIC_EXPORT_PRIVATE std::ostream& operator<<( - std::ostream& os, - const QuicTransportVersionVector& transport_versions); + std::ostream& os, const QuicTransportVersionVector& transport_versions); // Returns a vector of supported QUIC versions. QUIC_EXPORT_PRIVATE ParsedQuicVersionVector AllSupportedVersions(); @@ -490,12 +508,20 @@ CreateQuicVersionLabelVector(const ParsedQuicVersionVector& versions); QUIC_EXPORT_PRIVATE std::string QuicVersionLabelToString( QuicVersionLabel version_label); +// Helper function which translates from a QuicVersionLabel string to a +// ParsedQuicVersion. The version label string must be of the form returned +// by QuicVersionLabelToString, for example, "00000001" or "Q046", but not +// "51303433" (the hex encoding of the Q064 version label). Returns +// the ParsedQuicVersion which matches the label or UnsupportedQuicVersion() +// otherwise. +QUIC_EXPORT_PRIVATE ParsedQuicVersion +ParseQuicVersionLabelString(absl::string_view version_label_string); + // Returns |separator|-separated list of string representations of // QuicVersionLabel values in the supplied |version_labels| vector. The values // after the (0-based) |skip_after_nth_version|'th are skipped. QUIC_EXPORT_PRIVATE std::string QuicVersionLabelVectorToString( - const QuicVersionLabelVector& version_labels, - const std::string& separator, + const QuicVersionLabelVector& version_labels, const std::string& separator, size_t skip_after_nth_version); // Returns comma separated list of string representations of QuicVersionLabel @@ -529,8 +555,7 @@ QUIC_EXPORT_PRIVATE std::string ParsedQuicVersionVectorToString( // ParsedQuicVersion values in the supplied |versions| vector. The values after // the (0-based) |skip_after_nth_version|'th are skipped. QUIC_EXPORT_PRIVATE std::string ParsedQuicVersionVectorToString( - const ParsedQuicVersionVector& versions, - const std::string& separator, + const ParsedQuicVersionVector& versions, const std::string& separator, size_t skip_after_nth_version); // Returns comma separated list of string representations of ParsedQuicVersion 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 60790d61ece..8d420acaf3d 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 @@ -15,24 +15,16 @@ namespace quic { namespace test { namespace { -using ::testing::_; using ::testing::ElementsAre; using ::testing::IsEmpty; -class QuicVersionsTest : public QuicTest { - protected: - QuicVersionLabel MakeVersionLabel(char a, char b, char c, char d) { - return MakeQuicTag(d, c, b, a); - } -}; - -TEST_F(QuicVersionsTest, CreateQuicVersionLabelUnsupported) { +TEST(QuicVersionsTest, CreateQuicVersionLabelUnsupported) { EXPECT_QUIC_BUG( CreateQuicVersionLabel(UnsupportedQuicVersion()), "Unsupported version QUIC_VERSION_UNSUPPORTED PROTOCOL_UNSUPPORTED"); } -TEST_F(QuicVersionsTest, KnownAndValid) { +TEST(QuicVersionsTest, KnownAndValid) { for (const ParsedQuicVersion& version : AllSupportedVersions()) { EXPECT_TRUE(version.IsKnown()); EXPECT_TRUE(ParsedQuicVersionIsValid(version.handshake_protocol, @@ -59,7 +51,7 @@ TEST_F(QuicVersionsTest, KnownAndValid) { static_cast<QuicTransportVersion>(99))); } -TEST_F(QuicVersionsTest, Features) { +TEST(QuicVersionsTest, Features) { ParsedQuicVersion parsed_version_q043 = ParsedQuicVersion::Q043(); ParsedQuicVersion parsed_version_draft_29 = ParsedQuicVersion::Draft29(); @@ -110,7 +102,9 @@ TEST_F(QuicVersionsTest, Features) { EXPECT_FALSE(parsed_version_draft_29.UsesQuicCrypto()); } -TEST_F(QuicVersionsTest, ParseQuicVersionLabel) { +TEST(QuicVersionsTest, ParseQuicVersionLabel) { + static_assert(SupportedVersions().size() == 6u, + "Supported versions out of sync"); EXPECT_EQ(ParsedQuicVersion::Q043(), ParseQuicVersionLabel(MakeVersionLabel('Q', '0', '4', '3'))); EXPECT_EQ(ParsedQuicVersion::Q046(), @@ -121,15 +115,25 @@ TEST_F(QuicVersionsTest, ParseQuicVersionLabel) { ParseQuicVersionLabel(MakeVersionLabel(0xff, 0x00, 0x00, 0x1d))); EXPECT_EQ(ParsedQuicVersion::RFCv1(), ParseQuicVersionLabel(MakeVersionLabel(0x00, 0x00, 0x00, 0x01))); - EXPECT_EQ((ParsedQuicVersionVector{ParsedQuicVersion::RFCv1(), + EXPECT_EQ(ParsedQuicVersion::V2Draft01(), + ParseQuicVersionLabel(MakeVersionLabel(0x70, 0x9a, 0x50, 0xc4))); + EXPECT_EQ((ParsedQuicVersionVector{ParsedQuicVersion::V2Draft01(), + ParsedQuicVersion::RFCv1(), ParsedQuicVersion::Draft29()}), ParseQuicVersionLabelVector(QuicVersionLabelVector{ + MakeVersionLabel(0x70, 0x9a, 0x50, 0xc4), MakeVersionLabel(0x00, 0x00, 0x00, 0x01), MakeVersionLabel(0xaa, 0xaa, 0xaa, 0xaa), MakeVersionLabel(0xff, 0x00, 0x00, 0x1d)})); + + for (const ParsedQuicVersion& version : AllSupportedVersions()) { + EXPECT_EQ(version, ParseQuicVersionLabel(CreateQuicVersionLabel(version))); + } } -TEST_F(QuicVersionsTest, ParseQuicVersionString) { +TEST(QuicVersionsTest, ParseQuicVersionString) { + static_assert(SupportedVersions().size() == 6u, + "Supported versions out of sync"); EXPECT_EQ(ParsedQuicVersion::Q043(), ParseQuicVersionString("Q043")); EXPECT_EQ(ParsedQuicVersion::Q046(), ParseQuicVersionString("QUIC_VERSION_46")); @@ -149,13 +153,23 @@ TEST_F(QuicVersionsTest, ParseQuicVersionString) { EXPECT_EQ(ParsedQuicVersion::Draft29(), ParseQuicVersionString("draft29")); EXPECT_EQ(ParsedQuicVersion::Draft29(), ParseQuicVersionString("h3-29")); + EXPECT_EQ(ParsedQuicVersion::RFCv1(), ParseQuicVersionString("00000001")); + EXPECT_EQ(ParsedQuicVersion::RFCv1(), ParseQuicVersionString("h3")); + + // QUICv2 will never be the result for "h3". + for (const ParsedQuicVersion& version : AllSupportedVersions()) { EXPECT_EQ(version, ParseQuicVersionString(ParsedQuicVersionToString(version))); + EXPECT_EQ(version, ParseQuicVersionString(QuicVersionLabelToString( + CreateQuicVersionLabel(version)))); + if (!version.AlpnDeferToRFCv1()) { + EXPECT_EQ(version, ParseQuicVersionString(AlpnForVersion(version))); + } } } -TEST_F(QuicVersionsTest, ParseQuicVersionVectorString) { +TEST(QuicVersionsTest, ParseQuicVersionVectorString) { ParsedQuicVersion version_q046 = ParsedQuicVersion::Q046(); ParsedQuicVersion version_q050 = ParsedQuicVersion::Q050(); ParsedQuicVersion version_draft_29 = ParsedQuicVersion::Draft29(); @@ -207,53 +221,101 @@ TEST_F(QuicVersionsTest, ParseQuicVersionVectorString) { ElementsAre(version_draft_29)); } -TEST_F(QuicVersionsTest, CreateQuicVersionLabel) { - EXPECT_EQ(MakeVersionLabel('Q', '0', '4', '3'), - CreateQuicVersionLabel(ParsedQuicVersion::Q043())); - EXPECT_EQ(MakeVersionLabel('Q', '0', '4', '6'), - CreateQuicVersionLabel(ParsedQuicVersion::Q046())); - EXPECT_EQ(MakeVersionLabel('Q', '0', '5', '0'), - CreateQuicVersionLabel(ParsedQuicVersion::Q050())); - EXPECT_EQ(MakeVersionLabel(0xff, 0x00, 0x00, 0x1d), - CreateQuicVersionLabel(ParsedQuicVersion::Draft29())); - EXPECT_EQ(MakeVersionLabel(0x00, 0x00, 0x00, 0x01), - CreateQuicVersionLabel(ParsedQuicVersion::RFCv1())); +// Do not use MakeVersionLabel() to generate expectations, because +// CreateQuicVersionLabel() uses MakeVersionLabel() internally, +// in case it has a bug. +TEST(QuicVersionsTest, CreateQuicVersionLabel) { + static_assert(SupportedVersions().size() == 6u, + "Supported versions out of sync"); + EXPECT_EQ(0x51303433u, CreateQuicVersionLabel(ParsedQuicVersion::Q043())); + EXPECT_EQ(0x51303436u, CreateQuicVersionLabel(ParsedQuicVersion::Q046())); + EXPECT_EQ(0x51303530u, CreateQuicVersionLabel(ParsedQuicVersion::Q050())); + EXPECT_EQ(0xff00001du, CreateQuicVersionLabel(ParsedQuicVersion::Draft29())); + EXPECT_EQ(0x00000001u, CreateQuicVersionLabel(ParsedQuicVersion::RFCv1())); + EXPECT_EQ(0x709a50c4u, + CreateQuicVersionLabel(ParsedQuicVersion::V2Draft01())); // Make sure the negotiation reserved version is in the IETF reserved space. EXPECT_EQ( - MakeVersionLabel(0xda, 0x5a, 0x3a, 0x3a) & 0x0f0f0f0f, + 0xda5a3a3au & 0x0f0f0f0f, CreateQuicVersionLabel(ParsedQuicVersion::ReservedForNegotiation()) & 0x0f0f0f0f); // Make sure that disabling randomness works. SetQuicFlag(FLAGS_quic_disable_version_negotiation_grease_randomness, true); - EXPECT_EQ( - MakeVersionLabel(0xda, 0x5a, 0x3a, 0x3a), - CreateQuicVersionLabel(ParsedQuicVersion::ReservedForNegotiation())); + EXPECT_EQ(0xda5a3a3au, CreateQuicVersionLabel( + ParsedQuicVersion::ReservedForNegotiation())); } -TEST_F(QuicVersionsTest, QuicVersionLabelToString) { +TEST(QuicVersionsTest, QuicVersionLabelToString) { + static_assert(SupportedVersions().size() == 6u, + "Supported versions out of sync"); + EXPECT_EQ("Q043", QuicVersionLabelToString( + CreateQuicVersionLabel(ParsedQuicVersion::Q043()))); + EXPECT_EQ("Q046", QuicVersionLabelToString( + CreateQuicVersionLabel(ParsedQuicVersion::Q046()))); + EXPECT_EQ("Q050", QuicVersionLabelToString( + CreateQuicVersionLabel(ParsedQuicVersion::Q050()))); + EXPECT_EQ("ff00001d", QuicVersionLabelToString(CreateQuicVersionLabel( + ParsedQuicVersion::Draft29()))); + EXPECT_EQ("00000001", QuicVersionLabelToString(CreateQuicVersionLabel( + ParsedQuicVersion::RFCv1()))); + EXPECT_EQ("709a50c4", QuicVersionLabelToString(CreateQuicVersionLabel( + ParsedQuicVersion::V2Draft01()))); + QuicVersionLabelVector version_labels = { MakeVersionLabel('Q', '0', '3', '5'), - MakeVersionLabel('Q', '0', '3', '7'), MakeVersionLabel('T', '0', '3', '8'), + MakeVersionLabel(0xff, 0, 0, 7), }; EXPECT_EQ("Q035", QuicVersionLabelToString(version_labels[0])); - EXPECT_EQ("T038", QuicVersionLabelToString(version_labels[2])); + EXPECT_EQ("T038", QuicVersionLabelToString(version_labels[1])); + EXPECT_EQ("ff000007", QuicVersionLabelToString(version_labels[2])); - EXPECT_EQ("Q035,Q037,T038", QuicVersionLabelVectorToString(version_labels)); - EXPECT_EQ("Q035:Q037:T038", + EXPECT_EQ("Q035,T038,ff000007", + QuicVersionLabelVectorToString(version_labels)); + EXPECT_EQ("Q035:T038:ff000007", QuicVersionLabelVectorToString(version_labels, ":", 2)); - EXPECT_EQ("Q035|Q037|...", + EXPECT_EQ("Q035|T038|...", QuicVersionLabelVectorToString(version_labels, "|", 1)); std::ostringstream os; os << version_labels; - EXPECT_EQ("Q035,Q037,T038", os.str()); + EXPECT_EQ("Q035,T038,ff000007", os.str()); } -TEST_F(QuicVersionsTest, QuicVersionToString) { +TEST(QuicVersionsTest, ParseQuicVersionLabelString) { + static_assert(SupportedVersions().size() == 6u, + "Supported versions out of sync"); + // Explicitly test known QUIC version label strings. + EXPECT_EQ(ParsedQuicVersion::Q043(), ParseQuicVersionLabelString("Q043")); + EXPECT_EQ(ParsedQuicVersion::Q046(), ParseQuicVersionLabelString("Q046")); + EXPECT_EQ(ParsedQuicVersion::Q050(), ParseQuicVersionLabelString("Q050")); + EXPECT_EQ(ParsedQuicVersion::Draft29(), + ParseQuicVersionLabelString("ff00001d")); + EXPECT_EQ(ParsedQuicVersion::RFCv1(), + ParseQuicVersionLabelString("00000001")); + EXPECT_EQ(ParsedQuicVersion::V2Draft01(), + ParseQuicVersionLabelString("709a50c4")); + + // Sanity check that a variety of other serialization formats are ignored. + EXPECT_EQ(UnsupportedQuicVersion(), ParseQuicVersionLabelString("1")); + EXPECT_EQ(UnsupportedQuicVersion(), ParseQuicVersionLabelString("46")); + EXPECT_EQ(UnsupportedQuicVersion(), + ParseQuicVersionLabelString("QUIC_VERSION_46")); + EXPECT_EQ(UnsupportedQuicVersion(), ParseQuicVersionLabelString("h3")); + EXPECT_EQ(UnsupportedQuicVersion(), ParseQuicVersionLabelString("h3-29")); + + // Test round-trips between QuicVersionLabelToString and + // ParseQuicVersionLabelString. + for (const ParsedQuicVersion& version : AllSupportedVersions()) { + EXPECT_EQ(version, ParseQuicVersionLabelString(QuicVersionLabelToString( + CreateQuicVersionLabel(version)))); + } +} + +TEST(QuicVersionsTest, QuicVersionToString) { EXPECT_EQ("QUIC_VERSION_UNSUPPORTED", QuicVersionToString(QUIC_VERSION_UNSUPPORTED)); @@ -285,13 +347,15 @@ TEST_F(QuicVersionsTest, QuicVersionToString) { EXPECT_EQ("QUIC_VERSION_UNSUPPORTED,QUIC_VERSION_43", os.str()); } -TEST_F(QuicVersionsTest, ParsedQuicVersionToString) { +TEST(QuicVersionsTest, ParsedQuicVersionToString) { EXPECT_EQ("0", ParsedQuicVersionToString(ParsedQuicVersion::Unsupported())); EXPECT_EQ("Q043", ParsedQuicVersionToString(ParsedQuicVersion::Q043())); EXPECT_EQ("Q046", ParsedQuicVersionToString(ParsedQuicVersion::Q046())); EXPECT_EQ("Q050", ParsedQuicVersionToString(ParsedQuicVersion::Q050())); EXPECT_EQ("draft29", ParsedQuicVersionToString(ParsedQuicVersion::Draft29())); EXPECT_EQ("RFCv1", ParsedQuicVersionToString(ParsedQuicVersion::RFCv1())); + EXPECT_EQ("v2draft01", + ParsedQuicVersionToString(ParsedQuicVersion::V2Draft01())); ParsedQuicVersionVector versions_vector = {ParsedQuicVersion::Q043()}; EXPECT_EQ("Q043", ParsedQuicVersionVectorToString(versions_vector)); @@ -314,7 +378,7 @@ TEST_F(QuicVersionsTest, ParsedQuicVersionToString) { EXPECT_EQ("0,Q043", os.str()); } -TEST_F(QuicVersionsTest, FilterSupportedVersionsAllVersions) { +TEST(QuicVersionsTest, FilterSupportedVersionsAllVersions) { for (const ParsedQuicVersion& version : AllSupportedVersions()) { QuicEnableVersion(version); } @@ -327,7 +391,7 @@ TEST_F(QuicVersionsTest, FilterSupportedVersionsAllVersions) { EXPECT_EQ(expected_parsed_versions, AllSupportedVersions()); } -TEST_F(QuicVersionsTest, FilterSupportedVersionsWithoutFirstVersion) { +TEST(QuicVersionsTest, FilterSupportedVersionsWithoutFirstVersion) { for (const ParsedQuicVersion& version : AllSupportedVersions()) { QuicEnableVersion(version); } @@ -341,7 +405,7 @@ TEST_F(QuicVersionsTest, FilterSupportedVersionsWithoutFirstVersion) { FilterSupportedVersions(AllSupportedVersions())); } -TEST_F(QuicVersionsTest, LookUpParsedVersionByIndex) { +TEST(QuicVersionsTest, LookUpParsedVersionByIndex) { ParsedQuicVersionVector all_versions = AllSupportedVersions(); int version_count = all_versions.size(); for (int i = -5; i <= version_count + 1; ++i) { @@ -357,27 +421,29 @@ TEST_F(QuicVersionsTest, LookUpParsedVersionByIndex) { // This test may appear to be so simplistic as to be unnecessary, // yet a typo was made in doing the #defines and it was caught // only in some test far removed from here... Better safe than sorry. -TEST_F(QuicVersionsTest, CheckTransportVersionNumbersForTypos) { - static_assert(SupportedVersions().size() == 5u, +TEST(QuicVersionsTest, CheckTransportVersionNumbersForTypos) { + static_assert(SupportedVersions().size() == 6u, "Supported versions out of sync"); EXPECT_EQ(QUIC_VERSION_43, 43); EXPECT_EQ(QUIC_VERSION_46, 46); EXPECT_EQ(QUIC_VERSION_50, 50); EXPECT_EQ(QUIC_VERSION_IETF_DRAFT_29, 73); EXPECT_EQ(QUIC_VERSION_IETF_RFC_V1, 80); + EXPECT_EQ(QUIC_VERSION_IETF_2_DRAFT_01, 81); } -TEST_F(QuicVersionsTest, AlpnForVersion) { - static_assert(SupportedVersions().size() == 5u, +TEST(QuicVersionsTest, AlpnForVersion) { + static_assert(SupportedVersions().size() == 6u, "Supported versions out of sync"); EXPECT_EQ("h3-Q043", AlpnForVersion(ParsedQuicVersion::Q043())); EXPECT_EQ("h3-Q046", AlpnForVersion(ParsedQuicVersion::Q046())); EXPECT_EQ("h3-Q050", AlpnForVersion(ParsedQuicVersion::Q050())); EXPECT_EQ("h3-29", AlpnForVersion(ParsedQuicVersion::Draft29())); EXPECT_EQ("h3", AlpnForVersion(ParsedQuicVersion::RFCv1())); + EXPECT_EQ("h3", AlpnForVersion(ParsedQuicVersion::V2Draft01())); } -TEST_F(QuicVersionsTest, QuicVersionEnabling) { +TEST(QuicVersionsTest, QuicVersionEnabling) { for (const ParsedQuicVersion& version : AllSupportedVersions()) { QuicFlagSaver flag_saver; QuicDisableVersion(version); @@ -387,7 +453,7 @@ TEST_F(QuicVersionsTest, QuicVersionEnabling) { } } -TEST_F(QuicVersionsTest, ReservedForNegotiation) { +TEST(QuicVersionsTest, ReservedForNegotiation) { EXPECT_EQ(QUIC_VERSION_RESERVED_FOR_NEGOTIATION, QuicVersionReservedForNegotiation().transport_version); // QUIC_VERSION_RESERVED_FOR_NEGOTIATION MUST NOT be supported. @@ -396,7 +462,7 @@ TEST_F(QuicVersionsTest, ReservedForNegotiation) { } } -TEST_F(QuicVersionsTest, SupportedVersionsHasCorrectList) { +TEST(QuicVersionsTest, SupportedVersionsHasCorrectList) { size_t index = 0; for (HandshakeProtocol handshake_protocol : SupportedHandshakeProtocols()) { for (int trans_vers = 255; trans_vers > 0; trans_vers--) { @@ -414,7 +480,7 @@ TEST_F(QuicVersionsTest, SupportedVersionsHasCorrectList) { EXPECT_EQ(SupportedVersions().size(), index); } -TEST_F(QuicVersionsTest, SupportedVersionsAllDistinct) { +TEST(QuicVersionsTest, SupportedVersionsAllDistinct) { for (size_t index1 = 0; index1 < SupportedVersions().size(); ++index1) { ParsedQuicVersion version1 = SupportedVersions()[index1]; for (size_t index2 = index1 + 1; index2 < SupportedVersions().size(); @@ -424,13 +490,17 @@ TEST_F(QuicVersionsTest, SupportedVersionsAllDistinct) { EXPECT_NE(CreateQuicVersionLabel(version1), CreateQuicVersionLabel(version2)) << version1 << " " << version2; - EXPECT_NE(AlpnForVersion(version1), AlpnForVersion(version2)) - << version1 << " " << version2; + // The one pair where ALPNs are the same. + if ((version1 != ParsedQuicVersion::V2Draft01()) && + (version2 != ParsedQuicVersion::RFCv1())) { + EXPECT_NE(AlpnForVersion(version1), AlpnForVersion(version2)) + << version1 << " " << version2; + } } } } -TEST_F(QuicVersionsTest, CurrentSupportedHttp3Versions) { +TEST(QuicVersionsTest, CurrentSupportedHttp3Versions) { ParsedQuicVersionVector h3_versions = CurrentSupportedHttp3Versions(); ParsedQuicVersionVector all_current_supported_versions = CurrentSupportedVersions(); 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 c8306a83590..3cd47ad04c4 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 @@ -209,15 +209,17 @@ bool TlsClientHandshaker::SetAlpn() { // Enable ALPS only for versions that use HTTP/3 frames. for (const std::string& alpn_string : alpns) { - ParsedQuicVersion version = ParseQuicVersionString(alpn_string); - if (!version.IsKnown() || !version.UsesHttp3()) { - continue; - } - if (SSL_add_application_settings( - ssl(), reinterpret_cast<const uint8_t*>(alpn_string.data()), - alpn_string.size(), nullptr, /* settings_len = */ 0) != 1) { - QUIC_BUG(quic_bug_10576_7) << "Failed to enable ALPS."; - return false; + for (const ParsedQuicVersion& version : session()->supported_versions()) { + if (!version.UsesHttp3() || AlpnForVersion(version) != alpn_string) { + continue; + } + if (SSL_add_application_settings( + ssl(), reinterpret_cast<const uint8_t*>(alpn_string.data()), + alpn_string.size(), nullptr, /* settings_len = */ 0) != 1) { + QUIC_BUG(quic_bug_10576_7) << "Failed to enable ALPS."; + return false; + } + break; } } @@ -240,9 +242,6 @@ bool TlsClientHandshaker::SetTransportParameters() { if (!handshaker_delegate()->FillTransportParameters(¶ms)) { return false; } - if (!user_agent_id_.empty()) { - params.user_agent_id = user_agent_id_; - } // Notify QuicConnectionDebugVisitor. session()->connection()->OnTransportParametersSent(params); @@ -395,10 +394,6 @@ size_t TlsClientHandshaker::BufferSizeLimitForLevel( return TlsHandshaker::BufferSizeLimitForLevel(level); } -bool TlsClientHandshaker::KeyUpdateSupportedLocally() const { - return true; -} - std::unique_ptr<QuicDecrypter> TlsClientHandshaker::AdvanceKeysAndCreateCurrentOneRttDecrypter() { return TlsHandshaker::AdvanceKeysAndCreateCurrentOneRttDecrypter(); diff --git a/chromium/net/third_party/quiche/src/quic/core/tls_client_handshaker.h b/chromium/net/third_party/quiche/src/quic/core/tls_client_handshaker.h index 4543f9b2007..c7db0edd36d 100644 --- a/chromium/net/third_party/quiche/src/quic/core/tls_client_handshaker.h +++ b/chromium/net/third_party/quiche/src/quic/core/tls_client_handshaker.h @@ -61,7 +61,6 @@ class QUIC_EXPORT_PRIVATE TlsClientHandshaker CryptoMessageParser* crypto_message_parser() override; HandshakeState GetHandshakeState() const override; size_t BufferSizeLimitForLevel(EncryptionLevel level) const override; - bool KeyUpdateSupportedLocally() const override; std::unique_ptr<QuicDecrypter> AdvanceKeysAndCreateCurrentOneRttDecrypter() override; std::unique_ptr<QuicEncrypter> CreateCurrentOneRttEncrypter() override; 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 ac959f251d4..2eee1cc7559 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 @@ -25,8 +25,7 @@ TlsHandshaker::ProofVerifierCallbackImpl::ProofVerifierCallbackImpl( TlsHandshaker::ProofVerifierCallbackImpl::~ProofVerifierCallbackImpl() {} void TlsHandshaker::ProofVerifierCallbackImpl::Run( - bool ok, - const std::string& /*error_details*/, + bool ok, const std::string& /*error_details*/, std::unique_ptr<ProofVerifyDetails>* details) { if (parent_ == nullptr) { return; @@ -42,9 +41,7 @@ void TlsHandshaker::ProofVerifierCallbackImpl::Run( parent_->AdvanceHandshake(); } -void TlsHandshaker::ProofVerifierCallbackImpl::Cancel() { - parent_ = nullptr; -} +void TlsHandshaker::ProofVerifierCallbackImpl::Cancel() { parent_ = nullptr; } TlsHandshaker::TlsHandshaker(QuicCryptoStream* stream, QuicSession* session) : stream_(stream), handshaker_delegate_(session) {} @@ -243,10 +240,13 @@ void TlsHandshaker::SetWriteSecret(EncryptionLevel level, std::unique_ptr<QuicEncrypter> encrypter = QuicEncrypter::CreateFromCipherSuite(SSL_CIPHER_get_id(cipher)); const EVP_MD* prf = Prf(cipher); - CryptoUtils::SetKeyAndIV(prf, write_secret, encrypter.get()); + CryptoUtils::SetKeyAndIV(prf, write_secret, + handshaker_delegate_->parsed_version(), + encrypter.get()); std::vector<uint8_t> header_protection_key = - CryptoUtils::GenerateHeaderProtectionKey(prf, write_secret, - encrypter->GetKeySize()); + CryptoUtils::GenerateHeaderProtectionKey( + prf, write_secret, handshaker_delegate_->parsed_version(), + encrypter->GetKeySize()); encrypter->SetHeaderProtectionKey( absl::string_view(reinterpret_cast<char*>(header_protection_key.data()), header_protection_key.size())); @@ -266,10 +266,13 @@ bool TlsHandshaker::SetReadSecret(EncryptionLevel level, std::unique_ptr<QuicDecrypter> decrypter = QuicDecrypter::CreateFromCipherSuite(SSL_CIPHER_get_id(cipher)); const EVP_MD* prf = Prf(cipher); - CryptoUtils::SetKeyAndIV(prf, read_secret, decrypter.get()); + CryptoUtils::SetKeyAndIV(prf, read_secret, + handshaker_delegate_->parsed_version(), + decrypter.get()); std::vector<uint8_t> header_protection_key = - CryptoUtils::GenerateHeaderProtectionKey(prf, read_secret, - decrypter->GetKeySize()); + CryptoUtils::GenerateHeaderProtectionKey( + prf, read_secret, handshaker_delegate_->parsed_version(), + decrypter->GetKeySize()); decrypter->SetHeaderProtectionKey( absl::string_view(reinterpret_cast<char*>(header_protection_key.data()), header_protection_key.size())); @@ -296,14 +299,16 @@ TlsHandshaker::AdvanceKeysAndCreateCurrentOneRttDecrypter() { } const SSL_CIPHER* cipher = SSL_get_current_cipher(ssl()); const EVP_MD* prf = Prf(cipher); - latest_read_secret_ = - CryptoUtils::GenerateNextKeyPhaseSecret(prf, latest_read_secret_); - latest_write_secret_ = - CryptoUtils::GenerateNextKeyPhaseSecret(prf, latest_write_secret_); + latest_read_secret_ = CryptoUtils::GenerateNextKeyPhaseSecret( + prf, handshaker_delegate_->parsed_version(), latest_read_secret_); + latest_write_secret_ = CryptoUtils::GenerateNextKeyPhaseSecret( + prf, handshaker_delegate_->parsed_version(), latest_write_secret_); std::unique_ptr<QuicDecrypter> decrypter = QuicDecrypter::CreateFromCipherSuite(SSL_CIPHER_get_id(cipher)); - CryptoUtils::SetKeyAndIV(prf, latest_read_secret_, decrypter.get()); + CryptoUtils::SetKeyAndIV(prf, latest_read_secret_, + handshaker_delegate_->parsed_version(), + decrypter.get()); decrypter->SetHeaderProtectionKey(absl::string_view( reinterpret_cast<char*>(one_rtt_read_header_protection_key_.data()), one_rtt_read_header_protection_key_.size())); @@ -322,7 +327,9 @@ std::unique_ptr<QuicEncrypter> TlsHandshaker::CreateCurrentOneRttEncrypter() { const SSL_CIPHER* cipher = SSL_get_current_cipher(ssl()); std::unique_ptr<QuicEncrypter> encrypter = QuicEncrypter::CreateFromCipherSuite(SSL_CIPHER_get_id(cipher)); - CryptoUtils::SetKeyAndIV(Prf(cipher), latest_write_secret_, encrypter.get()); + CryptoUtils::SetKeyAndIV(Prf(cipher), latest_write_secret_, + handshaker_delegate_->parsed_version(), + encrypter.get()); encrypter->SetHeaderProtectionKey(absl::string_view( reinterpret_cast<char*>(one_rtt_write_header_protection_key_.data()), one_rtt_write_header_protection_key_.size())); @@ -333,9 +340,7 @@ bool TlsHandshaker::ExportKeyingMaterialForLabel(absl::string_view label, absl::string_view context, size_t result_len, std::string* result) { - // TODO(haoyuewang) Adding support of keying material export when 0-RTT is - // accepted. - if (SSL_in_init(ssl())) { + if (result == nullptr) { return false; } result->resize(result_len); 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 15eed38605e..a16e72956b1 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 @@ -419,10 +419,6 @@ size_t TlsServerHandshaker::BufferSizeLimitForLevel( return TlsHandshaker::BufferSizeLimitForLevel(level); } -bool TlsServerHandshaker::KeyUpdateSupportedLocally() const { - return true; -} - std::unique_ptr<QuicDecrypter> TlsServerHandshaker::AdvanceKeysAndCreateCurrentOneRttDecrypter() { return TlsHandshaker::AdvanceKeysAndCreateCurrentOneRttDecrypter(); @@ -483,24 +479,6 @@ bool TlsServerHandshaker::ProcessTransportParameters( // Notify QuicConnectionDebugVisitor. session()->connection()->OnTransportParametersReceived(client_params); - if (GetQuicReloadableFlag(quic_ignore_key_update_not_yet_supported)) { - QUIC_RELOADABLE_FLAG_COUNT_N(quic_ignore_key_update_not_yet_supported, 2, - 2); - } else { - // Chrome clients before 86.0.4233.0 did not send the - // key_update_not_yet_supported transport parameter, but they did send a - // Google-internal transport parameter with identifier 0x4751. We treat - // reception of 0x4751 as having received key_update_not_yet_supported to - // ensure we do not use key updates with those older clients. - // TODO(dschinazi) remove this workaround once all of our QUIC+TLS Finch - // experiments have a min_version greater than 86.0.4233.0. - if (client_params.custom_parameters.find( - static_cast<TransportParameters::TransportParameterId>(0x4751)) != - client_params.custom_parameters.end()) { - client_params.key_update_not_yet_supported = true; - } - } - if (client_params.legacy_version_information.has_value() && CryptoUtils::ValidateClientHelloVersion( client_params.legacy_version_information.value().version, @@ -524,10 +502,6 @@ bool TlsServerHandshaker::ProcessTransportParameters( } ProcessAdditionalTransportParameters(client_params); - if (!session()->user_agent_id().has_value() && - client_params.user_agent_id.has_value()) { - session()->SetUserAgentId(client_params.user_agent_id.value()); - } return true; } 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 01cf19b1107..d2f37b0eb26 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 @@ -80,7 +80,6 @@ class QUIC_EXPORT_PRIVATE TlsServerHandshaker void SetServerApplicationStateForResumption( std::unique_ptr<ApplicationState> state) override; size_t BufferSizeLimitForLevel(EncryptionLevel level) const override; - bool KeyUpdateSupportedLocally() const override; std::unique_ptr<QuicDecrypter> AdvanceKeysAndCreateCurrentOneRttDecrypter() override; std::unique_ptr<QuicEncrypter> CreateCurrentOneRttEncrypter() override; 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 214866c5fab..01fb7ab2ee4 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 @@ -10,6 +10,7 @@ #include "absl/base/macros.h" #include "absl/strings/string_view.h" +#include "quic/core/crypto/certificate_util.h" #include "quic/core/crypto/client_proof_source.h" #include "quic/core/crypto/proof_source.h" #include "quic/core/crypto/quic_random.h" @@ -49,21 +50,6 @@ namespace { const char kServerHostname[] = "test.example.com"; const uint16_t kServerPort = 443; -QuicReferenceCountedPointer<ClientProofSource::Chain> TestClientCertChain() { - return QuicReferenceCountedPointer<ClientProofSource::Chain>( - new ClientProofSource::Chain({std::string(kTestCertificate)})); -} - -CertificatePrivateKey TestClientCertPrivateKey() { - CBS private_key_cbs; - CBS_init(&private_key_cbs, - reinterpret_cast<const uint8_t*>(kTestCertificatePrivateKey.data()), - kTestCertificatePrivateKey.size()); - - return CertificatePrivateKey( - bssl::UniquePtr<EVP_PKEY>(EVP_parse_private_key(&private_key_cbs))); -} - struct TestParams { ParsedQuicVersion version; bool disable_resumption; @@ -400,6 +386,34 @@ class TlsServerHandshakerTest : public QuicTestWithParam<TestParams> { } protected: + // Setup the client to send a (self-signed) client cert to the server, if + // requested. InitializeFakeClient() must be called after this to take effect. + bool SetupClientCert() { + auto client_proof_source = std::make_unique<DefaultClientProofSource>(); + + CertificatePrivateKey client_cert_key( + MakeKeyPairForSelfSignedCertificate()); + + CertificateOptions options; + options.subject = "CN=subject"; + options.serial_number = 0x12345678; + options.validity_start = {2020, 1, 1, 0, 0, 0}; + options.validity_end = {2049, 12, 31, 0, 0, 0}; + std::string der_cert = + CreateSelfSignedCertificate(*client_cert_key.private_key(), options); + + QuicReferenceCountedPointer<ClientProofSource::Chain> client_cert_chain( + new ClientProofSource::Chain({der_cert})); + + if (!client_proof_source->AddCertAndKey({"*"}, client_cert_chain, + std::move(client_cert_key))) { + return false; + } + + client_crypto_config_->set_proof_source(std::move(client_proof_source)); + return true; + } + // Every connection gets its own MockQuicConnectionHelper and // MockAlarmFactory, tracked separately from the server and client state so // their lifetimes persist through the whole test. @@ -901,10 +915,7 @@ TEST_P(TlsServerHandshakerTest, ZeroRttRejectOnApplicationStateChange) { } TEST_P(TlsServerHandshakerTest, RequestClientCert) { - auto client_proof_source = std::make_unique<DefaultClientProofSource>(); - ASSERT_TRUE(client_proof_source->AddCertAndKey({"*"}, TestClientCertChain(), - TestClientCertPrivateKey())); - client_crypto_config_->set_proof_source(std::move(client_proof_source)); + ASSERT_TRUE(SetupClientCert()); InitializeFakeClient(); initial_client_cert_mode_ = ClientCertMode::kRequest; @@ -924,10 +935,7 @@ TEST_P(TlsServerHandshakerTest, RequestClientCert) { } TEST_P(TlsServerHandshakerTest, RequestClientCertByDelayedSslConfig) { - auto client_proof_source = std::make_unique<DefaultClientProofSource>(); - ASSERT_TRUE(client_proof_source->AddCertAndKey({"*"}, TestClientCertChain(), - TestClientCertPrivateKey())); - client_crypto_config_->set_proof_source(std::move(client_proof_source)); + ASSERT_TRUE(SetupClientCert()); InitializeFakeClient(); QuicDelayedSSLConfig delayed_ssl_config; @@ -966,10 +974,7 @@ TEST_P(TlsServerHandshakerTest, RequestClientCert_NoCert) { } TEST_P(TlsServerHandshakerTest, RequestAndRequireClientCert) { - auto client_proof_source = std::make_unique<DefaultClientProofSource>(); - ASSERT_TRUE(client_proof_source->AddCertAndKey({"*"}, TestClientCertChain(), - TestClientCertPrivateKey())); - client_crypto_config_->set_proof_source(std::move(client_proof_source)); + ASSERT_TRUE(SetupClientCert()); InitializeFakeClient(); initial_client_cert_mode_ = ClientCertMode::kRequire; @@ -990,10 +995,7 @@ TEST_P(TlsServerHandshakerTest, RequestAndRequireClientCert) { } TEST_P(TlsServerHandshakerTest, RequestAndRequireClientCertByDelayedSslConfig) { - auto client_proof_source = std::make_unique<DefaultClientProofSource>(); - ASSERT_TRUE(client_proof_source->AddCertAndKey({"*"}, TestClientCertChain(), - TestClientCertPrivateKey())); - client_crypto_config_->set_proof_source(std::move(client_proof_source)); + ASSERT_TRUE(SetupClientCert()); InitializeFakeClient(); QuicDelayedSSLConfig delayed_ssl_config; diff --git a/chromium/net/third_party/quiche/src/quic/core/uber_received_packet_manager.cc b/chromium/net/third_party/quiche/src/quic/core/uber_received_packet_manager.cc index 8f20b33c183..c25329c5fc8 100644 --- a/chromium/net/third_party/quiche/src/quic/core/uber_received_packet_manager.cc +++ b/chromium/net/third_party/quiche/src/quic/core/uber_received_packet_manager.cc @@ -228,7 +228,8 @@ void UberReceivedPacketManager::set_max_ack_ranges(size_t max_ack_ranges) { void UberReceivedPacketManager::set_save_timestamps(bool save_timestamps) { for (auto& received_packet_manager : received_packet_managers_) { - received_packet_manager.set_save_timestamps(save_timestamps); + received_packet_manager.set_save_timestamps( + save_timestamps, supports_multiple_packet_number_spaces_); } } 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 8af288fd37a..fb6a61d5b5a 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 @@ -23,17 +23,15 @@ #include "quic/platform/api/quic_socket_address.h" #include "quic/platform/api/quic_system_event_loop.h" #include "quic/tools/fake_proof_verifier.h" +#include "common/platform/api/quiche_command_line_flags.h" -DEFINE_QUIC_COMMAND_LINE_FLAG(bool, - disable_certificate_verification, - false, - "If true, don't verify the server certificate."); +DEFINE_QUICHE_COMMAND_LINE_FLAG( + bool, disable_certificate_verification, false, + "If true, don't verify the server certificate."); -DEFINE_QUIC_COMMAND_LINE_FLAG(std::string, - masque_mode, - "", - "Allows setting MASQUE mode, valid values are " - "open and legacy. Defaults to open."); +DEFINE_QUICHE_COMMAND_LINE_FLAG(std::string, masque_mode, "", + "Allows setting MASQUE mode, valid values are " + "open and legacy. Defaults to open."); namespace quic { @@ -48,9 +46,10 @@ int RunMasqueClient(int argc, char* argv[]) { // Note that the URI template expansion currently only supports string // replacement of {target_host} and {target_port}, not // {?target_host,target_port}. - std::vector<std::string> urls = QuicParseCommandLineFlags(usage, argc, argv); + std::vector<std::string> urls = + quiche::QuicheParseCommandLineFlags(usage, argc, argv); if (urls.empty()) { - QuicPrintCommandLineFlagHelp(usage); + quiche::QuichePrintCommandLineFlagHelp(usage); return 1; } 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 aaccda3722e..666f67f5bf0 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 @@ -15,40 +15,33 @@ #include "quic/platform/api/quic_logging.h" #include "quic/platform/api/quic_socket_address.h" #include "quic/platform/api/quic_system_event_loop.h" +#include "common/platform/api/quiche_command_line_flags.h" -DEFINE_QUIC_COMMAND_LINE_FLAG(int32_t, - port, - 9661, - "The port the MASQUE server will listen on."); +DEFINE_QUICHE_COMMAND_LINE_FLAG(int32_t, port, 9661, + "The port the MASQUE server will listen on."); -DEFINE_QUIC_COMMAND_LINE_FLAG( - std::string, - cache_dir, - "", +DEFINE_QUICHE_COMMAND_LINE_FLAG( + std::string, cache_dir, "", "Specifies the directory used during QuicHttpResponseCache " "construction to seed the cache. Cache directory can be " "generated using `wget -p --save-headers <url>`"); -DEFINE_QUIC_COMMAND_LINE_FLAG( - std::string, - server_authority, - "", +DEFINE_QUICHE_COMMAND_LINE_FLAG( + std::string, server_authority, "", "Specifies the authority over which the server will accept MASQUE " "requests. Defaults to empty which allows all authorities."); -DEFINE_QUIC_COMMAND_LINE_FLAG(std::string, - masque_mode, - "", - "Allows setting MASQUE mode, valid values are " - "open and legacy. Defaults to open."); +DEFINE_QUICHE_COMMAND_LINE_FLAG(std::string, masque_mode, "", + "Allows setting MASQUE mode, valid values are " + "open and legacy. Defaults to open."); int main(int argc, char* argv[]) { QuicSystemEventLoop event_loop("masque_server"); const char* usage = "Usage: masque_server [options]"; std::vector<std::string> non_option_args = - quic::QuicParseCommandLineFlags(usage, argc, argv); + quiche::QuicheParseCommandLineFlags(usage, argc, argv); if (!non_option_args.empty()) { - quic::QuicPrintCommandLineFlagHelp(usage); + quiche::QuichePrintCommandLineFlagHelp(usage); return 0; } diff --git a/chromium/net/third_party/quiche/src/quic/platform/api/quic_flags.h b/chromium/net/third_party/quiche/src/quic/platform/api/quic_flags.h index 1a9f2d99fa2..762e5907645 100644 --- a/chromium/net/third_party/quiche/src/quic/platform/api/quic_flags.h +++ b/chromium/net/third_party/quiche/src/quic/platform/api/quic_flags.h @@ -8,8 +8,7 @@ #include <string> #include <vector> -#include "net/quic/platform/impl/quic_flags_impl.h" - +#include "quiche_platform_impl/quic_flags_impl.h" #include "common/platform/api/quiche_flags.h" #define GetQuicReloadableFlag(flag) GetQuicheReloadableFlag(quic, flag) @@ -20,31 +19,4 @@ #define GetQuicFlag(flag) GetQuicheFlag(flag) #define SetQuicFlag(flag, value) SetQuicheFlag(flag, value) -// Define a command-line flag that can be automatically set via -// QuicParseCommandLineFlags(). -#define DEFINE_QUIC_COMMAND_LINE_FLAG(type, name, default_value, help) \ - DEFINE_QUIC_COMMAND_LINE_FLAG_IMPL(type, name, default_value, help) - -namespace quic { - -// Parses command-line flags, setting flag variables defined using -// DEFINE_QUIC_COMMAND_LINE_FLAG if they appear in the command line, and -// returning a list of any non-flag arguments specified in the command line. If -// the command line specifies '-h' or '--help', prints a usage message with flag -// descriptions to stdout and exits with status 0. If a flag has an unparsable -// value, writes an error message to stderr and exits with status 1. -inline std::vector<std::string> QuicParseCommandLineFlags( - const char* usage, - int argc, - const char* const* argv) { - return QuicParseCommandLineFlagsImpl(usage, argc, argv); -} - -// Prints a usage message with flag descriptions to stdout. -inline void QuicPrintCommandLineFlagHelp(const char* usage) { - QuicPrintCommandLineFlagHelpImpl(usage); -} - -} // namespace quic - #endif // QUICHE_QUIC_PLATFORM_API_QUIC_FLAGS_H_ diff --git a/chromium/net/third_party/quiche/src/quic/qbone/qbone_client_session.cc b/chromium/net/third_party/quiche/src/quic/qbone/qbone_client_session.cc index fce08d290ea..87cfb2d5266 100644 --- a/chromium/net/third_party/quiche/src/quic/qbone/qbone_client_session.cc +++ b/chromium/net/third_party/quiche/src/quic/qbone/qbone_client_session.cc @@ -9,8 +9,9 @@ #include "absl/strings/string_view.h" #include "quic/core/quic_types.h" #include "quic/qbone/qbone_constants.h" +#include "common/platform/api/quiche_command_line_flags.h" -DEFINE_QUIC_COMMAND_LINE_FLAG( +DEFINE_QUICHE_COMMAND_LINE_FLAG( bool, qbone_client_defer_control_stream_creation, true, "If true, control stream in QBONE client session is created after " "encryption established."); 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 7303b06091a..61a9afe4de9 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 @@ -6,6 +6,7 @@ #include <cstdint> #include <limits> + #include "absl/strings/string_view.h" #include "quic/core/quic_session.h" #include "quic/platform/api/quic_bug_tracker.h" @@ -25,6 +26,14 @@ QboneControlStreamBase::QboneControlStreamBase(QuicSession* session) BIDIRECTIONAL), pending_message_size_(0) {} +QboneControlStreamBase::QboneControlStreamBase(quic::PendingStream* pending, + QuicSession* session) + : QuicStream(pending, session, /*is_static=*/true), + pending_message_size_(0) { + QUICHE_DCHECK_EQ(pending->id(), QboneConstants::GetControlStreamId( + session->transport_version())); +} + void QboneControlStreamBase::OnDataAvailable() { sequencer()->Read(&buffer_); while (true) { diff --git a/chromium/net/third_party/quiche/src/quic/qbone/qbone_control_stream.h b/chromium/net/third_party/quiche/src/quic/qbone/qbone_control_stream.h index d0c2b1a1ed4..c38697dd2c6 100644 --- a/chromium/net/third_party/quiche/src/quic/qbone/qbone_control_stream.h +++ b/chromium/net/third_party/quiche/src/quic/qbone/qbone_control_stream.h @@ -16,6 +16,7 @@ class QboneSessionBase; class QUIC_EXPORT_PRIVATE QboneControlStreamBase : public QuicStream { public: explicit QboneControlStreamBase(QuicSession* session); + QboneControlStreamBase(quic::PendingStream* pending, QuicSession* session); void OnDataAvailable() override; @@ -46,6 +47,9 @@ class QUIC_EXPORT_PRIVATE QboneControlStream : public QboneControlStreamBase { QboneControlStream(QuicSession* session, Handler* handler) : QboneControlStreamBase(session), handler_(handler) {} + QboneControlStream(quic::PendingStream* pending, QuicSession* session, + Handler* handler) + : QboneControlStreamBase(pending, session), handler_(handler) {} bool SendRequest(const Outgoing& request) { return SendMessage(request); } diff --git a/chromium/net/third_party/quiche/src/quic/qbone/qbone_server_session.cc b/chromium/net/third_party/quiche/src/quic/qbone/qbone_server_session.cc index 2ddb8e4454b..bc2d8e53a3c 100644 --- a/chromium/net/third_party/quiche/src/quic/qbone/qbone_server_session.cc +++ b/chromium/net/third_party/quiche/src/quic/qbone/qbone_server_session.cc @@ -12,8 +12,9 @@ #include "quic/core/quic_types.h" #include "quic/core/quic_utils.h" #include "quic/qbone/qbone_constants.h" +#include "common/platform/api/quiche_command_line_flags.h" -DEFINE_QUIC_COMMAND_LINE_FLAG( +DEFINE_QUICHE_COMMAND_LINE_FLAG( bool, qbone_server_defer_control_stream_creation, true, "If true, control stream in QBONE server session is created after " "encryption established."); @@ -72,6 +73,17 @@ void QboneServerSession::CreateControlStream() { ActivateStream(std::move(control_stream)); } +QuicStream* QboneServerSession::CreateControlStreamFromPendingStream( + PendingStream* pending) { + QUICHE_DCHECK(control_stream_ == nullptr); + // Register the reserved control stream. + auto control_stream = + std::make_unique<QboneServerControlStream>(pending, this, handler_); + control_stream_ = control_stream.get(); + ActivateStream(std::move(control_stream)); + return control_stream_; +} + void QboneServerSession::Initialize() { QboneSessionBase::Initialize(); if (!GetQuicFlag(FLAGS_qbone_server_defer_control_stream_creation)) { diff --git a/chromium/net/third_party/quiche/src/quic/qbone/qbone_server_session.h b/chromium/net/third_party/quiche/src/quic/qbone/qbone_server_session.h index f5af8b43d33..d206e4d7012 100644 --- a/chromium/net/third_party/quiche/src/quic/qbone/qbone_server_session.h +++ b/chromium/net/third_party/quiche/src/quic/qbone/qbone_server_session.h @@ -76,15 +76,20 @@ class QUIC_EXPORT_PRIVATE QboneServerSession // QboneSessionBase interface implementation. std::unique_ptr<QuicCryptoStream> CreateCryptoStream() override; - // Instantiate QboneServerControlStream. + // Instantiates QboneServerControlStream. void CreateControlStream(); + // Instantiates QboneServerControlStream from the pending stream and returns a + // pointer to it. + QuicStream* CreateControlStreamFromPendingStream(PendingStream* pending); + // The packet processor. QbonePacketProcessor processor_; - private: // Config for QUIC crypto server stream, used by the server. const QuicCryptoServerConfig* quic_crypto_server_config_; + + private: // Used by QUIC crypto server stream to track most recently compressed certs. QuicCompressedCertsCache* compressed_certs_cache_; // This helper is needed when create QuicCryptoServerStream. 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 2f2f31e3239..702f185e2e2 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 @@ -18,11 +18,10 @@ #include "quic/platform/api/quic_testvalue.h" #include "quic/qbone/platform/icmp_packet.h" #include "quic/qbone/qbone_constants.h" +#include "common/platform/api/quiche_command_line_flags.h" -DEFINE_QUIC_COMMAND_LINE_FLAG( - bool, - qbone_close_ephemeral_frames, - true, +DEFINE_QUICHE_COMMAND_LINE_FLAG( + bool, qbone_close_ephemeral_frames, true, "If true, we'll call CloseStream even when we receive ephemeral frames."); namespace quic { @@ -108,7 +107,7 @@ bool QboneSessionBase::ShouldKeepConnectionAlive() const { std::unique_ptr<QuicStream> QboneSessionBase::CreateDataStream( QuicStreamId id) { - if (crypto_stream_ == nullptr || !crypto_stream_->encryption_established()) { + if (!IsEncryptionEstablished()) { // Encryption not active so no stream created return nullptr; } diff --git a/chromium/net/third_party/quiche/src/quic/qbone/qbone_stream.cc b/chromium/net/third_party/quiche/src/quic/qbone/qbone_stream.cc index 5aac3e73ada..39012336662 100644 --- a/chromium/net/third_party/quiche/src/quic/qbone/qbone_stream.cc +++ b/chromium/net/third_party/quiche/src/quic/qbone/qbone_stream.cc @@ -10,11 +10,10 @@ #include "quic/core/quic_types.h" #include "quic/qbone/qbone_constants.h" #include "quic/qbone/qbone_session_base.h" +#include "common/platform/api/quiche_command_line_flags.h" -DEFINE_QUIC_COMMAND_LINE_FLAG(int, - qbone_stream_ttl_secs, - 3, - "The QBONE Stream TTL in seconds."); +DEFINE_QUICHE_COMMAND_LINE_FLAG(int, qbone_stream_ttl_secs, 3, + "The QBONE Stream TTL in seconds."); namespace quic { diff --git a/chromium/net/third_party/quiche/src/quic/quic_transport/web_transport_fingerprint_proof_verifier.cc b/chromium/net/third_party/quiche/src/quic/quic_transport/web_transport_fingerprint_proof_verifier.cc index ec58386cfba..649406a914d 100644 --- a/chromium/net/third_party/quiche/src/quic/quic_transport/web_transport_fingerprint_proof_verifier.cc +++ b/chromium/net/third_party/quiche/src/quic/quic_transport/web_transport_fingerprint_proof_verifier.cc @@ -158,6 +158,14 @@ QuicAsyncStatus WebTransportFingerprintProofVerifier::VerifyCertChain( return QUIC_FAILURE; } + if (!IsKeyTypeAllowedByPolicy(*view)) { + *details = std::make_unique<Details>(Status::kDisallowedKeyAlgorithm); + *error_details = + absl::StrCat("Certificate uses a disallowed public key type (", + PublicKeyTypeToString(view->public_key_type()), ")"); + return QUIC_FAILURE; + } + *details = std::make_unique<Details>(Status::kValidCertificate); return QUIC_SUCCESS; } @@ -201,4 +209,21 @@ bool WebTransportFingerprintProofVerifier::IsWithinValidityPeriod( now.IsBefore(certificate.validity_end()); } +bool WebTransportFingerprintProofVerifier::IsKeyTypeAllowedByPolicy( + const CertificateView& certificate) { + switch (certificate.public_key_type()) { + // https://github.com/w3c/webtransport/pull/375 defines P-256 as an MTI + // algorithm, and prohibits RSA. We also allow P-384 and Ed25519. + case PublicKeyType::kP256: + case PublicKeyType::kP384: + case PublicKeyType::kEd25519: + return true; + case PublicKeyType::kRsa: + // TODO(b/213614428): this should be false by default. + return true; + default: + return false; + } +} + } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/quic_transport/web_transport_fingerprint_proof_verifier.h b/chromium/net/third_party/quiche/src/quic/quic_transport/web_transport_fingerprint_proof_verifier.h index 76323d50428..9a03c665818 100644 --- a/chromium/net/third_party/quiche/src/quic/quic_transport/web_transport_fingerprint_proof_verifier.h +++ b/chromium/net/third_party/quiche/src/quic/quic_transport/web_transport_fingerprint_proof_verifier.h @@ -61,8 +61,9 @@ class QUIC_EXPORT_PRIVATE WebTransportFingerprintProofVerifier kExpiryTooLong = 3, kExpired = 4, kInternalError = 5, + kDisallowedKeyAlgorithm = 6, - kMaxValue = kInternalError, + kMaxValue = kDisallowedKeyAlgorithm, }; class QUIC_EXPORT_PRIVATE Details : public ProofVerifyDetails { @@ -115,6 +116,9 @@ class QUIC_EXPORT_PRIVATE WebTransportFingerprintProofVerifier std::unique_ptr<ProofVerifierCallback> callback) override; std::unique_ptr<ProofVerifyContext> CreateDefaultContext() override; + protected: + virtual bool IsKeyTypeAllowedByPolicy(const CertificateView& certificate); + private: bool HasKnownFingerprint(absl::string_view der_certificate); bool HasValidExpiry(const CertificateView& certificate); 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 634c16ff009..cbf4fe2aa7e 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 @@ -393,6 +393,53 @@ void CommunicateHandshakeMessages(PacketSavingConnection* client_conn, } } +bool CommunicateHandshakeMessagesUntil(PacketSavingConnection* client_conn, + QuicCryptoStream* client, + std::function<bool()> client_condition, + PacketSavingConnection* server_conn, + QuicCryptoStream* server, + std::function<bool()> server_condition) { + size_t client_next_packet_to_deliver = + client_conn->number_of_packets_delivered_; + size_t server_next_packet_to_deliver = + server_conn->number_of_packets_delivered_; + while ( + client_conn->connected() && server_conn->connected() && + (!client_condition() || !server_condition()) && + (client_conn->encrypted_packets_.size() > client_next_packet_to_deliver || + server_conn->encrypted_packets_.size() > + server_next_packet_to_deliver)) { + if (!server_condition()) { + QUIC_LOG(INFO) << "Processing " + << client_conn->encrypted_packets_.size() - + client_next_packet_to_deliver + << " packets client->server"; + MovePackets(client_conn, &client_next_packet_to_deliver, server, + server_conn, Perspective::IS_SERVER); + } + if (!client_condition()) { + QUIC_LOG(INFO) << "Processing " + << server_conn->encrypted_packets_.size() - + server_next_packet_to_deliver + << " packets server->client"; + MovePackets(server_conn, &server_next_packet_to_deliver, client, + client_conn, Perspective::IS_CLIENT); + } + } + client_conn->number_of_packets_delivered_ = client_next_packet_to_deliver; + server_conn->number_of_packets_delivered_ = server_next_packet_to_deliver; + bool result = client_condition() && server_condition(); + if (!result) { + QUIC_LOG(INFO) << "CommunicateHandshakeMessagesUnti failed with state: " + "client connected? " + << client_conn->connected() << " server connected? " + << server_conn->connected() << " client condition met? " + << client_condition() << " server condition met? " + << server_condition(); + } + return result; +} + std::pair<size_t, size_t> AdvanceHandshake(PacketSavingConnection* client_conn, QuicCryptoStream* client, size_t client_i, diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/crypto_test_utils.h b/chromium/net/third_party/quiche/src/quic/test_tools/crypto_test_utils.h index 87e354a9f79..b4d772adb68 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/crypto_test_utils.h +++ b/chromium/net/third_party/quiche/src/quic/test_tools/crypto_test_utils.h @@ -109,6 +109,19 @@ void CommunicateHandshakeMessages(PacketSavingConnection* client_conn, PacketSavingConnection* server_conn, QuicCryptoStream* server); +// CommunicateHandshakeMessagesUntil: +// 1) Moves messages from |client| to |server| until |server_condition| is met. +// 2) Moves messages from |server| to |client| until |client_condition| is met. +// 3) Returns true if both conditions are met. +// 4) Returns false if either connection is closed or there is no more packet to +// deliver before both conditions are met. +bool CommunicateHandshakeMessagesUntil(PacketSavingConnection* client_conn, + QuicCryptoStream* client, + std::function<bool()> client_condition, + PacketSavingConnection* server_conn, + QuicCryptoStream* server, + std::function<bool()> server_condition); + // AdvanceHandshake attempts to moves messages from |client| to |server| and // |server| to |client|. Returns the number of messages moved. std::pair<size_t, size_t> AdvanceHandshake(PacketSavingConnection* client_conn, diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/qpack/qpack_test_utils.h b/chromium/net/third_party/quiche/src/quic/test_tools/qpack/qpack_test_utils.h index 23d54aab638..2230fbec940 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/qpack/qpack_test_utils.h +++ b/chromium/net/third_party/quiche/src/quic/test_tools/qpack/qpack_test_utils.h @@ -33,6 +33,7 @@ class MockQpackStreamSenderDelegate : public QpackStreamSenderDelegate { ~MockQpackStreamSenderDelegate() override = default; MOCK_METHOD(void, WriteStreamData, (absl::string_view data), (override)); + MOCK_METHOD(uint64_t, NumBytesBuffered, (), (const, override)); }; class NoopQpackStreamSenderDelegate : public QpackStreamSenderDelegate { @@ -40,6 +41,8 @@ class NoopQpackStreamSenderDelegate : public QpackStreamSenderDelegate { ~NoopQpackStreamSenderDelegate() override = default; void WriteStreamData(absl::string_view /*data*/) override {} + + uint64_t NumBytesBuffered() const override { return 0; } }; } // namespace test diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/quic_client_session_cache_peer.h b/chromium/net/third_party/quiche/src/quic/test_tools/quic_client_session_cache_peer.h index 6f0c667eb59..227a48de075 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/quic_client_session_cache_peer.h +++ b/chromium/net/third_party/quiche/src/quic/test_tools/quic_client_session_cache_peer.h @@ -20,6 +20,11 @@ class QuicClientSessionCachePeer { } return iter->second->token; } + + static bool HasEntry(QuicClientSessionCache* cache, + const QuicServerId& server_id) { + return cache->cache_.Lookup(server_id) != cache->cache_.end(); + } }; } // namespace test 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 b5119498c74..207582f738a 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 @@ -57,6 +57,12 @@ QuicTime::Delta QuicConnectionPeer::GetNetworkTimeout( } // static +QuicTime::Delta QuicConnectionPeer::GetHandshakeTimeout( + QuicConnection* connection) { + return connection->idle_network_detector_.handshake_timeout_; +} + +// static void QuicConnectionPeer::SetPerspective(QuicConnection* connection, Perspective perspective) { connection->perspective_ = perspective; @@ -407,12 +413,6 @@ size_t QuicConnectionPeer::NumUndecryptablePackets(QuicConnection* connection) { return connection->undecryptable_packets_.size(); } -// static -size_t QuicConnectionPeer::NumPendingPathChallengesToResponse( - QuicConnection* connection) { - return connection->pending_path_challenge_payloads_.size(); -} - void QuicConnectionPeer::SetConnectionClose(QuicConnection* connection) { connection->connected_ = false; } 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 aa30c2417ee..62ed60e2bef 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 @@ -50,6 +50,8 @@ class QuicConnectionPeer { static QuicTime::Delta GetNetworkTimeout(QuicConnection* connection); + static QuicTime::Delta GetHandshakeTimeout(QuicConnection* connection); + static void SetPerspective(QuicConnection* connection, Perspective perspective); @@ -166,8 +168,6 @@ class QuicConnectionPeer { static size_t NumUndecryptablePackets(QuicConnection* connection); - static size_t NumPendingPathChallengesToResponse(QuicConnection* connection); - static void SetConnectionClose(QuicConnection* connection); static void SendPing(QuicConnection* connection); diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/quic_sent_packet_manager_peer.cc b/chromium/net/third_party/quiche/src/quic/test_tools/quic_sent_packet_manager_peer.cc index 7d1ba86c666..8689535ecff 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/quic_sent_packet_manager_peer.cc +++ b/chromium/net/third_party/quiche/src/quic/test_tools/quic_sent_packet_manager_peer.cc @@ -27,12 +27,6 @@ void QuicSentPacketManagerPeer::SetMaxTailLossProbes( } // static -bool QuicSentPacketManagerPeer::GetEnableHalfRttTailLossProbe( - QuicSentPacketManager* sent_packet_manager) { - return sent_packet_manager->enable_half_rtt_tail_loss_probe_; -} - -// static bool QuicSentPacketManagerPeer::GetUseNewRto( QuicSentPacketManager* sent_packet_manager) { return sent_packet_manager->use_new_rto_; @@ -180,6 +174,12 @@ void QuicSentPacketManagerPeer::DisablePacerBursts( } // static +int QuicSentPacketManagerPeer::GetPacerInitialBurstSize( + QuicSentPacketManager* sent_packet_manager) { + return sent_packet_manager->pacing_sender_.initial_burst_size_; +} + +// static void QuicSentPacketManagerPeer::SetNextPacedPacketTime( QuicSentPacketManager* sent_packet_manager, QuicTime time) { diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/quic_sent_packet_manager_peer.h b/chromium/net/third_party/quiche/src/quic/test_tools/quic_sent_packet_manager_peer.h index a39915f15be..ef0af1e4654 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/quic_sent_packet_manager_peer.h +++ b/chromium/net/third_party/quiche/src/quic/test_tools/quic_sent_packet_manager_peer.h @@ -24,9 +24,6 @@ class QuicSentPacketManagerPeer { static void SetMaxTailLossProbes(QuicSentPacketManager* sent_packet_manager, size_t max_tail_loss_probes); - static bool GetEnableHalfRttTailLossProbe( - QuicSentPacketManager* sent_packet_manager); - static bool GetUseNewRto(QuicSentPacketManager* sent_packet_manager); static void SetPerspective(QuicSentPacketManager* sent_packet_manager, @@ -85,6 +82,9 @@ class QuicSentPacketManagerPeer { static void DisablePacerBursts(QuicSentPacketManager* sent_packet_manager); + static int GetPacerInitialBurstSize( + QuicSentPacketManager* sent_packet_manager); + static void SetNextPacedPacketTime(QuicSentPacketManager* sent_packet_manager, QuicTime time); diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/quic_spdy_stream_peer.cc b/chromium/net/third_party/quiche/src/quic/test_tools/quic_spdy_stream_peer.cc index 6db9a4a96c1..ea21081d36c 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/quic_spdy_stream_peer.cc +++ b/chromium/net/third_party/quiche/src/quic/test_tools/quic_spdy_stream_peer.cc @@ -28,5 +28,10 @@ bool QuicSpdyStreamPeer::use_datagram_contexts(QuicSpdyStream* stream) { return stream->use_datagram_contexts_; } +// static +bool QuicSpdyStreamPeer::OnHeadersFrameEnd(QuicSpdyStream* stream) { + return stream->OnHeadersFrameEnd(); +} + } // namespace test } // namespace quic diff --git a/chromium/net/third_party/quiche/src/quic/test_tools/quic_spdy_stream_peer.h b/chromium/net/third_party/quiche/src/quic/test_tools/quic_spdy_stream_peer.h index 8a5042689a0..692cbc38c20 100644 --- a/chromium/net/third_party/quiche/src/quic/test_tools/quic_spdy_stream_peer.h +++ b/chromium/net/third_party/quiche/src/quic/test_tools/quic_spdy_stream_peer.h @@ -24,6 +24,7 @@ class QuicSpdyStreamPeer { static const QuicIntervalSet<QuicStreamOffset>& unacked_frame_headers_offsets( QuicSpdyStream* stream); static bool use_datagram_contexts(QuicSpdyStream* stream); + static bool OnHeadersFrameEnd(QuicSpdyStream* stream); }; } // namespace test 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 b064a4abd9c..d8d09796162 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 @@ -745,6 +745,9 @@ class PacketSavingConnection : public MockQuicConnection { MOCK_METHOD(void, OnPacketSent, (EncryptionLevel, TransmissionType)); std::vector<std::unique_ptr<QuicEncryptedPacket>> encrypted_packets_; + // Number of packets in encrypted_packets that has been delivered to the peer + // connection. + size_t number_of_packets_delivered_ = 0; MockClock clock_; }; @@ -845,7 +848,6 @@ class MockQuicCryptoStream : public QuicCryptoStream { HandshakeState GetHandshakeState() const override { return HANDSHAKE_START; } void SetServerApplicationStateForResumption( std::unique_ptr<ApplicationState> /*application_state*/) override {} - bool KeyUpdateSupportedLocally() const override { return false; } std::unique_ptr<QuicDecrypter> AdvanceKeysAndCreateCurrentOneRttDecrypter() override { return nullptr; @@ -1464,7 +1466,7 @@ void CreateClientSessionForTest( // start time, otherwise nonce verification will fail. // supported_versions: Set of QUIC versions this server supports. // helper: Pointer to the MockQuicConnectionHelper to use for the session. -// crypto_server_config: Pointer to the crypto server config. +// server_crypto_config: Pointer to the crypto server config. // server_connection: Pointer reference for newly created // connection. This object will be owned by the // server_session. @@ -1474,7 +1476,7 @@ void CreateServerSessionForTest( QuicServerId server_id, QuicTime::Delta connection_start_time, ParsedQuicVersionVector supported_versions, MockQuicConnectionHelper* helper, MockAlarmFactory* alarm_factory, - QuicCryptoServerConfig* crypto_server_config, + QuicCryptoServerConfig* server_crypto_config, QuicCompressedCertsCache* compressed_certs_cache, PacketSavingConnection** server_connection, TestQuicSpdyServerSession** server_session); diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_client_bin.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_client_bin.cc index 0b981255200..deeddddbd9d 100644 --- a/chromium/net/third_party/quiche/src/quic/tools/quic_client_bin.cc +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_client_bin.cc @@ -47,6 +47,7 @@ #include "quic/platform/api/quic_system_event_loop.h" #include "quic/tools/quic_epoll_client_factory.h" #include "quic/tools/quic_toy_client.h" +#include "common/platform/api/quiche_command_line_flags.h" int main(int argc, char* argv[]) { QuicSystemEventLoop event_loop("quic_client"); @@ -54,9 +55,9 @@ int main(int argc, char* argv[]) { // All non-flag arguments should be interpreted as URLs to fetch. std::vector<std::string> urls = - quic::QuicParseCommandLineFlags(usage, argc, argv); + quiche::QuicheParseCommandLineFlags(usage, argc, argv); if (urls.size() != 1) { - quic::QuicPrintCommandLineFlagHelp(usage); + quiche::QuichePrintCommandLineFlagHelp(usage); exit(0); } diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_client_interop_test_bin.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_client_interop_test_bin.cc index c90c8dd4c26..6c14cc3e308 100644 --- a/chromium/net/third_party/quiche/src/quic/tools/quic_client_interop_test_bin.cc +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_client_interop_test_bin.cc @@ -19,19 +19,16 @@ #include "quic/tools/fake_proof_verifier.h" #include "quic/tools/quic_client.h" #include "quic/tools/quic_url.h" +#include "common/platform/api/quiche_command_line_flags.h" -DEFINE_QUIC_COMMAND_LINE_FLAG(std::string, - host, - "", - "The IP or hostname to connect to."); +DEFINE_QUICHE_COMMAND_LINE_FLAG(std::string, host, "", + "The IP or hostname to connect to."); -DEFINE_QUIC_COMMAND_LINE_FLAG( - std::string, - quic_version, - "", +DEFINE_QUICHE_COMMAND_LINE_FLAG( + std::string, quic_version, "", "The QUIC version to use. Defaults to most recent IETF QUIC version."); -DEFINE_QUIC_COMMAND_LINE_FLAG(int32_t, port, 0, "The port to connect to."); +DEFINE_QUICHE_COMMAND_LINE_FLAG(int32_t, port, 0, "The port to connect to."); namespace quic { @@ -403,9 +400,9 @@ int main(int argc, char* argv[]) { const char* usage = "Usage: quic_client_interop_test [options] [url]"; std::vector<std::string> args = - quic::QuicParseCommandLineFlags(usage, argc, argv); + quiche::QuicheParseCommandLineFlags(usage, argc, argv); if (args.size() > 1) { - quic::QuicPrintCommandLineFlagHelp(usage); + quiche::QuichePrintCommandLineFlagHelp(usage); exit(1); } std::string dns_host = GetQuicFlag(FLAGS_host); @@ -426,7 +423,7 @@ int main(int argc, char* argv[]) { port = 443; } if (dns_host.empty()) { - quic::QuicPrintCommandLineFlagHelp(usage); + quiche::QuichePrintCommandLineFlagHelp(usage); exit(1); } if (url_host.empty()) { diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_memory_cache_backend.h b/chromium/net/third_party/quiche/src/quic/tools/quic_memory_cache_backend.h index b8d28103ced..72c57843d60 100644 --- a/chromium/net/third_party/quiche/src/quic/tools/quic_memory_cache_backend.h +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_memory_cache_backend.h @@ -154,9 +154,9 @@ class QuicMemoryCacheBackend : public QuicSimpleServerBackend { void FetchResponseFromBackend( const spdy::Http2HeaderBlock& request_headers, const std::string& request_body, - QuicSimpleServerBackend::RequestHandler* quic_server_stream) override; + QuicSimpleServerBackend::RequestHandler* quic_stream) override; void CloseBackendResponseStream( - QuicSimpleServerBackend::RequestHandler* quic_server_stream) override; + QuicSimpleServerBackend::RequestHandler* quic_stream) override; WebTransportResponse ProcessWebTransportRequest( const spdy::Http2HeaderBlock& request_headers, WebTransportSession* session) override; diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_packet_printer_bin.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_packet_printer_bin.cc index ad357e9b43c..8ffae272ffe 100644 --- a/chromium/net/third_party/quiche/src/quic/tools/quic_packet_printer_bin.cc +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_packet_printer_bin.cc @@ -34,12 +34,11 @@ #include "quic/core/quic_types.h" #include "quic/core/quic_utils.h" #include "quic/platform/api/quic_flags.h" +#include "common/platform/api/quiche_command_line_flags.h" #include "common/quiche_text_utils.h" -DEFINE_QUIC_COMMAND_LINE_FLAG(std::string, - quic_version, - "", - "If set, specify the QUIC version to use."); +DEFINE_QUICHE_COMMAND_LINE_FLAG(std::string, quic_version, "", + "If set, specify the QUIC version to use."); namespace quic { @@ -248,10 +247,10 @@ class QuicPacketPrinter : public QuicFramerVisitorInterface { int main(int argc, char* argv[]) { const char* usage = "Usage: quic_packet_printer client|server <hex>"; std::vector<std::string> args = - quic::QuicParseCommandLineFlags(usage, argc, argv); + quiche::QuicheParseCommandLineFlags(usage, argc, argv); if (args.size() < 2) { - quic::QuicPrintCommandLineFlagHelp(usage); + quiche::QuichePrintCommandLineFlagHelp(usage); return 1; } @@ -263,7 +262,7 @@ int main(int argc, char* argv[]) { perspective = quic::Perspective::IS_SERVER; } else { std::cerr << "Invalid perspective" << std::endl; - quic::QuicPrintCommandLineFlagHelp(usage); + quiche::QuichePrintCommandLineFlagHelp(usage); return 1; } std::string hex = absl::HexStringToBytes(args[1]); diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_reject_reason_decoder_bin.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_reject_reason_decoder_bin.cc index 68443f37d1a..ed83d278b7d 100644 --- a/chromium/net/third_party/quiche/src/quic/tools/quic_reject_reason_decoder_bin.cc +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_reject_reason_decoder_bin.cc @@ -11,6 +11,7 @@ #include "quic/core/crypto/crypto_handshake.h" #include "quic/core/crypto/crypto_utils.h" #include "quic/platform/api/quic_flags.h" +#include "common/platform/api/quiche_command_line_flags.h" #include "common/quiche_text_utils.h" using quic::CryptoUtils; @@ -20,7 +21,7 @@ using quic::MAX_FAILURE_REASON; int main(int argc, char* argv[]) { const char* usage = "Usage: quic_reject_reason_decoder <packed_reason>"; std::vector<std::string> args = - quic::QuicParseCommandLineFlags(usage, argc, argv); + quiche::QuicheParseCommandLineFlags(usage, argc, argv); if (args.size() != 1) { std::cerr << usage << std::endl; diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_server_bin.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_server_bin.cc index c9713670a9c..5d3ed7202f4 100644 --- a/chromium/net/third_party/quiche/src/quic/tools/quic_server_bin.cc +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_server_bin.cc @@ -12,14 +12,15 @@ #include "quic/platform/api/quic_system_event_loop.h" #include "quic/tools/quic_epoll_server_factory.h" #include "quic/tools/quic_toy_server.h" +#include "common/platform/api/quiche_command_line_flags.h" int main(int argc, char* argv[]) { QuicSystemEventLoop event_loop("quic_server"); const char* usage = "Usage: quic_server [options]"; std::vector<std::string> non_option_args = - quic::QuicParseCommandLineFlags(usage, argc, argv); + quiche::QuicheParseCommandLineFlags(usage, argc, argv); if (!non_option_args.empty()) { - quic::QuicPrintCommandLineFlagHelp(usage); + quiche::QuichePrintCommandLineFlagHelp(usage); exit(0); } diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_session_test.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_session_test.cc index d23ba5d53c4..9cc7bfda17c 100644 --- a/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_session_test.cc +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_simple_server_session_test.cc @@ -39,7 +39,6 @@ #include "quic/tools/quic_simple_server_stream.h" using testing::_; -using testing::AnyNumber; using testing::AtLeast; using testing::InSequence; using testing::Invoke; 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 59fe4ec229b..7b6b760a6bb 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 @@ -63,171 +63,129 @@ #include "quic/platform/api/quic_system_event_loop.h" #include "quic/tools/fake_proof_verifier.h" #include "quic/tools/quic_url.h" +#include "common/platform/api/quiche_command_line_flags.h" #include "common/quiche_text_utils.h" namespace { -using quic::QuicUrl; using quiche::QuicheTextUtils; } // namespace -DEFINE_QUIC_COMMAND_LINE_FLAG( - std::string, - host, - "", +DEFINE_QUICHE_COMMAND_LINE_FLAG( + std::string, host, "", "The IP or hostname to connect to. If not provided, the host " "will be derived from the provided URL."); -DEFINE_QUIC_COMMAND_LINE_FLAG(int32_t, port, 0, "The port to connect to."); +DEFINE_QUICHE_COMMAND_LINE_FLAG(int32_t, port, 0, "The port to connect to."); -DEFINE_QUIC_COMMAND_LINE_FLAG(std::string, - ip_version_for_host_lookup, - "", - "Only used if host address lookup is needed. " - "4=ipv4; 6=ipv6; otherwise=don't care."); +DEFINE_QUICHE_COMMAND_LINE_FLAG(std::string, ip_version_for_host_lookup, "", + "Only used if host address lookup is needed. " + "4=ipv4; 6=ipv6; otherwise=don't care."); -DEFINE_QUIC_COMMAND_LINE_FLAG(std::string, - body, - "", - "If set, send a POST with this body."); +DEFINE_QUICHE_COMMAND_LINE_FLAG(std::string, body, "", + "If set, send a POST with this body."); -DEFINE_QUIC_COMMAND_LINE_FLAG( - std::string, - body_hex, - "", +DEFINE_QUICHE_COMMAND_LINE_FLAG( + std::string, body_hex, "", "If set, contents are converted from hex to ascii, before " "sending as body of a POST. e.g. --body_hex=\"68656c6c6f\""); -DEFINE_QUIC_COMMAND_LINE_FLAG( - std::string, - headers, - "", +DEFINE_QUICHE_COMMAND_LINE_FLAG( + std::string, headers, "", "A semicolon separated list of key:value pairs to " "add to request headers."); -DEFINE_QUIC_COMMAND_LINE_FLAG(bool, - quiet, - false, - "Set to true for a quieter output experience."); +DEFINE_QUICHE_COMMAND_LINE_FLAG(bool, quiet, false, + "Set to true for a quieter output experience."); -DEFINE_QUIC_COMMAND_LINE_FLAG( - std::string, - quic_version, - "", +DEFINE_QUICHE_COMMAND_LINE_FLAG( + std::string, quic_version, "", "QUIC version to speak, e.g. 21. If not set, then all available " "versions are offered in the handshake. Also supports wire versions " "such as Q043 or T099."); -DEFINE_QUIC_COMMAND_LINE_FLAG( - std::string, - connection_options, - "", +DEFINE_QUICHE_COMMAND_LINE_FLAG( + std::string, connection_options, "", "Connection options as ASCII tags separated by commas, " "e.g. \"ABCD,EFGH\""); -DEFINE_QUIC_COMMAND_LINE_FLAG( - std::string, - client_connection_options, - "", +DEFINE_QUICHE_COMMAND_LINE_FLAG( + std::string, client_connection_options, "", "Client connection options as ASCII tags separated by commas, " "e.g. \"ABCD,EFGH\""); -DEFINE_QUIC_COMMAND_LINE_FLAG(bool, - quic_ietf_draft, - false, - "Use the IETF draft version. This also enables " - "required internal QUIC flags."); +DEFINE_QUICHE_COMMAND_LINE_FLAG(bool, quic_ietf_draft, false, + "Use the IETF draft version. This also enables " + "required internal QUIC flags."); -DEFINE_QUIC_COMMAND_LINE_FLAG( - bool, - version_mismatch_ok, - false, +DEFINE_QUICHE_COMMAND_LINE_FLAG( + bool, version_mismatch_ok, false, "If true, a version mismatch in the handshake is not considered a " "failure. Useful for probing a server to determine if it speaks " "any version of QUIC."); -DEFINE_QUIC_COMMAND_LINE_FLAG( - bool, - force_version_negotiation, - false, +DEFINE_QUICHE_COMMAND_LINE_FLAG( + bool, force_version_negotiation, false, "If true, start by proposing a version that is reserved for version " "negotiation."); -DEFINE_QUIC_COMMAND_LINE_FLAG( - bool, - multi_packet_chlo, - false, +DEFINE_QUICHE_COMMAND_LINE_FLAG( + bool, multi_packet_chlo, false, "If true, add a transport parameter to make the ClientHello span two " "packets. Only works with QUIC+TLS."); -DEFINE_QUIC_COMMAND_LINE_FLAG( - bool, - redirect_is_success, - true, +DEFINE_QUICHE_COMMAND_LINE_FLAG( + bool, redirect_is_success, true, "If true, an HTTP response code of 3xx is considered to be a " "successful response, otherwise a failure."); -DEFINE_QUIC_COMMAND_LINE_FLAG(int32_t, - initial_mtu, - 0, - "Initial MTU of the connection."); +DEFINE_QUICHE_COMMAND_LINE_FLAG(int32_t, initial_mtu, 0, + "Initial MTU of the connection."); -DEFINE_QUIC_COMMAND_LINE_FLAG( - int32_t, - num_requests, - 1, +DEFINE_QUICHE_COMMAND_LINE_FLAG( + int32_t, num_requests, 1, "How many sequential requests to make on a single connection."); -DEFINE_QUIC_COMMAND_LINE_FLAG(bool, - disable_certificate_verification, - false, - "If true, don't verify the server certificate."); +DEFINE_QUICHE_COMMAND_LINE_FLAG( + bool, disable_certificate_verification, false, + "If true, don't verify the server certificate."); -DEFINE_QUIC_COMMAND_LINE_FLAG( +DEFINE_QUICHE_COMMAND_LINE_FLAG( std::string, default_client_cert, "", "The path to the file containing PEM-encoded client default certificate to " "be sent to the server, if server requested client certs."); -DEFINE_QUIC_COMMAND_LINE_FLAG( +DEFINE_QUICHE_COMMAND_LINE_FLAG( std::string, default_client_cert_key, "", "The path to the file containing PEM-encoded private key of the client's " "default certificate for signing, if server requested client certs."); -DEFINE_QUIC_COMMAND_LINE_FLAG( - bool, - drop_response_body, - false, +DEFINE_QUICHE_COMMAND_LINE_FLAG( + bool, drop_response_body, false, "If true, drop response body immediately after it is received."); -DEFINE_QUIC_COMMAND_LINE_FLAG( - bool, - disable_port_changes, - false, +DEFINE_QUICHE_COMMAND_LINE_FLAG( + bool, disable_port_changes, false, "If true, do not change local port after each request."); -DEFINE_QUIC_COMMAND_LINE_FLAG(bool, - one_connection_per_request, - false, - "If true, close the connection after each " - "request. This allows testing 0-RTT."); +DEFINE_QUICHE_COMMAND_LINE_FLAG(bool, one_connection_per_request, false, + "If true, close the connection after each " + "request. This allows testing 0-RTT."); -DEFINE_QUIC_COMMAND_LINE_FLAG(int32_t, - server_connection_id_length, - -1, - "Length of the server connection ID used."); +DEFINE_QUICHE_COMMAND_LINE_FLAG(int32_t, server_connection_id_length, -1, + "Length of the server connection ID used."); -DEFINE_QUIC_COMMAND_LINE_FLAG(int32_t, - client_connection_id_length, - -1, - "Length of the client connection ID used."); +DEFINE_QUICHE_COMMAND_LINE_FLAG(int32_t, client_connection_id_length, -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_QUICHE_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."); +DEFINE_QUICHE_COMMAND_LINE_FLAG( + int32_t, max_inbound_header_list_size, 128 * 1024, + "Max inbound header list size. 0 means default."); namespace quic { namespace { diff --git a/chromium/net/third_party/quiche/src/quic/tools/quic_toy_server.cc b/chromium/net/third_party/quiche/src/quic/tools/quic_toy_server.cc index e7b4e1ecd7a..cdbe9b3bce9 100644 --- a/chromium/net/third_party/quiche/src/quic/tools/quic_toy_server.cc +++ b/chromium/net/third_party/quiche/src/quic/tools/quic_toy_server.cc @@ -12,44 +12,33 @@ #include "quic/platform/api/quic_flags.h" #include "quic/platform/api/quic_socket_address.h" #include "quic/tools/quic_memory_cache_backend.h" +#include "common/platform/api/quiche_command_line_flags.h" -DEFINE_QUIC_COMMAND_LINE_FLAG(int32_t, - port, - 6121, - "The port the quic server will listen on."); +DEFINE_QUICHE_COMMAND_LINE_FLAG(int32_t, port, 6121, + "The port the quic server will listen on."); -DEFINE_QUIC_COMMAND_LINE_FLAG( - std::string, - quic_response_cache_dir, - "", +DEFINE_QUICHE_COMMAND_LINE_FLAG( + std::string, quic_response_cache_dir, "", "Specifies the directory used during QuicHttpResponseCache " "construction to seed the cache. Cache directory can be " "generated using `wget -p --save-headers <url>`"); -DEFINE_QUIC_COMMAND_LINE_FLAG( - bool, - generate_dynamic_responses, - false, +DEFINE_QUICHE_COMMAND_LINE_FLAG( + bool, generate_dynamic_responses, false, "If true, then URLs which have a numeric path will send a dynamically " "generated response of that many bytes."); -DEFINE_QUIC_COMMAND_LINE_FLAG(bool, - quic_ietf_draft, - false, - "Only enable IETF draft versions. This also " - "enables required internal QUIC flags."); +DEFINE_QUICHE_COMMAND_LINE_FLAG(bool, quic_ietf_draft, false, + "Only enable IETF draft versions. This also " + "enables required internal QUIC flags."); -DEFINE_QUIC_COMMAND_LINE_FLAG( - std::string, - quic_versions, - "", +DEFINE_QUICHE_COMMAND_LINE_FLAG( + std::string, quic_versions, "", "QUIC versions to enable, e.g. \"h3-25,h3-27\". If not set, then all " "available versions are enabled."); -DEFINE_QUIC_COMMAND_LINE_FLAG(bool, - enable_webtransport, - false, - "If true, WebTransport support is enabled."); +DEFINE_QUICHE_COMMAND_LINE_FLAG(bool, enable_webtransport, false, + "If true, WebTransport support is enabled."); namespace quic { 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 aaee6009740..3e7c21a0c72 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 @@ -8,8 +8,6 @@ #include "http2/decoder/decode_status.h" #include "common/platform/api/quiche_logging.h" -using ::http2::DecodeBuffer; - namespace spdy { namespace { const size_t kMaxDecodeBufferSizeBytes = 32 * 1024; // 32 KB diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_reader.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_reader.cc deleted file mode 100644 index bd2305f3621..00000000000 --- a/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_reader.cc +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright (c) 2012 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 "spdy/core/spdy_frame_reader.h" - -#include "common/quiche_endian.h" -#include "spdy/core/spdy_protocol.h" - -namespace spdy { - -SpdyFrameReader::SpdyFrameReader(const char* data, const size_t len) - : data_(data), len_(len), ofs_(0) {} - -bool SpdyFrameReader::ReadUInt8(uint8_t* result) { - // Make sure that we have the whole uint8_t. - if (!CanRead(1)) { - OnFailure(); - return false; - } - - // Read into result. - *result = *reinterpret_cast<const uint8_t*>(data_ + ofs_); - - // Iterate. - ofs_ += 1; - - return true; -} - -bool SpdyFrameReader::ReadUInt16(uint16_t* result) { - // Make sure that we have the whole uint16_t. - if (!CanRead(2)) { - OnFailure(); - return false; - } - - // Read into result. - *result = quiche::QuicheEndian::NetToHost16( - *(reinterpret_cast<const uint16_t*>(data_ + ofs_))); - - // Iterate. - ofs_ += 2; - - return true; -} - -bool SpdyFrameReader::ReadUInt32(uint32_t* result) { - // Make sure that we have the whole uint32_t. - if (!CanRead(4)) { - OnFailure(); - return false; - } - - // Read into result. - *result = quiche::QuicheEndian::NetToHost32( - *(reinterpret_cast<const uint32_t*>(data_ + ofs_))); - - // Iterate. - ofs_ += 4; - - return true; -} - -bool SpdyFrameReader::ReadUInt64(uint64_t* result) { - // Make sure that we have the whole uint64_t. - if (!CanRead(8)) { - OnFailure(); - return false; - } - - // Read into result. Network byte order is big-endian. - uint64_t upper = quiche::QuicheEndian::NetToHost32( - *(reinterpret_cast<const uint32_t*>(data_ + ofs_))); - uint64_t lower = quiche::QuicheEndian::NetToHost32( - *(reinterpret_cast<const uint32_t*>(data_ + ofs_ + 4))); - *result = (upper << 32) + lower; - - // Iterate. - ofs_ += 8; - - return true; -} - -bool SpdyFrameReader::ReadUInt31(uint32_t* result) { - bool success = ReadUInt32(result); - - // Zero out highest-order bit. - if (success) { - *result &= 0x7fffffff; - } - - return success; -} - -bool SpdyFrameReader::ReadUInt24(uint32_t* result) { - // Make sure that we have the whole uint24_t. - if (!CanRead(3)) { - OnFailure(); - return false; - } - - // Read into result. - *result = 0; - memcpy(reinterpret_cast<char*>(result) + 1, data_ + ofs_, 3); - *result = quiche::QuicheEndian::NetToHost32(*result); - - // Iterate. - ofs_ += 3; - - return true; -} - -bool SpdyFrameReader::ReadStringPiece16(absl::string_view* result) { - // Read resultant length. - uint16_t result_len; - if (!ReadUInt16(&result_len)) { - // OnFailure() already called. - return false; - } - - // Make sure that we have the whole string. - if (!CanRead(result_len)) { - OnFailure(); - return false; - } - - // Set result. - *result = absl::string_view(data_ + ofs_, result_len); - - // Iterate. - ofs_ += result_len; - - return true; -} - -bool SpdyFrameReader::ReadStringPiece32(absl::string_view* result) { - // Read resultant length. - uint32_t result_len; - if (!ReadUInt32(&result_len)) { - // OnFailure() already called. - return false; - } - - // Make sure that we have the whole string. - if (!CanRead(result_len)) { - OnFailure(); - return false; - } - - // Set result. - *result = absl::string_view(data_ + ofs_, result_len); - - // Iterate. - ofs_ += result_len; - - return true; -} - -bool SpdyFrameReader::ReadBytes(void* result, size_t size) { - // Make sure that we have enough data to read. - if (!CanRead(size)) { - OnFailure(); - return false; - } - - // Read into result. - memcpy(result, data_ + ofs_, size); - - // Iterate. - ofs_ += size; - - return true; -} - -bool SpdyFrameReader::Seek(size_t size) { - if (!CanRead(size)) { - OnFailure(); - return false; - } - - // Iterate. - ofs_ += size; - - return true; -} - -bool SpdyFrameReader::IsDoneReading() const { - return len_ == ofs_; -} - -bool SpdyFrameReader::CanRead(size_t bytes) const { - return bytes <= (len_ - ofs_); -} - -void SpdyFrameReader::OnFailure() { - // Set our iterator to the end of the buffer so that further reads fail - // immediately. - ofs_ = len_; -} - -} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_reader.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_reader.h deleted file mode 100644 index f4078c9eeb6..00000000000 --- a/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_reader.h +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) 2012 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_CORE_SPDY_FRAME_READER_H_ -#define QUICHE_SPDY_CORE_SPDY_FRAME_READER_H_ - -#include <cstdint> - -#include "absl/strings/string_view.h" -#include "common/platform/api/quiche_export.h" - -namespace spdy { - -// Used for reading SPDY frames. Though there isn't really anything terribly -// SPDY-specific here, it's a helper class that's useful when doing SPDY -// framing. -// -// To use, simply construct a SpdyFramerReader using the underlying buffer that -// you'd like to read fields from, then call one of the Read*() methods to -// actually do some reading. -// -// This class keeps an internal iterator to keep track of what's already been -// read and each successive Read*() call automatically increments said iterator -// on success. On failure, internal state of the SpdyFrameReader should not be -// trusted and it is up to the caller to throw away the failed instance and -// handle the error as appropriate. None of the Read*() methods should ever be -// called after failure, as they will also fail immediately. -class QUICHE_EXPORT_PRIVATE SpdyFrameReader { - public: - // Caller must provide an underlying buffer to work on. - SpdyFrameReader(const char* data, const size_t len); - - // Empty destructor. - ~SpdyFrameReader() {} - - // Reads an 8-bit unsigned integer into the given output parameter. - // Forwards the internal iterator on success. - // Returns true on success, false otherwise. - bool ReadUInt8(uint8_t* result); - - // Reads a 16-bit unsigned integer into the given output parameter. - // Forwards the internal iterator on success. - // Returns true on success, false otherwise. - bool ReadUInt16(uint16_t* result); - - // Reads a 32-bit unsigned integer into the given output parameter. - // Forwards the internal iterator on success. - // Returns true on success, false otherwise. - bool ReadUInt32(uint32_t* result); - - // Reads a 64-bit unsigned integer into the given output parameter. - // Forwards the internal iterator on success. - // Returns true on success, false otherwise. - bool ReadUInt64(uint64_t* result); - - // Reads a 31-bit unsigned integer into the given output parameter. This is - // equivalent to ReadUInt32() above except that the highest-order bit is - // discarded. - // Forwards the internal iterator (by 4B) on success. - // Returns true on success, false otherwise. - bool ReadUInt31(uint32_t* result); - - // Reads a 24-bit unsigned integer into the given output parameter. - // Forwards the internal iterator (by 3B) on success. - // Returns true on success, false otherwise. - bool ReadUInt24(uint32_t* result); - - // Reads a string prefixed with 16-bit length into the given output parameter. - // - // NOTE: Does not copy but rather references strings in the underlying buffer. - // This should be kept in mind when handling memory management! - // - // Forwards the internal iterator on success. - // Returns true on success, false otherwise. - bool ReadStringPiece16(absl::string_view* result); - - // Reads a string prefixed with 32-bit length into the given output parameter. - // - // NOTE: Does not copy but rather references strings in the underlying buffer. - // This should be kept in mind when handling memory management! - // - // Forwards the internal iterator on success. - // Returns true on success, false otherwise. - bool ReadStringPiece32(absl::string_view* result); - - // Reads a given number of bytes into the given buffer. The buffer - // must be of adequate size. - // Forwards the internal iterator on success. - // Returns true on success, false otherwise. - bool ReadBytes(void* result, size_t size); - - // Seeks a given number of bytes into the buffer from the current offset. - // Equivelant to an empty read. - // Forwards the internal iterator. - // Returns true on success, false otherwise. - bool Seek(size_t size); - - // Rewinds this reader to the beginning of the frame. - void Rewind() { ofs_ = 0; } - - // Returns true if the entirety of the underlying buffer has been read via - // Read*() calls. - bool IsDoneReading() const; - - // Returns the number of bytes that have been consumed by the reader so far. - size_t GetBytesConsumed() const { return ofs_; } - - private: - // Returns true if the underlying buffer has enough room to read the given - // amount of bytes. - bool CanRead(size_t bytes) const; - - // To be called when a read fails for any reason. - void OnFailure(); - - // The data buffer that we're reading from. - const char* data_; - - // The length of the data buffer that we're reading from. - const size_t len_; - - // The location of the next read from our data buffer. - size_t ofs_; -}; - -} // namespace spdy - -#endif // QUICHE_SPDY_CORE_SPDY_FRAME_READER_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_reader_test.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_reader_test.cc deleted file mode 100644 index 035cac9eceb..00000000000 --- a/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_reader_test.cc +++ /dev/null @@ -1,247 +0,0 @@ -// Copyright (c) 2012 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 "spdy/core/spdy_frame_reader.h" - -#include <cstdint> - -#include "absl/base/macros.h" -#include "common/platform/api/quiche_test.h" -#include "common/quiche_endian.h" - -namespace spdy { - -TEST(SpdyFrameReaderTest, ReadUInt16) { - // Frame data in network byte order. - const uint16_t kFrameData[] = { - quiche::QuicheEndian::HostToNet16(1), - quiche::QuicheEndian::HostToNet16(1 << 15), - }; - - SpdyFrameReader frame_reader(reinterpret_cast<const char*>(kFrameData), - sizeof(kFrameData)); - EXPECT_FALSE(frame_reader.IsDoneReading()); - - uint16_t uint16_val; - EXPECT_TRUE(frame_reader.ReadUInt16(&uint16_val)); - EXPECT_FALSE(frame_reader.IsDoneReading()); - EXPECT_EQ(1, uint16_val); - - EXPECT_TRUE(frame_reader.ReadUInt16(&uint16_val)); - EXPECT_TRUE(frame_reader.IsDoneReading()); - EXPECT_EQ(1 << 15, uint16_val); -} - -TEST(SpdyFrameReaderTest, ReadUInt32) { - // Frame data in network byte order. - const uint32_t kFrameData[] = { - quiche::QuicheEndian::HostToNet32(1), - quiche::QuicheEndian::HostToNet32(0x80000000), - }; - - SpdyFrameReader frame_reader(reinterpret_cast<const char*>(kFrameData), - ABSL_ARRAYSIZE(kFrameData) * sizeof(uint32_t)); - EXPECT_FALSE(frame_reader.IsDoneReading()); - - uint32_t uint32_val; - EXPECT_TRUE(frame_reader.ReadUInt32(&uint32_val)); - EXPECT_FALSE(frame_reader.IsDoneReading()); - EXPECT_EQ(1u, uint32_val); - - EXPECT_TRUE(frame_reader.ReadUInt32(&uint32_val)); - EXPECT_TRUE(frame_reader.IsDoneReading()); - EXPECT_EQ(1u << 31, uint32_val); -} - -TEST(SpdyFrameReaderTest, ReadStringPiece16) { - // Frame data in network byte order. - const char kFrameData[] = { - 0x00, 0x02, // uint16_t(2) - 0x48, 0x69, // "Hi" - 0x00, 0x10, // uint16_t(16) - 0x54, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2c, - 0x20, 0x31, 0x2c, 0x20, 0x32, 0x2c, 0x20, 0x33, // "Testing, 1, 2, 3" - }; - - SpdyFrameReader frame_reader(kFrameData, ABSL_ARRAYSIZE(kFrameData)); - EXPECT_FALSE(frame_reader.IsDoneReading()); - - absl::string_view stringpiece_val; - EXPECT_TRUE(frame_reader.ReadStringPiece16(&stringpiece_val)); - EXPECT_FALSE(frame_reader.IsDoneReading()); - EXPECT_EQ(0, stringpiece_val.compare("Hi")); - - EXPECT_TRUE(frame_reader.ReadStringPiece16(&stringpiece_val)); - EXPECT_TRUE(frame_reader.IsDoneReading()); - EXPECT_EQ(0, stringpiece_val.compare("Testing, 1, 2, 3")); -} - -TEST(SpdyFrameReaderTest, ReadStringPiece32) { - // Frame data in network byte order. - const char kFrameData[] = { - 0x00, 0x00, 0x00, 0x03, // uint32_t(3) - 0x66, 0x6f, 0x6f, // "foo" - 0x00, 0x00, 0x00, 0x10, // uint32_t(16) - 0x54, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2c, - 0x20, 0x34, 0x2c, 0x20, 0x35, 0x2c, 0x20, 0x36, // "Testing, 4, 5, 6" - }; - - SpdyFrameReader frame_reader(kFrameData, ABSL_ARRAYSIZE(kFrameData)); - EXPECT_FALSE(frame_reader.IsDoneReading()); - - absl::string_view stringpiece_val; - EXPECT_TRUE(frame_reader.ReadStringPiece32(&stringpiece_val)); - EXPECT_FALSE(frame_reader.IsDoneReading()); - EXPECT_EQ(0, stringpiece_val.compare("foo")); - - EXPECT_TRUE(frame_reader.ReadStringPiece32(&stringpiece_val)); - EXPECT_TRUE(frame_reader.IsDoneReading()); - EXPECT_EQ(0, stringpiece_val.compare("Testing, 4, 5, 6")); -} - -TEST(SpdyFrameReaderTest, ReadUInt16WithBufferTooSmall) { - // Frame data in network byte order. - const char kFrameData[] = { - 0x00, // part of a uint16_t - }; - - SpdyFrameReader frame_reader(kFrameData, ABSL_ARRAYSIZE(kFrameData)); - EXPECT_FALSE(frame_reader.IsDoneReading()); - - uint16_t uint16_val; - EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val)); -} - -TEST(SpdyFrameReaderTest, ReadUInt32WithBufferTooSmall) { - // Frame data in network byte order. - const char kFrameData[] = { - 0x00, 0x00, 0x00, // part of a uint32_t - }; - - SpdyFrameReader frame_reader(kFrameData, ABSL_ARRAYSIZE(kFrameData)); - EXPECT_FALSE(frame_reader.IsDoneReading()); - - uint32_t uint32_val; - EXPECT_FALSE(frame_reader.ReadUInt32(&uint32_val)); - - // Also make sure that trying to read a uint16_t, which technically could - // work, fails immediately due to previously encountered failed read. - uint16_t uint16_val; - EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val)); -} - -// Tests ReadStringPiece16() with a buffer too small to fit the entire string. -TEST(SpdyFrameReaderTest, ReadStringPiece16WithBufferTooSmall) { - // Frame data in network byte order. - const char kFrameData[] = { - 0x00, 0x03, // uint16_t(3) - 0x48, 0x69, // "Hi" - }; - - SpdyFrameReader frame_reader(kFrameData, ABSL_ARRAYSIZE(kFrameData)); - EXPECT_FALSE(frame_reader.IsDoneReading()); - - absl::string_view stringpiece_val; - EXPECT_FALSE(frame_reader.ReadStringPiece16(&stringpiece_val)); - - // Also make sure that trying to read a uint16_t, which technically could - // work, fails immediately due to previously encountered failed read. - uint16_t uint16_val; - EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val)); -} - -// Tests ReadStringPiece16() with a buffer too small even to fit the length. -TEST(SpdyFrameReaderTest, ReadStringPiece16WithBufferWayTooSmall) { - // Frame data in network byte order. - const char kFrameData[] = { - 0x00, // part of a uint16_t - }; - - SpdyFrameReader frame_reader(kFrameData, ABSL_ARRAYSIZE(kFrameData)); - EXPECT_FALSE(frame_reader.IsDoneReading()); - - absl::string_view stringpiece_val; - EXPECT_FALSE(frame_reader.ReadStringPiece16(&stringpiece_val)); - - // Also make sure that trying to read a uint16_t, which technically could - // work, fails immediately due to previously encountered failed read. - uint16_t uint16_val; - EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val)); -} - -// Tests ReadStringPiece32() with a buffer too small to fit the entire string. -TEST(SpdyFrameReaderTest, ReadStringPiece32WithBufferTooSmall) { - // Frame data in network byte order. - const char kFrameData[] = { - 0x00, 0x00, 0x00, 0x03, // uint32_t(3) - 0x48, 0x69, // "Hi" - }; - - SpdyFrameReader frame_reader(kFrameData, ABSL_ARRAYSIZE(kFrameData)); - EXPECT_FALSE(frame_reader.IsDoneReading()); - - absl::string_view stringpiece_val; - EXPECT_FALSE(frame_reader.ReadStringPiece32(&stringpiece_val)); - - // Also make sure that trying to read a uint16_t, which technically could - // work, fails immediately due to previously encountered failed read. - uint16_t uint16_val; - EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val)); -} - -// Tests ReadStringPiece32() with a buffer too small even to fit the length. -TEST(SpdyFrameReaderTest, ReadStringPiece32WithBufferWayTooSmall) { - // Frame data in network byte order. - const char kFrameData[] = { - 0x00, 0x00, 0x00, // part of a uint32_t - }; - - SpdyFrameReader frame_reader(kFrameData, ABSL_ARRAYSIZE(kFrameData)); - EXPECT_FALSE(frame_reader.IsDoneReading()); - - absl::string_view stringpiece_val; - EXPECT_FALSE(frame_reader.ReadStringPiece32(&stringpiece_val)); - - // Also make sure that trying to read a uint16_t, which technically could - // work, fails immediately due to previously encountered failed read. - uint16_t uint16_val; - EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val)); -} - -TEST(SpdyFrameReaderTest, ReadBytes) { - // Frame data in network byte order. - const char kFrameData[] = { - 0x66, 0x6f, 0x6f, // "foo" - 0x48, 0x69, // "Hi" - }; - - SpdyFrameReader frame_reader(kFrameData, ABSL_ARRAYSIZE(kFrameData)); - EXPECT_FALSE(frame_reader.IsDoneReading()); - - char dest1[3] = {}; - EXPECT_TRUE(frame_reader.ReadBytes(&dest1, ABSL_ARRAYSIZE(dest1))); - EXPECT_FALSE(frame_reader.IsDoneReading()); - EXPECT_EQ("foo", absl::string_view(dest1, ABSL_ARRAYSIZE(dest1))); - - char dest2[2] = {}; - EXPECT_TRUE(frame_reader.ReadBytes(&dest2, ABSL_ARRAYSIZE(dest2))); - EXPECT_TRUE(frame_reader.IsDoneReading()); - EXPECT_EQ("Hi", absl::string_view(dest2, ABSL_ARRAYSIZE(dest2))); -} - -TEST(SpdyFrameReaderTest, ReadBytesWithBufferTooSmall) { - // Frame data in network byte order. - const char kFrameData[] = { - 0x01, - }; - - SpdyFrameReader frame_reader(kFrameData, ABSL_ARRAYSIZE(kFrameData)); - EXPECT_FALSE(frame_reader.IsDoneReading()); - - char dest[ABSL_ARRAYSIZE(kFrameData) + 2] = {}; - EXPECT_FALSE(frame_reader.ReadBytes(&dest, ABSL_ARRAYSIZE(kFrameData) + 1)); - EXPECT_STREQ("", dest); -} - -} // 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 0719883fb6f..e97d45c49ca 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 @@ -11,13 +11,12 @@ #include <new> #include <utility> +#include "absl/base/macros.h" #include "absl/memory/memory.h" -#include "http2/platform/api/http2_macros.h" #include "common/platform/api/quiche_bug_tracker.h" #include "common/platform/api/quiche_logging.h" #include "spdy/core/spdy_bitmasks.h" #include "spdy/core/spdy_frame_builder.h" -#include "spdy/core/spdy_frame_reader.h" namespace spdy { @@ -421,7 +420,7 @@ std::unique_ptr<SpdyFrameSequence> SpdyFramer::CreateIterator( } case SpdyFrameType::DATA: { QUICHE_DVLOG(1) << "Serialize a stream end DATA frame for VTL"; - HTTP2_FALLTHROUGH; + ABSL_FALLTHROUGH_INTENDED; } default: { return std::make_unique<SpdyControlFrameIterator>(framer, 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 b138300fa5b..9f045763763 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 @@ -22,7 +22,6 @@ #include "spdy/core/recording_headers_handler.h" #include "spdy/core/spdy_bitmasks.h" #include "spdy/core/spdy_frame_builder.h" -#include "spdy/core/spdy_frame_reader.h" #include "spdy/core/spdy_protocol.h" #include "spdy/core/spdy_test_utils.h" 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 12a716e486c..3fcdcc38b93 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 @@ -182,7 +182,6 @@ bool ParseSettingsId(SpdySettingsId wire_setting_id, case SETTINGS_ENABLE_CONNECT_PROTOCOL: case SETTINGS_DEPRECATE_HTTP2_PRIORITIES: case SETTINGS_EXPERIMENT_SCHEDULER: - // FALLTHROUGH_INTENDED return true; } return false; |