diff --git a/src/device.c b/src/device.c
index 8cb79df..6a9936a 100644
--- a/src/device.c
+++ b/src/device.c
gatt_cache_cleanup(device);
bt_gatt_client_set_service_changed(device->client, NULL, NULL, NULL);
- bt_gatt_client_ready_unregister(device->client, device->gatt_ready_id);
+
+ if (device->gatt_ready_id > 0) {
+ bt_gatt_client_ready_unregister(device->client,
+ device->gatt_ready_id);
+ device->gatt_ready_id = 0;
+ }
+
bt_gatt_client_unref(device->client);
device->client = NULL;
}
diff --git a/src/gatt-client.c b/src/gatt-client.c
index 6c67841..eae5419 100644
--- a/src/gatt-client.c
+++ b/src/gatt-client.c
#include <stdbool.h>
#include <stdint.h>
#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
#include <dbus/dbus.h>
#include "error.h"
#include "adapter.h"
#include "device.h"
+#include "src/shared/io.h"
#include "src/shared/queue.h"
#include "src/shared/att.h"
#include "src/shared/gatt-db.h"
bt_uuid_t uuid;
char *path;
+ unsigned int ready_id;
+ DBusMessage *acquire_write;
+ struct io *write_io;
+
struct async_dbus_op *read_op;
struct async_dbus_op *write_op;
if (!gatt)
return btd_error_failed(msg, "Not connected");
+ if (chrc->write_io)
+ return btd_error_not_permitted(msg, "Write acquired");
+
if (chrc->write_op)
return btd_error_in_progress(msg);
return btd_error_not_supported(msg);
}
+static bool chrc_pipe_read(struct io *io, void *user_data)
+{
+ struct characteristic *chrc = user_data;
+ struct bt_gatt_client *gatt = chrc->service->client->gatt;
+ uint8_t buf[512];
+ int fd = io_get_fd(io);
+ ssize_t bytes_read;
+
+ bytes_read = read(fd, buf, sizeof(buf));
+ if (bytes_read < 0)
+ return false;
+
+ if (!gatt)
+ return false;
+
+ bt_gatt_client_write_without_response(gatt, chrc->value_handle,
+ chrc->props & BT_GATT_CHRC_PROP_AUTH,
+ buf, bytes_read);
+
+ return true;
+}
+
+static void characteristic_destroy_pipe(struct characteristic *chrc,
+ struct io *io)
+{
+ if (io == chrc->write_io) {
+ io_destroy(chrc->write_io);
+ chrc->write_io = NULL;
+ }
+}
+
+static bool characteristic_pipe_hup(struct io *io, void *user_data)
+{
+ struct characteristic *chrc = user_data;
+
+ DBG("%s: io %p", chrc->path, io);
+
+ characteristic_destroy_pipe(chrc, io);
+
+ return false;
+}
+
+static DBusMessage *characteristic_create_pipe(struct characteristic *chrc,
+ DBusMessage *msg)
+{
+ struct bt_gatt_client *gatt = chrc->service->client->gatt;
+ int pipefd[2];
+ struct io *io;
+ bool dir;
+ uint16_t mtu;
+ DBusMessage *reply;
+
+ if (!gatt || !bt_gatt_client_is_ready(gatt))
+ return btd_error_failed(msg, "Not connected");
+
+ if (pipe2(pipefd, O_DIRECT | O_NONBLOCK | O_CLOEXEC) < 0)
+ return btd_error_failed(msg, strerror(errno));
+
+ dir = dbus_message_has_member(msg, "AcquireWrite");
+
+ io = io_new(pipefd[!dir]);
+ if (!io) {
+ close(pipefd[0]);
+ close(pipefd[1]);
+ return btd_error_failed(msg, strerror(EIO));
+ }
+
+ io_set_close_on_destroy(io, true);
+
+ if (!io_set_read_handler(io, chrc_pipe_read, chrc, NULL))
+ goto fail;
+
+ if (!io_set_disconnect_handler(io, characteristic_pipe_hup, chrc, NULL))
+ goto fail;
+
+ mtu = bt_gatt_client_get_mtu(gatt);
+
+ reply = g_dbus_create_reply(msg, DBUS_TYPE_UNIX_FD, &pipefd[dir],
+ DBUS_TYPE_UINT16, &mtu,
+ DBUS_TYPE_INVALID);
+
+ close(pipefd[dir]);
+
+ if (dir)
+ chrc->write_io = io;
+
+ DBG("%s: sender %s io %p", dbus_message_get_member(msg),
+ dbus_message_get_sender(msg), io);
+
+ return reply;
+
+fail:
+ io_destroy(io);
+ close(pipefd[dir]);
+ return btd_error_failed(msg, strerror(EIO));
+}
+
+static void characteristic_ready(bool success, uint8_t ecode, void *user_data)
+{
+ struct characteristic *chrc = user_data;
+ DBusMessage *reply;
+
+ chrc->ready_id = 0;
+
+ if (chrc->acquire_write) {
+ reply = characteristic_create_pipe(chrc, chrc->acquire_write);
+
+ g_dbus_send_message(btd_get_dbus_connection(), reply);
+
+ dbus_message_unref(chrc->acquire_write);
+ chrc->acquire_write = NULL;
+ }
+}
+
+static DBusMessage *characteristic_acquire_write(DBusConnection *conn,
+ DBusMessage *msg, void *user_data)
+{
+ struct characteristic *chrc = user_data;
+ struct bt_gatt_client *gatt = chrc->service->client->gatt;
+
+ if (!gatt)
+ return btd_error_failed(msg, "Not connected");
+
+ if (chrc->write_io || chrc->acquire_write)
+ return btd_error_not_permitted(msg, "Write acquired");
+
+ if (!(chrc->props & BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP))
+ return btd_error_not_supported(msg);
+
+ if (!bt_gatt_client_is_ready(gatt)) {
+ DBG("GATT not ready, wait until it becomes read");
+ if (!chrc->ready_id)
+ chrc->ready_id = bt_gatt_client_ready_register(gatt,
+ characteristic_ready,
+ chrc, NULL);
+ chrc->acquire_write = dbus_message_ref(msg);
+ return NULL;
+ }
+
+ return characteristic_create_pipe(chrc, msg);
+}
+
struct notify_client {
struct characteristic *chrc;
int ref_count;
{ "options", "a{sv}" }),
NULL,
characteristic_write_value) },
+ { GDBUS_EXPERIMENTAL_ASYNC_METHOD("AcquireWrite", NULL,
+ GDBUS_ARGS({ "fd", "h" },
+ { "mtu", "q" }),
+ characteristic_acquire_write) },
{ GDBUS_ASYNC_METHOD("StartNotify", NULL, NULL,
characteristic_start_notify) },
{ GDBUS_METHOD("StopNotify", NULL, NULL,
queue_destroy(chrc->descs, NULL);
queue_destroy(chrc->notify_clients, NULL);
+ io_destroy(chrc->write_io);
+
+ if (chrc->acquire_write)
+ dbus_message_unref(chrc->acquire_write);
+
g_free(chrc->path);
free(chrc);
}