| // Copyright (c) 2018 The Chromium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "quiche/quic/core/crypto/transport_parameters.h" | |
| #include <algorithm> | |
| #include <cstdint> | |
| #include <cstring> | |
| #include <memory> | |
| #include <ostream> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/strings/escaping.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/string_view.h" | |
| #include "openssl/digest.h" | |
| #include "openssl/sha.h" | |
| #include "quiche/quic/core/crypto/quic_random.h" | |
| #include "quiche/quic/core/quic_connection_id.h" | |
| #include "quiche/quic/core/quic_constants.h" | |
| #include "quiche/quic/core/quic_data_reader.h" | |
| #include "quiche/quic/core/quic_data_writer.h" | |
| #include "quiche/quic/core/quic_tag.h" | |
| #include "quiche/quic/core/quic_types.h" | |
| #include "quiche/quic/core/quic_utils.h" | |
| #include "quiche/quic/core/quic_versions.h" | |
| #include "quiche/quic/platform/api/quic_bug_tracker.h" | |
| #include "quiche/quic/platform/api/quic_flag_utils.h" | |
| #include "quiche/quic/platform/api/quic_flags.h" | |
| #include "quiche/quic/platform/api/quic_ip_address.h" | |
| #include "quiche/quic/platform/api/quic_logging.h" | |
| #include "quiche/quic/platform/api/quic_socket_address.h" | |
| #include "quiche/common/platform/api/quiche_logging.h" | |
| #include "quiche/common/quiche_data_writer.h" | |
| #include "quiche/common/quiche_endian.h" | |
| namespace quic { | |
| namespace { | |
| std::string ObfuscateSni(const absl::string_view& input) { | |
| static constexpr char kObfuscationKey[] = {0x42, 0x7b, 0x40, 0x63, 0x11, 0x2a, | |
| 0x6f, 0x05, 0x58, 0x1f, 0x7f, 0x33, | |
| 0x32, 0x4d, 0x64, 0x16}; | |
| std::string output = std::string(input); | |
| for (size_t i = 0; i < output.size(); ++i) { | |
| output[i] ^= kObfuscationKey[i % sizeof(kObfuscationKey)]; | |
| } | |
| return output; | |
| } | |
| } // namespace | |
| // Values of the TransportParameterId enum as defined in the | |
| // "Transport Parameter Encoding" section of draft-ietf-quic-transport. | |
| // When parameters are encoded, one of these enum values is used to indicate | |
| // which parameter is encoded. The supported draft version is noted in | |
| // transport_parameters.h. | |
| enum TransportParameters::TransportParameterId : uint64_t { | |
| kOriginalDestinationConnectionId = 0, | |
| kMaxIdleTimeout = 1, | |
| kStatelessResetToken = 2, | |
| kMaxPacketSize = 3, | |
| kInitialMaxData = 4, | |
| kInitialMaxStreamDataBidiLocal = 5, | |
| kInitialMaxStreamDataBidiRemote = 6, | |
| kInitialMaxStreamDataUni = 7, | |
| kInitialMaxStreamsBidi = 8, | |
| kInitialMaxStreamsUni = 9, | |
| kAckDelayExponent = 0xa, | |
| kMaxAckDelay = 0xb, | |
| kDisableActiveMigration = 0xc, | |
| kPreferredAddress = 0xd, | |
| kActiveConnectionIdLimit = 0xe, | |
| kInitialSourceConnectionId = 0xf, | |
| kRetrySourceConnectionId = 0x10, | |
| kMaxDatagramFrameSize = 0x20, | |
| // https://github.com/quicwg/base-drafts/wiki/Quantum-Readiness-test | |
| kDiscard = 0x173E, | |
| // https://www.ietf.org/archive/id/draft-ietf-scone-protocol-04.html | |
| kSconeSupported = 0x219e, | |
| kGoogleHandshakeMessage = 0x26ab, | |
| kDebuggingSni = 0x219bbcd0, | |
| kInitialRoundTripTime = 0x3127, | |
| kGoogleConnectionOptions = 0x3128, | |
| // 0x3129 was used to convey the user agent string. | |
| // 0x312A was used only in T050 to indicate support for HANDSHAKE_DONE. | |
| // 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. | |
| // 0x4752 was used to transmit Google-specific version and supported_versions, | |
| // it was replaced by kVersionInformation. | |
| kMinAckDelayDraft10 = 0xFF04DE1B, // draft-ietf-quic-delayed-ack-10 and -11. | |
| kVersionInformation = 0x11, // RFC 9368. | |
| // draft-ietf-quic-reliable-stream-reset. | |
| kReliableStreamReset = 0x17F7586D2CB571, | |
| }; | |
| namespace { | |
| constexpr QuicVersionLabel kReservedVersionMask = 0x0f0f0f0f; | |
| constexpr QuicVersionLabel kReservedVersionBits = 0x0a0a0a0a; | |
| // The following constants define minimum and maximum allowed values for some of | |
| // the parameters. These come from the "Transport Parameter Definitions" | |
| // section of draft-ietf-quic-transport. | |
| constexpr uint64_t kMinMaxPacketSizeTransportParam = 1200; | |
| constexpr uint64_t kMaxAckDelayExponentTransportParam = 20; | |
| constexpr uint64_t kDefaultAckDelayExponentTransportParam = 3; | |
| constexpr uint64_t kMaxMaxAckDelayTransportParam = 16383; | |
| constexpr uint64_t kDefaultMaxAckDelayTransportParam = 25; | |
| constexpr uint64_t kMinActiveConnectionIdLimitTransportParam = 2; | |
| constexpr uint64_t kDefaultActiveConnectionIdLimitTransportParam = 2; | |
| std::string TransportParameterIdToString( | |
| TransportParameters::TransportParameterId param_id) { | |
| switch (param_id) { | |
| case TransportParameters::kOriginalDestinationConnectionId: | |
| return "original_destination_connection_id"; | |
| case TransportParameters::kMaxIdleTimeout: | |
| return "max_idle_timeout"; | |
| case TransportParameters::kStatelessResetToken: | |
| return "stateless_reset_token"; | |
| case TransportParameters::kMaxPacketSize: | |
| return "max_udp_payload_size"; | |
| case TransportParameters::kInitialMaxData: | |
| return "initial_max_data"; | |
| case TransportParameters::kInitialMaxStreamDataBidiLocal: | |
| return "initial_max_stream_data_bidi_local"; | |
| case TransportParameters::kInitialMaxStreamDataBidiRemote: | |
| return "initial_max_stream_data_bidi_remote"; | |
| case TransportParameters::kInitialMaxStreamDataUni: | |
| return "initial_max_stream_data_uni"; | |
| case TransportParameters::kInitialMaxStreamsBidi: | |
| return "initial_max_streams_bidi"; | |
| case TransportParameters::kInitialMaxStreamsUni: | |
| return "initial_max_streams_uni"; | |
| case TransportParameters::kAckDelayExponent: | |
| return "ack_delay_exponent"; | |
| case TransportParameters::kMaxAckDelay: | |
| return "max_ack_delay"; | |
| case TransportParameters::kDisableActiveMigration: | |
| return "disable_active_migration"; | |
| case TransportParameters::kPreferredAddress: | |
| return "preferred_address"; | |
| case TransportParameters::kActiveConnectionIdLimit: | |
| return "active_connection_id_limit"; | |
| case TransportParameters::kInitialSourceConnectionId: | |
| return "initial_source_connection_id"; | |
| case TransportParameters::kRetrySourceConnectionId: | |
| return "retry_source_connection_id"; | |
| case TransportParameters::kMaxDatagramFrameSize: | |
| return "max_datagram_frame_size"; | |
| case TransportParameters::kDiscard: | |
| return "discard"; | |
| case TransportParameters::kSconeSupported: | |
| return "scone_supported"; | |
| case TransportParameters::kGoogleHandshakeMessage: | |
| return "google_handshake_message"; | |
| case TransportParameters::kDebuggingSni: | |
| return "debugging_sni"; | |
| case TransportParameters::kInitialRoundTripTime: | |
| return "initial_round_trip_time"; | |
| case TransportParameters::kGoogleConnectionOptions: | |
| return "google_connection_options"; | |
| case TransportParameters::kMinAckDelayDraft10: | |
| return "min_ack_delay_us"; | |
| case TransportParameters::kVersionInformation: | |
| return "version_information"; | |
| case TransportParameters::kReliableStreamReset: | |
| return "reliable_stream_reset"; | |
| } | |
| return absl::StrCat("Unknown(", param_id, ")"); | |
| } | |
| bool WriteIntegerTlv(QuicDataWriter* writer, | |
| TransportParameters::TransportParameterId id, | |
| uint64_t value) { | |
| if (!writer->WriteVarInt62(id)) { | |
| QUIC_BUG(quic_bug_10743_1) | |
| << "Failed to write param_id " << TransportParameterIdToString(id); | |
| return false; | |
| } | |
| const quiche::QuicheVariableLengthIntegerLength value_length = | |
| QuicDataWriter::GetVarInt62Len(value); | |
| if (!writer->WriteVarInt62(value_length)) { | |
| QUIC_BUG(quic_bug_10743_2) << "Failed to write value_length for " | |
| << TransportParameterIdToString(id); | |
| return false; | |
| } | |
| if (!writer->WriteVarInt62WithForcedLength(value, value_length)) { | |
| QUIC_BUG(quic_bug_10743_3) | |
| << "Failed to write value for " << TransportParameterIdToString(id); | |
| return false; | |
| } | |
| return true; | |
| } | |
| // Just read the value, because the type and length have already been parsed to | |
| // identify the parameter as an integer and initialize |reader|. | |
| bool ReadIntegerValue(QuicDataReader* reader, | |
| TransportParameters::TransportParameterId param_id, | |
| uint64_t& value, std::string* error_details) { | |
| if (!reader->ReadVarInt62(&value)) { | |
| *error_details = | |
| "Failed to parse value " + TransportParameterIdToString(param_id); | |
| return false; | |
| } | |
| if (!reader->IsDoneReading()) { | |
| *error_details = absl::StrCat( | |
| "Received unexpected ", reader->BytesRemaining(), | |
| " bytes after parsing ", TransportParameterIdToString(param_id)); | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool TransportParameterIdIsKnown( | |
| TransportParameters::TransportParameterId param_id) { | |
| switch (param_id) { | |
| case TransportParameters::kOriginalDestinationConnectionId: | |
| case TransportParameters::kMaxIdleTimeout: | |
| case TransportParameters::kStatelessResetToken: | |
| case TransportParameters::kMaxPacketSize: | |
| case TransportParameters::kInitialMaxData: | |
| case TransportParameters::kInitialMaxStreamDataBidiLocal: | |
| case TransportParameters::kInitialMaxStreamDataBidiRemote: | |
| case TransportParameters::kInitialMaxStreamDataUni: | |
| case TransportParameters::kInitialMaxStreamsBidi: | |
| case TransportParameters::kInitialMaxStreamsUni: | |
| case TransportParameters::kAckDelayExponent: | |
| case TransportParameters::kMaxAckDelay: | |
| case TransportParameters::kDisableActiveMigration: | |
| case TransportParameters::kPreferredAddress: | |
| case TransportParameters::kActiveConnectionIdLimit: | |
| case TransportParameters::kInitialSourceConnectionId: | |
| case TransportParameters::kRetrySourceConnectionId: | |
| case TransportParameters::kMaxDatagramFrameSize: | |
| case TransportParameters::kDiscard: | |
| case TransportParameters::kSconeSupported: | |
| case TransportParameters::kGoogleHandshakeMessage: | |
| case TransportParameters::kDebuggingSni: | |
| case TransportParameters::kInitialRoundTripTime: | |
| case TransportParameters::kGoogleConnectionOptions: | |
| case TransportParameters::kMinAckDelayDraft10: | |
| case TransportParameters::kReliableStreamReset: | |
| case TransportParameters::kVersionInformation: | |
| return true; | |
| } | |
| return false; | |
| } | |
| } // namespace | |
| TransportParameters::IntegerParameter::IntegerParameter( | |
| TransportParameters::TransportParameterId param_id, uint64_t default_value, | |
| uint64_t min_value, uint64_t max_value) | |
| : param_id_(param_id), | |
| value_(default_value), | |
| default_value_(default_value), | |
| min_value_(min_value), | |
| max_value_(max_value), | |
| has_been_read_(false) { | |
| QUICHE_DCHECK_LE(min_value, default_value); | |
| QUICHE_DCHECK_LE(default_value, max_value); | |
| QUICHE_DCHECK_LE(max_value, quiche::kVarInt62MaxValue); | |
| } | |
| TransportParameters::IntegerParameter::IntegerParameter( | |
| TransportParameters::TransportParameterId param_id) | |
| : TransportParameters::IntegerParameter::IntegerParameter( | |
| param_id, 0, 0, quiche::kVarInt62MaxValue) {} | |
| void TransportParameters::IntegerParameter::set_value(uint64_t value) { | |
| value_ = value; | |
| } | |
| uint64_t TransportParameters::IntegerParameter::value() const { return value_; } | |
| bool TransportParameters::IntegerParameter::IsValid() const { | |
| return min_value_ <= value_ && value_ <= max_value_; | |
| } | |
| bool TransportParameters::IntegerParameter::Write( | |
| QuicDataWriter* writer) const { | |
| QUICHE_DCHECK(IsValid()); | |
| if (value_ == default_value_) { | |
| // Do not write if the value is default. | |
| return true; | |
| } | |
| return WriteIntegerTlv(writer, param_id_, value_); | |
| } | |
| bool TransportParameters::IntegerParameter::Read(QuicDataReader* reader, | |
| std::string* error_details) { | |
| if (has_been_read_) { | |
| *error_details = | |
| "Received a second " + TransportParameterIdToString(param_id_); | |
| return false; | |
| } | |
| has_been_read_ = true; | |
| return ReadIntegerValue(reader, param_id_, value_, error_details); | |
| } | |
| std::string TransportParameters::IntegerParameter::ToString( | |
| bool for_use_in_list) const { | |
| if (for_use_in_list && value_ == default_value_) { | |
| return ""; | |
| } | |
| std::string rv = for_use_in_list ? " " : ""; | |
| absl::StrAppend(&rv, TransportParameterIdToString(param_id_), " ", value_); | |
| if (!IsValid()) { | |
| rv += " (Invalid)"; | |
| } | |
| return rv; | |
| } | |
| std::ostream& operator<<(std::ostream& os, | |
| const TransportParameters::IntegerParameter& param) { | |
| os << param.ToString(/*for_use_in_list=*/false); | |
| return os; | |
| } | |
| TransportParameters::PreferredAddress::PreferredAddress() | |
| : ipv4_socket_address(QuicIpAddress::Any4(), 0), | |
| ipv6_socket_address(QuicIpAddress::Any6(), 0), | |
| connection_id(EmptyQuicConnectionId()), | |
| stateless_reset_token(kStatelessResetTokenLength, 0) {} | |
| TransportParameters::PreferredAddress::~PreferredAddress() {} | |
| bool TransportParameters::PreferredAddress::operator==( | |
| const PreferredAddress& rhs) const { | |
| return ipv4_socket_address == rhs.ipv4_socket_address && | |
| ipv6_socket_address == rhs.ipv6_socket_address && | |
| connection_id == rhs.connection_id && | |
| stateless_reset_token == rhs.stateless_reset_token; | |
| } | |
| bool TransportParameters::PreferredAddress::operator!=( | |
| const PreferredAddress& rhs) const { | |
| return !(*this == rhs); | |
| } | |
| std::ostream& operator<<( | |
| std::ostream& os, | |
| const TransportParameters::PreferredAddress& preferred_address) { | |
| os << preferred_address.ToString(); | |
| return os; | |
| } | |
| std::string TransportParameters::PreferredAddress::ToString() const { | |
| return "[" + ipv4_socket_address.ToString() + " " + | |
| ipv6_socket_address.ToString() + " connection_id " + | |
| connection_id.ToString() + " stateless_reset_token " + | |
| absl::BytesToHexString(absl::string_view( | |
| reinterpret_cast<const char*>(stateless_reset_token.data()), | |
| stateless_reset_token.size())) + | |
| "]"; | |
| } | |
| TransportParameters::VersionInformation::VersionInformation() | |
| : chosen_version(0) {} | |
| bool TransportParameters::VersionInformation::operator==( | |
| const VersionInformation& rhs) const { | |
| return chosen_version == rhs.chosen_version && | |
| other_versions == rhs.other_versions; | |
| } | |
| bool TransportParameters::VersionInformation::operator!=( | |
| const VersionInformation& rhs) const { | |
| return !(*this == rhs); | |
| } | |
| std::string TransportParameters::VersionInformation::ToString() const { | |
| std::string rv = absl::StrCat("[chosen_version ", | |
| QuicVersionLabelToString(chosen_version)); | |
| if (!other_versions.empty()) { | |
| absl::StrAppend(&rv, " other_versions " + | |
| QuicVersionLabelVectorToString(other_versions)); | |
| } | |
| absl::StrAppend(&rv, "]"); | |
| return rv; | |
| } | |
| std::ostream& operator<<( | |
| std::ostream& os, | |
| const TransportParameters::VersionInformation& version_information) { | |
| os << version_information.ToString(); | |
| return os; | |
| } | |
| std::ostream& operator<<(std::ostream& os, const TransportParameters& params) { | |
| os << params.ToString(); | |
| return os; | |
| } | |
| std::string TransportParameters::ToString() const { | |
| std::string rv = "["; | |
| if (perspective == Perspective::IS_SERVER) { | |
| rv += "Server"; | |
| } else { | |
| rv += "Client"; | |
| } | |
| if (version_information.has_value()) { | |
| rv += " " + version_information->ToString(); | |
| } | |
| if (original_destination_connection_id.has_value()) { | |
| rv += " " + TransportParameterIdToString(kOriginalDestinationConnectionId) + | |
| " " + original_destination_connection_id->ToString(); | |
| } | |
| rv += max_idle_timeout_ms.ToString(/*for_use_in_list=*/true); | |
| if (!stateless_reset_token.empty()) { | |
| rv += " " + TransportParameterIdToString(kStatelessResetToken) + " " + | |
| absl::BytesToHexString(absl::string_view( | |
| reinterpret_cast<const char*>(stateless_reset_token.data()), | |
| stateless_reset_token.size())); | |
| } | |
| rv += max_udp_payload_size.ToString(/*for_use_in_list=*/true); | |
| rv += initial_max_data.ToString(/*for_use_in_list=*/true); | |
| rv += initial_max_stream_data_bidi_local.ToString(/*for_use_in_list=*/true); | |
| rv += initial_max_stream_data_bidi_remote.ToString(/*for_use_in_list=*/true); | |
| rv += initial_max_stream_data_uni.ToString(/*for_use_in_list=*/true); | |
| rv += initial_max_streams_bidi.ToString(/*for_use_in_list=*/true); | |
| rv += initial_max_streams_uni.ToString(/*for_use_in_list=*/true); | |
| rv += ack_delay_exponent.ToString(/*for_use_in_list=*/true); | |
| rv += max_ack_delay.ToString(/*for_use_in_list=*/true); | |
| if (min_ack_delay_us_draft10.has_value()) { | |
| absl::StrAppend(&rv, " ", TransportParameterIdToString(kMinAckDelayDraft10), | |
| " ", *min_ack_delay_us_draft10); | |
| } | |
| if (disable_active_migration) { | |
| rv += " " + TransportParameterIdToString(kDisableActiveMigration); | |
| } | |
| if (reliable_stream_reset) { | |
| rv += " " + TransportParameterIdToString(kReliableStreamReset); | |
| } | |
| if (scone_supported) { | |
| rv += " " + TransportParameterIdToString(kSconeSupported); | |
| } | |
| if (preferred_address) { | |
| rv += " " + TransportParameterIdToString(kPreferredAddress) + " " + | |
| preferred_address->ToString(); | |
| } | |
| rv += active_connection_id_limit.ToString(/*for_use_in_list=*/true); | |
| if (initial_source_connection_id.has_value()) { | |
| rv += " " + TransportParameterIdToString(kInitialSourceConnectionId) + " " + | |
| initial_source_connection_id->ToString(); | |
| } | |
| if (retry_source_connection_id.has_value()) { | |
| rv += " " + TransportParameterIdToString(kRetrySourceConnectionId) + " " + | |
| retry_source_connection_id->ToString(); | |
| } | |
| rv += max_datagram_frame_size.ToString(/*for_use_in_list=*/true); | |
| if (discard_length >= 0) { | |
| absl::StrAppend(&rv, " ", TransportParameterIdToString(kDiscard), | |
| " length: ", discard_length); | |
| } | |
| if (google_handshake_message.has_value()) { | |
| absl::StrAppend(&rv, " ", | |
| TransportParameterIdToString(kGoogleHandshakeMessage), | |
| " length: ", google_handshake_message->length()); | |
| } | |
| if (debugging_sni.has_value()) { | |
| absl::StrAppend(&rv, " ", TransportParameterIdToString(kDebuggingSni), | |
| " value: ", *debugging_sni); | |
| } | |
| rv += initial_round_trip_time_us.ToString(/*for_use_in_list=*/true); | |
| if (google_connection_options.has_value()) { | |
| rv += " " + TransportParameterIdToString(kGoogleConnectionOptions) + " "; | |
| bool first = true; | |
| for (const QuicTag& connection_option : *google_connection_options) { | |
| if (first) { | |
| first = false; | |
| } else { | |
| rv += ","; | |
| } | |
| rv += QuicTagToString(connection_option); | |
| } | |
| } | |
| for (const auto& kv : custom_parameters) { | |
| absl::StrAppend(&rv, " 0x", absl::Hex(static_cast<uint32_t>(kv.first)), | |
| "="); | |
| static constexpr size_t kMaxPrintableLength = 32; | |
| if (kv.second.length() <= kMaxPrintableLength) { | |
| rv += absl::BytesToHexString(kv.second); | |
| } else { | |
| absl::string_view truncated(kv.second.data(), kMaxPrintableLength); | |
| rv += absl::StrCat(absl::BytesToHexString(truncated), "...(length ", | |
| kv.second.length(), ")"); | |
| } | |
| } | |
| rv += "]"; | |
| return rv; | |
| } | |
| TransportParameters::TransportParameters() | |
| : max_idle_timeout_ms(kMaxIdleTimeout), | |
| max_udp_payload_size(kMaxPacketSize, kDefaultMaxPacketSizeTransportParam, | |
| kMinMaxPacketSizeTransportParam, | |
| quiche::kVarInt62MaxValue), | |
| initial_max_data(kInitialMaxData), | |
| initial_max_stream_data_bidi_local(kInitialMaxStreamDataBidiLocal), | |
| initial_max_stream_data_bidi_remote(kInitialMaxStreamDataBidiRemote), | |
| initial_max_stream_data_uni(kInitialMaxStreamDataUni), | |
| initial_max_streams_bidi(kInitialMaxStreamsBidi), | |
| initial_max_streams_uni(kInitialMaxStreamsUni), | |
| ack_delay_exponent(kAckDelayExponent, | |
| kDefaultAckDelayExponentTransportParam, 0, | |
| kMaxAckDelayExponentTransportParam), | |
| max_ack_delay(kMaxAckDelay, kDefaultMaxAckDelayTransportParam, 0, | |
| kMaxMaxAckDelayTransportParam), | |
| active_connection_id_limit(kActiveConnectionIdLimit, | |
| kDefaultActiveConnectionIdLimitTransportParam, | |
| kMinActiveConnectionIdLimitTransportParam, | |
| quiche::kVarInt62MaxValue), | |
| max_datagram_frame_size(kMaxDatagramFrameSize), | |
| scone_supported(false), | |
| initial_round_trip_time_us(kInitialRoundTripTime), | |
| disable_active_migration(false), | |
| reliable_stream_reset(false) | |
| // Important note: any new transport parameters must be added | |
| // to TransportParameters::AreValid, SerializeTransportParameters and | |
| // ParseTransportParameters, TransportParameters's custom copy constructor, the | |
| // operator==, and TransportParametersTest.Comparator. | |
| {} | |
| TransportParameters::TransportParameters(const TransportParameters& other) | |
| : version_information(other.version_information), | |
| original_destination_connection_id( | |
| other.original_destination_connection_id), | |
| max_idle_timeout_ms(other.max_idle_timeout_ms), | |
| stateless_reset_token(other.stateless_reset_token), | |
| max_udp_payload_size(other.max_udp_payload_size), | |
| initial_max_data(other.initial_max_data), | |
| initial_max_stream_data_bidi_local( | |
| other.initial_max_stream_data_bidi_local), | |
| initial_max_stream_data_bidi_remote( | |
| other.initial_max_stream_data_bidi_remote), | |
| initial_max_stream_data_uni(other.initial_max_stream_data_uni), | |
| initial_max_streams_bidi(other.initial_max_streams_bidi), | |
| initial_max_streams_uni(other.initial_max_streams_uni), | |
| ack_delay_exponent(other.ack_delay_exponent), | |
| max_ack_delay(other.max_ack_delay), | |
| min_ack_delay_us_draft10(other.min_ack_delay_us_draft10), | |
| active_connection_id_limit(other.active_connection_id_limit), | |
| initial_source_connection_id(other.initial_source_connection_id), | |
| retry_source_connection_id(other.retry_source_connection_id), | |
| max_datagram_frame_size(other.max_datagram_frame_size), | |
| scone_supported(other.scone_supported), | |
| initial_round_trip_time_us(other.initial_round_trip_time_us), | |
| google_handshake_message(other.google_handshake_message), | |
| discard_length(other.discard_length), | |
| perspective(other.perspective), | |
| disable_active_migration(other.disable_active_migration), | |
| reliable_stream_reset(other.reliable_stream_reset), | |
| debugging_sni(other.debugging_sni), | |
| google_connection_options(other.google_connection_options), | |
| custom_parameters(other.custom_parameters) { | |
| if (other.preferred_address) { | |
| preferred_address = std::make_unique<TransportParameters::PreferredAddress>( | |
| *other.preferred_address); | |
| } | |
| } | |
| bool TransportParameters::operator==(const TransportParameters& rhs) const { | |
| if (!(perspective == rhs.perspective && | |
| version_information == rhs.version_information && | |
| original_destination_connection_id == | |
| rhs.original_destination_connection_id && | |
| max_idle_timeout_ms.value() == rhs.max_idle_timeout_ms.value() && | |
| stateless_reset_token == rhs.stateless_reset_token && | |
| max_udp_payload_size.value() == rhs.max_udp_payload_size.value() && | |
| initial_max_data.value() == rhs.initial_max_data.value() && | |
| initial_max_stream_data_bidi_local.value() == | |
| rhs.initial_max_stream_data_bidi_local.value() && | |
| initial_max_stream_data_bidi_remote.value() == | |
| rhs.initial_max_stream_data_bidi_remote.value() && | |
| initial_max_stream_data_uni.value() == | |
| rhs.initial_max_stream_data_uni.value() && | |
| initial_max_streams_bidi.value() == | |
| rhs.initial_max_streams_bidi.value() && | |
| initial_max_streams_uni.value() == | |
| rhs.initial_max_streams_uni.value() && | |
| ack_delay_exponent.value() == rhs.ack_delay_exponent.value() && | |
| max_ack_delay.value() == rhs.max_ack_delay.value() && | |
| min_ack_delay_us_draft10 == rhs.min_ack_delay_us_draft10 && | |
| disable_active_migration == rhs.disable_active_migration && | |
| active_connection_id_limit.value() == | |
| rhs.active_connection_id_limit.value() && | |
| initial_source_connection_id == rhs.initial_source_connection_id && | |
| retry_source_connection_id == rhs.retry_source_connection_id && | |
| max_datagram_frame_size.value() == | |
| rhs.max_datagram_frame_size.value() && | |
| reliable_stream_reset == rhs.reliable_stream_reset && | |
| scone_supported == rhs.scone_supported && | |
| initial_round_trip_time_us.value() == | |
| rhs.initial_round_trip_time_us.value() && | |
| discard_length == rhs.discard_length && | |
| google_handshake_message == rhs.google_handshake_message && | |
| debugging_sni == rhs.debugging_sni && | |
| google_connection_options == rhs.google_connection_options && | |
| custom_parameters == rhs.custom_parameters)) { | |
| return false; | |
| } | |
| if ((!preferred_address && rhs.preferred_address) || | |
| (preferred_address && !rhs.preferred_address)) { | |
| return false; | |
| } | |
| if (preferred_address && rhs.preferred_address && | |
| *preferred_address != *rhs.preferred_address) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool TransportParameters::operator!=(const TransportParameters& rhs) const { | |
| return !(*this == rhs); | |
| } | |
| bool TransportParameters::AreValid(std::string* error_details) const { | |
| QUICHE_DCHECK(perspective == Perspective::IS_CLIENT || | |
| perspective == Perspective::IS_SERVER); | |
| if (perspective == Perspective::IS_CLIENT && !stateless_reset_token.empty()) { | |
| *error_details = "Client cannot send stateless reset token"; | |
| return false; | |
| } | |
| if (perspective == Perspective::IS_CLIENT && | |
| original_destination_connection_id.has_value()) { | |
| *error_details = "Client cannot send original_destination_connection_id"; | |
| return false; | |
| } | |
| if (!stateless_reset_token.empty() && | |
| stateless_reset_token.size() != kStatelessResetTokenLength) { | |
| *error_details = absl::StrCat("Stateless reset token has bad length ", | |
| stateless_reset_token.size()); | |
| return false; | |
| } | |
| if (perspective == Perspective::IS_CLIENT && preferred_address) { | |
| *error_details = "Client cannot send preferred address"; | |
| return false; | |
| } | |
| if (preferred_address && preferred_address->stateless_reset_token.size() != | |
| kStatelessResetTokenLength) { | |
| *error_details = | |
| absl::StrCat("Preferred address stateless reset token has bad length ", | |
| preferred_address->stateless_reset_token.size()); | |
| return false; | |
| } | |
| if (preferred_address && | |
| (!preferred_address->ipv4_socket_address.host().IsIPv4() || | |
| !preferred_address->ipv6_socket_address.host().IsIPv6())) { | |
| QUIC_BUG(quic_bug_10743_4) << "Preferred address family failure"; | |
| *error_details = "Internal preferred address family failure"; | |
| return false; | |
| } | |
| if (perspective == Perspective::IS_CLIENT && | |
| retry_source_connection_id.has_value()) { | |
| *error_details = "Client cannot send retry_source_connection_id"; | |
| return false; | |
| } | |
| for (const auto& kv : custom_parameters) { | |
| if (TransportParameterIdIsKnown(kv.first)) { | |
| *error_details = absl::StrCat("Using custom_parameters with known ID ", | |
| TransportParameterIdToString(kv.first), | |
| " is not allowed"); | |
| return false; | |
| } | |
| } | |
| if (perspective == Perspective::IS_SERVER && | |
| google_handshake_message.has_value()) { | |
| *error_details = "Server cannot send google_handshake_message"; | |
| return false; | |
| } | |
| if (perspective == Perspective::IS_SERVER && debugging_sni.has_value()) { | |
| *error_details = "Server cannot send debugging_sni"; | |
| return false; | |
| } | |
| if (perspective == Perspective::IS_SERVER && | |
| initial_round_trip_time_us.value() > 0) { | |
| *error_details = "Server cannot send initial round trip time"; | |
| return false; | |
| } | |
| if (min_ack_delay_us_draft10.has_value() && | |
| (*min_ack_delay_us_draft10 > | |
| (max_ack_delay.value() * kNumMicrosPerMilli))) { | |
| *error_details = "min_ack_delay cannot be larger than max_ack_delay"; | |
| return false; | |
| } | |
| if (version_information.has_value()) { | |
| const QuicVersionLabel& chosen_version = | |
| version_information->chosen_version; | |
| const QuicVersionLabelVector& other_versions = | |
| version_information->other_versions; | |
| if (chosen_version == 0) { | |
| *error_details = "Invalid chosen version"; | |
| return false; | |
| } | |
| if (perspective == Perspective::IS_CLIENT && | |
| std::find(other_versions.begin(), other_versions.end(), | |
| chosen_version) == other_versions.end()) { | |
| // When sent by the client, chosen_version needs to be present in | |
| // other_versions because other_versions lists the compatible versions and | |
| // the chosen version is part of that list. When sent by the server, | |
| // other_version contains the list of fully-deployed versions which is | |
| // generally equal to the list of supported versions but can slightly | |
| // differ during removal of versions across a server fleet. See | |
| // draft-ietf-quic-version-negotiation for details. | |
| *error_details = "Client chosen version not in other versions"; | |
| return false; | |
| } | |
| } | |
| const bool ok = | |
| max_idle_timeout_ms.IsValid() && max_udp_payload_size.IsValid() && | |
| initial_max_data.IsValid() && | |
| initial_max_stream_data_bidi_local.IsValid() && | |
| initial_max_stream_data_bidi_remote.IsValid() && | |
| initial_max_stream_data_uni.IsValid() && | |
| initial_max_streams_bidi.IsValid() && initial_max_streams_uni.IsValid() && | |
| ack_delay_exponent.IsValid() && max_ack_delay.IsValid() && | |
| active_connection_id_limit.IsValid() && | |
| max_datagram_frame_size.IsValid() && initial_round_trip_time_us.IsValid(); | |
| if (!ok) { | |
| *error_details = "Invalid transport parameters " + this->ToString(); | |
| } | |
| return ok; | |
| } | |
| TransportParameters::~TransportParameters() = default; | |
| bool SerializeTransportParameters(const TransportParameters& in, | |
| std::vector<uint8_t>* out) { | |
| std::string error_details; | |
| if (!in.AreValid(&error_details)) { | |
| QUIC_BUG(invalid transport parameters) | |
| << "Not serializing invalid transport parameters: " << error_details; | |
| return false; | |
| } | |
| TransportParameters::ParameterMap custom_parameters = in.custom_parameters; | |
| for (const auto& kv : custom_parameters) { | |
| if (kv.first % 31 == 27) { | |
| // See the "Reserved Transport Parameters" section of RFC 9000. | |
| QUIC_BUG(custom_parameters with GREASE) | |
| << "Serializing custom_parameters with GREASE ID " << kv.first | |
| << " is not allowed"; | |
| return false; | |
| } | |
| } | |
| // Maximum length of the GREASE transport parameter (see below). | |
| static constexpr size_t kMaxGreaseLength = 16; | |
| // Empirically transport parameters generally fit within 128 bytes, but we | |
| // need to allocate the size up front. Integer transport parameters | |
| // have a maximum encoded length of 24 bytes (3 variable length integers), | |
| // other transport parameters have a length of 16 + the maximum value length. | |
| static constexpr size_t kTypeAndValueLength = 2 * sizeof(uint64_t); | |
| static constexpr size_t kIntegerParameterLength = | |
| kTypeAndValueLength + sizeof(uint64_t); | |
| static constexpr size_t kStatelessResetParameterLength = | |
| kTypeAndValueLength + 16 /* stateless reset token length */; | |
| static constexpr size_t kConnectionIdParameterLength = | |
| kTypeAndValueLength + 255 /* maximum connection ID length */; | |
| static constexpr size_t kPreferredAddressParameterLength = | |
| kTypeAndValueLength + 4 /*IPv4 address */ + 2 /* IPv4 port */ + | |
| 16 /* IPv6 address */ + 1 /* Connection ID length */ + | |
| 255 /* maximum connection ID length */ + 16 /* stateless reset token */; | |
| static constexpr size_t kKnownTransportParamLength = | |
| kConnectionIdParameterLength + // original_destination_connection_id | |
| kIntegerParameterLength + // max_idle_timeout | |
| kStatelessResetParameterLength + // stateless_reset_token | |
| kIntegerParameterLength + // max_udp_payload_size | |
| kIntegerParameterLength + // initial_max_data | |
| kIntegerParameterLength + // initial_max_stream_data_bidi_local | |
| kIntegerParameterLength + // initial_max_stream_data_bidi_remote | |
| kIntegerParameterLength + // initial_max_stream_data_uni | |
| kIntegerParameterLength + // initial_max_streams_bidi | |
| kIntegerParameterLength + // initial_max_streams_uni | |
| kIntegerParameterLength + // ack_delay_exponent | |
| kIntegerParameterLength + // max_ack_delay | |
| kIntegerParameterLength + // min_ack_delay_us_draft10 | |
| kTypeAndValueLength + // disable_active_migration | |
| kPreferredAddressParameterLength + // preferred_address | |
| kIntegerParameterLength + // active_connection_id_limit | |
| kConnectionIdParameterLength + // initial_source_connection_id | |
| kConnectionIdParameterLength + // retry_source_connection_id | |
| kIntegerParameterLength + // max_datagram_frame_size | |
| kTypeAndValueLength + // reliable_stream_reset | |
| kTypeAndValueLength + // scone_supported | |
| kIntegerParameterLength + // initial_round_trip_time_us | |
| kTypeAndValueLength + // discard | |
| kTypeAndValueLength + // google_handshake_message | |
| kTypeAndValueLength + // debugging_sni | |
| kTypeAndValueLength + // google_connection_options | |
| kTypeAndValueLength; // version_information | |
| std::vector<TransportParameters::TransportParameterId> parameter_ids = { | |
| TransportParameters::kOriginalDestinationConnectionId, | |
| TransportParameters::kMaxIdleTimeout, | |
| TransportParameters::kStatelessResetToken, | |
| TransportParameters::kMaxPacketSize, | |
| TransportParameters::kInitialMaxData, | |
| TransportParameters::kInitialMaxStreamDataBidiLocal, | |
| TransportParameters::kInitialMaxStreamDataBidiRemote, | |
| TransportParameters::kInitialMaxStreamDataUni, | |
| TransportParameters::kInitialMaxStreamsBidi, | |
| TransportParameters::kInitialMaxStreamsUni, | |
| TransportParameters::kAckDelayExponent, | |
| TransportParameters::kMaxAckDelay, | |
| TransportParameters::kMinAckDelayDraft10, | |
| TransportParameters::kActiveConnectionIdLimit, | |
| TransportParameters::kMaxDatagramFrameSize, | |
| TransportParameters::kReliableStreamReset, | |
| TransportParameters::kDiscard, | |
| TransportParameters::kSconeSupported, | |
| TransportParameters::kGoogleHandshakeMessage, | |
| TransportParameters::kDebuggingSni, | |
| TransportParameters::kInitialRoundTripTime, | |
| TransportParameters::kDisableActiveMigration, | |
| TransportParameters::kPreferredAddress, | |
| TransportParameters::kInitialSourceConnectionId, | |
| TransportParameters::kRetrySourceConnectionId, | |
| TransportParameters::kGoogleConnectionOptions, | |
| TransportParameters::kVersionInformation, | |
| }; | |
| size_t max_transport_param_length = kKnownTransportParamLength; | |
| // google_connection_options. | |
| if (in.google_connection_options.has_value()) { | |
| max_transport_param_length += | |
| in.google_connection_options->size() * sizeof(QuicTag); | |
| } | |
| // version_information. | |
| if (in.version_information.has_value()) { | |
| max_transport_param_length += | |
| sizeof(in.version_information->chosen_version) + | |
| // Add one for the added GREASE version. | |
| (in.version_information->other_versions.size() + 1) * | |
| sizeof(QuicVersionLabel); | |
| } | |
| // discard. | |
| if (in.discard_length >= 0) { | |
| max_transport_param_length += in.discard_length; | |
| } | |
| // google_handshake_message. | |
| if (in.google_handshake_message.has_value()) { | |
| max_transport_param_length += in.google_handshake_message->length(); | |
| } | |
| // debugging_sni. | |
| if (in.debugging_sni.has_value()) { | |
| max_transport_param_length += in.debugging_sni->length(); | |
| } | |
| // Add a random GREASE transport parameter, as defined in the | |
| // "Reserved Transport Parameters" section of RFC 9000. | |
| // This forces receivers to support unexpected input. | |
| QuicRandom* random = QuicRandom::GetInstance(); | |
| // Transport parameter identifiers are 62 bits long so we need to | |
| // ensure that the output of the computation below fits in 62 bits. | |
| uint64_t grease_id64 = random->RandUint64() % ((1ULL << 62) - 31); | |
| // Make sure grease_id % 31 == 27. Note that this is not uniformely | |
| // distributed but is acceptable since no security depends on this | |
| // randomness. | |
| grease_id64 = (grease_id64 / 31) * 31 + 27; | |
| TransportParameters::TransportParameterId grease_id = | |
| static_cast<TransportParameters::TransportParameterId>(grease_id64); | |
| const size_t grease_length = random->RandUint64() % kMaxGreaseLength; | |
| QUICHE_DCHECK_GE(kMaxGreaseLength, grease_length); | |
| char grease_contents[kMaxGreaseLength]; | |
| random->RandBytes(grease_contents, grease_length); | |
| custom_parameters[grease_id] = std::string(grease_contents, grease_length); | |
| // Custom parameters. | |
| for (const auto& kv : custom_parameters) { | |
| max_transport_param_length += kTypeAndValueLength + kv.second.length(); | |
| parameter_ids.push_back(kv.first); | |
| } | |
| // Randomize order of sent transport parameters by walking the array | |
| // backwards and swapping each element with a random earlier one. | |
| for (size_t i = parameter_ids.size() - 1; i > 0; i--) { | |
| std::swap(parameter_ids[i], | |
| parameter_ids[random->InsecureRandUint64() % (i + 1)]); | |
| } | |
| out->resize(max_transport_param_length); | |
| QuicDataWriter writer(out->size(), reinterpret_cast<char*>(out->data())); | |
| for (TransportParameters::TransportParameterId parameter_id : parameter_ids) { | |
| switch (parameter_id) { | |
| // original_destination_connection_id | |
| case TransportParameters::kOriginalDestinationConnectionId: { | |
| if (in.original_destination_connection_id.has_value()) { | |
| QUICHE_DCHECK_EQ(Perspective::IS_SERVER, in.perspective); | |
| QuicConnectionId original_destination_connection_id = | |
| *in.original_destination_connection_id; | |
| if (!writer.WriteVarInt62( | |
| TransportParameters::kOriginalDestinationConnectionId) || | |
| !writer.WriteStringPieceVarInt62(absl::string_view( | |
| original_destination_connection_id.data(), | |
| original_destination_connection_id.length()))) { | |
| QUIC_BUG(Failed to write original_destination_connection_id) | |
| << "Failed to write original_destination_connection_id " | |
| << original_destination_connection_id << " for " << in; | |
| return false; | |
| } | |
| } | |
| } break; | |
| // max_idle_timeout | |
| case TransportParameters::kMaxIdleTimeout: { | |
| if (!in.max_idle_timeout_ms.Write(&writer)) { | |
| QUIC_BUG(Failed to write idle_timeout) | |
| << "Failed to write idle_timeout for " << in; | |
| return false; | |
| } | |
| } break; | |
| // stateless_reset_token | |
| case TransportParameters::kStatelessResetToken: { | |
| if (!in.stateless_reset_token.empty()) { | |
| QUICHE_DCHECK_EQ(kStatelessResetTokenLength, | |
| in.stateless_reset_token.size()); | |
| QUICHE_DCHECK_EQ(Perspective::IS_SERVER, in.perspective); | |
| if (!writer.WriteVarInt62( | |
| TransportParameters::kStatelessResetToken) || | |
| !writer.WriteStringPieceVarInt62( | |
| absl::string_view(reinterpret_cast<const char*>( | |
| in.stateless_reset_token.data()), | |
| in.stateless_reset_token.size()))) { | |
| QUIC_BUG(Failed to write stateless_reset_token) | |
| << "Failed to write stateless_reset_token of length " | |
| << in.stateless_reset_token.size() << " for " << in; | |
| return false; | |
| } | |
| } | |
| } break; | |
| // max_udp_payload_size | |
| case TransportParameters::kMaxPacketSize: { | |
| if (!in.max_udp_payload_size.Write(&writer)) { | |
| QUIC_BUG(Failed to write max_udp_payload_size) | |
| << "Failed to write max_udp_payload_size for " << in; | |
| return false; | |
| } | |
| } break; | |
| // initial_max_data | |
| case TransportParameters::kInitialMaxData: { | |
| if (!in.initial_max_data.Write(&writer)) { | |
| QUIC_BUG(Failed to write initial_max_data) | |
| << "Failed to write initial_max_data for " << in; | |
| return false; | |
| } | |
| } break; | |
| // initial_max_stream_data_bidi_local | |
| case TransportParameters::kInitialMaxStreamDataBidiLocal: { | |
| if (!in.initial_max_stream_data_bidi_local.Write(&writer)) { | |
| QUIC_BUG(Failed to write initial_max_stream_data_bidi_local) | |
| << "Failed to write initial_max_stream_data_bidi_local for " | |
| << in; | |
| return false; | |
| } | |
| } break; | |
| // initial_max_stream_data_bidi_remote | |
| case TransportParameters::kInitialMaxStreamDataBidiRemote: { | |
| if (!in.initial_max_stream_data_bidi_remote.Write(&writer)) { | |
| QUIC_BUG(Failed to write initial_max_stream_data_bidi_remote) | |
| << "Failed to write initial_max_stream_data_bidi_remote for " | |
| << in; | |
| return false; | |
| } | |
| } break; | |
| // initial_max_stream_data_uni | |
| case TransportParameters::kInitialMaxStreamDataUni: { | |
| if (!in.initial_max_stream_data_uni.Write(&writer)) { | |
| QUIC_BUG(Failed to write initial_max_stream_data_uni) | |
| << "Failed to write initial_max_stream_data_uni for " << in; | |
| return false; | |
| } | |
| } break; | |
| // initial_max_streams_bidi | |
| case TransportParameters::kInitialMaxStreamsBidi: { | |
| if (!in.initial_max_streams_bidi.Write(&writer)) { | |
| QUIC_BUG(Failed to write initial_max_streams_bidi) | |
| << "Failed to write initial_max_streams_bidi for " << in; | |
| return false; | |
| } | |
| } break; | |
| // initial_max_streams_uni | |
| case TransportParameters::kInitialMaxStreamsUni: { | |
| if (!in.initial_max_streams_uni.Write(&writer)) { | |
| QUIC_BUG(Failed to write initial_max_streams_uni) | |
| << "Failed to write initial_max_streams_uni for " << in; | |
| return false; | |
| } | |
| } break; | |
| // ack_delay_exponent | |
| case TransportParameters::kAckDelayExponent: { | |
| if (!in.ack_delay_exponent.Write(&writer)) { | |
| QUIC_BUG(Failed to write ack_delay_exponent) | |
| << "Failed to write ack_delay_exponent for " << in; | |
| return false; | |
| } | |
| } break; | |
| // max_ack_delay | |
| case TransportParameters::kMaxAckDelay: { | |
| if (!in.max_ack_delay.Write(&writer)) { | |
| QUIC_BUG(Failed to write max_ack_delay) | |
| << "Failed to write max_ack_delay for " << in; | |
| return false; | |
| } | |
| } break; | |
| // min_ack_delay_us_draft10 | |
| case TransportParameters::kMinAckDelayDraft10: { | |
| if (!in.min_ack_delay_us_draft10.has_value()) { | |
| break; | |
| } | |
| if (!WriteIntegerTlv(&writer, parameter_id, | |
| *in.min_ack_delay_us_draft10)) { | |
| QUIC_BUG(quic_bug_min_ack_delay_failed_write) | |
| << "Failed to write " | |
| << " min_ack_delay_us_draft10 for " << in; | |
| return false; | |
| } | |
| } break; | |
| // active_connection_id_limit | |
| case TransportParameters::kActiveConnectionIdLimit: { | |
| if (!in.active_connection_id_limit.Write(&writer)) { | |
| QUIC_BUG(Failed to write active_connection_id_limit) | |
| << "Failed to write active_connection_id_limit for " << in; | |
| return false; | |
| } | |
| } break; | |
| // max_datagram_frame_size | |
| case TransportParameters::kMaxDatagramFrameSize: { | |
| if (!in.max_datagram_frame_size.Write(&writer)) { | |
| QUIC_BUG(Failed to write max_datagram_frame_size) | |
| << "Failed to write max_datagram_frame_size for " << in; | |
| return false; | |
| } | |
| } break; | |
| // discard | |
| case TransportParameters::kDiscard: { | |
| if (in.discard_length >= 0) { | |
| std::string discard_data(in.discard_length, '\0'); | |
| if (!writer.WriteVarInt62(TransportParameters::kDiscard) || | |
| !writer.WriteStringPieceVarInt62(discard_data)) { | |
| QUIC_BUG(Failed to write discard_data) | |
| << "Failed to write discard data of length: " | |
| << in.discard_length << " for " << in; | |
| return false; | |
| } | |
| } | |
| } break; | |
| // google_handshake_message | |
| case TransportParameters::kGoogleHandshakeMessage: { | |
| if (in.google_handshake_message.has_value()) { | |
| if (!writer.WriteVarInt62( | |
| TransportParameters::kGoogleHandshakeMessage) || | |
| !writer.WriteStringPieceVarInt62(*in.google_handshake_message)) { | |
| QUIC_BUG(Failed to write google_handshake_message) | |
| << "Failed to write google_handshake_message: " | |
| << *in.google_handshake_message << " for " << in; | |
| return false; | |
| } | |
| } | |
| } break; | |
| // debugging_sni. | |
| case TransportParameters::kDebuggingSni: { | |
| if (in.debugging_sni.has_value()) { | |
| QUICHE_DCHECK_EQ(Perspective::IS_CLIENT, in.perspective); | |
| // Obfuscate the SNI before writing it to the transport parameters. | |
| std::string obfuscated_sni = ObfuscateSni(*in.debugging_sni); | |
| if (!writer.WriteVarInt62(TransportParameters::kDebuggingSni) || | |
| !writer.WriteStringPieceVarInt62(obfuscated_sni)) { | |
| QUIC_BUG(failed_to_write_debugging_sni) | |
| << "Failed to write debugging_sni: " << *in.debugging_sni | |
| << " obfuscated to: " << obfuscated_sni << " for " << in; | |
| return false; | |
| } | |
| } | |
| } break; | |
| // initial_round_trip_time_us | |
| case TransportParameters::kInitialRoundTripTime: { | |
| if (!in.initial_round_trip_time_us.Write(&writer)) { | |
| QUIC_BUG(Failed to write initial_round_trip_time_us) | |
| << "Failed to write initial_round_trip_time_us for " << in; | |
| return false; | |
| } | |
| } break; | |
| // disable_active_migration | |
| case TransportParameters::kDisableActiveMigration: { | |
| if (in.disable_active_migration) { | |
| if (!writer.WriteVarInt62( | |
| TransportParameters::kDisableActiveMigration) || | |
| !writer.WriteVarInt62(/* transport parameter length */ 0)) { | |
| QUIC_BUG(Failed to write disable_active_migration) | |
| << "Failed to write disable_active_migration for " << in; | |
| return false; | |
| } | |
| } | |
| } break; | |
| // reliable_stream_reset | |
| case TransportParameters::kReliableStreamReset: { | |
| if (in.reliable_stream_reset) { | |
| if (!writer.WriteVarInt62( | |
| TransportParameters::kReliableStreamReset) || | |
| !writer.WriteVarInt62(/* transport parameter length */ 0)) { | |
| QUIC_BUG(Failed to write reliable_stream_reset) | |
| << "Failed to write reliable_stream_reset for " << in; | |
| return false; | |
| } | |
| } | |
| } break; | |
| // scone_supported | |
| case TransportParameters::kSconeSupported: { | |
| if (in.scone_supported) { | |
| if (!writer.WriteVarInt62(TransportParameters::kSconeSupported) || | |
| !writer.WriteVarInt62(/* transport parameter length */ 0)) { | |
| QUIC_BUG(failed_to_write_scone_supported) | |
| << "Failed to write scone_supported for " << in; | |
| return false; | |
| } | |
| } | |
| } break; | |
| // preferred_address | |
| case TransportParameters::kPreferredAddress: { | |
| if (in.preferred_address) { | |
| std::string v4_address_bytes = | |
| in.preferred_address->ipv4_socket_address.host().ToPackedString(); | |
| std::string v6_address_bytes = | |
| in.preferred_address->ipv6_socket_address.host().ToPackedString(); | |
| if (v4_address_bytes.length() != 4 || | |
| v6_address_bytes.length() != 16 || | |
| in.preferred_address->stateless_reset_token.size() != | |
| kStatelessResetTokenLength) { | |
| QUIC_BUG(quic_bug_10743_12) | |
| << "Bad lengths " << *in.preferred_address; | |
| return false; | |
| } | |
| const uint64_t preferred_address_length = | |
| v4_address_bytes.length() + /* IPv4 port */ sizeof(uint16_t) + | |
| v6_address_bytes.length() + /* IPv6 port */ sizeof(uint16_t) + | |
| /* connection ID length byte */ sizeof(uint8_t) + | |
| in.preferred_address->connection_id.length() + | |
| in.preferred_address->stateless_reset_token.size(); | |
| if (!writer.WriteVarInt62(TransportParameters::kPreferredAddress) || | |
| !writer.WriteVarInt62( | |
| /* transport parameter length */ preferred_address_length) || | |
| !writer.WriteStringPiece(v4_address_bytes) || | |
| !writer.WriteUInt16( | |
| in.preferred_address->ipv4_socket_address.port()) || | |
| !writer.WriteStringPiece(v6_address_bytes) || | |
| !writer.WriteUInt16( | |
| in.preferred_address->ipv6_socket_address.port()) || | |
| !writer.WriteUInt8( | |
| in.preferred_address->connection_id.length()) || | |
| !writer.WriteBytes( | |
| in.preferred_address->connection_id.data(), | |
| in.preferred_address->connection_id.length()) || | |
| !writer.WriteBytes( | |
| in.preferred_address->stateless_reset_token.data(), | |
| in.preferred_address->stateless_reset_token.size())) { | |
| QUIC_BUG(Failed to write preferred_address) | |
| << "Failed to write preferred_address for " << in; | |
| return false; | |
| } | |
| } | |
| } break; | |
| // initial_source_connection_id | |
| case TransportParameters::kInitialSourceConnectionId: { | |
| if (in.initial_source_connection_id.has_value()) { | |
| QuicConnectionId initial_source_connection_id = | |
| *in.initial_source_connection_id; | |
| if (!writer.WriteVarInt62( | |
| TransportParameters::kInitialSourceConnectionId) || | |
| !writer.WriteStringPieceVarInt62( | |
| absl::string_view(initial_source_connection_id.data(), | |
| initial_source_connection_id.length()))) { | |
| QUIC_BUG(Failed to write initial_source_connection_id) | |
| << "Failed to write initial_source_connection_id " | |
| << initial_source_connection_id << " for " << in; | |
| return false; | |
| } | |
| } | |
| } break; | |
| // retry_source_connection_id | |
| case TransportParameters::kRetrySourceConnectionId: { | |
| if (in.retry_source_connection_id.has_value()) { | |
| QUICHE_DCHECK_EQ(Perspective::IS_SERVER, in.perspective); | |
| QuicConnectionId retry_source_connection_id = | |
| *in.retry_source_connection_id; | |
| if (!writer.WriteVarInt62( | |
| TransportParameters::kRetrySourceConnectionId) || | |
| !writer.WriteStringPieceVarInt62( | |
| absl::string_view(retry_source_connection_id.data(), | |
| retry_source_connection_id.length()))) { | |
| QUIC_BUG(Failed to write retry_source_connection_id) | |
| << "Failed to write retry_source_connection_id " | |
| << retry_source_connection_id << " for " << in; | |
| return false; | |
| } | |
| } | |
| } break; | |
| // Google-specific connection options. | |
| case TransportParameters::kGoogleConnectionOptions: { | |
| if (in.google_connection_options.has_value()) { | |
| static_assert(sizeof(in.google_connection_options->front()) == 4, | |
| "bad size"); | |
| uint64_t connection_options_length = | |
| in.google_connection_options->size() * 4; | |
| if (!writer.WriteVarInt62( | |
| TransportParameters::kGoogleConnectionOptions) || | |
| !writer.WriteVarInt62( | |
| /* transport parameter length */ connection_options_length)) { | |
| QUIC_BUG(Failed to write google_connection_options) | |
| << "Failed to write google_connection_options of length " | |
| << connection_options_length << " for " << in; | |
| return false; | |
| } | |
| for (const QuicTag& connection_option : | |
| *in.google_connection_options) { | |
| if (!writer.WriteTag(connection_option)) { | |
| QUIC_BUG(Failed to write google_connection_option) | |
| << "Failed to write google_connection_option " | |
| << QuicTagToString(connection_option) << " for " << in; | |
| return false; | |
| } | |
| } | |
| } | |
| } break; | |
| // version_information. | |
| case TransportParameters::kVersionInformation: { | |
| if (!in.version_information.has_value()) { | |
| break; | |
| } | |
| static_assert(sizeof(QuicVersionLabel) == sizeof(uint32_t), | |
| "bad length"); | |
| QuicVersionLabelVector other_versions = | |
| in.version_information->other_versions; | |
| // Insert one GREASE version at a random index. | |
| const size_t grease_index = | |
| random->InsecureRandUint64() % (other_versions.size() + 1); | |
| other_versions.insert( | |
| other_versions.begin() + grease_index, | |
| CreateQuicVersionLabel(QuicVersionReservedForNegotiation())); | |
| const uint64_t version_information_length = | |
| sizeof(in.version_information->chosen_version) + | |
| sizeof(QuicVersionLabel) * other_versions.size(); | |
| if (!writer.WriteVarInt62(TransportParameters::kVersionInformation) || | |
| !writer.WriteVarInt62( | |
| /* transport parameter length */ version_information_length) || | |
| !writer.WriteUInt32(in.version_information->chosen_version)) { | |
| QUIC_BUG(Failed to write chosen version) | |
| << "Failed to write chosen version for " << in; | |
| return false; | |
| } | |
| for (QuicVersionLabel version_label : other_versions) { | |
| if (!writer.WriteUInt32(version_label)) { | |
| QUIC_BUG(Failed to write other version) | |
| << "Failed to write other version for " << in; | |
| return false; | |
| } | |
| } | |
| } break; | |
| // Custom parameters and GREASE. | |
| default: { | |
| auto it = custom_parameters.find(parameter_id); | |
| if (it == custom_parameters.end()) { | |
| QUIC_BUG(Unknown parameter) << "Unknown parameter " << parameter_id; | |
| return false; | |
| } | |
| if (!writer.WriteVarInt62(parameter_id) || | |
| !writer.WriteStringPieceVarInt62(it->second)) { | |
| QUIC_BUG(Failed to write custom parameter) | |
| << "Failed to write custom parameter " << parameter_id; | |
| return false; | |
| } | |
| } break; | |
| } | |
| } | |
| out->resize(writer.length()); | |
| QUIC_DLOG(INFO) << "Serialized " << in << " as " << writer.length() | |
| << " bytes"; | |
| return true; | |
| } // NOLINT(readability/fn_size) | |
| bool ParseTransportParameters(ParsedQuicVersion version, | |
| Perspective perspective, const uint8_t* in, | |
| size_t in_len, TransportParameters* out, | |
| std::string* error_details) { | |
| out->perspective = perspective; | |
| QuicDataReader reader(reinterpret_cast<const char*>(in), in_len); | |
| while (!reader.IsDoneReading()) { | |
| uint64_t param_id64; | |
| if (!reader.ReadVarInt62(¶m_id64)) { | |
| *error_details = "Failed to parse transport parameter ID"; | |
| return false; | |
| } | |
| TransportParameters::TransportParameterId param_id = | |
| static_cast<TransportParameters::TransportParameterId>(param_id64); | |
| absl::string_view value; | |
| if (!reader.ReadStringPieceVarInt62(&value)) { | |
| *error_details = | |
| "Failed to read length and value of transport parameter " + | |
| TransportParameterIdToString(param_id); | |
| return false; | |
| } | |
| QuicDataReader value_reader(value); | |
| bool parse_success = true; | |
| switch (param_id) { | |
| case TransportParameters::kOriginalDestinationConnectionId: { | |
| if (out->original_destination_connection_id.has_value()) { | |
| *error_details = | |
| "Received a second original_destination_connection_id"; | |
| return false; | |
| } | |
| const size_t connection_id_length = value_reader.BytesRemaining(); | |
| if (!QuicUtils::IsConnectionIdLengthValidForVersion( | |
| connection_id_length, version.transport_version)) { | |
| *error_details = absl::StrCat( | |
| "Received original_destination_connection_id of invalid length ", | |
| connection_id_length); | |
| return false; | |
| } | |
| QuicConnectionId original_destination_connection_id; | |
| if (!value_reader.ReadConnectionId(&original_destination_connection_id, | |
| connection_id_length)) { | |
| *error_details = "Failed to read original_destination_connection_id"; | |
| return false; | |
| } | |
| out->original_destination_connection_id = | |
| original_destination_connection_id; | |
| } break; | |
| case TransportParameters::kMaxIdleTimeout: | |
| parse_success = | |
| out->max_idle_timeout_ms.Read(&value_reader, error_details); | |
| break; | |
| case TransportParameters::kStatelessResetToken: { | |
| if (!out->stateless_reset_token.empty()) { | |
| *error_details = "Received a second stateless_reset_token"; | |
| return false; | |
| } | |
| absl::string_view stateless_reset_token = | |
| value_reader.ReadRemainingPayload(); | |
| if (stateless_reset_token.length() != kStatelessResetTokenLength) { | |
| *error_details = | |
| absl::StrCat("Received stateless_reset_token of invalid length ", | |
| stateless_reset_token.length()); | |
| return false; | |
| } | |
| out->stateless_reset_token.assign( | |
| stateless_reset_token.data(), | |
| stateless_reset_token.data() + stateless_reset_token.length()); | |
| } break; | |
| case TransportParameters::kMaxPacketSize: | |
| parse_success = | |
| out->max_udp_payload_size.Read(&value_reader, error_details); | |
| break; | |
| case TransportParameters::kInitialMaxData: | |
| parse_success = | |
| out->initial_max_data.Read(&value_reader, error_details); | |
| break; | |
| case TransportParameters::kInitialMaxStreamDataBidiLocal: | |
| parse_success = out->initial_max_stream_data_bidi_local.Read( | |
| &value_reader, error_details); | |
| break; | |
| case TransportParameters::kInitialMaxStreamDataBidiRemote: | |
| parse_success = out->initial_max_stream_data_bidi_remote.Read( | |
| &value_reader, error_details); | |
| break; | |
| case TransportParameters::kInitialMaxStreamDataUni: | |
| parse_success = | |
| out->initial_max_stream_data_uni.Read(&value_reader, error_details); | |
| break; | |
| case TransportParameters::kInitialMaxStreamsBidi: | |
| parse_success = | |
| out->initial_max_streams_bidi.Read(&value_reader, error_details); | |
| break; | |
| case TransportParameters::kInitialMaxStreamsUni: | |
| parse_success = | |
| out->initial_max_streams_uni.Read(&value_reader, error_details); | |
| break; | |
| case TransportParameters::kAckDelayExponent: | |
| parse_success = | |
| out->ack_delay_exponent.Read(&value_reader, error_details); | |
| break; | |
| case TransportParameters::kMaxAckDelay: | |
| parse_success = out->max_ack_delay.Read(&value_reader, error_details); | |
| break; | |
| case TransportParameters::kDisableActiveMigration: | |
| if (out->disable_active_migration) { | |
| *error_details = "Received a second disable_active_migration"; | |
| return false; | |
| } | |
| out->disable_active_migration = true; | |
| break; | |
| case TransportParameters::kPreferredAddress: { | |
| TransportParameters::PreferredAddress preferred_address; | |
| uint16_t ipv4_port, ipv6_port; | |
| in_addr ipv4_address; | |
| in6_addr ipv6_address; | |
| preferred_address.stateless_reset_token.resize( | |
| kStatelessResetTokenLength); | |
| if (!value_reader.ReadBytes(&ipv4_address, sizeof(ipv4_address)) || | |
| !value_reader.ReadUInt16(&ipv4_port) || | |
| !value_reader.ReadBytes(&ipv6_address, sizeof(ipv6_address)) || | |
| !value_reader.ReadUInt16(&ipv6_port) || | |
| !value_reader.ReadLengthPrefixedConnectionId( | |
| &preferred_address.connection_id) || | |
| !value_reader.ReadBytes(&preferred_address.stateless_reset_token[0], | |
| kStatelessResetTokenLength)) { | |
| *error_details = "Failed to read preferred_address"; | |
| return false; | |
| } | |
| preferred_address.ipv4_socket_address = | |
| QuicSocketAddress(QuicIpAddress(ipv4_address), ipv4_port); | |
| preferred_address.ipv6_socket_address = | |
| QuicSocketAddress(QuicIpAddress(ipv6_address), ipv6_port); | |
| if (!preferred_address.ipv4_socket_address.host().IsIPv4() || | |
| !preferred_address.ipv6_socket_address.host().IsIPv6()) { | |
| *error_details = "Received preferred_address of bad families " + | |
| preferred_address.ToString(); | |
| return false; | |
| } | |
| if (!QuicUtils::IsConnectionIdValidForVersion( | |
| preferred_address.connection_id, version.transport_version)) { | |
| *error_details = "Received invalid preferred_address connection ID " + | |
| preferred_address.ToString(); | |
| return false; | |
| } | |
| out->preferred_address = | |
| std::make_unique<TransportParameters::PreferredAddress>( | |
| preferred_address); | |
| } break; | |
| case TransportParameters::kActiveConnectionIdLimit: | |
| parse_success = | |
| out->active_connection_id_limit.Read(&value_reader, error_details); | |
| break; | |
| case TransportParameters::kInitialSourceConnectionId: { | |
| if (out->initial_source_connection_id.has_value()) { | |
| *error_details = "Received a second initial_source_connection_id"; | |
| return false; | |
| } | |
| const size_t connection_id_length = value_reader.BytesRemaining(); | |
| if (!QuicUtils::IsConnectionIdLengthValidForVersion( | |
| connection_id_length, version.transport_version)) { | |
| *error_details = absl::StrCat( | |
| "Received initial_source_connection_id of invalid length ", | |
| connection_id_length); | |
| return false; | |
| } | |
| QuicConnectionId initial_source_connection_id; | |
| if (!value_reader.ReadConnectionId(&initial_source_connection_id, | |
| connection_id_length)) { | |
| *error_details = "Failed to read initial_source_connection_id"; | |
| return false; | |
| } | |
| out->initial_source_connection_id = initial_source_connection_id; | |
| } break; | |
| case TransportParameters::kRetrySourceConnectionId: { | |
| if (out->retry_source_connection_id.has_value()) { | |
| *error_details = "Received a second retry_source_connection_id"; | |
| return false; | |
| } | |
| const size_t connection_id_length = value_reader.BytesRemaining(); | |
| if (!QuicUtils::IsConnectionIdLengthValidForVersion( | |
| connection_id_length, version.transport_version)) { | |
| *error_details = absl::StrCat( | |
| "Received retry_source_connection_id of invalid length ", | |
| connection_id_length); | |
| return false; | |
| } | |
| QuicConnectionId retry_source_connection_id; | |
| if (!value_reader.ReadConnectionId(&retry_source_connection_id, | |
| connection_id_length)) { | |
| *error_details = "Failed to read retry_source_connection_id"; | |
| return false; | |
| } | |
| out->retry_source_connection_id = retry_source_connection_id; | |
| } break; | |
| case TransportParameters::kMaxDatagramFrameSize: | |
| parse_success = | |
| out->max_datagram_frame_size.Read(&value_reader, error_details); | |
| break; | |
| case TransportParameters::kDiscard: | |
| out->discard_length = value_reader.ReadRemainingPayload().length(); | |
| break; | |
| case TransportParameters::kGoogleHandshakeMessage: | |
| if (out->google_handshake_message.has_value()) { | |
| *error_details = "Received a second google_handshake_message"; | |
| return false; | |
| } | |
| out->google_handshake_message = | |
| std::string(value_reader.ReadRemainingPayload()); | |
| break; | |
| case TransportParameters::kDebuggingSni: | |
| if (out->debugging_sni.has_value()) { | |
| *error_details = "Received a second debugging_sni"; | |
| return false; | |
| } | |
| // Deobfuscate the SNI and store it in the transport parameters. | |
| out->debugging_sni = ObfuscateSni(value_reader.ReadRemainingPayload()); | |
| break; | |
| case TransportParameters::kInitialRoundTripTime: | |
| parse_success = | |
| out->initial_round_trip_time_us.Read(&value_reader, error_details); | |
| break; | |
| case TransportParameters::kReliableStreamReset: | |
| if (out->reliable_stream_reset) { | |
| *error_details = "Received a second reliable_stream_reset"; | |
| return false; | |
| } | |
| out->reliable_stream_reset = true; | |
| break; | |
| case TransportParameters::kSconeSupported: | |
| if (out->scone_supported) { | |
| *error_details = "Received a second scone_supported"; | |
| return false; | |
| } | |
| out->scone_supported = true; | |
| break; | |
| case TransportParameters::kGoogleConnectionOptions: { | |
| if (out->google_connection_options.has_value()) { | |
| *error_details = "Received a second google_connection_options"; | |
| return false; | |
| } | |
| out->google_connection_options = QuicTagVector{}; | |
| while (!value_reader.IsDoneReading()) { | |
| QuicTag connection_option; | |
| if (!value_reader.ReadTag(&connection_option)) { | |
| *error_details = "Failed to read a google_connection_options"; | |
| return false; | |
| } | |
| out->google_connection_options->push_back(connection_option); | |
| } | |
| } break; | |
| case TransportParameters::kVersionInformation: { | |
| if (out->version_information.has_value()) { | |
| *error_details = "Received a second version_information"; | |
| return false; | |
| } | |
| out->version_information = TransportParameters::VersionInformation(); | |
| if (!value_reader.ReadUInt32( | |
| &out->version_information->chosen_version)) { | |
| *error_details = "Failed to read chosen version"; | |
| return false; | |
| } | |
| while (!value_reader.IsDoneReading()) { | |
| QuicVersionLabel other_version; | |
| if (!value_reader.ReadUInt32(&other_version)) { | |
| *error_details = "Failed to parse other version"; | |
| return false; | |
| } | |
| out->version_information->other_versions.push_back(other_version); | |
| } | |
| } break; | |
| case TransportParameters::kMinAckDelayDraft10: { | |
| if (out->min_ack_delay_us_draft10.has_value()) { | |
| *error_details = "Received a second min_ack_delay_us_draft10"; | |
| return false; | |
| } | |
| uint64_t delay_value; | |
| parse_success = ReadIntegerValue( | |
| &value_reader, TransportParameters::kMinAckDelayDraft10, | |
| delay_value, error_details); | |
| if (!parse_success) { | |
| return false; | |
| } | |
| out->min_ack_delay_us_draft10 = delay_value; | |
| } break; | |
| default: | |
| 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 (!parse_success) { | |
| QUICHE_DCHECK(!error_details->empty()); | |
| return false; | |
| } | |
| if (!value_reader.IsDoneReading()) { | |
| *error_details = absl::StrCat( | |
| "Received unexpected ", value_reader.BytesRemaining(), | |
| " bytes after parsing ", TransportParameterIdToString(param_id)); | |
| return false; | |
| } | |
| } | |
| if (!out->AreValid(error_details)) { | |
| QUICHE_DCHECK(!error_details->empty()); | |
| return false; | |
| } | |
| QUIC_DLOG(INFO) << "Parsed transport parameters " << *out << " from " | |
| << in_len << " bytes"; | |
| return true; | |
| } | |
| namespace { | |
| bool DigestUpdateIntegerParam( | |
| EVP_MD_CTX* hash_ctx, const TransportParameters::IntegerParameter& param) { | |
| uint64_t value = param.value(); | |
| return EVP_DigestUpdate(hash_ctx, &value, sizeof(value)); | |
| } | |
| } // namespace | |
| bool SerializeTransportParametersForTicket( | |
| const TransportParameters& in, const std::vector<uint8_t>& application_data, | |
| std::vector<uint8_t>* out) { | |
| std::string error_details; | |
| if (!in.AreValid(&error_details)) { | |
| QUIC_BUG(quic_bug_10743_26) | |
| << "Not serializing invalid transport parameters: " << error_details; | |
| return false; | |
| } | |
| out->resize(SHA256_DIGEST_LENGTH + 1); | |
| const uint8_t serialization_version = 0; | |
| (*out)[0] = serialization_version; | |
| bssl::ScopedEVP_MD_CTX hash_ctx; | |
| // Write application data: | |
| uint64_t app_data_len = application_data.size(); | |
| const uint64_t parameter_version = 0; | |
| // The format of the input to the hash function is as follows: | |
| // - The application data, prefixed with a 64-bit length field. | |
| // - Transport parameters: | |
| // - A 64-bit version field indicating which version of encoding is used | |
| // for transport parameters. | |
| // - A list of 64-bit integers representing the relevant parameters. | |
| // | |
| // When changing which parameters are included, additional parameters can be | |
| // added to the end of the list without changing the version field. New | |
| // parameters that are variable length must be length prefixed. If | |
| // parameters are removed from the list, the version field must be | |
| // incremented. | |
| // | |
| // Integers happen to be written in host byte order, not network byte order. | |
| if (!EVP_DigestInit(hash_ctx.get(), EVP_sha256()) || | |
| !EVP_DigestUpdate(hash_ctx.get(), &app_data_len, sizeof(app_data_len)) || | |
| !EVP_DigestUpdate(hash_ctx.get(), application_data.data(), | |
| application_data.size()) || | |
| !EVP_DigestUpdate(hash_ctx.get(), ¶meter_version, | |
| sizeof(parameter_version))) { | |
| QUIC_BUG(quic_bug_10743_27) | |
| << "Unexpected failure of EVP_Digest functions when hashing " | |
| "Transport Parameters for ticket"; | |
| return false; | |
| } | |
| // Write transport parameters specified by draft-ietf-quic-transport-28, | |
| // section 7.4.1, that are remembered for 0-RTT. | |
| if (!DigestUpdateIntegerParam(hash_ctx.get(), in.initial_max_data) || | |
| !DigestUpdateIntegerParam(hash_ctx.get(), | |
| in.initial_max_stream_data_bidi_local) || | |
| !DigestUpdateIntegerParam(hash_ctx.get(), | |
| in.initial_max_stream_data_bidi_remote) || | |
| !DigestUpdateIntegerParam(hash_ctx.get(), | |
| in.initial_max_stream_data_uni) || | |
| !DigestUpdateIntegerParam(hash_ctx.get(), in.initial_max_streams_bidi) || | |
| !DigestUpdateIntegerParam(hash_ctx.get(), in.initial_max_streams_uni) || | |
| !DigestUpdateIntegerParam(hash_ctx.get(), | |
| in.active_connection_id_limit)) { | |
| QUIC_BUG(quic_bug_10743_28) | |
| << "Unexpected failure of EVP_Digest functions when hashing " | |
| "Transport Parameters for ticket"; | |
| return false; | |
| } | |
| uint8_t disable_active_migration = in.disable_active_migration ? 1 : 0; | |
| uint8_t reliable_stream_reset = in.reliable_stream_reset ? 1 : 0; | |
| if (!EVP_DigestUpdate(hash_ctx.get(), &disable_active_migration, | |
| sizeof(disable_active_migration)) || | |
| (reliable_stream_reset && | |
| !EVP_DigestUpdate(hash_ctx.get(), "ResetStreamAt", 13)) || | |
| !EVP_DigestFinal(hash_ctx.get(), out->data() + 1, nullptr)) { | |
| QUIC_BUG(quic_bug_10743_29) | |
| << "Unexpected failure of EVP_Digest functions when hashing " | |
| "Transport Parameters for ticket"; | |
| return false; | |
| } | |
| return true; | |
| } | |
| void DegreaseTransportParameters(TransportParameters& parameters) { | |
| // Strip GREASE from custom parameters. | |
| for (auto it = parameters.custom_parameters.begin(); | |
| it != parameters.custom_parameters.end(); | |
| /**/) { | |
| // See the "Reserved Transport Parameters" section of RFC 9000. | |
| if (it->first % 31 == 27) { | |
| parameters.custom_parameters.erase(it++); | |
| } else { | |
| ++it; | |
| } | |
| } | |
| // Strip GREASE from versions. | |
| if (parameters.version_information.has_value()) { | |
| QuicVersionLabelVector clean_versions; | |
| for (QuicVersionLabel version : | |
| parameters.version_information->other_versions) { | |
| // See the "Versions" section of RFC 9000. | |
| if ((version & kReservedVersionMask) != kReservedVersionBits) { | |
| clean_versions.push_back(version); | |
| } | |
| } | |
| parameters.version_information->other_versions = std::move(clean_versions); | |
| } | |
| } | |
| } // namespace quic |