1 #include "quiche/spdy/core/metadata_extension.h"
2
3 #include <list>
4 #include <memory>
5 #include <string>
6
7 #include "absl/memory/memory.h"
8 #include "absl/strings/str_cat.h"
9 #include "quiche/http2/decoder/decode_buffer.h"
10 #include "quiche/http2/hpack/decoder/hpack_decoder.h"
11 #include "quiche/common/platform/api/quiche_bug_tracker.h"
12 #include "quiche/common/platform/api/quiche_logging.h"
13 #include "quiche/spdy/core/http2_header_block_hpack_listener.h"
14
15 namespace spdy {
16
17 // Non-standard constants related to METADATA frames.
18 const SpdySettingsId MetadataVisitor::kMetadataExtensionId = 0x4d44;
19 const uint8_t MetadataVisitor::kMetadataFrameType = 0x4d;
20 const uint8_t MetadataVisitor::kEndMetadataFlag = 0x4;
21
22 namespace {
23
24 const size_t kMaxMetadataBlockSize = 1 << 20; // 1 MB
25
26 } // anonymous namespace
27
MetadataFrameSequence(SpdyStreamId stream_id,spdy::Http2HeaderBlock payload)28 MetadataFrameSequence::MetadataFrameSequence(SpdyStreamId stream_id,
29 spdy::Http2HeaderBlock payload)
30 : stream_id_(stream_id), payload_(std::move(payload)) {
31 // Metadata should not use HPACK compression.
32 encoder_.DisableCompression();
33 HpackEncoder::Representations r;
34 for (const auto& kv_pair : payload_) {
35 r.push_back(kv_pair);
36 }
37 progressive_encoder_ = encoder_.EncodeRepresentations(r);
38 }
39
HasNext() const40 bool MetadataFrameSequence::HasNext() const {
41 return progressive_encoder_->HasNext();
42 }
43
Next()44 std::unique_ptr<spdy::SpdyFrameIR> MetadataFrameSequence::Next() {
45 if (!HasNext()) {
46 return nullptr;
47 }
48 // METADATA frames obey the HTTP/2 maximum frame size.
49 std::string payload =
50 progressive_encoder_->Next(spdy::kHttp2DefaultFramePayloadLimit);
51 const bool end_metadata = !HasNext();
52 const uint8_t flags = end_metadata ? MetadataVisitor::kEndMetadataFlag : 0;
53 return std::make_unique<spdy::SpdyUnknownIR>(
54 stream_id_, MetadataVisitor::kMetadataFrameType, flags,
55 std::move(payload));
56 }
57
58 struct MetadataVisitor::MetadataPayloadState {
MetadataPayloadStatespdy::MetadataVisitor::MetadataPayloadState59 MetadataPayloadState(size_t remaining, bool end)
60 : bytes_remaining(remaining), end_metadata(end) {}
61 std::list<std::string> buffer;
62 size_t bytes_remaining;
63 bool end_metadata;
64 };
65
MetadataVisitor(OnCompletePayload on_payload,OnMetadataSupport on_support)66 MetadataVisitor::MetadataVisitor(OnCompletePayload on_payload,
67 OnMetadataSupport on_support)
68 : on_payload_(std::move(on_payload)),
69 on_support_(std::move(on_support)),
70 peer_supports_metadata_(MetadataSupportState::UNSPECIFIED) {}
71
~MetadataVisitor()72 MetadataVisitor::~MetadataVisitor() {}
73
OnSetting(SpdySettingsId id,uint32_t value)74 void MetadataVisitor::OnSetting(SpdySettingsId id, uint32_t value) {
75 QUICHE_VLOG(1) << "MetadataVisitor::OnSetting(" << id << ", " << value << ")";
76 if (id == kMetadataExtensionId) {
77 if (value == 0) {
78 const MetadataSupportState previous_state = peer_supports_metadata_;
79 peer_supports_metadata_ = MetadataSupportState::NOT_SUPPORTED;
80 if (previous_state == MetadataSupportState::UNSPECIFIED ||
81 previous_state == MetadataSupportState::SUPPORTED) {
82 on_support_(false);
83 }
84 } else if (value == 1) {
85 const MetadataSupportState previous_state = peer_supports_metadata_;
86 peer_supports_metadata_ = MetadataSupportState::SUPPORTED;
87 if (previous_state == MetadataSupportState::UNSPECIFIED ||
88 previous_state == MetadataSupportState::NOT_SUPPORTED) {
89 on_support_(true);
90 }
91 } else {
92 QUICHE_LOG_EVERY_N_SEC(WARNING, 1)
93 << "Unrecognized value for setting " << id << ": " << value;
94 }
95 }
96 }
97
OnFrameHeader(SpdyStreamId stream_id,size_t length,uint8_t type,uint8_t flags)98 bool MetadataVisitor::OnFrameHeader(SpdyStreamId stream_id, size_t length,
99 uint8_t type, uint8_t flags) {
100 QUICHE_VLOG(1) << "OnFrameHeader(stream_id=" << stream_id
101 << ", length=" << length << ", type=" << static_cast<int>(type)
102 << ", flags=" << static_cast<int>(flags);
103 // TODO(birenroy): Consider disabling METADATA handling until our setting
104 // advertising METADATA support has been acked.
105 if (type != kMetadataFrameType) {
106 return false;
107 }
108 auto it = metadata_map_.find(stream_id);
109 if (it == metadata_map_.end()) {
110 auto state = std::make_unique<MetadataPayloadState>(
111 length, flags & kEndMetadataFlag);
112 auto result =
113 metadata_map_.insert(std::make_pair(stream_id, std::move(state)));
114 QUICHE_BUG_IF(bug_if_2781_1, !result.second) << "Map insertion failed.";
115 it = result.first;
116 } else {
117 QUICHE_BUG_IF(bug_22051_1, it->second->end_metadata)
118 << "Inconsistent metadata payload state!";
119 QUICHE_BUG_IF(bug_if_2781_2, it->second->bytes_remaining > 0)
120 << "Incomplete metadata block!";
121 }
122
123 if (it->second == nullptr) {
124 QUICHE_BUG(bug_2781_3) << "Null metadata payload state!";
125 return false;
126 }
127 current_stream_ = stream_id;
128 it->second->bytes_remaining = length;
129 it->second->end_metadata = (flags & kEndMetadataFlag);
130 return true;
131 }
132
OnFramePayload(const char * data,size_t len)133 void MetadataVisitor::OnFramePayload(const char* data, size_t len) {
134 QUICHE_VLOG(1) << "OnFramePayload(stream_id=" << current_stream_
135 << ", len=" << len << ")";
136 auto it = metadata_map_.find(current_stream_);
137 if (it == metadata_map_.end() || it->second == nullptr) {
138 QUICHE_BUG(bug_2781_4) << "Invalid order of operations on MetadataVisitor.";
139 } else {
140 MetadataPayloadState* state = it->second.get(); // For readability.
141 state->buffer.push_back(std::string(data, len));
142 if (len < state->bytes_remaining) {
143 state->bytes_remaining -= len;
144 } else {
145 QUICHE_BUG_IF(bug_22051_2, len > state->bytes_remaining)
146 << "Metadata payload overflow! len: " << len
147 << " bytes_remaining: " << state->bytes_remaining;
148 state->bytes_remaining = 0;
149 if (state->end_metadata) {
150 // The whole process of decoding the HPACK-encoded metadata block,
151 // below, is more cumbersome than it ought to be.
152 spdy::Http2HeaderBlockHpackListener listener;
153 http2::HpackDecoder decoder(&listener, kMaxMetadataBlockSize);
154
155 // If any operations fail, the decode process should be aborted.
156 bool success = decoder.StartDecodingBlock();
157 for (const std::string& slice : state->buffer) {
158 if (!success) {
159 break;
160 }
161 http2::DecodeBuffer buffer(slice.data(), slice.size());
162 success = success && decoder.DecodeFragment(&buffer);
163 }
164 success =
165 success && decoder.EndDecodingBlock() && !listener.hpack_error();
166 if (success) {
167 on_payload_(current_stream_, listener.release_header_block());
168 }
169 // TODO(birenroy): add varz counting metadata decode successes/failures.
170 metadata_map_.erase(it);
171 }
172 }
173 }
174 }
175
176 } // namespace spdy
177