1 /*
2 *
3 * BlueZ - Bluetooth protocol stack for Linux
4 *
5 * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
6 *
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 *
22 */
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <stdlib.h>
29 #include <errno.h>
30 #include <unistd.h>
31 #include <sys/ioctl.h>
32 #include <sys/socket.h>
33
34 #include <bluetooth/bluetooth.h>
35 #include <bluetooth/sdp.h>
36 #include <bluetooth/sdp_lib.h>
37
38 #include <glib.h>
39
40 #include "btio.h"
41 #include "sdpd.h"
42 #include "glib-helper.h"
43
44 /* Number of seconds to keep a sdp_session_t in the cache */
45 #define CACHE_TIMEOUT 2
46
47 struct cached_sdp_session {
48 bdaddr_t src;
49 bdaddr_t dst;
50 sdp_session_t *session;
51 guint timer;
52 };
53
54 static GSList *cached_sdp_sessions = NULL;
55
cached_session_expired(gpointer user_data)56 static gboolean cached_session_expired(gpointer user_data)
57 {
58 struct cached_sdp_session *cached = user_data;
59
60 cached_sdp_sessions = g_slist_remove(cached_sdp_sessions, cached);
61
62 sdp_close(cached->session);
63
64 g_free(cached);
65
66 return FALSE;
67 }
68
get_sdp_session(const bdaddr_t * src,const bdaddr_t * dst)69 static sdp_session_t *get_sdp_session(const bdaddr_t *src, const bdaddr_t *dst)
70 {
71 GSList *l;
72
73 for (l = cached_sdp_sessions; l != NULL; l = l->next) {
74 struct cached_sdp_session *c = l->data;
75 sdp_session_t *session;
76
77 if (bacmp(&c->src, src) || bacmp(&c->dst, dst))
78 continue;
79
80 g_source_remove(c->timer);
81
82 session = c->session;
83
84 cached_sdp_sessions = g_slist_remove(cached_sdp_sessions, c);
85 g_free(c);
86
87 return session;
88 }
89
90 return sdp_connect(src, dst, SDP_NON_BLOCKING);
91 }
92
cache_sdp_session(bdaddr_t * src,bdaddr_t * dst,sdp_session_t * session)93 static void cache_sdp_session(bdaddr_t *src, bdaddr_t *dst,
94 sdp_session_t *session)
95 {
96 struct cached_sdp_session *cached;
97
98 cached = g_new0(struct cached_sdp_session, 1);
99
100 bacpy(&cached->src, src);
101 bacpy(&cached->dst, dst);
102
103 cached->session = session;
104
105 cached_sdp_sessions = g_slist_append(cached_sdp_sessions, cached);
106
107 cached->timer = g_timeout_add_seconds(CACHE_TIMEOUT,
108 cached_session_expired,
109 cached);
110 }
111
112 struct search_context {
113 bdaddr_t src;
114 bdaddr_t dst;
115 sdp_session_t *session;
116 bt_callback_t cb;
117 bt_destroy_t destroy;
118 gpointer user_data;
119 uuid_t uuid;
120 guint io_id;
121 };
122
123 static GSList *context_list = NULL;
124
search_context_cleanup(struct search_context * ctxt)125 static void search_context_cleanup(struct search_context *ctxt)
126 {
127 context_list = g_slist_remove(context_list, ctxt);
128
129 if (ctxt->destroy)
130 ctxt->destroy(ctxt->user_data);
131
132 g_free(ctxt);
133 }
134
search_completed_cb(uint8_t type,uint16_t status,uint8_t * rsp,size_t size,void * user_data)135 static void search_completed_cb(uint8_t type, uint16_t status,
136 uint8_t *rsp, size_t size, void *user_data)
137 {
138 struct search_context *ctxt = user_data;
139 sdp_list_t *recs = NULL;
140 int scanned, seqlen = 0, bytesleft = size;
141 uint8_t dataType;
142 int err = 0;
143
144 if (status || type != SDP_SVC_SEARCH_ATTR_RSP) {
145 err = -EPROTO;
146 goto done;
147 }
148
149 scanned = sdp_extract_seqtype(rsp, bytesleft, &dataType, &seqlen);
150 if (!scanned || !seqlen)
151 goto done;
152
153 rsp += scanned;
154 bytesleft -= scanned;
155 do {
156 sdp_record_t *rec;
157 int recsize;
158
159 recsize = 0;
160 rec = sdp_extract_pdu(rsp, bytesleft, &recsize);
161 if (!rec)
162 break;
163
164 if (!recsize) {
165 sdp_record_free(rec);
166 break;
167 }
168
169 scanned += recsize;
170 rsp += recsize;
171 bytesleft -= recsize;
172
173 recs = sdp_list_append(recs, rec);
174 } while (scanned < (ssize_t) size && bytesleft > 0);
175
176 done:
177 cache_sdp_session(&ctxt->src, &ctxt->dst, ctxt->session);
178
179 if (ctxt->cb)
180 ctxt->cb(recs, err, ctxt->user_data);
181
182 if (recs)
183 sdp_list_free(recs, (sdp_free_func_t) sdp_record_free);
184
185 search_context_cleanup(ctxt);
186 }
187
search_process_cb(GIOChannel * chan,GIOCondition cond,gpointer user_data)188 static gboolean search_process_cb(GIOChannel *chan, GIOCondition cond,
189 gpointer user_data)
190 {
191 struct search_context *ctxt = user_data;
192 int err = 0;
193
194 if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) {
195 err = EIO;
196 goto failed;
197 }
198
199 if (sdp_process(ctxt->session) < 0)
200 goto failed;
201
202 return TRUE;
203
204 failed:
205 if (err) {
206 sdp_close(ctxt->session);
207 ctxt->session = NULL;
208
209 if (ctxt->cb)
210 ctxt->cb(NULL, err, ctxt->user_data);
211
212 search_context_cleanup(ctxt);
213 }
214
215 return FALSE;
216 }
217
connect_watch(GIOChannel * chan,GIOCondition cond,gpointer user_data)218 static gboolean connect_watch(GIOChannel *chan, GIOCondition cond,
219 gpointer user_data)
220 {
221 struct search_context *ctxt = user_data;
222 sdp_list_t *search, *attrids;
223 uint32_t range = 0x0000ffff;
224 socklen_t len;
225 int sk, err = 0;
226
227 sk = g_io_channel_unix_get_fd(chan);
228 ctxt->io_id = 0;
229
230 len = sizeof(err);
231 if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &err, &len) < 0) {
232 err = errno;
233 goto failed;
234 }
235
236 if (err != 0)
237 goto failed;
238
239 if (sdp_set_notify(ctxt->session, search_completed_cb, ctxt) < 0) {
240 err = EIO;
241 goto failed;
242 }
243
244 search = sdp_list_append(NULL, &ctxt->uuid);
245 attrids = sdp_list_append(NULL, &range);
246 if (sdp_service_search_attr_async(ctxt->session,
247 search, SDP_ATTR_REQ_RANGE, attrids) < 0) {
248 sdp_list_free(attrids, NULL);
249 sdp_list_free(search, NULL);
250 err = EIO;
251 goto failed;
252 }
253
254 sdp_list_free(attrids, NULL);
255 sdp_list_free(search, NULL);
256
257 /* Set callback responsible for update the internal SDP transaction */
258 ctxt->io_id = g_io_add_watch(chan,
259 G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
260 search_process_cb, ctxt);
261 return FALSE;
262
263 failed:
264 sdp_close(ctxt->session);
265 ctxt->session = NULL;
266
267 if (ctxt->cb)
268 ctxt->cb(NULL, -err, ctxt->user_data);
269
270 search_context_cleanup(ctxt);
271
272 return FALSE;
273 }
274
create_search_context(struct search_context ** ctxt,const bdaddr_t * src,const bdaddr_t * dst,uuid_t * uuid)275 static int create_search_context(struct search_context **ctxt,
276 const bdaddr_t *src,
277 const bdaddr_t *dst,
278 uuid_t *uuid)
279 {
280 sdp_session_t *s;
281 GIOChannel *chan;
282
283 if (!ctxt)
284 return -EINVAL;
285
286 s = get_sdp_session(src, dst);
287 if (!s)
288 return -errno;
289
290 *ctxt = g_try_malloc0(sizeof(struct search_context));
291 if (!*ctxt) {
292 sdp_close(s);
293 return -ENOMEM;
294 }
295
296 bacpy(&(*ctxt)->src, src);
297 bacpy(&(*ctxt)->dst, dst);
298 (*ctxt)->session = s;
299 (*ctxt)->uuid = *uuid;
300
301 chan = g_io_channel_unix_new(sdp_get_socket(s));
302 (*ctxt)->io_id = g_io_add_watch(chan,
303 G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
304 connect_watch, *ctxt);
305 g_io_channel_unref(chan);
306
307 return 0;
308 }
309
bt_search_service(const bdaddr_t * src,const bdaddr_t * dst,uuid_t * uuid,bt_callback_t cb,void * user_data,bt_destroy_t destroy)310 int bt_search_service(const bdaddr_t *src, const bdaddr_t *dst,
311 uuid_t *uuid, bt_callback_t cb, void *user_data,
312 bt_destroy_t destroy)
313 {
314 struct search_context *ctxt = NULL;
315 int err;
316
317 if (!cb)
318 return -EINVAL;
319
320 err = create_search_context(&ctxt, src, dst, uuid);
321 if (err < 0)
322 return err;
323
324 ctxt->cb = cb;
325 ctxt->destroy = destroy;
326 ctxt->user_data = user_data;
327
328 context_list = g_slist_append(context_list, ctxt);
329
330 return 0;
331 }
332
find_by_bdaddr(gconstpointer data,gconstpointer user_data)333 static gint find_by_bdaddr(gconstpointer data, gconstpointer user_data)
334 {
335 const struct search_context *ctxt = data, *search = user_data;
336
337 return (bacmp(&ctxt->dst, &search->dst) &&
338 bacmp(&ctxt->src, &search->src));
339 }
340
bt_cancel_discovery(const bdaddr_t * src,const bdaddr_t * dst)341 int bt_cancel_discovery(const bdaddr_t *src, const bdaddr_t *dst)
342 {
343 struct search_context match, *ctxt;
344 GSList *l;
345
346 memset(&match, 0, sizeof(match));
347 bacpy(&match.src, src);
348 bacpy(&match.dst, dst);
349
350 /* Ongoing SDP Discovery */
351 l = g_slist_find_custom(context_list, &match, find_by_bdaddr);
352 if (l == NULL)
353 return -ENOENT;
354
355 ctxt = l->data;
356
357 if (!ctxt->session)
358 return -ENOTCONN;
359
360 if (ctxt->io_id)
361 g_source_remove(ctxt->io_id);
362
363 if (ctxt->session)
364 sdp_close(ctxt->session);
365
366 search_context_cleanup(ctxt);
367
368 return 0;
369 }
370
bt_uuid2string(uuid_t * uuid)371 char *bt_uuid2string(uuid_t *uuid)
372 {
373 gchar *str;
374 uuid_t uuid128;
375 unsigned int data0;
376 unsigned short data1;
377 unsigned short data2;
378 unsigned short data3;
379 unsigned int data4;
380 unsigned short data5;
381
382 if (!uuid)
383 return NULL;
384
385 switch (uuid->type) {
386 case SDP_UUID16:
387 sdp_uuid16_to_uuid128(&uuid128, uuid);
388 break;
389 case SDP_UUID32:
390 sdp_uuid32_to_uuid128(&uuid128, uuid);
391 break;
392 case SDP_UUID128:
393 memcpy(&uuid128, uuid, sizeof(uuid_t));
394 break;
395 default:
396 /* Type of UUID unknown */
397 return NULL;
398 }
399
400 memcpy(&data0, &uuid128.value.uuid128.data[0], 4);
401 memcpy(&data1, &uuid128.value.uuid128.data[4], 2);
402 memcpy(&data2, &uuid128.value.uuid128.data[6], 2);
403 memcpy(&data3, &uuid128.value.uuid128.data[8], 2);
404 memcpy(&data4, &uuid128.value.uuid128.data[10], 4);
405 memcpy(&data5, &uuid128.value.uuid128.data[14], 2);
406
407 str = g_try_malloc0(MAX_LEN_UUID_STR);
408 if (!str)
409 return NULL;
410
411 sprintf(str, "%.8x-%.4x-%.4x-%.4x-%.8x%.4x",
412 g_ntohl(data0), g_ntohs(data1),
413 g_ntohs(data2), g_ntohs(data3),
414 g_ntohl(data4), g_ntohs(data5));
415
416 return str;
417 }
418
419 static struct {
420 const char *name;
421 uint16_t class;
422 } bt_services[] = {
423 { "vcp", VIDEO_CONF_SVCLASS_ID },
424 { "pbap", PBAP_SVCLASS_ID },
425 { "sap", SAP_SVCLASS_ID },
426 { "ftp", OBEX_FILETRANS_SVCLASS_ID },
427 { "bpp", BASIC_PRINTING_SVCLASS_ID },
428 { "bip", IMAGING_SVCLASS_ID },
429 { "synch", IRMC_SYNC_SVCLASS_ID },
430 { "dun", DIALUP_NET_SVCLASS_ID },
431 { "opp", OBEX_OBJPUSH_SVCLASS_ID },
432 { "fax", FAX_SVCLASS_ID },
433 { "spp", SERIAL_PORT_SVCLASS_ID },
434 { "hsp", HEADSET_SVCLASS_ID },
435 { "hfp", HANDSFREE_SVCLASS_ID },
436 { }
437 };
438
name2class(const char * pattern)439 static uint16_t name2class(const char *pattern)
440 {
441 int i;
442
443 for (i = 0; bt_services[i].name; i++) {
444 if (strcasecmp(bt_services[i].name, pattern) == 0)
445 return bt_services[i].class;
446 }
447
448 return 0;
449 }
450
is_uuid128(const char * string)451 static inline gboolean is_uuid128(const char *string)
452 {
453 return (strlen(string) == 36 &&
454 string[8] == '-' &&
455 string[13] == '-' &&
456 string[18] == '-' &&
457 string[23] == '-');
458 }
459
string2uuid16(uuid_t * uuid,const char * string)460 static int string2uuid16(uuid_t *uuid, const char *string)
461 {
462 int length = strlen(string);
463 char *endptr = NULL;
464 uint16_t u16;
465
466 if (length != 4 && length != 6)
467 return -EINVAL;
468
469 u16 = strtol(string, &endptr, 16);
470 if (endptr && *endptr == '\0') {
471 sdp_uuid16_create(uuid, u16);
472 return 0;
473 }
474
475 return -EINVAL;
476 }
477
bt_name2string(const char * pattern)478 char *bt_name2string(const char *pattern)
479 {
480 uuid_t uuid;
481 uint16_t uuid16;
482 int i;
483
484 /* UUID 128 string format */
485 if (is_uuid128(pattern))
486 return g_strdup(pattern);
487
488 /* Friendly service name format */
489 uuid16 = name2class(pattern);
490 if (uuid16)
491 goto proceed;
492
493 /* HEX format */
494 uuid16 = strtol(pattern, NULL, 16);
495 for (i = 0; bt_services[i].class; i++) {
496 if (bt_services[i].class == uuid16)
497 goto proceed;
498 }
499
500 return NULL;
501
502 proceed:
503 sdp_uuid16_create(&uuid, uuid16);
504
505 return bt_uuid2string(&uuid);
506 }
507
bt_string2uuid(uuid_t * uuid,const char * string)508 int bt_string2uuid(uuid_t *uuid, const char *string)
509 {
510 uint32_t data0, data4;
511 uint16_t data1, data2, data3, data5;
512
513 if (is_uuid128(string) &&
514 sscanf(string, "%08x-%04hx-%04hx-%04hx-%08x%04hx",
515 &data0, &data1, &data2, &data3, &data4, &data5) == 6) {
516 uint8_t val[16];
517
518 data0 = g_htonl(data0);
519 data1 = g_htons(data1);
520 data2 = g_htons(data2);
521 data3 = g_htons(data3);
522 data4 = g_htonl(data4);
523 data5 = g_htons(data5);
524
525 memcpy(&val[0], &data0, 4);
526 memcpy(&val[4], &data1, 2);
527 memcpy(&val[6], &data2, 2);
528 memcpy(&val[8], &data3, 2);
529 memcpy(&val[10], &data4, 4);
530 memcpy(&val[14], &data5, 2);
531
532 sdp_uuid128_create(uuid, val);
533
534 return 0;
535 } else {
536 uint16_t class = name2class(string);
537 if (class) {
538 sdp_uuid16_create(uuid, class);
539 return 0;
540 }
541
542 return string2uuid16(uuid, string);
543 }
544 }
545
bt_list2string(GSList * list)546 gchar *bt_list2string(GSList *list)
547 {
548 GSList *l;
549 gchar *str, *tmp;
550
551 if (!list)
552 return NULL;
553
554 str = g_strdup((const gchar *) list->data);
555
556 for (l = list->next; l; l = l->next) {
557 tmp = g_strconcat(str, " " , (const gchar *) l->data, NULL);
558 g_free(str);
559 str = tmp;
560 }
561
562 return str;
563 }
564
bt_string2list(const gchar * str)565 GSList *bt_string2list(const gchar *str)
566 {
567 GSList *l = NULL;
568 gchar **uuids;
569 int i = 0;
570
571 if (!str)
572 return NULL;
573
574 /* FIXME: eglib doesn't support g_strsplit */
575 uuids = g_strsplit(str, " ", 0);
576 if (!uuids)
577 return NULL;
578
579 while (uuids[i]) {
580 l = g_slist_append(l, uuids[i]);
581 i++;
582 }
583
584 g_free(uuids);
585
586 return l;
587 }
588