Add connection multiplexer

This commit is contained in:
Matthijs Tijink 2019-01-20 12:23:50 +01:00
parent 5e827917a5
commit cda1280456
8 changed files with 854 additions and 3 deletions

View file

@ -2,6 +2,9 @@
set(backends_kdeconnect_SRCS
${backends_kdeconnect_SRCS}
backends/bluetooth/multiplexchannel.cpp
backends/bluetooth/multiplexchannelstate.cpp
backends/bluetooth/connectionmultiplexer.cpp
backends/bluetooth/bluetoothlinkprovider.cpp
backends/bluetooth/bluetoothdevicelink.cpp
backends/bluetooth/bluetoothpairinghandler.cpp

View file

@ -43,11 +43,11 @@ Where the message type can be one of the following.
This message should be the first message send, and never at a later time. Its format is as follows:
```
| MESSAGE_PROTOCOL_VERSION header | Lowest version supported | Highest version supported |
| 19 bytes (UUID ignored) | 2 bytes (Big-Endian) | 2 bytes (Big-Endian) |
| MESSAGE_PROTOCOL_VERSION header | Lowest version supported | Highest version supported | Other data |
| 19 bytes (UUID ignored) | 2 bytes (Big-Endian) | 2 bytes (Big-Endian) | Remaining data bytes |
```
This message should be the first message to send. Use the maximum version supported by both endpoints (if any), or otherwise close the connection.
This message should be the first message to send. Use the maximum version supported by both endpoints (if any), or otherwise close the connection. The other data field is not used (and should be empty for protocol version 1), but it implies that message lengths of more than 4 need to be supported for future compatability.
Currently, no client will send this message with a version other than 1, but you *must* accept and check it, for forward compatibility.

View file

@ -0,0 +1,401 @@
/**
* Copyright 2019 Matthijs Tijink <matthijstijink@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "connectionmultiplexer.h"
#include <QtEndian>
#include <QIODevice>
#include <QBluetoothUuid>
#include <QBluetoothSocket>
#include "multiplexchannel.h"
#include "core_debug.h"
#include "multiplexchannelstate.h"
/**
* The default channel uuid. This channel is opened implicitely (without communication).
*/
constexpr const char DEFAULT_CHANNEL_UUID[] = "a0d0aaf4-1072-4d81-aa35-902a954b1266";
//Message type constants
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} {
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);
message[0] = MESSAGE_PROTOCOL_VERSION;
qToBigEndian<uint16_t>(4, &message.data()[1]);
//Leave UUID empty
//Only support version 1 (lowest supported = highest supported = 1)
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)
QMetaObject::invokeMethod(this, &ConnectionMultiplexer::bytesWritten, Qt::QueuedConnection);
//Always open the default channel
addChannel(QBluetoothUuid{QString{DEFAULT_CHANNEL_UUID}});
//Immediately check if we can read stuff ("readyRead" may not be called in that case)
if (mSocket->bytesAvailable()) {
//But invoke it queued
QMetaObject::invokeMethod(this, &ConnectionMultiplexer::readyRead, Qt::QueuedConnection);
}
}
ConnectionMultiplexer::~ConnectionMultiplexer() {
//Always make sure we close the connection
close();
}
void ConnectionMultiplexer::readyRead() {
//Continue parsing messages until we need more data for another message
while (tryParseMessage()) {}
}
void ConnectionMultiplexer::disconnected() {
//In case we get disconnected, remove all channels
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
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 {
return mSocket->isOpen();
}
bool ConnectionMultiplexer::tryParseMessage() {
mSocket->startTransaction();
//The message header is 19 bytes long
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];
QBluetoothUuid message_uuid = QBluetoothUuid(message_uuid_raw);
//Check if we have the full message including its data
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
if (message_type == MESSAGE_OPEN_CHANNEL) {
//The other endpoint requested us to open a channel
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
Q_ASSERT(message_length == 2);
//Read the number of bytes requested (2 bytes, Big-Endian)
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)
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
channel->freeWriteAmount += additional_read;
mSocket->commitTransaction();
//We might still have some data in the write buffer
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)
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)
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
channel->requestedReadAmount -= message_length;
channel->read_buffer.append(std::move(data));
mSocket->commitTransaction();
//Indicate that the channel can read some bytes
Q_EMIT channel->readyRead();
return true;
}
} else if (message_type == MESSAGE_CLOSE_CHANNEL) {
//The other endpoint wants to close a channel
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)
auto iter = channels.find(message_uuid);
if (iter != channels.end()) {
auto channel = *iter;
//We don't want signals anymore, since the channel is closed
disconnect(channel.data(), nullptr, this, nullptr);
removeChannel(message_uuid);
}
} else if (message_type == MESSAGE_PROTOCOL_VERSION) {
//Checks for protocol compatibility
Q_ASSERT(message_length >= 4);
//Read the lowest & highest version supported (each 2 bytes, Big-Endian)
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
Q_ASSERT(false);
}
mSocket->commitTransaction();
return true;
}
QBluetoothUuid ConnectionMultiplexer::newChannel() {
//Create a random uuid
QBluetoothUuid new_id(QUuid::createUuid());
//Open the channel on the other endpoint
QByteArray message(3, (char) 0);
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);
to_write_bytes.append(message);
//Add the channel ourselves
addChannel(new_id);
//Write the data
bytesWritten();
return new_id;
}
void ConnectionMultiplexer::addChannel(QBluetoothUuid new_id) {
MultiplexChannelState *channelState = new MultiplexChannelState();
//Connect all channels queued, so that we have opportunities to combine read/write requests
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);
auto channelStatePtr = QSharedPointer<MultiplexChannelState>{channelState};
channels[new_id] = channelStatePtr;
unrequested_channels[new_id] = new MultiplexChannel{channelStatePtr};
//Immediately ask for data in this channel
Q_EMIT channelStatePtr->readAvailable();
}
std::unique_ptr<MultiplexChannel> ConnectionMultiplexer::getChannel(QBluetoothUuid channelId) {
auto iter = unrequested_channels.find(channelId);
if (iter == unrequested_channels.end()) {
return nullptr;
} else if (!(*iter)->isOpen()) {
//Delete the channel
delete *iter;
unrequested_channels.erase(iter);
//Don't return closed channels
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{QString{DEFAULT_CHANNEL_UUID}});
}
void ConnectionMultiplexer::bytesWritten() {
if (to_write_bytes.size() > 0) {
//If we have stuff to write, try to write it
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
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) {
auto iter = channels.find(channelId);
if (iter == channels.end()) return;
auto channel = *iter;
//Check if we can request more data to read without overflowing the buffer
if (channel->read_buffer.size() + channel->requestedReadAmount < channel->BUFFER_SIZE) {
//Request the exact amount to fill up the buffer
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);
message[0] = MESSAGE_READ;
qToBigEndian<uint16_t>(2, &message.data()[1]);
quint128 id_raw = channelId.toUInt128();
message.append((const char*) id_raw.data, 16);
message.append(2, 0);
qToBigEndian<int16_t>(read_amount, &message.data()[19]);
to_write_bytes.append(message);
//Try to send it immediately
bytesWritten();
}
}
void ConnectionMultiplexer::channelCanWrite(QBluetoothUuid channelId) {
auto iter = channels.find(channelId);
if (iter == channels.end()) return;
auto channel = *iter;
//Check if we can freely send data and we actually have some data
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);
QByteArray data = channel->write_buffer.left(amount);
channel->write_buffer.remove(0, amount);
channel->freeWriteAmount -= amount;
//Send the data
QByteArray message(3, (char) 0);
message[0] = MESSAGE_WRITE;
qToBigEndian<uint16_t>(amount, &message.data()[1]);
quint128 id_raw = channelId.toUInt128();
message.append((const char*) id_raw.data, 16);
message.append(data);
to_write_bytes.append(message);
//Try to send it immediately
bytesWritten();
//Let the channel's users know that some data has been written
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
if (channel->write_buffer.isEmpty() && channel->close_after_write) {
closeChannel(channelId);
}
}
}
void ConnectionMultiplexer::closeChannel(QBluetoothUuid channelId) {
auto iter = channels.find(channelId);
if (iter == channels.end()) return;
auto channel = *iter;
//If the user wants to close a channel, then the user won't be reading from it anymore
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;
channels.erase(iter);
channel->connected = false;
//Send the actual close channel message
QByteArray message(3, (char) 0);
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);
to_write_bytes.append(message);
//Try to send it immediately
bytesWritten();
}
void ConnectionMultiplexer::removeChannel(QBluetoothUuid channelId) {
auto iter = channels.find(channelId);
if (iter == channels.end()) return;
auto channel = *iter;
//Remove the channel from the channel list
channels.erase(iter);
channel->connected = false;
Q_EMIT channel->disconnected();
}

View file

@ -0,0 +1,149 @@
/**
* Copyright 2019 Matthijs Tijink <matthijstijink@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef CONNECTIONMULTIPLEXER_H
#define CONNECTIONMULTIPLEXER_H
#include <QObject>
#include <QByteArray>
#include <QHash>
#include <QSharedPointer>
#include <memory>
class QBluetoothUuid;
class MultiplexChannel;
class MultiplexChannelState;
class QBluetoothSocket;
/**
* An utility class to split a single (bluetooth) connection to multiple independent channels.
* By default (and without needing any communication with the other endpoint), a single default channel is open.
*
* Destroying/closing this object will automatically close all channels.
*/
class ConnectionMultiplexer : public QObject {
Q_OBJECT
public:
ConnectionMultiplexer(QBluetoothSocket *socket, QObject *parent = nullptr);
~ConnectionMultiplexer();
/**
* Open a new channel within this connection.
*
* @return The uuid to refer to this channel.
* @see getChannel()
*/
QBluetoothUuid newChannel();
/**
* Get the channel device for the specified channel uuid.
* If the channel does not exist, this will return a null pointer.
*
* A channel is guaranteed to exist until the first call to get it.
* @param channelId The channel uuid
* @return A shared pointer to the channel object
* @see getDefaultChannel()
*/
std::unique_ptr<MultiplexChannel> getChannel(QBluetoothUuid channelId);
/**
* Get the default channel.
*
* @see getChannel()
*/
std::unique_ptr<MultiplexChannel> getDefaultChannel();
/**
* Close all channels and the underlying connection.
*/
void close();
/**
* Check if the underlying connection is still open.
* @return True if the connection is open
* @see close()
*/
bool isOpen() const;
private:
/**
* The underlying connection
*/
QBluetoothSocket *mSocket;
/**
* The buffer of to-be-written bytes
*/
QByteArray to_write_bytes;
/**
* The channels not requested by the user yet
*/
QHash<QBluetoothUuid, MultiplexChannel*> unrequested_channels;
/**
* All channels currently open
*/
QHash<QBluetoothUuid, QSharedPointer<MultiplexChannelState>> channels;
/**
* True once the other side has sent its protocol version
*/
bool receivedProtocolVersion;
/**
* Slot for connection reading
*/
void readyRead();
/**
* Slot for disconnection
*/
void disconnected();
/**
* Slot for progress in writing data/new data available to be written
*/
void bytesWritten();
/**
* Tries to parse a single connection message.
*
* @return True if a message was parsed succesfully.
*/
bool tryParseMessage();
/**
* Add a new channel. Assumes that the communication about this channel is done
* (i.e. the other endpoint also knows this channel exists).
*
* @param new_id The channel uuid
*/
void addChannel(QBluetoothUuid new_id);
/**
* Slot for closing a channel
*/
void closeChannel(QBluetoothUuid channelId);
/**
* Slot for writing a channel's data to the other endpoint
*/
void channelCanWrite(QBluetoothUuid channelId);
/**
* Slot for indicating that a channel can receive more data
*/
void channelCanRead(QBluetoothUuid channelId);
/**
* Slot for removing a channel from tracking
*/
void removeChannel(QBluetoothUuid channelId);
};
#endif

View file

@ -0,0 +1,103 @@
/**
* Copyright 2019 Matthijs Tijink <matthijstijink@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "multiplexchannel.h"
#include "multiplexchannelstate.h"
#include "core_debug.h"
MultiplexChannel::MultiplexChannel(QSharedPointer<MultiplexChannelState> state) : state{state} {
QIODevice::open(QIODevice::ReadWrite);
connect(this, &QIODevice::aboutToClose, state.data(), &MultiplexChannelState::requestClose);
connect(state.data(), &MultiplexChannelState::readyRead, this, &QIODevice::readyRead);
connect(state.data(), &MultiplexChannelState::bytesWritten, this, &QIODevice::bytesWritten);
connect(state.data(), &MultiplexChannelState::disconnected, this, &MultiplexChannel::disconnect);
}
MultiplexChannel::~MultiplexChannel() {}
bool MultiplexChannel::atEnd() const {
return !isOpen() || (!state->connected && state->read_buffer.isEmpty());
}
void MultiplexChannel::disconnect() {
state->connected = false;
setOpenMode(QIODevice::ReadOnly);
Q_EMIT state->readyRead();
Q_EMIT state->requestClose();
if (state->read_buffer.isEmpty()) {
close();
}
}
qint64 MultiplexChannel::bytesAvailable() const {
return state->read_buffer.size() + QIODevice::bytesAvailable();
}
qint64 MultiplexChannel::bytesToWrite() const {
return state->write_buffer.size() + QIODevice::bytesToWrite();
}
qint64 MultiplexChannel::readData(char* data, qint64 maxlen) {
if (maxlen <= state->read_buffer.size()) {
for (int i = 0; i < maxlen; ++i) {
data[i] = state->read_buffer[i];
}
state->read_buffer.remove(0, maxlen);
Q_EMIT state->readAvailable();
if (!state->connected && state->read_buffer.isEmpty()) {
close();
}
return maxlen;
} else if (state->read_buffer.size() > 0) {
auto num_to_read = state->read_buffer.size();
for (int i = 0; i < num_to_read; ++i) {
data[i] = state->read_buffer[i];
}
state->read_buffer.remove(0, num_to_read);
Q_EMIT state->readAvailable();
if (!state->connected && state->read_buffer.isEmpty()) {
close();
}
return num_to_read;
} else if (isOpen() && state->connected) {
if (state->requestedReadAmount < BUFFER_SIZE) {
Q_EMIT state->readAvailable();
}
return 0;
} else {
close();
return -1;
}
}
qint64 MultiplexChannel::writeData(const char* data, qint64 len) {
state->write_buffer.append(data, len);
Q_EMIT state->writeAvailable();
return len;
}
bool MultiplexChannel::canReadLine() const {
return isReadable() && (QIODevice::canReadLine() || state->read_buffer.contains('\n'));
}
bool MultiplexChannel::isSequential() const {
return true;
}

View file

@ -0,0 +1,71 @@
/**
* Copyright 2019 Matthijs Tijink <matthijstijink@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef MULTIPLEXCHANNEL_H
#define MULTIPLEXCHANNEL_H
#include <QIODevice>
#include <QSharedPointer>
class ConnectionMultiplexer;
class MultiplexChannelState;
/**
* Represents a single channel in a multiplexed connection
*
* @see ConnectionMultiplexer
* @see ConnectionMultiplexer::getChannel
*/
class MultiplexChannel : public QIODevice {
Q_OBJECT
private:
/**
* You cannot construct a MultiplexChannel yourself, use the ConnectionMultiplexer
*/
MultiplexChannel(QSharedPointer<MultiplexChannelState> state);
public:
~MultiplexChannel();
constexpr static int BUFFER_SIZE = 4096;
bool canReadLine() const override;
bool atEnd() const override;
qint64 bytesAvailable() const override;
qint64 bytesToWrite() const override;
bool isSequential() const override;
protected:
qint64 readData(char * data, qint64 maxlen) override;
qint64 writeData(const char * data, qint64 len) override;
private:
QSharedPointer<MultiplexChannelState> state;
/**
* Disconnects the channel
*/
void disconnect();
friend class ConnectionMultiplexer;
};
#endif

View file

@ -0,0 +1,23 @@
/**
* Copyright 2019 Matthijs Tijink <matthijstijink@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "multiplexchannelstate.h"
MultiplexChannelState::MultiplexChannelState() : requestedReadAmount{0}, freeWriteAmount{0}, connected{true}, close_after_write{false} {}

View file

@ -0,0 +1,101 @@
/**
* Copyright 2019 Matthijs Tijink <matthijstijink@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef MULTIPLEXCHANNELSTATE_H
#define MULTIPLEXCHANNELSTATE_H
#include <QObject>
#include <QByteArray>
class ConnectionMultiplexer;
class MultiplexChannel;
/**
* Represents a single channel in a multiplexed connection
*
* @internal
* @see ConnectionMultiplexer
*/
class MultiplexChannelState : public QObject {
Q_OBJECT
private:
MultiplexChannelState();
public:
~MultiplexChannelState() = default;
constexpr static int BUFFER_SIZE = 4096;
private:
/**
* The read buffer (already read from underlying connection but not read by the user of the channel)
*/
QByteArray read_buffer;
/**
* The write buffer (already written by the user of the channel, but not to the underlying connection yet)
*/
QByteArray write_buffer;
/**
* The amount of bytes requested to the other endpoint
*/
int requestedReadAmount;
/**
* The amount of bytes the other endpoint requested
*/
int freeWriteAmount;
/**
* True if the channel is still connected in the underlying connection
*/
bool connected;
/**
* True if the channel needs to be closed after writing the buffer
*/
bool close_after_write;
friend class ConnectionMultiplexer;
friend class MultiplexChannel;
Q_SIGNALS:
/**
* Emitted if there are new bytes available to be written
*/
void writeAvailable();
/**
* Emitted if the channel has buffer room for more bytes to be read
*/
void readAvailable();
/**
* Emitted if the channel bytes ready for reading
*/
void readyRead();
/**
* Emitted if some bytes of the channel have been written
*/
void bytesWritten(qint64 amount);
/**
* Emitted if the user requested to close the channel
*/
void requestClose();
/**
* Only fired on disconnections
*/
void disconnected();
};
#endif