Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
4#include "http2frames_p.h"
5
6#include <QtNetwork/qabstractsocket.h>
7
8#include <algorithm>
9#include <utility>
10
12
13namespace Http2
14{
15
16// HTTP/2 frames are defined by RFC7540, clauses 4 and 6.
17
22
24{
26
27 if (int(buffer[3]) >= int(FrameType::LAST_FRAME_TYPE))
29
30 return FrameType(buffer[3]);
31}
32
34{
36 return qFromBigEndian<quint32>(&buffer[5]);
37}
38
39FrameFlags Frame::flags() const
40{
42 return FrameFlags(buffer[4]);
43}
44
46{
48 return buffer[0] << 16 | buffer[1] << 8 | buffer[2];
49}
50
52{
54
56 return 0;
57
58 switch (type()) {
59 case FrameType::DATA:
63 return buffer[frameHeaderSize];
64 default:
65 return 0;
66 }
67}
68
69bool Frame::priority(quint32 *streamID, uchar *weight) const
70{
72
73 if (buffer.size() <= frameHeaderSize)
74 return false;
75
76 const uchar *src = &buffer[0] + frameHeaderSize;
78 ++src;
79
81 || type() == FrameType::PRIORITY) {
82 if (streamID)
83 *streamID = qFromBigEndian<quint32>(src);
84 if (weight)
85 *weight = src[4];
86 return true;
87 }
88
89 return false;
90}
91
93{
94 // Should be called only on a frame with
95 // a complete header.
97
98 const auto framePayloadSize = payloadSize();
99 // 4.2 Frame Size
100 if (framePayloadSize > maxPayloadSize)
102
103 switch (type()) {
105 // SETTINGS ACK can not have any payload.
106 // The payload of a SETTINGS frame consists of zero
107 // or more parameters, each consisting of an unsigned
108 // 16-bit setting identifier and an unsigned 32-bit value.
109 // Thus the payload size must be a multiple of 6.
110 if (flags().testFlag(FrameFlag::ACK) ? framePayloadSize : framePayloadSize % 6)
112 break;
114 // 6.3 PRIORITY
115 if (framePayloadSize != 5)
117 break;
118 case FrameType::PING:
119 // 6.7 PING
120 if (framePayloadSize != 8)
122 break;
124 // 6.8 GOAWAY
125 if (framePayloadSize < 8)
127 break;
130 // 6.4 RST_STREAM, 6.9 WINDOW_UPDATE
131 if (framePayloadSize != 4)
133 break;
135 // 6.6 PUSH_PROMISE
136 if (framePayloadSize < 4)
138 break;
139 default:
140 // DATA/HEADERS/CONTINUATION will be verified
141 // when we have payload.
142 // Frames of unknown types are ignored (5.1)
143 break;
144 }
145
147}
148
150{
151 // Should be called only on a complete frame with a valid header.
153
154 // Ignored, 5.1
157
158 auto size = payloadSize();
159 Q_ASSERT(buffer.size() >= frameHeaderSize && size == buffer.size() - frameHeaderSize);
160
161 const uchar *src = size ? &buffer[0] + frameHeaderSize : nullptr;
162 const auto frameFlags = flags();
163 switch (type()) {
164 // 6.1 DATA, 6.2 HEADERS
165 case FrameType::DATA:
167 if (frameFlags.testFlag(FrameFlag::PADDED)) {
168 if (!size || size < src[0])
170 size -= src[0];
171 }
172 if (type() == FrameType::HEADERS && frameFlags.testFlag(FrameFlag::PRIORITY)) {
173 if (size < 5)
175 }
176 break;
177 // 6.6 PUSH_PROMISE
179 if (frameFlags.testFlag(FrameFlag::PADDED)) {
180 if (!size || size < src[0])
182 size -= src[0];
183 }
184
185 if (size < 4)
187 break;
188 default:
189 break;
190 }
191
193}
194
195
197{
199
202 const uchar pad = padding();
203 // + 1 one for a byte with padding number itself:
204 size -= pad + 1;
205 }
206
207 if (priority())
208 size -= 5;
209
210 return size;
211}
212
214{
216
217 const auto frameType = type();
218 Q_ASSERT(frameType == FrameType::HEADERS ||
219 frameType == FrameType::PUSH_PROMISE ||
220 frameType == FrameType::CONTINUATION);
221
223 if (frameType == FrameType::PUSH_PROMISE) {
224 Q_ASSERT(size >= 4);
225 size -= 4;
226 }
227
228 return size;
229}
230
232{
234 if (buffer.size() <= frameHeaderSize)
235 return nullptr;
236
237 const uchar *src = &buffer[0] + frameHeaderSize;
239 ++src;
240
241 if (priority())
242 src += 5;
243
244 return src;
245}
246
248{
250
251 const auto frameType = type();
252 Q_ASSERT(frameType == FrameType::HEADERS ||
253 frameType == FrameType::PUSH_PROMISE ||
254 frameType == FrameType::CONTINUATION);
255
256 const uchar *begin = dataBegin();
257 if (frameType == FrameType::PUSH_PROMISE)
258 begin += 4; // That's a promised stream, skip it.
259 return begin;
260}
261
263{
264 if (offset < frameHeaderSize) {
265 if (!readHeader(socket))
267
268 const auto status = frame.validateHeader();
269 if (status != FrameStatus::goodFrame) {
270 // No need to read any payload.
271 return status;
272 }
273
276
277 frame.buffer.resize(frame.payloadSize() + frameHeaderSize);
278 }
279
280 if (offset < frame.buffer.size() && !readPayload(socket))
282
283 // Reset the offset, our frame can be re-used
284 // now (re-read):
285 offset = 0;
286
287 return frame.validatePayload();
288}
289
290bool FrameReader::readHeader(QIODevice &socket)
291{
293
294 auto &buffer = frame.buffer;
295 if (buffer.size() < frameHeaderSize)
296 buffer.resize(frameHeaderSize);
297
298 const auto chunkSize = socket.read(reinterpret_cast<char *>(&buffer[offset]),
300 if (chunkSize > 0)
301 offset += chunkSize;
302
303 return offset == frameHeaderSize;
304}
305
306bool FrameReader::readPayload(QIODevice &socket)
307{
308 Q_ASSERT(offset < frame.buffer.size());
309 Q_ASSERT(frame.buffer.size() > frameHeaderSize);
310
311 auto &buffer = frame.buffer;
312 // Casts and ugliness - to deal with MSVC. Values are guaranteed to fit into quint32.
313 const auto chunkSize = socket.read(reinterpret_cast<char *>(&buffer[offset]),
314 qint64(buffer.size() - offset));
315 if (chunkSize > 0)
316 offset += quint32(chunkSize);
317
318 return offset == buffer.size();
319}
320
324
326{
327 start(type, flags, streamID);
328}
329
331{
332 frame = std::move(newFrame);
333 updatePayloadSize();
334}
335
336void FrameWriter::start(FrameType type, FrameFlags flags, quint32 streamID)
337{
338 auto &buffer = frame.buffer;
339
340 buffer.resize(frameHeaderSize);
341 // The first three bytes - payload size, which is 0 for now.
342 buffer[0] = 0;
343 buffer[1] = 0;
344 buffer[2] = 0;
345
346 buffer[3] = uchar(type);
347 buffer[4] = uchar(flags);
348
349 qToBigEndian(streamID, &buffer[5]);
350}
351
353{
354 auto &buffer = frame.buffer;
355
358
359 buffer[0] = size >> 16;
360 buffer[1] = size >> 8;
361 buffer[2] = size;
362}
363
365{
366 Q_ASSERT(frame.buffer.size() >= frameHeaderSize);
367 frame.buffer[3] = uchar(type);
368}
369
371{
372 Q_ASSERT(frame.buffer.size() >= frameHeaderSize);
373 frame.buffer[4] = uchar(flags);
374}
375
377{
378 setFlags(frame.flags() | flag);
379}
380
382{
383 Q_ASSERT(begin && end);
384 Q_ASSERT(begin < end);
385
386 frame.buffer.insert(frame.buffer.end(), begin, end);
387 updatePayloadSize();
388}
389
390void FrameWriter::updatePayloadSize()
391{
392 const quint32 size = quint32(frame.buffer.size() - frameHeaderSize);
395}
396
398{
399 auto &buffer = frame.buffer;
401 // Do some sanity check first:
402
403 Q_ASSERT(int(frame.type()) < int(FrameType::LAST_FRAME_TYPE));
405
406 const auto nWritten = socket.write(reinterpret_cast<const char *>(&buffer[0]),
407 buffer.size());
408 return nWritten != -1 && size_type(nWritten) == buffer.size();
409}
410
412{
413 auto &buffer = frame.buffer;
415
416 if (sizeLimit > quint32(maxPayloadSize))
417 sizeLimit = quint32(maxPayloadSize);
418
419 if (quint32(buffer.size() - frameHeaderSize) <= sizeLimit) {
421 updatePayloadSize();
422 return write(socket);
423 }
424
425 // Our HPACK block does not fit into the size limit, remove
426 // END_HEADERS bit from the first frame, we'll later set
427 // it on the last CONTINUATION frame:
428 setFlags(frame.flags() & ~FrameFlags(FrameFlag::END_HEADERS));
429 // Write a frame's header (not controlled by sizeLimit) and
430 // as many bytes of payload as we can within sizeLimit,
431 // then send CONTINUATION frames, as needed.
432 setPayloadSize(sizeLimit);
433 const quint32 firstChunkSize = frameHeaderSize + sizeLimit;
434 qint64 written = socket.write(reinterpret_cast<const char *>(&buffer[0]),
435 firstChunkSize);
436
437 if (written != qint64(firstChunkSize))
438 return false;
439
440 FrameWriter continuationWriter(FrameType::CONTINUATION, FrameFlag::EMPTY, frame.streamID());
441 quint32 offset = firstChunkSize;
442
443 while (offset != buffer.size()) {
444 const auto chunkSize = std::min(sizeLimit, quint32(buffer.size() - offset));
445 if (chunkSize + offset == buffer.size())
446 continuationWriter.addFlag(FrameFlag::END_HEADERS);
447 continuationWriter.setPayloadSize(chunkSize);
448 if (!continuationWriter.write(socket))
449 return false;
450 written = socket.write(reinterpret_cast<const char *>(&buffer[offset]),
451 chunkSize);
452 if (written != qint64(chunkSize))
453 return false;
454
455 offset += chunkSize;
456 }
457
458 return true;
459}
460
462 const uchar *src, quint32 size)
463{
464 // With DATA frame(s) we always have:
465 // 1) frame's header (9 bytes)
466 // 2) a separate payload (from QNonContiguousByteDevice).
467 // We either fit within a sizeLimit, or split into several
468 // DATA frames.
469
470 Q_ASSERT(src);
471
472 if (sizeLimit > quint32(maxPayloadSize))
473 sizeLimit = quint32(maxPayloadSize);
474 // We NEVER set END_STREAM, since QHttp2ProtocolHandler works with
475 // QNonContiguousByteDevice and this 'writeDATA' is probably
476 // not the last one for a given request.
477 // This has to be done externally (sending an empty DATA frame with END_STREAM).
478 for (quint32 offset = 0; offset != size;) {
479 const auto chunkSize = std::min(size - offset, sizeLimit);
480 setPayloadSize(chunkSize);
481 // Frame's header first:
482 if (!write(socket))
483 return false;
484 // Payload (if any):
485 if (chunkSize) {
486 const auto written = socket.write(reinterpret_cast<const char*>(src + offset),
487 chunkSize);
488 if (written != qint64(chunkSize))
489 return false;
490 }
491
492 offset += chunkSize;
493 }
494
495 return true;
496}
497
498} // Namespace Http2
499
FrameStatus read(QIODevice &socket)
payload_type::size_type size_type
void setPayloadSize(quint32 size)
bool writeHEADERS(QIODevice &socket, quint32 sizeLimit)
void addFlag(FrameFlag flag)
void append(ValueType val)
void setOutboundFrame(Frame &&newFrame)
bool writeDATA(QIODevice &socket, quint32 sizeLimit, const uchar *src, quint32 size)
void setType(FrameType type)
void setFlags(FrameFlags flags)
void start(FrameType type, FrameFlags flags, quint32 streamID)
bool write(QIODevice &socket) const
\inmodule QtCore \reentrant
Definition qiodevice.h:34
qint64 write(const char *data, qint64 len)
Writes at most maxSize bytes of data from data to the device.
qint64 read(char *data, qint64 maxlen)
Reads at most maxSize bytes from the device into data, and returns the number of bytes read.
@ frameHeaderSize
Combined button and popup list for selecting options.
constexpr T qToBigEndian(T source)
Definition qendian.h:172
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint GLuint end
GLuint GLuint GLfloat weight
GLenum src
GLenum GLuint buffer
GLenum type
GLbitfield flags
GLuint start
GLenum GLuint GLintptr offset
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QtPrivate::QRegularExpressionMatchIteratorRangeBasedForIterator begin(const QRegularExpressionMatchIterator &iterator)
unsigned int quint32
Definition qtypes.h:50
unsigned char uchar
Definition qtypes.h:32
long long qint64
Definition qtypes.h:60
bool testFlag(MaskType mask, FlagType flag)
QTcpSocket * socket
[1]
FrameStatus validateHeader() const
bool priority(quint32 *streamID=nullptr, uchar *weight=nullptr) const
FrameStatus validatePayload() const
uchar padding() const
const uchar * dataBegin() const
quint32 hpackBlockSize() const
quint32 payloadSize() const
quint32 streamID() const
quint32 dataSize() const
const uchar * hpackBlockBegin() const
std::vector< uchar > buffer
FrameType type() const
FrameFlags flags() const