kdeconnect-kde/core/backends/bluetooth/connectionmultiplexer.cpp

428 lines
14 KiB
C++
Raw Normal View History

2019-01-20 11:23:50 +00:00
/**
* SPDX-FileCopyrightText: 2019 Matthijs Tijink <matthijstijink@gmail.com>
2019-01-20 11:23:50 +00:00
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
2019-01-20 11:23:50 +00:00
*/
#include "connectionmultiplexer.h"
#include "core_debug.h"
#include "multiplexchannel.h"
2019-01-20 11:23:50 +00:00
#include "multiplexchannelstate.h"
#include <QBluetoothSocket>
#include <QBluetoothUuid>
#include <QIODevice>
#include <QtEndian>
2019-01-20 11:23:50 +00:00
/**
2019-12-22 08:15:58 +00:00
* The default channel uuid. This channel is opened implicitly (without communication).
2019-01-20 11:23:50 +00:00
*/
#define DEFAULT_CHANNEL_UUID "a0d0aaf4-1072-4d81-aa35-902a954b1266"
2019-01-20 11:23:50 +00:00
// Message type constants
2019-01-20 11:23:50 +00:00
constexpr char MESSAGE_PROTOCOL_VERSION = 0;
constexpr char MESSAGE_OPEN_CHANNEL = 1;
constexpr char MESSAGE_CLOSE_CHANNEL = 2;
constexpr char MESSAGE_READ = 3;
constexpr char MESSAGE_WRITE = 4;
ConnectionMultiplexer::ConnectionMultiplexer(QBluetoothSocket *socket, QObject *parent)
: QObject(parent)
, mSocket{socket}
, receivedProtocolVersion{false}
{
2019-01-20 11:23:50 +00:00
connect(mSocket, &QIODevice::readyRead, this, &ConnectionMultiplexer::readyRead);
connect(mSocket, &QIODevice::aboutToClose, this, &ConnectionMultiplexer::disconnected);
connect(mSocket, &QBluetoothSocket::disconnected, this, &ConnectionMultiplexer::disconnected);
connect(mSocket, &QIODevice::bytesWritten, this, &ConnectionMultiplexer::bytesWritten);
// Send the protocol version
QByteArray message(23, (char)0);
2019-01-20 11:23:50 +00:00
message[0] = MESSAGE_PROTOCOL_VERSION;
qToBigEndian<uint16_t>(4, &message.data()[1]);
// Leave UUID empty
// Only support version 1 (lowest supported = highest supported = 1)
2019-01-20 11:23:50 +00:00
qToBigEndian<uint16_t>(1, &message.data()[19]);
qToBigEndian<uint16_t>(1, &message.data()[21]);
to_write_bytes.append(message);
// Send the protocol version message (queued)
2019-01-20 11:23:50 +00:00
QMetaObject::invokeMethod(this, &ConnectionMultiplexer::bytesWritten, Qt::QueuedConnection);
// Always open the default channel
addChannel(QBluetoothUuid{QStringLiteral(DEFAULT_CHANNEL_UUID)});
2019-01-20 11:23:50 +00:00
// Immediately check if we can read stuff ("readyRead" may not be called in that case)
2019-01-20 11:23:50 +00:00
if (mSocket->bytesAvailable()) {
// But invoke it queued
2019-01-20 11:23:50 +00:00
QMetaObject::invokeMethod(this, &ConnectionMultiplexer::readyRead, Qt::QueuedConnection);
}
}
ConnectionMultiplexer::~ConnectionMultiplexer()
{
// Always make sure we close the connection
2019-01-20 11:23:50 +00:00
close();
}
void ConnectionMultiplexer::readyRead()
{
// Continue parsing messages until we need more data for another message
while (tryParseMessage()) { }
2019-01-20 11:23:50 +00:00
}
void ConnectionMultiplexer::disconnected()
{
// In case we get disconnected, remove all channels
2019-01-20 11:23:50 +00:00
for (auto &&channel : channels) {
disconnect(channel.data(), nullptr, this, nullptr);
channel->disconnected();
}
channels.clear();
for (auto channel : unrequested_channels) {
delete channel;
}
unrequested_channels.clear();
}
void ConnectionMultiplexer::close()
{
// In case we want to close the connection, remove all channels
2019-01-20 11:23:50 +00:00
for (auto &&channel : channels) {
disconnect(channel.data(), nullptr, this, nullptr);
channel->disconnected();
}
channels.clear();
for (auto channel : unrequested_channels) {
delete channel;
}
unrequested_channels.clear();
mSocket->close();
}
bool ConnectionMultiplexer::isOpen() const
{
2019-01-20 11:23:50 +00:00
return mSocket->isOpen();
}
bool ConnectionMultiplexer::tryParseMessage()
{
2019-01-20 11:23:50 +00:00
mSocket->startTransaction();
// The message header is 19 bytes long
2019-01-20 11:23:50 +00:00
QByteArray header = mSocket->read(19);
if (header.size() != 19) {
mSocket->rollbackTransaction();
return false;
}
/**
* Parse the header:
* - message type (1 byte)
* - message length (2 bytes, Big-Endian), excludes header size
* - channel uuid (16 bytes, Big-Endian)
*/
char message_type = header[0];
uint16_t message_length = qFromBigEndian<uint16_t>(&header.data()[1]);
quint128 message_uuid_raw;
for (int i = 0; i < 16; ++i)
message_uuid_raw.data[i] = header[3 + i];
2019-01-20 11:23:50 +00:00
QBluetoothUuid message_uuid = QBluetoothUuid(message_uuid_raw);
// Check if we have the full message including its data
2019-01-20 11:23:50 +00:00
QByteArray data = mSocket->read(message_length);
if (data.size() != message_length) {
mSocket->rollbackTransaction();
return false;
}
Q_ASSERT(receivedProtocolVersion || message_type == MESSAGE_PROTOCOL_VERSION);
// Parse the different message types
2019-01-20 11:23:50 +00:00
if (message_type == MESSAGE_OPEN_CHANNEL) {
// The other endpoint requested us to open a channel
2019-01-20 11:23:50 +00:00
Q_ASSERT(message_length == 0);
addChannel(message_uuid);
} else if (message_type == MESSAGE_READ) {
// The other endpoint has read some data and requests more data
2019-01-20 11:23:50 +00:00
Q_ASSERT(message_length == 2);
// Read the number of bytes requested (2 bytes, Big-Endian)
2019-01-20 11:23:50 +00:00
uint16_t additional_read = qFromBigEndian<uint16_t>(data.data());
Q_ASSERT(additional_read > 0);
// Check if we haven't closed the channel in the meanwhile
// (note: different from the user's endpoint of a closed channel, since we might have outstanding buffers)
2019-01-20 11:23:50 +00:00
auto iter = channels.find(message_uuid);
if (iter != channels.end() && (*iter)->connected) {
auto channel = *iter;
// We have "additional_read" more bytes we can safely write in this channel
2019-01-20 11:23:50 +00:00
channel->freeWriteAmount += additional_read;
mSocket->commitTransaction();
// We might still have some data in the write buffer
2019-01-20 11:23:50 +00:00
Q_EMIT channel->writeAvailable();
return true;
}
} else if (message_type == MESSAGE_WRITE) {
// The other endpoint has written data into a channel (because we requested it)
2019-01-20 11:23:50 +00:00
Q_ASSERT(message_length > 0);
// Check if we haven't closed the channel in the meanwhile
// (note: different from the user's endpoint of a closed channel, since we might have outstanding buffers)
2019-01-20 11:23:50 +00:00
auto iter = channels.find(message_uuid);
if (iter != channels.end() && (*iter)->connected) {
auto channel = *iter;
Q_ASSERT(channel->requestedReadAmount >= message_length);
// We received some data, so update the buffer and the amount of outstanding read requests
2019-01-20 11:23:50 +00:00
channel->requestedReadAmount -= message_length;
channel->read_buffer.append(std::move(data));
mSocket->commitTransaction();
// Indicate that the channel can read some bytes
2019-01-20 11:23:50 +00:00
Q_EMIT channel->readyRead();
return true;
}
} else if (message_type == MESSAGE_CLOSE_CHANNEL) {
// The other endpoint wants to close a channel
2019-01-20 11:23:50 +00:00
Q_ASSERT(message_length == 0);
// Check if we haven't closed the channel in the meanwhile
// (note: different from the user's endpoint of a closed channel, since we might have outstanding buffers)
2019-01-20 11:23:50 +00:00
auto iter = channels.find(message_uuid);
if (iter != channels.end()) {
auto channel = *iter;
// We don't want signals anymore, since the channel is closed
2019-01-20 11:23:50 +00:00
disconnect(channel.data(), nullptr, this, nullptr);
removeChannel(message_uuid);
}
} else if (message_type == MESSAGE_PROTOCOL_VERSION) {
// Checks for protocol compatibility
2019-01-20 11:23:50 +00:00
Q_ASSERT(message_length >= 4);
// Read the lowest & highest version supported (each 2 bytes, Big-Endian)
2019-01-20 11:23:50 +00:00
uint16_t lowest_version = qFromBigEndian<uint16_t>(&data.data()[0]);
uint16_t highest_version = qFromBigEndian<uint16_t>(&data.data()[2]);
Q_ASSERT(lowest_version == 1);
Q_ASSERT(highest_version >= 1);
receivedProtocolVersion = true;
} else {
// Other message types are not supported
2019-01-20 11:23:50 +00:00
Q_ASSERT(false);
}
mSocket->commitTransaction();
return true;
}
QBluetoothUuid ConnectionMultiplexer::newChannel()
{
// Create a random uuid
2019-01-20 11:23:50 +00:00
QBluetoothUuid new_id(QUuid::createUuid());
// Open the channel on the other endpoint
QByteArray message(3, (char)0);
2019-01-20 11:23:50 +00:00
message[0] = MESSAGE_OPEN_CHANNEL;
qToBigEndian<uint16_t>(0, &message.data()[1]);
quint128 new_id_raw = new_id.toUInt128();
message.append((const char *)new_id_raw.data, 16);
2019-01-20 11:23:50 +00:00
to_write_bytes.append(message);
// Add the channel ourselves
2019-01-20 11:23:50 +00:00
addChannel(new_id);
// Write the data
2019-01-20 11:23:50 +00:00
bytesWritten();
return new_id;
}
void ConnectionMultiplexer::addChannel(QBluetoothUuid new_id)
{
2019-01-20 11:23:50 +00:00
MultiplexChannelState *channelState = new MultiplexChannelState();
// Connect all channels queued, so that we have opportunities to combine read/write requests
2019-01-20 11:23:50 +00:00
Q_ASSERT(unrequested_channels.size() <= 20);
// Note that none of the channels knows its own uuid, so we have to add it ourselves
connect(
channelState,
&MultiplexChannelState::readAvailable,
this,
[new_id, this]() {
channelCanRead(new_id);
},
Qt::QueuedConnection);
connect(
channelState,
&MultiplexChannelState::writeAvailable,
this,
[new_id, this]() {
channelCanWrite(new_id);
},
Qt::QueuedConnection);
connect(
channelState,
&MultiplexChannelState::requestClose,
this,
[new_id, this]() {
closeChannel(new_id);
},
Qt::QueuedConnection);
2019-01-20 11:23:50 +00:00
auto channelStatePtr = QSharedPointer<MultiplexChannelState>{channelState};
channels[new_id] = channelStatePtr;
unrequested_channels[new_id] = new MultiplexChannel{channelStatePtr};
// Immediately ask for data in this channel
2019-01-20 11:23:50 +00:00
Q_EMIT channelStatePtr->readAvailable();
}
std::unique_ptr<MultiplexChannel> ConnectionMultiplexer::getChannel(QBluetoothUuid channelId)
{
2019-01-20 11:23:50 +00:00
auto iter = unrequested_channels.find(channelId);
if (iter == unrequested_channels.end()) {
return nullptr;
} else if (!(*iter)->isOpen()) {
// Delete the channel
2019-01-20 11:23:50 +00:00
delete *iter;
unrequested_channels.erase(iter);
// Don't return closed channels
2019-01-20 11:23:50 +00:00
return nullptr;
} else {
auto channel = *iter;
unrequested_channels.erase(iter);
return std::unique_ptr<MultiplexChannel>{channel};
}
}
std::unique_ptr<MultiplexChannel> ConnectionMultiplexer::getDefaultChannel()
{
return getChannel(QBluetoothUuid{QStringLiteral(DEFAULT_CHANNEL_UUID)});
2019-01-20 11:23:50 +00:00
}
void ConnectionMultiplexer::bytesWritten()
{
2019-01-20 11:23:50 +00:00
if (to_write_bytes.size() > 0) {
// If we have stuff to write, try to write it
2019-01-20 11:23:50 +00:00
auto num_written = mSocket->write(to_write_bytes);
if (num_written <= 0) {
// On error: disconnected will be called later
// On buffer full: will be retried later
2019-01-20 11:23:50 +00:00
return;
} else if (num_written == to_write_bytes.size()) {
to_write_bytes.clear();
} else {
to_write_bytes.remove(0, num_written);
return;
}
}
}
void ConnectionMultiplexer::channelCanRead(QBluetoothUuid channelId)
{
2019-01-20 11:23:50 +00:00
auto iter = channels.find(channelId);
if (iter == channels.end())
return;
2019-01-20 11:23:50 +00:00
auto channel = *iter;
// Check if we can request more data to read without overflowing the buffer
2019-01-20 11:23:50 +00:00
if (channel->read_buffer.size() + channel->requestedReadAmount < channel->BUFFER_SIZE) {
// Request the exact amount to fill up the buffer
2019-01-20 11:23:50 +00:00
auto read_amount = channel->BUFFER_SIZE - channel->requestedReadAmount - channel->read_buffer.size();
channel->requestedReadAmount += read_amount;
// Send a MESSAGE_READ request for more data
QByteArray message(3, (char)0);
2019-01-20 11:23:50 +00:00
message[0] = MESSAGE_READ;
qToBigEndian<uint16_t>(2, &message.data()[1]);
quint128 id_raw = channelId.toUInt128();
message.append((const char *)id_raw.data, 16);
2019-01-20 11:23:50 +00:00
message.append(2, 0);
qToBigEndian<int16_t>(read_amount, &message.data()[19]);
to_write_bytes.append(message);
// Try to send it immediately
2019-01-20 11:23:50 +00:00
bytesWritten();
}
}
void ConnectionMultiplexer::channelCanWrite(QBluetoothUuid channelId)
{
2019-01-20 11:23:50 +00:00
auto iter = channels.find(channelId);
if (iter == channels.end())
return;
2019-01-20 11:23:50 +00:00
auto channel = *iter;
// Check if we can freely send data and we actually have some data
2019-01-20 11:23:50 +00:00
if (channel->write_buffer.size() > 0 && channel->freeWriteAmount > 0) {
// Figure out how much we can send now
auto amount = qMin((int)channel->write_buffer.size(), channel->freeWriteAmount);
2019-01-20 11:23:50 +00:00
QByteArray data = channel->write_buffer.left(amount);
channel->write_buffer.remove(0, amount);
channel->freeWriteAmount -= amount;
// Send the data
QByteArray message(3, (char)0);
2019-01-20 11:23:50 +00:00
message[0] = MESSAGE_WRITE;
qToBigEndian<uint16_t>(amount, &message.data()[1]);
quint128 id_raw = channelId.toUInt128();
message.append((const char *)id_raw.data, 16);
2019-01-20 11:23:50 +00:00
message.append(data);
to_write_bytes.append(message);
// Try to send it immediately
2019-01-20 11:23:50 +00:00
bytesWritten();
// Let the channel's users know that some data has been written
2019-01-20 11:23:50 +00:00
Q_EMIT channel->bytesWritten(amount);
// If the user previously asked to close the channel and we finally managed to write the buffer, actually close it
2019-01-20 11:23:50 +00:00
if (channel->write_buffer.isEmpty() && channel->close_after_write) {
closeChannel(channelId);
}
}
}
void ConnectionMultiplexer::closeChannel(QBluetoothUuid channelId)
{
2019-01-20 11:23:50 +00:00
auto iter = channels.find(channelId);
if (iter == channels.end())
return;
2019-01-20 11:23:50 +00:00
auto channel = *iter;
// If the user wants to close a channel, then the user won't be reading from it anymore
2019-01-20 11:23:50 +00:00
channel->read_buffer.clear();
channel->close_after_write = true;
// If there's still stuff to write, don't close it just yet
if (!channel->write_buffer.isEmpty())
return;
2019-01-20 11:23:50 +00:00
channels.erase(iter);
channel->connected = false;
// Send the actual close channel message
QByteArray message(3, (char)0);
2019-01-20 11:23:50 +00:00
message[0] = MESSAGE_CLOSE_CHANNEL;
qToBigEndian<uint16_t>(0, &message.data()[1]);
quint128 id_raw = channelId.toUInt128();
message.append((const char *)id_raw.data, 16);
2019-01-20 11:23:50 +00:00
to_write_bytes.append(message);
// Try to send it immediately
2019-01-20 11:23:50 +00:00
bytesWritten();
}
void ConnectionMultiplexer::removeChannel(QBluetoothUuid channelId)
{
2019-01-20 11:23:50 +00:00
auto iter = channels.find(channelId);
if (iter == channels.end())
return;
2019-01-20 11:23:50 +00:00
auto channel = *iter;
// Remove the channel from the channel list
2019-01-20 11:23:50 +00:00
channels.erase(iter);
channel->connected = false;
Q_EMIT channel->disconnected();
}