Fix build break for rpm
[framework/connectivity/bluez.git] / proximity / linkloss.c
1 /*
2  *
3  *  BlueZ - Bluetooth protocol stack for Linux
4  *
5  *  Copyright (C) 2012 Texas Instruments Corporation
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  *
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <glib.h>
28 #include <bluetooth/uuid.h>
29 #include <adapter.h>
30
31 #include <dbus/dbus.h>
32 #include <gdbus.h>
33
34 #include "log.h"
35 #include "att-database.h"
36 #include "gattrib.h"
37 #include "att.h"
38 #include "gatt.h"
39 #include "gatt-service.h"
40 #include "attrib-server.h"
41 #include "device.h"
42 #include "attio.h"
43 #include "dbus-common.h"
44 #include "reporter.h"
45 #include "linkloss.h"
46
47 #define BLUEZ_SERVICE "org.bluez"
48
49 struct link_loss_adapter {
50         struct btd_adapter *adapter;
51         uint16_t alert_lvl_value_handle;
52         DBusConnection *conn;
53         GSList *connected_devices;
54 };
55
56 struct connected_device {
57         struct btd_device *device;
58         struct link_loss_adapter *adapter;
59         uint8_t alert_level;
60         guint callback_id;
61         guint local_disc_id;
62 };
63
64 static GSList *link_loss_adapters;
65
66 static int lldevice_cmp(gconstpointer a, gconstpointer b)
67 {
68         const struct connected_device *llcondev = a;
69         const struct btd_device *device = b;
70
71         if (llcondev->device == device)
72                 return 0;
73
74         return -1;
75 }
76
77 static struct connected_device *
78 find_connected_device(struct link_loss_adapter *la, struct btd_device *device)
79 {
80         GSList *l = g_slist_find_custom(la->connected_devices, device,
81                                                                 lldevice_cmp);
82         if (!l)
83                 return NULL;
84
85         return l->data;
86 }
87
88 static int lladapter_cmp(gconstpointer a, gconstpointer b)
89 {
90         const struct link_loss_adapter *lladapter = a;
91         const struct btd_adapter *adapter = b;
92
93         if (lladapter->adapter == adapter)
94                 return 0;
95
96         return -1;
97 }
98
99 static struct link_loss_adapter *
100 find_link_loss_adapter(struct btd_adapter *adapter)
101 {
102         GSList *l = g_slist_find_custom(link_loss_adapters, adapter,
103                                                         lladapter_cmp);
104         if (!l)
105                 return NULL;
106
107         return l->data;
108 }
109
110 const char *link_loss_get_alert_level(struct btd_device *device)
111 {
112         struct link_loss_adapter *lladapter;
113         struct connected_device *condev;
114
115         if (!device)
116                 return get_alert_level_string(NO_ALERT);
117
118         lladapter = find_link_loss_adapter(device_get_adapter(device));
119         if (!lladapter)
120                 return get_alert_level_string(NO_ALERT);
121
122         condev = find_connected_device(lladapter, device);
123         if (!condev)
124                 return get_alert_level_string(NO_ALERT);
125
126         return get_alert_level_string(condev->alert_level);
127 }
128
129 static void link_loss_emit_alert_signal(struct connected_device *condev)
130 {
131         struct link_loss_adapter *adapter = condev->adapter;
132         const char *alert_level_str, *path;
133
134         if (!condev->device)
135                 return;
136
137         path = device_get_path(condev->device);
138         alert_level_str = get_alert_level_string(condev->alert_level);
139
140         DBG("alert %s remote %s", alert_level_str, path);
141
142         emit_property_changed(adapter->conn, path,
143                         PROXIMITY_REPORTER_INTERFACE, "LinkLossAlertLevel",
144                         DBUS_TYPE_STRING, &alert_level_str);
145 }
146
147 static uint8_t link_loss_alert_lvl_read(struct attribute *a,
148                                 struct btd_device *device, gpointer user_data)
149 {
150         struct link_loss_adapter *la = user_data;
151         struct connected_device *condev;
152         uint8_t alert_level = NO_ALERT;
153
154         if (!device)
155                 goto out;
156
157         condev = find_connected_device(la, device);
158         if (!condev)
159                 goto out;
160
161         alert_level = condev->alert_level;
162
163 out:
164         DBG("return alert level %d for dev %p", alert_level, device);
165
166         /* update the alert level according to the requesting device */
167         attrib_db_update(la->adapter, a->handle, NULL, &alert_level,
168                                                 sizeof(alert_level), NULL);
169
170         return 0;
171 }
172
173 /* condev can be NULL */
174 static void link_loss_remove_condev(struct connected_device *condev)
175 {
176         struct link_loss_adapter *la;
177
178         if (!condev)
179                 return;
180
181         la = condev->adapter;
182
183         if (condev->callback_id && condev->device)
184                 btd_device_remove_attio_callback(condev->device,
185                                                         condev->callback_id);
186
187         if (condev->local_disc_id && condev->device)
188                 device_remove_disconnect_watch(condev->device,
189                                                         condev->local_disc_id);
190
191         if (condev->device)
192                 btd_device_unref(condev->device);
193
194         la->connected_devices = g_slist_remove(la->connected_devices, condev);
195         g_free(condev);
196 }
197
198 static void link_loss_disc_cb(gpointer user_data)
199 {
200         struct connected_device *condev = user_data;
201
202         DBG("alert loss disconnect device %p", condev->device);
203
204         /* if an alert-level is set, emit a signal */
205         if (condev->alert_level != NO_ALERT)
206                 link_loss_emit_alert_signal(condev);
207
208         /* we are open for more changes now */
209         link_loss_remove_condev(condev);
210 }
211
212 static void link_loss_local_disc(struct btd_device *device,
213                                         gboolean removal, void *user_data)
214 {
215         struct connected_device *condev = user_data;
216
217         /* no need to alert on this device - we requested disconnection */
218         link_loss_remove_condev(condev);
219
220         DBG("alert level zeroed for locally disconnecting dev %p", device);
221 }
222
223 static uint8_t link_loss_alert_lvl_write(struct attribute *a,
224                                 struct btd_device *device, gpointer user_data)
225 {
226         uint8_t value;
227         struct link_loss_adapter *la = user_data;
228         struct connected_device *condev = NULL;
229
230         if (!device)
231                 goto set_error;
232
233         /* condev might remain NULL here if nothing is found */
234         condev = find_connected_device(la, device);
235
236         if (a->len == 0) {
237                 DBG("Illegal alert level length");
238                 goto set_error;
239         }
240
241         value = a->data[0];
242         if (value != NO_ALERT && value != MILD_ALERT && value != HIGH_ALERT) {
243                 DBG("Illegal alert value");
244                 goto set_error;
245         }
246
247         /* Register a disconnect cb if the alert level is non-zero */
248         if (value != NO_ALERT && !condev) {
249                 condev = g_new0(struct connected_device, 1);
250                 condev->device = btd_device_ref(device);
251                 condev->adapter = la;
252                 condev->callback_id = btd_device_add_attio_callback(device,
253                                         NULL, link_loss_disc_cb, condev);
254                 condev->local_disc_id = device_add_disconnect_watch(device,
255                                         link_loss_local_disc, condev, NULL);
256
257                 la->connected_devices = g_slist_append(la->connected_devices,
258                                                                 condev);
259         } else if (value == NO_ALERT && condev) {
260                 link_loss_remove_condev(condev);
261                 condev = NULL;
262         }
263
264         DBG("alert level set to %d by device %p", value, device);
265
266         if (condev)
267                 condev->alert_level = value;
268
269         return 0;
270
271 set_error:
272         error("Set link loss alert level for dev %p", device);
273         /* reset alert level on erroneous devices */
274         link_loss_remove_condev(condev);
275         return ATT_ECODE_IO;
276 }
277
278 void link_loss_register(struct btd_adapter *adapter, DBusConnection *conn)
279 {
280         gboolean svc_added;
281         bt_uuid_t uuid;
282         struct link_loss_adapter *lladapter;
283
284         bt_uuid16_create(&uuid, LINK_LOSS_SVC_UUID);
285
286         lladapter = g_new0(struct link_loss_adapter, 1);
287         lladapter->adapter = adapter;
288         lladapter->conn = dbus_connection_ref(conn);
289
290         link_loss_adapters = g_slist_append(link_loss_adapters, lladapter);
291
292         /* Link Loss Service */
293         svc_added = gatt_service_add(adapter,
294                         GATT_PRIM_SVC_UUID, &uuid,
295                         /* Alert level characteristic */
296                         GATT_OPT_CHR_UUID, ALERT_LEVEL_CHR_UUID,
297                         GATT_OPT_CHR_PROPS,
298                                 ATT_CHAR_PROPER_READ | ATT_CHAR_PROPER_WRITE,
299                         GATT_OPT_CHR_VALUE_CB, ATTRIB_READ,
300                                 link_loss_alert_lvl_read, lladapter,
301                         GATT_OPT_CHR_VALUE_CB, ATTRIB_WRITE,
302                                 link_loss_alert_lvl_write, lladapter,
303                         GATT_OPT_CHR_VALUE_GET_HANDLE,
304                                 &lladapter->alert_lvl_value_handle,
305                         GATT_OPT_INVALID);
306
307         if (!svc_added)
308                 goto err;
309
310         DBG("Link Loss service added");
311         return;
312
313 err:
314         error("Error adding Link Loss service");
315         link_loss_unregister(adapter);
316 }
317
318 static void remove_condev_list_item(gpointer data, gpointer user_data)
319 {
320         struct connected_device *condev = data;
321
322         link_loss_remove_condev(condev);
323 }
324
325 void link_loss_unregister(struct btd_adapter *adapter)
326 {
327         struct link_loss_adapter *lladapter;
328         lladapter = find_link_loss_adapter(adapter);
329         if (!lladapter)
330                 return;
331
332         g_slist_foreach(lladapter->connected_devices, remove_condev_list_item,
333                         NULL);
334         dbus_connection_unref(lladapter->conn);
335
336         link_loss_adapters = g_slist_remove(link_loss_adapters, lladapter);
337         g_free(lladapter);
338 }