| // 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_framer.h" |
| |
| #include <algorithm> |
| #include <cstdint> |
| #include <iterator> |
| #include <list> |
| #include <new> |
| #include <utility> |
| |
| #include "absl/base/macros.h" |
| #include "absl/memory/memory.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 { |
| |
| namespace { |
| |
| // Pack parent stream ID and exclusive flag into the format used by HTTP/2 |
| // headers and priority frames. |
| uint32_t PackStreamDependencyValues(bool exclusive, |
| SpdyStreamId parent_stream_id) { |
| // Make sure the highest-order bit in the parent stream id is zeroed out. |
| uint32_t parent = parent_stream_id & 0x7fffffff; |
| // Set the one-bit exclusivity flag. |
| uint32_t e_bit = exclusive ? 0x80000000 : 0; |
| return parent | e_bit; |
| } |
| |
| // Used to indicate no flags in a HTTP2 flags field. |
| const uint8_t kNoFlags = 0; |
| |
| // Wire size of pad length field. |
| const size_t kPadLengthFieldSize = 1; |
| |
| // The size of one parameter in SETTINGS frame. |
| const size_t kOneSettingParameterSize = 6; |
| |
| size_t GetUncompressedSerializedLength(const Http2HeaderBlock& headers) { |
| const size_t num_name_value_pairs_size = sizeof(uint32_t); |
| const size_t length_of_name_size = num_name_value_pairs_size; |
| const size_t length_of_value_size = num_name_value_pairs_size; |
| |
| size_t total_length = num_name_value_pairs_size; |
| for (const auto& header : headers) { |
| // We add space for the length of the name and the length of the value as |
| // well as the length of the name and the length of the value. |
| total_length += length_of_name_size + header.first.size() + |
| length_of_value_size + header.second.size(); |
| } |
| return total_length; |
| } |
| |
| // Serializes the flags octet for a given SpdyHeadersIR. |
| uint8_t SerializeHeaderFrameFlags(const SpdyHeadersIR& header_ir, |
| const bool end_headers) { |
| uint8_t flags = 0; |
| if (header_ir.fin()) { |
| flags |= CONTROL_FLAG_FIN; |
| } |
| if (end_headers) { |
| flags |= HEADERS_FLAG_END_HEADERS; |
| } |
| if (header_ir.padded()) { |
| flags |= HEADERS_FLAG_PADDED; |
| } |
| if (header_ir.has_priority()) { |
| flags |= HEADERS_FLAG_PRIORITY; |
| } |
| return flags; |
| } |
| |
| // Serializes the flags octet for a given SpdyPushPromiseIR. |
| uint8_t SerializePushPromiseFrameFlags(const SpdyPushPromiseIR& push_promise_ir, |
| const bool end_headers) { |
| uint8_t flags = 0; |
| if (push_promise_ir.padded()) { |
| flags = flags | PUSH_PROMISE_FLAG_PADDED; |
| } |
| if (end_headers) { |
| flags |= PUSH_PROMISE_FLAG_END_PUSH_PROMISE; |
| } |
| return flags; |
| } |
| |
| // Serializes a HEADERS frame from the given SpdyHeadersIR and encoded header |
| // block. Does not need or use the SpdyHeaderBlock inside SpdyHeadersIR. |
| // Return false if the serialization fails. |encoding| should not be empty. |
| bool SerializeHeadersGivenEncoding(const SpdyHeadersIR& headers, |
| const std::string& encoding, |
| const bool end_headers, |
| ZeroCopyOutputBuffer* output) { |
| const size_t frame_size = |
| GetHeaderFrameSizeSansBlock(headers) + encoding.size(); |
| SpdyFrameBuilder builder(frame_size, output); |
| bool ret = builder.BeginNewFrame( |
| SpdyFrameType::HEADERS, SerializeHeaderFrameFlags(headers, end_headers), |
| headers.stream_id(), frame_size - kFrameHeaderSize); |
| QUICHE_DCHECK_EQ(kFrameHeaderSize, builder.length()); |
| |
| if (ret && headers.padded()) { |
| ret &= builder.WriteUInt8(headers.padding_payload_len()); |
| } |
| |
| if (ret && headers.has_priority()) { |
| int weight = ClampHttp2Weight(headers.weight()); |
| ret &= builder.WriteUInt32(PackStreamDependencyValues( |
| headers.exclusive(), headers.parent_stream_id())); |
| // Per RFC 7540 section 6.3, serialized weight value is actual value - 1. |
| ret &= builder.WriteUInt8(weight - 1); |
| } |
| |
| if (ret) { |
| ret &= builder.WriteBytes(encoding.data(), encoding.size()); |
| } |
| |
| if (ret && headers.padding_payload_len() > 0) { |
| std::string padding(headers.padding_payload_len(), 0); |
| ret &= builder.WriteBytes(padding.data(), padding.length()); |
| } |
| |
| if (!ret) { |
| QUICHE_DLOG(WARNING) |
| << "Failed to build HEADERS. Not enough space in output"; |
| } |
| return ret; |
| } |
| |
| // Serializes a PUSH_PROMISE frame from the given SpdyPushPromiseIR and |
| // encoded header block. Does not need or use the SpdyHeaderBlock inside |
| // SpdyPushPromiseIR. |
| bool SerializePushPromiseGivenEncoding(const SpdyPushPromiseIR& push_promise, |
| const std::string& encoding, |
| const bool end_headers, |
| ZeroCopyOutputBuffer* output) { |
| const size_t frame_size = |
| GetPushPromiseFrameSizeSansBlock(push_promise) + encoding.size(); |
| SpdyFrameBuilder builder(frame_size, output); |
| bool ok = builder.BeginNewFrame( |
| SpdyFrameType::PUSH_PROMISE, |
| SerializePushPromiseFrameFlags(push_promise, end_headers), |
| push_promise.stream_id(), frame_size - kFrameHeaderSize); |
| |
| if (push_promise.padded()) { |
| ok = ok && builder.WriteUInt8(push_promise.padding_payload_len()); |
| } |
| ok = ok && builder.WriteUInt32(push_promise.promised_stream_id()) && |
| builder.WriteBytes(encoding.data(), encoding.size()); |
| if (ok && push_promise.padding_payload_len() > 0) { |
| std::string padding(push_promise.padding_payload_len(), 0); |
| ok = builder.WriteBytes(padding.data(), padding.length()); |
| } |
| |
| QUICHE_DLOG_IF(ERROR, !ok) |
| << "Failed to write PUSH_PROMISE encoding, not enough " |
| << "space in output"; |
| return ok; |
| } |
| |
| bool WritePayloadWithContinuation(SpdyFrameBuilder* builder, |
| const std::string& hpack_encoding, |
| SpdyStreamId stream_id, |
| SpdyFrameType type, |
| int padding_payload_len) { |
| uint8_t end_flag = 0; |
| uint8_t flags = 0; |
| if (type == SpdyFrameType::HEADERS) { |
| end_flag = HEADERS_FLAG_END_HEADERS; |
| } else if (type == SpdyFrameType::PUSH_PROMISE) { |
| end_flag = PUSH_PROMISE_FLAG_END_PUSH_PROMISE; |
| } else { |
| QUICHE_DLOG(FATAL) << "CONTINUATION frames cannot be used with frame type " |
| << FrameTypeToString(type); |
| } |
| |
| // Write all the padding payload and as much of the data payload as possible |
| // into the initial frame. |
| size_t bytes_remaining = 0; |
| bytes_remaining = hpack_encoding.size() - |
| std::min(hpack_encoding.size(), |
| kHttp2MaxControlFrameSendSize - builder->length() - |
| padding_payload_len); |
| bool ret = builder->WriteBytes(&hpack_encoding[0], |
| hpack_encoding.size() - bytes_remaining); |
| if (padding_payload_len > 0) { |
| std::string padding = std::string(padding_payload_len, 0); |
| ret &= builder->WriteBytes(padding.data(), padding.length()); |
| } |
| |
| // Tack on CONTINUATION frames for the overflow. |
| while (bytes_remaining > 0 && ret) { |
| size_t bytes_to_write = |
| std::min(bytes_remaining, |
| kHttp2MaxControlFrameSendSize - kContinuationFrameMinimumSize); |
| // Write CONTINUATION frame prefix. |
| if (bytes_remaining == bytes_to_write) { |
| flags |= end_flag; |
| } |
| ret &= builder->BeginNewFrame(SpdyFrameType::CONTINUATION, flags, stream_id, |
| bytes_to_write); |
| // Write payload fragment. |
| ret &= builder->WriteBytes( |
| &hpack_encoding[hpack_encoding.size() - bytes_remaining], |
| bytes_to_write); |
| bytes_remaining -= bytes_to_write; |
| } |
| return ret; |
| } |
| |
| void SerializeDataBuilderHelper(const SpdyDataIR& data_ir, |
| uint8_t* flags, |
| int* num_padding_fields, |
| size_t* size_with_padding) { |
| if (data_ir.fin()) { |
| *flags = DATA_FLAG_FIN; |
| } |
| |
| if (data_ir.padded()) { |
| *flags = *flags | DATA_FLAG_PADDED; |
| ++*num_padding_fields; |
| } |
| |
| *size_with_padding = *num_padding_fields + data_ir.data_len() + |
| data_ir.padding_payload_len() + kDataFrameMinimumSize; |
| } |
| |
| void SerializeDataFrameHeaderWithPaddingLengthFieldBuilderHelper( |
| const SpdyDataIR& data_ir, |
| uint8_t* flags, |
| size_t* frame_size, |
| size_t* num_padding_fields) { |
| *flags = DATA_FLAG_NONE; |
| if (data_ir.fin()) { |
| *flags = DATA_FLAG_FIN; |
| } |
| |
| *frame_size = kDataFrameMinimumSize; |
| if (data_ir.padded()) { |
| *flags = *flags | DATA_FLAG_PADDED; |
| ++(*num_padding_fields); |
| *frame_size = *frame_size + *num_padding_fields; |
| } |
| } |
| |
| void SerializeSettingsBuilderHelper(const SpdySettingsIR& settings, |
| uint8_t* flags, |
| const SettingsMap* values, |
| size_t* size) { |
| if (settings.is_ack()) { |
| *flags = *flags | SETTINGS_FLAG_ACK; |
| } |
| *size = |
| kSettingsFrameMinimumSize + (values->size() * kOneSettingParameterSize); |
| } |
| |
| void SerializeAltSvcBuilderHelper(const SpdyAltSvcIR& altsvc_ir, |
| std::string* value, |
| size_t* size) { |
| *size = kGetAltSvcFrameMinimumSize; |
| *size = *size + altsvc_ir.origin().length(); |
| *value = SpdyAltSvcWireFormat::SerializeHeaderFieldValue( |
| altsvc_ir.altsvc_vector()); |
| *size = *size + value->length(); |
| } |
| |
| } // namespace |
| |
| SpdyFramer::SpdyFramer(CompressionOption option) |
| : debug_visitor_(nullptr), compression_option_(option) { |
| static_assert(kHttp2MaxControlFrameSendSize <= kHttp2DefaultFrameSizeLimit, |
| "Our send limit should be at most our receive limit."); |
| } |
| |
| SpdyFramer::~SpdyFramer() = default; |
| |
| void SpdyFramer::set_debug_visitor( |
| SpdyFramerDebugVisitorInterface* debug_visitor) { |
| debug_visitor_ = debug_visitor; |
| } |
| |
| SpdyFramer::SpdyFrameIterator::SpdyFrameIterator(SpdyFramer* framer) |
| : framer_(framer), is_first_frame_(true), has_next_frame_(true) {} |
| |
| SpdyFramer::SpdyFrameIterator::~SpdyFrameIterator() = default; |
| |
| size_t SpdyFramer::SpdyFrameIterator::NextFrame(ZeroCopyOutputBuffer* output) { |
| const SpdyFrameIR& frame_ir = GetIR(); |
| if (!has_next_frame_) { |
| QUICHE_BUG(spdy_bug_75_1) |
| << "SpdyFramer::SpdyFrameIterator::NextFrame called without " |
| << "a next frame."; |
| return false; |
| } |
| |
| const size_t size_without_block = |
| is_first_frame_ ? GetFrameSizeSansBlock() : kContinuationFrameMinimumSize; |
| std::string encoding = |
| encoder_->Next(kHttp2MaxControlFrameSendSize - size_without_block); |
| has_next_frame_ = encoder_->HasNext(); |
| |
| if (framer_->debug_visitor_ != nullptr) { |
| const auto& header_block_frame_ir = |
| static_cast<const SpdyFrameWithHeaderBlockIR&>(frame_ir); |
| const size_t header_list_size = |
| GetUncompressedSerializedLength(header_block_frame_ir.header_block()); |
| framer_->debug_visitor_->OnSendCompressedFrame( |
| frame_ir.stream_id(), |
| is_first_frame_ ? frame_ir.frame_type() : SpdyFrameType::CONTINUATION, |
| header_list_size, size_without_block + encoding.size()); |
| } |
| |
| const size_t free_bytes_before = output->BytesFree(); |
| bool ok = false; |
| if (is_first_frame_) { |
| is_first_frame_ = false; |
| ok = SerializeGivenEncoding(encoding, output); |
| } else { |
| SpdyContinuationIR continuation_ir(frame_ir.stream_id()); |
| continuation_ir.take_encoding(std::move(encoding)); |
| continuation_ir.set_end_headers(!has_next_frame_); |
| ok = framer_->SerializeContinuation(continuation_ir, output); |
| } |
| return ok ? free_bytes_before - output->BytesFree() : 0; |
| } |
| |
| bool SpdyFramer::SpdyFrameIterator::HasNextFrame() const { |
| return has_next_frame_; |
| } |
| |
| SpdyFramer::SpdyHeaderFrameIterator::SpdyHeaderFrameIterator( |
| SpdyFramer* framer, |
| std::unique_ptr<const SpdyHeadersIR> headers_ir) |
| : SpdyFrameIterator(framer), headers_ir_(std::move(headers_ir)) { |
| SetEncoder(headers_ir_.get()); |
| } |
| |
| SpdyFramer::SpdyHeaderFrameIterator::~SpdyHeaderFrameIterator() = default; |
| |
| const SpdyFrameIR& SpdyFramer::SpdyHeaderFrameIterator::GetIR() const { |
| return *headers_ir_; |
| } |
| |
| size_t SpdyFramer::SpdyHeaderFrameIterator::GetFrameSizeSansBlock() const { |
| return GetHeaderFrameSizeSansBlock(*headers_ir_); |
| } |
| |
| bool SpdyFramer::SpdyHeaderFrameIterator::SerializeGivenEncoding( |
| const std::string& encoding, |
| ZeroCopyOutputBuffer* output) const { |
| return SerializeHeadersGivenEncoding(*headers_ir_, encoding, |
| !has_next_frame(), output); |
| } |
| |
| SpdyFramer::SpdyPushPromiseFrameIterator::SpdyPushPromiseFrameIterator( |
| SpdyFramer* framer, |
| std::unique_ptr<const SpdyPushPromiseIR> push_promise_ir) |
| : SpdyFrameIterator(framer), push_promise_ir_(std::move(push_promise_ir)) { |
| SetEncoder(push_promise_ir_.get()); |
| } |
| |
| SpdyFramer::SpdyPushPromiseFrameIterator::~SpdyPushPromiseFrameIterator() = |
| default; |
| |
| const SpdyFrameIR& SpdyFramer::SpdyPushPromiseFrameIterator::GetIR() const { |
| return *push_promise_ir_; |
| } |
| |
| size_t SpdyFramer::SpdyPushPromiseFrameIterator::GetFrameSizeSansBlock() const { |
| return GetPushPromiseFrameSizeSansBlock(*push_promise_ir_); |
| } |
| |
| bool SpdyFramer::SpdyPushPromiseFrameIterator::SerializeGivenEncoding( |
| const std::string& encoding, |
| ZeroCopyOutputBuffer* output) const { |
| return SerializePushPromiseGivenEncoding(*push_promise_ir_, encoding, |
| !has_next_frame(), output); |
| } |
| |
| SpdyFramer::SpdyControlFrameIterator::SpdyControlFrameIterator( |
| SpdyFramer* framer, |
| std::unique_ptr<const SpdyFrameIR> frame_ir) |
| : framer_(framer), frame_ir_(std::move(frame_ir)) {} |
| |
| SpdyFramer::SpdyControlFrameIterator::~SpdyControlFrameIterator() = default; |
| |
| size_t SpdyFramer::SpdyControlFrameIterator::NextFrame( |
| ZeroCopyOutputBuffer* output) { |
| size_t size_written = framer_->SerializeFrame(*frame_ir_, output); |
| has_next_frame_ = false; |
| return size_written; |
| } |
| |
| bool SpdyFramer::SpdyControlFrameIterator::HasNextFrame() const { |
| return has_next_frame_; |
| } |
| |
| const SpdyFrameIR& SpdyFramer::SpdyControlFrameIterator::GetIR() const { |
| return *frame_ir_; |
| } |
| |
| std::unique_ptr<SpdyFrameSequence> SpdyFramer::CreateIterator( |
| SpdyFramer* framer, |
| std::unique_ptr<const SpdyFrameIR> frame_ir) { |
| switch (frame_ir->frame_type()) { |
| case SpdyFrameType::HEADERS: { |
| return std::make_unique<SpdyHeaderFrameIterator>( |
| framer, absl::WrapUnique( |
| static_cast<const SpdyHeadersIR*>(frame_ir.release()))); |
| } |
| case SpdyFrameType::PUSH_PROMISE: { |
| return std::make_unique<SpdyPushPromiseFrameIterator>( |
| framer, absl::WrapUnique(static_cast<const SpdyPushPromiseIR*>( |
| frame_ir.release()))); |
| } |
| case SpdyFrameType::DATA: { |
| QUICHE_DVLOG(1) << "Serialize a stream end DATA frame for VTL"; |
| ABSL_FALLTHROUGH_INTENDED; |
| } |
| default: { |
| return std::make_unique<SpdyControlFrameIterator>(framer, |
| std::move(frame_ir)); |
| } |
| } |
| } |
| |
| SpdySerializedFrame SpdyFramer::SerializeData(const SpdyDataIR& data_ir) { |
| uint8_t flags = DATA_FLAG_NONE; |
| int num_padding_fields = 0; |
| size_t size_with_padding = 0; |
| SerializeDataBuilderHelper(data_ir, &flags, &num_padding_fields, |
| &size_with_padding); |
| |
| SpdyFrameBuilder builder(size_with_padding); |
| builder.BeginNewFrame(SpdyFrameType::DATA, flags, data_ir.stream_id()); |
| if (data_ir.padded()) { |
| builder.WriteUInt8(data_ir.padding_payload_len() & 0xff); |
| } |
| builder.WriteBytes(data_ir.data(), data_ir.data_len()); |
| if (data_ir.padding_payload_len() > 0) { |
| std::string padding(data_ir.padding_payload_len(), 0); |
| builder.WriteBytes(padding.data(), padding.length()); |
| } |
| QUICHE_DCHECK_EQ(size_with_padding, builder.length()); |
| return builder.take(); |
| } |
| |
| SpdySerializedFrame SpdyFramer::SerializeDataFrameHeaderWithPaddingLengthField( |
| const SpdyDataIR& data_ir) { |
| uint8_t flags = DATA_FLAG_NONE; |
| size_t frame_size = 0; |
| size_t num_padding_fields = 0; |
| SerializeDataFrameHeaderWithPaddingLengthFieldBuilderHelper( |
| data_ir, &flags, &frame_size, &num_padding_fields); |
| |
| SpdyFrameBuilder builder(frame_size); |
| builder.BeginNewFrame( |
| SpdyFrameType::DATA, flags, data_ir.stream_id(), |
| num_padding_fields + data_ir.data_len() + data_ir.padding_payload_len()); |
| if (data_ir.padded()) { |
| builder.WriteUInt8(data_ir.padding_payload_len() & 0xff); |
| } |
| QUICHE_DCHECK_EQ(frame_size, builder.length()); |
| return builder.take(); |
| } |
| |
| SpdySerializedFrame SpdyFramer::SerializeRstStream( |
| const SpdyRstStreamIR& rst_stream) const { |
| size_t expected_length = kRstStreamFrameSize; |
| SpdyFrameBuilder builder(expected_length); |
| |
| builder.BeginNewFrame(SpdyFrameType::RST_STREAM, 0, rst_stream.stream_id()); |
| |
| builder.WriteUInt32(rst_stream.error_code()); |
| |
| QUICHE_DCHECK_EQ(expected_length, builder.length()); |
| return builder.take(); |
| } |
| |
| SpdySerializedFrame SpdyFramer::SerializeSettings( |
| const SpdySettingsIR& settings) const { |
| uint8_t flags = 0; |
| // Size, in bytes, of this SETTINGS frame. |
| size_t size = 0; |
| const SettingsMap* values = &(settings.values()); |
| SerializeSettingsBuilderHelper(settings, &flags, values, &size); |
| SpdyFrameBuilder builder(size); |
| builder.BeginNewFrame(SpdyFrameType::SETTINGS, flags, 0); |
| |
| // If this is an ACK, payload should be empty. |
| if (settings.is_ack()) { |
| return builder.take(); |
| } |
| |
| QUICHE_DCHECK_EQ(kSettingsFrameMinimumSize, builder.length()); |
| for (auto it = values->begin(); it != values->end(); ++it) { |
| int setting_id = it->first; |
| QUICHE_DCHECK_GE(setting_id, 0); |
| builder.WriteUInt16(static_cast<SpdySettingsId>(setting_id)); |
| builder.WriteUInt32(it->second); |
| } |
| QUICHE_DCHECK_EQ(size, builder.length()); |
| return builder.take(); |
| } |
| |
| SpdySerializedFrame SpdyFramer::SerializePing(const SpdyPingIR& ping) const { |
| SpdyFrameBuilder builder(kPingFrameSize); |
| uint8_t flags = 0; |
| if (ping.is_ack()) { |
| flags |= PING_FLAG_ACK; |
| } |
| builder.BeginNewFrame(SpdyFrameType::PING, flags, 0); |
| builder.WriteUInt64(ping.id()); |
| QUICHE_DCHECK_EQ(kPingFrameSize, builder.length()); |
| return builder.take(); |
| } |
| |
| SpdySerializedFrame SpdyFramer::SerializeGoAway( |
| const SpdyGoAwayIR& goaway) const { |
| // Compute the output buffer size, take opaque data into account. |
| size_t expected_length = kGoawayFrameMinimumSize; |
| expected_length += goaway.description().size(); |
| SpdyFrameBuilder builder(expected_length); |
| |
| // Serialize the GOAWAY frame. |
| builder.BeginNewFrame(SpdyFrameType::GOAWAY, 0, 0); |
| |
| // GOAWAY frames specify the last good stream id. |
| builder.WriteUInt32(goaway.last_good_stream_id()); |
| |
| // GOAWAY frames also specify the error code. |
| builder.WriteUInt32(goaway.error_code()); |
| |
| // GOAWAY frames may also specify opaque data. |
| if (!goaway.description().empty()) { |
| builder.WriteBytes(goaway.description().data(), |
| goaway.description().size()); |
| } |
| |
| QUICHE_DCHECK_EQ(expected_length, builder.length()); |
| return builder.take(); |
| } |
| |
| void SpdyFramer::SerializeHeadersBuilderHelper(const SpdyHeadersIR& headers, |
| uint8_t* flags, |
| size_t* size, |
| std::string* hpack_encoding, |
| int* weight, |
| size_t* length_field) { |
| if (headers.fin()) { |
| *flags = *flags | CONTROL_FLAG_FIN; |
| } |
| // This will get overwritten if we overflow into a CONTINUATION frame. |
| *flags = *flags | HEADERS_FLAG_END_HEADERS; |
| if (headers.has_priority()) { |
| *flags = *flags | HEADERS_FLAG_PRIORITY; |
| } |
| if (headers.padded()) { |
| *flags = *flags | HEADERS_FLAG_PADDED; |
| } |
| |
| *size = kHeadersFrameMinimumSize; |
| |
| if (headers.padded()) { |
| *size = *size + kPadLengthFieldSize; |
| *size = *size + headers.padding_payload_len(); |
| } |
| |
| if (headers.has_priority()) { |
| *weight = ClampHttp2Weight(headers.weight()); |
| *size = *size + 5; |
| } |
| |
| *hpack_encoding = |
| GetHpackEncoder()->EncodeHeaderBlock(headers.header_block()); |
| *size = *size + hpack_encoding->size(); |
| if (*size > kHttp2MaxControlFrameSendSize) { |
| *size = *size + GetNumberRequiredContinuationFrames(*size) * |
| kContinuationFrameMinimumSize; |
| *flags = *flags & ~HEADERS_FLAG_END_HEADERS; |
| } |
| // Compute frame length field. |
| if (headers.padded()) { |
| *length_field = *length_field + kPadLengthFieldSize; |
| } |
| if (headers.has_priority()) { |
| *length_field = *length_field + 4; // Dependency field. |
| *length_field = *length_field + 1; // Weight field. |
| } |
| *length_field = *length_field + headers.padding_payload_len(); |
| *length_field = *length_field + hpack_encoding->size(); |
| // If the HEADERS frame with payload would exceed the max frame size, then |
| // WritePayloadWithContinuation() will serialize CONTINUATION frames as |
| // necessary. |
| *length_field = |
| std::min(*length_field, kHttp2MaxControlFrameSendSize - kFrameHeaderSize); |
| } |
| |
| SpdySerializedFrame SpdyFramer::SerializeHeaders(const SpdyHeadersIR& headers) { |
| uint8_t flags = 0; |
| // The size of this frame, including padding (if there is any) and |
| // variable-length header block. |
| size_t size = 0; |
| std::string hpack_encoding; |
| int weight = 0; |
| size_t length_field = 0; |
| SerializeHeadersBuilderHelper(headers, &flags, &size, &hpack_encoding, |
| &weight, &length_field); |
| |
| SpdyFrameBuilder builder(size); |
| builder.BeginNewFrame(SpdyFrameType::HEADERS, flags, headers.stream_id(), |
| length_field); |
| |
| QUICHE_DCHECK_EQ(kHeadersFrameMinimumSize, builder.length()); |
| |
| int padding_payload_len = 0; |
| if (headers.padded()) { |
| builder.WriteUInt8(headers.padding_payload_len()); |
| padding_payload_len = headers.padding_payload_len(); |
| } |
| if (headers.has_priority()) { |
| builder.WriteUInt32(PackStreamDependencyValues(headers.exclusive(), |
| headers.parent_stream_id())); |
| // Per RFC 7540 section 6.3, serialized weight value is actual value - 1. |
| builder.WriteUInt8(weight - 1); |
| } |
| WritePayloadWithContinuation(&builder, hpack_encoding, headers.stream_id(), |
| SpdyFrameType::HEADERS, padding_payload_len); |
| |
| if (debug_visitor_) { |
| const size_t header_list_size = |
| GetUncompressedSerializedLength(headers.header_block()); |
| debug_visitor_->OnSendCompressedFrame(headers.stream_id(), |
| SpdyFrameType::HEADERS, |
| header_list_size, builder.length()); |
| } |
| |
| return builder.take(); |
| } |
| |
| SpdySerializedFrame SpdyFramer::SerializeWindowUpdate( |
| const SpdyWindowUpdateIR& window_update) { |
| SpdyFrameBuilder builder(kWindowUpdateFrameSize); |
| builder.BeginNewFrame(SpdyFrameType::WINDOW_UPDATE, kNoFlags, |
| window_update.stream_id()); |
| builder.WriteUInt32(window_update.delta()); |
| QUICHE_DCHECK_EQ(kWindowUpdateFrameSize, builder.length()); |
| return builder.take(); |
| } |
| |
| void SpdyFramer::SerializePushPromiseBuilderHelper( |
| const SpdyPushPromiseIR& push_promise, |
| uint8_t* flags, |
| std::string* hpack_encoding, |
| size_t* size) { |
| *flags = 0; |
| // This will get overwritten if we overflow into a CONTINUATION frame. |
| *flags = *flags | PUSH_PROMISE_FLAG_END_PUSH_PROMISE; |
| // The size of this frame, including variable-length name-value block. |
| *size = kPushPromiseFrameMinimumSize; |
| |
| if (push_promise.padded()) { |
| *flags = *flags | PUSH_PROMISE_FLAG_PADDED; |
| *size = *size + kPadLengthFieldSize; |
| *size = *size + push_promise.padding_payload_len(); |
| } |
| |
| *hpack_encoding = |
| GetHpackEncoder()->EncodeHeaderBlock(push_promise.header_block()); |
| *size = *size + hpack_encoding->size(); |
| if (*size > kHttp2MaxControlFrameSendSize) { |
| *size = *size + GetNumberRequiredContinuationFrames(*size) * |
| kContinuationFrameMinimumSize; |
| *flags = *flags & ~PUSH_PROMISE_FLAG_END_PUSH_PROMISE; |
| } |
| } |
| |
| SpdySerializedFrame SpdyFramer::SerializePushPromise( |
| const SpdyPushPromiseIR& push_promise) { |
| uint8_t flags = 0; |
| size_t size = 0; |
| std::string hpack_encoding; |
| SerializePushPromiseBuilderHelper(push_promise, &flags, &hpack_encoding, |
| &size); |
| |
| SpdyFrameBuilder builder(size); |
| size_t length = |
| std::min(size, kHttp2MaxControlFrameSendSize) - kFrameHeaderSize; |
| builder.BeginNewFrame(SpdyFrameType::PUSH_PROMISE, flags, |
| push_promise.stream_id(), length); |
| int padding_payload_len = 0; |
| if (push_promise.padded()) { |
| builder.WriteUInt8(push_promise.padding_payload_len()); |
| builder.WriteUInt32(push_promise.promised_stream_id()); |
| QUICHE_DCHECK_EQ(kPushPromiseFrameMinimumSize + kPadLengthFieldSize, |
| builder.length()); |
| |
| padding_payload_len = push_promise.padding_payload_len(); |
| } else { |
| builder.WriteUInt32(push_promise.promised_stream_id()); |
| QUICHE_DCHECK_EQ(kPushPromiseFrameMinimumSize, builder.length()); |
| } |
| |
| WritePayloadWithContinuation( |
| &builder, hpack_encoding, push_promise.stream_id(), |
| SpdyFrameType::PUSH_PROMISE, padding_payload_len); |
| |
| if (debug_visitor_) { |
| const size_t header_list_size = |
| GetUncompressedSerializedLength(push_promise.header_block()); |
| debug_visitor_->OnSendCompressedFrame(push_promise.stream_id(), |
| SpdyFrameType::PUSH_PROMISE, |
| header_list_size, builder.length()); |
| } |
| |
| return builder.take(); |
| } |
| |
| SpdySerializedFrame SpdyFramer::SerializeContinuation( |
| const SpdyContinuationIR& continuation) const { |
| const std::string& encoding = continuation.encoding(); |
| size_t frame_size = kContinuationFrameMinimumSize + encoding.size(); |
| SpdyFrameBuilder builder(frame_size); |
| uint8_t flags = continuation.end_headers() ? HEADERS_FLAG_END_HEADERS : 0; |
| builder.BeginNewFrame(SpdyFrameType::CONTINUATION, flags, |
| continuation.stream_id()); |
| QUICHE_DCHECK_EQ(kFrameHeaderSize, builder.length()); |
| |
| builder.WriteBytes(encoding.data(), encoding.size()); |
| return builder.take(); |
| } |
| |
| SpdySerializedFrame SpdyFramer::SerializeAltSvc(const SpdyAltSvcIR& altsvc_ir) { |
| std::string value; |
| size_t size = 0; |
| SerializeAltSvcBuilderHelper(altsvc_ir, &value, &size); |
| SpdyFrameBuilder builder(size); |
| builder.BeginNewFrame(SpdyFrameType::ALTSVC, kNoFlags, altsvc_ir.stream_id()); |
| |
| builder.WriteUInt16(altsvc_ir.origin().length()); |
| builder.WriteBytes(altsvc_ir.origin().data(), altsvc_ir.origin().length()); |
| builder.WriteBytes(value.data(), value.length()); |
| QUICHE_DCHECK_LT(kGetAltSvcFrameMinimumSize, builder.length()); |
| return builder.take(); |
| } |
| |
| SpdySerializedFrame SpdyFramer::SerializePriority( |
| const SpdyPriorityIR& priority) const { |
| SpdyFrameBuilder builder(kPriorityFrameSize); |
| builder.BeginNewFrame(SpdyFrameType::PRIORITY, kNoFlags, |
| priority.stream_id()); |
| |
| builder.WriteUInt32(PackStreamDependencyValues(priority.exclusive(), |
| priority.parent_stream_id())); |
| // Per RFC 7540 section 6.3, serialized weight value is actual value - 1. |
| builder.WriteUInt8(priority.weight() - 1); |
| QUICHE_DCHECK_EQ(kPriorityFrameSize, builder.length()); |
| return builder.take(); |
| } |
| |
| SpdySerializedFrame SpdyFramer::SerializePriorityUpdate( |
| const SpdyPriorityUpdateIR& priority_update) const { |
| const size_t total_size = kPriorityUpdateFrameMinimumSize + |
| priority_update.priority_field_value().size(); |
| SpdyFrameBuilder builder(total_size); |
| builder.BeginNewFrame(SpdyFrameType::PRIORITY_UPDATE, kNoFlags, |
| priority_update.stream_id()); |
| |
| builder.WriteUInt32(priority_update.prioritized_stream_id()); |
| builder.WriteBytes(priority_update.priority_field_value().data(), |
| priority_update.priority_field_value().size()); |
| QUICHE_DCHECK_EQ(total_size, builder.length()); |
| return builder.take(); |
| } |
| |
| SpdySerializedFrame SpdyFramer::SerializeAcceptCh( |
| const SpdyAcceptChIR& accept_ch) const { |
| const size_t total_size = accept_ch.size(); |
| SpdyFrameBuilder builder(total_size); |
| builder.BeginNewFrame(SpdyFrameType::ACCEPT_CH, kNoFlags, |
| accept_ch.stream_id()); |
| |
| for (const AcceptChOriginValuePair& entry : accept_ch.entries()) { |
| builder.WriteUInt16(entry.origin.size()); |
| builder.WriteBytes(entry.origin.data(), entry.origin.size()); |
| builder.WriteUInt16(entry.value.size()); |
| builder.WriteBytes(entry.value.data(), entry.value.size()); |
| } |
| |
| QUICHE_DCHECK_EQ(total_size, builder.length()); |
| return builder.take(); |
| } |
| |
| SpdySerializedFrame SpdyFramer::SerializeUnknown( |
| const SpdyUnknownIR& unknown) const { |
| const size_t total_size = kFrameHeaderSize + unknown.payload().size(); |
| SpdyFrameBuilder builder(total_size); |
| builder.BeginNewUncheckedFrame(unknown.type(), unknown.flags(), |
| unknown.stream_id(), unknown.length()); |
| builder.WriteBytes(unknown.payload().data(), unknown.payload().size()); |
| return builder.take(); |
| } |
| |
| namespace { |
| |
| class FrameSerializationVisitor : public SpdyFrameVisitor { |
| public: |
| explicit FrameSerializationVisitor(SpdyFramer* framer) |
| : framer_(framer), frame_() {} |
| ~FrameSerializationVisitor() override = default; |
| |
| SpdySerializedFrame ReleaseSerializedFrame() { return std::move(frame_); } |
| |
| void VisitData(const SpdyDataIR& data) override { |
| frame_ = framer_->SerializeData(data); |
| } |
| void VisitRstStream(const SpdyRstStreamIR& rst_stream) override { |
| frame_ = framer_->SerializeRstStream(rst_stream); |
| } |
| void VisitSettings(const SpdySettingsIR& settings) override { |
| frame_ = framer_->SerializeSettings(settings); |
| } |
| void VisitPing(const SpdyPingIR& ping) override { |
| frame_ = framer_->SerializePing(ping); |
| } |
| void VisitGoAway(const SpdyGoAwayIR& goaway) override { |
| frame_ = framer_->SerializeGoAway(goaway); |
| } |
| void VisitHeaders(const SpdyHeadersIR& headers) override { |
| frame_ = framer_->SerializeHeaders(headers); |
| } |
| void VisitWindowUpdate(const SpdyWindowUpdateIR& window_update) override { |
| frame_ = framer_->SerializeWindowUpdate(window_update); |
| } |
| void VisitPushPromise(const SpdyPushPromiseIR& push_promise) override { |
| frame_ = framer_->SerializePushPromise(push_promise); |
| } |
| void VisitContinuation(const SpdyContinuationIR& continuation) override { |
| frame_ = framer_->SerializeContinuation(continuation); |
| } |
| void VisitAltSvc(const SpdyAltSvcIR& altsvc) override { |
| frame_ = framer_->SerializeAltSvc(altsvc); |
| } |
| void VisitPriority(const SpdyPriorityIR& priority) override { |
| frame_ = framer_->SerializePriority(priority); |
| } |
| void VisitPriorityUpdate( |
| const SpdyPriorityUpdateIR& priority_update) override { |
| frame_ = framer_->SerializePriorityUpdate(priority_update); |
| } |
| void VisitAcceptCh(const SpdyAcceptChIR& accept_ch) override { |
| frame_ = framer_->SerializeAcceptCh(accept_ch); |
| } |
| void VisitUnknown(const SpdyUnknownIR& unknown) override { |
| frame_ = framer_->SerializeUnknown(unknown); |
| } |
| |
| private: |
| SpdyFramer* framer_; |
| SpdySerializedFrame frame_; |
| }; |
| |
| // TODO(diannahu): Use also in frame serialization. |
| class FlagsSerializationVisitor : public SpdyFrameVisitor { |
| public: |
| void VisitData(const SpdyDataIR& data) override { |
| flags_ = DATA_FLAG_NONE; |
| if (data.fin()) { |
| flags_ |= DATA_FLAG_FIN; |
| } |
| if (data.padded()) { |
| flags_ |= DATA_FLAG_PADDED; |
| } |
| } |
| |
| void VisitRstStream(const SpdyRstStreamIR& /*rst_stream*/) override { |
| flags_ = kNoFlags; |
| } |
| |
| void VisitSettings(const SpdySettingsIR& settings) override { |
| flags_ = kNoFlags; |
| if (settings.is_ack()) { |
| flags_ |= SETTINGS_FLAG_ACK; |
| } |
| } |
| |
| void VisitPing(const SpdyPingIR& ping) override { |
| flags_ = kNoFlags; |
| if (ping.is_ack()) { |
| flags_ |= PING_FLAG_ACK; |
| } |
| } |
| |
| void VisitGoAway(const SpdyGoAwayIR& /*goaway*/) override { |
| flags_ = kNoFlags; |
| } |
| |
| // TODO(diannahu): The END_HEADERS flag is incorrect for HEADERS that require |
| // CONTINUATION frames. |
| void VisitHeaders(const SpdyHeadersIR& headers) override { |
| flags_ = HEADERS_FLAG_END_HEADERS; |
| if (headers.fin()) { |
| flags_ |= CONTROL_FLAG_FIN; |
| } |
| if (headers.padded()) { |
| flags_ |= HEADERS_FLAG_PADDED; |
| } |
| if (headers.has_priority()) { |
| flags_ |= HEADERS_FLAG_PRIORITY; |
| } |
| } |
| |
| void VisitWindowUpdate(const SpdyWindowUpdateIR& /*window_update*/) override { |
| flags_ = kNoFlags; |
| } |
| |
| // TODO(diannahu): The END_PUSH_PROMISE flag is incorrect for PUSH_PROMISEs |
| // that require CONTINUATION frames. |
| void VisitPushPromise(const SpdyPushPromiseIR& push_promise) override { |
| flags_ = PUSH_PROMISE_FLAG_END_PUSH_PROMISE; |
| if (push_promise.padded()) { |
| flags_ |= PUSH_PROMISE_FLAG_PADDED; |
| } |
| } |
| |
| // TODO(diannahu): The END_HEADERS flag is incorrect for CONTINUATIONs that |
| // require CONTINUATION frames. |
| void VisitContinuation(const SpdyContinuationIR& /*continuation*/) override { |
| flags_ = HEADERS_FLAG_END_HEADERS; |
| } |
| |
| void VisitAltSvc(const SpdyAltSvcIR& /*altsvc*/) override { |
| flags_ = kNoFlags; |
| } |
| |
| void VisitPriority(const SpdyPriorityIR& /*priority*/) override { |
| flags_ = kNoFlags; |
| } |
| |
| void VisitPriorityUpdate( |
| const SpdyPriorityUpdateIR& /*priority_update*/) override { |
| flags_ = kNoFlags; |
| } |
| |
| void VisitAcceptCh(const SpdyAcceptChIR& /*accept_ch*/) override { |
| flags_ = kNoFlags; |
| } |
| |
| uint8_t flags() const { return flags_; } |
| |
| private: |
| uint8_t flags_ = kNoFlags; |
| }; |
| |
| } // namespace |
| |
| SpdySerializedFrame SpdyFramer::SerializeFrame(const SpdyFrameIR& frame) { |
| FrameSerializationVisitor visitor(this); |
| frame.Visit(&visitor); |
| return visitor.ReleaseSerializedFrame(); |
| } |
| |
| uint8_t SpdyFramer::GetSerializedFlags(const SpdyFrameIR& frame) { |
| FlagsSerializationVisitor visitor; |
| frame.Visit(&visitor); |
| return visitor.flags(); |
| } |
| |
| bool SpdyFramer::SerializeData(const SpdyDataIR& data_ir, |
| ZeroCopyOutputBuffer* output) const { |
| uint8_t flags = DATA_FLAG_NONE; |
| int num_padding_fields = 0; |
| size_t size_with_padding = 0; |
| SerializeDataBuilderHelper(data_ir, &flags, &num_padding_fields, |
| &size_with_padding); |
| SpdyFrameBuilder builder(size_with_padding, output); |
| |
| bool ok = |
| builder.BeginNewFrame(SpdyFrameType::DATA, flags, data_ir.stream_id()); |
| |
| if (data_ir.padded()) { |
| ok = ok && builder.WriteUInt8(data_ir.padding_payload_len() & 0xff); |
| } |
| |
| ok = ok && builder.WriteBytes(data_ir.data(), data_ir.data_len()); |
| if (data_ir.padding_payload_len() > 0) { |
| std::string padding; |
| padding = std::string(data_ir.padding_payload_len(), 0); |
| ok = ok && builder.WriteBytes(padding.data(), padding.length()); |
| } |
| QUICHE_DCHECK_EQ(size_with_padding, builder.length()); |
| return ok; |
| } |
| |
| bool SpdyFramer::SerializeDataFrameHeaderWithPaddingLengthField( |
| const SpdyDataIR& data_ir, |
| ZeroCopyOutputBuffer* output) const { |
| uint8_t flags = DATA_FLAG_NONE; |
| size_t frame_size = 0; |
| size_t num_padding_fields = 0; |
| SerializeDataFrameHeaderWithPaddingLengthFieldBuilderHelper( |
| data_ir, &flags, &frame_size, &num_padding_fields); |
| |
| SpdyFrameBuilder builder(frame_size, output); |
| bool ok = true; |
| ok = ok && |
| builder.BeginNewFrame(SpdyFrameType::DATA, flags, data_ir.stream_id(), |
| num_padding_fields + data_ir.data_len() + |
| data_ir.padding_payload_len()); |
| if (data_ir.padded()) { |
| ok = ok && builder.WriteUInt8(data_ir.padding_payload_len() & 0xff); |
| } |
| QUICHE_DCHECK_EQ(frame_size, builder.length()); |
| return ok; |
| } |
| |
| bool SpdyFramer::SerializeRstStream(const SpdyRstStreamIR& rst_stream, |
| ZeroCopyOutputBuffer* output) const { |
| size_t expected_length = kRstStreamFrameSize; |
| SpdyFrameBuilder builder(expected_length, output); |
| bool ok = builder.BeginNewFrame(SpdyFrameType::RST_STREAM, 0, |
| rst_stream.stream_id()); |
| ok = ok && builder.WriteUInt32(rst_stream.error_code()); |
| |
| QUICHE_DCHECK_EQ(expected_length, builder.length()); |
| return ok; |
| } |
| |
| bool SpdyFramer::SerializeSettings(const SpdySettingsIR& settings, |
| ZeroCopyOutputBuffer* output) const { |
| uint8_t flags = 0; |
| // Size, in bytes, of this SETTINGS frame. |
| size_t size = 0; |
| const SettingsMap* values = &(settings.values()); |
| SerializeSettingsBuilderHelper(settings, &flags, values, &size); |
| SpdyFrameBuilder builder(size, output); |
| bool ok = builder.BeginNewFrame(SpdyFrameType::SETTINGS, flags, 0); |
| |
| // If this is an ACK, payload should be empty. |
| if (settings.is_ack()) { |
| return ok; |
| } |
| |
| QUICHE_DCHECK_EQ(kSettingsFrameMinimumSize, builder.length()); |
| for (auto it = values->begin(); it != values->end(); ++it) { |
| int setting_id = it->first; |
| QUICHE_DCHECK_GE(setting_id, 0); |
| ok = ok && builder.WriteUInt16(static_cast<SpdySettingsId>(setting_id)) && |
| builder.WriteUInt32(it->second); |
| } |
| QUICHE_DCHECK_EQ(size, builder.length()); |
| return ok; |
| } |
| |
| bool SpdyFramer::SerializePing(const SpdyPingIR& ping, |
| ZeroCopyOutputBuffer* output) const { |
| SpdyFrameBuilder builder(kPingFrameSize, output); |
| uint8_t flags = 0; |
| if (ping.is_ack()) { |
| flags |= PING_FLAG_ACK; |
| } |
| bool ok = builder.BeginNewFrame(SpdyFrameType::PING, flags, 0); |
| ok = ok && builder.WriteUInt64(ping.id()); |
| QUICHE_DCHECK_EQ(kPingFrameSize, builder.length()); |
| return ok; |
| } |
| |
| bool SpdyFramer::SerializeGoAway(const SpdyGoAwayIR& goaway, |
| ZeroCopyOutputBuffer* output) const { |
| // Compute the output buffer size, take opaque data into account. |
| size_t expected_length = kGoawayFrameMinimumSize; |
| expected_length += goaway.description().size(); |
| SpdyFrameBuilder builder(expected_length, output); |
| |
| // Serialize the GOAWAY frame. |
| bool ok = builder.BeginNewFrame(SpdyFrameType::GOAWAY, 0, 0); |
| |
| // GOAWAY frames specify the last good stream id. |
| ok = ok && builder.WriteUInt32(goaway.last_good_stream_id()) && |
| // GOAWAY frames also specify the error status code. |
| builder.WriteUInt32(goaway.error_code()); |
| |
| // GOAWAY frames may also specify opaque data. |
| if (!goaway.description().empty()) { |
| ok = ok && builder.WriteBytes(goaway.description().data(), |
| goaway.description().size()); |
| } |
| |
| QUICHE_DCHECK_EQ(expected_length, builder.length()); |
| return ok; |
| } |
| |
| bool SpdyFramer::SerializeHeaders(const SpdyHeadersIR& headers, |
| ZeroCopyOutputBuffer* output) { |
| uint8_t flags = 0; |
| // The size of this frame, including padding (if there is any) and |
| // variable-length header block. |
| size_t size = 0; |
| std::string hpack_encoding; |
| int weight = 0; |
| size_t length_field = 0; |
| SerializeHeadersBuilderHelper(headers, &flags, &size, &hpack_encoding, |
| &weight, &length_field); |
| |
| bool ok = true; |
| SpdyFrameBuilder builder(size, output); |
| ok = ok && builder.BeginNewFrame(SpdyFrameType::HEADERS, flags, |
| headers.stream_id(), length_field); |
| QUICHE_DCHECK_EQ(kHeadersFrameMinimumSize, builder.length()); |
| |
| int padding_payload_len = 0; |
| if (headers.padded()) { |
| ok = ok && builder.WriteUInt8(headers.padding_payload_len()); |
| padding_payload_len = headers.padding_payload_len(); |
| } |
| if (headers.has_priority()) { |
| ok = ok && |
| builder.WriteUInt32(PackStreamDependencyValues( |
| headers.exclusive(), headers.parent_stream_id())) && |
| // Per RFC 7540 section 6.3, serialized weight value is weight - 1. |
| builder.WriteUInt8(weight - 1); |
| } |
| ok = ok && WritePayloadWithContinuation( |
| &builder, hpack_encoding, headers.stream_id(), |
| SpdyFrameType::HEADERS, padding_payload_len); |
| |
| if (debug_visitor_) { |
| const size_t header_list_size = |
| GetUncompressedSerializedLength(headers.header_block()); |
| debug_visitor_->OnSendCompressedFrame(headers.stream_id(), |
| SpdyFrameType::HEADERS, |
| header_list_size, builder.length()); |
| } |
| |
| return ok; |
| } |
| |
| bool SpdyFramer::SerializeWindowUpdate(const SpdyWindowUpdateIR& window_update, |
| ZeroCopyOutputBuffer* output) const { |
| SpdyFrameBuilder builder(kWindowUpdateFrameSize, output); |
| bool ok = builder.BeginNewFrame(SpdyFrameType::WINDOW_UPDATE, kNoFlags, |
| window_update.stream_id()); |
| ok = ok && builder.WriteUInt32(window_update.delta()); |
| QUICHE_DCHECK_EQ(kWindowUpdateFrameSize, builder.length()); |
| return ok; |
| } |
| |
| bool SpdyFramer::SerializePushPromise(const SpdyPushPromiseIR& push_promise, |
| ZeroCopyOutputBuffer* output) { |
| uint8_t flags = 0; |
| size_t size = 0; |
| std::string hpack_encoding; |
| SerializePushPromiseBuilderHelper(push_promise, &flags, &hpack_encoding, |
| &size); |
| |
| bool ok = true; |
| SpdyFrameBuilder builder(size, output); |
| size_t length = |
| std::min(size, kHttp2MaxControlFrameSendSize) - kFrameHeaderSize; |
| ok = builder.BeginNewFrame(SpdyFrameType::PUSH_PROMISE, flags, |
| push_promise.stream_id(), length); |
| |
| int padding_payload_len = 0; |
| if (push_promise.padded()) { |
| ok = ok && builder.WriteUInt8(push_promise.padding_payload_len()) && |
| builder.WriteUInt32(push_promise.promised_stream_id()); |
| QUICHE_DCHECK_EQ(kPushPromiseFrameMinimumSize + kPadLengthFieldSize, |
| builder.length()); |
| |
| padding_payload_len = push_promise.padding_payload_len(); |
| } else { |
| ok = ok && builder.WriteUInt32(push_promise.promised_stream_id()); |
| QUICHE_DCHECK_EQ(kPushPromiseFrameMinimumSize, builder.length()); |
| } |
| |
| ok = ok && WritePayloadWithContinuation( |
| &builder, hpack_encoding, push_promise.stream_id(), |
| SpdyFrameType::PUSH_PROMISE, padding_payload_len); |
| |
| if (debug_visitor_) { |
| const size_t header_list_size = |
| GetUncompressedSerializedLength(push_promise.header_block()); |
| debug_visitor_->OnSendCompressedFrame(push_promise.stream_id(), |
| SpdyFrameType::PUSH_PROMISE, |
| header_list_size, builder.length()); |
| } |
| |
| return ok; |
| } |
| |
| bool SpdyFramer::SerializeContinuation(const SpdyContinuationIR& continuation, |
| ZeroCopyOutputBuffer* output) const { |
| const std::string& encoding = continuation.encoding(); |
| size_t frame_size = kContinuationFrameMinimumSize + encoding.size(); |
| SpdyFrameBuilder builder(frame_size, output); |
| uint8_t flags = continuation.end_headers() ? HEADERS_FLAG_END_HEADERS : 0; |
| bool ok = builder.BeginNewFrame(SpdyFrameType::CONTINUATION, flags, |
| continuation.stream_id(), |
| frame_size - kFrameHeaderSize); |
| QUICHE_DCHECK_EQ(kFrameHeaderSize, builder.length()); |
| |
| ok = ok && builder.WriteBytes(encoding.data(), encoding.size()); |
| return ok; |
| } |
| |
| bool SpdyFramer::SerializeAltSvc(const SpdyAltSvcIR& altsvc_ir, |
| ZeroCopyOutputBuffer* output) { |
| std::string value; |
| size_t size = 0; |
| SerializeAltSvcBuilderHelper(altsvc_ir, &value, &size); |
| SpdyFrameBuilder builder(size, output); |
| bool ok = builder.BeginNewFrame(SpdyFrameType::ALTSVC, kNoFlags, |
| altsvc_ir.stream_id()) && |
| builder.WriteUInt16(altsvc_ir.origin().length()) && |
| builder.WriteBytes(altsvc_ir.origin().data(), |
| altsvc_ir.origin().length()) && |
| builder.WriteBytes(value.data(), value.length()); |
| QUICHE_DCHECK_LT(kGetAltSvcFrameMinimumSize, builder.length()); |
| return ok; |
| } |
| |
| bool SpdyFramer::SerializePriority(const SpdyPriorityIR& priority, |
| ZeroCopyOutputBuffer* output) const { |
| SpdyFrameBuilder builder(kPriorityFrameSize, output); |
| bool ok = builder.BeginNewFrame(SpdyFrameType::PRIORITY, kNoFlags, |
| priority.stream_id()); |
| ok = ok && |
| builder.WriteUInt32(PackStreamDependencyValues( |
| priority.exclusive(), priority.parent_stream_id())) && |
| // Per RFC 7540 section 6.3, serialized weight value is actual value - 1. |
| builder.WriteUInt8(priority.weight() - 1); |
| QUICHE_DCHECK_EQ(kPriorityFrameSize, builder.length()); |
| return ok; |
| } |
| |
| bool SpdyFramer::SerializePriorityUpdate( |
| const SpdyPriorityUpdateIR& priority_update, |
| ZeroCopyOutputBuffer* output) const { |
| const size_t total_size = kPriorityUpdateFrameMinimumSize + |
| priority_update.priority_field_value().size(); |
| SpdyFrameBuilder builder(total_size, output); |
| bool ok = builder.BeginNewFrame(SpdyFrameType::PRIORITY_UPDATE, kNoFlags, |
| priority_update.stream_id()); |
| |
| ok = ok && builder.WriteUInt32(priority_update.prioritized_stream_id()); |
| ok = ok && builder.WriteBytes(priority_update.priority_field_value().data(), |
| priority_update.priority_field_value().size()); |
| QUICHE_DCHECK_EQ(total_size, builder.length()); |
| return ok; |
| } |
| |
| bool SpdyFramer::SerializeAcceptCh(const SpdyAcceptChIR& accept_ch, |
| ZeroCopyOutputBuffer* output) const { |
| const size_t total_size = accept_ch.size(); |
| SpdyFrameBuilder builder(total_size, output); |
| bool ok = builder.BeginNewFrame(SpdyFrameType::ACCEPT_CH, kNoFlags, |
| accept_ch.stream_id()); |
| |
| for (const AcceptChOriginValuePair& entry : accept_ch.entries()) { |
| ok = ok && builder.WriteUInt16(entry.origin.size()); |
| ok = ok && builder.WriteBytes(entry.origin.data(), entry.origin.size()); |
| ok = ok && builder.WriteUInt16(entry.value.size()); |
| ok = ok && builder.WriteBytes(entry.value.data(), entry.value.size()); |
| } |
| |
| QUICHE_DCHECK_EQ(total_size, builder.length()); |
| return ok; |
| } |
| |
| bool SpdyFramer::SerializeUnknown(const SpdyUnknownIR& unknown, |
| ZeroCopyOutputBuffer* output) const { |
| const size_t total_size = kFrameHeaderSize + unknown.payload().size(); |
| SpdyFrameBuilder builder(total_size, output); |
| bool ok = builder.BeginNewUncheckedFrame( |
| unknown.type(), unknown.flags(), unknown.stream_id(), unknown.length()); |
| ok = ok && |
| builder.WriteBytes(unknown.payload().data(), unknown.payload().size()); |
| return ok; |
| } |
| |
| namespace { |
| |
| class FrameSerializationVisitorWithOutput : public SpdyFrameVisitor { |
| public: |
| explicit FrameSerializationVisitorWithOutput(SpdyFramer* framer, |
| ZeroCopyOutputBuffer* output) |
| : framer_(framer), output_(output), result_(false) {} |
| ~FrameSerializationVisitorWithOutput() override = default; |
| |
| size_t Result() { return result_; } |
| |
| void VisitData(const SpdyDataIR& data) override { |
| result_ = framer_->SerializeData(data, output_); |
| } |
| void VisitRstStream(const SpdyRstStreamIR& rst_stream) override { |
| result_ = framer_->SerializeRstStream(rst_stream, output_); |
| } |
| void VisitSettings(const SpdySettingsIR& settings) override { |
| result_ = framer_->SerializeSettings(settings, output_); |
| } |
| void VisitPing(const SpdyPingIR& ping) override { |
| result_ = framer_->SerializePing(ping, output_); |
| } |
| void VisitGoAway(const SpdyGoAwayIR& goaway) override { |
| result_ = framer_->SerializeGoAway(goaway, output_); |
| } |
| void VisitHeaders(const SpdyHeadersIR& headers) override { |
| result_ = framer_->SerializeHeaders(headers, output_); |
| } |
| void VisitWindowUpdate(const SpdyWindowUpdateIR& window_update) override { |
| result_ = framer_->SerializeWindowUpdate(window_update, output_); |
| } |
| void VisitPushPromise(const SpdyPushPromiseIR& push_promise) override { |
| result_ = framer_->SerializePushPromise(push_promise, output_); |
| } |
| void VisitContinuation(const SpdyContinuationIR& continuation) override { |
| result_ = framer_->SerializeContinuation(continuation, output_); |
| } |
| void VisitAltSvc(const SpdyAltSvcIR& altsvc) override { |
| result_ = framer_->SerializeAltSvc(altsvc, output_); |
| } |
| void VisitPriority(const SpdyPriorityIR& priority) override { |
| result_ = framer_->SerializePriority(priority, output_); |
| } |
| void VisitPriorityUpdate( |
| const SpdyPriorityUpdateIR& priority_update) override { |
| result_ = framer_->SerializePriorityUpdate(priority_update, output_); |
| } |
| void VisitAcceptCh(const SpdyAcceptChIR& accept_ch) override { |
| result_ = framer_->SerializeAcceptCh(accept_ch, output_); |
| } |
| |
| void VisitUnknown(const SpdyUnknownIR& unknown) override { |
| result_ = framer_->SerializeUnknown(unknown, output_); |
| } |
| |
| private: |
| SpdyFramer* framer_; |
| ZeroCopyOutputBuffer* output_; |
| bool result_; |
| }; |
| |
| } // namespace |
| |
| size_t SpdyFramer::SerializeFrame(const SpdyFrameIR& frame, |
| ZeroCopyOutputBuffer* output) { |
| FrameSerializationVisitorWithOutput visitor(this, output); |
| size_t free_bytes_before = output->BytesFree(); |
| frame.Visit(&visitor); |
| return visitor.Result() ? free_bytes_before - output->BytesFree() : 0; |
| } |
| |
| HpackEncoder* SpdyFramer::GetHpackEncoder() { |
| if (hpack_encoder_ == nullptr) { |
| hpack_encoder_ = std::make_unique<HpackEncoder>(); |
| if (!compression_enabled()) { |
| hpack_encoder_->DisableCompression(); |
| } |
| } |
| return hpack_encoder_.get(); |
| } |
| |
| void SpdyFramer::UpdateHeaderEncoderTableSize(uint32_t value) { |
| GetHpackEncoder()->ApplyHeaderTableSizeSetting(value); |
| } |
| |
| size_t SpdyFramer::header_encoder_table_size() const { |
| if (hpack_encoder_ == nullptr) { |
| return kDefaultHeaderTableSizeSetting; |
| } else { |
| return hpack_encoder_->CurrentHeaderTableSizeSetting(); |
| } |
| } |
| |
| } // namespace spdy |