diff options
Diffstat (limited to 'saltoqd/obexconnection.cpp')
-rw-r--r-- | saltoqd/obexconnection.cpp | 320 |
1 files changed, 320 insertions, 0 deletions
diff --git a/saltoqd/obexconnection.cpp b/saltoqd/obexconnection.cpp new file mode 100644 index 0000000..28aa4e2 --- /dev/null +++ b/saltoqd/obexconnection.cpp @@ -0,0 +1,320 @@ +#include <QtCore/QTextCodec> +#include <openobex/obex.h> +#include "obexconnection.h" + +ObexTransfer::ObexTransfer(obex_t *obex, obex_object_t *obj, QObject *parent) : + QObject(parent), _obex(obex), _obj(obj) +{ +} + +ObexTransfer::~ObexTransfer() +{ + if (_obj) { + OBEX_ObjectDelete(_obex, _obj); + } +} + +void ObexTransfer::cancel() +{ + // TODO +} + +ObexConnection::ObexConnection(ToqConnection *conn, QObject *parent) : + QObject(parent), _conn(conn), + _reconnectTimer(new QTimer(this)), + _obex(0), _connected(false), _busy(false) +{ + connect(_conn, &ToqConnection::connected, + this, &ObexConnection::handleToqConnected); + connect(_conn, &ToqConnection::disconnected, + this, &ObexConnection::handleToqDisconnected); + + connect(_reconnectTimer, &QTimer::timeout, + this, &ObexConnection::tryConnect); + + _reconnectTimer->setSingleShot(true); + _reconnectTimer->setInterval(500); + + if (_conn->isConnected()) { + _reconnectTimer->start(); + } +} + +ObexTransfer * ObexConnection::put(const QString &name, const QByteArray &data) +{ + obex_object_t *obj = OBEX_ObjectNew(_obex, OBEX_CMD_PUT); + obex_headerdata_t hd; + hd.bq4 = data.size(); + OBEX_ObjectAddHeader(_obex, obj, OBEX_HDR_LENGTH, hd, 4, + OBEX_FL_FIT_ONE_PACKET); + + QTextCodec *codec = QTextCodec::codecForName("UTF-16BE"); + QByteArray unicode = codec->fromUnicode(name); + unicode.append("\0\0", 2); // Require null terminator + hd.bs = reinterpret_cast<const uint8_t *>(unicode.constData()); + OBEX_ObjectAddHeader(_obex, obj, OBEX_HDR_NAME, hd, unicode.size(), + OBEX_FL_FIT_ONE_PACKET); + + hd.bs = reinterpret_cast<const uint8_t *>(data.constData()); + OBEX_ObjectAddHeader(_obex, obj, OBEX_HDR_BODY, hd, data.size(), 0); + + ObexTransfer *transfer = new ObexTransfer(_obex, obj, this); + connect(transfer, &ObexTransfer::destroyed, + this, &ObexConnection::handleTransferDestroyed); + + _pending.push_back(transfer); + + handleNextPending(); + + return transfer; +} + +void ObexConnection::tryConnect() +{ + Q_ASSERT(!_socket); + Q_ASSERT(_conn->isConnected()); + + qDebug() << "Trying to connect"; + + _socket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol, this); + connect(_socket, &QBluetoothSocket::connected, + this, &ObexConnection::handleSocketConnected); + connect(_socket, &QBluetoothSocket::disconnected, + this, &ObexConnection::handleSocketDisconnected); + connect(_socket, (void (QBluetoothSocket::*)(QBluetoothSocket::SocketError))&QBluetoothSocket::error, + this, &ObexConnection::handleSocketError); + connect(_socket, &QBluetoothSocket::readyRead, + this, &ObexConnection::handleSocketData); + + _socket->connectToService(_conn->address(), 4); +} + +void ObexConnection::handleToqConnected() +{ + tryConnect(); +} + +void ObexConnection::handleToqDisconnected() +{ + _reconnectTimer->stop(); + if (_obex) { + OBEX_Cleanup(_obex); + _obex = 0; + } + if (_socket) { + _socket->disconnectFromService(); + if (_socket) { + _socket->deleteLater(); + _socket = 0; + } + } + _connected = false; + _busy = false; +} + +void ObexConnection::handleSocketConnected() +{ + qDebug() << "OPP connected"; + + _obex = OBEX_Init(OBEX_TRANS_CUSTOM, handleObexEvent, 0); + if (!_obex) { + qWarning() << "Could not initialize OBEX library"; + // If we fail to connect, the watch will eventually disconnect from us. + // So there's no need to do anything. + return; + } + + OBEX_SetUserData(_obex, this); + OBEX_SetTransportMTU(_obex, OBEX_MAXIMUM_MTU, OBEX_MAXIMUM_MTU); + + obex_ctrans_t trans; + trans.connect = obexConnect; + trans.disconnect = obexDisconnect; + trans.listen = obexListen; + trans.write = obexWrite; + trans.handleinput = obexHandleInput; + trans.customdata = _socket; + int rc = OBEX_RegisterCTransport(_obex, &trans); + if (rc < 0) { + qWarning() << "Could not initialize custom OBEX transport"; + return; + } + + obex_object_t *obj = OBEX_ObjectNew(_obex, OBEX_CMD_CONNECT); + Q_ASSERT(obj); + + if (OBEX_Request(_obex, obj) < 0) { + qWarning() << "Could not send OBEX_CMD_CONNECT"; + return; + } + + _busy = true; // We're waiting for CONNECT result +} + +void ObexConnection::handleSocketDisconnected() +{ + qDebug() << "OPP disconnected"; + if (_obex) { + OBEX_Cleanup(_obex); + _obex = 0; + } + if (_socket) { + _socket->deleteLater(); + _socket = 0; + } + emit disconnected(); + if (_conn->isConnected()) { + qDebug() << "OPP socket died but main socket still alive, retrying connection"; + _reconnectTimer->start(); + } + _connected = false; + _busy = false; +} + +void ObexConnection::handleSocketError(QBluetoothSocket::SocketError error) +{ + qWarning() << "OPP socket error" << error; +} + +void ObexConnection::handleSocketData() +{ + static char buffer[4096]; + while (_socket->bytesAvailable() > 0) { + qint64 read = _socket->read(buffer, sizeof(buffer)); + int rc = OBEX_CustomDataFeed(_obex, reinterpret_cast<uint8_t*>(buffer), read); + if (rc < 0) { + qWarning() << "Could not feed data to OBEX"; + } + } +} + +void ObexConnection::handleTransferDestroyed() +{ + ObexTransfer *transfer = static_cast<ObexTransfer*>(sender()); + _pending.removeAll(transfer); +} + +void ObexConnection::handleNextPending() +{ + if (!_connected) return; + if (_busy) return; + if (_pending.empty()) return; + + ObexTransfer *transfer = _pending.head(); + int rc = OBEX_Request(_obex, transfer->_obj); + if (rc < 0) { + qWarning() << "Failed to perform request"; + _socket->disconnectFromService(); + _pending.dequeue(); + } else { + qDebug() << "Request performed"; + _busy = true; + } +} + +void ObexConnection::handleObexEvent(obex_t *handle, obex_object_t *obj, int mode, int event, int obex_cmd, int obex_rsp) +{ + ObexConnection *self = static_cast<ObexConnection*>(OBEX_GetUserData(handle)); + ObexTransfer *transfer; + + Q_UNUSED(mode); + + qDebug() << "OBEX event" << event << obex_cmd << obex_rsp; + + switch (event) { + case OBEX_EV_PROGRESS: + qDebug() << "Progress"; + break; + case OBEX_EV_REQDONE: + if (!self->_busy) { + qWarning() << "Response was requested, but we were not expecting anything"; + } + + switch (obex_cmd) { + case OBEX_CMD_CONNECT: + switch (obex_rsp) { + case OBEX_RSP_SUCCESS: + qDebug() << "OPP OBEX connected"; + self->_connected = true; + self->_busy = false; + emit self->connected(); + self->handleNextPending(); + break; + default: + qWarning() << "OPP OBEX failed to connect:" << OBEX_ResponseToString(obex_rsp); + self->_socket->disconnectFromService(); + break; + } + + break; + case OBEX_CMD_PUT: + case OBEX_CMD_GET: + Q_ASSERT(!self->_pending.isEmpty()); + transfer = self->_pending.dequeue(); + self->disconnect(transfer, nullptr, self, nullptr); + + if (transfer->_obj != obj) { + qWarning() << "Got result for a request we did not make"; + self->_socket->disconnectFromService(); + return; + } + + switch (obex_rsp) { + case OBEX_RSP_SUCCESS: + qDebug() << "OPP OBEX transfer success"; + emit transfer->finished(); + break; + default: + qWarning() << "OPP OBEX transfer failed:" << OBEX_ResponseToString(obex_rsp); + emit transfer->error(obex_rsp); + break; + } + + self->_busy = false; + self->handleNextPending(); + + break; + default: + // Not interested + break; + } + + break; + } +} + +int ObexConnection::obexConnect(obex_t *handle, void *data) +{ + Q_UNUSED(handle); + Q_UNUSED(data); + return 1; +} + +int ObexConnection::obexDisconnect(obex_t *handle, void *data) +{ + Q_UNUSED(handle); + Q_UNUSED(data); + return 1; +} + +int ObexConnection::obexListen(obex_t *handle, void *data) +{ + Q_UNUSED(handle); + Q_UNUSED(data); + return -1; // Should never happen +} + +int ObexConnection::obexWrite(obex_t *handle, void *data, quint8 *buf, int buflen) +{ + QBluetoothSocket *socket = static_cast<QBluetoothSocket*>(data); + Q_UNUSED(handle); + return socket->write(reinterpret_cast<char*>(buf), buflen); +} + +int ObexConnection::obexHandleInput(obex_t *handle, void *data, int timeout) +{ + Q_UNUSED(handle); + Q_UNUSED(data); + Q_UNUSED(timeout); + return 1; +} |