diff options
Diffstat (limited to 'tests/auto/network')
60 files changed, 3822 insertions, 584 deletions
diff --git a/tests/auto/network/access/access.pro b/tests/auto/network/access/access.pro index bc76190e30..1d78cf253b 100644 --- a/tests/auto/network/access/access.pro +++ b/tests/auto/network/access/access.pro @@ -12,9 +12,12 @@ SUBDIRS=\ qftp \ qhttpnetworkreply \ qabstractnetworkcache \ + hpack \ + http2 -!contains(QT_CONFIG, private_tests): SUBDIRS -= \ +!qtConfig(private_tests): SUBDIRS -= \ qhttpnetworkconnection \ qhttpnetworkreply \ qftp \ - + hpack \ + http2 diff --git a/tests/auto/network/access/hpack/hpack.pro b/tests/auto/network/access/hpack/hpack.pro new file mode 100644 index 0000000000..3c8b8e7944 --- /dev/null +++ b/tests/auto/network/access/hpack/hpack.pro @@ -0,0 +1,6 @@ +QT += core core-private network network-private testlib +CONFIG += testcase parallel_test c++14 +TEMPLATE = app +TARGET = tst_hpack + +SOURCES += tst_hpack.cpp diff --git a/tests/auto/network/access/hpack/tst_hpack.cpp b/tests/auto/network/access/hpack/tst_hpack.cpp new file mode 100644 index 0000000000..bd337c9f5f --- /dev/null +++ b/tests/auto/network/access/hpack/tst_hpack.cpp @@ -0,0 +1,852 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2014 Governikus GmbH & Co. KG. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> + +#include <QtNetwork/private/bitstreams_p.h> +#include <QtNetwork/private/hpack_p.h> + +#include <QtCore/qbytearray.h> + +#include <cstdlib> +#include <vector> +#include <string> + +QT_USE_NAMESPACE + +using namespace HPack; + +class tst_Hpack: public QObject +{ + Q_OBJECT + +public: + tst_Hpack(); +private Q_SLOTS: + void bitstreamConstruction(); + void bitstreamWrite(); + void bitstreamReadWrite(); + void bitstreamCompression(); + void bitstreamErrors(); + + void lookupTableConstructor(); + + void lookupTableStatic_data(); + void lookupTableStatic(); + void lookupTableDynamic(); + + void hpackEncodeRequest_data(); + void hpackEncodeRequest(); + void hpackDecodeRequest_data(); + void hpackDecodeRequest(); + + void hpackEncodeResponse_data(); + void hpackEncodeResponse(); + void hpackDecodeResponse_data(); + void hpackDecodeResponse(); + + // TODO: more-more-more tests needed! + +private: + void hpackEncodeRequest(bool withHuffman); + void hpackEncodeResponse(bool withHuffman); + + HttpHeader header1; + std::vector<uchar> buffer1; + BitOStream request1; + + HttpHeader header2; + std::vector<uchar> buffer2; + BitOStream request2; + + HttpHeader header3; + std::vector<uchar> buffer3; + BitOStream request3; +}; + +using StreamError = BitIStream::Error; + +tst_Hpack::tst_Hpack() + : request1(buffer1), + request2(buffer2), + request3(buffer3) +{ +} + +void tst_Hpack::bitstreamConstruction() +{ + const uchar bytes[] = {0xDE, 0xAD, 0xBE, 0xEF}; + const int size = int(sizeof bytes); + + // Default ctors: + std::vector<uchar> buffer; + { + const BitOStream out(buffer); + QVERIFY(out.bitLength() == 0); + QVERIFY(out.byteLength() == 0); + + const BitIStream in; + QVERIFY(in.bitLength() == 0); + QVERIFY(in.streamOffset() == 0); + QVERIFY(in.error() == StreamError::NoError); + } + + // Create istream with some data: + { + BitIStream in(bytes, bytes + size); + QVERIFY(in.bitLength() == size * 8); + QVERIFY(in.streamOffset() == 0); + QVERIFY(in.error() == StreamError::NoError); + // 'Read' some data back: + for (int i = 0; i < size; ++i) { + uchar bitPattern = 0; + const auto bitsRead = in.peekBits(i * 8, 8, &bitPattern); + QVERIFY(bitsRead == 8); + QVERIFY(bitPattern == bytes[i]); + } + } + + // Copy ctors: + { + // Ostreams - copy is disabled. + // Istreams: + const BitIStream in1; + const BitIStream in2(in1); + QVERIFY(in2.bitLength() == in1.bitLength()); + QVERIFY(in2.streamOffset() == in1.streamOffset()); + QVERIFY(in2.error() == StreamError::NoError); + + const BitIStream in3(bytes, bytes + size); + const BitIStream in4(in3); + QVERIFY(in4.bitLength() == in3.bitLength()); + QVERIFY(in4.streamOffset() == in3.streamOffset()); + QVERIFY(in4.error() == StreamError::NoError); + } +} + +void tst_Hpack::bitstreamWrite() +{ + // Known representations, + // https://http2.github.io/http2-spec/compression.html. + // 5.1 Integer Representation + + // Test bit/byte lengths of the + // resulting data: + std::vector<uchar> buffer; + BitOStream out(buffer); + out.write(3); + // 11, fits into 8-bit prefix: + QVERIFY(out.bitLength() == 8); + QVERIFY(out.byteLength() == 1); + QVERIFY(out.begin()[0] == 3); + + out.clear(); + QVERIFY(out.bitLength() == 0); + QVERIFY(out.byteLength() == 0); + + // This number does not fit into 8-bit + // prefix we'll need 2 bytes: + out.write(256); + QVERIFY(out.byteLength() == 2); + QVERIFY(out.bitLength() == 16); + QVERIFY(out.begin()[0] == 0xff); + QVERIFY(out.begin()[1] == 1); + + out.clear(); + + // See 5.2 String Literal Representation. + + // We use Huffman code, + // char 'a' has a prefix code 00011 (5 bits) + out.write(QByteArray("aaa", 3), true); + QVERIFY(out.byteLength() == 3); + QVERIFY(out.bitLength() == 24); + // Now we must have in our stream: + // 10000010 | 00011000| 11000111 + const uchar *encoded = out.begin(); + QVERIFY(encoded[0] == 0x82); + QVERIFY(encoded[1] == 0x18); + QVERIFY(encoded[2] == 0xC7); + // TODO: add more tests ... +} + +void tst_Hpack::bitstreamReadWrite() +{ + // We can write into the bit stream: + // 1) bit patterns + // 2) integers (see HPACK, 5.1) + // 3) string (see HPACK, 5.2) + std::vector<uchar> buffer; + BitOStream out(buffer); + out.writeBits(0xf, 3); + QVERIFY(out.byteLength() == 1); + QVERIFY(out.bitLength() == 3); + + // Now, read it back: + { + BitIStream in(out.begin(), out.end()); + uchar bitPattern = 0; + const auto bitsRead = in.peekBits(0, 3, &bitPattern); + // peekBits pack into the most significant byte/bit: + QVERIFY(bitsRead == 3); + QVERIFY((bitPattern >> 5) == 7); + } + + const quint32 testInt = 133; + out.write(testInt); + + // This integer does not fit into the current 5-bit prefix, + // so byteLength == 2. + QVERIFY(out.byteLength() == 2); + const auto bitLength = out.bitLength(); + QVERIFY(bitLength > 3); + + // Now, read it back: + { + BitIStream in(out.begin(), out.end()); + in.skipBits(3); // Bit pattern + quint32 value = 0; + QVERIFY(in.read(&value)); + QVERIFY(in.error() == StreamError::NoError); + QCOMPARE(value, testInt); + } + + const QByteArray testString("ABCDE", 5); + out.write(testString, true); // Compressed + out.write(testString, false); // Non-compressed + QVERIFY(out.byteLength() > 2); + QVERIFY(out.bitLength() > bitLength); + + // Now, read it back: + { + BitIStream in(out.begin(), out.end()); + in.skipBits(bitLength); // Bit pattern and integer + QByteArray value; + // Read compressed string first ... + QVERIFY(in.read(&value)); + QCOMPARE(value, testString); + QCOMPARE(in.error(), StreamError::NoError); + // Now non-compressed ... + QVERIFY(in.read(&value)); + QCOMPARE(value, testString); + QCOMPARE(in.error(), StreamError::NoError); + } +} + +void tst_Hpack::bitstreamCompression() +{ + // Similar to bitstreamReadWrite but + // writes/reads a lot of mixed strings/integers. + std::vector<std::string> strings; + std::vector<quint32> integers; + std::vector<bool> isA; // integer or string. + const std::string bytes("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789()[]/*"); + const unsigned nValues = 100000; + + quint64 totalStringBytes = 0; + std::vector<uchar> buffer; + BitOStream out(buffer); + for (unsigned i = 0; i < nValues; ++i) { + const bool isString = std::rand() % 1000 > 500; + isA.push_back(isString); + if (!isString) { + integers.push_back(std::rand() % 1000); + out.write(integers.back()); + } else { + const auto start = std::rand() % (bytes.length() / 2); + auto end = start * 2; + if (!end) + end = bytes.length() / 2; + strings.push_back(bytes.substr(start, end - start)); + const auto &s = strings.back(); + totalStringBytes += s.size(); + QByteArray data(s.c_str(), int(s.size())); + const bool compressed(std::rand() % 1000 > 500); + out.write(data, compressed); + } + } + + qDebug() << "Compressed(?) byte length:" << out.byteLength() + << "total string bytes:" << totalStringBytes; + qDebug() << "total integer bytes (for quint32):" << integers.size() * sizeof(quint32); + + QVERIFY(out.byteLength() > 0); + QVERIFY(out.bitLength() > 0); + + BitIStream in(out.begin(), out.end()); + + for (unsigned i = 0, iS = 0, iI = 0; i < nValues; ++i) { + if (isA[i]) { + QByteArray data; + QVERIFY(in.read(&data)); + QCOMPARE(in.error(), StreamError::NoError); + QCOMPARE(data.toStdString(), strings[iS]); + ++iS; + } else { + quint32 value = 0; + QVERIFY(in.read(&value)); + QCOMPARE(in.error(), StreamError::NoError); + QCOMPARE(value, integers[iI]); + ++iI; + } + } +} + +void tst_Hpack::bitstreamErrors() +{ + { + BitIStream in; + quint32 val = 0; + QVERIFY(!in.read(&val)); + QCOMPARE(in.error(), StreamError::NotEnoughData); + } + { + // Integer in a stream, that does not fit into quint32. + const uchar bytes[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + BitIStream in(bytes, bytes + sizeof bytes); + quint32 val = 0; + QVERIFY(!in.read(&val)); + QCOMPARE(in.error(), StreamError::InvalidInteger); + } + { + const uchar byte = 0x82; // 1 - Huffman compressed, 2 - the (fake) byte length. + BitIStream in(&byte, &byte + 1); + QByteArray val; + QVERIFY(!in.read(&val)); + QCOMPARE(in.error(), StreamError::NotEnoughData); + } +} + +void tst_Hpack::lookupTableConstructor() +{ + { + FieldLookupTable nonIndexed(4096, false); + QVERIFY(nonIndexed.dynamicDataSize() == 0); + QVERIFY(nonIndexed.numberOfDynamicEntries() == 0); + QVERIFY(nonIndexed.numberOfStaticEntries() != 0); + QVERIFY(nonIndexed.numberOfStaticEntries() == nonIndexed.numberOfEntries()); + // Now we add some fake field and verify what 'non-indexed' means ... no search + // by name. + QVERIFY(nonIndexed.prependField("custom-key", "custom-value")); + // 54: 10 + 12 in name/value pair above + 32 required by HPACK specs ... + QVERIFY(nonIndexed.dynamicDataSize() == 54); + QVERIFY(nonIndexed.numberOfDynamicEntries() == 1); + QCOMPARE(nonIndexed.numberOfEntries(), nonIndexed.numberOfStaticEntries() + 1); + // Should fail to find it (invalid index 0) - search is disabled. + QVERIFY(nonIndexed.indexOf("custom-key", "custom-value") == 0); + } + { + // "key" + "value" == 8 bytes, + 32 (HPACK's requirement) == 40. + // Let's ask for a max-size 32 so that entry does not fit: + FieldLookupTable nonIndexed(32, false); + QVERIFY(nonIndexed.prependField("key", "value")); + QVERIFY(nonIndexed.numberOfEntries() == nonIndexed.numberOfStaticEntries()); + QVERIFY(nonIndexed.indexOf("key", "value") == 0); + } + { + FieldLookupTable indexed(4096, true); + QVERIFY(indexed.dynamicDataSize() == 0); + QVERIFY(indexed.numberOfDynamicEntries() == 0); + QVERIFY(indexed.numberOfStaticEntries() != 0); + QVERIFY(indexed.numberOfStaticEntries() == indexed.numberOfEntries()); + QVERIFY(indexed.prependField("custom-key", "custom-value")); + QVERIFY(indexed.dynamicDataSize() == 54); + QVERIFY(indexed.numberOfDynamicEntries() == 1); + QVERIFY(indexed.numberOfEntries() == indexed.numberOfStaticEntries() + 1); + QVERIFY(indexed.indexOf("custom-key") == indexed.numberOfStaticEntries() + 1); + QVERIFY(indexed.indexOf("custom-key", "custom-value") == indexed.numberOfStaticEntries() + 1); + } +} + +void tst_Hpack::lookupTableStatic_data() +{ + QTest::addColumn<QByteArray>("expectedName"); + QTest::addColumn<QByteArray>("expectedValue"); + + // Some predefined fields to find + // (they are always defined/required by HPACK). + QTest::newRow(":authority|") << QByteArray(":authority") << QByteArray(""); + QTest::newRow(":method|GET") << QByteArray(":method") << QByteArray("GET"); + QTest::newRow(":method|POST") << QByteArray(":method") << QByteArray("POST"); + QTest::newRow(":path|/") << QByteArray(":path") << QByteArray("/"); + QTest::newRow(":path|/index.html") << QByteArray(":path") << QByteArray("/index.html"); + QTest::newRow(":scheme|http") << QByteArray(":scheme") << QByteArray("http"); + QTest::newRow(":scheme|https") << QByteArray(":scheme") << QByteArray("https"); + QTest::newRow(":status|200") << QByteArray(":status") << QByteArray("200"); + QTest::newRow(":status|204") << QByteArray(":status") << QByteArray("204"); + QTest::newRow(":status|206") << QByteArray(":status") << QByteArray("206"); + QTest::newRow(":status|304") << QByteArray(":status") << QByteArray("304"); + QTest::newRow(":status|400") << QByteArray(":status") << QByteArray("400"); + QTest::newRow(":status|404") << QByteArray(":status") << QByteArray("404"); + QTest::newRow(":status|500") << QByteArray(":status") << QByteArray("500"); +} + +void tst_Hpack::lookupTableStatic() +{ + const FieldLookupTable table(0, false /*all static, no need in 'search index'*/); + + QFETCH(QByteArray, expectedName); + QFETCH(QByteArray, expectedValue); + + const quint32 index = table.indexOf(expectedName, expectedValue); + QVERIFY(index != 0); + + QByteArray name, value; + QVERIFY(table.field(index, &name, &value)); + QCOMPARE(name, expectedName); + QCOMPARE(value, expectedValue); +} + +void tst_Hpack::lookupTableDynamic() +{ + // HPACK's table size: + // for every field -> size += field.name.length() + field.value.length() + 32. + // Let's set some size limit and try to fill table with enough entries to have several + // items evicted. + const quint32 tableSize = 8192; + const char stringData[] = "abcdefghijklmnopABCDEFGHIJKLMNOP0123456789()[]:"; + const quint32 dataSize = sizeof stringData - 1; + + FieldLookupTable table(tableSize, true); + + std::vector<QByteArray> fieldsToFind; + quint32 evicted = 0; + + while (true) { + // Strings are repeating way too often, I want to + // have at least some items really evicted and not found, + // therefore these weird dances with start/len. + const quint32 start = std::rand() % (dataSize - 10); + quint32 len = std::rand() % (dataSize - start); + if (!len) + len = 1; + + const QByteArray val(stringData + start, len); + fieldsToFind.push_back(val); + const quint32 entriesBefore = table.numberOfDynamicEntries(); + QVERIFY(table.prependField(val, val)); + QVERIFY(table.indexOf(val)); + QVERIFY(table.indexOf(val) == table.indexOf(val, val)); + QByteArray fieldName, fieldValue; + table.field(table.indexOf(val), &fieldName, &fieldValue); + + QVERIFY(val == fieldName); + QVERIFY(val == fieldValue); + + if (table.numberOfDynamicEntries() <= entriesBefore) { + // We had to evict several items ... + evicted += entriesBefore - table.numberOfDynamicEntries() + 1; + if (evicted >= 200) + break; + } + } + + QVERIFY(table.dynamicDataSize() <= tableSize); + QVERIFY(table.numberOfDynamicEntries() > 0); + QVERIFY(table.indexOf(fieldsToFind.back())); // We MUST have it in a table! + + using size_type = std::vector<QByteArray>::size_type; + for (size_type i = 0, e = fieldsToFind.size(); i < e; ++i) { + const auto &val = fieldsToFind[i]; + const quint32 index = table.indexOf(val); + if (!index) { + QVERIFY(i < size_type(evicted)); + } else { + QVERIFY(index == table.indexOf(val, val)); + QByteArray fieldName, fieldValue; + QVERIFY(table.field(index, &fieldName, &fieldValue)); + QVERIFY(val == fieldName); + QVERIFY(val == fieldValue); + } + } + + table.clearDynamicTable(); + + QVERIFY(table.numberOfDynamicEntries() == 0); + QVERIFY(table.dynamicDataSize() == 0); + QVERIFY(table.indexOf(fieldsToFind.back()) == 0); + + QVERIFY(table.prependField("name1", "value1")); + QVERIFY(table.prependField("name2", "value2")); + + QVERIFY(table.indexOf("name1") == table.numberOfStaticEntries() + 2); + QVERIFY(table.indexOf("name2", "value2") == table.numberOfStaticEntries() + 1); + QVERIFY(table.indexOf("name1", "value2") == 0); + QVERIFY(table.indexOf("name2", "value1") == 0); + QVERIFY(table.indexOf("name3") == 0); + + QVERIFY(!table.indexIsValid(table.numberOfEntries() + 1)); + + QVERIFY(table.prependField("name1", "value1")); + QVERIFY(table.numberOfDynamicEntries() == 3); + table.evictEntry(); + QVERIFY(table.indexOf("name1") != 0); + table.evictEntry(); + QVERIFY(table.indexOf("name2") == 0); + QVERIFY(table.indexOf("name1") != 0); + table.evictEntry(); + QVERIFY(table.dynamicDataSize() == 0); + QVERIFY(table.numberOfDynamicEntries() == 0); + QVERIFY(table.indexOf("name1") == 0); +} + +void tst_Hpack::hpackEncodeRequest_data() +{ + QTest::addColumn<bool>("compression"); + QTest::newRow("no-string-compression") << false; + QTest::newRow("with-string-compression") << true; +} + +void tst_Hpack::hpackEncodeRequest(bool withHuffman) +{ + // This function uses examples from HPACK specs + // (see appendix). + + Encoder encoder(4096, withHuffman); + // HPACK, C.3.1 First Request + /* + :method: GET + :scheme: http + :path: / + :authority: www.example.com + + Hex dump of encoded data (without Huffman): + + 8286 8441 0f77 7777 2e65 7861 6d70 6c65 | ...A.www.example + 2e63 6f6d + + Hex dump of encoded data (with Huffman): + + 8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4 ff + */ + request1.clear(); + header1 = {{":method", "GET"}, + {":scheme", "http"}, + {":path", "/"}, + {":authority", "www.example.com"}}; + QVERIFY(encoder.encodeRequest(request1, header1)); + QVERIFY(encoder.dynamicTableSize() == 57); + + // HPACK, C.3.2 Second Request + /* + Header list to encode: + + :method: GET + :scheme: http + :path: / + :authority: www.example.com + cache-control: no-cache + + Hex dump of encoded data (without Huffman): + + 8286 84be 5808 6e6f 2d63 6163 6865 + + Hex dump of encoded data (with Huffman): + + 8286 84be 5886 a8eb 1064 9cbf + */ + + request2.clear(); + header2 = {{":method", "GET"}, + {":scheme", "http"}, + {":path", "/"}, + {":authority", "www.example.com"}, + {"cache-control", "no-cache"}}; + encoder.encodeRequest(request2, header2); + QVERIFY(encoder.dynamicTableSize() == 110); + + // HPACK, C.3.3 Third Request + /* + Header list to encode: + + :method: GET + :scheme: https + :path: /index.html + :authority: www.example.com + custom-key: custom-value + + Hex dump of encoded data (without Huffman): + + 8287 85bf 400a 6375 7374 6f6d 2d6b 6579 + 0c63 7573 746f 6d2d 7661 6c75 65 + + Hex dump of encoded data (with Huffman): + + 8287 85bf 4088 25a8 49e9 5ba9 7d7f 8925 + a849 e95b b8e8 b4bf + */ + request3.clear(); + header3 = {{":method", "GET"}, + {":scheme", "https"}, + {":path", "/index.html"}, + {":authority", "www.example.com"}, + {"custom-key", "custom-value"}}; + encoder.encodeRequest(request3, header3); + QVERIFY(encoder.dynamicTableSize() == 164); +} + +void tst_Hpack::hpackEncodeRequest() +{ + QFETCH(bool, compression); + + hpackEncodeRequest(compression); + + // See comments above about these hex dumps ... + const uchar bytes1NH[] = {0x82, 0x86, 0x84, 0x41, + 0x0f, 0x77, 0x77, 0x77, + 0x2e, 0x65, 0x78, 0x61, + 0x6d, 0x70, 0x6c, 0x65, + 0x2e, 0x63, 0x6f, 0x6d}; + + const uchar bytes1WH[] = {0x82, 0x86, 0x84, 0x41, + 0x8c, 0xf1, 0xe3, 0xc2, + 0xe5, 0xf2, 0x3a, 0x6b, + 0xa0, 0xab, 0x90, 0xf4, + 0xff}; + + const uchar *hexDump1 = compression ? bytes1WH : bytes1NH; + const quint64 byteLength1 = compression ? sizeof bytes1WH : sizeof bytes1NH; + + QCOMPARE(request1.byteLength(), byteLength1); + QCOMPARE(request1.bitLength(), byteLength1 * 8); + + for (quint32 i = 0, e = request1.byteLength(); i < e; ++i) + QCOMPARE(hexDump1[i], request1.begin()[i]); + + const uchar bytes2NH[] = {0x82, 0x86, 0x84, 0xbe, + 0x58, 0x08, 0x6e, 0x6f, + 0x2d, 0x63, 0x61, 0x63, + 0x68, 0x65}; + + const uchar bytes2WH[] = {0x82, 0x86, 0x84, 0xbe, + 0x58, 0x86, 0xa8, 0xeb, + 0x10, 0x64, 0x9c, 0xbf}; + + const uchar *hexDump2 = compression ? bytes2WH : bytes2NH; + const auto byteLength2 = compression ? sizeof bytes2WH : sizeof bytes2NH; + QVERIFY(request2.byteLength() == byteLength2); + QVERIFY(request2.bitLength() == byteLength2 * 8); + for (quint32 i = 0, e = request2.byteLength(); i < e; ++i) + QCOMPARE(hexDump2[i], request2.begin()[i]); + + const uchar bytes3NH[] = {0x82, 0x87, 0x85, 0xbf, + 0x40, 0x0a, 0x63, 0x75, + 0x73, 0x74, 0x6f, 0x6d, + 0x2d, 0x6b, 0x65, 0x79, + 0x0c, 0x63, 0x75, 0x73, + 0x74, 0x6f, 0x6d, 0x2d, + 0x76, 0x61, 0x6c, 0x75, + 0x65}; + const uchar bytes3WH[] = {0x82, 0x87, 0x85, 0xbf, + 0x40, 0x88, 0x25, 0xa8, + 0x49, 0xe9, 0x5b, 0xa9, + 0x7d, 0x7f, 0x89, 0x25, + 0xa8, 0x49, 0xe9, 0x5b, + 0xb8, 0xe8, 0xb4, 0xbf}; + + const uchar *hexDump3 = compression ? bytes3WH : bytes3NH; + const quint64 byteLength3 = compression ? sizeof bytes3WH : sizeof bytes3NH; + QCOMPARE(request3.byteLength(), byteLength3); + QCOMPARE(request3.bitLength(), byteLength3 * 8); + for (quint32 i = 0, e = request3.byteLength(); i < e; ++i) + QCOMPARE(hexDump3[i], request3.begin()[i]); +} + +void tst_Hpack::hpackDecodeRequest_data() +{ + QTest::addColumn<bool>("compression"); + QTest::newRow("no-string-compression") << false; + QTest::newRow("with-string-compression") << true; +} + +void tst_Hpack::hpackDecodeRequest() +{ + QFETCH(bool, compression); + hpackEncodeRequest(compression); + + QVERIFY(request1.byteLength()); + QVERIFY(request2.byteLength()); + QVERIFY(request3.byteLength()); + + Decoder decoder(4096); + BitIStream inputStream1(request1.begin(), request1.end()); + QVERIFY(decoder.decodeHeaderFields(inputStream1)); + QCOMPARE(decoder.dynamicTableSize(), quint32(57)); + { + const auto &decoded = decoder.decodedHeader(); + QVERIFY(decoded == header1); + } + + BitIStream inputStream2{request2.begin(), request2.end()}; + QVERIFY(decoder.decodeHeaderFields(inputStream2)); + QCOMPARE(decoder.dynamicTableSize(), quint32(110)); + { + const auto &decoded = decoder.decodedHeader(); + QVERIFY(decoded == header2); + } + + BitIStream inputStream3(request3.begin(), request3.end()); + QVERIFY(decoder.decodeHeaderFields(inputStream3)); + QCOMPARE(decoder.dynamicTableSize(), quint32(164)); + { + const auto &decoded = decoder.decodedHeader(); + QVERIFY(decoded == header3); + } +} + +void tst_Hpack::hpackEncodeResponse_data() +{ + hpackEncodeRequest_data(); +} + +void tst_Hpack::hpackEncodeResponse() +{ + QFETCH(bool, compression); + + hpackEncodeResponse(compression); + + // TODO: we can also test bytes - using hex dumps from HPACK's specs, + // for now only test a table behavior/expected sizes. +} + +void tst_Hpack::hpackEncodeResponse(bool withCompression) +{ + Encoder encoder(256, withCompression); // 256 - this will result in entries evicted. + + // HPACK, C.5.1 First Response + /* + Header list to encode: + + :status: 302 + cache-control: private + date: Mon, 21 Oct 2013 20:13:21 GMT + location: https://www.example.com + */ + request1.clear(); + header1 = {{":status", "302"}, + {"cache-control", "private"}, + {"date", "Mon, 21 Oct 2013 20:13:21 GMT"}, + {"location", "https://www.example.com"}}; + + QVERIFY(encoder.encodeResponse(request1, header1)); + QCOMPARE(encoder.dynamicTableSize(), quint32(222)); + + // HPACK, C.5.2 Second Response + /* + + + The (":status", "302") header field is evicted from the dynamic + table to free space to allow adding the (":status", "307") header field. + + Header list to encode: + + :status: 307 + cache-control: private + date: Mon, 21 Oct 2013 20:13:21 GMT + location: https://www.example.com + */ + request2.clear(); + header2 = {{":status", "307"}, + {"cache-control", "private"}, + {"date", "Mon, 21 Oct 2013 20:13:21 GMT"}, + {"location", "https://www.example.com"}}; + QVERIFY(encoder.encodeResponse(request2, header2)); + QCOMPARE(encoder.dynamicTableSize(), quint32(222)); + + // HPACK, C.5.3 Third Response + /* + Several header fields are evicted from the dynamic table + during the processing of this header list. + + Header list to encode: + + :status: 200 + cache-control: private + date: Mon, 21 Oct 2013 20:13:22 GMT + location: https://www.example.com + content-encoding: gzip + set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1 + */ + request3.clear(); + header3 = {{":status", "200"}, + {"cache-control", "private"}, + {"date", "Mon, 21 Oct 2013 20:13:22 GMT"}, + {"location", "https://www.example.com"}, + {"content-encoding", "gzip"}, + {"set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"}}; + QVERIFY(encoder.encodeResponse(request3, header3)); + QCOMPARE(encoder.dynamicTableSize(), quint32(215)); +} + +void tst_Hpack::hpackDecodeResponse_data() +{ + hpackEncodeRequest_data(); +} + +void tst_Hpack::hpackDecodeResponse() +{ + QFETCH(bool, compression); + + hpackEncodeResponse(compression); + + QVERIFY(request1.byteLength()); + Decoder decoder(256); // This size will result in entries evicted. + BitIStream inputStream1(request1.begin(), request1.end()); + QVERIFY(decoder.decodeHeaderFields(inputStream1)); + QCOMPARE(decoder.dynamicTableSize(), quint32(222)); + + { + const auto &decoded = decoder.decodedHeader(); + QVERIFY(decoded == header1); + } + + QVERIFY(request2.byteLength()); + BitIStream inputStream2(request2.begin(), request2.end()); + QVERIFY(decoder.decodeHeaderFields(inputStream2)); + QCOMPARE(decoder.dynamicTableSize(), quint32(222)); + + { + const auto &decoded = decoder.decodedHeader(); + QVERIFY(decoded == header2); + } + + QVERIFY(request3.byteLength()); + BitIStream inputStream3(request3.begin(), request3.end()); + QVERIFY(decoder.decodeHeaderFields(inputStream3)); + QCOMPARE(decoder.dynamicTableSize(), quint32(215)); + + { + const auto &decoded = decoder.decodedHeader(); + QVERIFY(decoded == header3); + } +} + +QTEST_MAIN(tst_Hpack) + +#include "tst_hpack.moc" diff --git a/tests/auto/network/access/http2/certs/fluke.cert b/tests/auto/network/access/http2/certs/fluke.cert new file mode 100644 index 0000000000..ace4e4f0eb --- /dev/null +++ b/tests/auto/network/access/http2/certs/fluke.cert @@ -0,0 +1,75 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 0 (0x0) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=NO, ST=Oslo, L=Nydalen, O=Nokia Corporation and/or its subsidiary(-ies), OU=Development, CN=fluke.troll.no/emailAddress=ahanssen@trolltech.com + Validity + Not Before: Dec 4 01:10:32 2007 GMT + Not After : Apr 21 01:10:32 2035 GMT + Subject: C=NO, ST=Oslo, O=Nokia Corporation and/or its subsidiary(-ies), OU=Development, CN=fluke.troll.no + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:a7:c8:a0:4a:c4:19:05:1b:66:ba:32:e2:d2:f1: + 1c:6f:17:82:e4:39:2e:01:51:90:db:04:34:32:11: + 21:c2:0d:6f:59:d8:53:90:54:3f:83:8f:a9:d3:b3: + d5:ee:1a:9b:80:ae:c3:25:c9:5e:a5:af:4b:60:05: + aa:a0:d1:91:01:1f:ca:04:83:e3:58:1c:99:32:45: + 84:70:72:58:03:98:4a:63:8b:41:f5:08:49:d2:91: + 02:60:6b:e4:64:fe:dd:a0:aa:74:08:e9:34:4c:91: + 5f:12:3d:37:4d:54:2c:ad:7f:5b:98:60:36:02:8c: + 3b:f6:45:f3:27:6a:9b:94:9d + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 21:85:04:3D:23:01:66:E5:F7:9F:1A:84:24:8A:AF:0A:79:F4:E5:AC + X509v3 Authority Key Identifier: + DirName:/C=NO/ST=Oslo/L=Nydalen/O=Nokia Corporation and/or its subsidiary(-ies)/OU=Development/CN=fluke.troll.no/emailAddress=ahanssen@trolltech.com + serial:8E:A8:B4:E8:91:B7:54:2E + + Signature Algorithm: sha1WithRSAEncryption + 6d:57:5f:d1:05:43:f0:62:05:ec:2a:71:a5:dc:19:08:f2:c4: + a6:bd:bb:25:d9:ca:89:01:0e:e4:cf:1f:c1:8c:c8:24:18:35: + 53:59:7b:c0:43:b4:32:e6:98:b2:a6:ef:15:05:0b:48:5f:e1: + a0:0c:97:a9:a1:77:d8:35:18:30:bc:a9:8f:d3:b7:54:c7:f1: + a9:9e:5d:e6:19:bf:f6:3c:5b:2b:d8:e4:3e:62:18:88:8b:d3: + 24:e1:40:9b:0c:e6:29:16:62:ab:ea:05:24:70:36:aa:55:93: + ef:02:81:1b:23:10:a2:04:eb:56:95:75:fc:f8:94:b1:5d:42: + c5:3f:36:44:85:5d:3a:2e:90:46:8a:a2:b9:6f:87:ae:0c:15: + 40:19:31:90:fc:3b:25:bb:ae:f1:66:13:0d:85:90:d9:49:34: + 8f:f2:5d:f9:7a:db:4d:5d:27:f6:76:9d:35:8c:06:a6:4c:a3: + b1:b2:b6:6f:1d:d7:a3:00:fd:72:eb:9e:ea:44:a1:af:21:34: + 7d:c7:42:e2:49:91:19:8b:c0:ad:ba:82:80:a8:71:70:f4:35: + 31:91:63:84:20:95:e9:60:af:64:8b:cc:ff:3d:8a:76:74:3d: + c8:55:6d:e4:8e:c3:2b:1c:e8:42:18:ae:9f:e6:6b:9c:34:06: + ec:6a:f2:c3 +-----BEGIN CERTIFICATE----- +MIIEEzCCAvugAwIBAgIBADANBgkqhkiG9w0BAQUFADCBnDELMAkGA1UEBhMCTk8x +DTALBgNVBAgTBE9zbG8xEDAOBgNVBAcTB055ZGFsZW4xFjAUBgNVBAoTDVRyb2xs +dGVjaCBBU0ExFDASBgNVBAsTC0RldmVsb3BtZW50MRcwFQYDVQQDEw5mbHVrZS50 +cm9sbC5ubzElMCMGCSqGSIb3DQEJARYWYWhhbnNzZW5AdHJvbGx0ZWNoLmNvbTAe +Fw0wNzEyMDQwMTEwMzJaFw0zNTA0MjEwMTEwMzJaMGMxCzAJBgNVBAYTAk5PMQ0w +CwYDVQQIEwRPc2xvMRYwFAYDVQQKEw1Ucm9sbHRlY2ggQVNBMRQwEgYDVQQLEwtE +ZXZlbG9wbWVudDEXMBUGA1UEAxMOZmx1a2UudHJvbGwubm8wgZ8wDQYJKoZIhvcN +AQEBBQADgY0AMIGJAoGBAKfIoErEGQUbZroy4tLxHG8XguQ5LgFRkNsENDIRIcIN +b1nYU5BUP4OPqdOz1e4am4CuwyXJXqWvS2AFqqDRkQEfygSD41gcmTJFhHByWAOY +SmOLQfUISdKRAmBr5GT+3aCqdAjpNEyRXxI9N01ULK1/W5hgNgKMO/ZF8ydqm5Sd +AgMBAAGjggEaMIIBFjAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NM +IEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUIYUEPSMBZuX3nxqEJIqv +Cnn05awwgbsGA1UdIwSBszCBsKGBoqSBnzCBnDELMAkGA1UEBhMCTk8xDTALBgNV +BAgTBE9zbG8xEDAOBgNVBAcTB055ZGFsZW4xFjAUBgNVBAoTDVRyb2xsdGVjaCBB +U0ExFDASBgNVBAsTC0RldmVsb3BtZW50MRcwFQYDVQQDEw5mbHVrZS50cm9sbC5u +bzElMCMGCSqGSIb3DQEJARYWYWhhbnNzZW5AdHJvbGx0ZWNoLmNvbYIJAI6otOiR +t1QuMA0GCSqGSIb3DQEBBQUAA4IBAQBtV1/RBUPwYgXsKnGl3BkI8sSmvbsl2cqJ +AQ7kzx/BjMgkGDVTWXvAQ7Qy5piypu8VBQtIX+GgDJepoXfYNRgwvKmP07dUx/Gp +nl3mGb/2PFsr2OQ+YhiIi9Mk4UCbDOYpFmKr6gUkcDaqVZPvAoEbIxCiBOtWlXX8 ++JSxXULFPzZEhV06LpBGiqK5b4euDBVAGTGQ/Dslu67xZhMNhZDZSTSP8l35ettN +XSf2dp01jAamTKOxsrZvHdejAP1y657qRKGvITR9x0LiSZEZi8CtuoKAqHFw9DUx +kWOEIJXpYK9ki8z/PYp2dD3IVW3kjsMrHOhCGK6f5mucNAbsavLD +-----END CERTIFICATE----- diff --git a/tests/auto/network/access/http2/certs/fluke.key b/tests/auto/network/access/http2/certs/fluke.key new file mode 100644 index 0000000000..9d1664d609 --- /dev/null +++ b/tests/auto/network/access/http2/certs/fluke.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCnyKBKxBkFG2a6MuLS8RxvF4LkOS4BUZDbBDQyESHCDW9Z2FOQ +VD+Dj6nTs9XuGpuArsMlyV6lr0tgBaqg0ZEBH8oEg+NYHJkyRYRwclgDmEpji0H1 +CEnSkQJga+Rk/t2gqnQI6TRMkV8SPTdNVCytf1uYYDYCjDv2RfMnapuUnQIDAQAB +AoGANFzLkanTeSGNFM0uttBipFT9F4a00dqHz6JnO7zXAT26I5r8sU1pqQBb6uLz +/+Qz5Zwk8RUAQcsMRgJetuPQUb0JZjF6Duv24hNazqXBCu7AZzUenjafwmKC/8ri +KpX3fTwqzfzi//FKGgbXQ80yykSSliDL3kn/drATxsLCgQECQQDXhEFWLJ0vVZ1s +1Ekf+3NITE+DR16X+LQ4W6vyEHAjTbaNWtcTKdAWLA2l6N4WAAPYSi6awm+zMxx4 +VomVTsjdAkEAx0z+e7natLeFcrrq8pbU+wa6SAP1VfhQWKitxL1e7u/QO90NCpxE +oQYKzMkmmpOOFjQwEMAy1dvFMbm4LHlewQJAC/ksDBaUcQHHqjktCtrUb8rVjAyW +A8lscckeB2fEYyG5J6dJVaY4ClNOOs5yMDS2Afk1F6H/xKvtQ/5CzInA/QJATDub +K+BPU8jO9q+gpuIi3VIZdupssVGmCgObVCHLakG4uO04y9IyPhV9lA9tALtoIf4c +VIvv5fWGXBrZ48kZAQJBAJmVCdzQxd9LZI5vxijUCj5EI4e+x5DRqVUvyP8KCZrC +AiNyoDP85T+hBZaSXK3aYGpVwelyj3bvo1GrTNwNWLw= +-----END RSA PRIVATE KEY----- diff --git a/tests/auto/network/access/http2/http2.pro b/tests/auto/network/access/http2/http2.pro new file mode 100644 index 0000000000..e130f30784 --- /dev/null +++ b/tests/auto/network/access/http2/http2.pro @@ -0,0 +1,8 @@ +QT += core core-private network network-private testlib + +CONFIG += testcase parallel_test c++11 +TARGET = tst_http2 +HEADERS += http2srv.h +SOURCES += tst_http2.cpp http2srv.cpp + +DEFINES += SRCDIR=\\\"$$PWD/\\\" diff --git a/tests/auto/network/access/http2/http2srv.cpp b/tests/auto/network/access/http2/http2srv.cpp new file mode 100644 index 0000000000..9d68b5c798 --- /dev/null +++ b/tests/auto/network/access/http2/http2srv.cpp @@ -0,0 +1,695 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> + +#include <QtNetwork/private/http2protocol_p.h> +#include <QtNetwork/private/bitstreams_p.h> + +#include "http2srv.h" + +#ifndef QT_NO_SSL +#include <QtNetwork/qsslconfiguration.h> +#include <QtNetwork/qsslsocket.h> +#include <QtNetwork/qsslkey.h> +#endif + +#include <QtNetwork/qtcpsocket.h> + +#include <QtCore/qdebug.h> +#include <QtCore/qlist.h> +#include <QtCore/qfile.h> + +#include <cstdlib> +#include <cstring> +#include <limits> + +QT_BEGIN_NAMESPACE + +using namespace Http2; +using namespace HPack; + +namespace +{ + +inline bool is_valid_client_stream(quint32 streamID) +{ + // A valid client stream ID is an odd integer number in the range [1, INT_MAX]. + return (streamID & 0x1) && streamID <= std::numeric_limits<qint32>::max(); +} + +void fill_push_header(const HttpHeader &originalRequest, HttpHeader &promisedRequest) +{ + for (const auto &field : originalRequest) { + if (field.name == QByteArray(":authority") || + field.name == QByteArray(":scheme")) { + promisedRequest.push_back(field); + } + } +} + +} + +Http2Server::Http2Server(bool h2c, const Http2Settings &ss, const Http2Settings &cs) + : serverSettings(ss), + clearTextHTTP2(h2c) +{ + for (const auto &s : cs) + expectedClientSettings[quint16(s.identifier)] = s.value; + + responseBody = "<html>\n" + "<head>\n" + "<title>Sample \"Hello, World\" Application</title>\n" + "</head>\n" + "<body bgcolor=white>\n" + "<table border=\"0\" cellpadding=\"10\">\n" + "<tr>\n" + "<td>\n" + "<img src=\"images/springsource.png\">\n" + "</td>\n" + "<td>\n" + "<h1>Sample \"Hello, World\" Application</h1>\n" + "</td>\n" + "</tr>\n" + "</table>\n" + "<p>This is the home page for the HelloWorld Web application. </p>\n" + "</body>\n" + "</html>"; +} + +Http2Server::~Http2Server() +{ +} + +void Http2Server::enablePushPromise(bool pushEnabled, const QByteArray &path) +{ + pushPromiseEnabled = pushEnabled; + pushPath = path; +} + +void Http2Server::setResponseBody(const QByteArray &body) +{ + responseBody = body; +} + +void Http2Server::startServer() +{ +#ifdef QT_NO_SSL + // Let the test fail with timeout. + if (!clearTextHTTP2) + return; +#endif + if (listen()) + emit serverStarted(serverPort()); +} + +void Http2Server::sendServerSettings() +{ + Q_ASSERT(socket); + + if (!serverSettings.size()) + return; + + writer.start(FrameType::SETTINGS, FrameFlag::EMPTY, connectionStreamID); + for (const auto &s : serverSettings) { + writer.append(s.identifier); + writer.append(s.value); + if (s.identifier == Settings::INITIAL_WINDOW_SIZE_ID) + streamRecvWindowSize = s.value; + } + writer.write(*socket); + // Now, let's update our peer on a session recv window size: + const quint32 updatedSize = 10 * streamRecvWindowSize; + if (sessionRecvWindowSize < updatedSize) { + const quint32 delta = updatedSize - sessionRecvWindowSize; + sessionRecvWindowSize = updatedSize; + sessionCurrRecvWindow = updatedSize; + sendWINDOW_UPDATE(connectionStreamID, delta); + } + + waitingClientAck = true; + settingsSent = true; +} + +void Http2Server::sendGOAWAY(quint32 streamID, quint32 error, quint32 lastStreamID) +{ + Q_ASSERT(socket); + + writer.start(FrameType::GOAWAY, FrameFlag::EMPTY, streamID); + writer.append(lastStreamID); + writer.append(error); + writer.write(*socket); +} + +void Http2Server::sendRST_STREAM(quint32 streamID, quint32 error) +{ + Q_ASSERT(socket); + + writer.start(FrameType::RST_STREAM, FrameFlag::EMPTY, streamID); + writer.append(error); + writer.write(*socket); +} + +void Http2Server::sendDATA(quint32 streamID, quint32 windowSize) +{ + Q_ASSERT(socket); + + const auto it = suspendedStreams.find(streamID); + Q_ASSERT(it != suspendedStreams.end()); + + const quint32 offset = it->second; + Q_ASSERT(offset < quint32(responseBody.size())); + + const quint32 bytes = std::min<quint32>(windowSize, responseBody.size() - offset); + const quint32 frameSizeLimit(clientSetting(Settings::MAX_FRAME_SIZE_ID, Http2::maxFrameSize)); + const uchar *src = reinterpret_cast<const uchar *>(responseBody.constData() + offset); + const bool last = offset + bytes == quint32(responseBody.size()); + + writer.start(FrameType::DATA, FrameFlag::EMPTY, streamID); + writer.writeDATA(*socket, frameSizeLimit, src, bytes); + + if (last) { + writer.start(FrameType::DATA, FrameFlag::END_STREAM, streamID); + writer.setPayloadSize(0); + writer.write(*socket); + suspendedStreams.erase(it); + activeRequests.erase(streamID); + + Q_ASSERT(closedStreams.find(streamID) == closedStreams.end()); + closedStreams.insert(streamID); + } else { + it->second += bytes; + } +} + +void Http2Server::sendWINDOW_UPDATE(quint32 streamID, quint32 delta) +{ + Q_ASSERT(socket); + + writer.start(FrameType::WINDOW_UPDATE, FrameFlag::EMPTY, streamID); + writer.append(delta); + writer.write(*socket); +} + +void Http2Server::incomingConnection(qintptr socketDescriptor) +{ + if (clearTextHTTP2) { + socket.reset(new QTcpSocket); + const bool set = socket->setSocketDescriptor(socketDescriptor); + Q_ASSERT(set); + // Stop listening: + close(); + QMetaObject::invokeMethod(this, "connectionEstablished", + Qt::QueuedConnection); + } else { +#ifndef QT_NO_SSL + socket.reset(new QSslSocket); + QSslSocket *sslSocket = static_cast<QSslSocket *>(socket.data()); + // Add HTTP2 as supported protocol: + auto conf = QSslConfiguration::defaultConfiguration(); + auto protos = conf.allowedNextProtocols(); + protos.prepend(QSslConfiguration::ALPNProtocolHTTP2); + conf.setAllowedNextProtocols(protos); + sslSocket->setSslConfiguration(conf); + // SSL-related setup ... + sslSocket->setPeerVerifyMode(QSslSocket::VerifyNone); + sslSocket->setProtocol(QSsl::TlsV1_2OrLater); + connect(sslSocket, SIGNAL(sslErrors(QList<QSslError>)), + this, SLOT(ignoreErrorSlot())); + QFile file(SRCDIR "certs/fluke.key"); + file.open(QIODevice::ReadOnly); + QSslKey key(file.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey); + sslSocket->setPrivateKey(key); + auto localCert = QSslCertificate::fromPath(SRCDIR "certs/fluke.cert"); + sslSocket->setLocalCertificateChain(localCert); + sslSocket->setSocketDescriptor(socketDescriptor, QAbstractSocket::ConnectedState); + // Stop listening. + close(); + // Start SSL handshake and ALPN: + connect(sslSocket, SIGNAL(encrypted()), this, SLOT(connectionEstablished())); + sslSocket->startServerEncryption(); +#else + Q_UNREACHABLE(); +#endif + } +} + +quint32 Http2Server::clientSetting(Http2::Settings identifier, quint32 defaultValue) +{ + const auto it = expectedClientSettings.find(quint16(identifier)); + if (it != expectedClientSettings.end()) + return it->second; + return defaultValue; +} + +void Http2Server::connectionEstablished() +{ + using namespace Http2; + + connect(socket.data(), SIGNAL(readyRead()), + this, SLOT(readReady())); + + waitingClientPreface = true; + waitingClientAck = false; + waitingClientSettings = false; + settingsSent = false; + // We immediately send our settings so that our client + // can use flow control correctly. + sendServerSettings(); + + if (socket->bytesAvailable()) + readReady(); +} + +void Http2Server::ignoreErrorSlot() +{ +#ifndef QT_NO_SSL + static_cast<QSslSocket *>(socket.data())->ignoreSslErrors(); +#endif +} + +// Now HTTP2 "server" part: +/* +This code is overly simplified but it tests the basic HTTP2 expected behavior: +1. CONNECTION PREFACE +2. SETTINGS +3. sends our own settings (to modify the flow control) +4. collects and reports requests +5. if asked - sends responds to those requests +6. does some very basic error handling +7. tests frames validity/stream logic at the very basic level. +*/ + +void Http2Server::readReady() +{ + if (connectionError) + return; + + if (waitingClientPreface) { + handleConnectionPreface(); + } else { + const auto status = reader.read(*socket); + switch (status) { + case FrameStatus::incompleteFrame: + break; + case FrameStatus::goodFrame: + handleIncomingFrame(); + break; + default: + connectionError = true; + sendGOAWAY(connectionStreamID, PROTOCOL_ERROR, connectionStreamID); + } + } + + if (socket->bytesAvailable()) + QMetaObject::invokeMethod(this, "readReady", Qt::QueuedConnection); +} + +void Http2Server::handleConnectionPreface() +{ + Q_ASSERT(waitingClientPreface); + + if (socket->bytesAvailable() < clientPrefaceLength) + return; // Wait for more data ... + + char buf[clientPrefaceLength] = {}; + socket->read(buf, clientPrefaceLength); + if (std::memcmp(buf, Http2clientPreface, clientPrefaceLength)) { + sendGOAWAY(connectionStreamID, PROTOCOL_ERROR, connectionStreamID); + emit clientPrefaceError(); + connectionError = true; + return; + } + + waitingClientPreface = false; + waitingClientSettings = true; +} + +void Http2Server::handleIncomingFrame() +{ + // Frames that our implementation can send include: + // 1. SETTINGS (happens only during connection preface, + // handled already by this point) + // 2. SETTIGNS with ACK should be sent only as a response + // to a server's SETTINGS + // 3. HEADERS + // 4. CONTINUATION + // 5. DATA + // 6. PING + // 7. RST_STREAM + // 8. GOAWAY + + inboundFrame = std::move(reader.inboundFrame()); + + if (continuedRequest.size()) { + if (inboundFrame.type() != FrameType::CONTINUATION || + inboundFrame.streamID() != continuedRequest.front().streamID()) { + sendGOAWAY(connectionStreamID, PROTOCOL_ERROR, connectionStreamID); + emit invalidFrame(); + connectionError = true; + return; + } + } + + switch (inboundFrame.type()) { + case FrameType::SETTINGS: + handleSETTINGS(); + break; + case FrameType::HEADERS: + case FrameType::CONTINUATION: + continuedRequest.push_back(std::move(inboundFrame)); + processRequest(); + break; + case FrameType::DATA: + handleDATA(); + break; + case FrameType::RST_STREAM: + // TODO: this is not tested for now. + break; + case FrameType::PING: + // TODO: this is not tested for now. + break; + case FrameType::GOAWAY: + // TODO: this is not tested for now. + break; + case FrameType::WINDOW_UPDATE: + handleWINDOW_UPDATE(); + break; + default:; + } +} + +void Http2Server::handleSETTINGS() +{ + // SETTINGS is either a part of the connection preface, + // or a SETTINGS ACK. + Q_ASSERT(inboundFrame.type() == FrameType::SETTINGS); + + if (inboundFrame.flags().testFlag(FrameFlag::ACK)) { + if (!waitingClientAck || inboundFrame.dataSize()) { + emit invalidFrame(); + connectionError = true; + waitingClientAck = false; + return; + } + + waitingClientAck = false; + emit serverSettingsAcked(); + return; + } + + // QHttp2ProtocolHandler always sends some settings, + // and the size is a multiple of 6. + if (!inboundFrame.dataSize() || inboundFrame.dataSize() % 6) { + sendGOAWAY(connectionStreamID, FRAME_SIZE_ERROR, connectionStreamID); + emit clientPrefaceError(); + connectionError = true; + return; + } + + const uchar *src = inboundFrame.dataBegin(); + const uchar *end = src + inboundFrame.dataSize(); + + const auto notFound = expectedClientSettings.end(); + + while (src != end) { + const auto id = qFromBigEndian<quint16>(src); + const auto value = qFromBigEndian<quint32>(src + 2); + if (expectedClientSettings.find(id) == notFound || + expectedClientSettings[id] != value) { + emit clientPrefaceError(); + connectionError = true; + return; + } + + src += 6; + } + + // Send SETTINGS ACK: + writer.start(FrameType::SETTINGS, FrameFlag::ACK, connectionStreamID); + writer.write(*socket); + waitingClientSettings = false; + emit clientPrefaceOK(); +} + +void Http2Server::handleDATA() +{ + Q_ASSERT(inboundFrame.type() == FrameType::DATA); + + const auto streamID = inboundFrame.streamID(); + + if (!is_valid_client_stream(streamID) || + closedStreams.find(streamID) != closedStreams.end()) { + emit invalidFrame(); + connectionError = true; + sendGOAWAY(connectionStreamID, PROTOCOL_ERROR, connectionStreamID); + return; + } + + const auto payloadSize = inboundFrame.payloadSize(); + if (sessionCurrRecvWindow < payloadSize) { + // Client does not respect our session window size! + emit invalidRequest(streamID); + connectionError = true; + sendGOAWAY(connectionStreamID, FLOW_CONTROL_ERROR, connectionStreamID); + return; + } + + auto it = streamWindows.find(streamID); + if (it == streamWindows.end()) + it = streamWindows.insert(std::make_pair(streamID, streamRecvWindowSize)).first; + + + if (it->second < payloadSize) { + emit invalidRequest(streamID); + connectionError = true; + sendGOAWAY(connectionStreamID, FLOW_CONTROL_ERROR, connectionStreamID); + return; + } + + it->second -= payloadSize; + if (it->second < streamRecvWindowSize / 2) { + sendWINDOW_UPDATE(streamID, streamRecvWindowSize / 2); + it->second += streamRecvWindowSize / 2; + } + + sessionCurrRecvWindow -= payloadSize; + + if (sessionCurrRecvWindow < sessionRecvWindowSize / 2) { + // This is some quite naive and trivial logic on when to update. + + sendWINDOW_UPDATE(connectionStreamID, sessionRecvWindowSize / 2); + sessionCurrRecvWindow += sessionRecvWindowSize / 2; + } + + if (inboundFrame.flags().testFlag(FrameFlag::END_STREAM)) { + closedStreams.insert(streamID); // Enter "half-closed remote" state. + streamWindows.erase(it); + emit receivedData(streamID); + } +} + +void Http2Server::handleWINDOW_UPDATE() +{ + const auto streamID = inboundFrame.streamID(); + if (!streamID) // We ignore this for now to keep things simple. + return; + + if (streamID && suspendedStreams.find(streamID) == suspendedStreams.end()) { + if (closedStreams.find(streamID) == closedStreams.end()) { + sendRST_STREAM(streamID, PROTOCOL_ERROR); + emit invalidFrame(); + connectionError = true; + } + + return; + } + + const quint32 delta = qFromBigEndian<quint32>(inboundFrame.dataBegin()); + if (!delta || delta > quint32(std::numeric_limits<qint32>::max())) { + sendRST_STREAM(streamID, PROTOCOL_ERROR); + emit invalidFrame(); + connectionError = true; + return; + } + + emit windowUpdate(streamID); + sendDATA(streamID, delta); +} + +void Http2Server::sendResponse(quint32 streamID, bool emptyBody) +{ + Q_ASSERT(activeRequests.find(streamID) != activeRequests.end()); + + const quint32 maxFrameSize(clientSetting(Settings::MAX_FRAME_SIZE_ID, + Http2::maxFrameSize)); + + if (pushPromiseEnabled) { + // A real server supporting PUSH_PROMISE will probably first send + // PUSH_PROMISE and then a normal response (to a real request), + // so that a client parsing this response and discovering another + // resource it needs, will _already_ have this additional resource + // in PUSH_PROMISE. + lastPromisedStream += 2; + + writer.start(FrameType::PUSH_PROMISE, FrameFlag::END_HEADERS, streamID); + writer.append(lastPromisedStream); + + HttpHeader pushHeader; + fill_push_header(activeRequests[streamID], pushHeader); + pushHeader.push_back(HeaderField(":method", "GET")); + pushHeader.push_back(HeaderField(":path", pushPath)); + + // Now interesting part, let's make it into 'stream': + activeRequests[lastPromisedStream] = pushHeader; + + HPack::BitOStream ostream(writer.outboundFrame().buffer); + const bool result = encoder.encodeRequest(ostream, pushHeader); + Q_ASSERT(result); + + // Well, it's not HEADERS, it's PUSH_PROMISE with ... HEADERS block. + // Should work. + writer.writeHEADERS(*socket, maxFrameSize); + qDebug() << "server sent a PUSH_PROMISE on" << lastPromisedStream; + + if (responseBody.isEmpty()) + responseBody = QByteArray("I PROMISE (AND PUSH) YOU ..."); + + // Now we send this promised data as a normal response on our reserved + // stream (disabling PUSH_PROMISE for the moment to avoid recursion): + pushPromiseEnabled = false; + sendResponse(lastPromisedStream, false); + pushPromiseEnabled = true; + // Now we'll continue with _normal_ response. + } + + writer.start(FrameType::HEADERS, FrameFlag::END_HEADERS, streamID); + if (emptyBody) + writer.addFlag(FrameFlag::END_STREAM); + + HttpHeader header = {{":status", "200"}}; + if (!emptyBody) { + header.push_back(HPack::HeaderField("content-length", + QString("%1").arg(responseBody.size()).toLatin1())); + } + + HPack::BitOStream ostream(writer.outboundFrame().buffer); + const bool result = encoder.encodeResponse(ostream, header); + Q_ASSERT(result); + + writer.writeHEADERS(*socket, maxFrameSize); + + if (!emptyBody) { + Q_ASSERT(suspendedStreams.find(streamID) == suspendedStreams.end()); + + const quint32 windowSize = clientSetting(Settings::INITIAL_WINDOW_SIZE_ID, + Http2::defaultSessionWindowSize); + // Suspend to immediately resume it. + suspendedStreams[streamID] = 0; // start sending from offset 0 + sendDATA(streamID, windowSize); + } else { + activeRequests.erase(streamID); + closedStreams.insert(streamID); + } +} + +void Http2Server::processRequest() +{ + Q_ASSERT(continuedRequest.size()); + + if (!continuedRequest.back().flags().testFlag(FrameFlag::END_HEADERS)) + return; + + // We test here: + // 1. stream is 'idle'. + // 2. has priority set and dependency (it's 0x0 at the moment). + // 3. header can be decompressed. + const auto &headersFrame = continuedRequest.front(); + const auto streamID = headersFrame.streamID(); + if (!is_valid_client_stream(streamID)) { + emit invalidRequest(streamID); + connectionError = true; + sendGOAWAY(connectionStreamID, PROTOCOL_ERROR, connectionStreamID); + return; + } + + if (closedStreams.find(streamID) != closedStreams.end()) { + emit invalidFrame(); + connectionError = true; + sendGOAWAY(connectionStreamID, PROTOCOL_ERROR, connectionStreamID); + return; + } + + quint32 dep = 0; + uchar w = 0; + if (!headersFrame.priority(&dep, &w)) { + emit invalidFrame(); + sendRST_STREAM(streamID, PROTOCOL_ERROR); + return; + } + + // Assemble headers ... + quint32 totalSize = 0; + for (const auto &frame : continuedRequest) { + if (std::numeric_limits<quint32>::max() - frame.dataSize() < totalSize) { + // Resulted in overflow ... + emit invalidFrame(); + connectionError = true; + sendGOAWAY(connectionStreamID, PROTOCOL_ERROR, connectionStreamID); + return; + } + totalSize += frame.dataSize(); + } + + std::vector<uchar> hpackBlock(totalSize); + auto dst = hpackBlock.begin(); + for (const auto &frame : continuedRequest) { + if (!frame.dataSize()) + continue; + std::copy(frame.dataBegin(), frame.dataBegin() + frame.dataSize(), dst); + dst += frame.dataSize(); + } + + HPack::BitIStream inputStream{&hpackBlock[0], &hpackBlock[0] + hpackBlock.size()}; + + if (!decoder.decodeHeaderFields(inputStream)) { + emit decompressionFailed(streamID); + sendRST_STREAM(streamID, COMPRESSION_ERROR); + closedStreams.insert(streamID); + return; + } + + // Actually, if needed, we can do a comparison here. + activeRequests[streamID] = decoder.decodedHeader(); + if (headersFrame.flags().testFlag(FrameFlag::END_STREAM)) + emit receivedRequest(streamID); + // else - we're waiting for incoming DATA frames ... + continuedRequest.clear(); +} + +QT_END_NAMESPACE diff --git a/tests/auto/network/access/http2/http2srv.h b/tests/auto/network/access/http2/http2srv.h new file mode 100644 index 0000000000..15a4f212c9 --- /dev/null +++ b/tests/auto/network/access/http2/http2srv.h @@ -0,0 +1,172 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef HTTP2SRV_H +#define HTTP2SRV_H + +#include <QtNetwork/private/http2protocol_p.h> +#include <QtNetwork/private/http2frames_p.h> +#include <QtNetwork/private/hpack_p.h> + +#include <QtNetwork/qabstractsocket.h> +#include <QtCore/qscopedpointer.h> +#include <QtNetwork/qtcpserver.h> +#include <QtCore/qbytearray.h> +#include <QtCore/qglobal.h> + +#include <vector> +#include <map> +#include <set> + +QT_BEGIN_NAMESPACE + +struct Http2Setting +{ + Http2::Settings identifier; + quint32 value = 0; + + Http2Setting(Http2::Settings ident, quint32 v) + : identifier(ident), + value(v) + {} +}; + +using Http2Settings = std::vector<Http2Setting>; + +class Http2Server : public QTcpServer +{ + Q_OBJECT +public: + Http2Server(bool clearText, const Http2Settings &serverSettings, + const Http2Settings &clientSettings); + + ~Http2Server(); + + // To be called before server started: + void enablePushPromise(bool enabled, const QByteArray &path = QByteArray()); + void setResponseBody(const QByteArray &body); + + // Invokables, since we can call them from the main thread, + // but server (can) work on its own thread. + Q_INVOKABLE void startServer(); + Q_INVOKABLE void sendServerSettings(); + Q_INVOKABLE void sendGOAWAY(quint32 streamID, quint32 error, + quint32 lastStreamID); + Q_INVOKABLE void sendRST_STREAM(quint32 streamID, quint32 error); + Q_INVOKABLE void sendDATA(quint32 streamID, quint32 windowSize); + Q_INVOKABLE void sendWINDOW_UPDATE(quint32 streamID, quint32 delta); + + Q_INVOKABLE void handleConnectionPreface(); + Q_INVOKABLE void handleIncomingFrame(); + Q_INVOKABLE void handleSETTINGS(); + Q_INVOKABLE void handleDATA(); + Q_INVOKABLE void handleWINDOW_UPDATE(); + + Q_INVOKABLE void sendResponse(quint32 streamID, bool emptyBody); + +private: + void processRequest(); + +Q_SIGNALS: + void serverStarted(quint16 port); + // Error/success notifications: + void clientPrefaceOK(); + void clientPrefaceError(); + void serverSettingsAcked(); + void invalidFrame(); + void invalidRequest(quint32 streamID); + void decompressionFailed(quint32 streamID); + void receivedRequest(quint32 streamID); + void receivedData(quint32 streamID); + void windowUpdate(quint32 streamID); + +private slots: + void connectionEstablished(); + void readReady(); + +private: + void incomingConnection(qintptr socketDescriptor) Q_DECL_OVERRIDE; + + quint32 clientSetting(Http2::Settings identifier, quint32 defaultValue); + + QScopedPointer<QAbstractSocket> socket; + + // Connection preface: + bool waitingClientPreface = false; + bool waitingClientSettings = false; + bool settingsSent = false; + bool waitingClientAck = false; + + Http2Settings serverSettings; + std::map<quint16, quint32> expectedClientSettings; + + bool connectionError = false; + + Http2::FrameReader reader; + Http2::Frame inboundFrame; + Http2::FrameWriter writer; + + using FrameSequence = std::vector<Http2::Frame>; + FrameSequence continuedRequest; + + std::map<quint32, quint32> streamWindows; + + HPack::Decoder decoder{HPack::FieldLookupTable::DefaultSize}; + HPack::Encoder encoder{HPack::FieldLookupTable::DefaultSize, true}; + + using Http2Requests = std::map<quint32, HPack::HttpHeader>; + Http2Requests activeRequests; + // 'remote half-closed' streams to keep + // track of streams with END_STREAM set: + std::set<quint32> closedStreams; + // streamID + offset in response body to send. + std::map<quint32, quint32> suspendedStreams; + + // We potentially reset this once (see sendServerSettings) + // and do not change later: + quint32 sessionRecvWindowSize = Http2::defaultSessionWindowSize; + // This changes in the range [0, sessionRecvWindowSize] + // while handling DATA frames: + quint32 sessionCurrRecvWindow = sessionRecvWindowSize; + // This we potentially update only once (sendServerSettings). + quint32 streamRecvWindowSize = Http2::defaultSessionWindowSize; + + QByteArray responseBody; + bool clearTextHTTP2 = false; + bool pushPromiseEnabled = false; + quint32 lastPromisedStream = 0; + QByteArray pushPath; + +protected slots: + void ignoreErrorSlot(); +}; + +QT_END_NAMESPACE + +#endif + diff --git a/tests/auto/network/access/http2/tst_http2.cpp b/tests/auto/network/access/http2/tst_http2.cpp new file mode 100644 index 0000000000..771ddb01be --- /dev/null +++ b/tests/auto/network/access/http2/tst_http2.cpp @@ -0,0 +1,539 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> + +#include "http2srv.h" + +#include <QtNetwork/qnetworkaccessmanager.h> +#include <QtNetwork/qnetworkrequest.h> +#include <QtNetwork/qnetworkreply.h> +#include <QtCore/qglobal.h> +#include <QtCore/qobject.h> +#include <QtCore/qthread.h> +#include <QtCore/qurl.h> + +#ifndef QT_NO_SSL +#ifndef QT_NO_OPENSSL +#include <QtNetwork/private/qsslsocket_openssl_symbols_p.h> +#endif // NO_OPENSSL +#endif // NO_SSL + +#include <cstdlib> +#include <string> + +// At the moment our HTTP/2 imlpementation requires ALPN and this means OpenSSL. +#if !defined(QT_NO_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(OPENSSL_NO_TLSEXT) +const bool clearTextHTTP2 = false; +#else +const bool clearTextHTTP2 = true; +#endif + +QT_BEGIN_NAMESPACE + +class tst_Http2 : public QObject +{ + Q_OBJECT +public: + tst_Http2(); + ~tst_Http2(); +private slots: + // Tests: + void singleRequest(); + void multipleRequests(); + void flowControlClientSide(); + void flowControlServerSide(); + void pushPromise(); + +protected slots: + // Slots to listen to our in-process server: + void serverStarted(quint16 port); + void clientPrefaceOK(); + void clientPrefaceError(); + void serverSettingsAcked(); + void invalidFrame(); + void invalidRequest(quint32 streamID); + void decompressionFailed(quint32 streamID); + void receivedRequest(quint32 streamID); + void receivedData(quint32 streamID); + void windowUpdated(quint32 streamID); + void replyFinished(); + +private: + void clearHTTP2State(); + // Run event for 'ms' milliseconds. + // The default value '5000' is enough for + // small payload. + void runEventLoop(int ms = 5000); + void stopEventLoop(); + Http2Server *newServer(const Http2Settings &serverSettings, + const Http2Settings &clientSettings = defaultClientSettings); + // Send a get or post request, depending on a payload (empty or not). + void sendRequest(int streamNumber, + QNetworkRequest::Priority priority = QNetworkRequest::NormalPriority, + const QByteArray &payload = QByteArray()); + + quint16 serverPort = 0; + QThread *workerThread = nullptr; + QNetworkAccessManager manager; + + QEventLoop eventLoop; + QTimer timer; + + int nRequests = 0; + int nSentRequests = 0; + + int windowUpdates = 0; + bool prefaceOK = false; + bool serverGotSettingsACK = false; + + static const Http2Settings defaultServerSettings; + static const Http2Settings defaultClientSettings; +}; + +const Http2Settings tst_Http2::defaultServerSettings{{Http2::Settings::MAX_CONCURRENT_STREAMS_ID, 100}}; +const Http2Settings tst_Http2::defaultClientSettings{{Http2::Settings::MAX_FRAME_SIZE_ID, quint32(Http2::maxFrameSize)}, + {Http2::Settings::ENABLE_PUSH_ID, quint32(0)}}; + +namespace { + +// Our server lives/works on a different thread so we invoke its 'deleteLater' +// instead of simple 'delete'. +struct ServerDeleter +{ + static void cleanup(Http2Server *srv) + { + if (srv) + QMetaObject::invokeMethod(srv, "deleteLater", Qt::QueuedConnection); + } +}; + +using ServerPtr = QScopedPointer<Http2Server, ServerDeleter>; + +struct EnvVarGuard +{ + EnvVarGuard(const char *name, const QByteArray &value) + : varName(name), + prevValue(qgetenv(name)) + { + Q_ASSERT(name); + qputenv(name, value); + } + ~EnvVarGuard() + { + if (prevValue.size()) + qputenv(varName.c_str(), prevValue); + else + qunsetenv(varName.c_str()); + } + + const std::string varName; + const QByteArray prevValue; +}; + +} // unnamed namespace + +tst_Http2::tst_Http2() + : workerThread(new QThread) +{ + workerThread->start(); + + timer.setInterval(10000); + timer.setSingleShot(true); + + connect(&timer, SIGNAL(timeout()), &eventLoop, SLOT(quit())); +} + +tst_Http2::~tst_Http2() +{ + workerThread->quit(); + workerThread->wait(5000); + + if (workerThread->isFinished()) { + delete workerThread; + } else { + connect(workerThread, &QThread::finished, + workerThread, &QThread::deleteLater); + } +} + +void tst_Http2::singleRequest() +{ + clearHTTP2State(); + + serverPort = 0; + nRequests = 1; + + ServerPtr srv(newServer(defaultServerSettings)); + + QMetaObject::invokeMethod(srv.data(), "startServer", Qt::QueuedConnection); + runEventLoop(); + + QVERIFY(serverPort != 0); + + const QString urlAsString(clearTextHTTP2 ? QString("http://127.0.0.1:%1/index.html") + : QString("https://127.0.0.1:%1/index.html")); + const QUrl url(urlAsString.arg(serverPort)); + + QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, QVariant(true)); + + auto reply = manager.get(request); + connect(reply, &QNetworkReply::finished, this, &tst_Http2::replyFinished); + // Since we're using self-signed certificates, + // ignore SSL errors: + reply->ignoreSslErrors(); + + runEventLoop(); + + QVERIFY(nRequests == 0); + QVERIFY(prefaceOK); + QVERIFY(serverGotSettingsACK); + + QCOMPARE(reply->error(), QNetworkReply::NoError); + QVERIFY(reply->isFinished()); +} + +void tst_Http2::multipleRequests() +{ + clearHTTP2State(); + + serverPort = 0; + nRequests = 10; + + ServerPtr srv(newServer(defaultServerSettings)); + + QMetaObject::invokeMethod(srv.data(), "startServer", Qt::QueuedConnection); + + runEventLoop(); + QVERIFY(serverPort != 0); + + // Just to make the order a bit more interesting + // we'll index this randomly: + QNetworkRequest::Priority priorities[] = {QNetworkRequest::HighPriority, + QNetworkRequest::NormalPriority, + QNetworkRequest::LowPriority}; + + for (int i = 0; i < nRequests; ++i) + sendRequest(i, priorities[std::rand() % 3]); + + runEventLoop(); + + QVERIFY(nRequests == 0); + QVERIFY(prefaceOK); + QVERIFY(serverGotSettingsACK); +} + +void tst_Http2::flowControlClientSide() +{ + // Create a server but impose limits: + // 1. Small MAX frame size, so we test CONTINUATION frames. + // 2. Small client windows so server responses cause client streams + // to suspend and server sends WINDOW_UPDATE frames. + // 3. Few concurrent streams, to test protocol handler can resume + // suspended requests. + using namespace Http2; + + clearHTTP2State(); + + serverPort = 0; + nRequests = 10; + windowUpdates = 0; + + const Http2Settings serverSettings = {{Settings::MAX_CONCURRENT_STREAMS_ID, 3}}; + + ServerPtr srv(newServer(serverSettings)); + + const QByteArray respond(int(Http2::defaultSessionWindowSize * 50), 'x'); + srv->setResponseBody(respond); + + QMetaObject::invokeMethod(srv.data(), "startServer", Qt::QueuedConnection); + + runEventLoop(); + QVERIFY(serverPort != 0); + + for (int i = 0; i < nRequests; ++i) + sendRequest(i); + + runEventLoop(120000); + + QVERIFY(nRequests == 0); + QVERIFY(prefaceOK); + QVERIFY(serverGotSettingsACK); + QVERIFY(windowUpdates > 0); +} + +void tst_Http2::flowControlServerSide() +{ + // Quite aggressive test: + // low MAX_FRAME_SIZE forces a lot of small DATA frames, + // payload size exceedes stream/session RECV window sizes + // so that our implementation should deal with WINDOW_UPDATE + // on a session/stream level correctly + resume/suspend streams + // to let all replies finish without any error. + using namespace Http2; + + clearHTTP2State(); + + serverPort = 0; + nRequests = 30; + + const Http2Settings serverSettings = {{Settings::MAX_CONCURRENT_STREAMS_ID, 7}}; + + ServerPtr srv(newServer(serverSettings)); + + const QByteArray payload(int(Http2::defaultSessionWindowSize * 500), 'x'); + + QMetaObject::invokeMethod(srv.data(), "startServer", Qt::QueuedConnection); + + runEventLoop(); + QVERIFY(serverPort != 0); + + for (int i = 0; i < nRequests; ++i) + sendRequest(i, QNetworkRequest::NormalPriority, payload); + + runEventLoop(120000); + + QVERIFY(nRequests == 0); + QVERIFY(prefaceOK); + QVERIFY(serverGotSettingsACK); +} + +void tst_Http2::pushPromise() +{ + // We will first send some request, the server should reply and also emulate + // PUSH_PROMISE sending us another response as promised. + using namespace Http2; + + clearHTTP2State(); + + serverPort = 0; + nRequests = 1; + + const EnvVarGuard env("QT_HTTP2_ENABLE_PUSH_PROMISE", "1"); + const Http2Settings clientSettings{{Settings::MAX_FRAME_SIZE_ID, quint32(Http2::maxFrameSize)}, + {Settings::ENABLE_PUSH_ID, quint32(1)}}; + + ServerPtr srv(newServer(defaultServerSettings, clientSettings)); + srv->enablePushPromise(true, QByteArray("/script.js")); + + QMetaObject::invokeMethod(srv.data(), "startServer", Qt::QueuedConnection); + runEventLoop(); + + QVERIFY(serverPort != 0); + + const QString urlAsString((clearTextHTTP2 ? QString("http://127.0.0.1:%1/") + : QString("https://127.0.0.1:%1/")).arg(serverPort)); + const QUrl requestUrl(urlAsString + "index.html"); + + QNetworkRequest request(requestUrl); + request.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, QVariant(true)); + + auto reply = manager.get(request); + connect(reply, &QNetworkReply::finished, this, &tst_Http2::replyFinished); + // Since we're using self-signed certificates, ignore SSL errors: + reply->ignoreSslErrors(); + + runEventLoop(); + + QVERIFY(nRequests == 0); + QVERIFY(prefaceOK); + QVERIFY(serverGotSettingsACK); + + QCOMPARE(reply->error(), QNetworkReply::NoError); + QVERIFY(reply->isFinished()); + + // Now, the most interesting part! + nSentRequests = 0; + nRequests = 1; + // Create an additional request (let's say, we parsed reply and realized we + // need another resource): + + const QUrl promisedUrl(urlAsString + "script.js"); + QNetworkRequest promisedRequest(promisedUrl); + promisedRequest.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, QVariant(true)); + reply = manager.get(promisedRequest); + connect(reply, &QNetworkReply::finished, this, &tst_Http2::replyFinished); + reply->ignoreSslErrors(); + + runEventLoop(); + + // Let's check that NO request was actually made: + QCOMPARE(nSentRequests, 0); + // Decreased by replyFinished(): + QCOMPARE(nRequests, 0); + QCOMPARE(reply->error(), QNetworkReply::NoError); + QVERIFY(reply->isFinished()); +} + +void tst_Http2::serverStarted(quint16 port) +{ + serverPort = port; + stopEventLoop(); +} + +void tst_Http2::clearHTTP2State() +{ + windowUpdates = 0; + prefaceOK = false; + serverGotSettingsACK = false; +} + +void tst_Http2::runEventLoop(int ms) +{ + timer.setInterval(ms); + timer.start(); + eventLoop.exec(); +} + +void tst_Http2::stopEventLoop() +{ + timer.stop(); + eventLoop.quit(); +} + +Http2Server *tst_Http2::newServer(const Http2Settings &serverSettings, + const Http2Settings &clientSettings) +{ + using namespace Http2; + auto srv = new Http2Server(clearTextHTTP2, serverSettings, clientSettings); + + using Srv = Http2Server; + using Cl = tst_Http2; + + connect(srv, &Srv::serverStarted, this, &Cl::serverStarted); + connect(srv, &Srv::clientPrefaceOK, this, &Cl::clientPrefaceOK); + connect(srv, &Srv::clientPrefaceError, this, &Cl::clientPrefaceError); + connect(srv, &Srv::serverSettingsAcked, this, &Cl::serverSettingsAcked); + connect(srv, &Srv::invalidFrame, this, &Cl::invalidFrame); + connect(srv, &Srv::invalidRequest, this, &Cl::invalidRequest); + connect(srv, &Srv::receivedRequest, this, &Cl::receivedRequest); + connect(srv, &Srv::receivedData, this, &Cl::receivedData); + connect(srv, &Srv::windowUpdate, this, &Cl::windowUpdated); + + srv->moveToThread(workerThread); + + return srv; +} + +void tst_Http2::sendRequest(int streamNumber, + QNetworkRequest::Priority priority, + const QByteArray &payload) +{ + static const QString urlAsString(clearTextHTTP2 ? "http://127.0.0.1:%1/stream%2.html" + : "https://127.0.0.1:%1/stream%2.html"); + + const QUrl url(urlAsString.arg(serverPort).arg(streamNumber)); + QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, QVariant(true)); + request.setPriority(priority); + + QNetworkReply *reply = nullptr; + if (payload.size()) + reply = manager.post(request, payload); + else + reply = manager.get(request); + + reply->ignoreSslErrors(); + connect(reply, &QNetworkReply::finished, this, &tst_Http2::replyFinished); +} + +void tst_Http2::clientPrefaceOK() +{ + prefaceOK = true; +} + +void tst_Http2::clientPrefaceError() +{ + prefaceOK = false; +} + +void tst_Http2::serverSettingsAcked() +{ + serverGotSettingsACK = true; +} + +void tst_Http2::invalidFrame() +{ +} + +void tst_Http2::invalidRequest(quint32 streamID) +{ + Q_UNUSED(streamID) +} + +void tst_Http2::decompressionFailed(quint32 streamID) +{ + Q_UNUSED(streamID) +} + +void tst_Http2::receivedRequest(quint32 streamID) +{ + ++nSentRequests; + qDebug() << " server got a request on stream" << streamID; + Http2Server *srv = qobject_cast<Http2Server *>(sender()); + Q_ASSERT(srv); + QMetaObject::invokeMethod(srv, "sendResponse", Qt::QueuedConnection, + Q_ARG(quint32, streamID), + Q_ARG(bool, false /*non-empty body*/)); +} + +void tst_Http2::receivedData(quint32 streamID) +{ + qDebug() << " server got a 'POST' request on stream" << streamID; + Http2Server *srv = qobject_cast<Http2Server *>(sender()); + Q_ASSERT(srv); + QMetaObject::invokeMethod(srv, "sendResponse", Qt::QueuedConnection, + Q_ARG(quint32, streamID), + Q_ARG(bool, true /*HEADERS only*/)); +} + +void tst_Http2::windowUpdated(quint32 streamID) +{ + Q_UNUSED(streamID) + + ++windowUpdates; +} + +void tst_Http2::replyFinished() +{ + QVERIFY(nRequests); + + if (const auto reply = qobject_cast<QNetworkReply *>(sender())) + QCOMPARE(reply->error(), QNetworkReply::NoError); + + --nRequests; + if (!nRequests) + stopEventLoop(); +} + +QT_END_NAMESPACE + +QTEST_MAIN(tst_Http2) + +#include "tst_http2.moc" diff --git a/tests/auto/network/access/qftp/qftp.pro b/tests/auto/network/access/qftp/qftp.pro index 4294f27e74..1959c1acac 100644 --- a/tests/auto/network/access/qftp/qftp.pro +++ b/tests/auto/network/access/qftp/qftp.pro @@ -2,12 +2,5 @@ CONFIG += testcase TARGET = tst_qftp SOURCES += tst_qftp.cpp -requires(contains(QT_CONFIG,private_tests)) +requires(qtConfig(private_tests)) QT = core network network-private testlib - -wince { - addFiles.files = rfc3252.txt - addFiles.path = . - DEPLOYMENT += addFiles -} - diff --git a/tests/auto/network/access/qftp/tst_qftp.cpp b/tests/auto/network/access/qftp/tst_qftp.cpp index edeb471401..a13fa86405 100644 --- a/tests/auto/network/access/qftp/tst_qftp.cpp +++ b/tests/auto/network/access/qftp/tst_qftp.cpp @@ -276,14 +276,9 @@ void tst_QFtp::init() inFileDirExistsFunction = false; -#if !defined(Q_OS_WINCE) srand(time(0)); uniqueExtension = QString::number((quintptr)this) + QString::number(rand()) + QString::number((qulonglong)time(0)); -#else - srand(0); - uniqueExtension = QString::number((quintptr)this) + QString::number(rand()) + QLatin1Char('0'); -#endif } void tst_QFtp::cleanup() @@ -1353,11 +1348,7 @@ void tst_QFtp::abort_data() QTest::newRow( "get_fluke02" ) << QtNetworkSettings::serverName() << (uint)21 << QString("qtest/rfc3252") << QByteArray(); // Qt/CE test environment has too little memory for this test -#if !defined(Q_OS_WINCE) QByteArray bigData( 10*1024*1024, 0 ); -#else - QByteArray bigData( 1*1024*1024, 0 ); -#endif bigData.fill( 'B' ); QTest::newRow( "put_fluke01" ) << QtNetworkSettings::serverName() << (uint)21 << QString("qtest/upload/abort_put") << bigData; } diff --git a/tests/auto/network/access/qhttpnetworkconnection/qhttpnetworkconnection.pro b/tests/auto/network/access/qhttpnetworkconnection/qhttpnetworkconnection.pro index bd20fd33dd..d32b651b86 100644 --- a/tests/auto/network/access/qhttpnetworkconnection/qhttpnetworkconnection.pro +++ b/tests/auto/network/access/qhttpnetworkconnection/qhttpnetworkconnection.pro @@ -1,6 +1,6 @@ CONFIG += testcase TARGET = tst_qhttpnetworkconnection SOURCES += tst_qhttpnetworkconnection.cpp -requires(contains(QT_CONFIG,private_tests)) +requires(qtConfig(private_tests)) QT = core-private network-private testlib diff --git a/tests/auto/network/access/qhttpnetworkconnection/tst_qhttpnetworkconnection.cpp b/tests/auto/network/access/qhttpnetworkconnection/tst_qhttpnetworkconnection.cpp index ef742aaa9a..84766f5484 100644 --- a/tests/auto/network/access/qhttpnetworkconnection/tst_qhttpnetworkconnection.cpp +++ b/tests/auto/network/access/qhttpnetworkconnection/tst_qhttpnetworkconnection.cpp @@ -151,14 +151,7 @@ void tst_QHttpNetworkConnection::head() QHttpNetworkRequest request(protocol + host + path, QHttpNetworkRequest::Head); QHttpNetworkReply *reply = connection.sendRequest(request); - QTime stopWatch; - stopWatch.start(); - do { - QCoreApplication::instance()->processEvents(); - if (stopWatch.elapsed() >= 30000) - break; - } while (!reply->isFinished()); - + QTRY_VERIFY_WITH_TIMEOUT(reply->isFinished(), 30000); QCOMPARE(reply->statusCode(), statusCode); QCOMPARE(reply->reasonPhrase(), statusString); // only check it if it is set and expected @@ -208,15 +201,7 @@ void tst_QHttpNetworkConnection::get() QHttpNetworkRequest request(protocol + host + path); QHttpNetworkReply *reply = connection.sendRequest(request); - QTime stopWatch; - stopWatch.start(); - forever { - QCoreApplication::instance()->processEvents(); - if (reply->bytesAvailable()) - break; - if (stopWatch.elapsed() >= 30000) - break; - } + QTRY_VERIFY_WITH_TIMEOUT(reply->bytesAvailable(), 30000); QCOMPARE(reply->statusCode(), statusCode); QCOMPARE(reply->reasonPhrase(), statusString); @@ -224,17 +209,8 @@ void tst_QHttpNetworkConnection::get() if (reply->contentLength() != -1 && contentLength != -1) QCOMPARE(reply->contentLength(), qint64(contentLength)); - stopWatch.start(); - QByteArray ba; - do { - QCoreApplication::instance()->processEvents(); - while (reply->bytesAvailable()) - ba += reply->readAny(); - if (stopWatch.elapsed() >= 30000) - break; - } while (!reply->isFinished()); - - QVERIFY(reply->isFinished()); + QTRY_VERIFY_WITH_TIMEOUT(reply->isFinished(), 30000); + QByteArray ba = reply->readAll(); //do not require server generated error pages to be a fixed size if (downloadSize != -1) QCOMPARE(ba.size(), downloadSize); @@ -303,13 +279,7 @@ void tst_QHttpNetworkConnection::put() connect(reply, SIGNAL(finishedWithError(QNetworkReply::NetworkError,QString)), SLOT(finishedWithError(QNetworkReply::NetworkError,QString))); - QTime stopWatch; - stopWatch.start(); - do { - QCoreApplication::instance()->processEvents(); - if (stopWatch.elapsed() >= 30000) - break; - } while (!reply->isFinished() && !finishedCalled && !finishedWithErrorCalled); + QTRY_VERIFY_WITH_TIMEOUT(reply->isFinished() || finishedCalled || finishedWithErrorCalled, 30000); if (reply->isFinished()) { QByteArray ba; @@ -385,16 +355,7 @@ void tst_QHttpNetworkConnection::post() QHttpNetworkReply *reply = connection.sendRequest(request); - QTime stopWatch; - stopWatch.start(); - forever { - QCoreApplication::instance()->processEvents(); - if (reply->bytesAvailable()) - break; - if (stopWatch.elapsed() >= 30000) - break; - } - + QTRY_VERIFY_WITH_TIMEOUT(reply->bytesAvailable(), 30000); QCOMPARE(reply->statusCode(), statusCode); QCOMPARE(reply->reasonPhrase(), statusString); @@ -411,17 +372,8 @@ void tst_QHttpNetworkConnection::post() } } - stopWatch.start(); - QByteArray ba; - do { - QCoreApplication::instance()->processEvents(); - while (reply->bytesAvailable()) - ba += reply->readAny(); - if (stopWatch.elapsed() >= 30000) - break; - } while (!reply->isFinished()); - - QVERIFY(reply->isFinished()); + QTRY_VERIFY_WITH_TIMEOUT(reply->isFinished(), 30000); + QByteArray ba = reply->readAll(); //don't require fixed size for generated error pages if (downloadSize != -1) QCOMPARE(ba.size(), downloadSize); @@ -536,17 +488,7 @@ void tst_QHttpNetworkConnection::get401() connect(reply, SIGNAL(finishedWithError(QNetworkReply::NetworkError,QString)), SLOT(finishedWithError(QNetworkReply::NetworkError,QString))); - QTime stopWatch; - stopWatch.start(); - forever { - QCoreApplication::instance()->processEvents(); - if (finishedCalled) - break; - if (finishedWithErrorCalled) - break; - if (stopWatch.elapsed() >= 30000) - break; - } + QTRY_VERIFY_WITH_TIMEOUT(finishedCalled || finishedWithErrorCalled, 30000); QCOMPARE(reply->statusCode(), statusCode); delete reply; } @@ -595,16 +537,8 @@ void tst_QHttpNetworkConnection::compression() if (!autoCompress) request.setHeaderField("Accept-Encoding", contentCoding.toLatin1()); QHttpNetworkReply *reply = connection.sendRequest(request); - QTime stopWatch; - stopWatch.start(); - forever { - QCoreApplication::instance()->processEvents(); - if (reply->bytesAvailable()) - break; - if (stopWatch.elapsed() >= 30000) - break; - } + QTRY_VERIFY_WITH_TIMEOUT(reply->bytesAvailable(), 30000); QCOMPARE(reply->statusCode(), statusCode); QCOMPARE(reply->reasonPhrase(), statusString); bool isLengthOk = (reply->contentLength() == qint64(contentLength) @@ -613,17 +547,8 @@ void tst_QHttpNetworkConnection::compression() QVERIFY(isLengthOk); - stopWatch.start(); - QByteArray ba; - do { - QCoreApplication::instance()->processEvents(); - while (reply->bytesAvailable()) - ba += reply->readAny(); - if (stopWatch.elapsed() >= 30000) - break; - } while (!reply->isFinished()); - - QVERIFY(reply->isFinished()); + QTRY_VERIFY_WITH_TIMEOUT(reply->isFinished(), 30000); + QByteArray ba = reply->readAll(); QCOMPARE(ba.size(), downloadSize); delete reply; @@ -694,17 +619,7 @@ void tst_QHttpNetworkConnection::ignoresslerror() connect(reply, SIGNAL(finished()), SLOT(finishedReply())); - QTime stopWatch; - stopWatch.start(); - forever { - QCoreApplication::instance()->processEvents(); - if (reply->bytesAvailable()) - break; - if (statusCode == 100 && finishedWithErrorCalled) - break; - if (stopWatch.elapsed() >= 30000) - break; - } + QTRY_VERIFY_WITH_TIMEOUT(reply->bytesAvailable() || (statusCode == 100 && finishedWithErrorCalled), 30000); QCOMPARE(reply->statusCode(), statusCode); delete reply; } @@ -746,15 +661,7 @@ void tst_QHttpNetworkConnection::nossl() connect(reply, SIGNAL(finishedWithError(QNetworkReply::NetworkError,QString)), SLOT(finishedWithError(QNetworkReply::NetworkError,QString))); - QTime stopWatch; - stopWatch.start(); - forever { - QCoreApplication::instance()->processEvents(); - if (finishedWithErrorCalled) - break; - if (stopWatch.elapsed() >= 30000) - break; - } + QTRY_VERIFY_WITH_TIMEOUT(finishedWithErrorCalled, 30000); QCOMPARE(netErrorCode, networkError); delete reply; } @@ -774,6 +681,15 @@ void tst_QHttpNetworkConnection::getMultiple_data() QTest::newRow("1 connection, pipelining allowed, 100 requests") << quint16(1) << true << 100; } +static bool allRepliesFinished(const QList<QHttpNetworkReply*> *_replies) +{ + const QList<QHttpNetworkReply*> &replies = *_replies; + for (int i = 0; i < replies.length(); i++) + if (!replies.at(i)->isFinished()) + return false; + return true; +} + void tst_QHttpNetworkConnection::getMultiple() { QFETCH(quint16, connectionCount); @@ -797,27 +713,7 @@ void tst_QHttpNetworkConnection::getMultiple() replies.append(reply); } - QTime stopWatch; - stopWatch.start(); - int finishedCount = 0; - do { - QCoreApplication::instance()->processEvents(); - if (stopWatch.elapsed() >= 60000) - break; - - finishedCount = 0; - for (int i = 0; i < replies.length(); i++) - if (replies.at(i)->isFinished()) - finishedCount++; - - } while (finishedCount != replies.length()); - - // redundant - for (int i = 0; i < replies.length(); i++) - QVERIFY(replies.at(i)->isFinished()); - - qDebug() << "===" << stopWatch.elapsed() << "msec ==="; - + QTRY_VERIFY_WITH_TIMEOUT(allRepliesFinished(&replies), 60000); qDeleteAll(requests); qDeleteAll(replies); } @@ -854,24 +750,10 @@ void tst_QHttpNetworkConnection::getMultipleWithPipeliningAndMultiplePriorities( replies.append(reply); } - QTime stopWatch; - stopWatch.start(); - int finishedCount = 0; - do { - QCoreApplication::instance()->processEvents(); - if (stopWatch.elapsed() >= 60000) - break; - - finishedCount = 0; - for (int i = 0; i < replies.length(); i++) - if (replies.at(i)->isFinished()) - finishedCount++; - - } while (finishedCount != replies.length()); + QTRY_VERIFY_WITH_TIMEOUT(allRepliesFinished(&replies), 60000); int pipelinedCount = 0; for (int i = 0; i < replies.length(); i++) { - QVERIFY(replies.at(i)->isFinished()); QVERIFY (!(replies.at(i)->request().isPipeliningAllowed() == false && replies.at(i)->isPipeliningUsed())); @@ -885,8 +767,6 @@ void tst_QHttpNetworkConnection::getMultipleWithPipeliningAndMultiplePriorities( // requests had been pipelined) QVERIFY(pipelinedCount >= requestCount / 2); - qDebug() << "===" << stopWatch.elapsed() << "msec ==="; - qDeleteAll(requests); qDeleteAll(replies); } @@ -1062,17 +942,7 @@ void tst_QHttpNetworkConnection::getAndThenDeleteObject() QHttpNetworkReply *reply = connection->sendRequest(request); reply->setDownstreamLimited(true); - QTime stopWatch; - stopWatch.start(); - forever { - QCoreApplication::instance()->processEvents(); - if (reply->bytesAvailable()) - break; - if (stopWatch.elapsed() >= 30000) - break; - } - - QVERIFY(reply->bytesAvailable()); + QTRY_VERIFY_WITH_TIMEOUT(reply->bytesAvailable(), 30000); QCOMPARE(reply->statusCode() ,200); QVERIFY(!reply->isFinished()); // must not be finished diff --git a/tests/auto/network/access/qhttpnetworkreply/qhttpnetworkreply.pro b/tests/auto/network/access/qhttpnetworkreply/qhttpnetworkreply.pro index 1810a38f6e..31570e6f01 100644 --- a/tests/auto/network/access/qhttpnetworkreply/qhttpnetworkreply.pro +++ b/tests/auto/network/access/qhttpnetworkreply/qhttpnetworkreply.pro @@ -1,6 +1,6 @@ CONFIG += testcase TARGET = tst_qhttpnetworkreply SOURCES += tst_qhttpnetworkreply.cpp -requires(contains(QT_CONFIG,private_tests)) +requires(qtConfig(private_tests)) QT = core-private network-private testlib diff --git a/tests/auto/network/access/qnetworkcookie/tst_qnetworkcookie.cpp b/tests/auto/network/access/qnetworkcookie/tst_qnetworkcookie.cpp index 0424fc47ed..96c4917473 100644 --- a/tests/auto/network/access/qnetworkcookie/tst_qnetworkcookie.cpp +++ b/tests/auto/network/access/qnetworkcookie/tst_qnetworkcookie.cpp @@ -46,33 +46,6 @@ private slots: void parseMultipleCookies(); }; -QT_BEGIN_NAMESPACE - -namespace QTest { - template<> - char *toString(const QNetworkCookie &cookie) - { - return qstrdup(cookie.toRawForm()); - } - - template<> - char *toString(const QList<QNetworkCookie> &list) - { - QByteArray result = "QList("; - bool first = true; - foreach (QNetworkCookie cookie, list) { - if (!first) - result += ", "; - first = false; - result += "QNetworkCookie(" + cookie.toRawForm() + ')'; - } - result.append(')'); - return qstrdup(result.constData()); - } -} - -QT_END_NAMESPACE - void tst_QNetworkCookie::getterSetter() { QNetworkCookie cookie; diff --git a/tests/auto/network/access/qnetworkcookiejar/tst_qnetworkcookiejar.cpp b/tests/auto/network/access/qnetworkcookiejar/tst_qnetworkcookiejar.cpp index 849e2d8662..a0459021be 100644 --- a/tests/auto/network/access/qnetworkcookiejar/tst_qnetworkcookiejar.cpp +++ b/tests/auto/network/access/qnetworkcookiejar/tst_qnetworkcookiejar.cpp @@ -55,34 +55,6 @@ private slots: void rfc6265(); }; -QT_BEGIN_NAMESPACE - -namespace QTest { - template<> - char *toString(const QNetworkCookie &cookie) - { - return qstrdup(cookie.toRawForm()); - } - - template<> - char *toString(const QList<QNetworkCookie> &list) - { - QByteArray result = "QList("; - bool first = true; - foreach (QNetworkCookie cookie, list) { - if (!first) - result += ", "; - first = false; - result += "QNetworkCookie(" + cookie.toRawForm() + ')'; - } - - result.append(')'); - return qstrdup(result.constData()); - } -} - -QT_END_NAMESPACE - class MyCookieJar: public QNetworkCookieJar { public: diff --git a/tests/auto/network/access/qnetworkreply/qnetworkreply.pro b/tests/auto/network/access/qnetworkreply/qnetworkreply.pro index bd10c77252..d3a92436ac 100644 --- a/tests/auto/network/access/qnetworkreply/qnetworkreply.pro +++ b/tests/auto/network/access/qnetworkreply/qnetworkreply.pro @@ -1,5 +1,5 @@ TEMPLATE = subdirs -!winrt:!wince: SUBDIRS += echo +!winrt:SUBDIRS += echo test.depends += $$SUBDIRS SUBDIRS += test diff --git a/tests/auto/network/access/qnetworkreply/test/test.pro b/tests/auto/network/access/qnetworkreply/test/test.pro index 772bb55990..45a5734305 100644 --- a/tests/auto/network/access/qnetworkreply/test/test.pro +++ b/tests/auto/network/access/qnetworkreply/test/test.pro @@ -5,12 +5,13 @@ SOURCES += ../tst_qnetworkreply.cpp TARGET = ../tst_qnetworkreply QT = core-private network-private testlib +QT_FOR_CONFIG += gui-private RESOURCES += ../qnetworkreply.qrc TESTDATA += ../empty ../rfc3252.txt ../resource ../bigfile ../*.jpg ../certs \ ../index.html ../smb-file.txt -contains(QT_CONFIG,xcb): CONFIG+=insignificant_test # unstable, QTBUG-21102 +qtConfig(xcb): CONFIG+=insignificant_test # unstable, QTBUG-21102 win32:CONFIG += insignificant_test # QTBUG-24226 !winrt: TEST_HELPER_INSTALLS = ../echo/echo diff --git a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp index b75b6d5df0..649278d48b 100644 --- a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp +++ b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp @@ -205,6 +205,7 @@ private Q_SLOTS: void invalidProtocol(); void getFromData_data(); void getFromData(); + void getFromFile_data(); void getFromFile(); void getFromFileSpecial_data(); void getFromFileSpecial(); @@ -489,45 +490,6 @@ private: bool tst_QNetworkReply::seedCreated = false; -QT_BEGIN_NAMESPACE - -namespace QTest { - template<> - char *toString(const QNetworkReply::NetworkError& code) - { - const QMetaObject *mo = &QNetworkReply::staticMetaObject; - int index = mo->indexOfEnumerator("NetworkError"); - if (index == -1) - return qstrdup(""); - - QMetaEnum qme = mo->enumerator(index); - return qstrdup(qme.valueToKey(code)); - } - - template<> - char *toString(const QNetworkCookie &cookie) - { - return qstrdup(cookie.toRawForm()); - } - - template<> - char *toString(const QList<QNetworkCookie> &list) - { - QByteArray result = "QList("; - bool first = true; - foreach (QNetworkCookie cookie, list) { - if (!first) - result += ", "; - first = false; - result += "QNetworkCookie(" + cookie.toRawForm() + ')'; - } - result.append(')'); - return qstrdup(result.constData()); - } -} - -QT_END_NAMESPACE - #define RUN_REQUEST(call) \ do { \ QString errorMsg = call; \ @@ -650,8 +612,10 @@ private slots: #endif void slotError(QAbstractSocket::SocketError err) { - Q_ASSERT(!client.isNull()); - qDebug() << "slotError" << err << client->errorString(); + if (client.isNull()) + qDebug() << "slotError" << err; + else + qDebug() << "slotError" << err << client->errorString(); } public slots: @@ -1674,14 +1638,26 @@ void tst_QNetworkReply::getFromData() QCOMPARE(reply->readAll(), expected); } +void tst_QNetworkReply::getFromFile_data() +{ + QTest::addColumn<bool>("backgroundAttribute"); + + QTest::newRow("no-background-attribute") << false; + QTest::newRow("background-attribute") << true; +} + void tst_QNetworkReply::getFromFile() { + QFETCH(bool, backgroundAttribute); + // create the file: QTemporaryFile file(QDir::currentPath() + "/temp-XXXXXX"); file.setAutoRemove(true); QVERIFY2(file.open(), qPrintable(file.errorString())); QNetworkRequest request(QUrl::fromLocalFile(file.fileName())); + if (backgroundAttribute) + request.setAttribute(QNetworkRequest::BackgroundRequestAttribute, QVariant::fromValue(true)); QNetworkReplyPtr reply; static const char fileData[] = "This is some data that is in the file.\r\n"; @@ -1691,6 +1667,7 @@ void tst_QNetworkReply::getFromFile() QCOMPARE(file.size(), qint64(data.size())); RUN_REQUEST(runSimpleRequest(QNetworkAccessManager::GetOperation, request, reply)); + QVERIFY(waitForFinish(reply) != Timeout); QCOMPARE(reply->url(), request.url()); QCOMPARE(reply->error(), QNetworkReply::NoError); @@ -4319,9 +4296,6 @@ void tst_QNetworkReply::ioPutToFileFromProcess() QSKIP("No qprocess support", SkipAll); #else -#if defined(Q_OS_WINCE) - QSKIP("Currently no stdin/out supported for Windows CE"); -#else #ifdef Q_OS_WIN if (qstrcmp(QTest::currentDataTag(), "small") == 0) QSKIP("When passing a CR-LF-LF sequence through Windows stdio, it gets converted, " @@ -4355,7 +4329,6 @@ void tst_QNetworkReply::ioPutToFileFromProcess() QCOMPARE(file.size(), qint64(data.size())); QByteArray contents = file.readAll(); QCOMPARE(contents, data); -#endif #endif // QT_NO_PROCESS } @@ -6328,17 +6301,7 @@ void tst_QNetworkReply::getAndThenDeleteObject() reply->setReadBufferSize(1); reply->setParent((QObject*)0); // must be 0 because else it is the manager - QTime stopWatch; - stopWatch.start(); - forever { - QCoreApplication::instance()->processEvents(); - if (reply->bytesAvailable()) - break; - if (stopWatch.elapsed() >= 30000) - break; - } - - QVERIFY(reply->bytesAvailable()); + QTRY_VERIFY_WITH_TIMEOUT(reply->bytesAvailable(), 30000); QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); QVERIFY(!reply->isFinished()); // must not be finished @@ -6561,12 +6524,7 @@ void tst_QNetworkReply::getFromHttpIntoBuffer2() QFETCH(bool, useDownloadBuffer); // On my Linux Desktop the results are already visible with 128 kB, however we use this to have good results. -#if defined(Q_OS_WINCE_WM) - // Show some mercy to non-desktop platform/s - enum {UploadSize = 4*1024*1024}; // 4 MB -#else enum {UploadSize = 32*1024*1024}; // 32 MB -#endif GetFromHttpIntoBuffer2Server server(UploadSize, true, false); @@ -7892,10 +7850,6 @@ void tst_QNetworkReply::backgroundRequestInterruption() QNetworkSessionPrivate::setUsagePolicies(*const_cast<QNetworkSession *>(session.data()), original); QVERIFY(reply->isFinished()); -#ifdef Q_OS_OSX - if (QSysInfo::MacintoshVersion == QSysInfo::MV_10_8) - QEXPECT_FAIL("ftp, bg, nobg", "See QTBUG-32435", Abort); -#endif QCOMPARE(reply->error(), error); #endif } diff --git a/tests/auto/network/access/qnetworkrequest/tst_qnetworkrequest.cpp b/tests/auto/network/access/qnetworkrequest/tst_qnetworkrequest.cpp index a14aaf3cb1..bc9144e40e 100644 --- a/tests/auto/network/access/qnetworkrequest/tst_qnetworkrequest.cpp +++ b/tests/auto/network/access/qnetworkrequest/tst_qnetworkrequest.cpp @@ -56,33 +56,6 @@ private slots: void removeHeader(); }; -QT_BEGIN_NAMESPACE - -namespace QTest { - template<> - char *toString(const QNetworkCookie &cookie) - { - return qstrdup(cookie.toRawForm()); - } - - template<> - char *toString(const QList<QNetworkCookie> &list) - { - QByteArray result = "QList("; - bool first = true; - foreach (QNetworkCookie cookie, list) { - if (!first) - result += ", "; - first = false; - result += "QNetworkCookie(" + cookie.toRawForm() + ')'; - } - result.append(')'); - return qstrdup(result.constData()); - } -} - -QT_END_NAMESPACE - void tst_QNetworkRequest::ctor_data() { QTest::addColumn<QUrl>("url"); diff --git a/tests/auto/network/kernel/kernel.pro b/tests/auto/network/kernel/kernel.pro index bb13c7dd7d..42df80dfa1 100644 --- a/tests/auto/network/kernel/kernel.pro +++ b/tests/auto/network/kernel/kernel.pro @@ -7,6 +7,7 @@ SUBDIRS=\ qauthenticator \ qnetworkproxy \ qnetworkinterface \ + qnetworkdatagram \ qnetworkaddressentry \ qhostaddress \ @@ -17,7 +18,7 @@ winrt: SUBDIRS -= \ osx: SUBDIRS -= \ # QTBUG-41847 qhostinfo \ -!contains(QT_CONFIG, private_tests): SUBDIRS -= \ +!qtConfig(private_tests): SUBDIRS -= \ qauthenticator \ qhostinfo \ diff --git a/tests/auto/network/kernel/qauthenticator/qauthenticator.pro b/tests/auto/network/kernel/qauthenticator/qauthenticator.pro index 5e4759b690..5038eea9af 100644 --- a/tests/auto/network/kernel/qauthenticator/qauthenticator.pro +++ b/tests/auto/network/kernel/qauthenticator/qauthenticator.pro @@ -1,6 +1,6 @@ CONFIG += testcase TARGET = tst_qauthenticator -requires(contains(QT_CONFIG,private_tests)) +requires(qtConfig(private_tests)) QT = core network-private testlib SOURCES += tst_qauthenticator.cpp DEFINES += SRCDIR=\\\"$$PWD/\\\" diff --git a/tests/auto/network/kernel/qhostaddress/qhostaddress.pro b/tests/auto/network/kernel/qhostaddress/qhostaddress.pro index 19d74dfd9b..a79fa2f59d 100644 --- a/tests/auto/network/kernel/qhostaddress/qhostaddress.pro +++ b/tests/auto/network/kernel/qhostaddress/qhostaddress.pro @@ -5,10 +5,4 @@ SOURCES += tst_qhostaddress.cpp QT = core network testlib -win32: { -wince { - LIBS += -lws2 -} else { - LIBS += -lws2_32 -} -} +win32:LIBS += -lws2_32 diff --git a/tests/auto/network/kernel/qhostaddress/tst_qhostaddress.cpp b/tests/auto/network/kernel/qhostaddress/tst_qhostaddress.cpp index 4fb97fe1f2..419c781aab 100644 --- a/tests/auto/network/kernel/qhostaddress/tst_qhostaddress.cpp +++ b/tests/auto/network/kernel/qhostaddress/tst_qhostaddress.cpp @@ -62,6 +62,8 @@ private slots: void specialAddresses(); void compare_data(); void compare(); + void isEqual_data(); + void isEqual(); void assignment(); void scopeId(); void hashKey(); @@ -291,6 +293,7 @@ void tst_QHostAddress::compare_data() QTest::newRow("5") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::Broadcast) << false; QTest::newRow("6") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::LocalHostIPv6) << false; QTest::newRow("7") << QHostAddress() << QHostAddress(QHostAddress::LocalHostIPv6) << false; + QTest::newRow("any4-any6") << QHostAddress(QHostAddress::AnyIPv4) << QHostAddress(QHostAddress::AnyIPv6) << false; Q_IPV6ADDR localhostv4mapped = { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 127, 0, 0, 1 } }; QTest::newRow("v4-v4mapped") << QHostAddress(QHostAddress::LocalHost) << QHostAddress("::ffff:127.0.0.1") << false; @@ -309,6 +312,53 @@ void tst_QHostAddress::compare() QCOMPARE(qHash(first), qHash(second)); } +void tst_QHostAddress::isEqual_data() +{ + QTest::addColumn<QHostAddress>("first"); + QTest::addColumn<QHostAddress>("second"); + QTest::addColumn<int>("flags"); + QTest::addColumn<bool>("result"); + + // QHostAddress::StrictConversion is already tested in compare() + QTest::newRow("localhost4to6-local") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertLocalHost << true; + QTest::newRow("localhost4to6-compat") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertV4CompatToIPv4 << false; + QTest::newRow("localhost4to6-mapped") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertV4MappedToIPv4 << false; + QTest::newRow("localhost4to6-unspec") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertUnspecifiedAddress << false; + QTest::newRow("0.0.0.1-::1-local") << QHostAddress("0.0.0.1") << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertLocalHost << false; + QTest::newRow("v4-v4compat-local") << QHostAddress("192.168.1.1") << QHostAddress("::192.168.1.1") << (int)QHostAddress::ConvertLocalHost << false; + QTest::newRow("v4-v4mapped-local") << QHostAddress("192.168.1.1") << QHostAddress("::ffff:192.168.1.1") << (int)QHostAddress::ConvertLocalHost << false; + QTest::newRow("0.0.0.1-::1-unspec") << QHostAddress("0.0.0.1") << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertUnspecifiedAddress << false; + QTest::newRow("v4-v4compat-unspec") << QHostAddress("192.168.1.1") << QHostAddress("::192.168.1.1") << (int)QHostAddress::ConvertUnspecifiedAddress << false; + QTest::newRow("v4-v4mapped-unspec") << QHostAddress("192.168.1.1") << QHostAddress("::ffff:192.168.1.1") << (int)QHostAddress::ConvertUnspecifiedAddress << false; + QTest::newRow("0.0.0.1-::1-compat") << QHostAddress("0.0.0.1") << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertV4CompatToIPv4 << false; + QTest::newRow("v4-v4compat-compat") << QHostAddress("192.168.1.1") << QHostAddress("::192.168.1.1") << (int)QHostAddress::ConvertV4CompatToIPv4 << true; + QTest::newRow("v4-v4mapped-compat") << QHostAddress("192.168.1.1") << QHostAddress("::ffff:192.168.1.1") << (int)QHostAddress::ConvertV4CompatToIPv4 << false; + QTest::newRow("0.0.0.1-::1-mapped") << QHostAddress("0.0.0.1") << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertV4MappedToIPv4 << false; + QTest::newRow("v4-v4compat-mapped") << QHostAddress("192.168.1.1") << QHostAddress("::192.168.1.1") << (int)QHostAddress::ConvertV4MappedToIPv4 << false; + QTest::newRow("v4-v4mapped-mapped") << QHostAddress("192.168.1.1") << QHostAddress("::FFFF:192.168.1.1") << (int)QHostAddress::ConvertV4MappedToIPv4 << true; + QTest::newRow("undef-any-local") << QHostAddress() << QHostAddress(QHostAddress::Any) << (int)QHostAddress::ConvertLocalHost << false; + QTest::newRow("undef-any-unspec") << QHostAddress() << QHostAddress(QHostAddress::Any) << (int)QHostAddress::ConvertUnspecifiedAddress << false; + QTest::newRow("anyv6-anyv4-compat") << QHostAddress(QHostAddress::AnyIPv6) << QHostAddress(QHostAddress::AnyIPv4) << (int)QHostAddress::ConvertV4CompatToIPv4 << true; + QTest::newRow("anyv6-anyv4-mapped") << QHostAddress(QHostAddress::AnyIPv6) << QHostAddress(QHostAddress::AnyIPv4) << (int)QHostAddress::ConvertV4MappedToIPv4 << false; + QTest::newRow("anyv6-anyv4-unspec") << QHostAddress(QHostAddress::AnyIPv6) << QHostAddress(QHostAddress::AnyIPv4) << (int)QHostAddress::ConvertUnspecifiedAddress << true; + QTest::newRow("any-anyv4-unspec") << QHostAddress(QHostAddress::Any) << QHostAddress(QHostAddress::AnyIPv4) << (int)QHostAddress::ConvertUnspecifiedAddress << true; + QTest::newRow("any-anyv6-unspec") << QHostAddress(QHostAddress::Any) << QHostAddress(QHostAddress::AnyIPv6) << (int)QHostAddress::ConvertUnspecifiedAddress << true; + QTest::newRow("anyv6-anyv4-local") << QHostAddress(QHostAddress::AnyIPv6) << QHostAddress(QHostAddress::AnyIPv4) << (int)QHostAddress::ConvertLocalHost << false; + QTest::newRow("any-anyv4-local") << QHostAddress(QHostAddress::Any) << QHostAddress(QHostAddress::AnyIPv4) << (int)QHostAddress::ConvertLocalHost << false; + QTest::newRow("any-anyv6-local") << QHostAddress(QHostAddress::Any) << QHostAddress(QHostAddress::AnyIPv6) << (int)QHostAddress::ConvertLocalHost << false; +} + +void tst_QHostAddress::isEqual() +{ + QFETCH(QHostAddress, first); + QFETCH(QHostAddress, second); + QFETCH(int, flags); + QFETCH(bool, result); + + QCOMPARE(first.isEqual(second, QHostAddress::ConversionModeFlag(flags)), result); + QCOMPARE(second.isEqual(first, QHostAddress::ConversionModeFlag(flags)), result); +} + void tst_QHostAddress::assignment() { QHostAddress address; diff --git a/tests/auto/network/kernel/qhostinfo/qhostinfo.pro b/tests/auto/network/kernel/qhostinfo/qhostinfo.pro index 12858c97ee..67a37faeb5 100644 --- a/tests/auto/network/kernel/qhostinfo/qhostinfo.pro +++ b/tests/auto/network/kernel/qhostinfo/qhostinfo.pro @@ -3,14 +3,10 @@ TARGET = tst_qhostinfo SOURCES += tst_qhostinfo.cpp -requires(contains(QT_CONFIG,private_tests)) +requires(qtConfig(private_tests)) QT = core-private network-private testlib -wince { - LIBS += ws2.lib -} else { - win32:LIBS += -lws2_32 -} +win32:LIBS += -lws2_32 # needed for getaddrinfo with official MinGW mingw:DEFINES += _WIN32_WINNT=0x0501 diff --git a/tests/auto/network/kernel/qhostinfo/tst_qhostinfo.cpp b/tests/auto/network/kernel/qhostinfo/tst_qhostinfo.cpp index 13d4442ada..f6d9b71aa2 100644 --- a/tests/auto/network/kernel/qhostinfo/tst_qhostinfo.cpp +++ b/tests/auto/network/kernel/qhostinfo/tst_qhostinfo.cpp @@ -66,11 +66,7 @@ #include "private/qhostinfo_p.h" #if !defined(QT_NO_GETADDRINFO) -# if !defined(Q_OS_WINCE) # include <sys/types.h> -# else -# include <types.h> -# endif # if defined(Q_OS_UNIX) # include <sys/socket.h> # endif @@ -399,11 +395,7 @@ protected: void tst_QHostInfo::threadSafety() { const int nattempts = 5; -#if defined(Q_OS_WINCE) - const int runs = 10; -#else const int runs = 100; -#endif LookupThread thr[nattempts]; for (int j = 0; j < runs; ++j) { for (int i = 0; i < nattempts; ++i) diff --git a/tests/auto/network/kernel/qnetworkdatagram/qnetworkdatagram.pro b/tests/auto/network/kernel/qnetworkdatagram/qnetworkdatagram.pro new file mode 100644 index 0000000000..a2fe44060e --- /dev/null +++ b/tests/auto/network/kernel/qnetworkdatagram/qnetworkdatagram.pro @@ -0,0 +1,5 @@ +CONFIG += testcase console +CONFIG -= app_bundle +TARGET = tst_qnetworkdatagram +SOURCES += tst_qnetworkdatagram.cpp +QT = core network testlib diff --git a/tests/auto/network/kernel/qnetworkdatagram/tst_qnetworkdatagram.cpp b/tests/auto/network/kernel/qnetworkdatagram/tst_qnetworkdatagram.cpp new file mode 100644 index 0000000000..3295580432 --- /dev/null +++ b/tests/auto/network/kernel/qnetworkdatagram/tst_qnetworkdatagram.cpp @@ -0,0 +1,146 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Intel Corporation. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QNetworkDatagram> +#include <QtTest> +#include <QCoreApplication> + +class tst_QNetworkDatagram : public QObject +{ + Q_OBJECT + +public: + tst_QNetworkDatagram(); + +private Q_SLOTS: + void getSetCheck(); + void makeReply_data(); + void makeReply(); +}; + +tst_QNetworkDatagram::tst_QNetworkDatagram() +{ +} + +void tst_QNetworkDatagram::getSetCheck() +{ + QNetworkDatagram dg; + + QVERIFY(dg.isNull()); + QVERIFY(!dg.isValid()); + QCOMPARE(dg.senderAddress(), QHostAddress()); + QCOMPARE(dg.destinationAddress(), QHostAddress()); + QCOMPARE(dg.senderPort(), -1); + QCOMPARE(dg.destinationPort(), -1); + QCOMPARE(dg.hopLimit(), -1); + QCOMPARE(dg.interfaceIndex(), 0U); + + dg.setHopLimit(1); + QCOMPARE(dg.hopLimit(), 1); + dg.setHopLimit(255); + QCOMPARE(dg.hopLimit(), 255); + + dg.setInterfaceIndex(1); + QCOMPARE(dg.interfaceIndex(), 1U); + dg.setInterfaceIndex(1234567U); + QCOMPARE(dg.interfaceIndex(), 1234567U); + + dg.setSender(QHostAddress::Any, 12345); + QCOMPARE(dg.senderAddress(), QHostAddress(QHostAddress::Any)); + QCOMPARE(dg.senderPort(), 12345); + dg.setSender(QHostAddress::LocalHost); + QCOMPARE(dg.senderAddress(), QHostAddress(QHostAddress::LocalHost)); + QCOMPARE(dg.senderPort(), 0); + + dg.setDestination(QHostAddress::LocalHostIPv6, 12345); + QCOMPARE(dg.destinationAddress(), QHostAddress(QHostAddress::LocalHostIPv6)); + QCOMPARE(dg.destinationPort(), 12345); + dg.setDestination(QHostAddress::Broadcast, 137); + QCOMPARE(dg.destinationAddress(), QHostAddress(QHostAddress::Broadcast)); + QCOMPARE(dg.destinationPort(), 137); +} + +void tst_QNetworkDatagram::makeReply_data() +{ + qRegisterMetaType<QNetworkDatagram>(); + QTest::addColumn<QNetworkDatagram>("dgram"); + QTest::addColumn<QString>("localAddress"); + + QNetworkDatagram dgram("some data", QHostAddress("192.0.2.1"), 10001); + dgram.setHopLimit(64); + dgram.setSender(QHostAddress::LocalHost, 12345); + QTest::newRow("ipv4") << dgram << "192.0.2.1"; + + dgram.setDestination(QHostAddress("224.0.0.1"), 10002); + QTest::newRow("ipv4-multicast") << dgram << QString(); + + dgram.setSender(QHostAddress::LocalHostIPv6, 12346); + dgram.setDestination(QHostAddress("2001:db8::1"), 12347); + QTest::newRow("ipv6") << dgram << "2001:db8::1"; + + dgram.setSender(QHostAddress("fe80::1%1"), 10003); + dgram.setDestination(QHostAddress("fe80::2%1"), 10004); + dgram.setInterfaceIndex(1); + QTest::newRow("ipv6-linklocal") << dgram << "fe80::2%1"; + + dgram.setDestination(QHostAddress("ff02::1%1"), 10005); + QTest::newRow("ipv6-multicast") << dgram << QString(); +} + +void tst_QNetworkDatagram::makeReply() +{ + QFETCH(QNetworkDatagram, dgram); + QFETCH(QString, localAddress); + + { + QNetworkDatagram reply = dgram.makeReply("World"); + QCOMPARE(reply.data(), QByteArray("World")); + QCOMPARE(reply.senderAddress(), QHostAddress(localAddress)); + QCOMPARE(reply.senderPort(), localAddress.isEmpty() ? -1 : dgram.destinationPort()); + QCOMPARE(reply.destinationAddress(), dgram.senderAddress()); + QCOMPARE(reply.destinationPort(), dgram.senderPort()); + QCOMPARE(reply.interfaceIndex(), dgram.interfaceIndex()); + QCOMPARE(reply.hopLimit(), -1); + } + + QNetworkDatagram copy = dgram; + copy.setData(copy.data()); + { + QNetworkDatagram reply = qMove(copy).makeReply("World"); + QCOMPARE(reply.data(), QByteArray("World")); + QCOMPARE(reply.senderAddress(), QHostAddress(localAddress)); + QCOMPARE(reply.senderPort(), localAddress.isEmpty() ? -1 : dgram.destinationPort()); + QCOMPARE(reply.destinationAddress(), dgram.senderAddress()); + QCOMPARE(reply.destinationPort(), dgram.senderPort()); + QCOMPARE(reply.interfaceIndex(), dgram.interfaceIndex()); + QCOMPARE(reply.hopLimit(), -1); + } +} + +QTEST_MAIN(tst_QNetworkDatagram) +#include "tst_qnetworkdatagram.moc" diff --git a/tests/auto/network/socket/platformsocketengine/platformsocketengine.pri b/tests/auto/network/socket/platformsocketengine/platformsocketengine.pri index a3b4e89450..46c722deba 100644 --- a/tests/auto/network/socket/platformsocketengine/platformsocketengine.pri +++ b/tests/auto/network/socket/platformsocketengine/platformsocketengine.pri @@ -4,15 +4,9 @@ QNETWORK_SRC = $$QT_SOURCE_TREE/src/network INCLUDEPATH += $$QNETWORK_SRC -win32 { - wince { - LIBS += -lws2 - } else { - LIBS += -lws2_32 - } -} +win32:LIBS += -lws2_32 -unix:contains(QT_CONFIG, reduce_exports) { +unix:qtConfig(reduce_exports) { SOURCES += $$QNETWORK_SRC/socket/qnativesocketengine_unix.cpp SOURCES += $$QNETWORK_SRC/socket/qnativesocketengine.cpp SOURCES += $$QNETWORK_SRC/socket/qabstractsocketengine.cpp diff --git a/tests/auto/network/socket/platformsocketengine/platformsocketengine.pro b/tests/auto/network/socket/platformsocketengine/platformsocketengine.pro index eee762037d..ab96bb444e 100644 --- a/tests/auto/network/socket/platformsocketengine/platformsocketengine.pro +++ b/tests/auto/network/socket/platformsocketengine/platformsocketengine.pro @@ -4,7 +4,7 @@ SOURCES += tst_platformsocketengine.cpp include(../platformsocketengine/platformsocketengine.pri) -requires(contains(QT_CONFIG,private_tests)) +requires(qtConfig(private_tests)) MOC_DIR=tmp diff --git a/tests/auto/network/socket/platformsocketengine/tst_platformsocketengine.cpp b/tests/auto/network/socket/platformsocketengine/tst_platformsocketengine.cpp index b3ecd884cd..43b5422635 100644 --- a/tests/auto/network/socket/platformsocketengine/tst_platformsocketengine.cpp +++ b/tests/auto/network/socket/platformsocketengine/tst_platformsocketengine.cpp @@ -495,9 +495,6 @@ void tst_PlatformSocketEngine::readWriteBufferSize() qint64 bufferSize = device.receiveBufferSize(); QVERIFY(bufferSize != -1); device.setReceiveBufferSize(bufferSize + 1); -#if defined(Q_OS_WINCE) - QEXPECT_FAIL(0, "Not supported by default on WinCE", Continue); -#endif QVERIFY(device.receiveBufferSize() > bufferSize); bufferSize = device.sendBufferSize(); @@ -607,8 +604,8 @@ void tst_PlatformSocketEngine::invalidSend() PLATFORMSOCKETENGINE socket; QVERIFY(socket.initialize(QAbstractSocket::TcpSocket)); - QTest::ignoreMessage(QtWarningMsg, PLATFORMSOCKETENGINESTRING "::writeDatagram() was" - " called by a socket other than QAbstractSocket::UdpSocket"); + QTest::ignoreMessage(QtWarningMsg, PLATFORMSOCKETENGINESTRING "::writeDatagram() was called" + " not in QAbstractSocket::BoundState or QAbstractSocket::ConnectedState"); QCOMPARE(socket.writeDatagram("hei", 3, QIpPacketHeader(QHostAddress::LocalHost, 143)), (qlonglong) -1); } @@ -650,7 +647,7 @@ void tst_PlatformSocketEngine::receiveUrgentData() QByteArray response; // Native OOB data test doesn't work on HP-UX or WinCE -#if !defined(Q_OS_HPUX) && !defined(Q_OS_WINCE) +#if !defined(Q_OS_HPUX) // The server sends an urgent message msg = 'Q'; QCOMPARE(int(::send(socketDescriptor, &msg, sizeof(msg), MSG_OOB)), 1); diff --git a/tests/auto/network/socket/qhttpsocketengine/qhttpsocketengine.pro b/tests/auto/network/socket/qhttpsocketengine/qhttpsocketengine.pro index 12ce576e23..56a4fb8aee 100644 --- a/tests/auto/network/socket/qhttpsocketengine/qhttpsocketengine.pro +++ b/tests/auto/network/socket/qhttpsocketengine/qhttpsocketengine.pro @@ -7,6 +7,6 @@ include(../platformsocketengine/platformsocketengine.pri) MOC_DIR=tmp -requires(contains(QT_CONFIG,private_tests)) +requires(qtConfig(private_tests)) QT = core-private network-private testlib diff --git a/tests/auto/network/socket/qhttpsocketengine/tst_qhttpsocketengine.cpp b/tests/auto/network/socket/qhttpsocketengine/tst_qhttpsocketengine.cpp index 7237542e5c..68f3ea059b 100644 --- a/tests/auto/network/socket/qhttpsocketengine/tst_qhttpsocketengine.cpp +++ b/tests/auto/network/socket/qhttpsocketengine/tst_qhttpsocketengine.cpp @@ -626,11 +626,7 @@ void tst_QHttpSocketEngine::downloadBigFile() QTime stopWatch; stopWatch.start(); -#if defined(Q_OS_WINCE) - QTestEventLoop::instance().enterLoop(240); -#else QTestEventLoop::instance().enterLoop(60); -#endif if (QTestEventLoop::instance().timeout()) QFAIL("Network operation timed out"); diff --git a/tests/auto/network/socket/qlocalsocket/test/test.pro b/tests/auto/network/socket/qlocalsocket/test/test.pro index 6a5df7f9b6..ab9ed90b1d 100644 --- a/tests/auto/network/socket/qlocalsocket/test/test.pro +++ b/tests/auto/network/socket/qlocalsocket/test/test.pro @@ -2,13 +2,7 @@ CONFIG += testcase DEFINES += QLOCALSERVER_DEBUG DEFINES += QLOCALSOCKET_DEBUG - -wince* { - DEFINES += QT_LOCALSOCKET_TCP - DEFINES += SRCDIR=\\\"../\\\" -} else { - DEFINES += SRCDIR=\\\"$$PWD/../\\\" -} +DEFINES += SRCDIR=\\\"$$PWD/../\\\" QT = core network testlib diff --git a/tests/auto/network/socket/qlocalsocket/tst_qlocalsocket.cpp b/tests/auto/network/socket/qlocalsocket/tst_qlocalsocket.cpp index 17d7697f94..8cc06a77ba 100644 --- a/tests/auto/network/socket/qlocalsocket/tst_qlocalsocket.cpp +++ b/tests/auto/network/socket/qlocalsocket/tst_qlocalsocket.cpp @@ -868,10 +868,8 @@ void tst_QLocalSocket::threadedConnection_data() QTest::newRow("1 client") << 1; QTest::newRow("2 clients") << 2; QTest::newRow("5 clients") << 5; -#ifndef Q_OS_WINCE QTest::newRow("10 clients") << 10; QTest::newRow("20 clients") << 20; -#endif } void tst_QLocalSocket::threadedConnection() diff --git a/tests/auto/network/socket/qsctpsocket/qsctpsocket.pro b/tests/auto/network/socket/qsctpsocket/qsctpsocket.pro new file mode 100644 index 0000000000..49a40ce9b5 --- /dev/null +++ b/tests/auto/network/socket/qsctpsocket/qsctpsocket.pro @@ -0,0 +1,6 @@ +CONFIG += testcase +TARGET = tst_qsctpsocket +QT = core network testlib + +SOURCES += tst_qsctpsocket.cpp + diff --git a/tests/auto/network/socket/qsctpsocket/tst_qsctpsocket.cpp b/tests/auto/network/socket/qsctpsocket/tst_qsctpsocket.cpp new file mode 100644 index 0000000000..fc976fbd0d --- /dev/null +++ b/tests/auto/network/socket/qsctpsocket/tst_qsctpsocket.cpp @@ -0,0 +1,488 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alex Trotsenko <alex1973tr@gmail.com> +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> +#include <QDebug> +#include <QEventLoop> +#include <QByteArray> +#include <QString> +#include <QHostAddress> +#include <QHostInfo> +#include <QNetworkInterface> +#include <QTime> + +#include <QSctpSocket> +#include <QSctpServer> + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/sctp.h> + +#define SOCKET int +#define INVALID_SOCKET -1 + +class tst_QSctpSocket : public QObject +{ + Q_OBJECT + +public: + static void enterLoop(int secs) + { + ++loopLevel; + QTestEventLoop::instance().enterLoop(secs); + --loopLevel; + } + static void exitLoop() + { + // Safe exit - if we aren't in an event loop, don't + // exit one. + if (loopLevel > 0) + QTestEventLoop::instance().exitLoop(); + } + static bool timeout() + { + return QTestEventLoop::instance().timeout(); + } + +private slots: + void constructing(); + void bind_data(); + void bind(); + void setInvalidSocketDescriptor(); + void setSocketDescriptor(); + void socketDescriptor(); + void hostNotFound(); + void connecting(); + void readAndWrite(); + void loop_data(); + void loop(); + void loopInTCPMode_data(); + void loopInTCPMode(); + void readDatagramAfterClose(); + void clientSendDataOnDelayedDisconnect(); + +protected slots: + void exitLoopSlot(); + +private: + static int loopLevel; +}; + +int tst_QSctpSocket::loopLevel = 0; + +//---------------------------------------------------------------------------------- +void tst_QSctpSocket::constructing() +{ + QSctpSocket socket; + + // Check the initial state of the QSctpSocket. + QCOMPARE(socket.state(), QAbstractSocket::UnconnectedState); + QVERIFY(socket.isSequential()); + QVERIFY(!socket.isOpen()); + QVERIFY(!socket.isValid()); + QCOMPARE(socket.socketType(), QAbstractSocket::SctpSocket); + QCOMPARE(socket.maxChannelCount(), 0); + QCOMPARE(socket.readChannelCount(), 0); + QCOMPARE(socket.writeChannelCount(), 0); + + char c; + QCOMPARE(socket.getChar(&c), false); + QCOMPARE(socket.bytesAvailable(), Q_INT64_C(0)); + QCOMPARE(socket.canReadLine(), false); + QCOMPARE(socket.readLine(), QByteArray()); + QCOMPARE(socket.socketDescriptor(), qintptr(-1)); + QCOMPARE(int(socket.localPort()), 0); + QVERIFY(socket.localAddress() == QHostAddress()); + QCOMPARE(int(socket.peerPort()), 0); + QVERIFY(socket.peerAddress() == QHostAddress()); + QCOMPARE(socket.error(), QAbstractSocket::UnknownSocketError); + QCOMPARE(socket.errorString(), QString("Unknown error")); +} + +//---------------------------------------------------------------------------------- +void tst_QSctpSocket::bind_data() +{ + QTest::addColumn<QString>("stringAddr"); + QTest::addColumn<bool>("successExpected"); + QTest::addColumn<QString>("stringExpectedLocalAddress"); + + // iterate all interfaces, add all addresses on them as test data + QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces(); + for (const QNetworkInterface &interface : interfaces) { + if (!interface.isValid()) + continue; + + for (const QNetworkAddressEntry &entry : interface.addressEntries()) { + if (entry.ip().isInSubnet(QHostAddress::parseSubnet("fe80::/10")) + || entry.ip().isInSubnet(QHostAddress::parseSubnet("169.254/16"))) + continue; // link-local bind will fail, at least on Linux, so skip it. + + QString ip(entry.ip().toString()); + QTest::newRow(ip.toLatin1().constData()) << ip << true << ip; + } + } + + // additionally, try bind to known-bad addresses, and make sure this doesn't work + // these ranges are guaranteed to be reserved for 'documentation purposes', + // and thus, should be unused in the real world. Not that I'm assuming the + // world is full of competent administrators, or anything. + QStringList knownBad; + knownBad << "198.51.100.1"; + knownBad << "2001:0DB8::1"; + foreach (const QString &badAddress, knownBad) { + QTest::newRow(badAddress.toLatin1().constData()) << badAddress << false << QString(); + } +} + +// Testing bind function +void tst_QSctpSocket::bind() +{ + QFETCH(QString, stringAddr); + QFETCH(bool, successExpected); + QFETCH(QString, stringExpectedLocalAddress); + + QHostAddress addr(stringAddr); + QHostAddress expectedLocalAddress(stringExpectedLocalAddress); + + QSctpSocket socket; + qDebug() << "Binding " << addr; + + if (successExpected) + QVERIFY2(socket.bind(addr), qPrintable(socket.errorString())); + else + QVERIFY(!socket.bind(addr)); + + QCOMPARE(socket.localAddress(), expectedLocalAddress); +} + +//---------------------------------------------------------------------------------- +void tst_QSctpSocket::setInvalidSocketDescriptor() +{ + QSctpSocket socket; + QCOMPARE(socket.socketDescriptor(), qintptr(INVALID_SOCKET)); + QVERIFY(!socket.setSocketDescriptor(-5, QAbstractSocket::UnconnectedState)); + QCOMPARE(socket.socketDescriptor(), qintptr(INVALID_SOCKET)); + + QCOMPARE(socket.error(), QAbstractSocket::UnsupportedSocketOperationError); +} + +//---------------------------------------------------------------------------------- +void tst_QSctpSocket::setSocketDescriptor() +{ + QSctpServer server; + + server.setMaxChannelCount(16); + QVERIFY(server.listen()); + + SOCKET sock = ::socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP); + + QVERIFY(sock != INVALID_SOCKET); + QSctpSocket socket; + QVERIFY(socket.setSocketDescriptor(sock, QAbstractSocket::UnconnectedState)); + QCOMPARE(socket.socketDescriptor(), qintptr(sock)); + QCOMPARE(socket.readChannelCount(), 0); + QCOMPARE(socket.writeChannelCount(), 0); + + socket.connectToHost(QHostAddress::LocalHost, server.serverPort()); + QVERIFY(socket.waitForConnected(3000)); + QVERIFY(server.waitForNewConnection(3000)); + + QCOMPARE(socket.readChannelCount(), server.maxChannelCount()); + QVERIFY(socket.writeChannelCount() <= server.maxChannelCount()); + + QSctpSocket *acceptedSocket = server.nextPendingDatagramConnection(); + QVERIFY(acceptedSocket); + QCOMPARE(acceptedSocket->readChannelCount(), socket.writeChannelCount()); + QCOMPARE(acceptedSocket->writeChannelCount(), socket.readChannelCount()); +} + +//---------------------------------------------------------------------------------- +void tst_QSctpSocket::socketDescriptor() +{ + QSctpSocket socket; + + QSctpServer server; + + QVERIFY(server.listen()); + + QCOMPARE(socket.socketDescriptor(), qintptr(INVALID_SOCKET)); + socket.connectToHost(QHostAddress::LocalHost, server.serverPort()); + QVERIFY(server.waitForNewConnection(3000)); + if (socket.state() != QAbstractSocket::ConnectedState) { + QVERIFY((socket.state() == QAbstractSocket::HostLookupState + && socket.socketDescriptor() == INVALID_SOCKET) + || socket.state() == QAbstractSocket::ConnectingState); + QVERIFY(socket.waitForConnected(3000)); + QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); + } + QVERIFY(socket.socketDescriptor() != INVALID_SOCKET); +} + +//---------------------------------------------------------------------------------- +void tst_QSctpSocket::hostNotFound() +{ + QSctpSocket socket; + + socket.connectToHost("nosuchserver.qt-project.org", 80); + QVERIFY(!socket.waitForConnected(3000)); + QCOMPARE(socket.state(), QTcpSocket::UnconnectedState); + QCOMPARE(socket.error(), QAbstractSocket::HostNotFoundError); +} + +// Testing connect function +void tst_QSctpSocket::connecting() +{ + QSctpServer server; + + QVERIFY(server.listen()); + + QSctpSocket socket; + socket.connectToHost(QHostAddress::LocalHost, server.serverPort()); + QVERIFY(socket.waitForConnected(3000)); + + QVERIFY(server.waitForNewConnection(3000)); + QSctpSocket *acceptedSocket = server.nextPendingDatagramConnection(); + QVERIFY(acceptedSocket); + + QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); + QCOMPARE(acceptedSocket->state(), QAbstractSocket::ConnectedState); + QCOMPARE(socket.readChannelCount(), acceptedSocket->readChannelCount()); + QCOMPARE(socket.writeChannelCount(),acceptedSocket->writeChannelCount()); +} + +// Testing read/write functions +void tst_QSctpSocket::readAndWrite() +{ + QSctpServer server; + + QVERIFY(server.listen()); + + QSctpSocket socket; + socket.connectToHost(QHostAddress::LocalHost, server.serverPort()); + QVERIFY(socket.waitForConnected(3000)); + + QVERIFY(server.waitForNewConnection(3000)); + QSctpSocket *acceptedSocket = server.nextPendingDatagramConnection(); + QVERIFY(acceptedSocket); + + QByteArray ba(1000, 1); + QVERIFY(acceptedSocket->writeDatagram(ba)); + QVERIFY(acceptedSocket->waitForBytesWritten(3000)); + + QVERIFY(socket.waitForReadyRead(3000)); + QNetworkDatagram datagram = socket.readDatagram(); + QVERIFY(datagram.isValid()); + QCOMPARE(datagram.data(), ba); + + QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); + QCOMPARE(socket.error(), QAbstractSocket::UnknownSocketError); + QCOMPARE(socket.errorString(), QString("Unknown error")); + QCOMPARE(acceptedSocket->state(), QAbstractSocket::ConnectedState); + QCOMPARE(acceptedSocket->error(), QAbstractSocket::UnknownSocketError); + QCOMPARE(acceptedSocket->errorString(), QString("Unknown error")); +} + +//---------------------------------------------------------------------------------- +void tst_QSctpSocket::loop_data() +{ + QTest::addColumn<QByteArray>("peterDatagram"); + QTest::addColumn<QByteArray>("paulDatagram"); + QTest::addColumn<int>("peterChannel"); + QTest::addColumn<int>("paulChannel"); + + QTest::newRow("\"Almond!\" | \"Joy!\"") << QByteArray("Almond!") << QByteArray("Joy!") << 0 << 0; + QTest::newRow("\"A\" | \"B\"") << QByteArray("A") << QByteArray("B") << 1 << 1; + QTest::newRow("\"AB\" | \"B\"") << QByteArray("AB") << QByteArray("B") << 0 << 1; + QTest::newRow("\"AB\" | \"BB\"") << QByteArray("AB") << QByteArray("BB") << 1 << 0; + QTest::newRow("\"A\\0B\" | \"B\\0B\"") << QByteArray::fromRawData("A\0B", 3) << QByteArray::fromRawData("B\0B", 3) << 0 << 1; + QTest::newRow("BigDatagram") << QByteArray(600, '@') << QByteArray(600, '@') << 1 << 0; +} + +void tst_QSctpSocket::loop() +{ + QFETCH(QByteArray, peterDatagram); + QFETCH(QByteArray, paulDatagram); + QFETCH(int, peterChannel); + QFETCH(int, paulChannel); + + QSctpServer server; + + server.setMaxChannelCount(10); + QVERIFY(server.listen()); + + QSctpSocket peter; + peter.setMaxChannelCount(10); + peter.connectToHost(QHostAddress::LocalHost, server.serverPort()); + QVERIFY(peter.waitForConnected(3000)); + + QVERIFY(server.waitForNewConnection(3000)); + QSctpSocket *paul = server.nextPendingDatagramConnection(); + QVERIFY(paul); + + peter.setCurrentWriteChannel(peterChannel); + QVERIFY(peter.writeDatagram(peterDatagram)); + paul->setCurrentWriteChannel(paulChannel); + QVERIFY(paul->writeDatagram(paulDatagram)); + QVERIFY(peter.flush()); + QVERIFY(paul->flush()); + + peter.setCurrentReadChannel(paulChannel); + QVERIFY(peter.waitForReadyRead(3000)); + QCOMPARE(peter.bytesAvailable(), paulDatagram.size()); + QCOMPARE(peter.readDatagram().data(), paulDatagram); + + paul->setCurrentReadChannel(peterChannel); + QVERIFY(paul->waitForReadyRead(3000)); + QCOMPARE(paul->bytesAvailable(), peterDatagram.size()); + QCOMPARE(paul->readDatagram().data(), peterDatagram); +} + +//---------------------------------------------------------------------------------- +void tst_QSctpSocket::loopInTCPMode_data() +{ + QTest::addColumn<QByteArray>("peterDatagram"); + QTest::addColumn<QByteArray>("paulDatagram"); + + QTest::newRow("\"Almond!\" | \"Joy!\"") << QByteArray("Almond!") << QByteArray("Joy!"); + QTest::newRow("\"A\" | \"B\"") << QByteArray("A") << QByteArray("B"); + QTest::newRow("\"AB\" | \"B\"") << QByteArray("AB") << QByteArray("B"); + QTest::newRow("\"AB\" | \"BB\"") << QByteArray("AB") << QByteArray("BB"); + QTest::newRow("\"A\\0B\" | \"B\\0B\"") << QByteArray::fromRawData("A\0B", 3) << QByteArray::fromRawData("B\0B", 3); + QTest::newRow("BigDatagram") << QByteArray(600, '@') << QByteArray(600, '@'); +} + +void tst_QSctpSocket::loopInTCPMode() +{ + QFETCH(QByteArray, peterDatagram); + QFETCH(QByteArray, paulDatagram); + + QSctpServer server; + + server.setMaxChannelCount(-1); + QVERIFY(server.listen()); + + QSctpSocket peter; + peter.setMaxChannelCount(-1); + peter.connectToHost(QHostAddress::LocalHost, server.serverPort()); + QVERIFY(peter.waitForConnected(3000)); + QVERIFY(server.waitForNewConnection(3000)); + + QTcpSocket *paul = server.nextPendingConnection(); + QVERIFY(paul); + + QCOMPARE(peter.write(peterDatagram), qint64(peterDatagram.size())); + QCOMPARE(paul->write(paulDatagram), qint64(paulDatagram.size())); + QVERIFY(peter.flush()); + QVERIFY(paul->flush()); + + QVERIFY(peter.waitForReadyRead(3000)); + QVERIFY(paul->waitForReadyRead(3000)); + + QCOMPARE(peter.bytesAvailable(), paulDatagram.size()); + QByteArray peterBuffer = peter.readAll(); + + QCOMPARE(paul->bytesAvailable(), peterDatagram.size()); + QByteArray paulBuffer = paul->readAll(); + + QCOMPARE(peterBuffer, paulDatagram); + QCOMPARE(paulBuffer, peterDatagram); +} + +//---------------------------------------------------------------------------------- +void tst_QSctpSocket::exitLoopSlot() +{ + exitLoop(); +} + +//---------------------------------------------------------------------------------- +void tst_QSctpSocket::readDatagramAfterClose() +{ + QSctpServer server; + + QVERIFY(server.listen()); + + QSctpSocket socket; + socket.connectToHost(QHostAddress::LocalHost, server.serverPort()); + QVERIFY(socket.waitForConnected(3000)); + QVERIFY(server.waitForNewConnection(3000)); + + QSctpSocket *acceptedSocket = server.nextPendingDatagramConnection(); + QVERIFY(acceptedSocket); + + connect(&socket, &QIODevice::readyRead, this, &tst_QSctpSocket::exitLoopSlot); + + QByteArray ba(1000, 1); + QVERIFY(acceptedSocket->writeDatagram(ba)); + + enterLoop(10); + if (timeout()) + QFAIL("Network operation timed out"); + + QCOMPARE(socket.bytesAvailable(), ba.size()); + socket.close(); + QVERIFY(!socket.readDatagram().isValid()); +} + +// Test buffered socket properly send data on delayed disconnect +void tst_QSctpSocket::clientSendDataOnDelayedDisconnect() +{ + QSctpServer server; + + QVERIFY(server.listen()); + + // Connect to server, write data and close socket + QSctpSocket socket; + socket.connectToHost(QHostAddress::LocalHost, server.serverPort()); + QVERIFY(socket.waitForConnected(3000)); + + QByteArray sendData("GET /\r\n"); + sendData = sendData.repeated(1000); + QVERIFY(socket.writeDatagram(sendData)); + socket.close(); + QCOMPARE(socket.state(), QAbstractSocket::ClosingState); + QVERIFY(socket.waitForDisconnected(3000)); + + QVERIFY(server.waitForNewConnection(3000)); + QSctpSocket *acceptedSocket = server.nextPendingDatagramConnection(); + QVERIFY(acceptedSocket); + + QVERIFY(acceptedSocket->waitForReadyRead(3000)); + QNetworkDatagram datagram = acceptedSocket->readDatagram(); + QVERIFY(datagram.isValid()); + QCOMPARE(datagram.data(), sendData); +} + +QTEST_MAIN(tst_QSctpSocket) + +#include "tst_qsctpsocket.moc" diff --git a/tests/auto/network/socket/qsocks5socketengine/qsocks5socketengine.pro b/tests/auto/network/socket/qsocks5socketengine/qsocks5socketengine.pro index f3c24e19fd..71ceafa133 100644 --- a/tests/auto/network/socket/qsocks5socketengine/qsocks5socketengine.pro +++ b/tests/auto/network/socket/qsocks5socketengine/qsocks5socketengine.pro @@ -10,4 +10,4 @@ MOC_DIR=tmp QT = core-private network-private testlib -requires(contains(QT_CONFIG,private_tests)) +requires(qtConfig(private_tests)) diff --git a/tests/auto/network/socket/qsocks5socketengine/tst_qsocks5socketengine.cpp b/tests/auto/network/socket/qsocks5socketengine/tst_qsocks5socketengine.cpp index b4955df107..c945d77cda 100644 --- a/tests/auto/network/socket/qsocks5socketengine/tst_qsocks5socketengine.cpp +++ b/tests/auto/network/socket/qsocks5socketengine/tst_qsocks5socketengine.cpp @@ -785,11 +785,7 @@ void tst_QSocks5SocketEngine::downloadBigFile() QTime stopWatch; stopWatch.start(); -#if !defined(Q_OS_WINCE) QTestEventLoop::instance().enterLoop(60); -#else - QTestEventLoop::instance().enterLoop(180); -#endif if (QTestEventLoop::instance().timeout()) QFAIL("Network operation timed out"); diff --git a/tests/auto/network/socket/qtcpserver/crashingServer/main.cpp b/tests/auto/network/socket/qtcpserver/crashingServer/main.cpp index fdf1c48adf..1a8e7920d3 100644 --- a/tests/auto/network/socket/qtcpserver/crashingServer/main.cpp +++ b/tests/auto/network/socket/qtcpserver/crashingServer/main.cpp @@ -47,16 +47,8 @@ int main(int argc, char *argv[]) return 1; } -#if defined(Q_OS_WINCE) - QFile file(QLatin1String("/test_signal.txt")); - file.open(QIODevice::WriteOnly); - file.write("Listening\n"); - file.flush(); - file.close(); -#else printf("Listening\n"); fflush(stdout); -#endif server.waitForNewConnection(5000); qFatal("Crash"); diff --git a/tests/auto/network/socket/qtcpserver/test/test.pro b/tests/auto/network/socket/qtcpserver/test/test.pro index f0abfbc085..4491523383 100644 --- a/tests/auto/network/socket/qtcpserver/test/test.pro +++ b/tests/auto/network/socket/qtcpserver/test/test.pro @@ -1,16 +1,7 @@ CONFIG += testcase SOURCES += ../tst_qtcpserver.cpp -win32: { -wince { - LIBS += -lws2 - crashApp.files = ../crashingServer/crashingServer.exe - crashApp.path = crashingServer - DEPLOYMENT += crashApp -} else { - LIBS += -lws2_32 -} -} +win32:LIBS += -lws2_32 TARGET = ../tst_qtcpserver diff --git a/tests/auto/network/socket/qtcpserver/tst_qtcpserver.cpp b/tests/auto/network/socket/qtcpserver/tst_qtcpserver.cpp index 30aab3bf34..5a0baf73b5 100644 --- a/tests/auto/network/socket/qtcpserver/tst_qtcpserver.cpp +++ b/tests/auto/network/socket/qtcpserver/tst_qtcpserver.cpp @@ -106,6 +106,8 @@ private slots: void eagainBlockingAccept(); + void canAccessPendingConnectionsWhileNotListening(); + private: #ifndef QT_NO_BEARERMANAGEMENT QNetworkSession *networkSession; @@ -467,11 +469,7 @@ void tst_QTcpServer::waitForConnectionTest() ThreadConnector connector(findLocalIpSocket.localAddress(), server.serverPort()); connector.start(); -#if defined(Q_OS_WINCE) - QVERIFY(server.waitForNewConnection(9000, &timeout)); -#else QVERIFY(server.waitForNewConnection(3000, &timeout)); -#endif QVERIFY(!timeout); } @@ -562,21 +560,6 @@ void tst_QTcpServer::addressReusable() QSKIP("No proxy support"); #endif // QT_NO_NETWORKPROXY } -#if defined(Q_OS_WINCE) - QString signalName = QString::fromLatin1("/test_signal.txt"); - QFile::remove(signalName); - // The crashingServer process will crash once it gets a connection. - QProcess process; - QString processExe = crashingServerDir + "/crashingServer"; - process.start(processExe); - QVERIFY2(process.waitForStarted(), qPrintable( - QString::fromLatin1("Could not start %1: %2").arg(processExe, process.errorString()))); - int waitCount = 5; - while (waitCount-- && !QFile::exists(signalName)) - QTest::qWait(1000); - QVERIFY(QFile::exists(signalName)); - QFile::remove(signalName); -#else // The crashingServer process will crash once it gets a connection. QProcess process; QString processExe = crashingServerDir + "/crashingServer"; @@ -584,7 +567,6 @@ void tst_QTcpServer::addressReusable() QVERIFY2(process.waitForStarted(), qPrintable( QString::fromLatin1("Could not start %1: %2").arg(processExe, process.errorString()))); QVERIFY(process.waitForReadyRead(5000)); -#endif QTcpSocket socket; socket.connectToHost(QHostAddress::LocalHost, 49199); @@ -990,5 +972,22 @@ void tst_QTcpServer::eagainBlockingAccept() server.close(); } +class NonListeningTcpServer : public QTcpServer +{ +public: + void addSocketFromOutside(QTcpSocket* s) + { + addPendingConnection(s); + } +}; + +void tst_QTcpServer::canAccessPendingConnectionsWhileNotListening() +{ + NonListeningTcpServer server; + QTcpSocket socket; + server.addSocketFromOutside(&socket); + QCOMPARE(&socket, server.nextPendingConnection()); +} + QTEST_MAIN(tst_QTcpServer) #include "tst_qtcpserver.moc" diff --git a/tests/auto/network/socket/qtcpsocket/qtcpsocket.pro b/tests/auto/network/socket/qtcpsocket/qtcpsocket.pro index fe6042b8a7..1183b23556 100644 --- a/tests/auto/network/socket/qtcpsocket/qtcpsocket.pro +++ b/tests/auto/network/socket/qtcpsocket/qtcpsocket.pro @@ -1,6 +1,6 @@ TEMPLATE = subdirs SUBDIRS = test -!wince:!vxworks: SUBDIRS += stressTest +!vxworks: SUBDIRS += stressTest -requires(contains(QT_CONFIG,private_tests)) +requires(qtConfig(private_tests)) diff --git a/tests/auto/network/socket/qtcpsocket/test/test.pro b/tests/auto/network/socket/qtcpsocket/test/test.pro index 3e64b87b53..337e75b372 100644 --- a/tests/auto/network/socket/qtcpsocket/test/test.pro +++ b/tests/auto/network/socket/qtcpsocket/test/test.pro @@ -2,13 +2,7 @@ CONFIG += testcase QT = core-private network-private testlib SOURCES += ../tst_qtcpsocket.cpp -win32: { -wince { - LIBS += -lws2 -} else { - LIBS += -lws2_32 -} -} +win32:LIBS += -lws2_32 TARGET = tst_qtcpsocket diff --git a/tests/auto/network/socket/qudpsocket/test/test.pro b/tests/auto/network/socket/qudpsocket/test/test.pro index e4812416dc..73486a2bc3 100644 --- a/tests/auto/network/socket/qudpsocket/test/test.pro +++ b/tests/auto/network/socket/qudpsocket/test/test.pro @@ -15,10 +15,4 @@ win32 { DESTDIR = ../ } -wince* { - addApp.files = ../clientserver/clientserver.exe - addApp.path = clientserver - DEPLOYMENT += addApp -} - TARGET = tst_qudpsocket diff --git a/tests/auto/network/socket/qudpsocket/tst_qudpsocket.cpp b/tests/auto/network/socket/qudpsocket/tst_qudpsocket.cpp index ba49e8b041..aa01384350 100644 --- a/tests/auto/network/socket/qudpsocket/tst_qudpsocket.cpp +++ b/tests/auto/network/socket/qudpsocket/tst_qudpsocket.cpp @@ -39,6 +39,7 @@ #include <qhostinfo.h> #include <qtcpsocket.h> #include <qmap.h> +#include <qnetworkdatagram.h> #include <QNetworkProxy> #include <QNetworkInterface> @@ -114,6 +115,7 @@ protected slots: void async_readDatagramSlot(); private: + QList<QHostAddress> allAddresses; #ifndef QT_NO_BEARERMANAGEMENT QNetworkConfigurationManager *netConfMan; QNetworkConfiguration networkConfiguration; @@ -173,6 +175,7 @@ void tst_QUdpSocket::initTestCase() { if (!QtNetworkSettings::verifyTestNetworkSettings()) QSKIP("No network test server available"); + allAddresses = QNetworkInterface::allAddresses(); } void tst_QUdpSocket::init() @@ -252,6 +255,11 @@ void tst_QUdpSocket::unconnectedServerAndClientTest() int(strlen(message[i]))); buf[strlen(message[i])] = '\0'; QCOMPARE(QByteArray(buf), QByteArray(message[i])); + QCOMPARE(port, clientSocket.localPort()); + if (host.toIPv4Address()) // in case the sender is IPv4 mapped in IPv6 + QCOMPARE(host.toIPv4Address(), makeNonAny(clientSocket.localAddress()).toIPv4Address()); + else + QCOMPARE(host, makeNonAny(clientSocket.localAddress())); } } @@ -325,14 +333,32 @@ void tst_QUdpSocket::broadcasting() QVERIFY(serverSocket.hasPendingDatagrams()); do { - QByteArray arr; arr.resize(serverSocket.pendingDatagramSize() + 1); - QHostAddress host; - quint16 port; const int messageLength = int(strlen(message[i])); - QCOMPARE((int) serverSocket.readDatagram(arr.data(), arr.size() - 1, &host, &port), - messageLength); + QNetworkDatagram dgram = serverSocket.receiveDatagram(); + QVERIFY(dgram.isValid()); + QByteArray arr = dgram.data(); + + QCOMPARE(arr.length(), messageLength); arr.resize(messageLength); QCOMPARE(arr, QByteArray(message[i])); + + if (dgram.senderAddress().toIPv4Address()) // in case it's a v6-mapped address + QVERIFY2(allAddresses.contains(QHostAddress(dgram.senderAddress().toIPv4Address())), + dgram.senderAddress().toString().toLatin1()); + else if (!dgram.senderAddress().isNull()) + QVERIFY2(allAddresses.contains(dgram.senderAddress()), + dgram.senderAddress().toString().toLatin1()); + QCOMPARE(dgram.senderPort(), int(broadcastSocket.localPort())); + if (!dgram.destinationAddress().isNull()) { + QVERIFY2(dgram.destinationAddress() == QHostAddress::Broadcast + || broadcastAddresses.contains(dgram.destinationAddress()), + dgram.destinationAddress().toString().toLatin1()); + QCOMPARE(dgram.destinationPort(), int(serverSocket.localPort())); + } + + int ttl = dgram.hopLimit(); + if (ttl != -1) + QVERIFY(ttl != 0); } while (serverSocket.hasPendingDatagrams()); } } @@ -435,13 +461,8 @@ void tst_QUdpSocket::ipv6Loop() char peterBuffer[16*1024]; char paulBuffer[16*1024]; -#if !defined(Q_OS_WINCE) - QVERIFY2(peter.waitForReadyRead(5000), QtNetworkSettings::msgSocketError(peter).constData()); - QVERIFY2(paul.waitForReadyRead(5000), QtNetworkSettings::msgSocketError(paul).constData()); -#else - QVERIFY(peter.waitForReadyRead(15000)); - QVERIFY(paul.waitForReadyRead(15000)); -#endif + QVERIFY(peter.waitForReadyRead(5000)); + QVERIFY(paul.waitForReadyRead(5000)); if (success) { QCOMPARE(peter.readDatagram(peterBuffer, sizeof(peterBuffer)), qint64(paulMessage.length())); QCOMPARE(paul.readDatagram(paulBuffer, sizeof(peterBuffer)), qint64(peterMessage.length())); @@ -938,9 +959,6 @@ void tst_QUdpSocket::outOfProcessConnectedClientServerTest() #ifdef QT_NO_PROCESS QSKIP("No qprocess support", SkipAll); #else -#if defined(Q_OS_WINCE) - QSKIP("This test depends on reading data from QProcess (not supported on Qt/WinCE)."); -#endif QProcess serverProcess; serverProcess.start(QLatin1String("clientserver/clientserver server 1 1"), QIODevice::ReadWrite | QIODevice::Text); @@ -1002,9 +1020,6 @@ void tst_QUdpSocket::outOfProcessUnconnectedClientServerTest() #ifdef QT_NO_PROCESS QSKIP("No qprocess support", SkipAll); #else -#if defined(Q_OS_WINCE) - QSKIP("This test depends on reading data from QProcess (not supported on Qt/WinCE)."); -#endif QProcess serverProcess; serverProcess.start(QLatin1String("clientserver/clientserver server 1 1"), QIODevice::ReadWrite | QIODevice::Text); @@ -1081,7 +1096,7 @@ void tst_QUdpSocket::zeroLengthDatagram() #ifdef FORCE_SESSION sender.setProperty("_q_networksession", QVariant::fromValue(networkSession)); #endif - QCOMPARE(sender.writeDatagram(QByteArray(), QHostAddress::LocalHost, receiver.localPort()), qint64(0)); + QCOMPARE(sender.writeDatagram(QNetworkDatagram(QByteArray(), QHostAddress::LocalHost, receiver.localPort())), qint64(0)); QVERIFY2(receiver.waitForReadyRead(1000), QtNetworkSettings::msgSocketError(receiver).constData()); QVERIFY(receiver.hasPendingDatagrams()); @@ -1366,10 +1381,20 @@ void tst_QUdpSocket::multicast() QVERIFY(receiver.hasPendingDatagrams()); QList<QByteArray> receivedDatagrams; while (receiver.hasPendingDatagrams()) { - QByteArray datagram; - datagram.resize(receiver.pendingDatagramSize()); - receiver.readDatagram(datagram.data(), datagram.size(), 0, 0); - receivedDatagrams << datagram; + QNetworkDatagram dgram = receiver.receiveDatagram(); + receivedDatagrams << dgram.data(); + + QVERIFY2(allAddresses.contains(dgram.senderAddress()), + dgram.senderAddress().toString().toLatin1()); + QCOMPARE(dgram.senderPort(), int(sender.localPort())); + if (!dgram.destinationAddress().isNull()) { + QCOMPARE(dgram.destinationAddress(), groupAddress); + QCOMPARE(dgram.destinationPort(), int(receiver.localPort())); + } + + int ttl = dgram.hopLimit(); + if (ttl != -1) + QVERIFY(ttl != 0); } QCOMPARE(receivedDatagrams, datagrams); @@ -1464,7 +1489,8 @@ void tst_QUdpSocket::linkLocalIPv6() quint16 port = 0; foreach (const QHostAddress& addr, addresses) { QUdpSocket *s = new QUdpSocket; - QVERIFY2(s->bind(addr, port), qPrintable(s->errorString())); + QVERIFY2(s->bind(addr, port), addr.toString().toLatin1() + + '/' + QByteArray::number(port) + ": " + qPrintable(s->errorString())); port = s->localPort(); //bind same port, different networks sockets << s; } @@ -1474,24 +1500,25 @@ void tst_QUdpSocket::linkLocalIPv6() QSignalSpy neutralReadSpy(&neutral, SIGNAL(readyRead())); QByteArray testData("hello"); - QByteArray receiveBuffer("xxxxx"); foreach (QUdpSocket *s, sockets) { QSignalSpy spy(s, SIGNAL(readyRead())); neutralReadSpy.clear(); QVERIFY(s->writeDatagram(testData, s->localAddress(), neutral.localPort())); QTRY_VERIFY(neutralReadSpy.count() > 0); //note may need to accept a firewall prompt - QHostAddress from; - quint16 fromPort; - QCOMPARE((int)neutral.readDatagram(receiveBuffer.data(), receiveBuffer.length(), &from, &fromPort), testData.length()); - QCOMPARE(from, s->localAddress()); - QCOMPARE(fromPort, s->localPort()); - QCOMPARE(receiveBuffer, testData); - - QVERIFY(neutral.writeDatagram(testData, s->localAddress(), s->localPort())); + + QNetworkDatagram dgram = neutral.receiveDatagram(testData.length() * 2); + QVERIFY(dgram.isValid()); + QCOMPARE(dgram.senderAddress(), s->localAddress()); + QCOMPARE(dgram.senderPort(), int(s->localPort())); + QCOMPARE(dgram.data().length(), testData.length()); + QCOMPARE(dgram.data(), testData); + + QVERIFY(neutral.writeDatagram(dgram.makeReply(testData))); QTRY_VERIFY(spy.count() > 0); //note may need to accept a firewall prompt - QCOMPARE((int)s->readDatagram(receiveBuffer.data(), receiveBuffer.length(), &from, &fromPort), testData.length()); - QCOMPARE(receiveBuffer, testData); + + dgram = s->receiveDatagram(testData.length() * 2); + QCOMPARE(dgram.data(), testData); //sockets bound to other interfaces shouldn't have received anything foreach (QUdpSocket *s2, sockets) { @@ -1546,21 +1573,23 @@ void tst_QUdpSocket::linkLocalIPv4() QVERIFY(neutral.bind(QHostAddress(QHostAddress::AnyIPv4))); QByteArray testData("hello"); - QByteArray receiveBuffer("xxxxx"); foreach (QUdpSocket *s, sockets) { QVERIFY(s->writeDatagram(testData, s->localAddress(), neutral.localPort())); QVERIFY2(neutral.waitForReadyRead(10000), QtNetworkSettings::msgSocketError(neutral).constData()); - QHostAddress from; - quint16 fromPort; - QCOMPARE((int)neutral.readDatagram(receiveBuffer.data(), receiveBuffer.length(), &from, &fromPort), testData.length()); - QCOMPARE(from, s->localAddress()); - QCOMPARE(fromPort, s->localPort()); - QCOMPARE(receiveBuffer, testData); - - QVERIFY(neutral.writeDatagram(testData, s->localAddress(), s->localPort())); + QVERIFY2(s->waitForReadyRead(10000), QtNetworkSettings::msgSocketError(*s).constData()); - QCOMPARE((int)s->readDatagram(receiveBuffer.data(), receiveBuffer.length(), &from, &fromPort), testData.length()); - QCOMPARE(receiveBuffer, testData); + QNetworkDatagram dgram = neutral.receiveDatagram(testData.length() * 2); + QVERIFY(dgram.isValid()); + QCOMPARE(dgram.senderAddress(), s->localAddress()); + QCOMPARE(dgram.senderPort(), int(s->localPort())); + QCOMPARE(dgram.data().length(), testData.length()); + QCOMPARE(dgram.data(), testData); + + QVERIFY(neutral.writeDatagram(dgram.makeReply(testData))); + + dgram = s->receiveDatagram(testData.length() * 2); + QVERIFY(dgram.isValid()); + QCOMPARE(dgram.data(), testData); //sockets bound to other interfaces shouldn't have received anything foreach (QUdpSocket *s2, sockets) { diff --git a/tests/auto/network/socket/socket.pro b/tests/auto/network/socket/socket.pro index 436ebe5c7f..06fe356a5a 100644 --- a/tests/auto/network/socket/socket.pro +++ b/tests/auto/network/socket/socket.pro @@ -1,4 +1,6 @@ TEMPLATE=subdirs +QT_FOR_CONFIG += network + SUBDIRS=\ qhttpsocketengine \ qudpsocket \ @@ -8,13 +10,17 @@ SUBDIRS=\ qsocks5socketengine \ qabstractsocket \ platformsocketengine \ + qsctpsocket \ -!contains(QT_CONFIG, private_tests): SUBDIRS -= \ +!qtConfig(private_tests): SUBDIRS -= \ platformsocketengine \ qtcpsocket \ qhttpsocketengine \ qsocks5socketengine \ +!qtConfig(sctp): SUBDIRS -= \ + qsctpsocket \ + winrt: SUBDIRS -= \ qhttpsocketengine \ qsocks5socketengine \ diff --git a/tests/auto/network/ssl/qsslcertificate/qsslcertificate.pro b/tests/auto/network/ssl/qsslcertificate/qsslcertificate.pro index 87a210c051..7c1cd5b66b 100644 --- a/tests/auto/network/ssl/qsslcertificate/qsslcertificate.pro +++ b/tests/auto/network/ssl/qsslcertificate/qsslcertificate.pro @@ -1,7 +1,7 @@ CONFIG += testcase SOURCES += tst_qsslcertificate.cpp -!wince:win32:LIBS += -lws2_32 +win32:LIBS += -lws2_32 QT = core network testlib TARGET = tst_qsslcertificate diff --git a/tests/auto/network/ssl/qsslcipher/qsslcipher.pro b/tests/auto/network/ssl/qsslcipher/qsslcipher.pro index 4cb2dfebab..81ef2d8d9a 100644 --- a/tests/auto/network/ssl/qsslcipher/qsslcipher.pro +++ b/tests/auto/network/ssl/qsslcipher/qsslcipher.pro @@ -1,7 +1,7 @@ CONFIG += testcase SOURCES += tst_qsslcipher.cpp -win32:!wince: LIBS += -lws2_32 +win32:LIBS += -lws2_32 QT = core network testlib TARGET = tst_qsslcipher diff --git a/tests/auto/network/ssl/qssldiffiehellmanparameters/qssldiffiehellmanparameters.pro b/tests/auto/network/ssl/qssldiffiehellmanparameters/qssldiffiehellmanparameters.pro new file mode 100644 index 0000000000..b8053f9eb3 --- /dev/null +++ b/tests/auto/network/ssl/qssldiffiehellmanparameters/qssldiffiehellmanparameters.pro @@ -0,0 +1,8 @@ +CONFIG += testcase +CONFIG += parallel_test + +SOURCES += tst_qssldiffiehellmanparameters.cpp +!wince*:win32:LIBS += -lws2_32 +QT = core network testlib + +TARGET = tst_qssldiffiehellmanparameters diff --git a/tests/auto/network/ssl/qssldiffiehellmanparameters/tst_qssldiffiehellmanparameters.cpp b/tests/auto/network/ssl/qssldiffiehellmanparameters/tst_qssldiffiehellmanparameters.cpp new file mode 100644 index 0000000000..f3b9003fbb --- /dev/null +++ b/tests/auto/network/ssl/qssldiffiehellmanparameters/tst_qssldiffiehellmanparameters.cpp @@ -0,0 +1,163 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Mikkel Krautz <mikkel@krautz.dk> +** Contact: http://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 <QtTest/QtTest> +#include <QSslDiffieHellmanParameters> +#include <QSslSocket> +#include <QByteArray> + +class tst_QSslDiffieHellmanParameters : public QObject +{ + Q_OBJECT + +#ifndef QT_NO_SSL +private Q_SLOTS: + void constructionEmpty(); + void constructionDefault(); + void constructionDER(); + void constructionPEM(); + void unsafe512Bits(); + void unsafeNonPrime(); +#endif +}; + +#ifndef QT_NO_SSL + +void tst_QSslDiffieHellmanParameters::constructionEmpty() +{ + QSslDiffieHellmanParameters dh; + + QCOMPARE(dh.isEmpty(), true); + QCOMPARE(dh.isValid(), true); + QCOMPARE(dh.error(), QSslDiffieHellmanParameters::NoError); +} + +void tst_QSslDiffieHellmanParameters::constructionDefault() +{ + QSslDiffieHellmanParameters dh = QSslDiffieHellmanParameters::defaultParameters(); + +#ifndef QT_NO_OPENSSL + QCOMPARE(dh.isValid(), true); + QCOMPARE(dh.error(), QSslDiffieHellmanParameters::NoError); +#endif +} + +void tst_QSslDiffieHellmanParameters::constructionDER() +{ + // Uniquely generated with 'openssl dhparam -outform DER -out out.der -check -2 4096' + const auto dh = QSslDiffieHellmanParameters::fromEncoded(QByteArray::fromBase64(QByteArrayLiteral( + "MIICCAKCAgEAsbQYx57ZlyEyWF8jD5WYEswGR2aTVFsHqP3026SdyTwcjY+YlMOae0EagK" + "jDA0UlPcih1kguQOvOVgyc5gI3YbBb4pCNEdy048xITlsdqG7qC3+2VvFR3vfixEbQQll9" + "2cGIIneD/36p7KJcDnBNUwwWj/VJKhTwelTfKTj2T39si9xGMkqZiQuCaXRk6vSKZ4ZDPk" + "jiq5Ti1kHVFbL9SMWRa8zplPtDMrVfhSyw10njgD4qKd1UoUPdmhEPhRZlHaZ/cAHNSHMj" + "uhDakeMpN+XP2/sl5IpPZ3/vVOk9PhBDFO1NYzKx/b7RQgZCUmXoglKYpfBiz8OheoI0hK" + "V0fU/OCtHjRrP4hE9vIHA2aE+gaQZiYCciGcR9BjHQ7Y8K9qHyTX8UIz2G4ZKzQZK9G+pA" + "K0xD+1H3qZ/MaUhzNDQOwwihnTjjXzTjfIGqYDdbouAhw+tX51CsGonI0cL3s3QMa3CwGH" + "mw+AH2b/Z68dTSy0sC3CYn9cNbrctqyeHwQrsx9FfpOz+Z6sk2WsPgqgSp/pDVVgm5oSfO" + "2mN7WAWgUlf9TQuj1HIRCTI+PbBq2vYvn+YResMRo+8ng1QptKAAgQoVVGNRYxZ9iAZlvO" + "52DcHKlsqDuafQ1XVGmzVIrKtBi2gfLtPqY4v6g6v26l8gbzK67PpWstllHiPb4VMCAQI=" + )), QSsl::Der); + +#ifndef QT_NO_OPENSSL + QCOMPARE(dh.isValid(), true); + QCOMPARE(dh.error(), QSslDiffieHellmanParameters::NoError); +#endif +} + +void tst_QSslDiffieHellmanParameters::constructionPEM() +{ + // Uniquely generated with 'openssl dhparam -outform PEM -out out.pem -check -2 4096' + const auto dh = QSslDiffieHellmanParameters::fromEncoded(QByteArrayLiteral( + "-----BEGIN DH PARAMETERS-----\n" + "MIICCAKCAgEA9QTdqhQkbGuhWzBsW5X475AjjrITpg1BHX5+mp1sstUd84Lshq1T\n" + "+S2QQQtdl25EPoUblpyyLAf8krFSH4YwR7jjLWklA8paDOwRYod0zLmVZ1Wx6og3\n" + "PRc8P+SCs+6gKTXfv//bJJhiJXnM73lDFsGHbSqN+msf20ei/zy5Rwey2t8dPjLC\n" + "Q+qkb/avlovi2t2rsUWcxMT1875TQ4HuApayqw3R3lTQe9u05b9rTrinmT7AE4mm\n" + "xGqO9FZJdXYE2sOKwwJkpM48KFyV90uJANmqJnQrkgdukaGTHwxZxgAyO6ur/RWC\n" + "kzf9STFT6IY4Qy05q+oZVJfh8xPHszKmmC8nWaLfiHMYBnL5fv+1kh/aU11Kz9TG\n" + "iDXwQ+tzhKAutQPUwe3IGQUYQMZPwZI4vegdU88/7YPXuWt7b/0Il5+2ma5FbtG2\n" + "u02PMi+J3JZsYi/tEUv1tJBVHGH0kDpgcyOm8rvkCtNbNkETzfwUPoEgA0oPMhVt\n" + "sFGub1av+jLRyFNGNBJcqXAO+Tq2zXG00DxbGY+aooJ50qU/Lh5gfnCEMDXlMM9P\n" + "T8JVpWaaNLCC+0Z5txsfYp+FO8mOttIPIF6F8FtmTnm/jhNntvqKvsU+NHylIYzr\n" + "o42EpiWwS7ktPPUS2GtG+IUdy8rvdO1xJ5kNxs7ZlygY4W1htOhbUusCAQI=\n" + "-----END DH PARAMETERS-----\n" + ), QSsl::Pem); + +#ifndef QT_NO_OPENSSL + QCOMPARE(dh.isValid(), true); + QCOMPARE(dh.error(), QSslDiffieHellmanParameters::NoError); +#endif +} + +void tst_QSslDiffieHellmanParameters::unsafe512Bits() +{ + // Uniquely generated with 'openssl dhparam -outform PEM -out out.pem -check -2 512' + const auto dh = QSslDiffieHellmanParameters::fromEncoded(QByteArrayLiteral( + "-----BEGIN DH PARAMETERS-----\n" + "MEYCQQCf8goDn56akiliAtEL1ZG7VH+9wfLxsv8/B1emTUG+rMKB1yaVAU7HaAiM\n" + "Gtmo2bAWUqBczUTOTzqmWTm28P6bAgEC\n" + "-----END DH PARAMETERS-----\n" + ), QSsl::Pem); + +#ifndef QT_NO_OPENSSL + QCOMPARE(dh.isValid(), false); + QCOMPARE(dh.error(), QSslDiffieHellmanParameters::UnsafeParametersError); +#endif +} + +void tst_QSslDiffieHellmanParameters::unsafeNonPrime() +{ + // Uniquely generated with 'openssl dhparam -outform DER -out out.der -check -2 1024' + // and then modified by hand to make P not be a prime number. + const auto dh = QSslDiffieHellmanParameters::fromEncoded(QByteArray::fromBase64(QByteArrayLiteral( + "MIGHAoGBALLcOLg+ow8TMnbCUeNjwys6wUTIH9mn4ZSeIbD6qvCsJgg4cUxXwJQmPY" + "Xl15AsKXgkXWh0n+/N6tjH0sSRJnzDvN2H3KxFLKkvxmBYrDOJMdCuMgZD50aOsVyd" + "vholAW9zilkoYkB6sqwxY1Z2dbpTWajCsUAWZQ0AIP4Y5nesAgEC" + )), QSsl::Der); + +#ifndef QT_NO_OPENSSL + QCOMPARE(dh.isValid(), false); + QCOMPARE(dh.error(), QSslDiffieHellmanParameters::UnsafeParametersError); +#endif +} + +#endif // QT_NO_SSL + +QTEST_MAIN(tst_QSslDiffieHellmanParameters) +#include "tst_qssldiffiehellmanparameters.moc" diff --git a/tests/auto/network/ssl/qsslellipticcurve/qsslellipticcurve.pro b/tests/auto/network/ssl/qsslellipticcurve/qsslellipticcurve.pro index e67b64b2b7..a180086c5e 100644 --- a/tests/auto/network/ssl/qsslellipticcurve/qsslellipticcurve.pro +++ b/tests/auto/network/ssl/qsslellipticcurve/qsslellipticcurve.pro @@ -1,7 +1,7 @@ CONFIG += testcase SOURCES += tst_qsslellipticcurve.cpp -win32:!wince: LIBS += -lws2_32 +win32:LIBS += -lws2_32 QT = core network testlib TARGET = tst_qsslellipticcurve diff --git a/tests/auto/network/ssl/qsslerror/qsslerror.pro b/tests/auto/network/ssl/qsslerror/qsslerror.pro index 7737aae3f1..117fd4ac27 100644 --- a/tests/auto/network/ssl/qsslerror/qsslerror.pro +++ b/tests/auto/network/ssl/qsslerror/qsslerror.pro @@ -1,7 +1,7 @@ CONFIG += testcase SOURCES += tst_qsslerror.cpp -win32:!wince: LIBS += -lws2_32 +win32:LIBS += -lws2_32 QT = core network testlib TARGET = tst_qsslerror diff --git a/tests/auto/network/ssl/qsslkey/qsslkey.pro b/tests/auto/network/ssl/qsslkey/qsslkey.pro index 7eb04793f3..8c3877631a 100644 --- a/tests/auto/network/ssl/qsslkey/qsslkey.pro +++ b/tests/auto/network/ssl/qsslkey/qsslkey.pro @@ -1,9 +1,9 @@ CONFIG += testcase SOURCES += tst_qsslkey.cpp -win32:!wince: LIBS += -lws2_32 +win32:LIBS += -lws2_32 QT = core network testlib -contains(QT_CONFIG, private_tests) { +qtConfig(private_tests) { QT += core-private network-private } diff --git a/tests/auto/network/ssl/qsslkey/tst_qsslkey.cpp b/tests/auto/network/ssl/qsslkey/tst_qsslkey.cpp index 8afc71a216..0112af4ed7 100644 --- a/tests/auto/network/ssl/qsslkey/tst_qsslkey.cpp +++ b/tests/auto/network/ssl/qsslkey/tst_qsslkey.cpp @@ -34,9 +34,14 @@ #include <QtNetwork/qhostaddress.h> #include <QtNetwork/qnetworkproxy.h> -#if !defined(QT_NO_SSL) && defined(QT_BUILD_INTERNAL) -#include "private/qsslkey_p.h" -#define TEST_CRYPTO +#ifdef QT_BUILD_INTERNAL + #ifndef QT_NO_SSL + #include "private/qsslkey_p.h" + #define TEST_CRYPTO + #endif + #ifndef QT_NO_OPENSSL + #include "private/qsslsocket_openssl_symbols_p.h" + #endif #endif class tst_QSslKey : public QObject @@ -58,7 +63,7 @@ class tst_QSslKey : public QObject QList<KeyInfo> keyInfoList; - void createPlainTestRows(); + void createPlainTestRows(bool filter = false, QSsl::EncodingFormat format = QSsl::EncodingFormat::Pem); public slots: void initTestCase(); @@ -69,6 +74,10 @@ private slots: void emptyConstructor(); void constructor_data(); void constructor(); +#ifndef QT_NO_OPENSSL + void constructorHandle_data(); + void constructorHandle(); +#endif void copyAndAssign_data(); void copyAndAssign(); void equalsOperator(); @@ -142,7 +151,7 @@ Q_DECLARE_METATYPE(QSsl::KeyAlgorithm) Q_DECLARE_METATYPE(QSsl::KeyType) Q_DECLARE_METATYPE(QSsl::EncodingFormat) -void tst_QSslKey::createPlainTestRows() +void tst_QSslKey::createPlainTestRows(bool filter, QSsl::EncodingFormat format) { QTest::addColumn<QString>("absFilePath"); QTest::addColumn<QSsl::KeyAlgorithm>("algorithm"); @@ -150,6 +159,9 @@ void tst_QSslKey::createPlainTestRows() QTest::addColumn<int>("length"); QTest::addColumn<QSsl::EncodingFormat>("format"); foreach (KeyInfo keyInfo, keyInfoList) { + if (filter && keyInfo.format != format) + continue; + QTest::newRow(keyInfo.fileInfo.fileName().toLatin1()) << keyInfo.fileInfo.absoluteFilePath() << keyInfo.algorithm << keyInfo.type << keyInfo.length << keyInfo.format; @@ -176,6 +188,45 @@ void tst_QSslKey::constructor() QVERIFY(!key.isNull()); } +#ifndef QT_NO_OPENSSL + +void tst_QSslKey::constructorHandle_data() +{ + createPlainTestRows(true); +} + +void tst_QSslKey::constructorHandle() +{ +#ifndef QT_BUILD_INTERNAL + QSKIP("This test requires -developer-build."); +#else + if (!QSslSocket::supportsSsl()) + return; + + QFETCH(QString, absFilePath); + QFETCH(QSsl::KeyAlgorithm, algorithm); + QFETCH(QSsl::KeyType, type); + QFETCH(int, length); + + QByteArray pem = readFile(absFilePath); + auto func = (type == QSsl::KeyType::PublicKey + ? q_PEM_read_bio_PUBKEY + : q_PEM_read_bio_PrivateKey); + + BIO* bio = q_BIO_new(q_BIO_s_mem()); + q_BIO_write(bio, pem.constData(), pem.length()); + QSslKey key(func(bio, nullptr, nullptr, nullptr), type); + q_BIO_free(bio); + + QVERIFY(!key.isNull()); + QCOMPARE(key.algorithm(), algorithm); + QCOMPARE(key.type(), type); + QCOMPARE(key.length(), length); +#endif +} + +#endif + void tst_QSslKey::copyAndAssign_data() { createPlainTestRows(); diff --git a/tests/auto/network/ssl/qsslsocket/qsslsocket.pro b/tests/auto/network/ssl/qsslsocket/qsslsocket.pro index de2be8e126..5c92ca833a 100644 --- a/tests/auto/network/ssl/qsslsocket/qsslsocket.pro +++ b/tests/auto/network/ssl/qsslsocket/qsslsocket.pro @@ -1,7 +1,7 @@ CONFIG += testcase SOURCES += tst_qsslsocket.cpp -win32:!wince: LIBS += -lws2_32 +win32:LIBS += -lws2_32 QT = core core-private network-private testlib TARGET = tst_qsslsocket @@ -15,19 +15,11 @@ win32 { } # OpenSSL support -contains(QT_CONFIG, openssl) | contains(QT_CONFIG, openssl-linked) { +qtConfig(openssl)|qtConfig(openssl-linked) { # Add optional SSL libs LIBS += $$OPENSSL_LIBS } -wince* { - DEFINES += SRCDIR=\\\"./\\\" +DEFINES += SRCDIR=\\\"$$PWD/\\\" - certFiles.files = certs ssl.tar.gz - certFiles.path = . - DEPLOYMENT += certFiles -} else { - DEFINES += SRCDIR=\\\"$$PWD/\\\" -} - -requires(contains(QT_CONFIG,private_tests)) +requires(qtConfig(private_tests)) diff --git a/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp b/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp index 00c3a41d88..4eb26d17fe 100644 --- a/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp +++ b/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp @@ -57,7 +57,7 @@ #include "private/qsslconfiguration_p.h" Q_DECLARE_METATYPE(QSslSocket::SslMode) -typedef QList<QSslError::SslError> SslErrorList; +typedef QVector<QSslError::SslError> SslErrorList; Q_DECLARE_METATYPE(SslErrorList) Q_DECLARE_METATYPE(QSslError) Q_DECLARE_METATYPE(QSslKey) @@ -220,6 +220,10 @@ private slots: void qtbug18498_peek(); void qtbug18498_peek2(); void dhServer(); +#ifndef QT_NO_OPENSSL + void dhServerCustomParamsNull(); + void dhServerCustomParams(); +#endif void ecdhServer(); void verifyClientCertificate_data(); void verifyClientCertificate(); @@ -230,6 +234,8 @@ private slots: void simplePskConnect(); void ephemeralServerKey_data(); void ephemeralServerKey(); + void allowedProtocolNegotiation(); + void pskServer(); #endif void setEmptyDefaultConfiguration(); // this test should be last @@ -381,14 +387,14 @@ void tst_QSslSocket::cleanup() #ifndef QT_NO_SSL QSslSocketPtr tst_QSslSocket::newSocket() { - QSslSocket *socket = new QSslSocket; + const auto socket = QSslSocketPtr::create(); proxyAuthCalled = 0; - connect(socket, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), + connect(socket.data(), SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), Qt::DirectConnection); - return QSslSocketPtr(socket); + return socket; } #endif @@ -628,7 +634,8 @@ void tst_QSslSocket::sslErrors() // check the SSL errors contain HostNameMismatch and an error due to // the certificate being self-signed SslErrorList sslErrors; - foreach (const QSslError &err, socket->sslErrors()) + const auto socketSslErrors = socket->sslErrors(); + for (const QSslError &err : socketSslErrors) sslErrors << err.error(); qSort(sslErrors); QVERIFY(sslErrors.contains(QSslError::HostNameMismatch)); @@ -637,7 +644,8 @@ void tst_QSslSocket::sslErrors() // check the same errors were emitted by sslErrors QVERIFY(!sslErrorsSpy.isEmpty()); SslErrorList emittedErrors; - foreach (const QSslError &err, qvariant_cast<QList<QSslError> >(sslErrorsSpy.first().first())) + const auto sslErrorsSpyErrors = qvariant_cast<QList<QSslError> >(qAsConst(sslErrorsSpy).first().first()); + for (const QSslError &err : sslErrorsSpyErrors) emittedErrors << err.error(); qSort(emittedErrors); QCOMPARE(sslErrors, emittedErrors); @@ -646,7 +654,7 @@ void tst_QSslSocket::sslErrors() QVERIFY(!peerVerifyErrorSpy.isEmpty()); SslErrorList peerErrors; const QList<QVariantList> &peerVerifyList = peerVerifyErrorSpy; - foreach (const QVariantList &args, peerVerifyList) + for (const QVariantList &args : peerVerifyList) peerErrors << qvariant_cast<QSslError>(args.first()).error(); qSort(peerErrors); QCOMPARE(sslErrors, peerErrors); @@ -1160,7 +1168,9 @@ void tst_QSslSocket::protocolServerSide_data() #if !defined(OPENSSL_NO_SSL2) && !defined(QT_SECURETRANSPORT) QTest::newRow("ssl2-ssl2") << QSsl::SslV2 << QSsl::SslV2 << false; // no idea why it does not work, but we don't care about SSL 2 #endif +#if !defined(OPENSSL_NO_SSL3) QTest::newRow("ssl3-ssl3") << QSsl::SslV3 << QSsl::SslV3 << true; +#endif QTest::newRow("tls1.0-tls1.0") << QSsl::TlsV1_0 << QSsl::TlsV1_0 << true; QTest::newRow("tls1ssl3-tls1ssl3") << QSsl::TlsV1SslV3 << QSsl::TlsV1SslV3 << true; QTest::newRow("any-any") << QSsl::AnyProtocol << QSsl::AnyProtocol << true; @@ -1174,23 +1184,27 @@ void tst_QSslSocket::protocolServerSide_data() QTest::newRow("ssl2-any") << QSsl::SslV2 << QSsl::AnyProtocol << false; // no idea why it does not work, but we don't care about SSL 2 #endif -#if !defined(OPENSSL_NO_SSL2) && !defined(QT_SECURETRANSPORT) +#if !defined(OPENSSL_NO_SSL2) && !defined(QT_SECURETRANSPORT) && !defined(OPENSSL_NO_SSL3) QTest::newRow("ssl3-ssl2") << QSsl::SslV3 << QSsl::SslV2 << false; #endif +#if !defined(OPENSSL_NO_SSL3) QTest::newRow("ssl3-tls1.0") << QSsl::SslV3 << QSsl::TlsV1_0 << false; QTest::newRow("ssl3-tls1ssl3") << QSsl::SslV3 << QSsl::TlsV1SslV3 << true; QTest::newRow("ssl3-secure") << QSsl::SslV3 << QSsl::SecureProtocols << false; -#if !defined(OPENSSL_NO_SSL2) && !defined(QT_SECURETRANSPORT) +#endif +#if !defined(OPENSSL_NO_SSL2) && !defined(QT_SECURETRANSPORT) && !defined(OPENSSL_NO_SSL3) QTest::newRow("ssl3-any") << QSsl::SslV3 << QSsl::AnyProtocol << false; // we won't set a SNI header here because we connect to a // numerical IP, so OpenSSL will send a SSL 2 handshake -#else +#elif !defined(OPENSSL_NO_SSL3) QTest::newRow("ssl3-any") << QSsl::SslV3 << QSsl::AnyProtocol << true; #endif #if !defined(OPENSSL_NO_SSL2) && !defined(QT_SECURETRANSPORT) QTest::newRow("tls1.0-ssl2") << QSsl::TlsV1_0 << QSsl::SslV2 << false; #endif +#if !defined(OPENSSL_NO_SSL3) QTest::newRow("tls1.0-ssl3") << QSsl::TlsV1_0 << QSsl::SslV3 << false; +#endif QTest::newRow("tls1-tls1ssl3") << QSsl::TlsV1_0 << QSsl::TlsV1SslV3 << true; QTest::newRow("tls1.0-secure") << QSsl::TlsV1_0 << QSsl::SecureProtocols << true; #if !defined(OPENSSL_NO_SSL2) && !defined(QT_SECURETRANSPORT) @@ -1203,7 +1217,9 @@ void tst_QSslSocket::protocolServerSide_data() #if !defined(OPENSSL_NO_SSL2) && !defined(QT_SECURETRANSPORT) QTest::newRow("tls1ssl3-ssl2") << QSsl::TlsV1SslV3 << QSsl::SslV2 << false; #endif +#if !defined(OPENSSL_NO_SSL3) QTest::newRow("tls1ssl3-ssl3") << QSsl::TlsV1SslV3 << QSsl::SslV3 << true; +#endif QTest::newRow("tls1ssl3-tls1.0") << QSsl::TlsV1SslV3 << QSsl::TlsV1_0 << true; QTest::newRow("tls1ssl3-secure") << QSsl::TlsV1SslV3 << QSsl::SecureProtocols << true; QTest::newRow("tls1ssl3-any") << QSsl::TlsV1SslV3 << QSsl::AnyProtocol << true; @@ -1211,7 +1227,9 @@ void tst_QSslSocket::protocolServerSide_data() #if !defined(OPENSSL_NO_SSL2) && !defined(QT_SECURETRANSPORT) QTest::newRow("secure-ssl2") << QSsl::SecureProtocols << QSsl::SslV2 << false; #endif +#if !defined(OPENSSL_NO_SSL3) QTest::newRow("secure-ssl3") << QSsl::SecureProtocols << QSsl::SslV3 << false; +#endif QTest::newRow("secure-tls1.0") << QSsl::SecureProtocols << QSsl::TlsV1_0 << true; QTest::newRow("secure-tls1ssl3") << QSsl::SecureProtocols << QSsl::TlsV1SslV3 << true; QTest::newRow("secure-any") << QSsl::SecureProtocols << QSsl::AnyProtocol << true; @@ -1219,7 +1237,9 @@ void tst_QSslSocket::protocolServerSide_data() #if !defined(OPENSSL_NO_SSL2) && !defined(QT_SECURETRANSPORT) QTest::newRow("any-ssl2") << QSsl::AnyProtocol << QSsl::SslV2 << false; // no idea why it does not work, but we don't care about SSL 2 #endif +#if !defined(OPENSSL_NO_SSL3) QTest::newRow("any-ssl3") << QSsl::AnyProtocol << QSsl::SslV3 << true; +#endif QTest::newRow("any-tls1.0") << QSsl::AnyProtocol << QSsl::TlsV1_0 << true; QTest::newRow("any-tls1ssl3") << QSsl::AnyProtocol << QSsl::TlsV1SslV3 << true; QTest::newRow("any-secure") << QSsl::AnyProtocol << QSsl::SecureProtocols << true; @@ -1244,8 +1264,8 @@ void tst_QSslSocket::protocolServerSide() QEventLoop loop; QTimer::singleShot(5000, &loop, SLOT(quit())); - QSslSocketPtr client(new QSslSocket); - socket = client.data(); + QSslSocket client; + socket = &client; QFETCH(QSsl::SslProtocol, clientProtocol); socket->setProtocol(clientProtocol); // upon SSL wrong version error, error will be triggered, not sslErrors @@ -1253,14 +1273,14 @@ void tst_QSslSocket::protocolServerSide() connect(socket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(ignoreErrorSlot())); connect(socket, SIGNAL(encrypted()), &loop, SLOT(quit())); - client->connectToHostEncrypted(QHostAddress(QHostAddress::LocalHost).toString(), server.serverPort()); + client.connectToHostEncrypted(QHostAddress(QHostAddress::LocalHost).toString(), server.serverPort()); loop.exec(); QFETCH(bool, works); QAbstractSocket::SocketState expectedState = (works) ? QAbstractSocket::ConnectedState : QAbstractSocket::UnconnectedState; - QCOMPARE(int(client->state()), int(expectedState)); - QCOMPARE(client->isEncrypted(), works); + QCOMPARE(int(client.state()), int(expectedState)); + QCOMPARE(client.isEncrypted(), works); } #ifndef QT_NO_OPENSSL @@ -1285,8 +1305,8 @@ void tst_QSslSocket::serverCipherPreferences() QEventLoop loop; QTimer::singleShot(5000, &loop, SLOT(quit())); - QSslSocketPtr client(new QSslSocket); - socket = client.data(); + QSslSocket client; + socket = &client; socket->setCiphers("AES256-SHA:AES128-SHA"); // upon SSL wrong version error, error will be triggered, not sslErrors @@ -1294,12 +1314,12 @@ void tst_QSslSocket::serverCipherPreferences() connect(socket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(ignoreErrorSlot())); connect(socket, SIGNAL(encrypted()), &loop, SLOT(quit())); - client->connectToHostEncrypted(QHostAddress(QHostAddress::LocalHost).toString(), server.serverPort()); + client.connectToHostEncrypted(QHostAddress(QHostAddress::LocalHost).toString(), server.serverPort()); loop.exec(); - QVERIFY(client->isEncrypted()); - QCOMPARE(client->sessionCipher().name(), QString("AES128-SHA")); + QVERIFY(client.isEncrypted()); + QCOMPARE(client.sessionCipher().name(), QString("AES128-SHA")); } { @@ -1314,8 +1334,8 @@ void tst_QSslSocket::serverCipherPreferences() QEventLoop loop; QTimer::singleShot(5000, &loop, SLOT(quit())); - QSslSocketPtr client(new QSslSocket); - socket = client.data(); + QSslSocket client; + socket = &client; socket->setCiphers("AES256-SHA:AES128-SHA"); // upon SSL wrong version error, error will be triggered, not sslErrors @@ -1323,12 +1343,12 @@ void tst_QSslSocket::serverCipherPreferences() connect(socket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(ignoreErrorSlot())); connect(socket, SIGNAL(encrypted()), &loop, SLOT(quit())); - client->connectToHostEncrypted(QHostAddress(QHostAddress::LocalHost).toString(), server.serverPort()); + client.connectToHostEncrypted(QHostAddress(QHostAddress::LocalHost).toString(), server.serverPort()); loop.exec(); - QVERIFY(client->isEncrypted()); - QCOMPARE(client->sessionCipher().name(), QString("AES256-SHA")); + QVERIFY(client.isEncrypted()); + QCOMPARE(client.sessionCipher().name(), QString("AES256-SHA")); } } @@ -1419,21 +1439,21 @@ void tst_QSslSocket::setSocketDescriptor() QEventLoop loop; QTimer::singleShot(5000, &loop, SLOT(quit())); - QSslSocketPtr client(new QSslSocket); - socket = client.data();; + QSslSocket client; + socket = &client; connect(socket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(ignoreErrorSlot())); connect(socket, SIGNAL(encrypted()), &loop, SLOT(quit())); - client->connectToHostEncrypted(QHostAddress(QHostAddress::LocalHost).toString(), server.serverPort()); + client.connectToHostEncrypted(QHostAddress(QHostAddress::LocalHost).toString(), server.serverPort()); loop.exec(); - QCOMPARE(client->state(), QAbstractSocket::ConnectedState); - QVERIFY(client->isEncrypted()); - QVERIFY(!client->peerAddress().isNull()); - QVERIFY(client->peerPort() != 0); - QVERIFY(!client->localAddress().isNull()); - QVERIFY(client->localPort() != 0); + QCOMPARE(client.state(), QAbstractSocket::ConnectedState); + QVERIFY(client.isEncrypted()); + QVERIFY(!client.peerAddress().isNull()); + QVERIFY(client.peerPort() != 0); + QVERIFY(!client.localAddress().isNull()); + QVERIFY(client.localPort() != 0); } void tst_QSslSocket::setSslConfiguration_data() @@ -2846,10 +2866,37 @@ void tst_QSslSocket::qtbug18498_peek2() void tst_QSslSocket::dhServer() { - if (!QSslSocket::supportsSsl()) { - qWarning("SSL not supported, skipping test"); + if (!QSslSocket::supportsSsl()) + QSKIP("No SSL support"); + + QFETCH_GLOBAL(bool, setProxy); + if (setProxy) return; - } + + SslServer server; + server.ciphers = QLatin1String("DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA"); + QVERIFY(server.listen()); + + QEventLoop loop; + QTimer::singleShot(5000, &loop, SLOT(quit())); + + QSslSocket client; + socket = &client; + connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), &loop, SLOT(quit())); + connect(socket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(ignoreErrorSlot())); + connect(socket, SIGNAL(encrypted()), &loop, SLOT(quit())); + + client.connectToHostEncrypted(QHostAddress(QHostAddress::LocalHost).toString(), server.serverPort()); + + loop.exec(); + QCOMPARE(client.state(), QAbstractSocket::ConnectedState); +} + +#ifndef QT_NO_OPENSSL +void tst_QSslSocket::dhServerCustomParamsNull() +{ + if (!QSslSocket::supportsSsl()) + QSKIP("No SSL support"); QFETCH_GLOBAL(bool, setProxy); if (setProxy) @@ -2857,22 +2904,74 @@ void tst_QSslSocket::dhServer() SslServer server; server.ciphers = QLatin1String("DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA"); + + QSslConfiguration cfg = server.config; + cfg.setDiffieHellmanParameters(QSslDiffieHellmanParameters()); + server.config = cfg; + QVERIFY(server.listen()); QEventLoop loop; QTimer::singleShot(5000, &loop, SLOT(quit())); - QSslSocketPtr client(new QSslSocket); - socket = client.data(); + QSslSocket client; + socket = &client; connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), &loop, SLOT(quit())); connect(socket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(ignoreErrorSlot())); connect(socket, SIGNAL(encrypted()), &loop, SLOT(quit())); - client->connectToHostEncrypted(QHostAddress(QHostAddress::LocalHost).toString(), server.serverPort()); + client.connectToHostEncrypted(QHostAddress(QHostAddress::LocalHost).toString(), server.serverPort()); loop.exec(); - QCOMPARE(client->state(), QAbstractSocket::ConnectedState); + + QVERIFY(client.state() != QAbstractSocket::ConnectedState); } +#endif // QT_NO_OPENSSL + +#ifndef QT_NO_OPENSSL +void tst_QSslSocket::dhServerCustomParams() +{ + if (!QSslSocket::supportsSsl()) + QSKIP("No SSL support"); + + QFETCH_GLOBAL(bool, setProxy); + if (setProxy) + return; + + SslServer server; + server.ciphers = QLatin1String("DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA"); + + QSslConfiguration cfg = server.config; + + // Custom 2048-bit DH parameters generated with 'openssl dhparam -outform DER -out out.der -check -2 2048' + const auto dh = QSslDiffieHellmanParameters::fromEncoded(QByteArray::fromBase64(QByteArrayLiteral( + "MIIBCAKCAQEAvVA7b8keTfjFutCtTJmP/pnQfw/prKa+GMed/pBWjrC4N1YwnI8h/A861d9WE/VWY7XMTjvjX3/0" + "aaU8wEe0EXNpFdlTH+ZMQctQTSJOyQH0RCTwJfDGPCPT9L+c9GKwEKWORH38Earip986HJc0w3UbnfIwXUdsWHiXi" + "Z6r3cpyBmTKlsXTFiDVAOUXSiO8d/zOb6zHZbDfyB/VbtZRmnA7TXVn9oMzC0g9+FXHdrV4K+XfdvNZdCegvoAZiy" + "R6ZQgNG9aZ36/AQekhg060hp55f9HDPgXqYeNeXBiferjUtU7S9b3s83XhOJAr01/0Tf5dENwCfg2gK36TM8cC4wI" + "BAg==")), QSsl::Der); + cfg.setDiffieHellmanParameters(dh); + + server.config = cfg; + + QVERIFY(server.listen()); + + QEventLoop loop; + QTimer::singleShot(5000, &loop, SLOT(quit())); + + QSslSocket client; + socket = &client; + connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), &loop, SLOT(quit())); + connect(socket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(ignoreErrorSlot())); + connect(socket, SIGNAL(encrypted()), &loop, SLOT(quit())); + + client.connectToHostEncrypted(QHostAddress(QHostAddress::LocalHost).toString(), server.serverPort()); + + loop.exec(); + + QVERIFY(client.state() == QAbstractSocket::ConnectedState); +} +#endif // QT_NO_OPENSSL void tst_QSslSocket::ecdhServer() { @@ -2892,16 +2991,16 @@ void tst_QSslSocket::ecdhServer() QEventLoop loop; QTimer::singleShot(5000, &loop, SLOT(quit())); - QSslSocketPtr client(new QSslSocket); - socket = client.data(); + QSslSocket client; + socket = &client; connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), &loop, SLOT(quit())); connect(socket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(ignoreErrorSlot())); connect(socket, SIGNAL(encrypted()), &loop, SLOT(quit())); - client->connectToHostEncrypted(QHostAddress(QHostAddress::LocalHost).toString(), server.serverPort()); + client.connectToHostEncrypted(QHostAddress(QHostAddress::LocalHost).toString(), server.serverPort()); loop.exec(); - QCOMPARE(client->state(), QAbstractSocket::ConnectedState); + QCOMPARE(client.state(), QAbstractSocket::ConnectedState); } void tst_QSslSocket::verifyClientCertificate_data() @@ -3003,16 +3102,16 @@ void tst_QSslSocket::verifyClientCertificate() QFETCH(QList<QSslCertificate>, clientCerts); QFETCH(QSslKey, clientKey); - QSslSocketPtr client(new QSslSocket); - client->setLocalCertificateChain(clientCerts); - client->setPrivateKey(clientKey); - socket = client.data(); + QSslSocket client; + client.setLocalCertificateChain(clientCerts); + client.setPrivateKey(clientKey); + socket = &client; connect(socket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(ignoreErrorSlot())); connect(socket, SIGNAL(disconnected()), &loop, SLOT(quit())); connect(socket, SIGNAL(encrypted()), &loop, SLOT(quit())); - client->connectToHostEncrypted(QHostAddress(QHostAddress::LocalHost).toString(), server.serverPort()); + client.connectToHostEncrypted(QHostAddress(QHostAddress::LocalHost).toString(), server.serverPort()); loop.exec(); @@ -3034,8 +3133,8 @@ void tst_QSslSocket::verifyClientCertificate() } // check client socket - QCOMPARE(int(client->state()), int(expectedState)); - QCOMPARE(client->isEncrypted(), works); + QCOMPARE(int(client.state()), int(expectedState)); + QCOMPARE(client.isEncrypted(), works); } void tst_QSslSocket::readBufferMaxSize() @@ -3126,8 +3225,12 @@ class PskProvider : public QObject Q_OBJECT public: + bool m_server; + QByteArray m_identity; + QByteArray m_psk; + explicit PskProvider(QObject *parent = 0) - : QObject(parent) + : QObject(parent), m_server(false) { } @@ -3146,7 +3249,11 @@ public slots: { QVERIFY(authenticator); QCOMPARE(authenticator->identityHint(), PSK_SERVER_IDENTITY_HINT); - QVERIFY(authenticator->maximumIdentityLength() > 0); + if (m_server) + QCOMPARE(authenticator->maximumIdentityLength(), 0); + else + QVERIFY(authenticator->maximumIdentityLength() > 0); + QVERIFY(authenticator->maximumPreSharedKeyLength() > 0); if (!m_identity.isEmpty()) { @@ -3159,12 +3266,61 @@ public slots: QCOMPARE(authenticator->preSharedKey(), m_psk); } } - -private: - QByteArray m_identity; - QByteArray m_psk; }; +class PskServer : public QTcpServer +{ + Q_OBJECT +public: + PskServer() + : socket(0), + config(QSslConfiguration::defaultConfiguration()), + ignoreSslErrors(true), + peerVerifyMode(QSslSocket::AutoVerifyPeer), + protocol(QSsl::TlsV1_0), + m_pskProvider() + { + m_pskProvider.m_server = true; + } + QSslSocket *socket; + QSslConfiguration config; + bool ignoreSslErrors; + QSslSocket::PeerVerifyMode peerVerifyMode; + QSsl::SslProtocol protocol; + QString ciphers; + PskProvider m_pskProvider; + +protected: + void incomingConnection(qintptr socketDescriptor) + { + socket = new QSslSocket(this); + socket->setSslConfiguration(config); + socket->setPeerVerifyMode(peerVerifyMode); + socket->setProtocol(protocol); + if (ignoreSslErrors) + connect(socket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(ignoreErrorSlot())); + + if (!ciphers.isEmpty()) { + socket->setCiphers(ciphers); + } + + QVERIFY(socket->setSocketDescriptor(socketDescriptor, QAbstractSocket::ConnectedState)); + QVERIFY(!socket->peerAddress().isNull()); + QVERIFY(socket->peerPort() != 0); + QVERIFY(!socket->localAddress().isNull()); + QVERIFY(socket->localPort() != 0); + + connect(socket, &QSslSocket::preSharedKeyAuthenticationRequired, &m_pskProvider, &PskProvider::providePsk); + + socket->startServerEncryption(); + } + +protected slots: + void ignoreErrorSlot() + { + socket->ignoreSslErrors(); + } +}; void tst_QSslSocket::simplePskConnect_data() { QTest::addColumn<PskConnectTestType>("pskTestType"); @@ -3188,7 +3344,7 @@ void tst_QSslSocket::simplePskConnect() bool pskCipherFound = false; const QList<QSslCipher> supportedCiphers = QSslSocket::supportedCiphers(); - foreach (const QSslCipher &cipher, supportedCiphers) { + for (const QSslCipher &cipher : supportedCiphers) { if (cipher.name() == PSK_CIPHER_WITHOUT_AUTH) { pskCipherFound = true; break; @@ -3466,6 +3622,129 @@ void tst_QSslSocket::ephemeralServerKey() QCOMPARE(client->sslConfiguration().ephemeralServerKey().isNull(), emptyKey); } +void tst_QSslSocket::allowedProtocolNegotiation() +{ +#if OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(OPENSSL_NO_TLSEXT) + + QFETCH_GLOBAL(bool, setProxy); + if (setProxy) + return; + + const QByteArray expectedNegotiated("cool-protocol"); + QList<QByteArray> serverProtos; + serverProtos << expectedNegotiated << "not-so-cool-protocol"; + QList<QByteArray> clientProtos; + clientProtos << "uber-cool-protocol" << expectedNegotiated << "not-so-cool-protocol"; + + + SslServer server; + server.config.setAllowedNextProtocols(serverProtos); + QVERIFY(server.listen()); + + QSslSocket clientSocket; + auto configuration = clientSocket.sslConfiguration(); + configuration.setAllowedNextProtocols(clientProtos); + clientSocket.setSslConfiguration(configuration); + + clientSocket.connectToHostEncrypted("127.0.0.1", server.serverPort()); + clientSocket.ignoreSslErrors(); + + QEventLoop loop; + QTimer::singleShot(5000, &loop, SLOT(quit())); + connect(&clientSocket, SIGNAL(encrypted()), &loop, SLOT(quit())); + loop.exec(); + + QVERIFY(server.socket->sslConfiguration().nextNegotiatedProtocol() == + clientSocket.sslConfiguration().nextNegotiatedProtocol()); + QVERIFY(server.socket->sslConfiguration().nextNegotiatedProtocol() == expectedNegotiated); + +#endif // OPENSSL_VERSION_NUMBER +} + +void tst_QSslSocket::pskServer() +{ + QFETCH_GLOBAL(bool, setProxy); + if (!QSslSocket::supportsSsl() || setProxy) + return; + + QSslSocket socket; + this->socket = &socket; + + QSignalSpy connectedSpy(&socket, SIGNAL(connected())); + QVERIFY(connectedSpy.isValid()); + + QSignalSpy disconnectedSpy(&socket, SIGNAL(disconnected())); + QVERIFY(disconnectedSpy.isValid()); + + QSignalSpy connectionEncryptedSpy(&socket, SIGNAL(encrypted())); + QVERIFY(connectionEncryptedSpy.isValid()); + + QSignalSpy pskAuthenticationRequiredSpy(&socket, SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*))); + QVERIFY(pskAuthenticationRequiredSpy.isValid()); + + connect(&socket, SIGNAL(connected()), this, SLOT(exitLoop())); + connect(&socket, SIGNAL(disconnected()), this, SLOT(exitLoop())); + connect(&socket, SIGNAL(modeChanged(QSslSocket::SslMode)), this, SLOT(exitLoop())); + connect(&socket, SIGNAL(encrypted()), this, SLOT(exitLoop())); + connect(&socket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(exitLoop())); + connect(&socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(exitLoop())); + connect(&socket, SIGNAL(peerVerifyError(QSslError)), this, SLOT(exitLoop())); + connect(&socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(exitLoop())); + + // force a PSK cipher w/o auth + socket.setCiphers(PSK_CIPHER_WITHOUT_AUTH); + + PskProvider provider; + provider.setIdentity(PSK_CLIENT_IDENTITY); + provider.setPreSharedKey(PSK_CLIENT_PRESHAREDKEY); + connect(&socket, SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)), &provider, SLOT(providePsk(QSslPreSharedKeyAuthenticator*))); + socket.setPeerVerifyMode(QSslSocket::VerifyNone); + + PskServer server; + server.m_pskProvider.setIdentity(provider.m_identity); + server.m_pskProvider.setPreSharedKey(provider.m_psk); + server.config.setPreSharedKeyIdentityHint(PSK_SERVER_IDENTITY_HINT); + QVERIFY(server.listen()); + + // Start connecting + socket.connectToHost(QHostAddress(QHostAddress::LocalHost).toString(), server.serverPort()); + enterLoop(5); + + // Entered connected state + QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); + QCOMPARE(socket.mode(), QSslSocket::UnencryptedMode); + QVERIFY(!socket.isEncrypted()); + QCOMPARE(connectedSpy.count(), 1); + QCOMPARE(disconnectedSpy.count(), 0); + + // Enter encrypted mode + socket.startClientEncryption(); + QCOMPARE(socket.mode(), QSslSocket::SslClientMode); + QVERIFY(!socket.isEncrypted()); + QCOMPARE(connectionEncryptedSpy.count(), 0); + + // Start handshake. + enterLoop(10); + + // We must get the PSK signal in all cases + QCOMPARE(pskAuthenticationRequiredSpy.count(), 1); + + QCOMPARE(connectionEncryptedSpy.count(), 1); + QVERIFY(socket.isEncrypted()); + QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); + + // check writing + socket.write("Hello from Qt TLS/PSK!"); + QVERIFY(socket.waitForBytesWritten()); + + // disconnect + socket.disconnectFromHost(); + enterLoop(10); + + QCOMPARE(socket.state(), QAbstractSocket::UnconnectedState); + QCOMPARE(disconnectedSpy.count(), 1); +} + #endif // QT_NO_OPENSSL #endif // QT_NO_SSL diff --git a/tests/auto/network/ssl/qsslsocket_onDemandCertificates_member/qsslsocket_onDemandCertificates_member.pro b/tests/auto/network/ssl/qsslsocket_onDemandCertificates_member/qsslsocket_onDemandCertificates_member.pro index ae911e43ed..c862b3d3ae 100644 --- a/tests/auto/network/ssl/qsslsocket_onDemandCertificates_member/qsslsocket_onDemandCertificates_member.pro +++ b/tests/auto/network/ssl/qsslsocket_onDemandCertificates_member/qsslsocket_onDemandCertificates_member.pro @@ -2,7 +2,7 @@ CONFIG += testcase testcase.timeout = 300 # this test is slow SOURCES += tst_qsslsocket_onDemandCertificates_member.cpp -win32:!wince: LIBS += -lws2_32 +win32:LIBS += -lws2_32 QT = core core-private network-private testlib TARGET = tst_qsslsocket_onDemandCertificates_member @@ -15,10 +15,6 @@ win32 { } } -wince* { - DEFINES += SRCDIR=\\\"./\\\" -} else { - DEFINES += SRCDIR=\\\"$$PWD/\\\" -} +DEFINES += SRCDIR=\\\"$$PWD/\\\" -requires(contains(QT_CONFIG,private_tests)) +requires(qtConfig(private_tests)) diff --git a/tests/auto/network/ssl/qsslsocket_onDemandCertificates_static/qsslsocket_onDemandCertificates_static.pro b/tests/auto/network/ssl/qsslsocket_onDemandCertificates_static/qsslsocket_onDemandCertificates_static.pro index 25e5a5d5c7..c27a58fcd2 100644 --- a/tests/auto/network/ssl/qsslsocket_onDemandCertificates_static/qsslsocket_onDemandCertificates_static.pro +++ b/tests/auto/network/ssl/qsslsocket_onDemandCertificates_static/qsslsocket_onDemandCertificates_static.pro @@ -1,7 +1,7 @@ CONFIG += testcase SOURCES += tst_qsslsocket_onDemandCertificates_static.cpp -win32:!wince: LIBS += -lws2_32 +win32:LIBS += -lws2_32 QT = core core-private network-private testlib TARGET = tst_qsslsocket_onDemandCertificates_static @@ -14,10 +14,6 @@ win32 { } } -wince* { - DEFINES += SRCDIR=\\\"./\\\" -} else { - DEFINES += SRCDIR=\\\"$$PWD/\\\" -} +DEFINES += SRCDIR=\\\"$$PWD/\\\" -requires(contains(QT_CONFIG,private_tests)) +requires(qtConfig(private_tests)) diff --git a/tests/auto/network/ssl/ssl.pro b/tests/auto/network/ssl/ssl.pro index 25d79ebfe8..175f361071 100644 --- a/tests/auto/network/ssl/ssl.pro +++ b/tests/auto/network/ssl/ssl.pro @@ -1,4 +1,6 @@ TEMPLATE=subdirs +QT_FOR_CONFIG += network + SUBDIRS=\ qsslcertificate \ qsslcipher \ @@ -6,8 +8,8 @@ SUBDIRS=\ qsslerror \ qsslkey \ -contains(QT_CONFIG, ssl) | contains(QT_CONFIG, openssl) | contains(QT_CONFIG, openssl-linked) { - contains(QT_CONFIG, private_tests) { +qtConfig(ssl) { + qtConfig(private_tests) { SUBDIRS += \ qsslsocket \ qsslsocket_onDemandCertificates_member \ @@ -19,8 +21,9 @@ winrt: SUBDIRS -= \ qsslsocket_onDemandCertificates_member \ qsslsocket_onDemandCertificates_static \ -contains(QT_CONFIG, ssl) | contains(QT_CONFIG, openssl) | contains(QT_CONFIG, openssl-linked) { - contains(QT_CONFIG, private_tests) { - SUBDIRS += qasn1element +qtConfig(ssl) { + qtConfig(private_tests) { + SUBDIRS += qasn1element \ + qssldiffiehellmanparameters } } |