summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJavier S. Pedro <maemo@javispedro.com>2013-12-03 03:05:47 +0100
committerJavier S. Pedro <maemo@javispedro.com>2013-12-03 03:05:47 +0100
commit47ada94baa424e56d2ded256fddc91e6aa4d3090 (patch)
tree46fd1da4c5fe29694aea0d05b70fe7db2ba7fa88
downloadlibgato-47ada94baa424e56d2ded256fddc91e6aa4d3090.tar.gz
libgato-47ada94baa424e56d2ded256fddc91e6aa4d3090.zip
initial import
-rw-r--r--gato.h13
-rw-r--r--gatoaddress.cpp110
-rw-r--r--gatoaddress.h32
-rw-r--r--gatoatt.cpp578
-rw-r--r--gatoatt.h97
-rw-r--r--gatocentralmanager.cpp222
-rw-r--r--gatocentralmanager.h44
-rw-r--r--gatocentralmanager_p.h32
-rw-r--r--gatocharacteristic.cpp159
-rw-r--r--gatocharacteristic.h69
-rw-r--r--gatodescriptor.cpp72
-rw-r--r--gatodescriptor.h32
-rw-r--r--gatoperipheral.cpp834
-rw-r--r--gatoperipheral.h67
-rw-r--r--gatoperipheral_p.h71
-rw-r--r--gatoservice.cpp129
-rw-r--r--gatoservice.h42
-rw-r--r--gatosocket.cpp193
-rw-r--r--gatosocket.h54
-rw-r--r--gatouuid.cpp184
-rw-r--r--gatouuid.h109
-rw-r--r--helpers.cpp64
-rw-r--r--helpers.h35
-rw-r--r--libgato.pro39
-rw-r--r--libgato_global.h14
25 files changed, 3295 insertions, 0 deletions
diff --git a/gato.h b/gato.h
new file mode 100644
index 0000000..833ae59
--- /dev/null
+++ b/gato.h
@@ -0,0 +1,13 @@
+#ifndef GATO_H
+#define GATO_H
+
+#include "libgato_global.h"
+#include "gatoaddress.h"
+#include "gatouuid.h"
+#include "gatocentralmanager.h"
+#include "gatoperipheral.h"
+#include "gatoservice.h"
+#include "gatocharacteristic.h"
+#include "gatodescriptor.h"
+
+#endif // GATO_H
diff --git a/gatoaddress.cpp b/gatoaddress.cpp
new file mode 100644
index 0000000..e29190b
--- /dev/null
+++ b/gatoaddress.cpp
@@ -0,0 +1,110 @@
+/*
+ * libgato - A GATT/ATT library for use with Bluez
+ *
+ * Copyright (C) 2013 Javier S. Pedro <maemo@javispedro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <QtCore/QString>
+#include <QtCore/QHash>
+#include <cstdio>
+
+#include <bluetooth/bluetooth.h>
+
+#include "gatoaddress.h"
+
+struct GatoAddressPrivate : QSharedData
+{
+ union {
+ bdaddr_t bd;
+ quint64 u64;
+ } addr;
+};
+
+GatoAddress::GatoAddress()
+ : d(new GatoAddressPrivate)
+{
+}
+
+
+
+GatoAddress::GatoAddress(quint64 addr)
+ : d(new GatoAddressPrivate)
+{
+ d->addr.u64 = addr;
+}
+
+GatoAddress::GatoAddress(quint8 addr[])
+ : d(new GatoAddressPrivate)
+{
+ d->addr.u64 = 0;
+ for (int i = 0; i < 6; i++) {
+ d->addr.bd.b[i] = addr[i];
+ }
+}
+
+GatoAddress::GatoAddress(const QString &addr)
+ : d(new GatoAddressPrivate)
+{
+ d->addr.u64 = 0;
+ str2ba(addr.toLatin1().constData(), &d->addr.bd);
+}
+
+GatoAddress::GatoAddress(const GatoAddress &o)
+ : d(o.d)
+{
+}
+
+GatoAddress::~GatoAddress()
+{
+}
+
+GatoAddress & GatoAddress::operator =(const GatoAddress& o)
+{
+ if (this != &o) {
+ d = o.d;
+ }
+ return *this;
+}
+
+quint64 GatoAddress::toUInt64() const
+{
+ return d->addr.u64;
+}
+
+void GatoAddress::toUInt8Array(quint8 addr[]) const
+{
+ for (int i = 0; i < 6; i++) {
+ addr[i] = d->addr.bd.b[i];
+ }
+}
+
+QString GatoAddress::toString() const
+{
+ char addr[18];
+ ba2str(&d->addr.bd, addr);
+ return QString::fromAscii(addr);
+}
+
+bool operator==(const GatoAddress &a, const GatoAddress &b)
+{
+ return a.toUInt64() == b.toUInt64();
+}
+
+uint qHash(const GatoAddress &a)
+{
+ return qHash(a.toUInt64());
+}
diff --git a/gatoaddress.h b/gatoaddress.h
new file mode 100644
index 0000000..f30ec43
--- /dev/null
+++ b/gatoaddress.h
@@ -0,0 +1,32 @@
+#ifndef GATOADDRESS_H
+#define GATOADDRESS_H
+
+#include <QtCore/QSharedDataPointer>
+#include "libgato_global.h"
+
+class GatoAddressPrivate;
+
+class LIBGATO_EXPORT GatoAddress
+{
+public:
+ GatoAddress();
+ explicit GatoAddress(quint8 addr[]);
+ explicit GatoAddress(quint64 addr);
+ explicit GatoAddress(const QString &addr);
+ GatoAddress(const GatoAddress& o);
+ ~GatoAddress();
+
+ GatoAddress& operator=(const GatoAddress& o);
+
+ void toUInt8Array(quint8 addr[]) const;
+ quint64 toUInt64() const;
+ QString toString() const;
+
+private:
+ QSharedDataPointer<GatoAddressPrivate> d;
+};
+
+LIBGATO_EXPORT bool operator==(const GatoAddress &a, const GatoAddress &b);
+LIBGATO_EXPORT uint qHash(const GatoAddress &a);
+
+#endif // GATOADDRESS_H
diff --git a/gatoatt.cpp b/gatoatt.cpp
new file mode 100644
index 0000000..8015757
--- /dev/null
+++ b/gatoatt.cpp
@@ -0,0 +1,578 @@
+/*
+ * libgato - A GATT/ATT library for use with Bluez
+ *
+ * Copyright (C) 2013 Javier S. Pedro <maemo@javispedro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <QtCore/QDebug>
+
+#include "gatoatt.h"
+#include "helpers.h"
+
+#define PROTOCOL_DEBUG 0
+
+#define ATT_CID 4
+#define ATT_PSM 31
+
+#define ATT_DEFAULT_LE_MTU 23
+
+enum AttOpcode {
+ AttOpNone = 0,
+ AttOpErrorResponse = 0x1,
+ AttOpExchangeMTURequest = 0x2,
+ AttOpExchangeMTUResponse = 0x3,
+ AttOpFindInformationRequest = 0x4,
+ AttOpFindInformationResponse = 0x5,
+ AttOpFindByTypeValueRequest = 0x6,
+ AttOpFindByTypeValueResponse = 0x7,
+ AttOpReadByTypeRequest = 0x8,
+ AttOpReadByTypeResponse = 0x9,
+ AttOpReadRequest = 0xA,
+ AttOpReadResponse = 0xB,
+ AttOpReadBlobRequest = 0xC,
+ AttOpReadBlobResponse = 0xD,
+ AttOpReadMultipleRequest = 0xE,
+ AttOpReadMultipleResponse = 0xF,
+ AttOpReadByGroupTypeRequest = 0x10,
+ AttOpReadByGroupTypeResponse = 0x11,
+ AttOpWriteRequest = 0x12,
+ AttOpWriteResponse = 0x13,
+ AttOpWriteCommand = 0x52,
+ AttOpPrepareWriteRequest = 0x16,
+ AttOpPrepareWriteResponse = 0x17,
+ AttOpExecuteWriteRequest = 0x18,
+ AttOpExecuteWriteResponse = 0x19,
+ AttOpHandleValueNotification = 0x1B,
+ AttOpHandleValueIndication = 0x1D,
+ AttOpHandleValueConfirmation = 0x1E,
+ AttOpSignedWriteCommand = 0xD2
+};
+
+static QByteArray remove_method_signature(const char *sig)
+{
+ const char* bracketPosition = strchr(sig, '(');
+ if (!bracketPosition || !(sig[0] >= '0' && sig[0] <= '3')) {
+ qWarning("Invalid slot specification");
+ return QByteArray();
+ }
+ return QByteArray(sig + 1, bracketPosition - 1 - sig);
+}
+
+GatoAtt::GatoAtt(QObject *parent) :
+ QObject(parent), socket(new GatoSocket(this)), mtu(ATT_DEFAULT_LE_MTU), next_id(1)
+{
+ connect(socket, SIGNAL(connected()), SLOT(handleSocketConnected()));
+ connect(socket, SIGNAL(disconnected()), SLOT(handleSocketDisconnected()));
+ connect(socket, SIGNAL(readyRead()), SLOT(handleSocketReadyRead()));
+}
+
+GatoAtt::~GatoAtt()
+{
+}
+
+GatoSocket::State GatoAtt::state() const
+{
+ return socket->state();
+}
+
+bool GatoAtt::connectTo(const GatoAddress &addr)
+{
+ return socket->connectTo(addr, ATT_CID);
+}
+
+void GatoAtt::close()
+{
+ socket->close();
+}
+
+uint GatoAtt::request(int opcode, const QByteArray &data, QObject *receiver, const char *member)
+{
+ Request req;
+ req.id = next_id++;
+ req.opcode = opcode;
+ req.pkt = data;
+ req.pkt.prepend(static_cast<char>(opcode));
+ req.receiver = receiver;
+ req.member = remove_method_signature(member);
+
+ pending_requests.enqueue(req);
+
+ if (pending_requests.size() == 1) {
+ // So we can just send this request instead of waiting for others to complete
+ sendARequest();
+ }
+
+ return req.id;
+}
+
+void GatoAtt::cancelRequest(uint id)
+{
+ QQueue<Request>::iterator it = pending_requests.begin();
+ while (it != pending_requests.end()) {
+ if (it->id == id) {
+ it = pending_requests.erase(it);
+ } else {
+ ++it;
+ }
+ }
+}
+
+uint GatoAtt::requestExchangeMTU(quint8 client_mtu, QObject *receiver, const char *member)
+{
+ QByteArray data(1, client_mtu);
+ return request(AttOpExchangeMTURequest, data, receiver, member);
+}
+
+uint GatoAtt::requestFindInformation(GatoHandle start, GatoHandle end, QObject *receiver, const char *member)
+{
+ QByteArray data;
+ QDataStream s(&data, QIODevice::WriteOnly);
+ s.setByteOrder(QDataStream::LittleEndian);
+ s << start << end;
+
+ return request(AttOpFindInformationRequest, data, receiver, member);
+}
+
+uint GatoAtt::requestFindByTypeValue(GatoHandle start, GatoHandle end, const GatoUUID &uuid, const QByteArray &value, QObject *receiver, const char *member)
+{
+ QByteArray data;
+ QDataStream s(&data, QIODevice::WriteOnly);
+ s.setByteOrder(QDataStream::LittleEndian);
+ s << start << end;
+
+ bool uuid16_ok;
+ quint16 uuid16 = uuid.toUInt16(&uuid16_ok);
+ if (uuid16_ok) {
+ s << uuid16;
+ } else {
+ qWarning() << "FindByTypeValue does not support UUIDs other than UUID16";
+ return -1;
+ }
+
+ s << value;
+
+ return request(AttOpFindByTypeValueRequest, data, receiver, member);
+}
+
+uint GatoAtt::requestReadByType(GatoHandle start, GatoHandle end, const GatoUUID &uuid, QObject *receiver, const char *member)
+{
+ QByteArray data;
+ QDataStream s(&data, QIODevice::WriteOnly);
+ s.setByteOrder(QDataStream::LittleEndian);
+ s << start << end;
+ writeUuid16or128(s, uuid);
+
+ return request(AttOpReadByTypeRequest, data, receiver, member);
+}
+
+uint GatoAtt::requestRead(GatoHandle handle, QObject *receiver, const char *member)
+{
+ QByteArray data;
+ QDataStream s(&data, QIODevice::WriteOnly);
+ s.setByteOrder(QDataStream::LittleEndian);
+ s << handle;
+
+ return request(AttOpReadRequest, data, receiver, member);
+}
+
+uint GatoAtt::requestReadByGroupType(GatoHandle start, GatoHandle end, const GatoUUID &uuid, QObject *receiver, const char *member)
+{
+ QByteArray data;
+ QDataStream s(&data, QIODevice::WriteOnly);
+ s.setByteOrder(QDataStream::LittleEndian);
+ s << start << end;
+ writeUuid16or128(s, uuid);
+
+ return request(AttOpReadByGroupTypeRequest, data, receiver, member);
+}
+
+uint GatoAtt::requestWrite(GatoHandle handle, const QByteArray &value, QObject *receiver, const char *member)
+{
+ QByteArray data;
+ QDataStream s(&data, QIODevice::WriteOnly);
+ s.setByteOrder(QDataStream::LittleEndian);
+ s << handle;
+ s.writeRawData(value.constData(), value.length());
+
+ return request(AttOpWriteRequest, data, receiver, member);
+}
+
+void GatoAtt::command(int opcode, const QByteArray &data)
+{
+ QByteArray packet = data;
+ packet.prepend(static_cast<char>(opcode));
+
+ socket->send(packet);
+
+#if PROTOCOL_DEBUG
+ qDebug() << "Wrote" << packet.size() << "bytes (command)" << packet.toHex();
+#endif
+}
+
+void GatoAtt::sendARequest()
+{
+ if (pending_requests.isEmpty()) {
+ return;
+ }
+
+ Request &req = pending_requests.head();
+ socket->send(req.pkt);
+
+#if PROTOCOL_DEBUG
+ qDebug() << "Wrote" << req.pkt.size() << "bytes (request)" << req.pkt.toHex();
+#endif
+}
+
+bool GatoAtt::handleEvent(const QByteArray &event)
+{
+ const char *data = event.constData();
+ quint8 opcode = event[0];
+ GatoHandle handle;
+
+ switch (opcode) {
+ case AttOpHandleValueNotification:
+ handle = read_le<GatoHandle>(&data[1]);
+ emit attributeUpdated(handle, event.mid(3), false);
+ return true;
+ case AttOpHandleValueIndication:
+ handle = read_le<GatoHandle>(&data[1]);
+
+ // Send the confirmation back
+ command(AttOpHandleValueConfirmation, QByteArray());
+
+ emit attributeUpdated(handle, event.mid(3), true);
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool GatoAtt::handleResponse(const Request &req, const QByteArray &response)
+{
+ // If we know the request, we can provide a decoded answer
+ switch (req.opcode) {
+ case AttOpExchangeMTURequest:
+ if (response[0] == AttOpExchangeMTUResponse) {
+ if (req.receiver) {
+ QMetaObject::invokeMethod(req.receiver, req.member.constData(),
+ Q_ARG(uint, req.id),
+ Q_ARG(quint8, response[1]));
+ }
+ return true;
+ } else if (response[0] == AttOpErrorResponse && response[1] == AttOpExchangeMTURequest) {
+ if (req.receiver) {
+ QMetaObject::invokeMethod(req.receiver, req.member.constData(),
+ Q_ARG(uint, req.id),
+ Q_ARG(quint8, response[1]));
+ }
+ return true;
+ } else {
+ return false;
+ }
+ break;
+ case AttOpFindInformationRequest:
+ if (response[0] == AttOpFindInformationResponse) {
+ if (req.receiver) {
+ QMetaObject::invokeMethod(req.receiver, req.member.constData(),
+ Q_ARG(uint, req.id),
+ Q_ARG(QList<GatoAtt::InformationData>, parseInformationData(response.mid(1))));
+ }
+ return true;
+ } else if (response[0] == AttOpErrorResponse && response[1] == AttOpFindInformationRequest) {
+ if (req.receiver) {
+ QMetaObject::invokeMethod(req.receiver, req.member.constData(),
+ Q_ARG(uint, req.id),
+ Q_ARG(QList<GatoAtt::InformationData>, QList<InformationData>()));
+ }
+ return true;
+ } else {
+ return false;
+ }
+ break;
+ case AttOpFindByTypeValueRequest:
+ if (response[0] == AttOpFindByTypeValueResponse) {
+ if (req.receiver) {
+ QMetaObject::invokeMethod(req.receiver, req.member.constData(),
+ Q_ARG(uint, req.id),
+ Q_ARG(QList<GatoAtt::HandleInformation>, parseHandleInformation(response.mid(1))));
+ }
+ return true;
+ } else if (response[0] == AttOpErrorResponse && response[1] == AttOpFindByTypeValueRequest) {
+ if (req.receiver) {
+ QMetaObject::invokeMethod(req.receiver, req.member.constData(),
+ Q_ARG(uint, req.id),
+ Q_ARG(QList<GatoAtt::HandleInformation>, QList<HandleInformation>()));
+ }
+ return true;
+ } else {
+ return false;
+ }
+ break;
+ case AttOpReadByTypeRequest:
+ if (response[0] == AttOpReadByTypeResponse) {
+ if (req.receiver) {
+ QMetaObject::invokeMethod(req.receiver, req.member.constData(),
+ Q_ARG(uint, req.id),
+ Q_ARG(QList<GatoAtt::AttributeData>, parseAttributeData(response.mid(1))));
+ }
+ return true;
+ } else if (response[0] == AttOpErrorResponse && response[1] == AttOpReadByTypeRequest) {
+ if (req.receiver) {
+ QMetaObject::invokeMethod(req.receiver, req.member.constData(),
+ Q_ARG(uint, req.id),
+ Q_ARG(QList<GatoAtt::AttributeData>, QList<AttributeData>()));
+ }
+ return true;
+ } else {
+ return false;
+ }
+ break;
+ case AttOpReadRequest:
+ if (response[0] == AttOpReadResponse) {
+ if (req.receiver) {
+ QMetaObject::invokeMethod(req.receiver, req.member.constData(),
+ Q_ARG(uint, req.id),
+ Q_ARG(QByteArray, response.mid(1)));
+ }
+ return true;
+ } else if (response[0] == AttOpErrorResponse && response[1] == AttOpReadRequest) {
+ if (req.receiver) {
+ QMetaObject::invokeMethod(req.receiver, req.member.constData(),
+ Q_ARG(uint, req.id),
+ Q_ARG(QByteArray, QByteArray()));
+ }
+ return true;
+ } else {
+ return false;
+ }
+ break;
+ case AttOpReadByGroupTypeRequest:
+ if (response[0] == AttOpReadByGroupTypeResponse) {
+ if (req.receiver) {
+ QMetaObject::invokeMethod(req.receiver, req.member.constData(),
+ Q_ARG(uint, req.id),
+ Q_ARG(QList<GatoAtt::AttributeGroupData>, parseAttributeGroupData(response.mid(1))));
+ }
+ return true;
+ } else if (response[0] == AttOpErrorResponse && response[1] == AttOpReadByGroupTypeRequest) {
+ if (req.receiver) {
+ QMetaObject::invokeMethod(req.receiver, req.member.constData(),
+ Q_ARG(uint, req.id),
+ Q_ARG(QList<GatoAtt::AttributeGroupData>, QList<AttributeGroupData>()));
+ }
+ return true;
+ } else {
+ return false;
+ }
+ break;
+ case AttOpWriteRequest:
+ if (response[0] == AttOpWriteResponse) {
+ if (req.receiver) {
+ QMetaObject::invokeMethod(req.receiver, req.member.constData(),
+ Q_ARG(uint, req.id),
+ Q_ARG(bool, true));
+ }
+ return true;
+ } else if (response[0] == AttOpErrorResponse && response[1] == AttOpWriteRequest) {
+ if (req.receiver) {
+ QMetaObject::invokeMethod(req.receiver, req.member.constData(),
+ Q_ARG(uint, req.id),
+ Q_ARG(bool, false));
+ }
+ return true;
+ } else {
+ return false;
+ }
+ break;
+ default: // Otherwise just send a QByteArray.
+ if (req.receiver) {
+ QMetaObject::invokeMethod(req.receiver, req.member.constData(),
+ Q_ARG(const QByteArray&, response));
+ }
+ return true;
+ }
+}
+
+void GatoAtt::writeUuid16or128(QDataStream &s, const GatoUUID &uuid)
+{
+ s.setByteOrder(QDataStream::LittleEndian);
+
+ bool uuid16_ok;
+ quint16 uuid16 = uuid.toUInt16(&uuid16_ok);
+ if (uuid16_ok) {
+ s << uuid16;
+ } else {
+ s << uuid.toUInt128();
+ }
+}
+
+QList<GatoAtt::InformationData> GatoAtt::parseInformationData(const QByteArray &data)
+{
+ const int format = data[0];
+ QList<InformationData> list;
+ int item_len;
+
+ switch (format) {
+ case 1:
+ item_len = 2 + 2;
+ break;
+ case 2:
+ item_len = 2 + 16;
+ break;
+ default:
+ qWarning() << "Unknown InformationData format!";
+ return list;
+ }
+
+ int items = (data.size() - 1) / item_len;
+ list.reserve(items);
+
+ int pos = 1;
+ const char *s = data.constData();
+ for (int i = 0; i < items; i++) {
+ InformationData d;
+ d.handle = read_le<GatoHandle>(&s[pos]);
+ switch (format) {
+ case 1:
+ d.uuid = GatoUUID(read_le<quint16>(&s[pos + 2]));
+ break;
+ case 2:
+ d.uuid = GatoUUID(read_le<gatouint128>(&s[pos + 2]));
+ break;
+ }
+
+ list.append(d);
+
+ pos += item_len;
+ }
+
+ return list;
+}
+
+QList<GatoAtt::HandleInformation> GatoAtt::parseHandleInformation(const QByteArray &data)
+{
+ const int item_len = 2;
+ const int items = data.size() / item_len;
+ QList<HandleInformation> list;
+ list.reserve(items);
+
+ int pos = 0;
+ const char *s = data.constData();
+ for (int i = 0; i < items; i++) {
+ HandleInformation d;
+ d.start = read_le<GatoHandle>(&s[pos]);
+ d.end = read_le<GatoHandle>(&s[pos + 2]);
+ list.append(d);
+
+ pos += item_len;
+ }
+
+ return list;
+}
+
+QList<GatoAtt::AttributeData> GatoAtt::parseAttributeData(const QByteArray &data)
+{
+ const int item_len = data[0];
+ const int items = (data.size() - 1) / item_len;
+ QList<AttributeData> list;
+ list.reserve(items);
+
+ int pos = 1;
+ const char *s = data.constData();
+ for (int i = 0; i < items; i++) {
+ AttributeData d;
+ d.handle = read_le<GatoHandle>(&s[pos]);
+ d.value = data.mid(pos + 2, item_len - 2);
+ list.append(d);
+
+ pos += item_len;
+ }
+
+ return list;
+}
+
+QList<GatoAtt::AttributeGroupData> GatoAtt::parseAttributeGroupData(const QByteArray &data)
+{
+ const int item_len = data[0];
+ const int items = (data.size() - 1) / item_len;
+ QList<AttributeGroupData> list;
+ list.reserve(items);
+
+ int pos = 1;
+ const char *s = data.constData();
+ for (int i = 0; i < items; i++) {
+ AttributeGroupData d;
+ d.start = read_le<GatoHandle>(&s[pos]);
+ d.end = read_le<GatoHandle>(&s[pos + 2]);
+ d.value = data.mid(pos + 4, item_len - 4);
+ list.append(d);
+
+ pos += item_len;
+ }
+
+ return list;
+}
+
+void GatoAtt::handleSocketConnected()
+{
+ requestExchangeMTU(ATT_DEFAULT_LE_MTU, this, SLOT(handleServerMTU(quint8)));
+ emit connected();
+}
+
+void GatoAtt::handleSocketDisconnected()
+{
+ emit disconnected();
+}
+
+void GatoAtt::handleSocketReadyRead()
+{
+ QByteArray pkt = socket->receive();
+ if (!pkt.isEmpty()) {
+#if PROTOCOL_DEBUG
+ qDebug() << "Received" << pkt.size() << "bytes" << pkt.toHex();
+#endif
+
+ // Check if it is an event
+ if (handleEvent(pkt)) {
+ return;
+ }
+
+ // Otherwise, if we have a request waiting, check if this answers it
+ if (!pending_requests.isEmpty()) {
+ if (handleResponse(pending_requests.head(), pkt)) {
+ pending_requests.dequeue();
+ // Proceed to next request
+ if (!pending_requests.isEmpty()) {
+ sendARequest();
+ }
+ return;
+ }
+ }
+
+ qDebug() << "No idea what this packet is";
+ }
+}
+
+void GatoAtt::handleServerMTU(uint req, quint8 server_mtu)
+{
+ Q_UNUSED(req);
+ if (server_mtu != 0) {
+ mtu = server_mtu;
+ }
+}
+
+
diff --git a/gatoatt.h b/gatoatt.h
new file mode 100644
index 0000000..27cea3e
--- /dev/null
+++ b/gatoatt.h
@@ -0,0 +1,97 @@
+#ifndef GATOATTSOCKET_H
+#define GATOATTSOCKET_H
+
+#include <QtCore/QObject>
+#include <QtCore/QQueue>
+#include "gatosocket.h"
+#include "gatouuid.h"
+
+class GatoAtt : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit GatoAtt(QObject *parent = 0);
+ ~GatoAtt();
+
+ GatoSocket::State state() const;
+
+ bool connectTo(const GatoAddress& addr);
+ void close();
+
+ struct InformationData
+ {
+ GatoHandle handle;
+ GatoUUID uuid;
+ };
+ struct HandleInformation
+ {
+ GatoHandle start;
+ GatoHandle end;
+ };
+ struct AttributeData
+ {
+ GatoHandle handle;
+ QByteArray value;
+ };
+ struct AttributeGroupData
+ {
+ GatoHandle start;
+ GatoHandle end;
+ QByteArray value;
+ };
+
+ uint request(int opcode, const QByteArray &data, QObject *receiver, const char *member);
+ uint requestExchangeMTU(quint8 client_mtu, QObject *receiver, const char *member);
+ uint requestFindInformation(GatoHandle start, GatoHandle end, QObject *receiver, const char *member);
+ uint requestFindByTypeValue(GatoHandle start, GatoHandle end, const GatoUUID &uuid, const QByteArray& value, QObject *receiver, const char *member);
+ uint requestReadByType(GatoHandle start, GatoHandle end, const GatoUUID &uuid, QObject *receiver, const char *member);
+ uint requestRead(GatoHandle handle, QObject *receiver, const char *member);
+ uint requestReadByGroupType(GatoHandle start, GatoHandle end, const GatoUUID &uuid, QObject *receiver, const char *member);
+ uint requestWrite(GatoHandle handle, const QByteArray &value, QObject *receiver, const char *member);
+ void cancelRequest(uint id);
+
+ void command(int opcode, const QByteArray &data);
+
+signals:
+ void connected();
+ void disconnected();
+
+ void attributeUpdated(GatoHandle handle, const QByteArray &value, bool confirmed);
+
+private:
+ struct Request
+ {
+ uint id;
+ quint8 opcode;
+ QByteArray pkt;
+ QObject *receiver;
+ QByteArray member;
+ };
+
+ void sendARequest();
+ bool handleEvent(const QByteArray &event);
+ bool handleResponse(const Request& req, const QByteArray &response);
+
+ void writeUuid16or128(QDataStream& s, const GatoUUID& uuid);
+
+ QList<InformationData> parseInformationData(const QByteArray &data);
+ QList<HandleInformation> parseHandleInformation(const QByteArray &data);
+ QList<AttributeData> parseAttributeData(const QByteArray &data);
+ QList<AttributeGroupData> parseAttributeGroupData(const QByteArray &data);
+
+private slots:
+ void handleSocketConnected();
+ void handleSocketDisconnected();
+ void handleSocketReadyRead();
+
+ void handleServerMTU(uint req, quint8 server_mtu);
+
+private:
+ GatoSocket *socket;
+ quint8 mtu;
+ uint next_id;
+ QQueue<Request> pending_requests;
+};
+
+#endif // GATOATTSOCKET_H
diff --git a/gatocentralmanager.cpp b/gatocentralmanager.cpp
new file mode 100644
index 0000000..3073d34
--- /dev/null
+++ b/gatocentralmanager.cpp
@@ -0,0 +1,222 @@
+/*
+ * libgato - A GATT/ATT library for use with Bluez
+ *
+ * Copyright (C) 2013 Javier S. Pedro <maemo@javispedro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <QtCore/QDebug>
+
+#include <unistd.h>
+#include <errno.h>
+#include <assert.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+
+#include "gatocentralmanager_p.h"
+#include "gatoperipheral.h"
+
+GatoCentralManager::GatoCentralManager(QObject *parent) :
+ QObject(parent), d_ptr(new GatoCentralManagerPrivate)
+{
+ Q_D(GatoCentralManager);
+ d->q_ptr = this;
+
+ // Hardcode the "default" bluetooth adapter for now.
+ d->dev_id = hci_get_route(NULL);
+ d->timeout = 1000;
+ d->hci = -1;
+ d->notifier = 0;
+}
+
+GatoCentralManager::~GatoCentralManager()
+{
+ Q_D(GatoCentralManager);
+ if (d->scanning()) stopScan();
+ delete d_ptr;
+}
+
+void GatoCentralManager::scanForPeripherals(PeripheralScanOptions options)
+{
+ scanForPeripheralsWithServices(QList<GatoUUID>(), options);
+}
+
+void GatoCentralManager::scanForPeripheralsWithServices(const QList<GatoUUID> &uuids, PeripheralScanOptions options)
+{
+ Q_D(GatoCentralManager);
+
+ if (d->scanning()) stopScan();
+
+ if (!d->openDevice()) return;
+ d->filter_uuids = uuids;
+
+ quint8 filter_dup = options & PeripheralScanOptionAllowDuplicates ? 0 : 1;
+ quint8 scan_type = options & PeripheralScanOptionActive ? 1 : 0;
+ int rc;
+
+ hci_le_set_scan_enable(d->hci, 0, 0, d->timeout);
+
+ rc = hci_le_set_scan_parameters(d->hci, scan_type,
+ htobs(0x0010), htobs(0x0010),
+ 0 /* Public address */,
+ 0 /* No filter ? */,
+ d->timeout);
+ if (rc < 0) {
+ qErrnoWarning("LE Set scan parameters failed");
+ d->closeDevice();
+ return;
+ }
+
+ rc = hci_le_set_scan_enable(d->hci, 1, filter_dup, d->timeout);
+
+ if (rc < 0) {
+ qErrnoWarning("LE Set scan enable failed");
+ d->closeDevice();
+ return;
+ }
+
+ qDebug() << "LE Scan in progress";
+
+ d->notifier = new QSocketNotifier(d->hci, QSocketNotifier::Read);
+ connect(d->notifier, SIGNAL(activated(int)), this, SLOT(_q_readNotify()));
+
+ socklen_t olen = sizeof(d->hci_of);
+ if (getsockopt(d->hci, SOL_HCI, HCI_FILTER, &d->hci_of, &olen) < 0) {
+ qErrnoWarning("Could not get existing HCI socket options");
+ return;
+ }
+
+ hci_filter_clear(&d->hci_nf);
+ hci_filter_set_ptype(HCI_EVENT_PKT, &d->hci_nf);
+ hci_filter_set_event(EVT_LE_META_EVENT, &d->hci_nf);
+
+ if (setsockopt(d->hci, SOL_HCI, HCI_FILTER, &d->hci_nf, sizeof(d->hci_nf)) < 0) {
+ qErrnoWarning("Could not set HCI socket options");
+ return;
+ }
+
+ // SocketNotifier will call _q_readNotify() when ready
+}
+
+void GatoCentralManager::stopScan()
+{
+ Q_D(GatoCentralManager);
+ if (d->scanning()) {
+ qDebug() << "Stopping LE scan";
+ delete d->notifier;
+ setsockopt(d->hci, SOL_HCI, HCI_FILTER, &d->hci_of, sizeof(d->hci_of));
+ hci_le_set_scan_enable(d->hci, 0, 0, d->timeout);
+ d->closeDevice();
+ } else {
+ qDebug() << "No scan to stop";
+ }
+ d->notifier = 0;
+ d->filter_uuids.clear();
+ hci_filter_clear(&d->hci_nf);
+ hci_filter_clear(&d->hci_of);
+}
+
+void GatoCentralManager::_q_readNotify()
+{
+ Q_D(GatoCentralManager);
+ unsigned char buf[HCI_MAX_EVENT_SIZE];
+
+ // Read a full event
+ int len;
+ if ((len = read(d->hci, buf, sizeof(buf))) < 0) {
+ if (errno != EAGAIN && errno != EINTR) {
+ qErrnoWarning("Could not read HCI events");
+ }
+ return; // Will be notified later, probably.
+ }
+
+ int pos = HCI_EVENT_HDR_SIZE + 1;
+ assert(pos < len);
+ evt_le_meta_event *meta = reinterpret_cast<evt_le_meta_event*>(&buf[pos]);
+
+
+ if (meta->subevent == EVT_LE_ADVERTISING_REPORT) {
+ pos++; // Skip subevent field
+ int num_reports = buf[pos];
+ pos++; // Skip num_reports field
+
+ assert(pos < len);
+
+ while (num_reports > 0) {
+ le_advertising_info *info = reinterpret_cast<le_advertising_info*>(&buf[pos]);
+ assert(pos + LE_ADVERTISING_INFO_SIZE < len);
+ assert(pos + LE_ADVERTISING_INFO_SIZE + info->length < len);
+ int8_t *rssi = reinterpret_cast<int8_t*>(&buf[pos + LE_ADVERTISING_INFO_SIZE + info->length]);
+
+ d->handleAdvertising(info, *rssi);
+
+ pos += LE_ADVERTISING_INFO_SIZE + info->length + 1;
+ num_reports--;
+ }
+ }
+}
+
+bool GatoCentralManagerPrivate::scanning()
+{
+ // If the HCI device is open for any reason, it means we're scanning.
+ return hci != -1;
+}
+
+bool GatoCentralManagerPrivate::openDevice()
+{
+ hci = hci_open_dev(dev_id);
+
+ if (hci == -1) {
+ qErrnoWarning("Could not open device");
+ return false;
+ }
+
+ return true;
+}
+
+void GatoCentralManagerPrivate::closeDevice()
+{
+ hci_close_dev(hci);
+ hci = -1;
+}
+
+void GatoCentralManagerPrivate::handleAdvertising(le_advertising_info *info, int rssi)
+{
+ Q_Q(GatoCentralManager);
+
+ qDebug() << "Advertising event type" << info->evt_type
+ << "address type" << info->bdaddr_type
+ << "data length" << info->length
+ << "rssi" << rssi;
+
+ GatoAddress addr(info->bdaddr.b);
+ GatoPeripheral *peripheral;
+ QHash<GatoAddress, GatoPeripheral*>::iterator it = peripherals.find(addr);
+ if (it == peripherals.end()) {
+ peripheral = new GatoPeripheral(addr, q);
+ peripherals.insert(addr, peripheral);
+ } else {
+ peripheral = *it;
+ }
+
+ if (info->length > 0) {
+ peripheral->parseEIR(info->data, info->length);
+ }
+
+ emit q->discoveredPeripheral(peripheral, rssi);
+}
diff --git a/gatocentralmanager.h b/gatocentralmanager.h
new file mode 100644
index 0000000..00225a0
--- /dev/null
+++ b/gatocentralmanager.h
@@ -0,0 +1,44 @@
+#ifndef GATOCENTRALMANAGER_H
+#define GATOCENTRALMANAGER_H
+
+#include <QtCore/QObject>
+#include "libgato_global.h"
+#include "gatouuid.h"
+
+class GatoPeripheral;
+class GatoCentralManagerPrivate;
+
+class LIBGATO_EXPORT GatoCentralManager : public QObject
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(GatoCentralManager)
+ Q_FLAGS(PeripheralScanOptions)
+
+public:
+ enum PeripheralScanOption {
+ PeripheralScanOptionActive = 1 << 0,
+ PeripheralScanOptionAllowDuplicates = 1 << 1
+ };
+ Q_DECLARE_FLAGS(PeripheralScanOptions, PeripheralScanOption)
+
+ explicit GatoCentralManager(QObject *parent = 0);
+ ~GatoCentralManager();
+
+public slots:
+ void scanForPeripherals(PeripheralScanOptions options = 0);
+ void scanForPeripheralsWithServices(const QList<GatoUUID>& uuids, PeripheralScanOptions options = 0);
+ void stopScan();
+
+signals:
+ void discoveredPeripheral(GatoPeripheral *peripheral, int rssi);
+
+private slots:
+ void _q_readNotify();
+
+private:
+ GatoCentralManagerPrivate *const d_ptr;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(GatoCentralManager::PeripheralScanOptions)
+
+#endif // GATOCENTRALMANAGER_H
diff --git a/gatocentralmanager_p.h b/gatocentralmanager_p.h
new file mode 100644
index 0000000..62ef954
--- /dev/null
+++ b/gatocentralmanager_p.h
@@ -0,0 +1,32 @@
+#ifndef GATOCENTRALMANAGER_P_H
+#define GATOCENTRALMANAGER_P_H
+
+#include <QtCore/QSocketNotifier>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+
+#include "gatocentralmanager.h"
+#include "gatoaddress.h"
+
+class GatoCentralManagerPrivate
+{
+ Q_DECLARE_PUBLIC(GatoCentralManager)
+
+ GatoCentralManager *q_ptr;
+ int dev_id;
+ int timeout;
+ int hci;
+ QSocketNotifier *notifier;
+ QList<GatoUUID> filter_uuids;
+ hci_filter hci_nf, hci_of;
+ QHash<GatoAddress, GatoPeripheral*> peripherals;
+
+ bool scanning();
+ bool openDevice();
+ void closeDevice();
+
+ void handleAdvertising(le_advertising_info *info, int rssi);
+};
+
+#endif // GATOCENTRALMANAGER_P_H
diff --git a/gatocharacteristic.cpp b/gatocharacteristic.cpp
new file mode 100644
index 0000000..a95c4a2
--- /dev/null
+++ b/gatocharacteristic.cpp
@@ -0,0 +1,159 @@
+/*
+ * libgato - A GATT/ATT library for use with Bluez
+ *
+ * Copyright (C) 2013 Javier S. Pedro <maemo@javispedro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <QtCore/QSharedData>
+
+#include "gatocharacteristic.h"
+#include "gatodescriptor.h"
+
+struct GatoCharacteristicPrivate : public QSharedData
+{
+ GatoUUID uuid;
+ GatoHandle start;
+ GatoHandle end;
+ GatoCharacteristic::Properties properties;
+ GatoHandle value_handle;
+ QMap<GatoHandle, GatoDescriptor> descriptors;
+ QHash<GatoUUID, GatoHandle> desc_uuids;
+};
+
+GatoCharacteristic::GatoCharacteristic()
+ : d(new GatoCharacteristicPrivate)
+{
+ d->start = 0;
+ d->end = 0;
+ d->properties = 0;
+ d->value_handle = 0;
+}
+
+GatoCharacteristic::GatoCharacteristic(const GatoCharacteristic &o)
+ : d(o.d)
+{
+}
+
+GatoCharacteristic::~GatoCharacteristic()
+{
+}
+
+bool GatoCharacteristic::isNull() const
+{
+ return d->start == 0 || d->end == 0 || d->uuid.isNull();
+}
+
+GatoUUID GatoCharacteristic::uuid() const
+{
+ return d->uuid;
+}
+
+void GatoCharacteristic::setUuid(const GatoUUID &uuid)
+{
+ d->uuid = uuid;
+}
+
+GatoCharacteristic::Properties GatoCharacteristic::properties() const
+{
+ return d->properties;
+}
+
+void GatoCharacteristic::setProperties(GatoCharacteristic::Properties props)
+{
+ d->properties = props;
+}
+
+GatoHandle GatoCharacteristic::startHandle() const
+{
+ return d->start;
+}
+
+void GatoCharacteristic::setStartHandle(GatoHandle handle)
+{
+ d->start = handle;
+}
+
+GatoHandle GatoCharacteristic::endHandle() const
+{
+ return d->end;
+}
+
+void GatoCharacteristic::setEndHandle(GatoHandle handle)
+{
+ d->end = handle;
+}
+
+GatoHandle GatoCharacteristic::valueHandle() const
+{
+ return d->value_handle;
+}
+
+void GatoCharacteristic::setValueHandle(GatoHandle handle)
+{
+ d->value_handle = handle;
+}
+
+QList<GatoDescriptor> GatoCharacteristic::descriptors() const
+{
+ return d->descriptors.values();
+}
+
+bool GatoCharacteristic::containsDescriptor(GatoUUID uuid) const
+{
+ return d->desc_uuids.contains(uuid);
+}
+
+bool GatoCharacteristic::containsDescriptor(GatoHandle handle) const
+{
+ return d->descriptors.contains(handle);
+}
+
+GatoDescriptor GatoCharacteristic::getDescriptor(GatoUUID uuid) const
+{
+ return d->descriptors.value(d->desc_uuids.value(uuid));
+}
+
+GatoDescriptor GatoCharacteristic::getDescriptor(GatoHandle handle) const
+{
+ return d->descriptors.value(handle);
+}
+
+void GatoCharacteristic::addDescriptor(const GatoDescriptor &descriptor)
+{
+ d->descriptors.insert(descriptor.handle(), descriptor);
+ d->desc_uuids.insert(descriptor.uuid(), descriptor.handle());
+}
+
+void GatoCharacteristic::removeDescriptor(const GatoDescriptor &descriptor)
+{
+ d->descriptors.remove(descriptor.handle());
+ d->desc_uuids.remove(descriptor.uuid());
+}
+
+void GatoCharacteristic::clearDescriptors()
+{
+ d->descriptors.clear();
+ d->desc_uuids.clear();
+}
+
+GatoCharacteristic &GatoCharacteristic::operator=(const GatoCharacteristic &o)
+{
+ if (this != &o) {
+ d = o.d;
+ }
+ return *this;
+}
diff --git a/gatocharacteristic.h b/gatocharacteristic.h
new file mode 100644
index 0000000..2b2f457
--- /dev/null
+++ b/gatocharacteristic.h
@@ -0,0 +1,69 @@
+#ifndef GATOCHARACTERISTIC_H
+#define GATOCHARACTERISTIC_H
+
+#include <QtCore/QObject>
+#include <QtCore/QSharedDataPointer>
+
+#include "gatouuid.h"
+
+class GatoCharacteristicPrivate;
+class GatoDescriptor;
+
+class LIBGATO_EXPORT GatoCharacteristic
+{
+ Q_GADGET
+ Q_FLAGS(CharacteristicProperties)
+
+public:
+ enum Property {
+ PropertyBroadcast = 0x1,
+ PropertyRead = 0x2,
+ PropertyWriteWithoutResponse = 0x4,
+ PropertyWrite = 0x5,
+ PropertyNotify = 0x10,
+ PropertyIndicate = 0x20,
+ PropertyAuthenticatedSignedWrites = 0x40,
+ PropertyExtendedProperties = 0x80
+ };
+ Q_DECLARE_FLAGS(Properties, Property)
+
+ GatoCharacteristic();
+ GatoCharacteristic(const GatoCharacteristic &o);
+ ~GatoCharacteristic();
+
+ bool isNull() const;
+
+ GatoUUID uuid() const;
+ void setUuid(const GatoUUID &uuid);
+
+ Properties properties() const;
+ void setProperties(Properties props);
+
+ GatoHandle startHandle() const;
+ void setStartHandle(GatoHandle handle);
+
+ GatoHandle endHandle() const;
+ void setEndHandle(GatoHandle handle);
+
+ GatoHandle valueHandle() const;
+ void setValueHandle(GatoHandle handle);
+
+ QList<GatoDescriptor> descriptors() const;
+ bool containsDescriptor(const GatoDescriptor& descriptor) const;
+ bool containsDescriptor(GatoUUID uuid) const;
+ bool containsDescriptor(GatoHandle handle) const;
+ GatoDescriptor getDescriptor(GatoUUID uuid) const;
+ GatoDescriptor getDescriptor(GatoHandle handle) const;
+ void addDescriptor(const GatoDescriptor& descriptor);
+ void removeDescriptor(const GatoDescriptor& descriptor);
+ void clearDescriptors();
+
+ GatoCharacteristic &operator=(const GatoCharacteristic &o);
+
+private:
+ QSharedDataPointer<GatoCharacteristicPrivate> d;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(GatoCharacteristic::Properties)
+
+#endif // GATOCHARACTERISTIC_H
diff --git a/gatodescriptor.cpp b/gatodescriptor.cpp
new file mode 100644
index 0000000..dda6afd
--- /dev/null
+++ b/gatodescriptor.cpp
@@ -0,0 +1,72 @@
+/*
+ * libgato - A GATT/ATT library for use with Bluez
+ *
+ * Copyright (C) 2013 Javier S. Pedro <maemo@javispedro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <QtCore/QSharedData>
+
+#include "gatodescriptor.h"
+
+struct GatoDescriptorPrivate : public QSharedData
+{
+ GatoUUID uuid;
+ GatoHandle handle;
+};
+
+GatoDescriptor::GatoDescriptor()
+ : d(new GatoDescriptorPrivate)
+{
+ d->handle = 0;
+}
+
+GatoDescriptor::GatoDescriptor(const GatoDescriptor &o)
+ : d(o.d)
+{
+}
+
+GatoDescriptor::~GatoDescriptor()
+{
+}
+
+GatoUUID GatoDescriptor::uuid() const
+{
+ return d->uuid;
+}
+
+void GatoDescriptor::setUuid(const GatoUUID &uuid)
+{
+ d->uuid = uuid;
+}
+
+GatoHandle GatoDescriptor::handle() const
+{
+ return d->handle;
+}
+
+void GatoDescriptor::setHandle(GatoHandle handle)
+{
+ d->handle = handle;
+}
+
+GatoDescriptor &GatoDescriptor::operator=(const GatoDescriptor &o)
+{
+ if (this != &o) {
+ d = o.d;
+ }
+ return *this;
+}
diff --git a/gatodescriptor.h b/gatodescriptor.h
new file mode 100644
index 0000000..7ced38a
--- /dev/null
+++ b/gatodescriptor.h
@@ -0,0 +1,32 @@
+#ifndef GATODESCRIPTOR_H
+#define GATODESCRIPTOR_H
+
+#include <QtCore/QObject>
+#include <QtCore/QSharedDataPointer>
+
+#include "gatouuid.h"
+
+class GatoDescriptorPrivate;
+
+class LIBGATO_EXPORT GatoDescriptor
+{
+ Q_GADGET
+
+public:
+ GatoDescriptor();
+ GatoDescriptor(const GatoDescriptor &o);
+ ~GatoDescriptor();
+
+ GatoUUID uuid() const;
+ void setUuid(const GatoUUID &uuid);
+
+ GatoHandle handle() const;
+ void setHandle(GatoHandle handle);
+
+ GatoDescriptor &operator=(const GatoDescriptor &o);
+
+private:
+ QSharedDataPointer<GatoDescriptorPrivate> d;
+};
+
+#endif // GATODESCRIPTOR_H
diff --git a/gatoperipheral.cpp b/gatoperipheral.cpp
new file mode 100644
index 0000000..faec8cd
--- /dev/null
+++ b/gatoperipheral.cpp
@@ -0,0 +1,834 @@
+/*
+ * libgato - A GATT/ATT library for use with Bluez
+ *
+ * Copyright (C) 2013 Javier S. Pedro <maemo@javispedro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <QtCore/QDebug>
+
+#include <assert.h>
+#include <bluetooth/bluetooth.h>
+
+#include "gatoperipheral_p.h"
+#include "gatoaddress.h"
+#include "gatouuid.h"
+#include "helpers.h"
+
+enum EIRDataFields {
+ EIRFlags = 0x01,
+ EIRIncompleteUUID16List = 0x02,
+ EIRCompleteUUID16List = 0x03,
+ EIRIncompleteUUID32List = 0x04,
+ EIRCompleteUUID32List = 0x05,
+ EIRIncompleteUUID128List = 0x06,
+ EIRCompleteUUID128List = 0x07,
+ EIRIncompleteLocalName = 0x08,
+ EIRCompleteLocalName = 0x09,
+ EIRTxPowerLevel = 0x0A,
+ EIRDeviceClass = 0x0D,
+ EIRSecurityManagerTKValue = 0x10,
+ EIRSecurityManagerOutOfBandFlags = 0x11
+};
+
+GatoPeripheral::GatoPeripheral(const GatoAddress &addr, QObject *parent) :
+ QObject(parent), d_ptr(new GatoPeripheralPrivate(this))
+{
+ Q_D(GatoPeripheral);
+ d->addr = addr;
+ d->att = new GatoAtt(this);
+
+ connect(d->att, SIGNAL(connected()), d, SLOT(handleAttConnected()));
+ connect(d->att, SIGNAL(disconnected()), d, SLOT(handleAttDisconnected()));
+ connect(d->att, SIGNAL(attributeUpdated(GatoHandle,QByteArray,bool)), d, SLOT(handleAttAttributeUpdated(GatoHandle,QByteArray,bool)));
+}
+
+GatoPeripheral::~GatoPeripheral()
+{
+ if (state() != StateDisconnected) {
+ disconnect();
+ }
+ delete d_ptr;
+}
+
+GatoPeripheral::State GatoPeripheral::state() const
+{
+ Q_D(const GatoPeripheral);
+ return static_cast<State>(d->att->state());
+}
+
+GatoAddress GatoPeripheral::address() const
+{
+ Q_D(const GatoPeripheral);
+ return d->addr;
+}
+
+QString GatoPeripheral::name() const
+{
+ Q_D(const GatoPeripheral);
+ return d->name;
+}
+
+QList<GatoService> GatoPeripheral::services() const
+{
+ Q_D(const GatoPeripheral);
+ return d->services.values();
+}
+
+void GatoPeripheral::parseEIR(quint8 data[], int len)
+{
+ Q_D(GatoPeripheral);
+
+ int pos = 0;
+ while (pos < len) {
+ int item_len = data[pos];
+ pos++;
+ if (item_len == 0) break;
+ int type = data[pos];
+ assert(pos + item_len <= len);
+ switch (type) {
+ case EIRFlags:
+ d->parseEIRFlags(&data[pos + 1], item_len - 1);
+ break;
+ case EIRIncompleteUUID16List:
+ d->parseEIRUUIDs(16/8, false, &data[pos + 1], item_len - 1);
+ break;
+ case EIRCompleteUUID16List:
+ d->parseEIRUUIDs(16/8, true, &data[pos + 1], item_len - 1);
+ break;
+ case EIRIncompleteUUID32List:
+ d->parseEIRUUIDs(32/8, false, &data[pos + 1], item_len - 1);
+ break;
+ case EIRCompleteUUID32List:
+ d->parseEIRUUIDs(32/8, true, &data[pos + 1], item_len - 1);
+ break;
+ case EIRIncompleteUUID128List:
+ d->parseEIRUUIDs(128/8, false, &data[pos + 1], item_len - 1);
+ break;
+ case EIRCompleteUUID128List:
+ d->parseEIRUUIDs(128/8, true, &data[pos + 1], item_len - 1);
+ break;
+ case EIRIncompleteLocalName:
+ d->parseName(false, &data[pos + 1], item_len - 1);
+ break;
+ case EIRCompleteLocalName:
+ d->parseName(true, &data[pos + 1], item_len - 1);
+ break;
+ default:
+ qWarning() << "Unknown EIR data type" << type;
+ break;
+ }
+
+ pos += item_len;
+ }
+
+ assert(pos == len);
+}
+
+void GatoPeripheral::connectPeripheral()
+{
+ Q_D(GatoPeripheral);
+ if (d->att->state() != GatoSocket::StateDisconnected) {
+ qDebug() << "Already connecting";
+ return;
+ }
+
+ d->att->connectTo(d->addr);
+}
+
+void GatoPeripheral::disconnectPeripheral()
+{
+ Q_D(GatoPeripheral);
+
+ d->att->close();
+}
+
+void GatoPeripheral::discoverServices()
+{
+ Q_D(GatoPeripheral);
+ if (!d->complete_services && state() == StateConnected) {
+ d->clearServices();
+ d->att->requestReadByGroupType(0x0001, 0xFFFF, GatoUUID::GattPrimaryService,
+ d, SLOT(handlePrimary(QList<GatoAtt::AttributeGroupData>)));
+ } else {
+ qWarning() << "Not connected";
+ }
+}
+
+void GatoPeripheral::discoverServices(const QList<GatoUUID> &serviceUUIDs)
+{
+ Q_D(GatoPeripheral);
+ if (serviceUUIDs.isEmpty()) return;
+ if (state() == StateConnected) {
+ foreach (const GatoUUID& uuid, serviceUUIDs) {
+ QByteArray value = gatouuid_to_bytearray(uuid, true, false);
+ uint req = d->att->requestFindByTypeValue(0x0001, 0xFFFF, GatoUUID::GattPrimaryService, value,
+ d, SLOT(handlePrimaryForService(uint,QList<GatoAtt::HandleInformation>)));
+ d->pending_primary_reqs.insert(req, uuid);
+ }
+ } else {
+ qWarning() << "Not connected";
+ }
+}
+
+void GatoPeripheral::discoverCharacteristics(const GatoService &service)
+{
+ Q_D(GatoPeripheral);
+
+ if (!d->services.contains(service.startHandle())) {
+ qWarning() << "Unknown service for this peripheral";
+ return;
+ }
+
+ GatoService &our_service = d->services[service.startHandle()];
+
+ if (our_service.startHandle() != service.startHandle() ||
+ our_service.endHandle() != service.endHandle() ||
+ our_service.uuid() != service.uuid()) {
+ qWarning() << "Unknown service for this peripheral";
+ return;
+ }
+
+ if (state() == StateConnected) {
+ GatoHandle start = our_service.startHandle();
+ GatoHandle end = our_service.endHandle();
+
+ d->clearServiceCharacteristics(&our_service);
+
+ uint req = d->att->requestReadByType(start, end, GatoUUID::GattCharacteristic,
+ d, SLOT(handleCharacteristic(QList<GatoAtt::AttributeData>)));
+ d->pending_characteristic_reqs.insert(req, start);
+ } else {
+ qWarning() << "Not connected";
+ }
+}
+
+void GatoPeripheral::discoverCharacteristics(const GatoService &service, const QList<GatoUUID> &characteristicUUIDs)
+{
+ // TODO There seems to be no way to ask for the peripheral to filter by uuid
+ Q_UNUSED(characteristicUUIDs);
+ discoverCharacteristics(service);
+}
+
+void GatoPeripheral::discoverDescriptors(const GatoCharacteristic &characteristic)
+{
+ Q_D(GatoPeripheral);
+
+ GatoHandle char_handle = characteristic.startHandle();
+ GatoHandle service_handle = d->characteristic_to_service.value(char_handle);
+
+ if (!service_handle) {
+ qWarning() << "Unknown characteristic for this peripheral";
+ return;
+ }
+
+ GatoService &our_service = d->services[service_handle];
+ Q_ASSERT(our_service.containsCharacteristic(char_handle));
+ GatoCharacteristic our_char = our_service.getCharacteristic(char_handle);
+ Q_ASSERT(our_char.startHandle() == char_handle);
+
+ if (state() == StateConnected) {
+ d->clearCharacteristicDescriptors(&our_char);
+ our_service.addCharacteristic(our_char); // Update service with empty descriptors list
+ uint req = d->att->requestFindInformation(our_char.startHandle() + 1, our_char.endHandle(),
+ d, SLOT(handleDescriptors(uint,QList<GatoAtt::InformationData>)));
+ d->pending_descriptor_reqs.insert(req, char_handle);
+ } else {
+ qWarning() << "Not connected";
+ }
+}
+
+void GatoPeripheral::readValue(const GatoCharacteristic &characteristic)
+{
+ Q_D(GatoPeripheral);
+
+ GatoHandle char_handle = characteristic.startHandle();
+ GatoHandle service_handle = d->characteristic_to_service.value(char_handle);
+
+ if (!service_handle) {
+ qWarning() << "Unknown characteristic for this peripheral";
+ return;
+ }
+
+ GatoService &our_service = d->services[service_handle];
+ Q_ASSERT(our_service.containsCharacteristic(char_handle));
+
+ if (state() == StateConnected) {
+ uint req = d->att->requestRead(characteristic.valueHandle(),
+ d, SLOT(handleCharacteristicRead(uint,QByteArray)));
+ d->pending_characteristic_read_reqs.insert(req, char_handle);
+ } else {
+ qWarning() << "Not connected";
+ }
+}
+
+void GatoPeripheral::readValue(const GatoDescriptor &descriptor)
+{
+ Q_D(GatoPeripheral);
+
+ GatoHandle desc_handle = descriptor.handle();
+ GatoHandle char_handle = d->descriptor_to_characteristic.value(desc_handle);
+
+ if (!char_handle) {
+ qWarning() << "Unknown descriptor for this peripheral";
+ return;
+ }
+
+ GatoHandle service_handle = d->characteristic_to_service.value(char_handle);
+ Q_ASSERT(service_handle);
+
+ GatoService &our_service = d->services[service_handle];
+ Q_ASSERT(our_service.containsCharacteristic(char_handle));
+
+ if (state() == StateConnected) {
+ uint req = d->att->requestRead(descriptor.handle(),
+ d, SLOT(handleDescriptorRead(uint,QByteArray)));
+ d->pending_descriptor_read_reqs.insert(req, char_handle);
+ } else {
+ qWarning() << "Not connected";
+ }
+}
+
+void GatoPeripheral::writeValue(const GatoCharacteristic &characteristic, const QByteArray &data)
+{
+ Q_D(GatoPeripheral);
+
+ GatoHandle char_handle = characteristic.startHandle();
+ GatoHandle service_handle = d->characteristic_to_service.value(char_handle);
+
+ if (!service_handle) {
+ qWarning() << "Unknown characteristic for this peripheral";
+ return;
+ }
+
+ GatoService &our_service = d->services[service_handle];
+ Q_ASSERT(our_service.containsCharacteristic(char_handle));
+
+ if (state() == StateConnected) {
+ d->att->requestWrite(characteristic.valueHandle(), data,
+ d, SLOT(handleCharacteristicRead(uint,QByteArray)));
+ } else {
+ qWarning() << "Not connected";
+ }
+}
+
+void GatoPeripheral::writeValue(const GatoDescriptor &descriptor, const QByteArray &data)
+{
+ Q_D(GatoPeripheral);
+
+ GatoHandle desc_handle = descriptor.handle();
+ GatoHandle char_handle = d->descriptor_to_characteristic.value(desc_handle);
+
+ if (!char_handle) {
+ qWarning() << "Unknown descriptor for this peripheral";
+ return;
+ }
+
+ GatoHandle service_handle = d->characteristic_to_service.value(char_handle);
+ Q_ASSERT(service_handle);
+
+ GatoService &our_service = d->services[service_handle];
+ Q_ASSERT(our_service.containsCharacteristic(char_handle));
+
+ if (state() == StateConnected) {
+ d->att->requestWrite(descriptor.handle(), data,
+ d, SLOT(handleDescriptorWrite(uint,bool)));
+ } else {
+ qWarning() << "Not connected";
+ }
+}
+
+void GatoPeripheral::setNotification(const GatoCharacteristic &characteristic, bool enabled)
+{
+ Q_D(GatoPeripheral);
+
+ GatoHandle char_handle = characteristic.startHandle();
+ GatoHandle service_handle = d->characteristic_to_service.value(char_handle);
+
+ if (!service_handle) {
+ qWarning() << "Unknown characteristic for this peripheral";
+ return;
+ }
+
+ GatoService &our_service = d->services[service_handle];
+ Q_ASSERT(our_service.containsCharacteristic(char_handle));
+ GatoCharacteristic our_char = our_service.getCharacteristic(char_handle);
+
+ if (!(our_char.properties() & GatoCharacteristic::PropertyNotify)) {
+ qWarning() << "Characteristic does not support notifications";
+ return;
+ }
+
+ if (state() != StateConnected) {
+ qWarning() << "Not connected";
+ return;
+ }
+
+ const GatoUUID uuid(GatoUUID::GattClientCharacteristicConfiguration);
+ if (our_char.containsDescriptor(uuid)) {
+ GatoDescriptor desc = our_char.getDescriptor(uuid);
+ d->pending_set_notify.remove(char_handle);
+ writeValue(characteristic, d->genClientCharConfiguration(true, false));
+ } else {
+ d->pending_set_notify[char_handle] = enabled;
+ discoverDescriptors(our_char); // May need to find appropiate descriptor
+ }
+}
+
+GatoPeripheralPrivate::GatoPeripheralPrivate(GatoPeripheral *parent)
+ : QObject(parent), q_ptr(parent),
+ complete_name(false), complete_services(false)
+{
+}
+
+GatoPeripheralPrivate::~GatoPeripheralPrivate()
+{
+ delete att;
+}
+
+void GatoPeripheralPrivate::parseEIRFlags(quint8 data[], int len)
+{
+ Q_UNUSED(data);
+ Q_UNUSED(len);
+ // Nothing to do for now.
+}
+
+void GatoPeripheralPrivate::parseEIRUUIDs(int size, bool complete, quint8 data[], int len)
+{
+ Q_UNUSED(complete);
+
+ for (int pos = 0; pos < len; pos += size) {
+ GatoUUID uuid;
+ switch (size) {
+ case 16/8:
+ uuid = GatoUUID(qFromLittleEndian<quint16>(&data[pos]));
+ break;
+ case 32/8:
+ uuid = GatoUUID(qFromLittleEndian<quint32>(&data[pos]));
+ break;
+ case 128/8:
+ uuid = GatoUUID(qFromLittleEndian<gatouint128>(&data[pos]));
+ break;
+ }
+
+ service_uuids.insert(uuid);
+ }
+}
+
+void GatoPeripheralPrivate::parseName(bool complete, quint8 data[], int len)
+{
+ Q_Q(GatoPeripheral);
+ if (complete || !complete_name) {
+ name = QString::fromAscii(reinterpret_cast<char*>(data), len);
+ complete_name = complete;
+ emit q->nameChanged();
+ }
+}
+
+GatoCharacteristic GatoPeripheralPrivate::parseCharacteristicValue(const QByteArray &ba)
+{
+ GatoCharacteristic characteristic;
+ const char *data = ba.constData();
+
+ quint8 properties = data[0];
+ characteristic.setProperties(GatoCharacteristic::Properties(properties));
+
+ GatoHandle handle = read_le<quint16>(&data[1]);
+ characteristic.setValueHandle(handle);
+
+ GatoUUID uuid = bytearray_to_gatouuid(ba.mid(3));
+ characteristic.setUuid(uuid);
+
+ return characteristic;
+}
+
+QByteArray GatoPeripheralPrivate::genClientCharConfiguration(bool notification, bool indication)
+{
+ QByteArray ba;
+ ba.resize(sizeof(quint16));
+
+ quint16 val = 0;
+ if (notification)
+ val |= 0x1;
+ if (indication)
+ val |= 0x2;
+
+ write_le<quint16>(val, ba.data());
+
+ return ba;
+}
+
+void GatoPeripheralPrivate::clearServices()
+{
+ characteristic_to_service.clear();
+ value_to_characteristic.clear();
+ descriptor_to_characteristic.clear();
+ services.clear();
+}
+
+void GatoPeripheralPrivate::clearServiceCharacteristics(GatoService *service)
+{
+ QList<GatoCharacteristic> chars = service->characteristics();
+ QList<GatoCharacteristic>::iterator it;
+ for (it = chars.begin(); it != chars.end(); ++it) {
+ clearCharacteristicDescriptors(&*it);
+ characteristic_to_service.remove(it->startHandle());
+ value_to_characteristic.remove(it->valueHandle());
+ }
+ service->clearCharacteristics();
+}
+
+void GatoPeripheralPrivate::clearCharacteristicDescriptors(GatoCharacteristic *characteristic)
+{
+ QList<GatoDescriptor> descs = characteristic->descriptors();
+ foreach (const GatoDescriptor& d, descs) {
+ descriptor_to_characteristic.remove(d.handle());
+ }
+ characteristic->clearDescriptors();
+}
+
+void GatoPeripheralPrivate::finishSetNotifyOperations(const GatoCharacteristic &characteristic)
+{
+ Q_Q(GatoPeripheral);
+
+ GatoHandle handle = characteristic.startHandle();
+
+ if (pending_set_notify.contains(handle)) {
+ const GatoUUID uuid(GatoUUID::GattClientCharacteristicConfiguration);
+ bool notify = pending_set_notify.value(handle);
+
+ foreach (const GatoDescriptor &descriptor, characteristic.descriptors()) {
+ if (descriptor.uuid() == uuid) {
+ q->writeValue(descriptor, genClientCharConfiguration(notify, false));
+ }
+ }
+
+ pending_set_notify.remove(handle);
+ }
+}
+
+void GatoPeripheralPrivate::handleAttConnected()
+{
+ Q_Q(GatoPeripheral);
+
+ emit q->connected();
+}
+
+void GatoPeripheralPrivate::handleAttDisconnected()
+{
+ Q_Q(GatoPeripheral);
+
+ // Forget about all pending requests
+ pending_primary_reqs.clear();
+ pending_characteristic_reqs.clear();
+ pending_characteristic_read_reqs.clear();
+ pending_descriptor_reqs.clear();
+ pending_descriptor_read_reqs.clear();
+
+ emit q->disconnected();
+}
+
+void GatoPeripheralPrivate::handleAttAttributeUpdated(GatoHandle handle, const QByteArray &value, bool confirmed)
+{
+ Q_Q(GatoPeripheral);
+ Q_UNUSED(confirmed);
+
+ // Let's see if this is a handle we know about.
+ if (value_to_characteristic.contains(handle)) {
+ // Ok, it's a characteristic value.
+ GatoHandle char_handle = value_to_characteristic.value(handle);
+ GatoHandle service_handle = characteristic_to_service.value(char_handle);
+ if (!service_handle) {
+ qWarning() << "Got a notification for a characteristic I don't know about";
+ return;
+ }
+
+ GatoService &service = services[service_handle];
+ GatoCharacteristic characteristic = service.getCharacteristic(char_handle);
+
+ emit q->valueUpdated(characteristic, value);
+ }
+}
+
+void GatoPeripheralPrivate::handlePrimary(uint req, const QList<GatoAtt::AttributeGroupData> &list)
+{
+ Q_Q(GatoPeripheral);
+ Q_UNUSED(req);
+
+ if (list.isEmpty()) {
+ complete_services = true;
+ emit q->servicesDiscovered();
+ } else {
+ GatoHandle last_handle = 0;
+
+ foreach (const GatoAtt::AttributeGroupData &data, list) {
+ GatoUUID uuid = bytearray_to_gatouuid(data.value);
+ GatoService service;
+
+ service.setUuid(uuid);
+ service.setStartHandle(data.start);
+ service.setEndHandle(data.end);
+
+ services.insert(data.start, service);
+ service_uuids.insert(uuid);
+
+ last_handle = data.end;
+ }
+
+ // Fetch following attributes
+ att->requestReadByGroupType(last_handle + 1, 0xFFFF, GatoUUID::GattPrimaryService,
+ this, SLOT(handlePrimary(uint,QList<GatoAtt::AttributeGroupData>)));
+ }
+}
+
+void GatoPeripheralPrivate::handlePrimaryForService(uint req, const QList<GatoAtt::HandleInformation> &list)
+{
+ Q_Q(GatoPeripheral);
+
+ GatoUUID uuid = pending_primary_reqs.value(req, GatoUUID());
+ if (uuid.isNull()) {
+ qDebug() << "Got primary for service response for a request I did not make";
+ return;
+ }
+ pending_primary_reqs.remove(req);
+
+ if (list.isEmpty()) {
+ if (pending_primary_reqs.isEmpty()) {
+ emit q->servicesDiscovered();
+ }
+ } else {
+ GatoHandle last_handle = 0;
+
+ foreach (const GatoAtt::HandleInformation &data, list) {
+ GatoService service;
+
+ service.setUuid(uuid);
+ service.setStartHandle(data.start);
+ service.setEndHandle(data.end);
+
+ services.insert(data.start, service);
+ service_uuids.insert(uuid);
+
+ last_handle = data.end;
+ }
+
+ // Fetch following attributes
+ QByteArray value = gatouuid_to_bytearray(uuid, true, false);
+ uint req = att->requestFindByTypeValue(last_handle + 1, 0xFFFF, GatoUUID::GattPrimaryService, value,
+ this, SLOT(handlePrimaryForService(uint,QList<GatoAtt::HandleInformation>)));
+ pending_primary_reqs.insert(req, uuid);
+ }
+}
+
+void GatoPeripheralPrivate::handleCharacteristic(uint req, const QList<GatoAtt::AttributeData> &list)
+{
+ Q_Q(GatoPeripheral);
+
+ GatoHandle service_start = pending_characteristic_reqs.value(req, 0);
+ if (!service_start) {
+ qDebug() << "Got characteristics for a request I did not make";
+ return;
+ }
+ pending_characteristic_reqs.remove(req);
+
+ Q_ASSERT(services.contains(service_start));
+ GatoService &service = services[service_start];
+ Q_ASSERT(service.startHandle() == service_start);
+
+ if (list.isEmpty()) {
+ emit q->characteristicsDiscovered(service);
+ } else {
+ GatoHandle last_handle = 0;
+
+ // If we are continuing a characteristic list, this means the
+ // last service we discovered in the previous iteration was not
+ // the last one, so we have to reduce its endHandle!
+ QList<GatoCharacteristic> cur_chars = service.characteristics();
+ if (!cur_chars.isEmpty()) {
+ GatoCharacteristic &last = cur_chars.back();
+ last.setEndHandle(list.front().handle - 1);
+ service.addCharacteristic(last);
+ }
+
+ for (int i = 0; i < list.size(); i++) {
+ const GatoAtt::AttributeData &data = list.at(i);
+ GatoCharacteristic characteristic = parseCharacteristicValue(data.value);
+
+ characteristic.setStartHandle(data.handle);
+ if (i + 1 < list.size()) {
+ characteristic.setEndHandle(list.at(i + 1).handle - 1);
+ } else {
+ characteristic.setEndHandle(service.endHandle());
+ }
+
+ service.addCharacteristic(characteristic);
+ characteristic_to_service.insert(data.handle, service_start);
+ value_to_characteristic.insert(characteristic.valueHandle(), data.handle);
+
+ last_handle = data.handle;
+ }
+
+ if (last_handle >= service.endHandle()) {
+ // Already finished, no need to send another request
+ emit q->characteristicsDiscovered(service);
+ return;
+ }
+
+ // Fetch following attributes
+ uint req = att->requestReadByType(last_handle + 1, service.endHandle(), GatoUUID::GattCharacteristic,
+ this, SLOT(handleCharacteristic(uint,QList<GatoAtt::AttributeData>)));
+ pending_characteristic_reqs.insert(req, service.startHandle());
+ }
+}
+
+void GatoPeripheralPrivate::handleDescriptors(uint req, const QList<GatoAtt::InformationData> &list)
+{
+ Q_Q(GatoPeripheral);
+
+ GatoHandle char_handle = pending_descriptor_reqs.value(req);
+ if (!char_handle) {
+ qDebug() << "Got descriptor for a request I did not make";
+ return;
+ }
+ pending_descriptor_reqs.remove(req);
+ GatoHandle service_handle = characteristic_to_service.value(char_handle);
+ if (!service_handle) {
+ qWarning() << "Unknown characteristic during descriptor discovery: " << char_handle;
+ return;
+ }
+
+ Q_ASSERT(services.contains(service_handle));
+ GatoService &service = services[service_handle];
+ Q_ASSERT(service.startHandle() == service_handle);
+
+ Q_ASSERT(service.containsCharacteristic(char_handle));
+ GatoCharacteristic characteristic = service.getCharacteristic(char_handle);
+
+ if (list.isEmpty()) {
+ finishSetNotifyOperations(characteristic);
+ emit q->descriptorsDiscovered(characteristic);
+ } else {
+ GatoHandle last_handle = 0;
+
+ foreach (const GatoAtt::InformationData &data, list) {
+ // Skip the value attribute itself.
+ if (data.handle == characteristic.valueHandle()) continue;
+
+ GatoDescriptor descriptor;
+
+ descriptor.setHandle(data.handle);
+ descriptor.setUuid(data.uuid);
+
+ characteristic.addDescriptor(descriptor);
+
+ service.addCharacteristic(characteristic);
+ descriptor_to_characteristic.insert(data.handle, char_handle);
+
+ last_handle = data.handle;
+ }
+
+ service.addCharacteristic(characteristic);
+
+ if (last_handle >= characteristic.endHandle()) {
+ // Already finished, no need to send another request
+ finishSetNotifyOperations(characteristic);
+ emit q->descriptorsDiscovered(characteristic);
+ return;
+ }
+
+ // Fetch following attributes
+ uint req = att->requestFindInformation(last_handle + 1, characteristic.endHandle(),
+ this, SLOT(handleDescriptors(uint,QList<GatoAtt::InformationData>)));
+ pending_descriptor_reqs.insert(req, char_handle);
+
+ }
+}
+
+void GatoPeripheralPrivate::handleCharacteristicRead(uint req, const QByteArray &value)
+{
+ Q_Q(GatoPeripheral);
+
+ GatoHandle char_handle = pending_characteristic_read_reqs.value(req);
+ if (!char_handle) {
+ qDebug() << "Got characteristics for a request I did not make";
+ return;
+ }
+ pending_characteristic_read_reqs.remove(req);
+ GatoHandle service_handle = characteristic_to_service.value(char_handle);
+ if (!service_handle) {
+ qWarning() << "Unknown characteristic during read: " << char_handle;
+ return;
+ }
+
+ Q_ASSERT(services.contains(service_handle));
+ GatoService &service = services[service_handle];
+ Q_ASSERT(service.startHandle() == service_handle);
+
+ Q_ASSERT(service.containsCharacteristic(char_handle));
+ GatoCharacteristic characteristic = service.getCharacteristic(char_handle);
+
+ emit q->valueUpdated(characteristic, value);
+}
+
+void GatoPeripheralPrivate::handleDescriptorRead(uint req, const QByteArray &value)
+{
+ Q_Q(GatoPeripheral);
+
+ GatoHandle desc_handle = pending_descriptor_read_reqs.value(req);
+ if (!desc_handle) {
+ qDebug() << "Got characteristics for a request I did not make";
+ return;
+ }
+ pending_descriptor_read_reqs.remove(req);
+ GatoHandle char_handle = descriptor_to_characteristic.value(desc_handle);
+ if (!char_handle) {
+ qWarning() << "Unknown characteristic during read: " << char_handle;
+ return;
+ }
+ GatoHandle service_handle = characteristic_to_service.value(char_handle);
+ if (!service_handle) {
+ qWarning() << "Unknown characteristic during read: " << char_handle;
+ return;
+ }
+
+ Q_ASSERT(services.contains(service_handle));
+ GatoService &service = services[service_handle];
+ Q_ASSERT(service.startHandle() == service_handle);
+
+ Q_ASSERT(service.containsCharacteristic(char_handle));
+ GatoCharacteristic characteristic = service.getCharacteristic(char_handle);
+
+ Q_ASSERT(characteristic.containsDescriptor(desc_handle));
+ GatoDescriptor descriptor = characteristic.getDescriptor(desc_handle);
+
+ emit q->descriptorValueUpdated(descriptor, value);
+}
+
+void GatoPeripheralPrivate::handleCharacteristicWrite(uint req, bool ok)
+{
+ Q_UNUSED(req);
+ if (!ok) {
+ qWarning() << "Failed to write some characteristic";
+ }
+}
+
+void GatoPeripheralPrivate::handleDescriptorWrite(uint req, bool ok)
+{
+ Q_UNUSED(req);
+ if (!ok) {
+ qWarning() << "Failed to write some characteristic";
+ }
+}
diff --git a/gatoperipheral.h b/gatoperipheral.h
new file mode 100644
index 0000000..6ad6782
--- /dev/null
+++ b/gatoperipheral.h
@@ -0,0 +1,67 @@
+#ifndef GATOPERIPHERAL_H
+#define GATOPERIPHERAL_H
+
+#include <QtCore/QObject>
+#include "libgato_global.h"
+#include "gatouuid.h"
+#include "gatoaddress.h"
+
+class GatoService;
+class GatoCharacteristic;
+class GatoDescriptor;
+class GatoPeripheralPrivate;
+
+class LIBGATO_EXPORT GatoPeripheral : public QObject
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(GatoPeripheral)
+ Q_ENUMS(State)
+ Q_PROPERTY(GatoAddress address READ address)
+ Q_PROPERTY(QString name READ name NOTIFY nameChanged)
+
+public:
+ GatoPeripheral(const GatoAddress& addr, QObject *parent = 0);
+ ~GatoPeripheral();
+
+ enum State {
+ StateDisconnected,
+ StateConnecting,
+ StateConnected
+ };
+
+ State state() const;
+ GatoAddress address() const;
+ QString name() const;
+ QList<GatoService> services() const;
+
+ void parseEIR(quint8 data[], int len);
+
+public slots:
+ void connectPeripheral();
+ void disconnectPeripheral();
+ void discoverServices();
+ void discoverServices(const QList<GatoUUID>& serviceUUIDs);
+ void discoverCharacteristics(const GatoService &service);
+ void discoverCharacteristics(const GatoService &service, const QList<GatoUUID>& characteristicUUIDs);
+ void discoverDescriptors(const GatoCharacteristic &characteristic);
+ void readValue(const GatoCharacteristic &characteristic);
+ void readValue(const GatoDescriptor &descriptor);
+ void writeValue(const GatoCharacteristic &characteristic, const QByteArray &data);
+ void writeValue(const GatoDescriptor &descriptor, const QByteArray &data);
+ void setNotification(const GatoCharacteristic &characteristic, bool enabled);
+
+signals:
+ void connected();
+ void disconnected();
+ void nameChanged();
+ void servicesDiscovered();
+ void characteristicsDiscovered(const GatoService &service);
+ void descriptorsDiscovered(const GatoCharacteristic &characteristic);
+ void valueUpdated(const GatoCharacteristic &characteristic, const QByteArray &value);
+ void descriptorValueUpdated(const GatoDescriptor &descriptor, const QByteArray &value);
+
+private:
+ GatoPeripheralPrivate *const d_ptr;
+};
+
+#endif // GATOPERIPHERAL_H
diff --git a/gatoperipheral_p.h b/gatoperipheral_p.h
new file mode 100644
index 0000000..895270e
--- /dev/null
+++ b/gatoperipheral_p.h
@@ -0,0 +1,71 @@
+#ifndef GATOPERIPHERAL_P_H
+#define GATOPERIPHERAL_P_H
+
+#include "gatoperipheral.h"
+#include "gatoservice.h"
+#include "gatocharacteristic.h"
+#include "gatodescriptor.h"
+#include "gatoatt.h"
+
+class GatoPeripheralPrivate : public QObject
+{
+ Q_OBJECT
+
+ Q_DECLARE_PUBLIC(GatoPeripheral)
+
+public:
+ GatoPeripheralPrivate(GatoPeripheral *parent);
+ ~GatoPeripheralPrivate();
+
+ GatoPeripheral *q_ptr;
+ GatoAddress addr;
+ QString name;
+ QSet<GatoUUID> service_uuids;
+ QMap<GatoHandle, GatoService> services;
+
+ bool complete_name : 1;
+ bool complete_services : 1;
+
+ /** Maps attribute handles to service handles. */
+ QMap<GatoHandle, GatoHandle> characteristic_to_service;
+ QMap<GatoHandle, GatoHandle> value_to_characteristic;
+ QMap<GatoHandle, GatoHandle> descriptor_to_characteristic;
+
+ GatoAtt *att;
+ QMap<uint, GatoUUID> pending_primary_reqs;
+ QMap<uint, GatoHandle> pending_characteristic_reqs;
+ QMap<uint, GatoHandle> pending_characteristic_read_reqs;
+ QMap<uint, GatoHandle> pending_descriptor_reqs;
+ QMap<uint, GatoHandle> pending_descriptor_read_reqs;
+
+ QMap<GatoHandle, bool> pending_set_notify;
+
+ void parseEIRFlags(quint8 data[], int len);
+ void parseEIRUUIDs(int size, bool complete, quint8 data[], int len);
+ void parseName(bool complete, quint8 data[], int len);
+
+ static GatoCharacteristic parseCharacteristicValue(const QByteArray &ba);
+
+ static QByteArray genClientCharConfiguration(bool notification, bool indication);
+
+ void clearServices();
+ void clearServiceCharacteristics(GatoService *service);
+ void clearCharacteristicDescriptors(GatoCharacteristic *characteristic);
+
+ void finishSetNotifyOperations(const GatoCharacteristic &characteristic);
+
+public slots:
+ void handleAttConnected();
+ void handleAttDisconnected();
+ void handleAttAttributeUpdated(GatoHandle handle, const QByteArray &value, bool confirmed);
+ void handlePrimary(uint req, const QList<GatoAtt::AttributeGroupData>& list);
+ void handlePrimaryForService(uint req, const QList<GatoAtt::HandleInformation>& list);
+ void handleCharacteristic(uint req, const QList<GatoAtt::AttributeData> &list);
+ void handleDescriptors(uint req, const QList<GatoAtt::InformationData> &list);
+ void handleCharacteristicRead(uint req, const QByteArray &value);
+ void handleDescriptorRead(uint req, const QByteArray &value);
+ void handleCharacteristicWrite(uint req, bool ok);
+ void handleDescriptorWrite(uint req, bool ok);
+};
+
+#endif // GATOPERIPHERAL_P_H
diff --git a/gatoservice.cpp b/gatoservice.cpp
new file mode 100644
index 0000000..ad9ff1b
--- /dev/null
+++ b/gatoservice.cpp
@@ -0,0 +1,129 @@
+/*
+ * libgato - A GATT/ATT library for use with Bluez
+ *
+ * Copyright (C) 2013 Javier S. Pedro <maemo@javispedro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "gatoservice.h"
+#include "gatocharacteristic.h"
+
+struct GatoServicePrivate : QSharedData
+{
+ GatoServicePrivate();
+ GatoUUID uuid;
+ GatoHandle start;
+ GatoHandle end;
+
+ QMap<GatoHandle, GatoCharacteristic> characteristics;
+};
+
+GatoService::GatoService()
+ : d(new GatoServicePrivate)
+{
+}
+
+GatoService::GatoService(const GatoService &o)
+ : d(o.d)
+{
+}
+
+GatoService::~GatoService()
+{
+}
+
+GatoUUID GatoService::uuid() const
+{
+ return d->uuid;
+}
+
+void GatoService::setUuid(const GatoUUID &uuid)
+{
+ d->uuid = uuid;
+}
+
+GatoHandle GatoService::startHandle() const
+{
+ return d->start;
+}
+
+void GatoService::setStartHandle(GatoHandle handle)
+{
+ d->start = handle;
+}
+
+GatoHandle GatoService::endHandle() const
+{
+ return d->end;
+}
+
+void GatoService::setEndHandle(GatoHandle handle)
+{
+ d->end = handle;
+}
+
+QList<GatoCharacteristic> GatoService::characteristics() const
+{
+ return d->characteristics.values();
+}
+
+bool GatoService::containsCharacteristic(const GatoCharacteristic &characteristic) const
+{
+ GatoHandle char_handle = characteristic.startHandle();
+ if (d->characteristics.contains(char_handle)) {
+ return d->characteristics[char_handle].uuid() == characteristic.uuid();
+ } else {
+ return false;
+ }
+}
+
+bool GatoService::containsCharacteristic(GatoHandle handle) const
+{
+ return d->characteristics.contains(handle);
+}
+
+GatoCharacteristic GatoService::getCharacteristic(GatoHandle handle) const
+{
+ return d->characteristics.value(handle);
+}
+
+void GatoService::addCharacteristic(const GatoCharacteristic &characteristic)
+{
+ d->characteristics.insert(characteristic.startHandle(), characteristic);
+}
+
+void GatoService::removeCharacteristic(const GatoCharacteristic &characteristic)
+{
+ d->characteristics.remove(characteristic.startHandle());
+}
+
+void GatoService::clearCharacteristics()
+{
+ d->characteristics.clear();
+}
+
+GatoService & GatoService::operator =(const GatoService &o)
+{
+ if (this != &o) {
+ d = o.d;
+ }
+ return *this;
+}
+
+GatoServicePrivate::GatoServicePrivate()
+ : uuid(), start(0), end(0)
+{
+}
diff --git a/gatoservice.h b/gatoservice.h
new file mode 100644
index 0000000..7af976a
--- /dev/null
+++ b/gatoservice.h
@@ -0,0 +1,42 @@
+#ifndef GATOSERVICE_H
+#define GATOSERVICE_H
+
+#include <QtCore/QSharedDataPointer>
+#include "gatouuid.h"
+
+class GatoServicePrivate;
+class GatoCharacteristic;
+
+class LIBGATO_EXPORT GatoService
+{
+ Q_GADGET
+
+public:
+ GatoService();
+ GatoService(const GatoService &o);
+ ~GatoService();
+
+ GatoUUID uuid() const;
+ void setUuid(const GatoUUID &uuid);
+
+ GatoHandle startHandle() const;
+ void setStartHandle(GatoHandle handle);
+
+ GatoHandle endHandle() const;
+ void setEndHandle(GatoHandle handle);
+
+ QList<GatoCharacteristic> characteristics() const;
+ bool containsCharacteristic(const GatoCharacteristic& characteristic) const;
+ bool containsCharacteristic(GatoHandle handle) const;
+ GatoCharacteristic getCharacteristic(GatoHandle handle) const;
+ void addCharacteristic(const GatoCharacteristic& characteristic);
+ void removeCharacteristic(const GatoCharacteristic& characteristic);
+ void clearCharacteristics();
+
+ GatoService & operator=(const GatoService &o);
+
+private:
+ QSharedDataPointer<GatoServicePrivate> d;
+};
+
+#endif // GATOSERVICE_H
diff --git a/gatosocket.cpp b/gatosocket.cpp
new file mode 100644
index 0000000..e46a811
--- /dev/null
+++ b/gatosocket.cpp
@@ -0,0 +1,193 @@
+/*
+ * libgato - A GATT/ATT library for use with Bluez
+ *
+ * Copyright (C) 2013 Javier S. Pedro <maemo@javispedro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <QtCore/QDebug>
+
+#include <limits>
+
+#include <unistd.h>
+#include <errno.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/l2cap.h>
+#include "gatosocket.h"
+
+GatoSocket::GatoSocket(QObject *parent)
+ : QObject(parent), s(StateDisconnected), fd(-1)
+{
+}
+
+GatoSocket::~GatoSocket()
+{
+ if (s != StateDisconnected) {
+ close();
+ }
+}
+
+GatoSocket::State GatoSocket::state() const
+{
+ return s;
+}
+
+bool GatoSocket::connectTo(const GatoAddress &addr, unsigned short cid)
+{
+ if (s != StateDisconnected) {
+ qWarning() << "Already connecting or connected";
+ return false;
+ }
+
+ fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
+ if (fd == -1) {
+ qErrnoWarning("Could not create L2CAP socket");
+ return false;
+ }
+
+ s = StateConnecting;
+
+ readNotifier = new QSocketNotifier(fd, QSocketNotifier::Read, this);
+ writeNotifier = new QSocketNotifier(fd, QSocketNotifier::Write, this);
+ connect(readNotifier, SIGNAL(activated(int)), SLOT(readNotify()));
+ connect(writeNotifier, SIGNAL(activated(int)), SLOT(writeNotify()));
+
+ struct sockaddr_l2 l2addr;
+ memset(&l2addr, 0, sizeof(l2addr));
+
+ l2addr.l2_family = AF_BLUETOOTH;
+ l2addr.l2_cid = htobs(cid);
+#ifdef BDADDR_LE_PUBLIC
+ l2addr.l2_bdaddr_type = BDADDR_LE_PUBLIC; // TODO
+#endif
+ addr.toUInt8Array(l2addr.l2_bdaddr.b);
+
+ int err = ::connect(fd, reinterpret_cast<sockaddr*>(&l2addr), sizeof(l2addr));
+ if (err == -1 && errno != EINPROGRESS) {
+ qErrnoWarning("Could not connect to L2CAP socket");
+ close();
+ return false;
+ }
+
+ return true;
+}
+
+void GatoSocket::close()
+{
+ if (s != StateDisconnected) {
+ delete readNotifier;
+ delete writeNotifier;
+ readQueue.clear();
+ writeQueue.clear();
+ fd = -1;
+ s = StateDisconnected;
+ emit disconnected();
+ }
+}
+
+QByteArray GatoSocket::receive()
+{
+ if (readQueue.isEmpty()) {
+ return QByteArray();
+ } else {
+ return readQueue.dequeue();
+ }
+}
+
+void GatoSocket::send(const QByteArray &pkt)
+{
+ if (s == StateConnected && writeQueue.isEmpty()) {
+ if (transmit(pkt)) {
+ // Packet transmited succesfully without any queuing
+ return;
+ }
+ }
+
+ writeQueue.enqueue(pkt);
+ writeNotifier->setEnabled(true);
+}
+
+bool GatoSocket::transmit(const QByteArray &pkt)
+{
+ int written = ::write(fd, pkt.constData(), pkt.size());
+ if (written < 0) {
+ qErrnoWarning("Could not write to L2 socket");
+ close();
+ return false;
+ } else if (written < pkt.size()) {
+ qWarning("Could not write full packet to L2 socket");
+ return true;
+ } else {
+ return true;
+ }
+}
+
+void GatoSocket::readNotify()
+{
+ QByteArray buf;
+ buf.resize(1024); // Max packet size
+
+ int read = ::read(fd, buf.data(), buf.size());
+ if (read < 0) {
+ qErrnoWarning("Could not read to L2 socket");
+ close();
+ return;
+ } else if (read == 0) {
+ return;
+ }
+
+ buf.resize(read);
+
+ readQueue.enqueue(buf);
+
+ if (readQueue.size() == 1) {
+ emit readyRead();
+ }
+}
+
+void GatoSocket::writeNotify()
+{
+ if (s == StateConnecting) {
+ int soerror = 0;
+ socklen_t len = sizeof(soerror);
+ if (::getsockopt(fd, SOL_SOCKET, SO_ERROR, &soerror, &len) != 0) {
+ // An error while reading the error?
+ qErrnoWarning("Could not get L2 socket options");
+ close();
+ return;
+ }
+ if (soerror != 0) {
+ qWarning() << "Could not connect to L2 socket: " << strerror(soerror);
+ close();
+ return;
+ }
+
+ s = StateConnected;
+ emit connected();
+ } else if (s == StateConnected) {
+ if (!writeQueue.isEmpty()) {
+ if (transmit(writeQueue.head())) {
+ writeQueue.dequeue();
+ }
+ }
+
+ if (writeQueue.isEmpty()) {
+ writeNotifier->setEnabled(false);
+ }
+ }
+}
diff --git a/gatosocket.h b/gatosocket.h
new file mode 100644
index 0000000..6bb6998
--- /dev/null
+++ b/gatosocket.h
@@ -0,0 +1,54 @@
+#ifndef GATOSOCKET_H
+#define GATOSOCKET_H
+
+#include <QtCore/QObject>
+#include <QtCore/QQueue>
+#include <QtCore/QSocketNotifier>
+
+#include "gatoaddress.h"
+
+class GatoSocket : public QObject
+{
+ Q_OBJECT
+ Q_ENUMS(State)
+
+public:
+ explicit GatoSocket(QObject *parent);
+ ~GatoSocket();
+
+ enum State {
+ StateDisconnected,
+ StateConnecting,
+ StateConnected
+ };
+
+ State state() const;
+
+ bool connectTo(const GatoAddress &addr, unsigned short cid);
+ void close();
+
+ QByteArray receive();
+ void send(const QByteArray &pkt);
+
+signals:
+ void connected();
+ void disconnected();
+ void readyRead();
+
+private:
+ bool transmit(const QByteArray &pkt);
+
+private slots:
+ void readNotify();
+ void writeNotify();
+
+private:
+ State s;
+ int fd;
+ QSocketNotifier *readNotifier;
+ QQueue<QByteArray> readQueue;
+ QSocketNotifier *writeNotifier;
+ QQueue<QByteArray> writeQueue;
+};
+
+#endif // GATOSOCKET_H
diff --git a/gatouuid.cpp b/gatouuid.cpp
new file mode 100644
index 0000000..a1a3866
--- /dev/null
+++ b/gatouuid.cpp
@@ -0,0 +1,184 @@
+/*
+ * libgato - A GATT/ATT library for use with Bluez
+ *
+ * Copyright (C) 2013 Javier S. Pedro <maemo@javispedro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <QtCore/QDebug>
+#include <QtCore/QHash>
+#include <bluetooth/uuid.h>
+
+#include "gatouuid.h"
+
+union convert128 {
+ uint128_t u;
+ gatouint128 g;
+};
+
+struct GatoUUIDPrivate : QSharedData
+{
+ bt_uuid_t uuid;
+};
+
+GatoUUID::GatoUUID()
+ : d(new GatoUUIDPrivate)
+{
+ memset(&d->uuid, 0, sizeof(d->uuid));
+}
+
+GatoUUID::GatoUUID(GattUuid uuid)
+ : d(new GatoUUIDPrivate)
+{
+ bt_uuid16_create(&d->uuid, uuid);
+}
+
+GatoUUID::GatoUUID(quint16 uuid)
+ : d(new GatoUUIDPrivate)
+{
+ bt_uuid16_create(&d->uuid, uuid);
+}
+
+GatoUUID::GatoUUID(quint32 uuid)
+ : d(new GatoUUIDPrivate)
+{
+ bt_uuid32_create(&d->uuid, uuid);
+}
+
+GatoUUID::GatoUUID(gatouint128 uuid)
+ : d(new GatoUUIDPrivate)
+{
+ convert128 c128;
+ c128.g = uuid;
+ bt_uuid128_create(&d->uuid, c128.u);
+}
+
+GatoUUID::GatoUUID(const QString &uuid)
+ : d(new GatoUUIDPrivate)
+{
+ bt_string_to_uuid(&d->uuid, uuid.toLatin1().constData());
+}
+
+GatoUUID::GatoUUID(const GatoUUID &o)
+ : d(o.d)
+{
+}
+
+GatoUUID::~GatoUUID()
+{
+}
+
+bool GatoUUID::isNull() const
+{
+ return d->uuid.type == bt_uuid_t::BT_UUID_UNSPEC;
+}
+
+quint16 GatoUUID::toUInt16(bool *ok) const
+{
+ if (d->uuid.type == bt_uuid_t::BT_UUID16) {
+ if (ok) *ok = true;
+ return d->uuid.value.u16;
+ } else {
+ if (ok) *ok = false;
+ return 0;
+ }
+}
+
+quint32 GatoUUID::toUInt32(bool *ok) const
+{
+ if (d->uuid.type == bt_uuid_t::BT_UUID32) {
+ if (ok) *ok = true;
+ return d->uuid.value.u32;
+ } else {
+ if (ok) *ok = false;
+ return 0;
+ }
+}
+
+gatouint128 GatoUUID::toUInt128() const
+{
+ bt_uuid_t u128;
+ bt_uuid_to_uuid128(&d->uuid, &u128);
+ return *reinterpret_cast<gatouint128*>(&u128.value.u128);
+}
+
+QString GatoUUID::toString() const
+{
+ char buf[MAX_LEN_UUID_STR + 1];
+ if (bt_uuid_to_string(&d->uuid, buf, MAX_LEN_UUID_STR) == 0) {
+ return QString::fromAscii(buf);
+ } else {
+ return QString();
+ }
+}
+
+GatoUUID & GatoUUID::operator =(const GatoUUID &o)
+{
+ if (this != &o) {
+ d = o.d;
+ }
+ return *this;
+}
+
+bool operator==(const GatoUUID &a, const GatoUUID &b)
+{
+ return bt_uuid_cmp(&a.d->uuid, &b.d->uuid) == 0;
+}
+
+bool operator!=(const GatoUUID &a, const GatoUUID &b)
+{
+ return bt_uuid_cmp(&a.d->uuid, &b.d->uuid) != 0;
+}
+
+QDataStream & operator<<(QDataStream &s, const gatouint128 &u)
+{
+ if (static_cast<QSysInfo::Endian>(s.byteOrder()) == QSysInfo::ByteOrder) {
+ for (int i = 0; i < 16; i++) {
+ s << u.data[i];
+ }
+ } else {
+ for (int i = 15; i >= 0; i--) {
+ s << u.data[i];
+ }
+ }
+ return s;
+}
+
+QDataStream & operator>>(QDataStream &s, gatouint128 &u)
+{
+ if (static_cast<QSysInfo::Endian>(s.byteOrder()) == QSysInfo::ByteOrder) {
+ for (int i = 0; i < 16; i++) {
+ s >> u.data[i];
+ }
+ } else {
+ for (int i = 15; i >= 0; i--) {
+ s >> u.data[i];
+ }
+ }
+ return s;
+}
+
+uint qHash(const GatoUUID &a)
+{
+ gatouint128 u128 = a.toUInt128();
+
+ uint h = u128.data[0] << 24 | u128.data[1] << 16 | u128.data[2] << 8 | u128.data[3] << 0;
+ h ^= u128.data[4] << 24 | u128.data[5] << 16 | u128.data[6] << 8 | u128.data[7] << 0;
+ h ^= u128.data[8] << 24 | u128.data[9] << 16 | u128.data[10] << 8 | u128.data[11] << 0;
+ h ^= u128.data[12] << 24 | u128.data[13] << 16 | u128.data[14] << 8 | u128.data[15] << 0;
+
+ return h;
+}
diff --git a/gatouuid.h b/gatouuid.h
new file mode 100644
index 0000000..e091512
--- /dev/null
+++ b/gatouuid.h
@@ -0,0 +1,109 @@
+#ifndef GATOUUID_H
+#define GATOUUID_H
+
+#include <QtCore/QDebug>
+#include <QtCore/QtEndian>
+#include <QtCore/QSharedDataPointer>
+#include <QtCore/QString>
+#include "libgato_global.h"
+
+class GatoUUIDPrivate;
+
+struct gatouint128
+{
+ quint8 data[16];
+};
+
+template<>
+inline LIBGATO_EXPORT gatouint128 qFromLittleEndian<gatouint128>(const uchar *src)
+{
+ gatouint128 dest;
+#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
+ for (int i = 0; i < 16; i++) {
+ dest.data[i] = src[i];
+ }
+#else
+ for (int i = 0; i < 16; i++) {
+ dest.data[i] = src[15 - i];
+ }
+#endif
+ return dest;
+}
+
+template<>
+inline LIBGATO_EXPORT void qToLittleEndian<gatouint128>(gatouint128 src, uchar *dest)
+{
+#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
+ for (int i = 0; i < 16; i++) {
+ dest[i] = src.data[i];
+ }
+#else
+ for (int i = 0; i < 16; i++) {
+ dest[i] = src.data[15 - i];
+ }
+#endif
+}
+
+class LIBGATO_EXPORT GatoUUID
+{
+ Q_GADGET
+ Q_ENUMS(GattUuid)
+
+public:
+ enum GattUuid {
+ GattGenericAccessProfile = 0x1800,
+ GattGenericAttributeProfile = 0x1801,
+ GattPrimaryService = 0x2800,
+ GattSecondaryService = 0x2801,
+ GattInclude = 0x2802,
+ GattCharacteristic = 0x2803,
+ GattCharacteristicExtendedProperties = 0x2900,
+ GattCharacteristicUserDescription = 0x2901,
+ GattClientCharacteristicConfiguration = 0x2902,
+ GattServerCharacteristicConfiguration = 0x2903,
+ GattCharacteristicFormat = 0x2904,
+ GattCharacteristicAggregateFormat = 0x2905,
+ GattDeviceName = 0x2A00,
+ GattAppearance = 0x2A01,
+ GattPeripheralPrivacyFlag = 0x2A02,
+ GattReconnectionAddress = 0x2A03,
+ GattPeripheralPreferredConnectionParameters = 0x2A04,
+ GattServiceChanged = 0x2A05
+ };
+
+ GatoUUID();
+ GatoUUID(GattUuid uuid);
+ explicit GatoUUID(quint16 uuid);
+ explicit GatoUUID(quint32 uuid);
+ explicit GatoUUID(gatouint128 uuid);
+ explicit GatoUUID(const QString &uuid);
+ GatoUUID(const GatoUUID &o);
+ ~GatoUUID();
+
+ bool isNull() const;
+
+ quint16 toUInt16(bool *ok = 0) const;
+ quint32 toUInt32(bool *ok = 0) const;
+ gatouint128 toUInt128() const;
+ QString toString() const;
+
+ GatoUUID& operator=(const GatoUUID& o);
+ friend bool operator==(const GatoUUID &a, const GatoUUID &b);
+ friend bool operator!=(const GatoUUID &a, const GatoUUID &b);
+
+private:
+ QSharedDataPointer<GatoUUIDPrivate> d;
+};
+
+inline QDebug operator<<(QDebug debug, const GatoUUID &uuid)
+{
+ debug << uuid.toString();
+ return debug;
+}
+
+LIBGATO_EXPORT QDataStream & operator<<(QDataStream &s, const gatouint128 &u);
+LIBGATO_EXPORT QDataStream & operator>>(QDataStream &s, gatouint128 &u);
+
+LIBGATO_EXPORT uint qHash(const GatoUUID &a);
+
+#endif // GATOUUID_H
diff --git a/helpers.cpp b/helpers.cpp
new file mode 100644
index 0000000..b62b464
--- /dev/null
+++ b/helpers.cpp
@@ -0,0 +1,64 @@
+/*
+ * libgato - A GATT/ATT library for use with Bluez
+ *
+ * Copyright (C) 2013 Javier S. Pedro <maemo@javispedro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "helpers.h"
+
+GatoUUID bytearray_to_gatouuid(const QByteArray &ba)
+{
+ switch (ba.size()) {
+ case 2:
+ return GatoUUID(read_le<quint16>(ba.constData()));
+ case 4:
+ return GatoUUID(read_le<quint32>(ba.constData()));
+ case 16:
+ return GatoUUID(read_le<gatouint128>(ba.constData()));
+ default:
+ return GatoUUID();
+ }
+}
+
+QByteArray gatouuid_to_bytearray(const GatoUUID &uuid, bool use_uuid16, bool use_uuid32)
+{
+ QByteArray ba;
+
+ if (use_uuid16) {
+ quint16 uuid16 = uuid.toUInt16(&use_uuid16);
+ if (use_uuid16) {
+ ba.resize(sizeof(quint16));
+ write_le(uuid16, ba.data());
+ return ba;
+ }
+ }
+ if (use_uuid32) {
+ quint32 uuid32 = uuid.toUInt32(&use_uuid32);
+ if (use_uuid32) {
+ ba.resize(sizeof(quint32));
+ write_le(uuid32, ba.data());
+ return ba;
+ }
+ }
+
+ gatouint128 uuid128 = uuid.toUInt128();
+ ba.resize(sizeof(gatouint128));
+ Q_ASSERT(ba.size() == 16);
+ write_le(uuid128, ba.data());
+
+ return ba;
+}
diff --git a/helpers.h b/helpers.h
new file mode 100644
index 0000000..962026c
--- /dev/null
+++ b/helpers.h
@@ -0,0 +1,35 @@
+#ifndef HELPERS_H
+#define HELPERS_H
+
+#include <QtCore/QByteArray>
+#include <QtCore/QtEndian>
+#include "gatouuid.h"
+
+template<typename T>
+inline T read_le(const uchar *src)
+{
+ return qFromLittleEndian<T>(src);
+}
+
+template<typename T>
+inline T read_le(const char *src)
+{
+ return qFromLittleEndian<T>(reinterpret_cast<const uchar*>(src));
+}
+
+template<typename T>
+void write_le(T src, uchar *dst)
+{
+ qToLittleEndian<T>(src, dst);
+}
+
+template<typename T>
+void write_le(T src, char *dst)
+{
+ qToLittleEndian<T>(src, reinterpret_cast<uchar*>(dst));
+}
+
+GatoUUID bytearray_to_gatouuid(const QByteArray &ba);
+QByteArray gatouuid_to_bytearray(const GatoUUID &uuid, bool use_uuid16, bool use_uuid32);
+
+#endif // HELPERS_H
diff --git a/libgato.pro b/libgato.pro
new file mode 100644
index 0000000..7da152e
--- /dev/null
+++ b/libgato.pro
@@ -0,0 +1,39 @@
+TEMPLATE = lib
+TARGET = gato
+
+QT -= gui
+
+DEFINES += LIBGATO_LIBRARY
+
+CONFIG += link_pkgconfig
+PKGCONFIG += bluez
+
+SOURCES += \
+ gatocentralmanager.cpp \
+ gatouuid.cpp \
+ gatoperipheral.cpp \
+ gatoaddress.cpp \
+ gatosocket.cpp \
+ gatoatt.cpp \
+ helpers.cpp \
+ gatoservice.cpp \
+ gatocharacteristic.cpp \
+ gatodescriptor.cpp
+
+HEADERS += libgato_global.h \
+ gatocentralmanager.h \
+ gatouuid.h \
+ gatoperipheral.h \
+ gatoaddress.h \
+ gatosocket.h \
+ gatoatt.h \
+ helpers.h \
+ gatoperipheral_p.h \
+ gatocentralmanager_p.h \
+ gatoservice.h \
+ gatocharacteristic.h \
+ gatodescriptor.h \
+ gato.h
+
+target.path = /usr/lib
+INSTALLS += target
diff --git a/libgato_global.h b/libgato_global.h
new file mode 100644
index 0000000..ddc4173
--- /dev/null
+++ b/libgato_global.h
@@ -0,0 +1,14 @@
+#ifndef LIBGATO_GLOBAL_H
+#define LIBGATO_GLOBAL_H
+
+#include <QtCore/qglobal.h>
+
+#if defined(LIBGATO_LIBRARY)
+# define LIBGATO_EXPORT Q_DECL_EXPORT
+#else
+# define LIBGATO_EXPORT Q_DECL_IMPORT
+#endif
+
+typedef quint16 GatoHandle;
+
+#endif // LIBGATO_GLOBAL_H