summaryrefslogtreecommitdiff
path: root/saltoqd/toqconnection.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'saltoqd/toqconnection.cpp')
-rw-r--r--saltoqd/toqconnection.cpp187
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));
+ }
+}
+