summaryrefslogtreecommitdiff
path: root/chromium/net/third_party/quiche/src/spdy/core/metadata_extension.cc
blob: 1683dcc4ff2bea94a546fd0666c4c16e3f4707f5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
#include "spdy/core/metadata_extension.h"

#include <list>
#include <string>

#include "absl/memory/memory.h"
#include "absl/strings/str_cat.h"
#include "http2/decoder/decode_buffer.h"
#include "http2/hpack/decoder/hpack_decoder.h"
#include "common/platform/api/quiche_bug_tracker.h"
#include "common/platform/api/quiche_logging.h"
#include "spdy/core/hpack/hpack_encoder.h"
#include "spdy/core/http2_header_block_hpack_listener.h"

namespace spdy {

// Non-standard constants related to METADATA frames.
const SpdySettingsId MetadataVisitor::kMetadataExtensionId = 0x4d44;
const uint8_t MetadataVisitor::kMetadataFrameType = 0x4d;
const uint8_t MetadataVisitor::kEndMetadataFlag = 0x4;

namespace {

const size_t kMaxMetadataBlockSize = 1 << 20;  // 1 MB

// This class uses an HpackEncoder to serialize a METADATA block as a series of
// METADATA frames.
class MetadataFrameSequence : public MetadataSerializer::FrameSequence {
 public:
  MetadataFrameSequence(SpdyStreamId stream_id, spdy::SpdyHeaderBlock payload)
      : stream_id_(stream_id), payload_(std::move(payload)) {
    // Metadata should not use HPACK compression.
    encoder_.DisableCompression();
    HpackEncoder::Representations r;
    for (const auto& kv_pair : payload_) {
      r.push_back(kv_pair);
    }
    progressive_encoder_ = encoder_.EncodeRepresentations(r);
  }

  // Copies are not allowed.
  MetadataFrameSequence(const MetadataFrameSequence& other) = delete;
  MetadataFrameSequence& operator=(const MetadataFrameSequence& other) = delete;

  std::unique_ptr<spdy::SpdyFrameIR> Next() override;

 private:
  SpdyStreamId stream_id_;
  SpdyHeaderBlock payload_;
  HpackEncoder encoder_;
  std::unique_ptr<HpackEncoder::ProgressiveEncoder> progressive_encoder_;
};

std::unique_ptr<spdy::SpdyFrameIR> MetadataFrameSequence::Next() {
  if (!progressive_encoder_->HasNext()) {
    return nullptr;
  }
  // METADATA frames obey the HTTP/2 maximum frame size.
  std::string payload =
      progressive_encoder_->Next(spdy::kHttp2DefaultFramePayloadLimit);
  const bool end_metadata = (!progressive_encoder_->HasNext());
  const uint8_t flags = end_metadata ? MetadataVisitor::kEndMetadataFlag : 0;
  return absl::make_unique<spdy::SpdyUnknownIR>(
      stream_id_, MetadataVisitor::kMetadataFrameType, flags,
      std::move(payload));
}

}  // anonymous namespace

struct MetadataVisitor::MetadataPayloadState {
  MetadataPayloadState(size_t remaining, bool end)
      : bytes_remaining(remaining), end_metadata(end) {}
  std::list<std::string> buffer;
  size_t bytes_remaining;
  bool end_metadata;
};

MetadataVisitor::MetadataVisitor(OnCompletePayload on_payload,
                                 OnMetadataSupport on_support)
    : on_payload_(std::move(on_payload)),
      on_support_(std::move(on_support)),
      peer_supports_metadata_(MetadataSupportState::UNSPECIFIED) {}

MetadataVisitor::~MetadataVisitor() {}

void MetadataVisitor::OnSetting(SpdySettingsId id, uint32_t value) {
  QUICHE_VLOG(1) << "MetadataVisitor::OnSetting(" << id << ", " << value << ")";
  if (id == kMetadataExtensionId) {
    if (value == 0) {
      const MetadataSupportState previous_state = peer_supports_metadata_;
      peer_supports_metadata_ = MetadataSupportState::NOT_SUPPORTED;
      if (previous_state == MetadataSupportState::UNSPECIFIED ||
          previous_state == MetadataSupportState::SUPPORTED) {
        on_support_(false);
      }
    } else if (value == 1) {
      const MetadataSupportState previous_state = peer_supports_metadata_;
      peer_supports_metadata_ = MetadataSupportState::SUPPORTED;
      if (previous_state == MetadataSupportState::UNSPECIFIED ||
          previous_state == MetadataSupportState::NOT_SUPPORTED) {
        on_support_(true);
      }
    } else {
      LOG_EVERY_N_SEC(WARNING, 1)
          << "Unrecognized value for setting " << id << ": " << value;
    }
  }
}

bool MetadataVisitor::OnFrameHeader(SpdyStreamId stream_id, size_t length,
                                    uint8_t type, uint8_t flags) {
  QUICHE_VLOG(1) << "OnFrameHeader(stream_id=" << stream_id
                 << ", length=" << length << ", type=" << static_cast<int>(type)
                 << ", flags=" << static_cast<int>(flags);
  // TODO(birenroy): Consider disabling METADATA handling until our setting
  // advertising METADATA support has been acked.
  if (type != kMetadataFrameType) {
    return false;
  }
  auto it = metadata_map_.find(stream_id);
  if (it == metadata_map_.end()) {
    auto state = absl::make_unique<MetadataPayloadState>(
        length, flags & kEndMetadataFlag);
    auto result = metadata_map_.insert(std::make_pair(stream_id,
                                                      std::move(state)));
    QUICHE_BUG_IF(bug_if_2781_1, !result.second) << "Map insertion failed.";
    it = result.first;
  } else {
    QUICHE_BUG_IF(bug_22051_1, it->second->end_metadata)
        << "Inconsistent metadata payload state!";
    QUICHE_BUG_IF(bug_if_2781_2, it->second->bytes_remaining > 0)
        << "Incomplete metadata block!";
  }

  if (it->second == nullptr) {
    QUICHE_BUG(bug_2781_3) << "Null metadata payload state!";
    return false;
  }
  current_stream_ = stream_id;
  it->second->bytes_remaining = length;
  it->second->end_metadata = (flags & kEndMetadataFlag);
  return true;
}

void MetadataVisitor::OnFramePayload(const char* data, size_t len) {
  QUICHE_VLOG(1) << "OnFramePayload(stream_id=" << current_stream_
                 << ", len=" << len << ")";
  auto it = metadata_map_.find(current_stream_);
  if (it == metadata_map_.end() || it->second == nullptr) {
    QUICHE_BUG(bug_2781_4) << "Invalid order of operations on MetadataVisitor.";
  } else {
    MetadataPayloadState* state = it->second.get();  // For readability.
    state->buffer.push_back(std::string(data, len));
    if (len < state->bytes_remaining) {
      state->bytes_remaining -= len;
    } else {
      QUICHE_BUG_IF(bug_22051_2, len > state->bytes_remaining)
          << "Metadata payload overflow! len: " << len
          << " bytes_remaining: " << state->bytes_remaining;
      state->bytes_remaining = 0;
      if (state->end_metadata) {
        // The whole process of decoding the HPACK-encoded metadata block,
        // below, is more cumbersome than it ought to be.
        spdy::Http2HeaderBlockHpackListener listener;
        http2::HpackDecoder decoder(&listener, kMaxMetadataBlockSize);

        // If any operations fail, the decode process should be aborted.
        bool success = decoder.StartDecodingBlock();
        for (const std::string& slice : state->buffer) {
          if (!success) {
            break;
          }
          http2::DecodeBuffer buffer(slice.data(), slice.size());
          success = success && decoder.DecodeFragment(&buffer);
        }
        success =
            success && decoder.EndDecodingBlock() && !listener.hpack_error();
        if (success) {
          on_payload_(current_stream_, listener.release_header_block());
        }
        // TODO(birenroy): add varz counting metadata decode successes/failures.
        metadata_map_.erase(it);
      }
    }
  }
}

std::unique_ptr<MetadataSerializer::FrameSequence>
MetadataSerializer::FrameSequenceForPayload(SpdyStreamId stream_id,
                                            MetadataPayload payload) {
  return absl::make_unique<MetadataFrameSequence>(stream_id,
                                                  std::move(payload));
}

}  // namespace spdy