diff --git a/core/backends/devicelink.h b/core/backends/devicelink.h index 945a7933..35922f3f 100644 --- a/core/backends/devicelink.h +++ b/core/backends/devicelink.h @@ -1,77 +1,75 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef DEVICELINK_H #define DEVICELINK_H #include -#include //Fix build on older QCA -#include #include "core/networkpacket.h" class PairingHandler; class NetworkPacket; class LinkProvider; class Device; class DeviceLink : public QObject { Q_OBJECT public: enum PairStatus { NotPaired, Paired }; DeviceLink(const QString& deviceId, LinkProvider* parent); virtual ~DeviceLink() = default; virtual QString name() = 0; const QString& deviceId() const { return m_deviceId; } LinkProvider* provider() { return m_linkProvider; } virtual bool sendPacket(NetworkPacket& np) = 0; //user actions virtual void userRequestsPair() = 0; virtual void userRequestsUnpair() = 0; PairStatus pairStatus() const { return m_pairStatus; } virtual void setPairStatus(PairStatus status); //The daemon will periodically destroy unpaired links if this returns false virtual bool linkShouldBeKeptAlive() { return false; } Q_SIGNALS: void pairingRequest(PairingHandler* handler); void pairingRequestExpired(PairingHandler* handler); void pairStatusChanged(DeviceLink::PairStatus status); void pairingError(const QString& error); void receivedPacket(const NetworkPacket& np); private: const QString m_deviceId; LinkProvider* m_linkProvider; PairStatus m_pairStatus; }; #endif diff --git a/core/networkpacket.h b/core/networkpacket.h index 1391ae1e..c19d3668 100644 --- a/core/networkpacket.h +++ b/core/networkpacket.h @@ -1,102 +1,101 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef NETWORKPACKET_H #define NETWORKPACKET_H #include "networkpackettypes.h" #include #include #include #include -//#include #include #include #include "kdeconnectcore_export.h" class FileTransferJob; class KDECONNECTCORE_EXPORT NetworkPacket { Q_GADGET Q_PROPERTY( QString id READ id WRITE setId ) Q_PROPERTY( QString type READ type WRITE setType ) Q_PROPERTY( QVariantMap body READ body WRITE setBody ) Q_PROPERTY( QVariantMap payloadTransferInfo READ payloadTransferInfo WRITE setPayloadTransferInfo ) Q_PROPERTY( qint64 payloadSize READ payloadSize WRITE setPayloadSize ) public: const static int s_protocolVersion; explicit NetworkPacket(const QString& type = QStringLiteral("empty"), const QVariantMap& body = {}); NetworkPacket(const NetworkPacket& other); // Copy constructor, required for QMetaType and queued signals static void createIdentityPacket(NetworkPacket*); QByteArray serialize() const; static bool unserialize(const QByteArray& json, NetworkPacket* out); const QString& id() const { return m_id; } const QString& type() const { return m_type; } QVariantMap& body() { return m_body; } const QVariantMap& body() const { return m_body; } //Get and set info from body. Note that id and type can not be accessed through these. template T get(const QString& key, const T& defaultValue = {}) const { return m_body.value(key,defaultValue).template value(); //Important note: Awesome template syntax is awesome } template void set(const QString& key, const T& value) { m_body[key] = QVariant(value); } bool has(const QString& key) const { return m_body.contains(key); } QSharedPointer payload() const { return m_payload; } void setPayload(const QSharedPointer& device, qint64 payloadSize) { m_payload = device; m_payloadSize = payloadSize; Q_ASSERT(m_payloadSize >= -1); } bool hasPayload() const { return (m_payloadSize != 0); } qint64 payloadSize() const { return m_payloadSize; } //-1 means it is an endless stream FileTransferJob* createPayloadTransferJob(const QUrl& destination) const; //To be called by a particular DeviceLink QVariantMap payloadTransferInfo() const { return m_payloadTransferInfo; } void setPayloadTransferInfo(const QVariantMap& map) { m_payloadTransferInfo = map; } bool hasPayloadTransferInfo() const { return !m_payloadTransferInfo.isEmpty(); } private: void setId(const QString& id) { m_id = id; } void setType(const QString& t) { m_type = t; } void setBody(const QVariantMap& b) { m_body = b; } void setPayloadSize(qint64 s) { m_payloadSize = s; } QString m_id; QString m_type; QVariantMap m_body; QSharedPointer m_payload; qint64 m_payloadSize; QVariantMap m_payloadTransferInfo; }; KDECONNECTCORE_EXPORT QDebug operator<<(QDebug s, const NetworkPacket& pkg); Q_DECLARE_METATYPE(NetworkPacket) #endif // NETWORKPACKET_H diff --git a/tests/lanlinkprovidertest.cpp b/tests/lanlinkprovidertest.cpp index 89ae08dd..446469b2 100644 --- a/tests/lanlinkprovidertest.cpp +++ b/tests/lanlinkprovidertest.cpp @@ -1,356 +1,357 @@ /** * Copyright 2015 Vineet Garg * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // This class tests the behaviour of the class LanLinkProvider, be sure to kill process kdeconnectd to avoid any port binding issues #include "../core/backends/lan/lanlinkprovider.h" #include "../core/backends/lan/server.h" #include "../core/backends/lan/socketlinereader.h" #include "../core/kdeconnectconfig.h" #include #include #include #include #include +#include /* * This class tests the working of LanLinkProvider under different conditions that when identity packet is received over TCP, over UDP and same when the device is paired. * It depends on KdeConnectConfig since LanLinkProvider internally uses it. */ class LanLinkProviderTest : public QObject { Q_OBJECT public: explicit LanLinkProviderTest() : m_lanLinkProvider(true) { QStandardPaths::setTestModeEnabled(true); } public Q_SLOTS: void initTestCase(); private Q_SLOTS: void pairedDeviceTcpPacketReceived(); void pairedDeviceUdpPacketReceived(); void unpairedDeviceTcpPacketReceived(); void unpairedDeviceUdpPacketReceived(); private: const int TEST_PORT = 8520; // Add some private fields here LanLinkProvider m_lanLinkProvider; Server* m_server; SocketLineReader* m_reader; QUdpSocket* m_udpSocket; QString m_identityPacket; // Attributes for test device QString m_deviceId; QString m_name; QCA::PrivateKey m_privateKey; QSslCertificate m_certificate; QSslCertificate generateCertificate(QString&, QCA::PrivateKey&); void addTrustedDevice(); void removeTrustedDevice(); void setSocketAttributes(QSslSocket* socket); void testIdentityPacket(QByteArray& identityPacket); }; void LanLinkProviderTest::initTestCase() { removeTrustedDevice(); // Remove trusted device if left by chance by any test m_deviceId = QStringLiteral("testdevice"); m_name = QStringLiteral("Test Device"); m_privateKey = QCA::KeyGenerator().createRSA(2048); m_certificate = generateCertificate(m_deviceId, m_privateKey); m_lanLinkProvider.onStart(); m_identityPacket = QStringLiteral("{\"id\":1439365924847,\"type\":\"kdeconnect.identity\",\"body\":{\"deviceId\":\"testdevice\",\"deviceName\":\"Test Device\",\"protocolVersion\":6,\"deviceType\":\"phone\",\"tcpPort\":") + QString::number(TEST_PORT) + QStringLiteral("}}"); } void LanLinkProviderTest::pairedDeviceTcpPacketReceived() { KdeConnectConfig* kcc = KdeConnectConfig::instance(); addTrustedDevice(); QUdpSocket* mUdpServer = new QUdpSocket; bool b = mUdpServer->bind(QHostAddress::LocalHost, LanLinkProvider::UDP_PORT, QUdpSocket::ShareAddress); QVERIFY(b); QSignalSpy spy(mUdpServer, SIGNAL(readyRead())); m_lanLinkProvider.onNetworkChange(); QVERIFY(!spy.isEmpty() || spy.wait()); QByteArray datagram; datagram.resize(mUdpServer->pendingDatagramSize()); QHostAddress sender; mUdpServer->readDatagram(datagram.data(), datagram.size(), &sender); testIdentityPacket(datagram); QJsonDocument jsonDocument = QJsonDocument::fromJson(datagram); QJsonObject body = jsonDocument.object().value(QStringLiteral("body")).toObject(); int tcpPort = body.value(QStringLiteral("tcpPort")).toInt(); QSslSocket socket; QSignalSpy spy2(&socket, SIGNAL(connected())); socket.connectToHost(sender, tcpPort); QVERIFY(spy2.wait()); QVERIFY2(socket.isOpen(), "Socket disconnected immediately"); socket.write(m_identityPacket.toLatin1()); socket.waitForBytesWritten(2000); QSignalSpy spy3(&socket, SIGNAL(encrypted())); setSocketAttributes(&socket); socket.addCaCertificate(kcc->certificate()); socket.setPeerVerifyMode(QSslSocket::VerifyPeer); socket.setPeerVerifyName(kcc->name()); socket.startServerEncryption(); QVERIFY(spy3.wait()); QCOMPARE(socket.sslErrors().size(), 0); QVERIFY2(socket.isValid(), "Server socket disconnected"); QVERIFY2(socket.isEncrypted(), "Server socket not yet encrypted"); QVERIFY2(!socket.peerCertificate().isNull(), "Peer certificate is null"); removeTrustedDevice(); delete mUdpServer; } void LanLinkProviderTest::pairedDeviceUdpPacketReceived() { KdeConnectConfig* kcc = KdeConnectConfig::instance(); addTrustedDevice(); m_server = new Server(this); m_udpSocket = new QUdpSocket(this); m_server->listen(QHostAddress::LocalHost, TEST_PORT); QSignalSpy spy(m_server, SIGNAL(newConnection())); qint64 bytesWritten = m_udpSocket->writeDatagram(m_identityPacket.toLatin1(), QHostAddress::LocalHost, LanLinkProvider::UDP_PORT); // write an identity packet to udp socket here, we do not broadcast it here QCOMPARE(bytesWritten, m_identityPacket.size()); // We should have an incoming connection now, wait for incoming connection QVERIFY(!spy.isEmpty() || spy.wait()); QSslSocket* serverSocket = m_server->nextPendingConnection(); QVERIFY2(serverSocket != 0, "Server socket is null"); QVERIFY2(serverSocket->isOpen(), "Server socket already closed"); m_reader = new SocketLineReader(serverSocket, this); QSignalSpy spy2(m_reader, SIGNAL(readyRead())); QVERIFY(spy2.wait()); QByteArray receivedPacket = m_reader->readLine(); testIdentityPacket(receivedPacket); // Received identity packet from LanLinkProvider now start ssl QSignalSpy spy3(serverSocket, SIGNAL(encrypted())); QVERIFY(connect(serverSocket, static_cast(&QSslSocket::error), this, [](QAbstractSocket::SocketError error){ qDebug() << "error:" << error; })); setSocketAttributes(serverSocket); serverSocket->addCaCertificate(kcc->certificate()); serverSocket->setPeerVerifyMode(QSslSocket::VerifyPeer); serverSocket->setPeerVerifyName(kcc->deviceId()); serverSocket->startClientEncryption(); // Its TCP server. but SSL client QVERIFY(!serverSocket->isEncrypted()); spy3.wait(2000); qDebug() << "xxxxxxxxx" << serverSocket->sslErrors(); QCOMPARE(serverSocket->sslErrors().size(), 0); QVERIFY2(serverSocket->isValid(), "Server socket disconnected"); QVERIFY2(serverSocket->isEncrypted(), "Server socket not yet encrypted"); QVERIFY2(!serverSocket->peerCertificate().isNull(), "Peer certificate is null"); removeTrustedDevice(); delete m_server; delete m_udpSocket; } void LanLinkProviderTest::unpairedDeviceTcpPacketReceived() { QUdpSocket* mUdpServer = new QUdpSocket; bool b = mUdpServer->bind(QHostAddress::LocalHost, LanLinkProvider::UDP_PORT, QUdpSocket::ShareAddress); QVERIFY(b); QSignalSpy spy(mUdpServer, SIGNAL(readyRead())); m_lanLinkProvider.onNetworkChange(); QVERIFY(!spy.isEmpty() || spy.wait()); QByteArray datagram; datagram.resize(mUdpServer->pendingDatagramSize()); QHostAddress sender; mUdpServer->readDatagram(datagram.data(), datagram.size(), &sender); testIdentityPacket(datagram); QJsonDocument jsonDocument = QJsonDocument::fromJson(datagram); QJsonObject body = jsonDocument.object().value(QStringLiteral("body")).toObject(); int tcpPort = body.value(QStringLiteral("tcpPort")).toInt(); QSslSocket socket; QSignalSpy spy2(&socket, SIGNAL(connected())); socket.connectToHost(sender, tcpPort); QVERIFY(spy2.wait()); QVERIFY2(socket.isOpen(), "Socket disconnected immediately"); socket.write(m_identityPacket.toLatin1()); socket.waitForBytesWritten(2000); QSignalSpy spy3(&socket, SIGNAL(encrypted())); // We don't take care for sslErrors signal here, but signal will emit still we will get successful connection setSocketAttributes(&socket); socket.setPeerVerifyMode(QSslSocket::QueryPeer); socket.startServerEncryption(); QVERIFY(spy3.wait()); QVERIFY2(socket.isValid(), "Server socket disconnected"); QVERIFY2(socket.isEncrypted(), "Server socket not yet encrypted"); QVERIFY2(!socket.peerCertificate().isNull(), "Peer certificate is null"); delete mUdpServer; } void LanLinkProviderTest::unpairedDeviceUdpPacketReceived() { m_server = new Server(this); m_udpSocket = new QUdpSocket(this); m_server->listen(QHostAddress::LocalHost, TEST_PORT); QSignalSpy spy(m_server, &Server::newConnection); qint64 bytesWritten = m_udpSocket->writeDatagram(m_identityPacket.toLatin1(), QHostAddress::LocalHost, LanLinkProvider::UDP_PORT); // write an identity packet to udp socket here, we do not broadcast it here QCOMPARE(bytesWritten, m_identityPacket.size()); QVERIFY(!spy.isEmpty() || spy.wait()); QSslSocket* serverSocket = m_server->nextPendingConnection(); QVERIFY2(serverSocket != 0, "Server socket is null"); QVERIFY2(serverSocket->isOpen(), "Server socket already closed"); m_reader = new SocketLineReader(serverSocket, this); QSignalSpy spy2(m_reader, &SocketLineReader::readyRead); QVERIFY(spy2.wait()); QByteArray receivedPacket = m_reader->readLine(); QVERIFY2(!receivedPacket.isEmpty(), "Empty packet received"); testIdentityPacket(receivedPacket); // Received identity packet from LanLinkProvider now start ssl QSignalSpy spy3(serverSocket, SIGNAL(encrypted())); setSocketAttributes(serverSocket); serverSocket->setPeerVerifyMode(QSslSocket::QueryPeer); serverSocket->startClientEncryption(); // Its TCP server. but SSL client QVERIFY(spy3.wait()); QVERIFY2(serverSocket->isValid(), "Server socket disconnected"); QVERIFY2(serverSocket->isEncrypted(), "Server socket not yet encrypted"); QVERIFY2(!serverSocket->peerCertificate().isNull(), "Peer certificate is null"); delete m_server; delete m_udpSocket; } void LanLinkProviderTest::testIdentityPacket(QByteArray& identityPacket) { QJsonDocument jsonDocument = QJsonDocument::fromJson(identityPacket); QJsonObject jsonObject = jsonDocument.object(); QJsonObject body = jsonObject.value(QStringLiteral("body")).toObject(); QCOMPARE(jsonObject.value("type").toString(), QString("kdeconnect.identity")); QVERIFY2(body.contains("deviceName"), "Device name not found in identity packet"); QVERIFY2(body.contains("deviceId"), "Device id not found in identity packet"); QVERIFY2(body.contains("protocolVersion"), "Protocol version not found in identity packet"); QVERIFY2(body.contains("deviceType"), "Device type not found in identity packet"); } QSslCertificate LanLinkProviderTest::generateCertificate(QString& commonName, QCA::PrivateKey& privateKey) { QDateTime startTime = QDateTime::currentDateTime(); QDateTime endTime = startTime.addYears(10); QCA::CertificateInfo certificateInfo; certificateInfo.insert(QCA::CommonName,commonName); certificateInfo.insert(QCA::Organization,QStringLiteral("KDE")); certificateInfo.insert(QCA::OrganizationalUnit,QStringLiteral("Kde connect")); QCA::CertificateOptions certificateOptions(QCA::PKCS10); certificateOptions.setSerialNumber(10); certificateOptions.setInfo(certificateInfo); certificateOptions.setValidityPeriod(startTime, endTime); certificateOptions.setFormat(QCA::PKCS10); QSslCertificate certificate = QSslCertificate(QCA::Certificate(certificateOptions, privateKey).toPEM().toLatin1()); return certificate; } void LanLinkProviderTest::setSocketAttributes(QSslSocket* socket) { socket->setPrivateKey(QSslKey(m_privateKey.toPEM().toLatin1(), QSsl::Rsa)); socket->setLocalCertificate(m_certificate); } void LanLinkProviderTest::addTrustedDevice() { KdeConnectConfig* kcc = KdeConnectConfig::instance(); kcc->addTrustedDevice(m_deviceId, m_name, QStringLiteral("phone")); kcc->setDeviceProperty(m_deviceId, QStringLiteral("certificate"), QString::fromLatin1(m_certificate.toPem())); } void LanLinkProviderTest::removeTrustedDevice() { KdeConnectConfig* kcc = KdeConnectConfig::instance(); kcc->removeTrustedDevice(m_deviceId); } QTEST_GUILESS_MAIN(LanLinkProviderTest) #include "lanlinkprovidertest.moc" diff --git a/tests/networkpackettests.cpp b/tests/networkpackettests.cpp index 18f91856..09f480f0 100644 --- a/tests/networkpackettests.cpp +++ b/tests/networkpackettests.cpp @@ -1,96 +1,95 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "networkpackettests.h" #include "core/networkpacket.h" #include -#include QTEST_GUILESS_MAIN(NetworkPacketTests); void NetworkPacketTests::initTestCase() { // Called before the first testfunction is executed } void NetworkPacketTests::networkPacketTest() { NetworkPacket np(QStringLiteral("com.test")); np.set(QStringLiteral("hello"),"hola"); QCOMPARE( (np.get("hello","bye")) , QString("hola") ); np.set(QStringLiteral("hello"),""); QCOMPARE( (np.get("hello","bye")) , QString("") ); np.body().remove(QStringLiteral("hello")); QCOMPARE( (np.get("hello","bye")) , QString("bye") ); np.set(QStringLiteral("foo"), "bar"); QByteArray ba = np.serialize(); //qDebug() << "Serialized packet:" << ba; NetworkPacket np2(QLatin1String("")); NetworkPacket::unserialize(ba,&np2); QCOMPARE( np.id(), np2.id() ); QCOMPARE( np.type(), np2.type() ); QCOMPARE( np.body(), np2.body() ); QByteArray json("{\"id\":\"123\",\"type\":\"test\",\"body\":{\"testing\":true}}"); //qDebug() << json; NetworkPacket::unserialize(json,&np2); QCOMPARE( np2.id(), QString("123") ); QCOMPARE( (np2.get("testing")), true ); QCOMPARE( (np2.get("not_testing")), false ); QCOMPARE( (np2.get("not_testing",true)), true ); //NetworkPacket::unserialize("this is not json",&np2); //QtTest::ignoreMessage(QtSystemMsg, "json_parser - syntax error found, forcing abort, Line 1 Column 0"); //QtTest::ignoreMessage(QtDebugMsg, "Unserialization error: 1 \"syntax error, unexpected string\""); } void NetworkPacketTests::networkPacketIdentityTest() { NetworkPacket np(QLatin1String("")); NetworkPacket::createIdentityPacket(&np); QCOMPARE( np.get("protocolVersion", -1) , NetworkPacket::s_protocolVersion ); QCOMPARE( np.type() , PACKET_TYPE_IDENTITY ); } void NetworkPacketTests::cleanupTestCase() { // Called after the last testfunction was executed } void NetworkPacketTests::init() { // Called before each testfunction is executed } void NetworkPacketTests::cleanup() { // Called after every testfunction }