db.c revision 1.1
1/*	$NetBSD: db.c,v 1.1 2009/05/12 10:05:06 plunky Exp $	*/
2
3/*-
4 * Copyright (c) 2009 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Iain Hibbert.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#include <sys/cdefs.h>
33__RCSID("$NetBSD: db.c,v 1.1 2009/05/12 10:05:06 plunky Exp $");
34
35#include <bluetooth.h>
36#include <sdp.h>
37#include <stdbool.h>
38#include <stdlib.h>
39#include <string.h>
40#include <uuid.h>
41
42#include "sdpd.h"
43
44/*
45 * Using a prebuilt service record means that providing ServerState
46 * and a non-hardcoded ProviderName are difficult. Look into that later.
47 */
48
49/* ServiceDiscoveryServer service record */
50static uint8_t sds_data[] = {
51	0x09, 0x00, 0x00,	//  uint16	ServiceRecordHandle
52	0x0a, 0x00, 0x00, 0x00,	//  uint32	0x00000000
53	0x00,
54
55	0x09, 0x00, 0x01,	//  uint16	ServiceClassIDList
56	0x35, 0x03,		//  seq8(3)
57	0x19, 0x10, 0x00,	//   uuid16	ServiceDiscoveryServer
58
59	0x09, 0x00, 0x04,	//  uint16	ProtocolDescriptorList
60	0x35, 0x0d,		//  seq8(13)
61	0x35, 0x06,		//   seq8(6)
62	0x19, 0x01, 0x00,	//    uuid16	L2CAP
63	0x09, 0x00, 0x01,	//    uint16	L2CAP_PSM_SDP
64	0x35, 0x03,		//   seq8(3)
65	0x19, 0x00, 0x01,	//    uuid16	SDP
66
67	0x09, 0x00, 0x05,	//  uint16	BrowseGroupList
68	0x35, 0x03,		//  seq8(3)
69	0x19, 0x10, 0x02,	//   uuid16	PublicBrowseGroup
70
71	0x09, 0x00, 0x06,	//  uint16	LanguageBaseAttributeIDList
72	0x35, 0x09,		//  seq8(9)
73	0x09, 0x65, 0x6e,	//   uint16	0x656e	("en")
74	0x09, 0x00, 0x6a,	//   uint16	106	(UTF-8)
75	0x09, 0x01, 0x00,	//   uint16	PrimaryLanguageBaseID
76
77	0x09, 0x01, 0x00,	//  uint16	PrimaryLanguageBaseID + ServiceNameOffset
78	0x25, 0x1b, 0x42, 0x6c,	//  str8(27)	"Bluetooth service discovery"
79	0x75, 0x65, 0x74, 0x6f,
80	0x6f, 0x74, 0x68, 0x20,
81	0x73, 0x65, 0x72, 0x76,
82	0x69, 0x63, 0x65, 0x20,
83	0x64, 0x69, 0x73, 0x63,
84	0x6f, 0x76, 0x65, 0x72,
85	0x79,
86
87	0x09, 0x01, 0x02,	//  uint16	PrimaryLanguageBaseID + ProviderNameOffset
88	0x25, 0x06, 0x4e, 0x65,	//  str8(6)	"NetBSD"
89	0x74, 0x42, 0x53, 0x44,
90
91	0x09, 0x02, 0x00,	//  uint16	VersionNumberList
92	0x35, 0x03,		//  seq8(3)
93	0x09, 0x01, 0x00,	//   uint16	v1.0
94};
95
96/* BrowseGroupDescriptor service record */
97static uint8_t bgd_data[] = {
98	0x09, 0x00, 0x00,	//  uint16	ServiceRecordHandle
99	0x0a, 0x00, 0x00, 0x00,	//  uint32	0x00000001
100	0x01,
101
102	0x09, 0x00, 0x01,	//  uint16	ServiceClassIDList
103	0x35, 0x03,		//  seq8(3)
104	0x19, 0x10, 0x01,	//   uuid16	BrowseGroupDescriptor
105
106	0x09, 0x00, 0x06,	//  uint16	LanguageBaseAttributeIDList
107	0x35, 0x09,		//  seq8(9)
108	0x09, 0x65, 0x6e,	//   uint16	0x656e	("en")
109	0x09, 0x00, 0x6a,	//   uint16	106	(UTF-8)
110	0x09, 0x01, 0x00,	//   uint16	PrimaryLanguageBaseID
111
112	0x09, 0x01, 0x00,	//  uint16	PrimaryLanguageBaseID + ServiceNameOffset
113	0x25, 0x12, 0x50, 0x75,	//  str8(18)	"Public Browse Root"
114	0x62, 0x6c, 0x69, 0x63,
115	0x20, 0x42, 0x72, 0x6f,
116	0x77, 0x73, 0x65, 0x20,
117	0x52, 0x6f, 0x6f, 0x74,
118
119	0x09, 0x02, 0x00,	//  uint16	GroupID
120	0x19, 0x10, 0x02,	//  uuid16	PublicBrowseRoot
121};
122
123/*
124 * Initialise the record database with the ServiceDiscoveryServer
125 * and BrowseGroupDescriptor records
126 */
127bool
128db_init(server_t *srv)
129{
130	sdp_data_t d;
131
132	LIST_INIT(&srv->rlist);
133	srv->handle = 0;
134
135	d.next = sds_data;
136	d.end = sds_data + sizeof(sds_data);
137	if (!db_create(srv, -1, BDADDR_ANY, srv->handle++, &d))
138		return false;
139
140	d.next = bgd_data;
141	d.end = bgd_data + sizeof(bgd_data);
142	if (!db_create(srv, -1, BDADDR_ANY, srv->handle++, &d))
143		return false;
144
145	return true;
146}
147
148/*
149 * Iterate through records selected by fd.  rec should point to a NULL
150 * value to start the iteration, and false will be returned when there
151 * are no more records to return.
152 */
153bool
154db_next(server_t *srv, int fd, record_t **rec)
155{
156	record_t *r;
157
158	if (*rec == NULL)
159		r = LIST_FIRST(&srv->rlist);
160	else
161		r = LIST_NEXT(*rec, next);
162
163	while (r != NULL && !FD_ISSET(fd, &r->refset))
164		r = LIST_NEXT(r, next);
165
166	*rec = r;
167	return (r == NULL) ? false : true;
168}
169
170/*
171 * Match a ServiceRecord against a UUID. Note that because we already
172 * know that the record data is valid, we don't need to recurse here
173 * and can just skip over SEQ and ALT headers. Return true if equivalent
174 * UUID is found.
175 */
176static bool
177db_match_uuid(record_t *rec, uuid_t *uuid)
178{
179	uint8_t *p = rec->data.next;
180	uuid_t u;
181
182	while (p < rec->data.end) {
183		switch(*p++) {
184		case SDP_DATA_NIL:
185			break;
186
187		case SDP_DATA_BOOL:
188		case SDP_DATA_INT8:
189		case SDP_DATA_UINT8:
190		case SDP_DATA_SEQ8:
191		case SDP_DATA_ALT8:
192			p += 1;
193			break;
194
195		case SDP_DATA_INT16:
196		case SDP_DATA_UINT16:
197		case SDP_DATA_SEQ16:
198		case SDP_DATA_ALT16:
199			p += 2;
200			break;
201
202		case SDP_DATA_INT32:
203		case SDP_DATA_UINT32:
204		case SDP_DATA_SEQ32:
205		case SDP_DATA_ALT32:
206			p += 4;
207			break;
208
209		case SDP_DATA_INT64:
210		case SDP_DATA_UINT64:
211			p += 8;
212			break;
213
214		case SDP_DATA_INT128:
215		case SDP_DATA_UINT128:
216			p += 16;
217			break;
218
219		case SDP_DATA_STR8:
220		case SDP_DATA_URL8:
221			p += 1 + *p;
222			break;
223
224		case SDP_DATA_STR16:
225		case SDP_DATA_URL16:
226			p += 2 + be16dec(p);
227			break;
228
229		case SDP_DATA_STR32:
230		case SDP_DATA_URL32:
231			p += 4 + be32dec(p);
232			break;
233
234		case SDP_DATA_UUID16:
235			u = BLUETOOTH_BASE_UUID;
236			u.time_low = be16dec(p);
237
238			if (uuid_equal(&u, uuid, NULL))
239				return true;
240
241			p += 2;
242			break;
243
244		case SDP_DATA_UUID32:
245			u = BLUETOOTH_BASE_UUID;
246			u.time_low = be32dec(p);
247
248			if (uuid_equal(&u, uuid, NULL))
249				return true;
250
251			p += 4;
252			break;
253
254		case SDP_DATA_UUID128:
255			uuid_dec_be(p, &u);
256
257			if (uuid_equal(&u, uuid, NULL))
258				return true;
259
260			p += 16;
261			break;
262
263		default:
264			return false;
265		}
266	}
267
268	return false;
269}
270
271/*
272 * Select ServiceRecords matching ServiceSearchPattern
273 *
274 * A record is selected when it is visible to the client and
275 * contains each and every UUID from the ServiceSearchPattern
276 */
277void
278db_select_ssp(server_t *srv, int fd, sdp_data_t *ssp)
279{
280	record_t *r;
281	sdp_data_t s;
282	uuid_t u;
283
284	LIST_FOREACH(r, &srv->rlist, next) {
285		if (!r->valid)
286			continue;
287
288		if (!srv->fdidx[fd].control
289		    && !bdaddr_any(&r->bdaddr)
290		    && !bdaddr_same(&r->bdaddr, &srv->fdidx[fd].bdaddr))
291			continue;
292
293		s = *ssp;
294		for (;;) {
295			if (!sdp_get_uuid(&s, &u)) {
296				/* matched all UUIDs */
297				FD_SET(fd, &r->refset);
298				r->refcnt++;
299				break;
300			}
301
302			if (!db_match_uuid(r, &u)) {
303				/* does not match UUID */
304				break;
305			}
306		}
307	}
308}
309
310/*
311 * Select a ServiceRecord given the RecordHandle.
312 */
313void
314db_select_handle(server_t *srv, int fd, uint32_t handle)
315{
316	record_t *r;
317
318	LIST_FOREACH(r, &srv->rlist, next) {
319		if (!r->valid)
320			continue;
321
322		if (!srv->fdidx[fd].control
323		    && !bdaddr_any(&r->bdaddr)
324		    && !bdaddr_same(&r->bdaddr, &srv->fdidx[fd].bdaddr))
325			continue;
326
327		if (handle == r->handle) {
328			FD_SET(fd, &r->refset);
329			r->refcnt++;
330			break;
331		}
332	}
333}
334
335/*
336 * Create a record and insert in server record list in ascending handle
337 * order. Where a selectable record exists with the same handle number,
338 * it will be expired.
339 */
340bool
341db_create(server_t *srv, int fd, const bdaddr_t *bdaddr, uint32_t handle, sdp_data_t *data)
342{
343	record_t *n, *r, *rec;
344	sdp_data_t d, v;
345	uint16_t a;
346	size_t len;
347
348	d = *data;
349	if (!sdp_get_attr(&d, &a, &v)
350	    || a != SDP_ATTR_SERVICE_RECORD_HANDLE
351	    || sdp_data_type(&v) != SDP_DATA_UINT32)
352		return false;
353
354	sdp_set_uint(&v, handle);
355
356	len = data->end - data->next;
357	rec = malloc(sizeof(record_t) + len);
358	if (rec == NULL)
359		return false;
360
361	memset(rec, 0, sizeof(record_t));
362	FD_ZERO(&rec->refset);
363	rec->handle = handle;
364	rec->valid = true;
365	rec->fd = fd;
366	bdaddr_copy(&rec->bdaddr, bdaddr);
367	rec->data.next = rec->ext;
368	rec->data.end = rec->ext + len;
369	memcpy(rec->ext, data->next, len);
370
371	/*
372	 * Note, this does not handle the case where we expire
373	 * the first record on the list, as that won't happen.
374	 */
375	n = LIST_FIRST(&srv->rlist);
376	if (n != NULL) {
377		do {
378			r = n;
379			n = LIST_NEXT(r, next);
380		} while (n != NULL && n->handle < handle);
381
382		if (n != NULL && n->valid && n->handle == handle) {
383			if (n->refcnt-- == 0) {
384				LIST_REMOVE(n, next);
385				free(n);
386			} else {
387				n->valid = false;
388				n->fd = -1;
389			}
390		}
391
392		LIST_INSERT_AFTER(r, rec, next);
393	} else {
394		LIST_INSERT_HEAD(&srv->rlist, rec, next);
395	}
396
397	return true;
398}
399
400/*
401 * Unselect any ServiceRecords selected by fd
402 */
403void
404db_unselect(server_t *srv, int fd)
405{
406	record_t *n, *r;
407
408	n = LIST_FIRST(&srv->rlist);
409	while (n != NULL) {
410		r = n;
411		n = LIST_NEXT(r, next);
412
413		if (FD_ISSET(fd, &r->refset)) {
414			if (r->refcnt-- == 0) {
415				LIST_REMOVE(r, next);
416				free(r);
417			} else {
418				FD_CLR(fd, &r->refset);
419			}
420		}
421	}
422}
423
424/*
425 * Invalidate or release all records owned by fd
426 */
427void
428db_release(server_t *srv, int fd)
429{
430	record_t *n, *r;
431
432	n = LIST_FIRST(&srv->rlist);
433	while (n != NULL) {
434		r = n;
435		n = LIST_NEXT(r, next);
436
437		if (r->fd == fd) {
438			if (r->refcnt-- == 0) {
439				LIST_REMOVE(r, next);
440				free(r);
441			} else {
442				r->valid = false;
443				r->fd = -1;
444			}
445		}
446	}
447}
448