1 /*
2 *
3 * BlueZ - Bluetooth protocol stack for Linux
4 *
5 * Copyright (C) 2001-2002 Nokia Corporation
6 * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com>
7 * Copyright (C) 2002-2008 Marcel Holtmann <marcel@holtmann.org>
8 * Copyright (C) 2002-2003 Stephen Crane <steve.crane@rococosoft.com>
9 *
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
24 *
25 */
26
27 #ifdef HAVE_CONFIG_H
28 #include <config.h>
29 #endif
30
31 #include <stdio.h>
32 #include <errno.h>
33 #include <stdlib.h>
34 #include <sys/socket.h>
35
36 #include <bluetooth/bluetooth.h>
37 #include <bluetooth/sdp.h>
38 #include <bluetooth/sdp_lib.h>
39
40 #include <netinet/in.h>
41
42 #include "sdpd.h"
43 #include "logging.h"
44
45 static sdp_record_t *server = NULL;
46
47 static uint8_t service_classes = 0x00;
48 static service_classes_callback_t service_classes_callback = NULL;
49
50 static uint16_t did_vendor = 0x0000;
51 static uint16_t did_product = 0x0000;
52 static uint16_t did_version = 0x0000;
53
54 /*
55 * List of version numbers supported by the SDP server.
56 * Add to this list when newer versions are supported.
57 */
58 static sdp_version_t sdpVnumArray[1] = {
59 { 1, 0 }
60 };
61 static const int sdpServerVnumEntries = 1;
62
63 /*
64 * The service database state is an attribute of the service record
65 * of the SDP server itself. This attribute is guaranteed to
66 * change if any of the contents of the service repository
67 * changes. This function updates the timestamp of value of
68 * the svcDBState attribute
69 * Set the SDP server DB. Simply a timestamp which is the marker
70 * when the DB was modified.
71 */
update_db_timestamp(void)72 static void update_db_timestamp(void)
73 {
74 uint32_t dbts = sdp_get_time();
75 sdp_data_t *d = sdp_data_alloc(SDP_UINT32, &dbts);
76 sdp_attr_replace(server, SDP_ATTR_SVCDB_STATE, d);
77 }
78
update_svclass_list(void)79 static void update_svclass_list(void)
80 {
81 sdp_list_t *list = sdp_get_record_list();
82 uint8_t val = 0;
83
84 for (; list; list = list->next) {
85 sdp_record_t *rec = (sdp_record_t *) list->data;
86
87 if (rec->svclass.type != SDP_UUID16)
88 continue;
89
90 switch (rec->svclass.value.uuid16) {
91 case DIALUP_NET_SVCLASS_ID:
92 case CIP_SVCLASS_ID:
93 val |= 0x42; /* Telephony & Networking */
94 break;
95 case IRMC_SYNC_SVCLASS_ID:
96 case OBEX_OBJPUSH_SVCLASS_ID:
97 case OBEX_FILETRANS_SVCLASS_ID:
98 case IRMC_SYNC_CMD_SVCLASS_ID:
99 case PBAP_PSE_SVCLASS_ID:
100 val |= 0x10; /* Object Transfer */
101 break;
102 case HEADSET_SVCLASS_ID:
103 case HANDSFREE_SVCLASS_ID:
104 val |= 0x20; /* Audio */
105 break;
106 case CORDLESS_TELEPHONY_SVCLASS_ID:
107 case INTERCOM_SVCLASS_ID:
108 case FAX_SVCLASS_ID:
109 case SAP_SVCLASS_ID:
110 case HEADSET_AGW_SVCLASS_ID:
111 case HANDSFREE_AGW_SVCLASS_ID:
112 val |= 0x40; /* Telephony */
113 break;
114 case AUDIO_SOURCE_SVCLASS_ID:
115 case VIDEO_SOURCE_SVCLASS_ID:
116 val |= 0x08; /* Capturing */
117 break;
118 case AUDIO_SINK_SVCLASS_ID:
119 case VIDEO_SINK_SVCLASS_ID:
120 val |= 0x04; /* Rendering */
121 break;
122 case PANU_SVCLASS_ID:
123 case NAP_SVCLASS_ID:
124 case GN_SVCLASS_ID:
125 val |= 0x02; /* Networking */
126 break;
127 }
128 }
129
130 debug("Service classes 0x%02x", val);
131
132 service_classes = val;
133
134 if (service_classes_callback)
135 service_classes_callback(BDADDR_ANY, val);
136 }
137
get_service_classes(const bdaddr_t * bdaddr)138 uint8_t get_service_classes(const bdaddr_t *bdaddr)
139 {
140 return service_classes;
141 }
142
set_service_classes_callback(service_classes_callback_t callback)143 void set_service_classes_callback(service_classes_callback_t callback)
144 {
145 service_classes_callback = callback;
146 }
147
create_ext_inquiry_response(const char * name,uint8_t * data)148 void create_ext_inquiry_response(const char *name, uint8_t *data)
149 {
150 sdp_list_t *list = sdp_get_record_list();
151 uint8_t *ptr = data;
152 uint16_t uuid[24];
153 int i, index = 0;
154
155 if (name) {
156 int len = strlen(name);
157
158 if (len > 48) {
159 len = 48;
160 ptr[1] = 0x08;
161 } else
162 ptr[1] = 0x09;
163
164 ptr[0] = len + 1;
165
166 memcpy(ptr + 2, name, len);
167
168 ptr += len + 2;
169 }
170
171 if (did_vendor != 0x0000) {
172 uint16_t source = 0x0002;
173 *ptr++ = 9;
174 *ptr++ = 11;
175 *ptr++ = (source & 0x00ff);
176 *ptr++ = (source & 0xff00) >> 8;
177 *ptr++ = (did_vendor & 0x00ff);
178 *ptr++ = (did_vendor & 0xff00) >> 8;
179 *ptr++ = (did_product & 0x00ff);
180 *ptr++ = (did_product & 0xff00) >> 8;
181 *ptr++ = (did_version & 0x00ff);
182 *ptr++ = (did_version & 0xff00) >> 8;
183 }
184
185 ptr[1] = 0x03;
186
187 for (; list; list = list->next) {
188 sdp_record_t *rec = (sdp_record_t *) list->data;
189
190 if (rec->svclass.type != SDP_UUID16)
191 continue;
192
193 if (rec->svclass.value.uuid16 < 0x1100)
194 continue;
195
196 if (index > 23) {
197 ptr[1] = 0x02;
198 break;
199 }
200
201 for (i = 0; i < index; i++)
202 if (uuid[i] == rec->svclass.value.uuid16)
203 break;
204
205 if (i == index - 1)
206 continue;
207
208 uuid[index++] = rec->svclass.value.uuid16;
209 }
210
211 if (index > 0) {
212 ptr[0] = (index * 2) + 1;
213 ptr += 2;
214
215 for (i = 0; i < index; i++) {
216 *ptr++ = (uuid[i] & 0x00ff);
217 *ptr++ = (uuid[i] & 0xff00) >> 8;
218 }
219 }
220 }
221
register_public_browse_group(void)222 void register_public_browse_group(void)
223 {
224 sdp_list_t *browselist;
225 uuid_t bgscid, pbgid;
226 sdp_data_t *sdpdata;
227 sdp_record_t *browse = sdp_record_alloc();
228
229 browse->handle = SDP_SERVER_RECORD_HANDLE + 1;
230
231 sdp_record_add(BDADDR_ANY, browse);
232 sdpdata = sdp_data_alloc(SDP_UINT32, &browse->handle);
233 sdp_attr_add(browse, SDP_ATTR_RECORD_HANDLE, sdpdata);
234
235 sdp_uuid16_create(&bgscid, BROWSE_GRP_DESC_SVCLASS_ID);
236 browselist = sdp_list_append(0, &bgscid);
237 sdp_set_service_classes(browse, browselist);
238 sdp_list_free(browselist, 0);
239
240 sdp_uuid16_create(&pbgid, PUBLIC_BROWSE_GROUP);
241 sdp_attr_add_new(browse, SDP_ATTR_GROUP_ID,
242 SDP_UUID16, &pbgid.value.uuid16);
243 }
244
245 /*
246 * The SDP server must present its own service record to
247 * the service repository. This can be accessed by service
248 * discovery clients. This method constructs a service record
249 * and stores it in the repository
250 */
register_server_service(void)251 void register_server_service(void)
252 {
253 sdp_list_t *classIDList;
254 uuid_t classID;
255 void **versions, **versionDTDs;
256 uint8_t dtd;
257 sdp_data_t *pData;
258 int i;
259
260 server = sdp_record_alloc();
261 server->pattern = NULL;
262
263 /* Force the record to be SDP_SERVER_RECORD_HANDLE */
264 server->handle = SDP_SERVER_RECORD_HANDLE;
265
266 sdp_record_add(BDADDR_ANY, server);
267 sdp_attr_add(server, SDP_ATTR_RECORD_HANDLE,
268 sdp_data_alloc(SDP_UINT32, &server->handle));
269
270 sdp_uuid16_create(&classID, SDP_SERVER_SVCLASS_ID);
271 classIDList = sdp_list_append(0, &classID);
272 sdp_set_service_classes(server, classIDList);
273 sdp_list_free(classIDList, 0);
274
275 /*
276 * Set the version numbers supported, these are passed as arguments
277 * to the server on command line. Now defaults to 1.0
278 * Build the version number sequence first
279 */
280 versions = (void **)malloc(sdpServerVnumEntries * sizeof(void *));
281 versionDTDs = (void **)malloc(sdpServerVnumEntries * sizeof(void *));
282 dtd = SDP_UINT16;
283 for (i = 0; i < sdpServerVnumEntries; i++) {
284 uint16_t *version = malloc(sizeof(uint16_t));
285 *version = sdpVnumArray[i].major;
286 *version = (*version << 8);
287 *version |= sdpVnumArray[i].minor;
288 versions[i] = version;
289 versionDTDs[i] = &dtd;
290 }
291 pData = sdp_seq_alloc(versionDTDs, versions, sdpServerVnumEntries);
292 for (i = 0; i < sdpServerVnumEntries; i++)
293 free(versions[i]);
294 free(versions);
295 free(versionDTDs);
296 sdp_attr_add(server, SDP_ATTR_VERSION_NUM_LIST, pData);
297
298 update_db_timestamp();
299 update_svclass_list();
300 }
301
register_device_id(const uint16_t vendor,const uint16_t product,const uint16_t version)302 void register_device_id(const uint16_t vendor, const uint16_t product,
303 const uint16_t version)
304 {
305 const uint16_t spec = 0x0102, source = 0x0002;
306 const uint8_t primary = 1;
307 sdp_list_t *class_list, *group_list, *profile_list;
308 uuid_t class_uuid, group_uuid;
309 sdp_data_t *sdp_data, *primary_data, *source_data;
310 sdp_data_t *spec_data, *vendor_data, *product_data, *version_data;
311 sdp_profile_desc_t profile;
312 sdp_record_t *record = sdp_record_alloc();
313
314 info("Adding device id record for %04x:%04x", vendor, product);
315
316 did_vendor = vendor;
317 did_product = product;
318 did_version = version;
319
320 record->handle = sdp_next_handle();
321
322 sdp_record_add(BDADDR_ANY, record);
323 sdp_data = sdp_data_alloc(SDP_UINT32, &record->handle);
324 sdp_attr_add(record, SDP_ATTR_RECORD_HANDLE, sdp_data);
325
326 sdp_uuid16_create(&class_uuid, PNP_INFO_SVCLASS_ID);
327 class_list = sdp_list_append(0, &class_uuid);
328 sdp_set_service_classes(record, class_list);
329 sdp_list_free(class_list, NULL);
330
331 sdp_uuid16_create(&group_uuid, PUBLIC_BROWSE_GROUP);
332 group_list = sdp_list_append(NULL, &group_uuid);
333 sdp_set_browse_groups(record, group_list);
334 sdp_list_free(group_list, NULL);
335
336 sdp_uuid16_create(&profile.uuid, PNP_INFO_PROFILE_ID);
337 profile.version = spec;
338 profile_list = sdp_list_append(NULL, &profile);
339 sdp_set_profile_descs(record, profile_list);
340 sdp_list_free(profile_list, NULL);
341
342 spec_data = sdp_data_alloc(SDP_UINT16, &spec);
343 sdp_attr_add(record, 0x0200, spec_data);
344
345 vendor_data = sdp_data_alloc(SDP_UINT16, &vendor);
346 sdp_attr_add(record, 0x0201, vendor_data);
347
348 product_data = sdp_data_alloc(SDP_UINT16, &product);
349 sdp_attr_add(record, 0x0202, product_data);
350
351 version_data = sdp_data_alloc(SDP_UINT16, &version);
352 sdp_attr_add(record, 0x0203, version_data);
353
354 primary_data = sdp_data_alloc(SDP_BOOL, &primary);
355 sdp_attr_add(record, 0x0204, primary_data);
356
357 source_data = sdp_data_alloc(SDP_UINT16, &source);
358 sdp_attr_add(record, 0x0205, source_data);
359
360 update_db_timestamp();
361 update_svclass_list();
362 }
363
add_record_to_server(bdaddr_t * src,sdp_record_t * rec)364 int add_record_to_server(bdaddr_t *src, sdp_record_t *rec)
365 {
366 sdp_data_t *data;
367
368 if (rec->handle == 0xffffffff) {
369 rec->handle = sdp_next_handle();
370 if (rec->handle < 0x10000)
371 return -1;
372 } else {
373 if (sdp_record_find(rec->handle))
374 return -1;
375 }
376
377 debug("Adding record with handle 0x%05x", rec->handle);
378
379 sdp_record_add(src, rec);
380
381 data = sdp_data_alloc(SDP_UINT32, &rec->handle);
382 sdp_attr_replace(rec, SDP_ATTR_RECORD_HANDLE, data);
383
384 if (sdp_data_get(rec, SDP_ATTR_BROWSE_GRP_LIST) == NULL) {
385 uuid_t uuid;
386 sdp_uuid16_create(&uuid, PUBLIC_BROWSE_GROUP);
387 sdp_pattern_add_uuid(rec, &uuid);
388 }
389
390 update_db_timestamp();
391 update_svclass_list();
392
393 return 0;
394 }
395
remove_record_from_server(uint32_t handle)396 int remove_record_from_server(uint32_t handle)
397 {
398 sdp_record_t *rec;
399
400 debug("Removing record with handle 0x%05x", handle);
401
402 rec = sdp_record_find(handle);
403 if (!rec)
404 return -ENOENT;
405
406 if (sdp_record_remove(handle) == 0) {
407 update_db_timestamp();
408 update_svclass_list();
409 }
410
411 sdp_record_free(rec);
412
413 return 0;
414 }
415
416 // FIXME: refactor for server-side
extract_pdu_server(bdaddr_t * device,uint8_t * p,int bufsize,uint32_t handleExpected,int * scanned)417 static sdp_record_t *extract_pdu_server(bdaddr_t *device, uint8_t *p, int bufsize, uint32_t handleExpected, int *scanned)
418 {
419 int extractStatus = -1, localExtractedLength = 0;
420 uint8_t dtd;
421 int seqlen = 0;
422 sdp_record_t *rec = NULL;
423 uint16_t attrId, lookAheadAttrId;
424 sdp_data_t *pAttr = NULL;
425 uint32_t handle = 0xffffffff;
426
427 *scanned = sdp_extract_seqtype_safe(p, bufsize, &dtd, &seqlen);
428 p += *scanned;
429 bufsize -= *scanned;
430
431 if (bufsize < sizeof(uint8_t) + sizeof(uint8_t)) {
432 debug("Unexpected end of packet");
433 return NULL;
434 }
435
436 lookAheadAttrId = ntohs(bt_get_unaligned((uint16_t *) (p + sizeof(uint8_t))));
437
438 debug("Look ahead attr id : %d", lookAheadAttrId);
439
440 if (lookAheadAttrId == SDP_ATTR_RECORD_HANDLE) {
441 if (bufsize < (sizeof(uint8_t) * 2) + sizeof(uint16_t) + sizeof(uint32_t)) {
442 debug("Unexpected end of packet");
443 return NULL;
444 }
445 handle = ntohl(bt_get_unaligned((uint32_t *) (p +
446 sizeof(uint8_t) + sizeof(uint16_t) +
447 sizeof(uint8_t))));
448 debug("SvcRecHandle : 0x%x", handle);
449 rec = sdp_record_find(handle);
450 } else if (handleExpected != 0xffffffff)
451 rec = sdp_record_find(handleExpected);
452
453 if (!rec) {
454 rec = sdp_record_alloc();
455 rec->attrlist = NULL;
456 if (lookAheadAttrId == SDP_ATTR_RECORD_HANDLE) {
457 rec->handle = handle;
458 sdp_record_add(device, rec);
459 } else if (handleExpected != 0xffffffff) {
460 rec->handle = handleExpected;
461 sdp_record_add(device, rec);
462 }
463 } else {
464 sdp_list_free(rec->attrlist, (sdp_free_func_t) sdp_data_free);
465 rec->attrlist = NULL;
466 }
467
468 while (localExtractedLength < seqlen) {
469 int attrSize = sizeof(uint8_t);
470 int attrValueLength = 0;
471
472 if (bufsize < attrSize + sizeof(uint16_t)) {
473 debug("Unexpected end of packet: Terminating extraction of attributes");
474 break;
475 }
476
477 debug("Extract PDU, sequenceLength: %d localExtractedLength: %d", seqlen, localExtractedLength);
478 dtd = *(uint8_t *) p;
479
480 attrId = ntohs(bt_get_unaligned((uint16_t *) (p + attrSize)));
481 attrSize += sizeof(uint16_t);
482
483 debug("DTD of attrId : %d Attr id : 0x%x", dtd, attrId);
484
485 pAttr = sdp_extract_attr_safe(p + attrSize, bufsize - attrSize,
486 &attrValueLength, rec);
487
488 debug("Attr id : 0x%x attrValueLength : %d", attrId, attrValueLength);
489
490 attrSize += attrValueLength;
491 if (pAttr == NULL) {
492 debug("Terminating extraction of attributes");
493 break;
494 }
495 localExtractedLength += attrSize;
496 p += attrSize;
497 bufsize -= attrSize;
498 sdp_attr_replace(rec, attrId, pAttr);
499 extractStatus = 0;
500 debug("Extract PDU, seqLength: %d localExtractedLength: %d",
501 seqlen, localExtractedLength);
502 }
503
504 if (extractStatus == 0) {
505 debug("Successful extracting of Svc Rec attributes");
506 #ifdef SDP_DEBUG
507 sdp_print_service_attr(rec->attrlist);
508 #endif
509 *scanned += seqlen;
510 }
511 return rec;
512 }
513
514 /*
515 * Add the newly created service record to the service repository
516 */
service_register_req(sdp_req_t * req,sdp_buf_t * rsp)517 int service_register_req(sdp_req_t *req, sdp_buf_t *rsp)
518 {
519 int scanned = 0;
520 sdp_data_t *handle;
521 uint8_t *p = req->buf + sizeof(sdp_pdu_hdr_t);
522 int bufsize = req->len - sizeof(sdp_pdu_hdr_t);
523 sdp_record_t *rec;
524
525 req->flags = *p++;
526 if (req->flags & SDP_DEVICE_RECORD) {
527 bacpy(&req->device, (bdaddr_t *) p);
528 p += sizeof(bdaddr_t);
529 bufsize -= sizeof(bdaddr_t);
530 }
531
532 // save image of PDU: we need it when clients request this attribute
533 rec = extract_pdu_server(&req->device, p, bufsize, 0xffffffff, &scanned);
534 if (!rec)
535 goto invalid;
536
537 if (rec->handle == 0xffffffff) {
538 rec->handle = sdp_next_handle();
539 if (rec->handle < 0x10000) {
540 sdp_record_free(rec);
541 goto invalid;
542 }
543 } else {
544 if (sdp_record_find(rec->handle)) {
545 /* This is a bug: extract_pdu_server() adds the
546 * attribute, if it is missing, so we are guaranteed
547 * to find it here. Instead of freeing it, we just
548 * return success, skipping over the code below that
549 * adds the attribute, in order to avoid adding that
550 * attribute twice.
551 */
552 #if 0
553 sdp_record_free(rec);
554 goto invalid;
555 #else
556 goto success;
557 #endif
558 }
559 }
560
561 sdp_record_add(&req->device, rec);
562 if (!(req->flags & SDP_RECORD_PERSIST))
563 sdp_svcdb_set_collectable(rec, req->sock);
564
565 handle = sdp_data_alloc(SDP_UINT32, &rec->handle);
566 sdp_attr_replace(rec, SDP_ATTR_RECORD_HANDLE, handle);
567
568 /*
569 * if the browse group descriptor is NULL,
570 * ensure that the record belongs to the ROOT group
571 */
572 if (sdp_data_get(rec, SDP_ATTR_BROWSE_GRP_LIST) == NULL) {
573 uuid_t uuid;
574 sdp_uuid16_create(&uuid, PUBLIC_BROWSE_GROUP);
575 sdp_pattern_add_uuid(rec, &uuid);
576 }
577 success:
578
579 update_db_timestamp();
580 update_svclass_list();
581
582 /* Build a rsp buffer */
583 bt_put_unaligned(htonl(rec->handle), (uint32_t *) rsp->data);
584 rsp->data_size = sizeof(uint32_t);
585
586 return 0;
587
588 invalid:
589 bt_put_unaligned(htons(SDP_INVALID_SYNTAX), (uint16_t *) rsp->data);
590 rsp->data_size = sizeof(uint16_t);
591
592 return -1;
593 }
594
595 /*
596 * Update a service record
597 */
service_update_req(sdp_req_t * req,sdp_buf_t * rsp)598 int service_update_req(sdp_req_t *req, sdp_buf_t *rsp)
599 {
600 sdp_record_t *orec;
601 int status = 0, scanned = 0;
602 uint8_t *p = req->buf + sizeof(sdp_pdu_hdr_t);
603 int bufsize = req->len - sizeof(sdp_pdu_hdr_t);
604 uint32_t handle = ntohl(bt_get_unaligned((uint32_t *) p));
605
606 debug("Svc Rec Handle: 0x%x", handle);
607
608 p += sizeof(uint32_t);
609 bufsize -= sizeof(uint32_t);
610
611 orec = sdp_record_find(handle);
612
613 debug("SvcRecOld: %p", orec);
614
615 if (orec) {
616 sdp_record_t *nrec = extract_pdu_server(BDADDR_ANY, p, bufsize, handle, &scanned);
617 if (nrec && handle == nrec->handle) {
618 update_db_timestamp();
619 update_svclass_list();
620 } else {
621 debug("SvcRecHandle : 0x%x", handle);
622 debug("SvcRecHandleNew : 0x%x", nrec->handle);
623 debug("SvcRecNew : %p", nrec);
624 debug("SvcRecOld : %p", orec);
625 debug("Failure to update, restore old value");
626
627 if (nrec)
628 sdp_record_free(nrec);
629 status = SDP_INVALID_SYNTAX;
630 }
631 } else
632 status = SDP_INVALID_RECORD_HANDLE;
633
634 p = rsp->data;
635 bt_put_unaligned(htons(status), (uint16_t *) p);
636 rsp->data_size = sizeof(uint16_t);
637 return status;
638 }
639
640 /*
641 * Remove a registered service record
642 */
service_remove_req(sdp_req_t * req,sdp_buf_t * rsp)643 int service_remove_req(sdp_req_t *req, sdp_buf_t *rsp)
644 {
645 uint8_t *p = req->buf + sizeof(sdp_pdu_hdr_t);
646 uint32_t handle = ntohl(bt_get_unaligned((uint32_t *) p));
647 sdp_record_t *rec;
648 int status = 0;
649
650 /* extract service record handle */
651 p += sizeof(uint32_t);
652
653 rec = sdp_record_find(handle);
654 if (rec) {
655 sdp_svcdb_collect(rec);
656 status = sdp_record_remove(handle);
657 sdp_record_free(rec);
658 if (status == 0) {
659 update_db_timestamp();
660 update_svclass_list();
661 }
662 } else {
663 status = SDP_INVALID_RECORD_HANDLE;
664 debug("Could not find record : 0x%x", handle);
665 }
666
667 p = rsp->data;
668 bt_put_unaligned(htons(status), (uint16_t *) p);
669 rsp->data_size = sizeof(uint16_t);
670
671 return status;
672 }
673