• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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