[OpenDocString] kdeconnect-kde (cpp)
bluetoothlinkprovider.cpp
BluetoothLinkProvider::BluetoothLinkProvider()
    : mServiceUuid(QBluetoothUuid(QStringLiteral("185f3df4-3268-4e3f-9fca-d4d5059915bd")))
    , mServiceDiscoveryAgent(new QBluetoothServiceDiscoveryAgent(this))
    , connectTimer(new QTimer(this))
{
    connectTimer->setInterval(30000);
    connectTimer->setSingleShot(false);

    mServiceDiscoveryAgent->setUuidFilter(mServiceUuid);
    connect(connectTimer, &QTimer::timeout, this, [this]() {
        mServiceDiscoveryAgent->start();
    });

    connect(mServiceDiscoveryAgent, &QBluetoothServiceDiscoveryAgent::serviceDiscovered, this, &BluetoothLinkProvider::serviceDiscovered);
}
This constructor builds a BluetoothLinkProvider object and its deregistration agent. It takes the current uuid of the bluetooth instance and sets up a timer to discover new services.
void BluetoothLinkProvider::onStart()
{
    QBluetoothLocalDevice localDevice;
    if (!localDevice.isValid()) {
        qCWarning(KDECONNECT_CORE) << "No local bluetooth adapter found";
        return;
    }

    mBluetoothServer = new QBluetoothServer(QBluetoothServiceInfo::RfcommProtocol, this);
    mBluetoothServer->setSecurityFlags(QBluetooth::Encryption | QBluetooth::Secure);
    connect(mBluetoothServer, SIGNAL(newConnection()), this, SLOT(serverNewConnection()));

    mServiceDiscoveryAgent->start();

    connectTimer->start();
    mKdeconnectService = mBluetoothServer->listen(mServiceUuid, QStringLiteral("KDE Connect"));
}
This starts the bluetooth link provider. It creates a BluetoothServer object, connects to it, sets up signal and secure connections, and starts the KDE Connect service.
void BluetoothLinkProvider::onStop()
{
    if (!mBluetoothServer) {
        return;
    }

    connectTimer->stop();

    mKdeconnectService.unregisterService();
    mBluetoothServer->close();
    mBluetoothServer->deleteLater();
}
This removes the BluetoothLinkProvider object upon shutdown. It stops the connect timer and unregisters the KdeconnectService.
void BluetoothLinkProvider::onNetworkChange()
{
}
This implements the network change signal.
void BluetoothLinkProvider::connectError()
{
    QBluetoothSocket *socket = qobject_cast(sender());
    if (!socket)
        return;

    qCWarning(KDECONNECT_CORE) << "Couldn't connect to socket:" << socket->errorString();

    disconnect(socket, SIGNAL(connected()), this, SLOT(clientConnected()));
    disconnect(socket, SIGNAL(readyRead()), this, SLOT(serverDataReceived()));
    disconnect(socket, SIGNAL(error(QBluetoothSocket::SocketError)), this, SLOT(connectError()));

    mSockets.remove(socket->peerAddress());
    socket->deleteLater();
}
This removes the socket that sent the connectError signal from the internal list of sockets. It logs a warning if the socket cannot be opened.
void BluetoothLinkProvider::addLink(BluetoothDeviceLink *deviceLink, const QString &deviceId)
{
    QMap::iterator oldLinkIterator = mLinks.find(deviceId);
    if (oldLinkIterator != mLinks.end()) {
        DeviceLink *oldLink = oldLinkIterator.value();
        disconnect(oldLink, SIGNAL(destroyed(QObject *)), this, SLOT(deviceLinkDestroyed(QObject *)));
        oldLink->deleteLater();
        mLinks.erase(oldLinkIterator);
    }

    mLinks[deviceId] = deviceLink;
}
This adds a BluetoothDeviceLink object to the list of links associated with a device. It disconnects the old device link if it exists, and removes the old device link.
void BluetoothLinkProvider::serviceDiscovered(const QBluetoothServiceInfo &old_info)
{
    auto info = old_info;
    info.setServiceUuid(mServiceUuid);
    if (mSockets.contains(info.device().address())) {
        return;
    }

    QBluetoothSocket *socket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol);

    // Delay before sending data
    QPointer deleteableSocket = socket;
    connect(socket, &QBluetoothSocket::connected, this, [this, deleteableSocket]() {
        QTimer::singleShot(500, this, [this, deleteableSocket]() {
            clientConnected(deleteableSocket);
        });
    });
    connect(socket, SIGNAL(error(QBluetoothSocket::SocketError)), this, SLOT(connectError()));

    socket->connectToService(info);

    qCDebug(KDECONNECT_CORE()) << "Connecting to" << info.device().address();

    if (socket->error() != QBluetoothSocket::NoSocketError) {
        qCWarning(KDECONNECT_CORE) << "Socket connection error:" << socket->errorString();
    }
}
This creates a socket and connects it to the device's service. It also connects to the socket and sets the service uuid to the old_info.
void BluetoothLinkProvider::clientConnected(QPointer socket)
{
    if (!socket)
        return;

    auto peer = socket->peerAddress();

    qCDebug(KDECONNECT_CORE()) << "Connected to" << peer;

    if (mSockets.contains(socket->peerAddress())) {
        qCWarning(KDECONNECT_CORE()) << "Duplicate connection to" << peer;
        socket->close();
        socket->deleteLater();
        return;
    }

    ConnectionMultiplexer *multiplexer = new ConnectionMultiplexer(socket, this);

    mSockets.insert(peer, multiplexer);
    disconnect(socket, nullptr, this, nullptr);

    auto channel = QSharedPointer{multiplexer->getDefaultChannel().release()};
    connect(channel.data(), &MultiplexChannel::readyRead, this, [this, peer, channel]() {
        clientIdentityReceived(peer, channel);
    });
    connect(channel.data(), &MultiplexChannel::aboutToClose, this, [this, peer, channel]() {
        socketDisconnected(peer, channel.data());
    });

    if (channel->bytesAvailable())
        clientIdentityReceived(peer, channel);
    if (!channel->isOpen())
        socketDisconnected(peer, channel.data());
}
This creates a multiplexer and connects the socket to its peer. It disconnects the socket from the peer, and creates a channel to receive data on the channel.
void BluetoothLinkProvider::clientIdentityReceived(const QBluetoothAddress &peer, QSharedPointer socket)
{
    socket->startTransaction();

    QByteArray identityArray = socket->readLine();
    if (identityArray.isEmpty()) {
        socket->rollbackTransaction();
        return;
    }
    socket->commitTransaction();

    disconnect(socket.data(), &MultiplexChannel::readyRead, this, nullptr);

    NetworkPacket receivedPacket;
    bool success = NetworkPacket::unserialize(identityArray, &receivedPacket);

    if (!success || receivedPacket.type() != PACKET_TYPE_IDENTITY) {
        qCWarning(KDECONNECT_CORE) << "Not an identity packet";
        mSockets.remove(peer);
        socket->close();
        socket->deleteLater();
        return;
    }

    qCDebug(KDECONNECT_CORE()) << "Received identity packet from" << peer;

    // TODO?
    // disconnect(socket, SIGNAL(error(QBluetoothSocket::SocketError)), this, SLOT(connectError()));

    const QString &deviceId = receivedPacket.get(QStringLiteral("deviceId"));
    BluetoothDeviceLink *deviceLink = new BluetoothDeviceLink(deviceId, this, mSockets[peer], socket);

    NetworkPacket np2;
    NetworkPacket::createIdentityPacket(&np2);
    success = deviceLink->sendPacket(np2);

    if (success) {
        qCDebug(KDECONNECT_CORE) << "Handshaking done (I'm the new device)";

        connect(deviceLink, SIGNAL(destroyed(QObject *)), this, SLOT(deviceLinkDestroyed(QObject *)));

        Q_EMIT onConnectionReceived(receivedPacket, deviceLink);

        // We kill any possible link from this same device
        addLink(deviceLink, deviceId);

    } else {
        // Connection might be lost. Delete it.
        delete deviceLink;
    }

    // We don't delete the socket because now it's owned by the BluetoothDeviceLink
}
This receives an identity packet from a peer, and creates a BluetoothDeviceLink object and sends it to the device link provider. It takes the packet from the socket, and creates a NetworkPacket object from the received packet. It checks if the packet is an identity packet, if it is, it creates a NetworkPacket object, and sends it to the device link. Then it kills any possible link if the connection is lost.
void BluetoothLinkProvider::serverNewConnection()
{
    QBluetoothSocket *socket = mBluetoothServer->nextPendingConnection();

    qCDebug(KDECONNECT_CORE()) << "Received connection from" << socket->peerAddress();

    QBluetoothAddress peer = socket->peerAddress();

    if (mSockets.contains(peer)) {
        qCDebug(KDECONNECT_CORE()) << "Duplicate connection from" << peer;
        socket->close();
        socket->deleteLater();
        return;
    }

    ConnectionMultiplexer *multiplexer = new ConnectionMultiplexer(socket, this);

    mSockets.insert(peer, multiplexer);
    disconnect(socket, nullptr, this, nullptr);

    auto channel = QSharedPointer{multiplexer->getDefaultChannel().release()};
    connect(channel.data(), &MultiplexChannel::readyRead, this, [this, peer, channel]() {
        serverDataReceived(peer, channel);
    });
    connect(channel.data(), &MultiplexChannel::aboutToClose, this, [this, peer, channel]() {
        socketDisconnected(peer, channel.data());
    });

    if (!channel->isOpen()) {
        socketDisconnected(peer, channel.data());
        return;
    }

    NetworkPacket np2;
    NetworkPacket::createIdentityPacket(&np2);
    socket->write(np2.serialize());

    qCDebug(KDECONNECT_CORE()) << "Sent identity packet to" << socket->peerAddress();
}
This creates a new connection from the server and multiplexes it to the peer. It creates a ConnectionMultiplexer object, connects the socket to the peer and writes the packet to the peer's network buffer. It also connects the channel to the peer's multiplexer channel and writes the packet to the peer's socket.
void BluetoothLinkProvider::serverDataReceived(const QBluetoothAddress &peer, QSharedPointer socket)
{
    QByteArray identityArray;
    socket->startTransaction();
    identityArray = socket->readLine();

    if (identityArray.isEmpty()) {
        socket->rollbackTransaction();
        return;
    }
    socket->commitTransaction();

    disconnect(socket.data(), &MultiplexChannel::readyRead, this, nullptr);

    NetworkPacket receivedPacket;
    bool success = NetworkPacket::unserialize(identityArray, &receivedPacket);

    if (!success || receivedPacket.type() != PACKET_TYPE_IDENTITY) {
        qCWarning(KDECONNECT_CORE) << "Not an identity packet.";
        mSockets.remove(peer);
        socket->close();
        socket->deleteLater();
        return;
    }

    qCDebug(KDECONNECT_CORE()) << "Received identity packet from" << peer;

    const QString &deviceId = receivedPacket.get(QStringLiteral("deviceId"));
    BluetoothDeviceLink *deviceLink = new BluetoothDeviceLink(deviceId, this, mSockets[peer], socket);

    connect(deviceLink, SIGNAL(destroyed(QObject *)), this, SLOT(deviceLinkDestroyed(QObject *)));

    Q_EMIT onConnectionReceived(receivedPacket, deviceLink);

    addLink(deviceLink, deviceId);
}
This reads a network packet from the given peer and converts it to a BluetoothDeviceLink object. It first connects the packet to the device link object, and removes the socket from the list of sockets. Then it connects the packet to the device link object, and emits the signal onConnectionReceived.
void BluetoothLinkProvider::deviceLinkDestroyed(QObject *destroyedDeviceLink)
{
    const QString id = destroyedDeviceLink->property("deviceId").toString();
    qCDebug(KDECONNECT_CORE()) << "Device disconnected:" << id;
    QMap::iterator oldLinkIterator = mLinks.find(id);
    if (oldLinkIterator != mLinks.end() && oldLinkIterator.value() == destroyedDeviceLink) {
        mLinks.erase(oldLinkIterator);
    }
}
This removes the device link object from the list of links. If the list has an old device link object with the same id, the function erases the old link object from the list of links. If the list has no old link object, the function erases the old one.
void BluetoothLinkProvider::socketDisconnected(const QBluetoothAddress &peer, MultiplexChannel *socket)
{
    qCDebug(KDECONNECT_CORE()) << "Disconnected";
    disconnect(socket, nullptr, this, nullptr);

    mSockets.remove(peer);
}
This removes the socket object from the list of sockets, and removes the socket object from the list of sockets.
BluetoothLinkProvider::~BluetoothLinkProvider()
{
}
This implements the singleton design pattern and provides access to the bluetooth link provider.