[OpenDocString] kdeconnect-kde (cpp)
device.cpp
static void warn(const QString &info)
{
    qWarning() << "Device pairing error" << info;
}
This implements a warning message.
Device::Device(QObject *parent, const QString &id)
    : QObject(parent)
    , d(new Device::DevicePrivate(id))
{
    d->m_protocolVersion = NetworkPacket::s_protocolVersion;
    KdeConnectConfig::DeviceInfo info = KdeConnectConfig::instance().getTrustedDevice(d->m_deviceId);

    d->m_deviceName = info.deviceName;
    d->m_deviceType = str2type(info.deviceType);

    // Register in bus
    QDBusConnection::sessionBus().registerObject(dbusPath(), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportAdaptors);

    // Assume every plugin is supported until addLink is called and we can get the actual list
    d->m_allPlugins = PluginLoader::instance()->getPluginList().toSet();
    d->m_supportedPlugins = d->m_allPlugins;

    connect(this, &Device::pairingError, this, &warn);
}
This constructs a device object from an id, and registers itself in DBus and sets up connections for error handling, as well creates a plugin list and supported plugin list. It also connects the pairingError signal to the warn method.
Device::Device(QObject *parent, const NetworkPacket &identityPacket, DeviceLink *dl)
    : QObject(parent)
    , d(new Device::DevicePrivate(identityPacket.get(QStringLiteral("deviceId"))))
{
    d->m_deviceName = identityPacket.get(QStringLiteral("deviceName"));
    d->m_allPlugins = PluginLoader::instance()->getPluginList().toSet();

    addLink(identityPacket, dl);

    // Register in bus
    QDBusConnection::sessionBus().registerObject(dbusPath(), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportAdaptors);

    connect(this, &Device::pairingError, this, &warn);

    connect(this, &Device::reachableChanged, this, &Device::statusIconNameChanged);
    connect(this, &Device::trustedChanged, this, &Device::statusIconNameChanged);
}
This constructs a device object from an identity network packet and a device link pointer. It takes the device id and device name from the packet. It registers itself in DBus and sets up signal/slot connections for error handling, as well as reachable and trusted change events.
Device::~Device()
{
    delete d;
}
This removes the device object upon destruction.
QString Device::id() const
{
    return d->m_deviceId;
}
Returns the id of the device.
QString Device::name() const
{
    return d->m_deviceName;
}
Returns the device name stored in the d-pointer.
QString Device::type() const
{
    return type2str(d->m_deviceType);
}
Returns the type represented in the device type object of the d-pointer.
bool Device::isReachable() const
{
    return !d->m_deviceLinks.isEmpty();
}
This implements checking if the device is reachable. It is reachable when the internal device links list is not empty.
int Device::protocolVersion()
{
    return d->m_protocolVersion;
}
Returns the protocol version number in the internal list.
QStringList Device::supportedPlugins() const
{
    return d->m_supportedPlugins.toList();
}
Returns the list of supported plugins as QStringList.
bool Device::hasPlugin(const QString &name) const
{
    return d->m_plugins.contains(name);
}
Returns true if the plugin with the given name exists.
QStringList Device::loadedPlugins() const
{
    return d->m_plugins.keys();
}
Returns the list of loaded plugins.
void Device::reloadPlugins()
{
    QHash newPluginMap, oldPluginMap = d->m_plugins;
    QMultiMap newPluginsByIncomingCapability;

    if (isTrusted() && isReachable()) { // Do not load any plugin for unpaired devices, nor useless loading them for unreachable devices

        PluginLoader *loader = PluginLoader::instance();

        for (const QString &pluginName : qAsConst(d->m_supportedPlugins)) {
            const KPluginMetaData service = loader->getPluginInfo(pluginName);

            const bool pluginEnabled = isPluginEnabled(pluginName);
            const QSet incomingCapabilities =
                KPluginMetaData::readStringList(service.rawData(), QStringLiteral("X-KdeConnect-SupportedPacketType")).toSet();

            if (pluginEnabled) {
                KdeConnectPlugin *plugin = d->m_plugins.take(pluginName);

                if (!plugin) {
                    plugin = loader->instantiatePluginForDevice(pluginName, this);
                }
                Q_ASSERT(plugin);

                for (const QString &interface : incomingCapabilities) {
                    newPluginsByIncomingCapability.insert(interface, plugin);
                }

                newPluginMap[pluginName] = plugin;
            }
        }
    }

    const bool differentPlugins = oldPluginMap != newPluginMap;

    // Erase all left plugins in the original map (meaning that we don't want
    // them anymore, otherwise they would have been moved to the newPluginMap)
    qDeleteAll(d->m_plugins);
    d->m_plugins = newPluginMap;
    d->m_pluginsByIncomingCapability = newPluginsByIncomingCapability;

    QDBusConnection bus = QDBusConnection::sessionBus();
    for (KdeConnectPlugin *plugin : qAsConst(d->m_plugins)) {
        // TODO: see how it works in Android (only done once, when created)
        plugin->connected();

        const QString dbusPath = plugin->dbusPath();
        if (!dbusPath.isEmpty()) {
            bus.registerObject(dbusPath,
                               plugin,
                               QDBusConnection::ExportAllProperties | QDBusConnection::ExportScriptableInvokables | QDBusConnection::ExportScriptableSignals
                                   | QDBusConnection::ExportScriptableSlots);
        }
    }
    if (differentPlugins) {
        Q_EMIT pluginsChanged();
    }
}
This function is responsible for reloading the plugins associated with a device when there is a change in the supported capabilities. First, the function creates two maps: newPluginMap and oldPluginMap, to track the changes in the plugins. Then, it checks if the device is trusted and reachable, since it will only load plugins for devices that are paired and reachable. Next, it iterates over the supported plugins of the device and, for each plugin, retrieves its metadata and checks if it is enabled. If the plugin is enabled, it is either taken from the oldPluginMap or instantiated and added to the newPluginMap. The incoming capabilities of the plugin are added to the newPluginsByIncomingCapability map. Then, the function erases all plugins that are not in the newPluginMap, and emits the pluginsChanged signal. Then, the function is also called to emit the pluginsChanged signal.
QString Device::pluginsConfigFile() const
{
    return KdeConnectConfig::instance().deviceConfigDir(id()).absoluteFilePath(QStringLiteral("config"));
}
Returns the device plugins config path as a QString by reading it from the device config dir.
void Device::requestPair()
{
    if (isTrusted()) {
        Q_EMIT pairingError(i18n("Already paired"));
        return;
    }

    if (!isReachable()) {
        Q_EMIT pairingError(i18n("Device not reachable"));
        return;
    }

    for (DeviceLink *dl : qAsConst(d->m_deviceLinks)) {
        dl->userRequestsPair();
    }
}
This requests that the device be paired. It checks that the device is trusted and if it is reachable. Then it requests pairing from all known device links.
void Device::unpair()
{
    for (DeviceLink *dl : qAsConst(d->m_deviceLinks)) {
        dl->userRequestsUnpair();
    }
    KdeConnectConfig::instance().removeTrustedDevice(id());
    Q_EMIT trustedChanged(false);
}
This removes the trusted device after requesting unpair for each device link of the internal device link list. Finally it emits the trustedChanged signal.
void Device::pairStatusChanged(DeviceLink::PairStatus status)
{
    if (status == DeviceLink::NotPaired) {
        KdeConnectConfig::instance().removeTrustedDevice(id());

        for (DeviceLink *dl : qAsConst(d->m_deviceLinks)) {
            if (dl != sender()) {
                dl->setPairStatus(DeviceLink::NotPaired);
            }
        }
    } else {
        KdeConnectConfig::instance().addTrustedDevice(id(), name(), type());
    }

    reloadPlugins(); // Will load/unload plugins

    bool isTrusted = (status == DeviceLink::Paired);
    Q_EMIT trustedChanged(isTrusted);
    Q_ASSERT(isTrusted == this->isTrusted());
}
If not paired, this code removes the device and updates the pair status for each device link. If the link is paired, it adds it to the trusted devices list in the KdeConnectConfig singleton. Then it reloads the plugins and emits the trustedChanged signal if the status is paired. Finally, with an assert it makes sure this device is trusted.
static bool lessThan(DeviceLink *p1, DeviceLink *p2)
{
    return p1->provider()->priority() > p2->provider()->priority();
}
This implements the less than operator to determine if the first device link has a higher priority than the given second device link.
void Device::addLink(const NetworkPacket &identityPacket, DeviceLink *link)
{
    // qCDebug(KDECONNECT_CORE) << "Adding link to" << id() << "via" << link->provider();

    setName(identityPacket.get(QStringLiteral("deviceName")));
    setType(identityPacket.get(QStringLiteral("deviceType")));

    if (d->m_deviceLinks.contains(link)) {
        return;
    }

    d->m_protocolVersion = identityPacket.get(QStringLiteral("protocolVersion"), -1);
    if (d->m_protocolVersion != NetworkPacket::s_protocolVersion) {
        qCWarning(KDECONNECT_CORE) << d->m_deviceName << "- warning, device uses a different protocol version" << d->m_protocolVersion << "expected"
                                   << NetworkPacket::s_protocolVersion;
    }

    connect(link, &QObject::destroyed, this, &Device::linkDestroyed);

    d->m_deviceLinks.append(link);

    // Theoretically we will never add two links from the same provider (the provider should destroy
    // the old one before this is called), so we do not have to worry about destroying old links.
    //-- Actually, we should not destroy them or the provider will store an invalid ref!

    connect(link, &DeviceLink::receivedPacket, this, &Device::privateReceivedPacket);

    std::sort(d->m_deviceLinks.begin(), d->m_deviceLinks.end(), lessThan);

    const bool capabilitiesSupported = identityPacket.has(QStringLiteral("incomingCapabilities")) || identityPacket.has(QStringLiteral("outgoingCapabilities"));
    if (capabilitiesSupported) {
        const QSet outgoingCapabilities = identityPacket.get(QStringLiteral("outgoingCapabilities")).toSet(),
                            incomingCapabilities = identityPacket.get(QStringLiteral("incomingCapabilities")).toSet();

        d->m_supportedPlugins = PluginLoader::instance()->pluginsForCapabilities(incomingCapabilities, outgoingCapabilities);
        // qDebug() << "new plugins for" << m_deviceName << m_supportedPlugins << incomingCapabilities << outgoingCapabilities;
    } else {
        d->m_supportedPlugins = PluginLoader::instance()->getPluginList().toSet();
    }

    reloadPlugins();

    if (d->m_deviceLinks.size() == 1) {
        Q_EMIT reachableChanged(true);
    }

    connect(link, &DeviceLink::pairStatusChanged, this, &Device::pairStatusChanged);
    connect(link, &DeviceLink::pairingRequest, this, &Device::addPairingRequest);
    connect(link, &DeviceLink::pairingRequestExpired, this, &Device::removePairingRequest);
    connect(link, &DeviceLink::pairingError, this, &Device::pairingError);
}
This adds a DeviceLink object to the device's list of links. The given NetworkPacket is used to set the name, type, and protocol version of the device, and to determine which plugins the device supports. If the link parameter is already in the device's list of links, the method returns early. Otherwise, the link object is added to the list, the list is sorted, and a number of connections are made between the link object and the Device object's member functions, including linkDestroyed(), receivedPacket(), outgoingCapabilities(), pairStatusChanged(), pairingRequestExpired(), and pairingError(). If this is the first link added to the device, the reachableChanged() signal is emitted with true.
void Device::addPairingRequest(PairingHandler *handler)
{
    const bool wasEmpty = d->m_pairRequests.isEmpty();
    d->m_pairRequests.insert(handler);

    if (wasEmpty != d->m_pairRequests.isEmpty())
        Q_EMIT hasPairingRequestsChanged(!d->m_pairRequests.isEmpty());
}
This adds a pairing handler to the device->m_pairRequests list and emits a signal hasPairingRequestsChanged.
void Device::removePairingRequest(PairingHandler *handler)
{
    const bool wasEmpty = d->m_pairRequests.isEmpty();
    d->m_pairRequests.remove(handler);

    if (wasEmpty != d->m_pairRequests.isEmpty())
        Q_EMIT hasPairingRequestsChanged(!d->m_pairRequests.isEmpty());
}
This removes the pairing request object. If the set was previously empty and is now non-empty, it will emit a signal called hasPairingRequestsChanged with 'true'.
bool Device::hasPairingRequests() const
{
    return !d->m_pairRequests.isEmpty();
}
This implements checking if the device has pairing requests by checking if the list holding the requests is empty.
void Device::acceptPairing()
{
    if (d->m_pairRequests.isEmpty())
        qWarning() << "no pair requests to accept!";

    // copying because the pairing handler will be removed upon accept
    const auto prCopy = d->m_pairRequests;
    for (auto ph : prCopy)
        ph->acceptPairing();
}
This code sets all pair request handlers to accept pairing. It makes a copy of the internal list because the handler will be removed upon accept. If the pair request list is empty, it logs a warning.
void Device::rejectPairing()
{
    if (d->m_pairRequests.isEmpty())
        qWarning() << "no pair requests to reject!";

    // copying because the pairing handler will be removed upon reject
    const auto prCopy = d->m_pairRequests;
    for (auto ph : prCopy)
        ph->rejectPairing();
}
This code sets all pair request handlers to reject pairing. It makes a copy of the internal list because the handler will be removed upon accept. If the reject request list is empty, it logs a warning.
void Device::linkDestroyed(QObject *o)
{
    removeLink(static_cast(o));
}
This removes the device link object from the list of links.
void Device::removeLink(DeviceLink *link)
{
    d->m_deviceLinks.removeAll(link);

    // qCDebug(KDECONNECT_CORE) << "RemoveLink" << m_deviceLinks.size() << "links remaining";

    if (d->m_deviceLinks.isEmpty()) {
        reloadPlugins();
        Q_EMIT reachableChanged(false);
    }
}
This removes the given device link object from the device list. It reloads the plugins if the list is empty, and emits a signal reachableChanged.
bool Device::sendPacket(NetworkPacket &np)
{
    Q_ASSERT(np.type() != PACKET_TYPE_PAIR);
    Q_ASSERT(isTrusted());

    // Maybe we could block here any packet that is not an identity or a pairing packet to prevent sending non encrypted data
    for (DeviceLink *dl : qAsConst(d->m_deviceLinks)) {
        if (dl->sendPacket(np))
            return true;
    }

    return false;
}
This sends a network packet, if possible. It checks that the packet is not a PAIR packet (used for device pairing), and that the device is trusted. Then it loops over the available DeviceLink objects and tries to send the packet on each one. It returns true on the first successful attempt. If none of the links can send the packet, the function returns false.
void Device::privateReceivedPacket(const NetworkPacket &np)
{
    Q_ASSERT(np.type() != PACKET_TYPE_PAIR);
    if (isTrusted()) {
        const QList plugins = d->m_pluginsByIncomingCapability.values(np.type());
        if (plugins.isEmpty()) {
            qWarning() << "discarding unsupported packet" << np.type() << "for" << name();
        }
        for (KdeConnectPlugin *plugin : plugins) {
            plugin->receivePacket(np);
        }
    } else {
        qCDebug(KDECONNECT_CORE) << "device" << name() << "not paired, ignoring packet" << np.type();
        unpair();
    }
}
It receives a NetworkPacket object np and processes it. The method first checks that the packet type is not a PACKET_TYPE_PAIR, as those are handled separately. If the device is trusted (paired), the method obtains the list of plugins that can handle the incoming packet based on the np.type() value, from the d->m_pluginsByIncomingCapability map, which is a QMultiMap that stores plugins that can handle incoming packets grouped by the packet type. If there are no plugins that can handle the packet type, a warning message is printed to the console, and the packet is discarded. If there are plugins that can handle the packet type, the packet is sent to each plugin's receivePacket method, which will process the packet. If the device is not trusted (unpaired), the packet is ignored, and a debug message is printed to the console. The unpair method is also called on the device, which removes the pairing information.
bool Device::isTrusted() const
{
    return KdeConnectConfig::instance().trustedDevices().contains(id());
}
This implements checking if the device is trusted.
QStringList Device::availableLinks() const
{
    QStringList sl;
    sl.reserve(d->m_deviceLinks.size());
    for (DeviceLink *dl : qAsConst(d->m_deviceLinks)) {
        sl.append(dl->provider()->name());
    }
    return sl;
}
Returns a list of the names of all available device links.
void Device::cleanUnneededLinks()
{
    if (isTrusted()) {
        return;
    }
    for (int i = 0; i < d->m_deviceLinks.size();) {
        DeviceLink *dl = d->m_deviceLinks[i];
        if (!dl->linkShouldBeKeptAlive()) {
            dl->deleteLater();
            d->m_deviceLinks.remove(i);
        } else {
            i++;
        }
    }
}
This removes all device links if the device is not trusted, except the ones that should be kept alive.
QHostAddress Device::getLocalIpAddress() const
{
    for (DeviceLink *dl : qAsConst(d->m_deviceLinks)) {
        LanDeviceLink *ldl = dynamic_cast(dl);
        if (ldl) {
            return ldl->hostAddress();
        }
    }
    return QHostAddress::Null;
}
Returns the IP address of the first LAN device it finds. If it doesn't find any, it returns QHostAddress::Null.
Device::DeviceType Device::str2type(const QString &deviceType)
{
    if (deviceType == QLatin1String("desktop"))
        return Desktop;
    if (deviceType == QLatin1String("laptop"))
        return Laptop;
    if (deviceType == QLatin1String("smartphone") || deviceType == QLatin1String("phone"))
        return Phone;
    if (deviceType == QLatin1String("tablet"))
        return Tablet;
    if (deviceType == QLatin1String("tv"))
        return Tv;
    return Unknown;
}
Turns a string into a DeviceType. Possible strings are 'desktop', 'laptop','smartphone', 'phone', 'tablet' and 'tv'. For all other strings it returns Unknown.
QString Device::type2str(Device::DeviceType deviceType)
{
    if (deviceType == Desktop)
        return QStringLiteral("desktop");
    if (deviceType == Laptop)
        return QStringLiteral("laptop");
    if (deviceType == Phone)
        return QStringLiteral("smartphone");
    if (deviceType == Tablet)
        return QStringLiteral("tablet");
    if (deviceType == Tv)
        return QStringLiteral("tv");
    return QStringLiteral("unknown");
}
Turns a DeviceType into a QString. Possible strings are 'desktop', 'laptop','smartphone', 'phone', 'tablet' and 'tv'. For all other strings it returns 'unknown'.
QString Device::statusIconName() const
{
    return iconForStatus(isReachable(), isTrusted());
}
Returns the icon name as a QString depending on if the device is reachable and trusted.
QString Device::iconName() const
{
    return iconForStatus(true, false);
}
Returns the icon name as a QString.
QString Device::iconForStatus(bool reachable, bool trusted) const
{
    Device::DeviceType deviceType = d->m_deviceType;
    if (deviceType == Device::Unknown) {
        deviceType = Device::Phone; // Assume phone if we don't know the type
    } else if (deviceType == Device::Desktop) {
        deviceType = Device::Device::Laptop; // We don't have desktop icon yet
    }

    QString status = (reachable ? (trusted ? QStringLiteral("connected") : QStringLiteral("disconnected")) : QStringLiteral("trusted"));
    QString type = type2str(deviceType);

    return type + status;
}
It returns a QString object that represents the icon for the device based on its status. The function first gets the device type from the internal data of the device object, and if it is unknown, it assumes the device is a phone. If the device is a desktop, it assumes the device is a laptop since there is no desktop icon yet. Then it creates a QString object called "status" based on the values of the "reachable" and "trusted" parameters. If the device is reachable and trusted, the status is "connected." If the device is reachable but not trusted, the status is "disconnected." If the device is not reachable, the status is "trusted." Finally, the function creates another QString object called "type" by calling a helper function called "type2str" to convert the "DeviceType" enum to a string representation. The function then returns the concatenation of "type" and "status," which represents the icon for the device based on its status.
void Device::setName(const QString &name)
{
    if (d->m_deviceName != name) {
        d->m_deviceName = name;
        KdeConnectConfig::instance().setDeviceProperty(d->m_deviceId, QStringLiteral("name"), name);
        Q_EMIT nameChanged(name);
    }
}
Sets the device name by storing it in the d-pointer member variable. Then it sets the device property on the KdeConnectConfig singleton instance and emits the signal 'nameChanged'.
void Device::setType(const QString &strtype)
{
    auto type = str2type(strtype);
    if (d->m_deviceType != type) {
        d->m_deviceType = type;
        KdeConnectConfig::instance().setDeviceProperty(d->m_deviceId, QStringLiteral("type"), strtype);
        Q_EMIT typeChanged(strtype);
    }
}
Sets the type string within in the Device object, and sets the device property in the KdeConnectConfig singleton instance, before emitting a typeChanged signal.
KdeConnectPlugin *Device::plugin(const QString &pluginName) const
{
    return d->m_plugins[pluginName];
}
Returns a pointer to a KdeConnectPlugin object, given a plugin name.
void Device::setPluginEnabled(const QString &pluginName, bool enabled)
{
    if (!d->m_allPlugins.contains(pluginName)) {
        return;
    }

    KConfigGroup pluginStates = KSharedConfig::openConfig(pluginsConfigFile())->group("Plugins");

    const QString enabledKey = pluginName + QStringLiteral("Enabled");
    pluginStates.writeEntry(enabledKey, enabled);
    reloadPlugins();
}
Sets the plugin enabled value on in the internal configuration list. First it checks if the list of plugins contains the plugin. Then it constructs the enabled-key and writes the state to the plugin states config group. Then it reloads the plugins.
bool Device::isPluginEnabled(const QString &pluginName) const
{
    const QString enabledKey = pluginName + QStringLiteral("Enabled");
    KConfigGroup pluginStates = KSharedConfig::openConfig(pluginsConfigFile())->group("Plugins");

    return (pluginStates.hasKey(enabledKey) ? pluginStates.readEntry(enabledKey, false)
                                            : PluginLoader::instance()->getPluginInfo(pluginName).isEnabledByDefault());
}
Returns true if the plugin is enabled. It first constructs the enabledKey, then checks the plugin states for that key. If the key exists, the function reads the value of the key using the "readEntry" function and returns it. If the key is not found, the function gets the default enabled state of the plugin by calling the "isEnabledByDefault" function of the "PluginLoader" singleton instance for the device. The return value is based on the retrieved value from the configuration file or the default value from the plugin info.
QString Device::encryptionInfo() const
{
    QString result;
    const QCryptographicHash::Algorithm digestAlgorithm = QCryptographicHash::Algorithm::Sha256;

    QString localChecksum = QString::fromLatin1(KdeConnectConfig::instance().certificate().digest(digestAlgorithm).toHex());
    for (int i = 2; i < localChecksum.size(); i += 3) {
        localChecksum.insert(i, QStringLiteral(":")); // Improve readability
    }
    result += i18n("SHA256 fingerprint of your device certificate is: %1\n", localChecksum);

    std::string remotePem = KdeConnectConfig::instance().getDeviceProperty(id(), QStringLiteral("certificate")).toStdString();
    QSslCertificate remoteCertificate = QSslCertificate(QByteArray(remotePem.c_str(), (int)remotePem.size()));
    QString remoteChecksum = QString::fromLatin1(remoteCertificate.digest(digestAlgorithm).toHex());
    for (int i = 2; i < remoteChecksum.size(); i += 3) {
        remoteChecksum.insert(i, QStringLiteral(":")); // Improve readability
    }
    result += i18n("SHA256 fingerprint of remote device certificate is: %1\n", remoteChecksum);

    return result;
}
This retrieves the encryption info for the certificate from KdeConnectConfig. It produces a string containing the SHA256 fingerprint from the local device and remote device certificate checksums, and returns it.
QSslCertificate Device::certificate() const
{
    if (!d->m_deviceLinks.isEmpty()) {
        return d->m_deviceLinks[0]->certificate();
    }
    return QSslCertificate();
}
Returns a copy of the certificate object, which uses implicit sharing for its data. If there are device links in the list, it returns the certificate of the first item in the list, otherwise it creates one.
QByteArray Device::verificationKey() const
{
    auto a = KdeConnectConfig::instance().certificate().publicKey().toDer();
    auto b = certificate().publicKey().toDer();
    if (a < b) {
        std::swap(a, b);
    }

    QCryptographicHash hash(QCryptographicHash::Sha256);
    hash.addData(a);
    hash.addData(b);
    return hash.result().toHex();
}
This generates a verification key from the certificate. The function first retrieves the public key of the certificate from the "KdeConnectConfig" instance and converts it to DER format, which is a binary format for representing cryptographic objects. Then, the function retrieves the public key of the certificate for the current device and also converts it to DER format., and compares the two public keys. If the first key is lexicographically less than the second key, the function swaps the values of "a" and "b" using the "std::swap" function. After that, the function creates a "QCryptographicHash" object using the SHA-256 hash algorithm and adds the two DER-encoded keys to it using the "addData" function. Finally, the function returns the result of the hash computation as a QByteArray object in hexadecimal format using the "toHex" function. This QByteArray object represents the verification key for the device, which can be used to verify its identity in a secure communication protocol.
QString Device::pluginIconName(const QString &pluginName)
{
    if (hasPlugin(pluginName)) {
        return d->m_plugins[pluginName]->iconName();
    }
    return QString();
}
Returns the icon name of the plugin as a QString.