diff options
Diffstat (limited to 'tests/auto/qmqttclient/tst_qmqttclient.cpp')
-rw-r--r-- | tests/auto/qmqttclient/tst_qmqttclient.cpp | 406 |
1 files changed, 367 insertions, 39 deletions
diff --git a/tests/auto/qmqttclient/tst_qmqttclient.cpp b/tests/auto/qmqttclient/tst_qmqttclient.cpp index b94bfd4..8aec3d5 100644 --- a/tests/auto/qmqttclient/tst_qmqttclient.cpp +++ b/tests/auto/qmqttclient/tst_qmqttclient.cpp @@ -46,18 +46,34 @@ public: private Q_SLOTS: void initTestCase(); void cleanupTestCase(); + void getSetCheck_data(); void getSetCheck(); void sendReceive_data(); void sendReceive(); + void retainMessage_data(); void retainMessage(); + void willMessage_data(); void willMessage(); void compliantTopic_data(); void compliantTopic(); + void subscribeLongTopic_data(); void subscribeLongTopic(); + void dataIncludingZero_data(); void dataIncludingZero(); + void publishLongTopic_data(); void publishLongTopic(); + void reconnect_QTBUG65726_data(); void reconnect_QTBUG65726(); + void openIODevice_QTBUG66955_data(); void openIODevice_QTBUG66955(); + void staticProperties_QTBUG_67176_data(); + void staticProperties_QTBUG_67176(); + void authentication(); + void messageStatus_data(); + void messageStatus(); + void messageStatusReceive_data(); + void messageStatusReceive(); + void subscriptionIdsOverlap(); private: QProcess m_brokerProcess; QString m_testBroker; @@ -79,9 +95,12 @@ void Tst_QMqttClient::cleanupTestCase() { } +DefaultVersionTestData(Tst_QMqttClient::getSetCheck_data) + void Tst_QMqttClient::getSetCheck() { - QMqttClient client; + QFETCH(QMqttClient::ProtocolVersion, mqttVersion); + VersionClient(mqttVersion, client); QVERIFY(client.clientId().size() > 0); const QString clientId = QLatin1String("testclient123"); @@ -101,15 +120,27 @@ void Tst_QMqttClient::getSetCheck() client.setKeepAlive(10); QCOMPARE(client.keepAlive(), quint16(10)); - QCOMPARE(client.protocolVersion(), QMqttClient::MQTT_3_1_1); - client.setProtocolVersion(QMqttClient::ProtocolVersion(0)); - QCOMPARE(client.protocolVersion(), QMqttClient::MQTT_3_1_1); - client.setProtocolVersion(QMqttClient::ProtocolVersion(5)); - QCOMPARE(client.protocolVersion(), QMqttClient::MQTT_3_1_1); - client.setProtocolVersion(QMqttClient::MQTT_3_1); - QCOMPARE(client.protocolVersion(), QMqttClient::MQTT_3_1); - + // Available protocol versions + QMqttClient client2; + QCOMPARE(client2.protocolVersion(), QMqttClient::MQTT_3_1_1); + client2.setProtocolVersion(QMqttClient::ProtocolVersion(0)); + QCOMPARE(client2.protocolVersion(), QMqttClient::MQTT_3_1_1); + client2.setProtocolVersion(QMqttClient::ProtocolVersion(6)); + QCOMPARE(client2.protocolVersion(), QMqttClient::MQTT_3_1_1); + client2.setProtocolVersion(QMqttClient::MQTT_3_1); + QCOMPARE(client2.protocolVersion(), QMqttClient::MQTT_3_1); + client2.setProtocolVersion(QMqttClient::MQTT_5_0); + QCOMPARE(client2.protocolVersion(), QMqttClient::MQTT_5_0); + +#ifdef QT_BUILD_INTERNAL + if (qEnvironmentVariableIsSet("QT_MQTT_TEST_USERNAME")) + QEXPECT_FAIL("", "Default username has been overwritten.", Continue); +#endif QCOMPARE(client.username(), QString()); +#ifdef QT_BUILD_INTERNAL + if (qEnvironmentVariableIsSet("QT_MQTT_TEST_PASSWORD")) + QEXPECT_FAIL("", "Default username has been overwritten.", Continue); +#endif QCOMPARE(client.password(), QString()); QCOMPARE(client.cleanSession(), true); QCOMPARE(client.willTopic(), QString()); @@ -120,22 +151,29 @@ void Tst_QMqttClient::getSetCheck() void Tst_QMqttClient::sendReceive_data() { + QTest::addColumn<QMqttClient::ProtocolVersion>("mqttVersion"); QTest::addColumn<QByteArray>("data"); - QTest::newRow("empty") << QByteArray(); - QTest::newRow("simple") << QByteArray("This is a test message"); - QByteArray d; - d.fill('A', 500); - QTest::newRow("big") << d; - d.fill('B', (128 * 128 * 128) + 4); - QTest::newRow("huge") << d; + + QList<QMqttClient::ProtocolVersion> versions{QMqttClient::MQTT_3_1_1, QMqttClient::MQTT_5_0}; + + for (int i = 0; i < 2; ++i) { + QTest::newRow(qPrintable(QString::number(versions[i]) + ":empty")) << versions[i] << QByteArray(); + QTest::newRow(qPrintable(QString::number(versions[i]) + ":simple")) << versions[i] << QByteArray("This is a test message"); + QByteArray d; + d.fill('A', 500); + QTest::newRow(qPrintable(QString::number(versions[i]) + ":big")) << versions[i] << d; + d.fill('B', (128 * 128 * 128) + 4); + QTest::newRow(qPrintable(QString::number(versions[i]) + ":huge")) << versions[i] << d; + } } void Tst_QMqttClient::sendReceive() { + QFETCH(QMqttClient::ProtocolVersion, mqttVersion); QFETCH(QByteArray, data); - const QString testTopic = QLatin1String("Topic"); + const QString testTopic = QLatin1String("Qt/QMqttClient/Topic"); - QMqttClient publisher; + VersionClient(mqttVersion, publisher); publisher.setClientId(QLatin1String("publisher")); publisher.setHostname(m_testBroker); publisher.setPort(m_port); @@ -143,7 +181,7 @@ void Tst_QMqttClient::sendReceive() publisher.connectToHost(); QTRY_COMPARE(publisher.state(), QMqttClient::Connected); - QMqttClient subscriber; + VersionClient(mqttVersion, subscriber); subscriber.setClientId(QLatin1String("subscriber")); subscriber.setHostname(m_testBroker); subscriber.setPort(m_port); @@ -162,19 +200,26 @@ void Tst_QMqttClient::sendReceive() QTRY_COMPARE(sub->state(), QMqttSubscription::Subscribed); + if (subscriber.protocolVersion() == QMqttClient::MQTT_5_0 && + (quint32)data.size() > subscriber.serverConnectionProperties().maximumPacketSize()) + QSKIP("The MQTT 5 test broker does not support huge packages.", SkipOnce); publisher.publish(testTopic, data, 1); QTRY_VERIFY2(received, "Subscriber did not receive message"); QVERIFY2(verified, "Subscriber received different message"); } +DefaultVersionTestData(Tst_QMqttClient::retainMessage_data) + void Tst_QMqttClient::retainMessage() { - const QString testTopic = QLatin1String("Topic2"); + QFETCH(QMqttClient::ProtocolVersion, mqttVersion); + + const QString testTopic = QLatin1String("Qt/QMqttClient/Topic2"); const QByteArray testMessage("retainedMessage"); // Publisher - QMqttClient publisher; + VersionClient(mqttVersion, publisher); publisher.setClientId(QLatin1String("publisher")); publisher.setHostname(m_testBroker); publisher.setPort(m_port); @@ -197,7 +242,7 @@ void Tst_QMqttClient::retainMessage() publisher.publish(testTopic, testMessage, 1, i == 1 ? true : false); QTRY_COMPARE(publishSpy.count(), 1); - QMqttClient sub; + VersionClient(mqttVersion, sub); sub.setClientId(QLatin1String("SubA")); sub.setHostname(m_testBroker); sub.setPort(m_port); @@ -213,19 +258,22 @@ void Tst_QMqttClient::retainMessage() auto subscription = sub.subscribe(testTopic); QTRY_COMPARE(subscription->state(), QMqttSubscription::Subscribed); - QTest::qWait(5000); - QVERIFY(msgCount == i); + QTRY_VERIFY(msgCount == i); } publisher.disconnect(); } +DefaultVersionTestData(Tst_QMqttClient::willMessage_data) + void Tst_QMqttClient::willMessage() { - const QString willTopic = QLatin1String("will/topic"); + QFETCH(QMqttClient::ProtocolVersion, mqttVersion); + + const QString willTopic = QLatin1String("Qt/QMqttClient/will/topic"); const QByteArray willMessage("The client died...."); // Client A connects - QMqttClient client1; + VersionClient(mqttVersion, client1); client1.setHostname(m_testBroker); client1.setPort(m_port); client1.connectToHost(); @@ -247,7 +295,7 @@ void Tst_QMqttClient::willMessage() QVERIFY(sock.waitForConnected()); for (int i = 1; i > 0; --i) { - QMqttClient willClient; + VersionClient(mqttVersion, willClient); if (i == 1) willClient.setTransport(&sock, QMqttClient::AbstractSocket); else { @@ -277,13 +325,21 @@ void Tst_QMqttClient::willMessage() void Tst_QMqttClient::compliantTopic_data() { + QTest::addColumn<QMqttClient::ProtocolVersion>("mqttVersion"); QTest::addColumn<QString>("topic"); - QTest::newRow("simple") << QString::fromLatin1("topic"); - QTest::newRow("subPath") << QString::fromLatin1("topic/subtopic"); - QString l; - l.fill(QLatin1Char('T'), std::numeric_limits<std::uint16_t>::max()); - QTest::newRow("maxSize") << l; + QList<QMqttClient::ProtocolVersion> versions{QMqttClient::MQTT_3_1_1, QMqttClient::MQTT_5_0}; + + for (int i = 0; i < 2; ++i) { + QTest::newRow(qPrintable(QString::number(versions[i]) + ":simple")) << versions[i] << QString::fromLatin1("Qt/QMqttClient/topic"); + QTest::newRow(qPrintable(QString::number(versions[i]) + ":subPath")) << versions[i] << QString::fromLatin1("Qt/QMqttClient/topic/subtopic"); + + if (versions[i] != QMqttClient::MQTT_5_0) { + QString l; + l.fill(QLatin1Char('T'), std::numeric_limits<std::uint16_t>::max()); + QTest::newRow(qPrintable(QString::number(versions[i]) + ":maxSize")) << versions[i] << l; + } + } } void Tst_QMqttClient::compliantTopic() @@ -324,9 +380,13 @@ void Tst_QMqttClient::compliantTopic() QVERIFY2(verified, "Subscriber received different message"); } +DefaultVersionTestData(Tst_QMqttClient::subscribeLongTopic_data) + void Tst_QMqttClient::subscribeLongTopic() { - QMqttClient subscriber; + QFETCH(QMqttClient::ProtocolVersion, mqttVersion); + + VersionClient(mqttVersion, subscriber); subscriber.setClientId(QLatin1String("subscriber")); subscriber.setHostname(m_testBroker); subscriber.setPort(m_port); @@ -340,14 +400,18 @@ void Tst_QMqttClient::subscribeLongTopic() QCOMPARE(sub, nullptr); } +DefaultVersionTestData(Tst_QMqttClient::dataIncludingZero_data) + void Tst_QMqttClient::dataIncludingZero() { + QFETCH(QMqttClient::ProtocolVersion, mqttVersion); + QByteArray data; const int dataSize = 200; data.fill('A', dataSize); data[100] = '\0'; - QMqttClient client; + VersionClient(mqttVersion, client); client.setHostname(m_testBroker); client.setPort(m_port); @@ -357,7 +421,7 @@ void Tst_QMqttClient::dataIncludingZero() bool received = false; bool verified = false; bool correctSize = false; - const QString testTopic(QLatin1String("some/topic")); + const QString testTopic(QLatin1String("Qt/QMqttClient/some/topic")); auto sub = client.subscribe(testTopic, 1); QVERIFY(sub); connect(sub, &QMqttSubscription::messageReceived, [&](QMqttMessage msg) { @@ -375,9 +439,13 @@ void Tst_QMqttClient::dataIncludingZero() QVERIFY2(correctSize, "Subscriber received message of different size"); } +DefaultVersionTestData(Tst_QMqttClient::publishLongTopic_data) + void Tst_QMqttClient::publishLongTopic() { - QMqttClient publisher; + QFETCH(QMqttClient::ProtocolVersion, mqttVersion); + + VersionClient(mqttVersion, publisher); publisher.setClientId(QLatin1String("publisher")); publisher.setHostname(m_testBroker); publisher.setPort(m_port); @@ -428,11 +496,15 @@ public: bool connectionSuccess{false}; }; +DefaultVersionTestData(Tst_QMqttClient::reconnect_QTBUG65726_data) + void Tst_QMqttClient::reconnect_QTBUG65726() { + QFETCH(QMqttClient::ProtocolVersion, mqttVersion); + FakeServer server; - QMqttClient client; + VersionClient(mqttVersion, client); client.setClientId(QLatin1String("bugclient")); client.setHostname(QLatin1String("localhost")); client.setPort(5726); @@ -465,24 +537,280 @@ public: if (data[0] == 0x10) written = 1; else - qWarning() << "Received unknown/invalid data"; + qDebug() << "Received unknown/invalid data"; return len; } QAtomicInt written{0}; }; +DefaultVersionTestData(Tst_QMqttClient::openIODevice_QTBUG66955_data) + void Tst_QMqttClient::openIODevice_QTBUG66955() { + QFETCH(QMqttClient::ProtocolVersion, mqttVersion); + IOTransport trans; trans.open(QIODevice::ReadWrite); - QMqttClient client; + VersionClient(mqttVersion, client); client.setTransport(&trans, QMqttClient::IODevice); client.connectToHost(); QTRY_COMPARE(trans.written, 1); } +DefaultVersionTestData(Tst_QMqttClient::staticProperties_QTBUG_67176_data) + +void Tst_QMqttClient::staticProperties_QTBUG_67176() +{ + QFETCH(QMqttClient::ProtocolVersion, mqttVersion); + + VersionClient(mqttVersion, client); + client.setHostname(m_testBroker); + client.setPort(m_port); + + const QString clientId = client.clientId(); + const quint16 keepAlive = client.keepAlive(); + const bool clean = client.cleanSession(); + + client.connectToHost(); + QTRY_VERIFY2(client.state() == QMqttClient::Connected, "Could not connect to broker."); + + client.setClientId(QLatin1String("someclient")); + QCOMPARE(client.clientId(), clientId); + + client.setHostname(QLatin1String("some.domain.foo")); + QCOMPARE(client.hostname(), m_testBroker); + + client.setPort(1234); + QCOMPARE(client.port(), m_port); + + client.setKeepAlive(keepAlive + 10); + QCOMPARE(client.keepAlive(), keepAlive); + + client.setProtocolVersion(QMqttClient::MQTT_3_1); + QCOMPARE(client.protocolVersion(), mqttVersion); + + client.setUsername(QLatin1String("someUser")); + QCOMPARE(client.username(), QLatin1String()); + + client.setPassword(QLatin1String("somePassword")); + QCOMPARE(client.password(), QLatin1String()); + + client.setCleanSession(!clean); + QCOMPARE(client.cleanSession(), clean); +} + +void Tst_QMqttClient::authentication() +{ + QMqttClient client; + client.setProtocolVersion(QMqttClient::MQTT_5_0); + client.setHostname(m_testBroker); + client.setPort(m_port); + + QMqttConnectionProperties connectionProperties; + connectionProperties.setAuthenticationMethod(QLatin1String("SCRAM-SHA-1")); + client.setConnectionProperties(connectionProperties); + + connect(&client, &QMqttClient::authenticationRequested, [](const QMqttAuthenticationProperties &prop) + { + qDebug() << "Authentication requested:" << prop.authenticationMethod(); + }); + + connect(&client, &QMqttClient::authenticationFinished, [](const QMqttAuthenticationProperties &prop) + { + qDebug() << "Authentication finished:" << prop.authenticationMethod(); + }); + + // ### FIXME : There is no public test broker yet able to handle authentication methods + // Theoretically the broker should send an AUTH request, followed by AUTH call including + // authentication data. See 4.12 of MQTT v5 specs. + QSKIP("No broker available with enhanced authentication."); + client.connectToHost(); + QTRY_COMPARE(client.state(), QMqttClient::Connected); +} + +Q_DECLARE_METATYPE(QMqtt::MessageStatus) + +void Tst_QMqttClient::messageStatus_data() +{ + QTest::addColumn<int>("qos"); + QTest::addColumn<QList<QMqtt::MessageStatus>>("expectedStatus"); + + QTest::newRow("QoS1") << 1 << (QList<QMqtt::MessageStatus>() << QMqtt::MessageStatus::Acknowledged); + QTest::newRow("QoS2") << 2 << (QList<QMqtt::MessageStatus>() << QMqtt::MessageStatus::Received + << QMqtt::MessageStatus::Completed); +} + +void Tst_QMqttClient::messageStatus() +{ + QFETCH(int, qos); + QFETCH(QList<QMqtt::MessageStatus>, expectedStatus); + + QMqttClient client; + client.setProtocolVersion(QMqttClient::MQTT_5_0); + client.setHostname(m_testBroker); + client.setPort(m_port); + + client.connectToHost(); + QTRY_VERIFY2(client.state() == QMqttClient::Connected, "Could not connect to broker."); + + const QString topic = QLatin1String("Qt/client/statusCheck"); + + connect(&client, &QMqttClient::messageStatusChanged, [&expectedStatus](qint32, + QMqtt::MessageStatus s, + const QMqttMessageStatusProperties &) + { + QCOMPARE(s, expectedStatus.first()); + expectedStatus.takeFirst(); + }); + + QSignalSpy publishSpy(&client, &QMqttClient::messageSent); + client.publish(topic, QByteArray("someContent"), quint8(qos)); + QTRY_VERIFY2(publishSpy.count() == 1, "Could not publish message"); + QTRY_VERIFY2(expectedStatus.isEmpty(), "Did not receive all status updates."); +} + +void Tst_QMqttClient::messageStatusReceive_data() +{ + QTest::addColumn<int>("qos"); + QTest::addColumn<QList<QMqtt::MessageStatus>>("expectedStatus"); + + QTest::newRow("QoS1") << 1 << (QList<QMqtt::MessageStatus>() << QMqtt::MessageStatus::Published); + QTest::newRow("QoS2") << 2 << (QList<QMqtt::MessageStatus>() << QMqtt::MessageStatus::Published + << QMqtt::MessageStatus::Released); +} + +void Tst_QMqttClient::messageStatusReceive() +{ + QFETCH(int, qos); + QFETCH(QList<QMqtt::MessageStatus>, expectedStatus); + + QMqttClient publisher; + publisher.setProtocolVersion(QMqttClient::MQTT_5_0); + publisher.setHostname(m_testBroker); + publisher.setPort(m_port); + + publisher.connectToHost(); + QTRY_VERIFY2(publisher.state() == QMqttClient::Connected, "Could not connect to broker."); + + QMqttClient subscriber; + subscriber.setProtocolVersion(QMqttClient::MQTT_5_0); + subscriber.setHostname(m_testBroker); + subscriber.setPort(m_port); + + subscriber.connectToHost(); + QTRY_VERIFY2(subscriber.state() == QMqttClient::Connected, "Could not connect to broker."); + + const QString topic = QLatin1String("Qt/client/statusCheckReceive"); + + auto subscription = subscriber.subscribe(topic, quint8(qos)); + QTRY_VERIFY2(subscription->state() == QMqttSubscription::Subscribed, "Could not subscribe to topic"); + QVERIFY(subscription->qos() >= qos); + + connect(&subscriber, &QMqttClient::messageStatusChanged, [&expectedStatus](qint32, + QMqtt::MessageStatus s, + const QMqttMessageStatusProperties &) + { + QCOMPARE(s, expectedStatus.first()); + expectedStatus.takeFirst(); + }); + + QSignalSpy publishSpy(&publisher, &QMqttClient::messageSent); + QSignalSpy receiveSpy(&subscriber, &QMqttClient::messageReceived); + + publisher.publish(topic, QByteArray("someContent"), quint8(qos)); + QTRY_VERIFY2(publishSpy.count() == 1, "Could not publish message"); + QTRY_VERIFY2(receiveSpy.count() == 1, "Did not receive message"); + + QTRY_VERIFY2(expectedStatus.isEmpty(), "Did not receive all status updates."); +} + +void Tst_QMqttClient::subscriptionIdsOverlap() +{ + + // If the Server sends a single copy of the message it MUST include in the + // PUBLISH packet the Subscription Identifiers for all matching + // subscriptions which have a Subscription Identifiers, their order is not + // significant [MQTT-3.3.4-4]. + // If the Server sends multiple PUBLISH packets it MUST send, in each of + // them, the Subscription Identifier of the matching subscription if it has + // a Subscription Identifier [MQTT-3.3.4-5]. + + const QString topic = QLatin1String("Qt/client/idcheck"); + // Connect publisher + QMqttClient pub; + pub.setProtocolVersion(QMqttClient::MQTT_5_0); + pub.setHostname(m_testBroker); + pub.setPort(m_port); + + pub.connectToHost(); + QTRY_VERIFY2(pub.state() == QMqttClient::Connected, "Could not connect publisher."); + + // Connect subA + QMqttClient subClientA; + subClientA.setProtocolVersion(QMqttClient::MQTT_5_0); + subClientA.setHostname(m_testBroker); + subClientA.setPort(m_port); + + subClientA.connectToHost(); + QTRY_VERIFY2(subClientA.state() == QMqttClient::Connected, "Could not connect subscriber A."); + + QMqttSubscriptionProperties subAProp; + subAProp.setSubscriptionIdentifier(8); + auto subA = subClientA.subscribe(topic, subAProp, 1); + QTRY_VERIFY2(subA->state() == QMqttSubscription::Subscribed, "Could not subscibe A."); + + int receiveACounter = 0; + connect(subA, &QMqttSubscription::messageReceived, [&receiveACounter](QMqttMessage msg) { + qDebug() << "Sub A received:" << msg.publishProperties().subscriptionIdentifiers(); + // ### TODO: Wait for fix at https://github.com/eclipse/paho.mqtt.testing/issues/56 + //QVERIFY(msg.publishProperties().subscriptionIdentifiers().size() == 1); + //QVERIFY(msg.publishProperties().subscriptionIdentifiers().at(0) == 8); // Use sub->id(); + receiveACounter++; + }); + + // Connect subB + QMqttClient subClientB; + subClientB.setProtocolVersion(QMqttClient::MQTT_5_0); + subClientB.setHostname(m_testBroker); + subClientB.setPort(m_port); + + subClientB.connectToHost(); + QTRY_VERIFY2(subClientB.state() == QMqttClient::Connected, "Could not connect subscriber A."); + + QMqttSubscriptionProperties subBProp; + subBProp.setSubscriptionIdentifier(9); + auto subB = subClientB.subscribe(topic, subBProp, 1); + QTRY_VERIFY2(subB->state() == QMqttSubscription::Subscribed, "Could not subscibe A."); + + int receiveBCounter = 2; + connect(subB, &QMqttSubscription::messageReceived, [&receiveBCounter](QMqttMessage msg) { + qDebug() << "Sub B received:" << msg.publishProperties().subscriptionIdentifiers(); + QVERIFY(msg.publishProperties().subscriptionIdentifiers().size() > 0); + receiveBCounter -= msg.publishProperties().subscriptionIdentifiers().size(); + }); + + QMqttSubscriptionProperties subB2Prop; + subB2Prop.setSubscriptionIdentifier(14); + auto subB2 = subClientB.subscribe(topic + "/#", subB2Prop, 1); + QTRY_VERIFY2(subB2->state() == QMqttSubscription::Subscribed, "Could not subscibe A."); + + int receiveB2Counter = 2; + connect(subB2, &QMqttSubscription::messageReceived, [&receiveB2Counter](QMqttMessage msg) { + qDebug() << "Sub B2 received:" << msg.publishProperties().subscriptionIdentifiers(); + QVERIFY(msg.publishProperties().subscriptionIdentifiers().size() > 0); + receiveB2Counter -= msg.publishProperties().subscriptionIdentifiers().size(); + }); + + QSignalSpy publishSpy(&pub, &QMqttClient::messageSent); + pub.publish(topic, "SomeData", 1); + QTRY_VERIFY2(publishSpy.count() == 1, "Could not finalize publication."); + QTRY_VERIFY2(receiveBCounter == 0, "Did not receive both messages."); + QTRY_VERIFY2(receiveB2Counter == 0, "Did not receive both messages."); + QTRY_VERIFY2(receiveACounter == 1, "Did not receive non-overlapping message."); +} + QTEST_MAIN(Tst_QMqttClient) #include "tst_qmqttclient.moc" |