diff options
Diffstat (limited to 'saltoqd/toqconnection.cpp')
-rw-r--r-- | saltoqd/toqconnection.cpp | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/saltoqd/toqconnection.cpp b/saltoqd/toqconnection.cpp new file mode 100644 index 0000000..c2e2349 --- /dev/null +++ b/saltoqd/toqconnection.cpp @@ -0,0 +1,187 @@ +#include <zlib.h> +#include <QtDebug> +#include <QtEndian> +#include <QtCore/QMetaEnum> + +#include "toqconnection.h" + +static const int HEADER_LENGTH = 10; + +ToqConnection::ToqConnection(const QBluetoothAddress &address, QObject *parent) : + QObject(parent), + _address(address), _socket(0), + _reconnectTimer(new QTimer(this)), + _lastTransactionId(0) +{ + connect(_reconnectTimer, &QTimer::timeout, + this, &ToqConnection::tryConnect); + + _reconnectTimer->setSingleShot(true); + _reconnectTimer->setInterval(1000); + _reconnectTimer->start(); +} + +const char * ToqConnection::nameOfEndpoint(Endpoint ep) +{ + int index = staticMetaObject.indexOfEnumerator("CoreEndpoints"); + QMetaEnum epEnum = staticMetaObject.enumerator(index); + return epEnum.valueToKey(ep); +} + +quint32 ToqConnection::checksum(const QByteArray &data) +{ + uLong crc = crc32(0L, Z_NULL, 0); + crc = crc32(crc, reinterpret_cast<const Bytef*>(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<const Bytef*>(&buffer[0]), read); + } + return crc; +} + +bool ToqConnection::isConnected() const +{ + return _socket && _socket->state() == QBluetoothSocket::ConnectedState; +} + +quint16 ToqConnection::nextTransactionId() +{ + 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<const uchar*>(data.constData()); + + quint16 message_length = qFromBigEndian<quint16>(&header[2]); + Q_ASSERT(data.length() == message_length + HEADER_LENGTH - 4); + msg.source = header[0]; + msg.destination = header[1]; + msg.transactionId = qFromBigEndian<quint16>(&header[4]); + msg.type = qFromBigEndian<quint32>(&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<quint16>(payload.length() + 4, &header[2]); + qToBigEndian<quint16>(msg.transactionId, &header[4]); + qToBigEndian<quint32>(msg.type, &header[6]); + + payload.prepend(reinterpret_cast<char*>(&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, QIODevice::ReadWrite); +} + +void ToqConnection::handleSocketConnected() +{ + qDebug() << "Connected"; + Q_ASSERT(_socket); + _reconnectTimer->stop(); + emit connected(); +} + +void ToqConnection::handleSocketDisconnected() +{ + qDebug() << "Disconnected"; + Q_ASSERT(_socket); + _socket->deleteLater(); + _socket = 0; + _reconnectTimer->start(); + emit disconnected(); +} + +void ToqConnection::handleSocketError(QBluetoothSocket::SocketError error) +{ + qWarning() << error << _socket->errorString(); +} + +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<char*>(header), HEADER_LENGTH); + + quint16 message_length = qFromBigEndian<quint16>(&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); + emit messageReceived(unpackMessage(data)); + } +} + |