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
57 if (!isPadded())
58 return 0;
59
61 return buffer[frameHeaderSize];
62}
63
64bool Frame::isPadded() const
65{
67}
68
70{
71 switch (type()) {
72 case FrameType::DATA:
74 case FrameType::HEADERS:
75 return true;
76 default:
77 break;
78 }
79 return false;
80}
81
83{
85
87 return false;
88
89 const uchar *src = &buffer[0] + frameHeaderSize;
91 ++src;
92
94 || type() == FrameType::PRIORITY) {
95 if (streamID)
97 if (weight)
98 *weight = src[4];
99 return true;
100 }
101
102 return false;
103}
104
106{
107 // Should be called only on a frame with
108 // a complete header.
110
111 const auto framePayloadSize = payloadSize();
112 // 4.2 Frame Size
114 return FrameStatus::sizeError;
115
116 switch (type()) {
117 case FrameType::SETTINGS:
118 // SETTINGS ACK can not have any payload.
119 // The payload of a SETTINGS frame consists of zero
120 // or more parameters, each consisting of an unsigned
121 // 16-bit setting identifier and an unsigned 32-bit value.
122 // Thus the payload size must be a multiple of 6.
124 return FrameStatus::sizeError;
125 break;
126 case FrameType::PRIORITY:
127 // 6.3 PRIORITY
128 if (framePayloadSize != 5)
129 return FrameStatus::sizeError;
130 break;
131 case FrameType::PING:
132 // 6.7 PING
133 if (framePayloadSize != 8)
134 return FrameStatus::sizeError;
135 break;
136 case FrameType::GOAWAY:
137 // 6.8 GOAWAY
138 if (framePayloadSize < 8)
139 return FrameStatus::sizeError;
140 break;
141 case FrameType::RST_STREAM:
143 // 6.4 RST_STREAM, 6.9 WINDOW_UPDATE
144 if (framePayloadSize != 4)
145 return FrameStatus::sizeError;
146 break;
148 // 6.6 PUSH_PROMISE
149 if (framePayloadSize < 4)
150 return FrameStatus::sizeError;
151 break;
152 default:
153 // DATA/HEADERS/CONTINUATION will be verified
154 // when we have payload.
155 // Frames of unknown types are ignored (5.1)
156 break;
157 }
158
159 return FrameStatus::goodFrame;
160}
161
163{
164 // Should be called only on a complete frame with a valid header.
166
167 // Ignored, 5.1
169 return FrameStatus::goodFrame;
170
171 auto size = payloadSize();
173
174 const uchar *src = size ? &buffer[0] + frameHeaderSize : nullptr;
175 const auto frameFlags = flags();
176 switch (type()) {
177 // 6.1 DATA, 6.2 HEADERS
178 case FrameType::DATA:
179 case FrameType::HEADERS:
181 // size must cover the 1-byte pad-length field plus src[0] padding bytes
182 if (!size || size - 1 < src[0])
183 return FrameStatus::sizeError;
184 size -= src[0] + 1;
185 }
187 if (size < 5)
188 return FrameStatus::sizeError;
189 }
190 break;
191 // 6.6 PUSH_PROMISE
194 // size must cover the 1-byte pad-length field plus src[0] padding bytes
195 if (!size || size - 1 < src[0])
196 return FrameStatus::sizeError;
197 size -= src[0] + 1;
198 }
199
200 if (size < 4)
201 return FrameStatus::sizeError;
202 break;
203 default:
204 break;
205 }
206
207 return FrameStatus::goodFrame;
208}
209
210
212{
214
216 if (isPadded()) {
217 const uchar pad = padding();
218 // + 1 one for a byte with padding number itself:
219 size -= pad + 1;
220 }
221
222 if (priority())
223 size -= 5;
224
225 return size;
226}
227
229{
231
232 const auto frameType = type();
236
239 Q_ASSERT(size >= 4);
240 size -= 4;
241 }
242
243 return size;
244}
245
246const uchar *Frame::dataBegin() const
247{
249 if (buffer.size() <= frameHeaderSize)
250 return nullptr;
251
252 const uchar *src = &buffer[0] + frameHeaderSize;
253 if (isPadded())
254 ++src;
255
256 if (priority())
257 src += 5;
258
259 return src;
260}
261
263{
265
266 const auto frameType = type();
270
271 const uchar *begin = dataBegin();
273 begin += 4; // That's a promised stream, skip it.
274 return begin;
275}
276
278{
279 if (offset < frameHeaderSize) {
280 if (!readHeader(socket))
282 }
283
284 const auto status = frame.validateHeader();
285 if (status != FrameStatus::goodFrame) {
289 }
290 return status;
291 }
292
295
298
299 // Reset the offset, our frame can be re-used
300 // now (re-read):
301 offset = 0;
302
303 return frame.validatePayload();
304}
305
307{
309
310 auto &buffer = frame.buffer;
313
314 const auto chunkSize = socket.read(reinterpret_cast<char *>(&buffer[offset]),
316 if (chunkSize > 0)
317 offset += chunkSize;
318
319 return offset == frameHeaderSize;
320}
321
323{
326
327 auto &buffer = frame.buffer;
328 // Casts and ugliness - to deal with MSVC. Values are guaranteed to fit into quint32.
329 const auto chunkSize = socket.read(reinterpret_cast<char *>(&buffer[offset]),
330 qint64(buffer.size() - offset));
331 if (chunkSize > 0)
333
334 return offset == buffer.size();
335}
336
337// Returns true if there is nothing more to discard
339{
340 Q_ASSERT(offset >= frameHeaderSize); // Frame header is already read when this is called
341
342 using namespace Http2;
343 auto frameType = frame.type();
346 return true; // Connection will be closed, nothing needs to be discarded
347
351 while (totalFrameSize > offset) {
353 const auto skipped = socket.skip(remainingSize);
354 if (skipped > 0)
356 else
357 return false;
358 }
359 offset = 0;
360 return true;
361}
362
366
371
377
379{
380 auto &buffer = frame.buffer;
381
383 // The first three bytes - payload size, which is 0 for now.
384 buffer[0] = 0;
385 buffer[1] = 0;
386 buffer[2] = 0;
387
388 buffer[3] = uchar(type);
389 buffer[4] = uchar(flags);
390
392}
393
395{
396 auto &buffer = frame.buffer;
397
400
401 buffer[0] = size >> 16;
402 buffer[1] = size >> 8;
403 buffer[2] = size;
404}
405
411
417
422
423void FrameWriter::append(const uchar *begin, const uchar *end)
424{
425 Q_ASSERT(begin && end);
426 Q_ASSERT(begin < end);
427
430}
431
433{
437}
438
440{
441 auto &buffer = frame.buffer;
443 // Do some sanity check first:
444
447
448 const auto nWritten = socket.write(reinterpret_cast<const char *>(&buffer[0]),
449 buffer.size());
450 return nWritten != -1 && size_type(nWritten) == buffer.size();
451}
452
454{
455 auto &buffer = frame.buffer;
457
460
464 return write(socket);
465 }
466
467 // Our HPACK block does not fit into the size limit, remove
468 // END_HEADERS bit from the first frame, we'll later set
469 // it on the last CONTINUATION frame:
471 // Write a frame's header (not controlled by sizeLimit) and
472 // as many bytes of payload as we can within sizeLimit,
473 // then send CONTINUATION frames, as needed.
476 qint64 written = socket.write(reinterpret_cast<const char *>(&buffer[0]),
478
480 return false;
481
484
485 while (offset != buffer.size()) {
486 const auto chunkSize = std::min(sizeLimit, quint32(buffer.size() - offset));
487 if (chunkSize + offset == buffer.size())
491 return false;
492 written = socket.write(reinterpret_cast<const char *>(&buffer[offset]),
493 chunkSize);
494 if (written != qint64(chunkSize))
495 return false;
496
497 offset += chunkSize;
498 }
499
500 return true;
501}
502
504 const uchar *src, quint32 size)
505{
506 // With DATA frame(s) we always have:
507 // 1) frame's header (9 bytes)
508 // 2) a separate payload (from QNonContiguousByteDevice).
509 // We either fit within a sizeLimit, or split into several
510 // DATA frames.
511
512 Q_ASSERT(src);
513
516 // We NEVER set END_STREAM, since QHttp2ProtocolHandler works with
517 // QNonContiguousByteDevice and this 'writeDATA' is probably
518 // not the last one for a given request.
519 // This has to be done externally (sending an empty DATA frame with END_STREAM).
520 for (quint32 offset = 0; offset != size;) {
521 const auto chunkSize = std::min(size - offset, sizeLimit);
523 // Frame's header first:
524 if (!write(socket))
525 return false;
526 // Payload (if any):
527 if (chunkSize) {
528 const auto written = socket.write(reinterpret_cast<const char*>(src + offset),
529 chunkSize);
530 if (written != qint64(chunkSize))
531 return false;
532 }
533
534 offset += chunkSize;
535 }
536
537 return true;
538}
539
540} // Namespace Http2
541
542QT_END_NAMESPACE
Combined button and popup list for selecting options.