From b2c0f9713c1c39bc3214df49f0d2b44bcc84d790 Mon Sep 17 00:00:00 2001 From: Timur Pocheptsov Date: Tue, 12 Jan 2016 14:45:47 +0100 Subject: HTTP2 protocol handler Add HTTP2 support in QNAM - QHttp2ProtocolHandler + aux. classes. [ChangeLog][QtNetwork] Added support for HTTP/2 protocol Task-number: QTBUG-50956 Change-Id: I3a119cfbb1fb3118d9003225f5e54b46ae2829b6 Reviewed-by: Edward Welbourne --- src/network/access/http2/http2.pri | 10 +- src/network/access/http2/http2frames.cpp | 525 +++++++++++++++++++++++++++++ src/network/access/http2/http2frames_p.h | 173 ++++++++++ src/network/access/http2/http2protocol.cpp | 156 +++++++++ src/network/access/http2/http2protocol_p.h | 164 +++++++++ src/network/access/http2/http2streams.cpp | 100 ++++++ src/network/access/http2/http2streams_p.h | 92 +++++ 7 files changed, 1218 insertions(+), 2 deletions(-) create mode 100644 src/network/access/http2/http2frames.cpp create mode 100644 src/network/access/http2/http2frames_p.h create mode 100644 src/network/access/http2/http2protocol.cpp create mode 100644 src/network/access/http2/http2protocol_p.h create mode 100644 src/network/access/http2/http2streams.cpp create mode 100644 src/network/access/http2/http2streams_p.h (limited to 'src/network/access/http2') diff --git a/src/network/access/http2/http2.pri b/src/network/access/http2/http2.pri index 2157e35e2f..e9f30aeb4a 100644 --- a/src/network/access/http2/http2.pri +++ b/src/network/access/http2/http2.pri @@ -2,10 +2,16 @@ HEADERS += \ access/http2/bitstreams_p.h \ access/http2/huffman_p.h \ access/http2/hpack_p.h \ - access/http2/hpacktable_p.h + access/http2/hpacktable_p.h \ + access/http2/http2frames_p.h \ + access/http2/http2streams_p.h \ + access/http2/http2protocol_p.h SOURCES += \ access/http2/bitstreams.cpp \ access/http2/huffman.cpp \ access/http2/hpack.cpp \ - access/http2/hpacktable.cpp + access/http2/hpacktable.cpp \ + access/http2/http2frames.cpp \ + access/http2/http2streams.cpp \ + access/http2/http2protocol.cpp diff --git a/src/network/access/http2/http2frames.cpp b/src/network/access/http2/http2frames.cpp new file mode 100644 index 0000000000..471fb2c7fb --- /dev/null +++ b/src/network/access/http2/http2frames.cpp @@ -0,0 +1,525 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "http2frames_p.h" + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace Http2 +{ + +// HTTP/2 frames are defined by RFC7540, clauses 4 and 6. + +FrameStatus validate_frame_header(FrameType type, FrameFlags flags, quint32 payloadSize) +{ + // 4.2 Frame Size + if (payloadSize > maxPayloadSize) + return FrameStatus::sizeError; + + switch (type) { + case FrameType::SETTINGS: + // SETTINGS ACK can not have any payload. + // The payload of a SETTINGS frame consists of zero + // or more parameters, each consisting of an unsigned + // 16-bit setting identifier and an unsigned 32-bit value. + // Thus the payload size must be a multiple of 6. + if (flags.testFlag(FrameFlag::ACK) ? payloadSize : payloadSize % 6) + return FrameStatus::sizeError; + break; + case FrameType::PRIORITY: + // 6.3 PRIORITY + if (payloadSize != 5) + return FrameStatus::sizeError; + break; + case FrameType::PING: + case FrameType::GOAWAY: + // 6.7 PING, 6.8 GOAWAY + if (payloadSize != 8) + return FrameStatus::sizeError; + break; + case FrameType::RST_STREAM: + case FrameType::WINDOW_UPDATE: + // 6.4 RST_STREAM, 6.9 WINDOW_UPDATE + if (payloadSize != 4) + return FrameStatus::sizeError; + break; + case FrameType::PUSH_PROMISE: + // 6.6 PUSH_PROMISE + if (payloadSize < 4) + return FrameStatus::sizeError; + default: + // DATA/HEADERS/CONTINUATION will be verified + // when we have payload. + // Frames of unknown types are ignored (5.1) + break; + } + + return FrameStatus::goodFrame; +} + +FrameStatus validate_frame_payload(FrameType type, FrameFlags flags, + quint32 size, const uchar *src) +{ + Q_ASSERT(!size || src); + + // Ignored, 5.1 + if (type == FrameType::LAST_FRAME_TYPE) + return FrameStatus::goodFrame; + + // 6.1 DATA, 6.2 HEADERS + if (type == FrameType::DATA || type == FrameType::HEADERS) { + if (flags.testFlag(FrameFlag::PADDED)) { + if (!size || size < src[0]) + return FrameStatus::sizeError; + size -= src[0]; + } + if (type == FrameType::HEADERS && flags.testFlag(FrameFlag::PRIORITY)) { + if (size < 5) + return FrameStatus::sizeError; + } + } + + // 6.6 PUSH_PROMISE + if (type == FrameType::PUSH_PROMISE) { + if (flags.testFlag(FrameFlag::PADDED)) { + if (!size || size < src[0]) + return FrameStatus::sizeError; + size -= src[0]; + } + + if (size < 4) + return FrameStatus::sizeError; + } + + return FrameStatus::goodFrame; +} + +FrameStatus validate_frame_payload(FrameType type, FrameFlags flags, + const std::vector &payload) +{ + const uchar *src = payload.size() ? &payload[0] : nullptr; + return validate_frame_payload(type, flags, quint32(payload.size()), src); +} + + +FrameReader::FrameReader(FrameReader &&rhs) + : framePayload(std::move(rhs.framePayload)) +{ + type = rhs.type; + rhs.type = FrameType::LAST_FRAME_TYPE; + + flags = rhs.flags; + rhs.flags = FrameFlag::EMPTY; + + streamID = rhs.streamID; + rhs.streamID = 0; + + payloadSize = rhs.payloadSize; + rhs.payloadSize = 0; + + incompleteRead = rhs.incompleteRead; + rhs.incompleteRead = false; + + offset = rhs.offset; + rhs.offset = 0; +} + +FrameReader &FrameReader::operator = (FrameReader &&rhs) +{ + framePayload = std::move(rhs.framePayload); + + type = rhs.type; + rhs.type = FrameType::LAST_FRAME_TYPE; + + flags = rhs.flags; + rhs.flags = FrameFlag::EMPTY; + + streamID = rhs.streamID; + rhs.streamID = 0; + + payloadSize = rhs.payloadSize; + rhs.payloadSize = 0; + + incompleteRead = rhs.incompleteRead; + rhs.incompleteRead = false; + + offset = rhs.offset; + rhs.offset = 0; + + return *this; +} + +FrameStatus FrameReader::read(QAbstractSocket &socket) +{ + if (!incompleteRead) { + if (!readHeader(socket)) + return FrameStatus::incompleteFrame; + + const auto status = validate_frame_header(type, flags, payloadSize); + if (status != FrameStatus::goodFrame) { + // No need to read any payload. + return status; + } + + if (Http2PredefinedParameters::maxFrameSize < payloadSize) + return FrameStatus::sizeError; + + framePayload.resize(payloadSize); + offset = 0; + } + + if (framePayload.size()) { + if (!readPayload(socket)) + return FrameStatus::incompleteFrame; + } + + return validate_frame_payload(type, flags, framePayload); +} + +bool FrameReader::padded(uchar *pad) const +{ + Q_ASSERT(pad); + + if (!flags.testFlag(FrameFlag::PADDED)) + return false; + + if (type == FrameType::DATA + || type == FrameType::PUSH_PROMISE + || type == FrameType::HEADERS) { + Q_ASSERT(framePayload.size() >= 1); + *pad = framePayload[0]; + return true; + } + + return false; +} + +bool FrameReader::priority(quint32 *streamID, uchar *weight) const +{ + Q_ASSERT(streamID); + Q_ASSERT(weight); + + if (!framePayload.size()) + return false; + + const uchar *src = &framePayload[0]; + if (type == FrameType::HEADERS && flags.testFlag(FrameFlag::PADDED)) + ++src; + + if ((type == FrameType::HEADERS && flags.testFlag(FrameFlag::PRIORITY)) + || type == FrameType::PRIORITY) { + *streamID = qFromBigEndian(src); + *weight = src[4]; + return true; + } + + return false; +} + +quint32 FrameReader::dataSize() const +{ + quint32 size = quint32(framePayload.size()); + uchar pad = 0; + if (padded(&pad)) { + // + 1 one for a byte with padding number itself: + size -= pad + 1; + } + + quint32 dummyID = 0; + uchar dummyW = 0; + if (priority(&dummyID, &dummyW)) + size -= 5; + + return size; +} + +const uchar *FrameReader::dataBegin() const +{ + if (!framePayload.size()) + return nullptr; + + const uchar *src = &framePayload[0]; + uchar dummyPad = 0; + if (padded(&dummyPad)) + ++src; + + quint32 dummyID = 0; + uchar dummyW = 0; + if (priority(&dummyID, &dummyW)) + src += 5; + + return src; +} + +bool FrameReader::readHeader(QAbstractSocket &socket) +{ + if (socket.bytesAvailable() < frameHeaderSize) + return false; + + uchar src[frameHeaderSize] = {}; + socket.read(reinterpret_cast(src), frameHeaderSize); + + payloadSize = src[0] << 16 | src[1] << 8 | src[2]; + + type = FrameType(src[3]); + if (int(type) >= int(FrameType::LAST_FRAME_TYPE)) + type = FrameType::LAST_FRAME_TYPE; // To be ignored, 5.1 + + flags = FrameFlags(src[4]); + streamID = qFromBigEndian(src + 5); + + return true; +} + +bool FrameReader::readPayload(QAbstractSocket &socket) +{ + Q_ASSERT(offset <= framePayload.size()); + + // Casts and ugliness - to deal with MSVC. Values are guaranteed to fit into quint32. + if (const auto residue = std::min(qint64(framePayload.size() - offset), socket.bytesAvailable())) { + socket.read(reinterpret_cast(&framePayload[offset]), residue); + offset += quint32(residue); + } + + incompleteRead = offset < framePayload.size(); + return !incompleteRead; +} + + +FrameWriter::FrameWriter() +{ + frameBuffer.reserve(Http2PredefinedParameters::maxFrameSize + + Http2PredefinedParameters::frameHeaderSize); +} + +FrameWriter::FrameWriter(FrameType type, FrameFlags flags, quint32 streamID) +{ + frameBuffer.reserve(Http2PredefinedParameters::maxFrameSize + + Http2PredefinedParameters::frameHeaderSize); + start(type, flags, streamID); +} + +void FrameWriter::start(FrameType type, FrameFlags flags, quint32 streamID) +{ + frameBuffer.resize(frameHeaderSize); + // The first three bytes - payload size, which is 0 for now. + frameBuffer[0] = 0; + frameBuffer[1] = 0; + frameBuffer[2] = 0; + + frameBuffer[3] = uchar(type); + frameBuffer[4] = uchar(flags); + + qToBigEndian(streamID, &frameBuffer[5]); +} + +void FrameWriter::setPayloadSize(quint32 size) +{ + Q_ASSERT(frameBuffer.size() >= frameHeaderSize); + Q_ASSERT(size < maxPayloadSize); + + frameBuffer[0] = size >> 16; + frameBuffer[1] = size >> 8; + frameBuffer[2] = size; +} + +quint32 FrameWriter::payloadSize() const +{ + Q_ASSERT(frameBuffer.size() >= frameHeaderSize); + return frameBuffer[0] << 16 | frameBuffer[1] << 8 | frameBuffer[2]; +} + +void FrameWriter::setType(FrameType type) +{ + Q_ASSERT(frameBuffer.size() >= frameHeaderSize); + frameBuffer[3] = uchar(type); +} + +FrameType FrameWriter::type() const +{ + Q_ASSERT(frameBuffer.size() >= frameHeaderSize); + return FrameType(frameBuffer[3]); +} + +void FrameWriter::setFlags(FrameFlags flags) +{ + Q_ASSERT(frameBuffer.size() >= frameHeaderSize); + frameBuffer[4] = uchar(flags); +} + +void FrameWriter::addFlag(FrameFlag flag) +{ + setFlags(flags() | flag); +} + +FrameFlags FrameWriter::flags() const +{ + Q_ASSERT(frameBuffer.size() >= frameHeaderSize); + return FrameFlags(frameBuffer[4]); +} + +quint32 FrameWriter::streamID() const +{ + return qFromBigEndian(&frameBuffer[5]); +} + +void FrameWriter::append(uchar val) +{ + frameBuffer.push_back(val); + updatePayloadSize(); +} + +void FrameWriter::append(const uchar *begin, const uchar *end) +{ + Q_ASSERT(begin && end); + Q_ASSERT(begin < end); + + frameBuffer.insert(frameBuffer.end(), begin, end); + updatePayloadSize(); +} + +void FrameWriter::updatePayloadSize() +{ + // First, compute size: + const quint32 payloadSize = quint32(frameBuffer.size() - frameHeaderSize); + Q_ASSERT(payloadSize <= maxPayloadSize); + setPayloadSize(payloadSize); +} + +bool FrameWriter::write(QAbstractSocket &socket) const +{ + Q_ASSERT(frameBuffer.size() >= frameHeaderSize); + // Do some sanity check first: + Q_ASSERT(int(type()) < int(FrameType::LAST_FRAME_TYPE)); + Q_ASSERT(validate_frame_header(type(), flags(), payloadSize()) == FrameStatus::goodFrame); + + const auto nWritten = socket.write(reinterpret_cast(&frameBuffer[0]), + frameBuffer.size()); + return nWritten != -1 && size_type(nWritten) == frameBuffer.size(); +} + +bool FrameWriter::writeHEADERS(QAbstractSocket &socket, quint32 sizeLimit) +{ + Q_ASSERT(frameBuffer.size() >= frameHeaderSize); + + if (sizeLimit > quint32(maxPayloadSize)) + sizeLimit = quint32(maxPayloadSize); + + if (quint32(frameBuffer.size() - frameHeaderSize) <= sizeLimit) { + updatePayloadSize(); + return write(socket); + } + + // Write a frame's header (not controlled by sizeLimit) and + // as many bytes of payload as we can within sizeLimit, + // then send CONTINUATION frames, as needed. + setPayloadSize(sizeLimit); + const quint32 firstChunkSize = frameHeaderSize + sizeLimit; + qint64 written = socket.write(reinterpret_cast(&frameBuffer[0]), + firstChunkSize); + + if (written != qint64(firstChunkSize)) + return false; + + FrameWriter continuationFrame(FrameType::CONTINUATION, FrameFlag::EMPTY, streamID()); + quint32 offset = firstChunkSize; + + while (offset != frameBuffer.size()) { + const auto chunkSize = std::min(sizeLimit, quint32(frameBuffer.size() - offset)); + if (chunkSize + offset == frameBuffer.size()) + continuationFrame.addFlag(FrameFlag::END_HEADERS); + continuationFrame.setPayloadSize(chunkSize); + if (!continuationFrame.write(socket)) + return false; + written = socket.write(reinterpret_cast(&frameBuffer[offset]), + chunkSize); + if (written != qint64(chunkSize)) + return false; + + offset += chunkSize; + } + + return true; +} + +bool FrameWriter::writeDATA(QAbstractSocket &socket, quint32 sizeLimit, + const uchar *src, quint32 size) +{ + // With DATA frame(s) we always have: + // 1) frame's header (9 bytes) + // 2) a separate payload (from QNonContiguousByteDevice). + // We either fit within a sizeLimit, or split into several + // DATA frames. + + Q_ASSERT(src); + + if (sizeLimit > quint32(maxPayloadSize)) + sizeLimit = quint32(maxPayloadSize); + // We NEVER set END_STREAM, since QHttp2ProtocolHandler works with + // QNonContiguousByteDevice and this 'writeDATA' is probably + // not the last one for a given request. + // This has to be done externally (sending an empty DATA frame with END_STREAM). + for (quint32 offset = 0; offset != size;) { + const auto chunkSize = std::min(size - offset, sizeLimit); + setPayloadSize(chunkSize); + // Frame's header first: + if (!write(socket)) + return false; + // Payload (if any): + if (chunkSize) { + const auto written = socket.write(reinterpret_cast(src + offset), + chunkSize); + if (written != qint64(chunkSize)) + return false; + } + + offset += chunkSize; + } + + return true; +} + +} + +QT_END_NAMESPACE diff --git a/src/network/access/http2/http2frames_p.h b/src/network/access/http2/http2frames_p.h new file mode 100644 index 0000000000..860555c180 --- /dev/null +++ b/src/network/access/http2/http2frames_p.h @@ -0,0 +1,173 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef HTTP2FRAMES_P_H +#define HTTP2FRAMES_P_H + +#include "http2protocol_p.h" +#include "hpack_p.h" + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QHttp2ProtocolHandler; +class QAbstractSocket; + +namespace Http2 +{ + +class Q_AUTOTEST_EXPORT FrameReader +{ + friend class QT_PREPEND_NAMESPACE(QHttp2ProtocolHandler); + +public: + FrameReader() = default; + + FrameReader(const FrameReader &) = default; + FrameReader(FrameReader &&rhs); + + FrameReader &operator = (const FrameReader &) = default; + FrameReader &operator = (FrameReader &&rhs); + + FrameStatus read(QAbstractSocket &socket); + + bool padded(uchar *pad) const; + bool priority(quint32 *streamID, uchar *weight) const; + + // N of bytes without padding and/or priority + quint32 dataSize() const; + // Beginning of payload without priority/padding + // bytes. + const uchar *dataBegin() const; + + FrameType type = FrameType::LAST_FRAME_TYPE; + FrameFlags flags = FrameFlag::EMPTY; + quint32 streamID = 0; + quint32 payloadSize = 0; + +private: + bool readHeader(QAbstractSocket &socket); + bool readPayload(QAbstractSocket &socket); + + // As soon as we got a header, we + // know payload size, offset is + // needed if we do not have enough + // data and will read the next chunk. + bool incompleteRead = false; + quint32 offset = 0; + std::vector framePayload; +}; + +class Q_AUTOTEST_EXPORT FrameWriter +{ + friend class QT_PREPEND_NAMESPACE(QHttp2ProtocolHandler); + +public: + using payload_type = std::vector; + using size_type = payload_type::size_type; + + FrameWriter(); + FrameWriter(FrameType type, FrameFlags flags, quint32 streamID); + + void start(FrameType type, FrameFlags flags, quint32 streamID); + + void setPayloadSize(quint32 size); + quint32 payloadSize() const; + + void setType(FrameType type); + FrameType type() const; + + void setFlags(FrameFlags flags); + void addFlag(FrameFlag flag); + FrameFlags flags() const; + + quint32 streamID() const; + + // All append functions also update frame's payload + // length. + template + void append(ValueType val) + { + uchar wired[sizeof val] = {}; + qToBigEndian(val, wired); + append(wired, wired + sizeof val); + } + void append(uchar val); + void append(Settings identifier) + { + append(quint16(identifier)); + } + void append(const payload_type &payload) + { + append(&payload[0], &payload[0] + payload.size()); + } + + void append(const uchar *begin, const uchar *end); + + // Write 'frameBuffer' as a single frame: + bool write(QAbstractSocket &socket) const; + // Write as a single frame if we can, or write headers and + // CONTINUATION(s) frame(s). + bool writeHEADERS(QAbstractSocket &socket, quint32 sizeLimit); + // Write either a single DATA frame or several DATA frames + // depending on 'sizeLimit'. Data itself is 'external' to + // FrameWriter, since it's a 'readPointer' from QNonContiguousData. + bool writeDATA(QAbstractSocket &socket, quint32 sizeLimit, + const uchar *src, quint32 size); + + std::vector &rawFrameBuffer() + { + return frameBuffer; + } + +private: + void updatePayloadSize(); + std::vector frameBuffer; +}; + +} + +QT_END_NAMESPACE + +#endif diff --git a/src/network/access/http2/http2protocol.cpp b/src/network/access/http2/http2protocol.cpp new file mode 100644 index 0000000000..7f788a6f42 --- /dev/null +++ b/src/network/access/http2/http2protocol.cpp @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#include "http2protocol_p.h" + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(QT_HTTP2, "qt.network.http2") + +namespace Http2 +{ + +// 3.5 HTTP/2 Connection Preface: +// "That is, the connection preface starts with the string +// PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n)." +const char Http2clientPreface[clientPrefaceLength] = + {0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, + 0x48, 0x54, 0x54, 0x50, 0x2f, 0x32, + 0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, + 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a}; + + +void qt_error(quint32 errorCode, QNetworkReply::NetworkError &error, + QString &errorMessage) +{ + if (errorCode > quint32(HTTP_1_1_REQUIRED)) { + error = QNetworkReply::ProtocolFailure; + errorMessage = QLatin1String("RST_STREAM with unknown error code (%1)"); + errorMessage = errorMessage.arg(errorCode); + return; + } + + const Http2Error http2Error = Http2Error(errorCode); + + switch (http2Error) { + case HTTP2_NO_ERROR: + error = QNetworkReply::NoError; + errorMessage.clear(); + break; + case PROTOCOL_ERROR: + error = QNetworkReply::ProtocolFailure; + errorMessage = QLatin1String("HTTP/2 protocol error"); + break; + case INTERNAL_ERROR: + error = QNetworkReply::InternalServerError; + errorMessage = QLatin1String("Internal server error"); + break; + case FLOW_CONTROL_ERROR: + error = QNetworkReply::ProtocolFailure; + errorMessage = QLatin1String("Flow control error"); + break; + case SETTINGS_TIMEOUT: + error = QNetworkReply::TimeoutError; + errorMessage = QLatin1String("SETTINGS ACK timeout error"); + break; + case STREAM_CLOSED: + error = QNetworkReply::ProtocolFailure; + errorMessage = QLatin1String("Server received frame(s) on a half-closed stream"); + break; + case FRAME_SIZE_ERROR: + error = QNetworkReply::ProtocolFailure; + errorMessage = QLatin1String("Server received a frame with an invalid size"); + break; + case REFUSE_STREAM: + error = QNetworkReply::ProtocolFailure; + errorMessage = QLatin1String("Server refused a stream"); + break; + case CANCEL: + error = QNetworkReply::ProtocolFailure; + errorMessage = QLatin1String("Stream is no longer needed"); + break; + case COMPRESSION_ERROR: + error = QNetworkReply::ProtocolFailure; + errorMessage = QLatin1String("Server is unable to maintain the " + "header compression context for the connection"); + break; + case CONNECT_ERROR: + // TODO: in Qt6 we'll have to add more error codes in QNetworkReply. + error = QNetworkReply::UnknownNetworkError; + errorMessage = QLatin1String("The connection established in response " + "to a CONNECT request was reset or abnormally closed"); + break; + case ENHANCE_YOUR_CALM: + error = QNetworkReply::UnknownServerError; + errorMessage = QLatin1String("Server dislikes our behavior, excessive load detected."); + break; + case INADEQUATE_SECURITY: + error = QNetworkReply::ContentAccessDenied; + errorMessage = QLatin1String("The underlying transport has properties " + "that do not meet minimum security " + "requirements"); + break; + case HTTP_1_1_REQUIRED: + error = QNetworkReply::ProtocolFailure; + errorMessage = QLatin1String("Server requires that HTTP/1.1 " + "be used instead of HTTP/2."); + } +} + +QString qt_error_string(quint32 errorCode) +{ + QNetworkReply::NetworkError error = QNetworkReply::NoError; + QString message; + qt_error(errorCode, error, message); + return message; +} + +QNetworkReply::NetworkError qt_error(quint32 errorCode) +{ + QNetworkReply::NetworkError error = QNetworkReply::NoError; + QString message; + qt_error(errorCode, error, message); + return error; +} + +} + +QT_END_NAMESPACE diff --git a/src/network/access/http2/http2protocol_p.h b/src/network/access/http2/http2protocol_p.h new file mode 100644 index 0000000000..c7088e2179 --- /dev/null +++ b/src/network/access/http2/http2protocol_p.h @@ -0,0 +1,164 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef HTTP2PROTOCOL_P_H +#define HTTP2PROTOCOL_P_H + +#include +#include +#include + +// Different HTTP/2 constants/values as defined by RFC 7540. + +QT_BEGIN_NAMESPACE + +class QString; + +namespace Http2 +{ + +enum class Settings : quint16 +{ + HEADER_TABLE_SIZE_ID = 0x1, + ENABLE_PUSH_ID = 0x2, + MAX_CONCURRENT_STREAMS_ID = 0x3, + INITIAL_WINDOW_SIZE_ID = 0x4, + MAX_FRAME_SIZE_ID = 0x5, + MAX_HEADER_LIST_SIZE_ID = 0x6 +}; + +enum class FrameType : uchar +{ + DATA = 0x0, + HEADERS = 0x1, + PRIORITY = 0x2, + RST_STREAM = 0x3, + SETTINGS = 0x4, + PUSH_PROMISE = 0x5, + PING = 0x6, + GOAWAY = 0x7, + WINDOW_UPDATE = 0x8, + CONTINUATION = 0x9, + // ATTENTION: enumerators must be sorted. + // We use LAST_FRAME_TYPE to check if + // frame type is known, if not - this frame + // must be ignored, HTTP/2 5.1). + LAST_FRAME_TYPE +}; + +enum class FrameFlag : uchar +{ + EMPTY = 0x0, // Valid for any frame type. + ACK = 0x1, // Valid for PING, SETTINGS + END_STREAM = 0x1, // Valid for HEADERS, DATA + END_HEADERS = 0x4, // Valid for PUSH_PROMISE, HEADERS, + PADDED = 0x8, // Valid for PUSH_PROMISE, HEADERS, DATA + PRIORITY = 0x20 // Valid for HEADERS, +}; + +Q_DECLARE_FLAGS(FrameFlags, FrameFlag) +Q_DECLARE_OPERATORS_FOR_FLAGS(FrameFlags) + +enum Http2PredefinedParameters +{ + // Old-style enum, so we + // can use as Http2::frameHeaderSize for example. + clientPrefaceLength = 24, // HTTP/2, 3.5 + connectionStreamID = 0, // HTTP/2, 5.1.1 + frameHeaderSize = 9, // HTTP/2, 4.1 + + // It's our max frame size we send in SETTINGS frame, + // it's also the default one and we also use it to later + // validate incoming frames: + maxFrameSize = 16384, // HTTP/2 6.5.2 + + defaultSessionWindowSize = 65535, // HTTP/2 6.5.2 + maxPayloadSize = (1 << 24) - 1, // HTTP/2 6.5.2 + // Using 1000 (rather arbitrarily), just to + // impose *some* upper limit: + maxPeerConcurrentStreams = 1000, + maxConcurrentStreams = 100 // HTTP/2, 6.5.2 +}; + +extern const Q_AUTOTEST_EXPORT char Http2clientPreface[clientPrefaceLength]; + +enum class FrameStatus +{ + protocolError, + sizeError, + incompleteFrame, + goodFrame +}; + +enum Http2Error +{ + // Old-style enum to avoid excessive name + // qualification ... + // NB: + // I use the last enumerator to check + // that errorCode (quint32) is valid, + // so it needs to be the highest-numbered! + // HTTP/2 7: + HTTP2_NO_ERROR = 0x0, + PROTOCOL_ERROR = 0x1, + INTERNAL_ERROR = 0x2, + FLOW_CONTROL_ERROR = 0x3, + SETTINGS_TIMEOUT = 0x4, + STREAM_CLOSED = 0x5, + FRAME_SIZE_ERROR = 0x6, + REFUSE_STREAM = 0x7, + CANCEL = 0x8, + COMPRESSION_ERROR = 0x9, + CONNECT_ERROR = 0xa, + ENHANCE_YOUR_CALM = 0xb, + INADEQUATE_SECURITY = 0xc, + HTTP_1_1_REQUIRED = 0xd +}; + +void qt_error(quint32 errorCode, QNetworkReply::NetworkError &error, QString &errorString); +QString qt_error_string(quint32 errorCode); +QNetworkReply::NetworkError qt_error(quint32 errorCode); + +} + +Q_DECLARE_LOGGING_CATEGORY(QT_HTTP2) + +QT_END_NAMESPACE + +#endif diff --git a/src/network/access/http2/http2streams.cpp b/src/network/access/http2/http2streams.cpp new file mode 100644 index 0000000000..f57f8d8367 --- /dev/null +++ b/src/network/access/http2/http2streams.cpp @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "http2streams_p.h" + +#include "private/qhttp2protocolhandler_p.h" +#include "private/qhttpnetworkreply_p.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace Http2 +{ + +Stream::Stream(const HttpMessagePair &message, quint32 id, qint32 sendSize, qint32 recvSize) + : httpPair(message), + streamID(id), + sendWindow(sendSize), + recvWindow(recvSize) +{ +} + +QHttpNetworkReply *Stream::reply() const +{ + return httpPair.second; +} + +const QHttpNetworkRequest &Stream::request() const +{ + return httpPair.first; +} + +QHttpNetworkRequest &Stream::request() +{ + return httpPair.first; +} + +QHttpNetworkRequest::Priority Stream::priority() const +{ + return httpPair.first.priority(); +} + +uchar Stream::weight() const +{ + switch (priority()) { + case QHttpNetworkRequest::LowPriority: + return 0; + case QHttpNetworkRequest::NormalPriority: + return 127; + case QHttpNetworkRequest::HighPriority: + default: + return 255; + } +} + +QNonContiguousByteDevice *Stream::data() const +{ + return httpPair.first.uploadByteDevice(); +} + +} + +QT_END_NAMESPACE diff --git a/src/network/access/http2/http2streams_p.h b/src/network/access/http2/http2streams_p.h new file mode 100644 index 0000000000..7db5d21694 --- /dev/null +++ b/src/network/access/http2/http2streams_p.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef HTTP2STREAMS_P_H +#define HTTP2STREAMS_P_H + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QNonContiguousByteDevice; + +namespace Http2 +{ + +struct Q_AUTOTEST_EXPORT Stream +{ + enum StreamState { + idle, + open, + halfClosedLocal, + halfClosedRemote, + closed + }; + + Stream() = default; + Stream(const HttpMessagePair &message, quint32 streamID, qint32 sendSize, + qint32 recvSize); + + // TODO: check includes!!! + QHttpNetworkReply *reply() const; + const QHttpNetworkRequest &request() const; + QHttpNetworkRequest &request(); + QHttpNetworkRequest::Priority priority() const; + uchar weight() const; + + QNonContiguousByteDevice *data() const; + + HttpMessagePair httpPair; + quint32 streamID = 0; + // Signed as window sizes can become negative: + qint32 sendWindow = 65535; + qint32 recvWindow = 65535; + + StreamState state = idle; +}; + +} + +QT_END_NAMESPACE + +#endif + -- cgit v1.2.3