File indexing completed on 2024-05-12 04:58:51
0001 /** 0002 * SPDX-FileCopyrightText: 2019 Matthijs Tijink <matthijstijink@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "connectionmultiplexer.h" 0008 0009 #include "core_debug.h" 0010 #include "multiplexchannel.h" 0011 #include "multiplexchannelstate.h" 0012 #include <QBluetoothSocket> 0013 #include <QBluetoothUuid> 0014 #include <QIODevice> 0015 #include <QtEndian> 0016 0017 /** 0018 * The default channel uuid. This channel is opened implicitly (without communication). 0019 */ 0020 #define DEFAULT_CHANNEL_UUID "a0d0aaf4-1072-4d81-aa35-902a954b1266" 0021 0022 // Message type constants 0023 constexpr char MESSAGE_PROTOCOL_VERSION = 0; 0024 constexpr char MESSAGE_OPEN_CHANNEL = 1; 0025 constexpr char MESSAGE_CLOSE_CHANNEL = 2; 0026 constexpr char MESSAGE_READ = 3; 0027 constexpr char MESSAGE_WRITE = 4; 0028 0029 ConnectionMultiplexer::ConnectionMultiplexer(QBluetoothSocket *socket, QObject *parent) 0030 : QObject(parent) 0031 , mSocket{socket} 0032 , receivedProtocolVersion{false} 0033 { 0034 connect(mSocket, &QIODevice::readyRead, this, &ConnectionMultiplexer::readyRead); 0035 connect(mSocket, &QIODevice::aboutToClose, this, &ConnectionMultiplexer::disconnected); 0036 connect(mSocket, &QBluetoothSocket::disconnected, this, &ConnectionMultiplexer::disconnected); 0037 connect(mSocket, &QIODevice::bytesWritten, this, &ConnectionMultiplexer::bytesWritten); 0038 0039 // Send the protocol version 0040 QByteArray message(23, (char)0); 0041 message[0] = MESSAGE_PROTOCOL_VERSION; 0042 qToBigEndian<uint16_t>(4, &message.data()[1]); 0043 // Leave UUID empty 0044 // Only support version 1 (lowest supported = highest supported = 1) 0045 qToBigEndian<uint16_t>(1, &message.data()[19]); 0046 qToBigEndian<uint16_t>(1, &message.data()[21]); 0047 0048 socket->write(message); 0049 0050 // Send the protocol version message (queued) 0051 QMetaObject::invokeMethod(this, &ConnectionMultiplexer::bytesWritten, Qt::QueuedConnection); 0052 0053 // Always open the default channel 0054 addChannel(QBluetoothUuid{QStringLiteral(DEFAULT_CHANNEL_UUID)}); 0055 0056 // Immediately check if we can read stuff ("readyRead" may not be called in that case) 0057 if (mSocket->bytesAvailable()) { 0058 // But invoke it queued 0059 QMetaObject::invokeMethod(this, &ConnectionMultiplexer::readyRead, Qt::QueuedConnection); 0060 } 0061 } 0062 0063 ConnectionMultiplexer::~ConnectionMultiplexer() 0064 { 0065 // Always make sure we close the connection 0066 close(); 0067 } 0068 0069 void ConnectionMultiplexer::readyRead() 0070 { 0071 // Continue parsing messages until we need more data for another message 0072 while (tryParseMessage()) { } 0073 } 0074 0075 void ConnectionMultiplexer::disconnected() 0076 { 0077 // In case we get disconnected, remove all channels 0078 for (auto &&channel : channels) { 0079 disconnect(channel.data(), nullptr, this, nullptr); 0080 channel->disconnected(); 0081 } 0082 channels.clear(); 0083 for (auto channel : unrequested_channels) { 0084 delete channel; 0085 } 0086 unrequested_channels.clear(); 0087 } 0088 0089 void ConnectionMultiplexer::close() 0090 { 0091 // In case we want to close the connection, remove all channels 0092 for (auto &&channel : channels) { 0093 disconnect(channel.data(), nullptr, this, nullptr); 0094 channel->disconnected(); 0095 } 0096 channels.clear(); 0097 for (auto channel : unrequested_channels) { 0098 delete channel; 0099 } 0100 unrequested_channels.clear(); 0101 0102 mSocket->close(); 0103 } 0104 0105 bool ConnectionMultiplexer::isOpen() const 0106 { 0107 return mSocket->isOpen(); 0108 } 0109 0110 bool ConnectionMultiplexer::tryParseMessage() 0111 { 0112 mSocket->startTransaction(); 0113 0114 // The message header is 19 bytes long 0115 QByteArray header = mSocket->read(19); 0116 if (header.size() != 19) { 0117 mSocket->rollbackTransaction(); 0118 return false; 0119 } 0120 0121 /** 0122 * Parse the header: 0123 * - message type (1 byte) 0124 * - message length (2 bytes, Big-Endian), excludes header size 0125 * - channel uuid (16 bytes, Big-Endian) 0126 */ 0127 char message_type = header[0]; 0128 uint16_t message_length = qFromBigEndian<uint16_t>(&header.data()[1]); 0129 0130 quint128 message_uuid_raw; 0131 #ifndef QT_SUPPORTS_INT128 0132 for (int i = 0; i < 16; ++i) { 0133 message_uuid_raw.data[i] = header[3 + i]; 0134 } 0135 #else 0136 message_uuid_raw = qFromBigEndian<quint128>(&header.data()[3]); 0137 #endif 0138 QBluetoothUuid message_uuid = QBluetoothUuid(message_uuid_raw); 0139 0140 // Check if we have the full message including its data 0141 QByteArray data = mSocket->read(message_length); 0142 if (data.size() != message_length) { 0143 mSocket->rollbackTransaction(); 0144 return false; 0145 } 0146 0147 Q_ASSERT(receivedProtocolVersion || message_type == MESSAGE_PROTOCOL_VERSION); 0148 0149 // Parse the different message types 0150 if (message_type == MESSAGE_OPEN_CHANNEL) { 0151 // The other endpoint requested us to open a channel 0152 Q_ASSERT(message_length == 0); 0153 0154 addChannel(message_uuid); 0155 } else if (message_type == MESSAGE_READ) { 0156 // The other endpoint has read some data and requests more data 0157 Q_ASSERT(message_length == 2); 0158 // Read the number of bytes requested (2 bytes, Big-Endian) 0159 uint16_t additional_read = qFromBigEndian<uint16_t>(data.data()); 0160 Q_ASSERT(additional_read > 0); 0161 0162 // Check if we haven't closed the channel in the meanwhile 0163 // (note: different from the user's endpoint of a closed channel, since we might have outstanding buffers) 0164 auto iter = channels.find(message_uuid); 0165 if (iter != channels.end() && (*iter)->connected) { 0166 auto channel = *iter; 0167 0168 // We have "additional_read" more bytes we can safely write in this channel 0169 channel->freeWriteAmount += additional_read; 0170 mSocket->commitTransaction(); 0171 // We might still have some data in the write buffer 0172 Q_EMIT channel->writeAvailable(); 0173 return true; 0174 } 0175 } else if (message_type == MESSAGE_WRITE) { 0176 // The other endpoint has written data into a channel (because we requested it) 0177 Q_ASSERT(message_length > 0); 0178 0179 // Check if we haven't closed the channel in the meanwhile 0180 // (note: different from the user's endpoint of a closed channel, since we might have outstanding buffers) 0181 auto iter = channels.find(message_uuid); 0182 if (iter != channels.end() && (*iter)->connected) { 0183 auto channel = *iter; 0184 0185 Q_ASSERT(channel->requestedReadAmount >= message_length); 0186 0187 // We received some data, so update the buffer and the amount of outstanding read requests 0188 channel->requestedReadAmount -= message_length; 0189 channel->read_buffer.append(std::move(data)); 0190 0191 mSocket->commitTransaction(); 0192 // Indicate that the channel can read some bytes 0193 Q_EMIT channel->readyRead(); 0194 return true; 0195 } 0196 } else if (message_type == MESSAGE_CLOSE_CHANNEL) { 0197 // The other endpoint wants to close a channel 0198 Q_ASSERT(message_length == 0); 0199 0200 // Check if we haven't closed the channel in the meanwhile 0201 // (note: different from the user's endpoint of a closed channel, since we might have outstanding buffers) 0202 auto iter = channels.find(message_uuid); 0203 if (iter != channels.end()) { 0204 auto channel = *iter; 0205 0206 // We don't want signals anymore, since the channel is closed 0207 disconnect(channel.data(), nullptr, this, nullptr); 0208 removeChannel(message_uuid); 0209 } 0210 } else if (message_type == MESSAGE_PROTOCOL_VERSION) { 0211 // Checks for protocol compatibility 0212 Q_ASSERT(message_length >= 4); 0213 // Read the lowest & highest version supported (each 2 bytes, Big-Endian) 0214 uint16_t lowest_version = qFromBigEndian<uint16_t>(&data.data()[0]); 0215 uint16_t highest_version = qFromBigEndian<uint16_t>(&data.data()[2]); 0216 0217 Q_ASSERT(lowest_version == 1); 0218 Q_ASSERT(highest_version >= 1); 0219 receivedProtocolVersion = true; 0220 } else { 0221 // Other message types are not supported 0222 Q_ASSERT(false); 0223 } 0224 0225 mSocket->commitTransaction(); 0226 return true; 0227 } 0228 0229 QBluetoothUuid ConnectionMultiplexer::newChannel() 0230 { 0231 // Create a random uuid 0232 QBluetoothUuid new_id(QUuid::createUuid()); 0233 0234 // Open the channel on the other endpoint 0235 QByteArray message(3, (char)0); 0236 message[0] = MESSAGE_OPEN_CHANNEL; 0237 qToBigEndian<uint16_t>(0, &message.data()[1]); 0238 0239 #if QT_VERSION_MAJOR == 5 0240 quint128 id_raw = new_id.toUInt128(); 0241 message.append((const char *)id_raw.data, 16); 0242 #else 0243 const auto channelBytes = new_id.toByteArray(); 0244 message.append(channelBytes.constData(), 16); 0245 #endif 0246 to_write_bytes.append(message); 0247 0248 // Add the channel ourselves 0249 addChannel(new_id); 0250 // Write the data 0251 bytesWritten(); 0252 return new_id; 0253 } 0254 0255 void ConnectionMultiplexer::addChannel(QBluetoothUuid new_id) 0256 { 0257 MultiplexChannelState *channelState = new MultiplexChannelState(); 0258 // Connect all channels queued, so that we have opportunities to combine read/write requests 0259 0260 Q_ASSERT(unrequested_channels.size() <= 20); 0261 0262 // Note that none of the channels knows its own uuid, so we have to add it ourselves 0263 connect( 0264 channelState, 0265 &MultiplexChannelState::readAvailable, 0266 this, 0267 [new_id, this]() { 0268 channelCanRead(new_id); 0269 }, 0270 Qt::QueuedConnection); 0271 connect( 0272 channelState, 0273 &MultiplexChannelState::writeAvailable, 0274 this, 0275 [new_id, this]() { 0276 channelCanWrite(new_id); 0277 }, 0278 Qt::QueuedConnection); 0279 connect( 0280 channelState, 0281 &MultiplexChannelState::requestClose, 0282 this, 0283 [new_id, this]() { 0284 closeChannel(new_id); 0285 }, 0286 Qt::QueuedConnection); 0287 0288 auto channelStatePtr = QSharedPointer<MultiplexChannelState>{channelState}; 0289 channels[new_id] = channelStatePtr; 0290 unrequested_channels[new_id] = new MultiplexChannel{channelStatePtr}; 0291 // Immediately ask for data in this channel 0292 Q_EMIT channelStatePtr->readAvailable(); 0293 } 0294 0295 std::unique_ptr<MultiplexChannel> ConnectionMultiplexer::getChannel(QBluetoothUuid channelId) 0296 { 0297 auto iter = unrequested_channels.find(channelId); 0298 if (iter == unrequested_channels.end()) { 0299 return nullptr; 0300 } else if (!(*iter)->isOpen()) { 0301 // Delete the channel 0302 delete *iter; 0303 unrequested_channels.erase(iter); 0304 // Don't return closed channels 0305 return nullptr; 0306 } else { 0307 auto channel = *iter; 0308 unrequested_channels.erase(iter); 0309 return std::unique_ptr<MultiplexChannel>{channel}; 0310 } 0311 } 0312 0313 std::unique_ptr<MultiplexChannel> ConnectionMultiplexer::getDefaultChannel() 0314 { 0315 return getChannel(QBluetoothUuid{QStringLiteral(DEFAULT_CHANNEL_UUID)}); 0316 } 0317 0318 void ConnectionMultiplexer::bytesWritten() 0319 { 0320 if (to_write_bytes.size() > 0) { 0321 // If we have stuff to write, try to write it 0322 auto num_written = mSocket->write(to_write_bytes); 0323 if (num_written <= 0) { 0324 // On error: disconnected will be called later 0325 // On buffer full: will be retried later 0326 return; 0327 } else if (num_written == to_write_bytes.size()) { 0328 to_write_bytes.clear(); 0329 } else { 0330 to_write_bytes.remove(0, num_written); 0331 return; 0332 } 0333 } 0334 } 0335 0336 void ConnectionMultiplexer::channelCanRead(QBluetoothUuid channelId) 0337 { 0338 auto iter = channels.find(channelId); 0339 if (iter == channels.end()) 0340 return; 0341 auto channel = *iter; 0342 0343 // Check if we can request more data to read without overflowing the buffer 0344 if (channel->read_buffer.size() + channel->requestedReadAmount < channel->BUFFER_SIZE) { 0345 // Request the exact amount to fill up the buffer 0346 auto read_amount = channel->BUFFER_SIZE - channel->requestedReadAmount - channel->read_buffer.size(); 0347 channel->requestedReadAmount += read_amount; 0348 0349 // Send a MESSAGE_READ request for more data 0350 QByteArray message(3, (char)0); 0351 message[0] = MESSAGE_READ; 0352 qToBigEndian<uint16_t>(2, &message.data()[1]); 0353 #if QT_VERSION_MAJOR == 5 0354 quint128 id_raw = channelId.toUInt128(); 0355 message.append((const char *)id_raw.data, 16); 0356 #else 0357 const auto channelBytes = channelId.toByteArray(); 0358 message.append(channelBytes.constData(), 16); 0359 #endif 0360 message.append(2, 0); 0361 qToBigEndian<int16_t>(read_amount, &message.data()[19]); 0362 to_write_bytes.append(message); 0363 // Try to send it immediately 0364 bytesWritten(); 0365 } 0366 } 0367 0368 void ConnectionMultiplexer::channelCanWrite(QBluetoothUuid channelId) 0369 { 0370 auto iter = channels.find(channelId); 0371 if (iter == channels.end()) 0372 return; 0373 auto channel = *iter; 0374 0375 // Check if we can freely send data and we actually have some data 0376 if (channel->write_buffer.size() > 0 && channel->freeWriteAmount > 0) { 0377 // Figure out how much we can send now 0378 auto amount = qMin((int)channel->write_buffer.size(), channel->freeWriteAmount); 0379 QByteArray data = channel->write_buffer.left(amount); 0380 channel->write_buffer.remove(0, amount); 0381 channel->freeWriteAmount -= amount; 0382 0383 // Send the data 0384 QByteArray message(3, (char)0); 0385 message[0] = MESSAGE_WRITE; 0386 qToBigEndian<uint16_t>(amount, &message.data()[1]); 0387 0388 #if QT_VERSION_MAJOR == 5 0389 quint128 id_raw = channelId.toUInt128(); 0390 message.append((const char *)id_raw.data, 16); 0391 #else 0392 const auto channelBytes = channelId.toByteArray(); 0393 message.append(channelBytes.constData(), 16); 0394 #endif 0395 0396 message.append(data); 0397 to_write_bytes.append(message); 0398 // Try to send it immediately 0399 bytesWritten(); 0400 // Let the channel's users know that some data has been written 0401 Q_EMIT channel->bytesWritten(amount); 0402 0403 // If the user previously asked to close the channel and we finally managed to write the buffer, actually close it 0404 if (channel->write_buffer.isEmpty() && channel->close_after_write) { 0405 closeChannel(channelId); 0406 } 0407 } 0408 } 0409 0410 void ConnectionMultiplexer::closeChannel(QBluetoothUuid channelId) 0411 { 0412 auto iter = channels.find(channelId); 0413 if (iter == channels.end()) 0414 return; 0415 auto channel = *iter; 0416 0417 // If the user wants to close a channel, then the user won't be reading from it anymore 0418 channel->read_buffer.clear(); 0419 channel->close_after_write = true; 0420 0421 // If there's still stuff to write, don't close it just yet 0422 if (!channel->write_buffer.isEmpty()) 0423 return; 0424 channels.erase(iter); 0425 channel->connected = false; 0426 0427 // Send the actual close channel message 0428 QByteArray message(3, (char)0); 0429 message[0] = MESSAGE_CLOSE_CHANNEL; 0430 qToBigEndian<uint16_t>(0, &message.data()[1]); 0431 0432 #if QT_VERSION_MAJOR == 5 0433 quint128 id_raw = channelId.toUInt128(); 0434 message.append((const char *)id_raw.data, 16); 0435 #else 0436 const auto channelBytes = channelId.toByteArray(); 0437 message.append(channelBytes.constData(), 16); 0438 #endif 0439 to_write_bytes.append(message); 0440 // Try to send it immediately 0441 bytesWritten(); 0442 } 0443 0444 void ConnectionMultiplexer::removeChannel(QBluetoothUuid channelId) 0445 { 0446 auto iter = channels.find(channelId); 0447 if (iter == channels.end()) 0448 return; 0449 auto channel = *iter; 0450 0451 // Remove the channel from the channel list 0452 channels.erase(iter); 0453 channel->connected = false; 0454 0455 Q_EMIT channel->disconnected(); 0456 } 0457 0458 #include "moc_connectionmultiplexer.cpp"