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