summaryrefslogtreecommitdiff
path: root/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.h
blob: 2128728e9d1c54f264d44e4e9da7e88b2cc3c440 (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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
// Copyright 2016 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.

#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_H_
#define QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_H_

// HpackStringDecoder decodes strings encoded per the HPACK spec; this does
// not mean decompressing Huffman encoded strings, just identifying the length,
// encoding and contents for a listener.

#include <stddef.h>

#include <algorithm>
#include <cstdint>

#include "base/logging.h"
#include "base/macros.h"
#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h"
#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"

namespace http2 {

// Decodes a single string in an HPACK header entry. The high order bit of
// the first byte of the length is the H (Huffman) bit indicating whether
// the value is Huffman encoded, and the remainder of the byte is the first
// 7 bits of an HPACK varint.
//
// Call Start() to begin decoding; if it returns kDecodeInProgress, then call
// Resume() when more input is available, repeating until kDecodeInProgress is
// not returned. If kDecodeDone or kDecodeError is returned, then Resume() must
// not be called until Start() has been called to start decoding a new string.
class HTTP2_EXPORT_PRIVATE HpackStringDecoder {
 public:
  enum StringDecoderState {
    kStartDecodingLength,
    kDecodingString,
    kResumeDecodingLength,
  };

  template <class Listener>
  DecodeStatus Start(DecodeBuffer* db, Listener* cb) {
    // Fast decode path is used if the string is under 127 bytes and the
    // entire length of the string is in the decode buffer. More than 83% of
    // string lengths are encoded in just one byte.
    if (db->HasData() && (*db->cursor() & 0x7f) != 0x7f) {
      // The string is short.
      uint8_t h_and_prefix = db->DecodeUInt8();
      uint8_t length = h_and_prefix & 0x7f;
      bool huffman_encoded = (h_and_prefix & 0x80) == 0x80;
      cb->OnStringStart(huffman_encoded, length);
      if (length <= db->Remaining()) {
        // Yeah, we've got the whole thing in the decode buffer.
        // Ideally this will be the common case. Note that we don't
        // update any of the member variables in this path.
        cb->OnStringData(db->cursor(), length);
        db->AdvanceCursor(length);
        cb->OnStringEnd();
        return DecodeStatus::kDecodeDone;
      }
      // Not all in the buffer.
      huffman_encoded_ = huffman_encoded;
      remaining_ = length;
      // Call Resume to decode the string body, which is only partially
      // in the decode buffer (or not at all).
      state_ = kDecodingString;
      return Resume(db, cb);
    }
    // Call Resume to decode the string length, which is either not in
    // the decode buffer, or spans multiple bytes.
    state_ = kStartDecodingLength;
    return Resume(db, cb);
  }

  template <class Listener>
  DecodeStatus Resume(DecodeBuffer* db, Listener* cb) {
    DecodeStatus status;
    while (true) {
      switch (state_) {
        case kStartDecodingLength:
          DVLOG(2) << "kStartDecodingLength: db->Remaining=" << db->Remaining();
          if (!StartDecodingLength(db, cb, &status)) {
            // The length is split across decode buffers.
            return status;
          }
          // We've finished decoding the length, which spanned one or more
          // bytes. Approximately 17% of strings have a length that is greater
          // than 126 bytes, and thus the length is encoded in more than one
          // byte, and so doesn't get the benefit of the optimization in
          // Start() for single byte lengths. But, we still expect that most
          // of such strings will be contained entirely in a single decode
          // buffer, and hence this fall through skips another trip through the
          // switch above and more importantly skips setting the state_ variable
          // again in those cases where we don't need it.
          HTTP2_FALLTHROUGH;

        case kDecodingString:
          DVLOG(2) << "kDecodingString: db->Remaining=" << db->Remaining()
                   << "    remaining_=" << remaining_;
          return DecodeString(db, cb);

        case kResumeDecodingLength:
          DVLOG(2) << "kResumeDecodingLength: db->Remaining="
                   << db->Remaining();
          if (!ResumeDecodingLength(db, cb, &status)) {
            return status;
          }
      }
    }
  }

  Http2String DebugString() const;

 private:
  static Http2String StateToString(StringDecoderState v);

  // Returns true if the length is fully decoded and the listener wants the
  // decoding to continue, false otherwise; status is set to the status from
  // the varint decoder.
  // If the length is not fully decoded, case state_ is set appropriately
  // for the next call to Resume.
  template <class Listener>
  bool StartDecodingLength(DecodeBuffer* db,
                           Listener* cb,
                           DecodeStatus* status) {
    if (db->Empty()) {
      *status = DecodeStatus::kDecodeInProgress;
      state_ = kStartDecodingLength;
      return false;
    }
    uint8_t h_and_prefix = db->DecodeUInt8();
    huffman_encoded_ = (h_and_prefix & 0x80) == 0x80;
    *status = length_decoder_.Start(h_and_prefix, 7, db);
    if (*status == DecodeStatus::kDecodeDone) {
      OnStringStart(cb, status);
      return true;
    }
    // Set the state to cover the DecodeStatus::kDecodeInProgress case.
    // Won't be needed if the status is kDecodeError.
    state_ = kResumeDecodingLength;
    return false;
  }

  // Returns true if the length is fully decoded and the listener wants the
  // decoding to continue, false otherwise; status is set to the status from
  // the varint decoder; state_ is updated when fully decoded.
  // If the length is not fully decoded, case state_ is set appropriately
  // for the next call to Resume.
  template <class Listener>
  bool ResumeDecodingLength(DecodeBuffer* db,
                            Listener* cb,
                            DecodeStatus* status) {
    DCHECK_EQ(state_, kResumeDecodingLength);
    *status = length_decoder_.Resume(db);
    if (*status == DecodeStatus::kDecodeDone) {
      state_ = kDecodingString;
      OnStringStart(cb, status);
      return true;
    }
    return false;
  }

  // Returns true if the listener wants the decoding to continue, and
  // false otherwise, in which case status set.
  template <class Listener>
  void OnStringStart(Listener* cb, DecodeStatus* status) {
    // TODO(vasilvv): fail explicitly in case of truncation.
    remaining_ = static_cast<size_t>(length_decoder_.value());
    // Make callback so consumer knows what is coming.
    cb->OnStringStart(huffman_encoded_, remaining_);
  }

  // Passes the available portion of the string to the listener, and signals
  // the end of the string when it is reached. Returns kDecodeDone or
  // kDecodeInProgress as appropriate.
  template <class Listener>
  DecodeStatus DecodeString(DecodeBuffer* db, Listener* cb) {
    size_t len = std::min(remaining_, db->Remaining());
    if (len > 0) {
      cb->OnStringData(db->cursor(), len);
      db->AdvanceCursor(len);
      remaining_ -= len;
    }
    if (remaining_ == 0) {
      cb->OnStringEnd();
      return DecodeStatus::kDecodeDone;
    }
    state_ = kDecodingString;
    return DecodeStatus::kDecodeInProgress;
  }

  HpackVarintDecoder length_decoder_;

  // These fields are initialized just to keep ASAN happy about reading
  // them from DebugString().
  size_t remaining_ = 0;
  StringDecoderState state_ = kStartDecodingLength;
  bool huffman_encoded_ = false;
};

HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
                                              const HpackStringDecoder& v);

}  // namespace http2
#endif  // QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_H_