#include #include #include #include #include "toqconnection.h" static const int HEADER_LENGTH = 10; ToqConnection::ToqConnection(QObject *parent) : QObject(parent), _socket(0), _reconnectTimer(new QTimer(this)), _lastTransactionId(0) { connect(_reconnectTimer, &QTimer::timeout, this, &ToqConnection::tryConnect); _reconnectTimer->setSingleShot(true); _reconnectTimer->setInterval(1000); } QString ToqConnection::nameOfEndpoint(Endpoint ep) { int index = staticMetaObject.indexOfEnumerator("CoreEndpoints"); QMetaEnum epEnum = staticMetaObject.enumerator(index); const char * ret = epEnum.valueToKey(ep); if (ret) { return QString::fromLatin1(ret); } else { return QString::number(ep); } } quint32 ToqConnection::checksum(const QByteArray &data) { uLong crc = crc32(0L, Z_NULL, 0); crc = crc32(crc, reinterpret_cast(data.constData()), data.size()); return crc; } quint32 ToqConnection::checksum(QIODevice *dev) { uLong crc = crc32(0L, Z_NULL, 0); char buffer[4 * 1024]; qint64 read; while ((read = dev->read(buffer, sizeof(buffer))) > 0) { crc = crc32(crc, reinterpret_cast(&buffer[0]), read); } return crc; } void ToqConnection::setAddress(const QBluetoothAddress &address) { qDebug() << address.toString() << _address.toString(); if (address != _address) { _address = address; if (isConnected()) { _socket->disconnectFromService(); } else { _reconnectTimer->start(); } } } quint16 ToqConnection::newTransactionId() { if (_lastTransactionId >= 0xFFFA) { // The last transaction ids (as well as 0) seem to be reserved // Avoid using them _lastTransactionId = 0; } return ++_lastTransactionId; } void ToqConnection::sendMessage(const Message &msg) { if (_socket) { _socket->write(packMessage(msg)); } else { qWarning() << "Discarding message because connection is broken"; } } ToqConnection::Message ToqConnection::unpackMessage(const QByteArray &data) { Message msg; Q_ASSERT(data.length() >= HEADER_LENGTH); const uchar *header = reinterpret_cast(data.constData()); quint16 message_length = qFromBigEndian(&header[2]); Q_ASSERT(data.length() == message_length + HEADER_LENGTH - 4); msg.source = header[0]; msg.destination = header[1]; msg.transactionId = qFromBigEndian(&header[4]); msg.type = qFromBigEndian(&header[6]); if (!data.isEmpty()) { QJsonParseError error; msg.payload = QJsonDocument::fromJson(data.mid(HEADER_LENGTH), &error); if (error.error) { qWarning() << "Failure while parsing message JSON payload: " << error.errorString(); } } return msg; } QByteArray ToqConnection::packMessage(const Message &msg) { QByteArray payload = msg.payload.toJson(QJsonDocument::Compact); uchar header[HEADER_LENGTH]; header[0] = msg.source; header[1] = msg.destination; qToBigEndian(payload.length() + 4, &header[2]); qToBigEndian(msg.transactionId, &header[4]); qToBigEndian(msg.type, &header[6]); payload.prepend(reinterpret_cast(&header[0]), HEADER_LENGTH); return payload; } void ToqConnection::tryConnect() { Q_ASSERT(!_socket); _socket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol, this); connect(_socket, &QBluetoothSocket::connected, this, &ToqConnection::handleSocketConnected); connect(_socket, &QBluetoothSocket::disconnected, this, &ToqConnection::handleSocketDisconnected); connect(_socket, (void (QBluetoothSocket::*)(QBluetoothSocket::SocketError))&QBluetoothSocket::error, this, &ToqConnection::handleSocketError); connect(_socket, &QBluetoothSocket::readyRead, this, &ToqConnection::handleSocketData); qDebug() << "Connecting to" << _address.toString(); _socket->connectToService(_address, 1); } void ToqConnection::handleSocketConnected() { qDebug() << "Connected"; Q_ASSERT(_socket); _reconnectTimer->stop(); emit connected(); emit connectedChanged(); } void ToqConnection::handleSocketDisconnected() { if (_socket) { qDebug() << "Disconnected"; _socket->deleteLater(); _socket = 0; _reconnectTimer->start(); emit disconnected(); emit connectedChanged(); } } void ToqConnection::handleSocketError(QBluetoothSocket::SocketError error) { if (_socket) { qWarning() << error << _socket->errorString(); _socket->disconnectFromService(); } } void ToqConnection::handleSocketData() { // Keep attempting to read messages as long as at least a header is present while (_socket->bytesAvailable() >= HEADER_LENGTH) { // Take a look at the header, but do not remove it from the socket input buffer. // We will only remove it once we're sure the entire packet is in the buffer. uchar header[HEADER_LENGTH]; _socket->peek(reinterpret_cast(header), HEADER_LENGTH); quint16 message_length = qFromBigEndian(&header[2]); // Sanity checks on the message_length if (message_length == 0) { qWarning() << "received empty message"; _socket->read(HEADER_LENGTH); // skip this header continue; // check if there are additional headers. } // Now wait for the entire message if (_socket->bytesAvailable() < HEADER_LENGTH + message_length - 4) { qDebug() << "incomplete msg body in read buffer"; return; // try again once more data comes in } // We can now safely remove the message from the input buffer, // as we know the entire message is in the input buffer. QByteArray data = _socket->read(HEADER_LENGTH + message_length - 4); Message msg = unpackMessage(data); if (msg.transactionId > _lastTransactionId) _lastTransactionId = msg.transactionId; emit messageReceived(msg); } }