Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
http2frames.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:network-protocol
4
6
7#include <QtNetwork/qabstractsocket.h>
8
9#include <algorithm>
10#include <utility>
11
13
14namespace Http2
15{
16
17// HTTP/2 frames are defined by RFC7540, clauses 4 and 6.
18
23
25{
27
28 if (int(buffer[3]) >= int(FrameType::LAST_FRAME_TYPE))
30
31 return FrameType(buffer[3]);
32}
33
35{
37 // RFC 9113, 4.1: 31-bit Stream ID; lastValidStreamID(0x7FFFFFFF) masks out the reserved MSB
39}
40
42{
44 return FrameFlags(buffer[4]);
45}
46
48{
50 return buffer[0] << 16 | buffer[1] << 8 | buffer[2];
51}
52
54{
56
58 return 0;
59
60 switch (type()) {
61 case FrameType::DATA:
63 case FrameType::HEADERS:
65 return buffer[frameHeaderSize];
66 default:
67 return 0;
68 }
69}
70
72{
74
76 return false;
77
78 const uchar *src = &buffer[0] + frameHeaderSize;
80 ++src;
81
83 || type() == FrameType::PRIORITY) {
84 if (streamID)
86 if (weight)
87 *weight = src[4];
88 return true;
89 }
90
91 return false;
92}
93
95{
96 // Should be called only on a frame with
97 // a complete header.
99
100 const auto framePayloadSize = payloadSize();
101 // 4.2 Frame Size
103 return FrameStatus::sizeError;
104
105 switch (type()) {
106 case FrameType::SETTINGS:
107 // SETTINGS ACK can not have any payload.
108 // The payload of a SETTINGS frame consists of zero
109 // or more parameters, each consisting of an unsigned
110 // 16-bit setting identifier and an unsigned 32-bit value.
111 // Thus the payload size must be a multiple of 6.
113 return FrameStatus::sizeError;
114 break;
115 case FrameType::PRIORITY:
116 // 6.3 PRIORITY
117 if (framePayloadSize != 5)
118 return FrameStatus::sizeError;
119 break;
120 case FrameType::PING:
121 // 6.7 PING
122 if (framePayloadSize != 8)
123 return FrameStatus::sizeError;
124 break;
125 case FrameType::GOAWAY:
126 // 6.8 GOAWAY
127 if (framePayloadSize < 8)
128 return FrameStatus::sizeError;
129 break;
130 case FrameType::RST_STREAM:
132 // 6.4 RST_STREAM, 6.9 WINDOW_UPDATE
133 if (framePayloadSize != 4)
134 return FrameStatus::sizeError;
135 break;
137 // 6.6 PUSH_PROMISE
138 if (framePayloadSize < 4)
139 return FrameStatus::sizeError;
140 break;
141 default:
142 // DATA/HEADERS/CONTINUATION will be verified
143 // when we have payload.
144 // Frames of unknown types are ignored (5.1)
145 break;
146 }
147
148 return FrameStatus::goodFrame;
149}
150
152{
153 // Should be called only on a complete frame with a valid header.
155
156 // Ignored, 5.1
158 return FrameStatus::goodFrame;
159
160 auto size = payloadSize();
162
163 const uchar *src = size ? &buffer[0] + frameHeaderSize : nullptr;
164 const auto frameFlags = flags();
165 switch (type()) {
166 // 6.1 DATA, 6.2 HEADERS
167 case FrameType::DATA:
168 case FrameType::HEADERS:
170 if (!size || size < src[0])
171 return FrameStatus::sizeError;
172 size -= src[0];
173 }
175 if (size < 5)
176 return FrameStatus::sizeError;
177 }
178 break;
179 // 6.6 PUSH_PROMISE
182 if (!size || size < src[0])
183 return FrameStatus::sizeError;
184 size -= src[0];
185 }
186
187 if (size < 4)
188 return FrameStatus::sizeError;
189 break;
190 default:
191 break;
192 }
193
194 return FrameStatus::goodFrame;
195}
196
197
199{
201
203 if (flags().testFlag(FrameFlag::PADDED)) {
204 const uchar pad = padding();
205 // + 1 one for a byte with padding number itself:
206 size -= pad + 1;
207 }
208
209 if (priority())
210 size -= 5;
211
212 return size;
213}
214
216{
218
219 const auto frameType = type();
223
226 Q_ASSERT(size >= 4);
227 size -= 4;
228 }
229
230 return size;
231}
232
233const uchar *Frame::dataBegin() const
234{
236 if (buffer.size() <= frameHeaderSize)
237 return nullptr;
238
239 const uchar *src = &buffer[0] + frameHeaderSize;
241 ++src;
242
243 if (priority())
244 src += 5;
245
246 return src;
247}
248
250{
252
253 const auto frameType = type();
257
258 const uchar *begin = dataBegin();
260 begin += 4; // That's a promised stream, skip it.
261 return begin;
262}
263
265{
266 if (offset < frameHeaderSize) {
267 if (!readHeader(socket))
269 }
270
271 const auto status = frame.validateHeader();
272 if (status != FrameStatus::goodFrame) {
276 }
277 return status;
278 }
279
282
285
286 // Reset the offset, our frame can be re-used
287 // now (re-read):
288 offset = 0;
289
290 return frame.validatePayload();
291}
292
294{
296
297 auto &buffer = frame.buffer;
300
301 const auto chunkSize = socket.read(reinterpret_cast<char *>(&buffer[offset]),
303 if (chunkSize > 0)
304 offset += chunkSize;
305
306 return offset == frameHeaderSize;
307}
308
310{
313
314 auto &buffer = frame.buffer;
315 // Casts and ugliness - to deal with MSVC. Values are guaranteed to fit into quint32.
316 const auto chunkSize = socket.read(reinterpret_cast<char *>(&buffer[offset]),
317 qint64(buffer.size() - offset));
318 if (chunkSize > 0)
320
321 return offset == buffer.size();
322}
323
324// Returns true if there is nothing more to discard
326{
327 Q_ASSERT(offset >= frameHeaderSize); // Frame header is already read when this is called
328
329 using namespace Http2;
330 auto frameType = frame.type();
333 return true; // Connection will be closed, nothing needs to be discarded
334
338 while (totalFrameSize > offset) {
340 const auto skipped = socket.skip(remainingSize);
341 if (skipped > 0)
343 else
344 return false;
345 }
346 offset = 0;
347 return true;
348}
349
353
358
364
366{
367 auto &buffer = frame.buffer;
368
370 // The first three bytes - payload size, which is 0 for now.
371 buffer[0] = 0;
372 buffer[1] = 0;
373 buffer[2] = 0;
374
375 buffer[3] = uchar(type);
376 buffer[4] = uchar(flags);
377
379}
380
382{
383 auto &buffer = frame.buffer;
384
387
388 buffer[0] = size >> 16;
389 buffer[1] = size >> 8;
390 buffer[2] = size;
391}
392
398
404
409
410void FrameWriter::append(const uchar *begin, const uchar *end)
411{
412 Q_ASSERT(begin && end);
413 Q_ASSERT(begin < end);
414
417}
418
420{
424}
425
427{
428 auto &buffer = frame.buffer;
430 // Do some sanity check first:
431
434
435 const auto nWritten = socket.write(reinterpret_cast<const char *>(&buffer[0]),
436 buffer.size());
437 return nWritten != -1 && size_type(nWritten) == buffer.size();
438}
439
441{
442 auto &buffer = frame.buffer;
444
447
451 return write(socket);
452 }
453
454 // Our HPACK block does not fit into the size limit, remove
455 // END_HEADERS bit from the first frame, we'll later set
456 // it on the last CONTINUATION frame:
458 // Write a frame's header (not controlled by sizeLimit) and
459 // as many bytes of payload as we can within sizeLimit,
460 // then send CONTINUATION frames, as needed.
463 qint64 written = socket.write(reinterpret_cast<const char *>(&buffer[0]),
465
467 return false;
468
471
472 while (offset != buffer.size()) {
473 const auto chunkSize = std::min(sizeLimit, quint32(buffer.size() - offset));
474 if (chunkSize + offset == buffer.size())
478 return false;
479 written = socket.write(reinterpret_cast<const char *>(&buffer[offset]),
480 chunkSize);
481 if (written != qint64(chunkSize))
482 return false;
483
484 offset += chunkSize;
485 }
486
487 return true;
488}
489
491 const uchar *src, quint32 size)
492{
493 // With DATA frame(s) we always have:
494 // 1) frame's header (9 bytes)
495 // 2) a separate payload (from QNonContiguousByteDevice).
496 // We either fit within a sizeLimit, or split into several
497 // DATA frames.
498
499 Q_ASSERT(src);
500
503 // We NEVER set END_STREAM, since QHttp2ProtocolHandler works with
504 // QNonContiguousByteDevice and this 'writeDATA' is probably
505 // not the last one for a given request.
506 // This has to be done externally (sending an empty DATA frame with END_STREAM).
507 for (quint32 offset = 0; offset != size;) {
508 const auto chunkSize = std::min(size - offset, sizeLimit);
510 // Frame's header first:
511 if (!write(socket))
512 return false;
513 // Payload (if any):
514 if (chunkSize) {
515 const auto written = socket.write(reinterpret_cast<const char*>(src + offset),
516 chunkSize);
517 if (written != qint64(chunkSize))
518 return false;
519 }
520
521 offset += chunkSize;
522 }
523
524 return true;
525}
526
527} // Namespace Http2
528
529QT_END_NAMESPACE
Combined button and popup list for selecting options.