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
11QT_BEGIN_NAMESPACE
12
13namespace Http2
14{
15
16// HTTP/2 frames are defined by RFC7540, clauses 4 and 6.
17
18Frame::Frame()
19 : buffer(frameHeaderSize)
20{
21}
22
23FrameType Frame::type() const
24{
25 Q_ASSERT(buffer.size() >= frameHeaderSize);
26
27 if (int(buffer[3]) >= int(FrameType::LAST_FRAME_TYPE))
28 return FrameType::LAST_FRAME_TYPE;
29
30 return FrameType(buffer[3]);
31}
32
33quint32 Frame::streamID() const
34{
35 Q_ASSERT(buffer.size() >= frameHeaderSize);
36 return qFromBigEndian<quint32>(src: &buffer[5]);
37}
38
39FrameFlags Frame::flags() const
40{
41 Q_ASSERT(buffer.size() >= frameHeaderSize);
42 return FrameFlags(buffer[4]);
43}
44
45quint32 Frame::payloadSize() const
46{
47 Q_ASSERT(buffer.size() >= frameHeaderSize);
48 return buffer[0] << 16 | buffer[1] << 8 | buffer[2];
49}
50
51uchar Frame::padding() const
52{
53 Q_ASSERT(validateHeader() == FrameStatus::goodFrame);
54
55 if (!flags().testFlag(flag: FrameFlag::PADDED))
56 return 0;
57
58 switch (type()) {
59 case FrameType::DATA:
60 case FrameType::PUSH_PROMISE:
61 case FrameType::HEADERS:
62 Q_ASSERT(buffer.size() > frameHeaderSize);
63 return buffer[frameHeaderSize];
64 default:
65 return 0;
66 }
67}
68
69bool Frame::priority(quint32 *streamID, uchar *weight) const
70{
71 Q_ASSERT(validatePayload() == FrameStatus::goodFrame);
72
73 if (buffer.size() <= frameHeaderSize)
74 return false;
75
76 const uchar *src = &buffer[0] + frameHeaderSize;
77 if (type() == FrameType::HEADERS && flags().testFlag(flag: FrameFlag::PADDED))
78 ++src;
79
80 if ((type() == FrameType::HEADERS && flags().testFlag(flag: FrameFlag::PRIORITY))
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
92FrameStatus Frame::validateHeader() const
93{
94 // Should be called only on a frame with
95 // a complete header.
96 Q_ASSERT(buffer.size() >= frameHeaderSize);
97
98 const auto framePayloadSize = payloadSize();
99 // 4.2 Frame Size
100 if (framePayloadSize > maxPayloadSize)
101 return FrameStatus::sizeError;
102
103 switch (type()) {
104 case FrameType::SETTINGS:
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(flag: FrameFlag::ACK) ? framePayloadSize : framePayloadSize % 6)
111 return FrameStatus::sizeError;
112 break;
113 case FrameType::PRIORITY:
114 // 6.3 PRIORITY
115 if (framePayloadSize != 5)
116 return FrameStatus::sizeError;
117 break;
118 case FrameType::PING:
119 // 6.7 PING
120 if (framePayloadSize != 8)
121 return FrameStatus::sizeError;
122 break;
123 case FrameType::GOAWAY:
124 // 6.8 GOAWAY
125 if (framePayloadSize < 8)
126 return FrameStatus::sizeError;
127 break;
128 case FrameType::RST_STREAM:
129 case FrameType::WINDOW_UPDATE:
130 // 6.4 RST_STREAM, 6.9 WINDOW_UPDATE
131 if (framePayloadSize != 4)
132 return FrameStatus::sizeError;
133 break;
134 case FrameType::PUSH_PROMISE:
135 // 6.6 PUSH_PROMISE
136 if (framePayloadSize < 4)
137 return FrameStatus::sizeError;
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
146 return FrameStatus::goodFrame;
147}
148
149FrameStatus Frame::validatePayload() const
150{
151 // Should be called only on a complete frame with a valid header.
152 Q_ASSERT(validateHeader() == FrameStatus::goodFrame);
153
154 // Ignored, 5.1
155 if (type() == FrameType::LAST_FRAME_TYPE)
156 return FrameStatus::goodFrame;
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:
166 case FrameType::HEADERS:
167 if (frameFlags.testFlag(flag: FrameFlag::PADDED)) {
168 if (!size || size < src[0])
169 return FrameStatus::sizeError;
170 size -= src[0];
171 }
172 if (type() == FrameType::HEADERS && frameFlags.testFlag(flag: FrameFlag::PRIORITY)) {
173 if (size < 5)
174 return FrameStatus::sizeError;
175 }
176 break;
177 // 6.6 PUSH_PROMISE
178 case FrameType::PUSH_PROMISE:
179 if (frameFlags.testFlag(flag: FrameFlag::PADDED)) {
180 if (!size || size < src[0])
181 return FrameStatus::sizeError;
182 size -= src[0];
183 }
184
185 if (size < 4)
186 return FrameStatus::sizeError;
187 break;
188 default:
189 break;
190 }
191
192 return FrameStatus::goodFrame;
193}
194
195
196quint32 Frame::dataSize() const
197{
198 Q_ASSERT(validatePayload() == FrameStatus::goodFrame);
199
200 quint32 size = payloadSize();
201 if (flags().testFlag(flag: FrameFlag::PADDED)) {
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
213quint32 Frame::hpackBlockSize() const
214{
215 Q_ASSERT(validatePayload() == FrameStatus::goodFrame);
216
217 const auto frameType = type();
218 Q_ASSERT(frameType == FrameType::HEADERS ||
219 frameType == FrameType::PUSH_PROMISE ||
220 frameType == FrameType::CONTINUATION);
221
222 quint32 size = dataSize();
223 if (frameType == FrameType::PUSH_PROMISE) {
224 Q_ASSERT(size >= 4);
225 size -= 4;
226 }
227
228 return size;
229}
230
231const uchar *Frame::dataBegin() const
232{
233 Q_ASSERT(validatePayload() == FrameStatus::goodFrame);
234 if (buffer.size() <= frameHeaderSize)
235 return nullptr;
236
237 const uchar *src = &buffer[0] + frameHeaderSize;
238 if (flags().testFlag(flag: FrameFlag::PADDED))
239 ++src;
240
241 if (priority())
242 src += 5;
243
244 return src;
245}
246
247const uchar *Frame::hpackBlockBegin() const
248{
249 Q_ASSERT(validatePayload() == FrameStatus::goodFrame);
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
262FrameStatus FrameReader::read(QIODevice &socket)
263{
264 if (offset < frameHeaderSize) {
265 if (!readHeader(socket))
266 return FrameStatus::incompleteFrame;
267
268 const auto status = frame.validateHeader();
269 if (status != FrameStatus::goodFrame) {
270 // No need to read any payload.
271 return status;
272 }
273
274 if (Http2PredefinedParameters::maxPayloadSize < frame.payloadSize())
275 return FrameStatus::sizeError;
276
277 frame.buffer.resize(new_size: frame.payloadSize() + frameHeaderSize);
278 }
279
280 if (offset < frame.buffer.size() && !readPayload(socket))
281 return FrameStatus::incompleteFrame;
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{
292 Q_ASSERT(offset < frameHeaderSize);
293
294 auto &buffer = frame.buffer;
295 if (buffer.size() < frameHeaderSize)
296 buffer.resize(new_size: frameHeaderSize);
297
298 const auto chunkSize = socket.read(data: reinterpret_cast<char *>(&buffer[offset]),
299 maxlen: frameHeaderSize - 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(data: reinterpret_cast<char *>(&buffer[offset]),
314 maxlen: qint64(buffer.size() - offset));
315 if (chunkSize > 0)
316 offset += quint32(chunkSize);
317
318 return offset == buffer.size();
319}
320
321FrameWriter::FrameWriter()
322{
323}
324
325FrameWriter::FrameWriter(FrameType type, FrameFlags flags, quint32 streamID)
326{
327 start(type, flags, streamID);
328}
329
330void FrameWriter::setOutboundFrame(Frame &&newFrame)
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(new_size: 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(src: streamID, dest: &buffer[5]);
350}
351
352void FrameWriter::setPayloadSize(quint32 size)
353{
354 auto &buffer = frame.buffer;
355
356 Q_ASSERT(buffer.size() >= frameHeaderSize);
357 Q_ASSERT(size <= maxPayloadSize);
358
359 buffer[0] = size >> 16;
360 buffer[1] = size >> 8;
361 buffer[2] = size;
362}
363
364void FrameWriter::setType(FrameType type)
365{
366 Q_ASSERT(frame.buffer.size() >= frameHeaderSize);
367 frame.buffer[3] = uchar(type);
368}
369
370void FrameWriter::setFlags(FrameFlags flags)
371{
372 Q_ASSERT(frame.buffer.size() >= frameHeaderSize);
373 frame.buffer[4] = uchar(flags);
374}
375
376void FrameWriter::addFlag(FrameFlag flag)
377{
378 setFlags(frame.flags() | flag);
379}
380
381void FrameWriter::append(const uchar *begin, const uchar *end)
382{
383 Q_ASSERT(begin && end);
384 Q_ASSERT(begin < end);
385
386 frame.buffer.insert(position: frame.buffer.end(), first: begin, last: end);
387 updatePayloadSize();
388}
389
390void FrameWriter::updatePayloadSize()
391{
392 const quint32 size = quint32(frame.buffer.size() - frameHeaderSize);
393 Q_ASSERT(size <= maxPayloadSize);
394 setPayloadSize(size);
395}
396
397bool FrameWriter::write(QIODevice &socket) const
398{
399 auto &buffer = frame.buffer;
400 Q_ASSERT(buffer.size() >= frameHeaderSize);
401 // Do some sanity check first:
402
403 Q_ASSERT(int(frame.type()) < int(FrameType::LAST_FRAME_TYPE));
404 Q_ASSERT(frame.validateHeader() == FrameStatus::goodFrame);
405
406 const auto nWritten = socket.write(data: reinterpret_cast<const char *>(&buffer[0]),
407 len: buffer.size());
408 return nWritten != -1 && size_type(nWritten) == buffer.size();
409}
410
411bool FrameWriter::writeHEADERS(QIODevice &socket, quint32 sizeLimit)
412{
413 auto &buffer = frame.buffer;
414 Q_ASSERT(buffer.size() >= frameHeaderSize);
415
416 if (sizeLimit > quint32(maxPayloadSize))
417 sizeLimit = quint32(maxPayloadSize);
418
419 if (quint32(buffer.size() - frameHeaderSize) <= sizeLimit) {
420 addFlag(flag: FrameFlag::END_HEADERS);
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(data: reinterpret_cast<const char *>(&buffer[0]),
435 len: 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(a: sizeLimit, b: quint32(buffer.size() - offset));
445 if (chunkSize + offset == buffer.size())
446 continuationWriter.addFlag(flag: FrameFlag::END_HEADERS);
447 continuationWriter.setPayloadSize(chunkSize);
448 if (!continuationWriter.write(socket))
449 return false;
450 written = socket.write(data: reinterpret_cast<const char *>(&buffer[offset]),
451 len: chunkSize);
452 if (written != qint64(chunkSize))
453 return false;
454
455 offset += chunkSize;
456 }
457
458 return true;
459}
460
461bool FrameWriter::writeDATA(QIODevice &socket, quint32 sizeLimit,
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(a: size - offset, b: 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(data: reinterpret_cast<const char*>(src + offset),
487 len: 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
500QT_END_NAMESPACE
501

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtbase/src/network/access/http2/http2frames.cpp