| // 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_protocol.h" | |
| #include <limits> | |
| #include <ostream> | |
| #include "absl/strings/str_cat.h" | |
| #include "spdy/platform/api/spdy_bug_tracker.h" | |
| #include "spdy/platform/api/spdy_string_utils.h" | |
| namespace spdy { | |
| const char* const kHttp2ConnectionHeaderPrefix = | |
| "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"; | |
| std::ostream& operator<<(std::ostream& out, SpdyKnownSettingsId id) { | |
| return out << static_cast<SpdySettingsId>(id); | |
| } | |
| std::ostream& operator<<(std::ostream& out, SpdyFrameType frame_type) { | |
| return out << SerializeFrameType(frame_type); | |
| } | |
| SpdyPriority ClampSpdy3Priority(SpdyPriority priority) { | |
| static_assert(std::numeric_limits<SpdyPriority>::min() == kV3HighestPriority, | |
| "The value of given priority shouldn't be smaller than highest " | |
| "priority. Check this invariant explicitly."); | |
| if (priority > kV3LowestPriority) { | |
| SPDY_BUG << "Invalid priority: " << static_cast<int>(priority); | |
| return kV3LowestPriority; | |
| } | |
| return priority; | |
| } | |
| int ClampHttp2Weight(int weight) { | |
| if (weight < kHttp2MinStreamWeight) { | |
| SPDY_BUG << "Invalid weight: " << weight; | |
| return kHttp2MinStreamWeight; | |
| } | |
| if (weight > kHttp2MaxStreamWeight) { | |
| SPDY_BUG << "Invalid weight: " << weight; | |
| return kHttp2MaxStreamWeight; | |
| } | |
| return weight; | |
| } | |
| int Spdy3PriorityToHttp2Weight(SpdyPriority priority) { | |
| priority = ClampSpdy3Priority(priority); | |
| const float kSteps = 255.9f / 7.f; | |
| return static_cast<int>(kSteps * (7.f - priority)) + 1; | |
| } | |
| SpdyPriority Http2WeightToSpdy3Priority(int weight) { | |
| weight = ClampHttp2Weight(weight); | |
| const float kSteps = 255.9f / 7.f; | |
| return static_cast<SpdyPriority>(7.f - (weight - 1) / kSteps); | |
| } | |
| bool IsDefinedFrameType(uint8_t frame_type_field) { | |
| switch (static_cast<SpdyFrameType>(frame_type_field)) { | |
| case SpdyFrameType::DATA: | |
| return true; | |
| case SpdyFrameType::HEADERS: | |
| return true; | |
| case SpdyFrameType::PRIORITY: | |
| return true; | |
| case SpdyFrameType::RST_STREAM: | |
| return true; | |
| case SpdyFrameType::SETTINGS: | |
| return true; | |
| case SpdyFrameType::PUSH_PROMISE: | |
| return true; | |
| case SpdyFrameType::PING: | |
| return true; | |
| case SpdyFrameType::GOAWAY: | |
| return true; | |
| case SpdyFrameType::WINDOW_UPDATE: | |
| return true; | |
| case SpdyFrameType::CONTINUATION: | |
| return true; | |
| case SpdyFrameType::ALTSVC: | |
| return true; | |
| case SpdyFrameType::PRIORITY_UPDATE: | |
| return true; | |
| } | |
| return false; | |
| } | |
| SpdyFrameType ParseFrameType(uint8_t frame_type_field) { | |
| SPDY_BUG_IF(!IsDefinedFrameType(frame_type_field)) | |
| << "Frame type not defined: " << static_cast<int>(frame_type_field); | |
| return static_cast<SpdyFrameType>(frame_type_field); | |
| } | |
| uint8_t SerializeFrameType(SpdyFrameType frame_type) { | |
| return static_cast<uint8_t>(frame_type); | |
| } | |
| bool IsValidHTTP2FrameStreamId(SpdyStreamId current_frame_stream_id, | |
| SpdyFrameType frame_type_field) { | |
| if (current_frame_stream_id == 0) { | |
| switch (frame_type_field) { | |
| case SpdyFrameType::DATA: | |
| case SpdyFrameType::HEADERS: | |
| case SpdyFrameType::PRIORITY: | |
| case SpdyFrameType::RST_STREAM: | |
| case SpdyFrameType::CONTINUATION: | |
| case SpdyFrameType::PUSH_PROMISE: | |
| // These frame types must specify a stream | |
| return false; | |
| default: | |
| return true; | |
| } | |
| } else { | |
| switch (frame_type_field) { | |
| case SpdyFrameType::GOAWAY: | |
| case SpdyFrameType::SETTINGS: | |
| case SpdyFrameType::PING: | |
| // These frame types must not specify a stream | |
| return false; | |
| default: | |
| return true; | |
| } | |
| } | |
| } | |
| const char* FrameTypeToString(SpdyFrameType frame_type) { | |
| switch (frame_type) { | |
| case SpdyFrameType::DATA: | |
| return "DATA"; | |
| case SpdyFrameType::RST_STREAM: | |
| return "RST_STREAM"; | |
| case SpdyFrameType::SETTINGS: | |
| return "SETTINGS"; | |
| case SpdyFrameType::PING: | |
| return "PING"; | |
| case SpdyFrameType::GOAWAY: | |
| return "GOAWAY"; | |
| case SpdyFrameType::HEADERS: | |
| return "HEADERS"; | |
| case SpdyFrameType::WINDOW_UPDATE: | |
| return "WINDOW_UPDATE"; | |
| case SpdyFrameType::PUSH_PROMISE: | |
| return "PUSH_PROMISE"; | |
| case SpdyFrameType::CONTINUATION: | |
| return "CONTINUATION"; | |
| case SpdyFrameType::PRIORITY: | |
| return "PRIORITY"; | |
| case SpdyFrameType::ALTSVC: | |
| return "ALTSVC"; | |
| case SpdyFrameType::PRIORITY_UPDATE: | |
| return "PRIORITY_UPDATE"; | |
| } | |
| return "UNKNOWN_FRAME_TYPE"; | |
| } | |
| bool ParseSettingsId(SpdySettingsId wire_setting_id, | |
| SpdyKnownSettingsId* setting_id) { | |
| if (wire_setting_id != SETTINGS_EXPERIMENT_SCHEDULER && | |
| (wire_setting_id < SETTINGS_MIN || wire_setting_id > SETTINGS_MAX)) { | |
| return false; | |
| } | |
| *setting_id = static_cast<SpdyKnownSettingsId>(wire_setting_id); | |
| // This switch ensures that the casted value is valid. The default case is | |
| // explicitly omitted to have compile-time guarantees that new additions to | |
| // |SpdyKnownSettingsId| must also be handled here. | |
| switch (*setting_id) { | |
| case SETTINGS_HEADER_TABLE_SIZE: | |
| case SETTINGS_ENABLE_PUSH: | |
| case SETTINGS_MAX_CONCURRENT_STREAMS: | |
| case SETTINGS_INITIAL_WINDOW_SIZE: | |
| case SETTINGS_MAX_FRAME_SIZE: | |
| case SETTINGS_MAX_HEADER_LIST_SIZE: | |
| case SETTINGS_ENABLE_CONNECT_PROTOCOL: | |
| case SETTINGS_DEPRECATE_HTTP2_PRIORITIES: | |
| case SETTINGS_EXPERIMENT_SCHEDULER: | |
| // FALLTHROUGH_INTENDED | |
| return true; | |
| } | |
| return false; | |
| } | |
| std::string SettingsIdToString(SpdySettingsId id) { | |
| SpdyKnownSettingsId known_id; | |
| if (!ParseSettingsId(id, &known_id)) { | |
| return absl::StrCat("SETTINGS_UNKNOWN_", | |
| SpdyHexEncodeUInt32AndTrim(uint32_t{id})); | |
| } | |
| switch (known_id) { | |
| case SETTINGS_HEADER_TABLE_SIZE: | |
| return "SETTINGS_HEADER_TABLE_SIZE"; | |
| case SETTINGS_ENABLE_PUSH: | |
| return "SETTINGS_ENABLE_PUSH"; | |
| case SETTINGS_MAX_CONCURRENT_STREAMS: | |
| return "SETTINGS_MAX_CONCURRENT_STREAMS"; | |
| case SETTINGS_INITIAL_WINDOW_SIZE: | |
| return "SETTINGS_INITIAL_WINDOW_SIZE"; | |
| case SETTINGS_MAX_FRAME_SIZE: | |
| return "SETTINGS_MAX_FRAME_SIZE"; | |
| case SETTINGS_MAX_HEADER_LIST_SIZE: | |
| return "SETTINGS_MAX_HEADER_LIST_SIZE"; | |
| case SETTINGS_ENABLE_CONNECT_PROTOCOL: | |
| return "SETTINGS_ENABLE_CONNECT_PROTOCOL"; | |
| case SETTINGS_DEPRECATE_HTTP2_PRIORITIES: | |
| return "SETTINGS_DEPRECATE_HTTP2_PRIORITIES"; | |
| case SETTINGS_EXPERIMENT_SCHEDULER: | |
| return "SETTINGS_EXPERIMENT_SCHEDULER"; | |
| } | |
| return absl::StrCat("SETTINGS_UNKNOWN_", | |
| SpdyHexEncodeUInt32AndTrim(uint32_t{id})); | |
| } | |
| SpdyErrorCode ParseErrorCode(uint32_t wire_error_code) { | |
| if (wire_error_code > ERROR_CODE_MAX) { | |
| return ERROR_CODE_INTERNAL_ERROR; | |
| } | |
| return static_cast<SpdyErrorCode>(wire_error_code); | |
| } | |
| const char* ErrorCodeToString(SpdyErrorCode error_code) { | |
| switch (error_code) { | |
| case ERROR_CODE_NO_ERROR: | |
| return "NO_ERROR"; | |
| case ERROR_CODE_PROTOCOL_ERROR: | |
| return "PROTOCOL_ERROR"; | |
| case ERROR_CODE_INTERNAL_ERROR: | |
| return "INTERNAL_ERROR"; | |
| case ERROR_CODE_FLOW_CONTROL_ERROR: | |
| return "FLOW_CONTROL_ERROR"; | |
| case ERROR_CODE_SETTINGS_TIMEOUT: | |
| return "SETTINGS_TIMEOUT"; | |
| case ERROR_CODE_STREAM_CLOSED: | |
| return "STREAM_CLOSED"; | |
| case ERROR_CODE_FRAME_SIZE_ERROR: | |
| return "FRAME_SIZE_ERROR"; | |
| case ERROR_CODE_REFUSED_STREAM: | |
| return "REFUSED_STREAM"; | |
| case ERROR_CODE_CANCEL: | |
| return "CANCEL"; | |
| case ERROR_CODE_COMPRESSION_ERROR: | |
| return "COMPRESSION_ERROR"; | |
| case ERROR_CODE_CONNECT_ERROR: | |
| return "CONNECT_ERROR"; | |
| case ERROR_CODE_ENHANCE_YOUR_CALM: | |
| return "ENHANCE_YOUR_CALM"; | |
| case ERROR_CODE_INADEQUATE_SECURITY: | |
| return "INADEQUATE_SECURITY"; | |
| case ERROR_CODE_HTTP_1_1_REQUIRED: | |
| return "HTTP_1_1_REQUIRED"; | |
| } | |
| return "UNKNOWN_ERROR_CODE"; | |
| } | |
| const char* WriteSchedulerTypeToString(WriteSchedulerType type) { | |
| switch (type) { | |
| case WriteSchedulerType::LIFO: | |
| return "LIFO"; | |
| case WriteSchedulerType::SPDY: | |
| return "SPDY"; | |
| case WriteSchedulerType::HTTP2: | |
| return "HTTP2"; | |
| case WriteSchedulerType::FIFO: | |
| return "FIFO"; | |
| } | |
| return "UNKNOWN"; | |
| } | |
| size_t GetNumberRequiredContinuationFrames(size_t size) { | |
| DCHECK_GT(size, kHttp2MaxControlFrameSendSize); | |
| size_t overflow = size - kHttp2MaxControlFrameSendSize; | |
| int payload_size = | |
| kHttp2MaxControlFrameSendSize - kContinuationFrameMinimumSize; | |
| // This is ceiling(overflow/payload_size) using integer arithmetics. | |
| return (overflow - 1) / payload_size + 1; | |
| } | |
| const char* const kHttp2Npn = "h2"; | |
| const char* const kHttp2AuthorityHeader = ":authority"; | |
| const char* const kHttp2MethodHeader = ":method"; | |
| const char* const kHttp2PathHeader = ":path"; | |
| const char* const kHttp2SchemeHeader = ":scheme"; | |
| const char* const kHttp2ProtocolHeader = ":protocol"; | |
| const char* const kHttp2StatusHeader = ":status"; | |
| bool SpdyFrameIR::fin() const { | |
| return false; | |
| } | |
| int SpdyFrameIR::flow_control_window_consumed() const { | |
| return 0; | |
| } | |
| bool SpdyFrameWithFinIR::fin() const { | |
| return fin_; | |
| } | |
| SpdyFrameWithHeaderBlockIR::SpdyFrameWithHeaderBlockIR( | |
| SpdyStreamId stream_id, | |
| Http2HeaderBlock header_block) | |
| : SpdyFrameWithFinIR(stream_id), header_block_(std::move(header_block)) {} | |
| SpdyFrameWithHeaderBlockIR::~SpdyFrameWithHeaderBlockIR() = default; | |
| SpdyDataIR::SpdyDataIR(SpdyStreamId stream_id, absl::string_view data) | |
| : SpdyFrameWithFinIR(stream_id), | |
| data_(nullptr), | |
| data_len_(0), | |
| padded_(false), | |
| padding_payload_len_(0) { | |
| SetDataDeep(data); | |
| } | |
| SpdyDataIR::SpdyDataIR(SpdyStreamId stream_id, const char* data) | |
| : SpdyDataIR(stream_id, absl::string_view(data)) {} | |
| SpdyDataIR::SpdyDataIR(SpdyStreamId stream_id, std::string data) | |
| : SpdyFrameWithFinIR(stream_id), | |
| data_store_(std::make_unique<std::string>(std::move(data))), | |
| data_(data_store_->data()), | |
| data_len_(data_store_->size()), | |
| padded_(false), | |
| padding_payload_len_(0) {} | |
| SpdyDataIR::SpdyDataIR(SpdyStreamId stream_id) | |
| : SpdyFrameWithFinIR(stream_id), | |
| data_(nullptr), | |
| data_len_(0), | |
| padded_(false), | |
| padding_payload_len_(0) {} | |
| SpdyDataIR::~SpdyDataIR() = default; | |
| void SpdyDataIR::Visit(SpdyFrameVisitor* visitor) const { | |
| return visitor->VisitData(*this); | |
| } | |
| SpdyFrameType SpdyDataIR::frame_type() const { | |
| return SpdyFrameType::DATA; | |
| } | |
| int SpdyDataIR::flow_control_window_consumed() const { | |
| return padded_ ? 1 + padding_payload_len_ + data_len_ : data_len_; | |
| } | |
| size_t SpdyDataIR::size() const { | |
| return kFrameHeaderSize + | |
| (padded() ? 1 + padding_payload_len() + data_len() : data_len()); | |
| } | |
| SpdyRstStreamIR::SpdyRstStreamIR(SpdyStreamId stream_id, | |
| SpdyErrorCode error_code) | |
| : SpdyFrameIR(stream_id) { | |
| set_error_code(error_code); | |
| } | |
| SpdyRstStreamIR::~SpdyRstStreamIR() = default; | |
| void SpdyRstStreamIR::Visit(SpdyFrameVisitor* visitor) const { | |
| return visitor->VisitRstStream(*this); | |
| } | |
| SpdyFrameType SpdyRstStreamIR::frame_type() const { | |
| return SpdyFrameType::RST_STREAM; | |
| } | |
| size_t SpdyRstStreamIR::size() const { | |
| return kRstStreamFrameSize; | |
| } | |
| SpdySettingsIR::SpdySettingsIR() : is_ack_(false) {} | |
| SpdySettingsIR::~SpdySettingsIR() = default; | |
| void SpdySettingsIR::Visit(SpdyFrameVisitor* visitor) const { | |
| return visitor->VisitSettings(*this); | |
| } | |
| SpdyFrameType SpdySettingsIR::frame_type() const { | |
| return SpdyFrameType::SETTINGS; | |
| } | |
| size_t SpdySettingsIR::size() const { | |
| return kFrameHeaderSize + values_.size() * kSettingsOneSettingSize; | |
| } | |
| void SpdyPingIR::Visit(SpdyFrameVisitor* visitor) const { | |
| return visitor->VisitPing(*this); | |
| } | |
| SpdyFrameType SpdyPingIR::frame_type() const { | |
| return SpdyFrameType::PING; | |
| } | |
| size_t SpdyPingIR::size() const { | |
| return kPingFrameSize; | |
| } | |
| SpdyGoAwayIR::SpdyGoAwayIR(SpdyStreamId last_good_stream_id, | |
| SpdyErrorCode error_code, | |
| absl::string_view description) | |
| : description_(description) { | |
| set_last_good_stream_id(last_good_stream_id); | |
| set_error_code(error_code); | |
| } | |
| SpdyGoAwayIR::SpdyGoAwayIR(SpdyStreamId last_good_stream_id, | |
| SpdyErrorCode error_code, | |
| const char* description) | |
| : SpdyGoAwayIR(last_good_stream_id, | |
| error_code, | |
| absl::string_view(description)) {} | |
| SpdyGoAwayIR::SpdyGoAwayIR(SpdyStreamId last_good_stream_id, | |
| SpdyErrorCode error_code, | |
| std::string description) | |
| : description_store_(std::move(description)), | |
| description_(description_store_) { | |
| set_last_good_stream_id(last_good_stream_id); | |
| set_error_code(error_code); | |
| } | |
| SpdyGoAwayIR::~SpdyGoAwayIR() = default; | |
| void SpdyGoAwayIR::Visit(SpdyFrameVisitor* visitor) const { | |
| return visitor->VisitGoAway(*this); | |
| } | |
| SpdyFrameType SpdyGoAwayIR::frame_type() const { | |
| return SpdyFrameType::GOAWAY; | |
| } | |
| size_t SpdyGoAwayIR::size() const { | |
| return kGoawayFrameMinimumSize + description_.size(); | |
| } | |
| SpdyContinuationIR::SpdyContinuationIR(SpdyStreamId stream_id) | |
| : SpdyFrameIR(stream_id), end_headers_(false) { | |
| encoding_ = std::make_unique<std::string>(); | |
| } | |
| SpdyContinuationIR::~SpdyContinuationIR() = default; | |
| void SpdyContinuationIR::Visit(SpdyFrameVisitor* visitor) const { | |
| return visitor->VisitContinuation(*this); | |
| } | |
| SpdyFrameType SpdyContinuationIR::frame_type() const { | |
| return SpdyFrameType::CONTINUATION; | |
| } | |
| size_t SpdyContinuationIR::size() const { | |
| // We don't need to get the size of CONTINUATION frame directly. It is | |
| // calculated in HEADERS or PUSH_PROMISE frame. | |
| SPDY_DLOG(WARNING) << "Shouldn't not call size() for CONTINUATION frame."; | |
| return 0; | |
| } | |
| void SpdyHeadersIR::Visit(SpdyFrameVisitor* visitor) const { | |
| return visitor->VisitHeaders(*this); | |
| } | |
| SpdyFrameType SpdyHeadersIR::frame_type() const { | |
| return SpdyFrameType::HEADERS; | |
| } | |
| size_t SpdyHeadersIR::size() const { | |
| size_t size = kHeadersFrameMinimumSize; | |
| if (padded_) { | |
| // Padding field length. | |
| size += 1; | |
| size += padding_payload_len_; | |
| } | |
| if (has_priority_) { | |
| size += 5; | |
| } | |
| // Assume no hpack encoding is applied. | |
| size += header_block().TotalBytesUsed() + | |
| header_block().size() * kPerHeaderHpackOverhead; | |
| if (size > kHttp2MaxControlFrameSendSize) { | |
| size += GetNumberRequiredContinuationFrames(size) * | |
| kContinuationFrameMinimumSize; | |
| } | |
| return size; | |
| } | |
| void SpdyWindowUpdateIR::Visit(SpdyFrameVisitor* visitor) const { | |
| return visitor->VisitWindowUpdate(*this); | |
| } | |
| SpdyFrameType SpdyWindowUpdateIR::frame_type() const { | |
| return SpdyFrameType::WINDOW_UPDATE; | |
| } | |
| size_t SpdyWindowUpdateIR::size() const { | |
| return kWindowUpdateFrameSize; | |
| } | |
| void SpdyPushPromiseIR::Visit(SpdyFrameVisitor* visitor) const { | |
| return visitor->VisitPushPromise(*this); | |
| } | |
| SpdyFrameType SpdyPushPromiseIR::frame_type() const { | |
| return SpdyFrameType::PUSH_PROMISE; | |
| } | |
| size_t SpdyPushPromiseIR::size() const { | |
| size_t size = kPushPromiseFrameMinimumSize; | |
| if (padded_) { | |
| // Padding length field. | |
| size += 1; | |
| size += padding_payload_len_; | |
| } | |
| size += header_block().TotalBytesUsed(); | |
| if (size > kHttp2MaxControlFrameSendSize) { | |
| size += GetNumberRequiredContinuationFrames(size) * | |
| kContinuationFrameMinimumSize; | |
| } | |
| return size; | |
| } | |
| SpdyAltSvcIR::SpdyAltSvcIR(SpdyStreamId stream_id) : SpdyFrameIR(stream_id) {} | |
| SpdyAltSvcIR::~SpdyAltSvcIR() = default; | |
| void SpdyAltSvcIR::Visit(SpdyFrameVisitor* visitor) const { | |
| return visitor->VisitAltSvc(*this); | |
| } | |
| SpdyFrameType SpdyAltSvcIR::frame_type() const { | |
| return SpdyFrameType::ALTSVC; | |
| } | |
| size_t SpdyAltSvcIR::size() const { | |
| size_t size = kGetAltSvcFrameMinimumSize; | |
| size += origin_.length(); | |
| // TODO(yasong): estimates the size without serializing the vector. | |
| std::string str = | |
| SpdyAltSvcWireFormat::SerializeHeaderFieldValue(altsvc_vector_); | |
| size += str.size(); | |
| return size; | |
| } | |
| void SpdyPriorityIR::Visit(SpdyFrameVisitor* visitor) const { | |
| return visitor->VisitPriority(*this); | |
| } | |
| SpdyFrameType SpdyPriorityIR::frame_type() const { | |
| return SpdyFrameType::PRIORITY; | |
| } | |
| size_t SpdyPriorityIR::size() const { | |
| return kPriorityFrameSize; | |
| } | |
| void SpdyPriorityUpdateIR::Visit(SpdyFrameVisitor* visitor) const { | |
| return visitor->VisitPriorityUpdate(*this); | |
| } | |
| SpdyFrameType SpdyPriorityUpdateIR::frame_type() const { | |
| return SpdyFrameType::PRIORITY_UPDATE; | |
| } | |
| size_t SpdyPriorityUpdateIR::size() const { | |
| return kPriorityUpdateFrameMinimumSize + priority_field_value_.size(); | |
| } | |
| void SpdyUnknownIR::Visit(SpdyFrameVisitor* visitor) const { | |
| return visitor->VisitUnknown(*this); | |
| } | |
| SpdyFrameType SpdyUnknownIR::frame_type() const { | |
| return static_cast<SpdyFrameType>(type()); | |
| } | |
| size_t SpdyUnknownIR::size() const { | |
| return kFrameHeaderSize + payload_.size(); | |
| } | |
| int SpdyUnknownIR::flow_control_window_consumed() const { | |
| if (frame_type() == SpdyFrameType::DATA) { | |
| return payload_.size(); | |
| } else { | |
| return 0; | |
| } | |
| } | |
| // Wire size of pad length field. | |
| const size_t kPadLengthFieldSize = 1; | |
| size_t GetHeaderFrameSizeSansBlock(const SpdyHeadersIR& header_ir) { | |
| size_t min_size = kFrameHeaderSize; | |
| if (header_ir.padded()) { | |
| min_size += kPadLengthFieldSize; | |
| min_size += header_ir.padding_payload_len(); | |
| } | |
| if (header_ir.has_priority()) { | |
| min_size += 5; | |
| } | |
| return min_size; | |
| } | |
| size_t GetPushPromiseFrameSizeSansBlock( | |
| const SpdyPushPromiseIR& push_promise_ir) { | |
| size_t min_size = kPushPromiseFrameMinimumSize; | |
| if (push_promise_ir.padded()) { | |
| min_size += kPadLengthFieldSize; | |
| min_size += push_promise_ir.padding_payload_len(); | |
| } | |
| return min_size; | |
| } | |
| } // namespace spdy |