#include #include #include #include #include "toqconnection.h" static const QBluetoothUuid LISTEN_UUID(QLatin1String("00000001-476D-42C4-BD11-9D377C45694F")); static const int HEADER_LENGTH = 10; static const int FIRST_CONNECTION_INTERVAL = 10 * 1000; // Because the watch will actually try to reconnect to us frequently, // we can afford to set a relaxed interval here. static const int RETRY_CONNECTION_INTERVAL = 10 * 60 * 1000; ToqConnection::ToqConnection(QObject *parent) : QObject(parent), _server(new QBluetoothServer(QBluetoothServiceInfo::RfcommProtocol, this)), _socket(0), _reconnectTimer(new QTimer(this)), _lastTransactionId(0) { connect(_reconnectTimer, &QTimer::timeout, this, &ToqConnection::tryConnect); _reconnectTimer->setTimerType(Qt::VeryCoarseTimer); _reconnectTimer->setSingleShot(true); connect(_server, &QBluetoothServer::newConnection, this, &ToqConnection::handleServerConnection); QBluetoothServiceInfo service = _server->listen(LISTEN_UUID, "PHubCommServer"); if (service.isValid()) { qDebug() << "Started listening on channel" << service.serverChannel(); } else { qWarning() << "Could not register the server service"; } } ToqConnection::Message::Message(Endpoint source, Endpoint destination, quint16 transactionId, quint32 type, const QJsonDocument &payload) : source(source), destination(destination), transactionId(transactionId), type(type), payload(payload.toJson(QJsonDocument::Compact)) { } QJsonDocument ToqConnection::Message::toJson() const { QJsonDocument doc; QJsonParseError error; doc = QJsonDocument::fromJson(payload, &error); if (error.error) { qWarning() << "Failure while parsing message JSON payload: " << error.errorString(); } return doc; } 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) { if (address != _address) { _address = address; if (isConnected()) { _socket->disconnectFromService(); } else { _reconnectTimer->start(FIRST_CONNECTION_INTERVAL); } } } 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"; } } void ToqConnection::disconnectFromDevice() { if (_socket) { _socket->disconnectFromService(); } else { qWarning() << "Not connected"; } } 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]); msg.payload = data.mid(HEADER_LENGTH); return msg; } QByteArray ToqConnection::packMessage(const Message &msg) { uchar header[HEADER_LENGTH]; header[0] = msg.source; header[1] = msg.destination; qToBigEndian(msg.payload.length() + 4, &header[2]); qToBigEndian(msg.transactionId, &header[4]); qToBigEndian(msg.type, &header[6]); QByteArray data; data.reserve(HEADER_LENGTH + msg.payload.length()); data.append(reinterpret_cast(&header[0]), HEADER_LENGTH); data.append(msg.payload); return data; } void ToqConnection::setSocket(QBluetoothSocket *socket) { Q_ASSERT(!_socket); _socket = socket; 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); if (_socket->state() == QBluetoothSocket::ConnectedState) { handleSocketConnected(); } } void ToqConnection::tryConnect() { Q_ASSERT(!_socket); QBluetoothSocket *socket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol, this); setSocket(socket); qDebug() << "Connecting to" << _address.toString(); socket->connectToService(_address, 1); } void ToqConnection::handleServerConnection() { qDebug() << "Got a connection from the server"; QBluetoothSocket *socket = _server->nextPendingConnection(); if (_socket) { // If we have an existing socket, give priority to the received one. qDebug() << "Terminating current connection first"; _socket->disconnectFromService(); if (_socket) { _socket->deleteLater(); _socket = 0; } } setSocket(socket); } void ToqConnection::handleSocketConnected() { qDebug() << "Connected"; Q_ASSERT(_socket); _reconnectTimer->stop(); emit connected(); emit connectedChanged(); } void ToqConnection::handleSocketDisconnected() { if (_socket) { qDebug() << "Disconnected"; Q_ASSERT(_socket->state() == QBluetoothSocket::UnconnectedState || _socket->state() == QBluetoothSocket::ClosingState); _socket->deleteLater(); _socket = 0; if (!_address.isNull()) { _reconnectTimer->start(RETRY_CONNECTION_INTERVAL); } 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); } }