1 /* 2 * search.c 3 * 4 * Copyright (c) 2001-2003 Maksim Yevmenkin <m_evmenkin@yahoo.com> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 * 28 * $Id: search.c,v 1.2 2003/09/08 17:35:15 max Exp $ 29 * $FreeBSD$ 30 */ 31 32 #include <bluetooth.h> 33 #include <ctype.h> 34 #include <sdp.h> 35 #include <stdio.h> 36 #include <stdlib.h> 37 #include "sdpcontrol.h" 38 39 /* List of the attributes we are looking for */ 40 static uint32_t attrs[] = 41 { 42 SDP_ATTR_RANGE( SDP_ATTR_SERVICE_RECORD_HANDLE, 43 SDP_ATTR_SERVICE_RECORD_HANDLE), 44 SDP_ATTR_RANGE( SDP_ATTR_SERVICE_CLASS_ID_LIST, 45 SDP_ATTR_SERVICE_CLASS_ID_LIST), 46 SDP_ATTR_RANGE( SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST, 47 SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST), 48 SDP_ATTR_RANGE( SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST, 49 SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST) 50 }; 51 #define attrs_len (sizeof(attrs)/sizeof(attrs[0])) 52 53 /* Buffer for the attributes */ 54 #define NRECS 25 /* request this much records from the SDP server */ 55 #define BSIZE 256 /* one attribute buffer size */ 56 static uint8_t buffer[NRECS * attrs_len][BSIZE]; 57 58 /* SDP attributes */ 59 static sdp_attr_t values[NRECS * attrs_len]; 60 #define values_len (sizeof(values)/sizeof(values[0])) 61 62 /* 63 * Print Service Class ID List 64 * 65 * The ServiceClassIDList attribute consists of a data element sequence in 66 * which each data element is a UUID representing the service classes that 67 * a given service record conforms to. The UUIDs are listed in order from 68 * the most specific class to the most general class. The ServiceClassIDList 69 * must contain at least one service class UUID. 70 */ 71 72 static void 73 print_service_class_id_list(uint8_t const *start, uint8_t const *end) 74 { 75 uint32_t type, len, value; 76 77 if (end - start < 2) { 78 fprintf(stderr, "Invalid Service Class ID List. " \ 79 "Too short, len=%zd\n", end - start); 80 return; 81 } 82 83 SDP_GET8(type, start); 84 switch (type) { 85 case SDP_DATA_SEQ8: 86 SDP_GET8(len, start); 87 break; 88 89 case SDP_DATA_SEQ16: 90 SDP_GET16(len, start); 91 break; 92 93 case SDP_DATA_SEQ32: 94 SDP_GET32(len, start); 95 break; 96 97 default: 98 fprintf(stderr, "Invalid Service Class ID List. " \ 99 "Not a sequence, type=%#x\n", type); 100 return; 101 /* NOT REACHED */ 102 } 103 104 while (start < end) { 105 SDP_GET8(type, start); 106 switch (type) { 107 case SDP_DATA_UUID16: 108 SDP_GET16(value, start); 109 fprintf(stdout, "\t%s (%#4.4x)\n", 110 sdp_uuid2desc(value), value); 111 break; 112 113 case SDP_DATA_UUID32: 114 SDP_GET32(value, start); 115 fprintf(stdout, "\t%#8.8x\n", value); 116 break; 117 118 case SDP_DATA_UUID128: { 119 int128_t uuid; 120 121 SDP_GET128(&uuid, start); 122 fprintf(stdout, "\t%#8.8x-%4.4x-%4.4x-%4.4x-%4.4x%8.8x\n", 123 *(uint32_t *)&uuid.b[0], 124 *(uint16_t *)&uuid.b[4], 125 *(uint16_t *)&uuid.b[6], 126 *(uint16_t *)&uuid.b[8], 127 *(uint16_t *)&uuid.b[10], 128 *(uint32_t *)&uuid.b[12]); 129 } break; 130 131 default: 132 fprintf(stderr, "Invalid Service Class ID List. " \ 133 "Not a UUID, type=%#x\n", type); 134 return; 135 /* NOT REACHED */ 136 } 137 } 138 } /* print_service_class_id_list */ 139 140 /* 141 * Print Protocol Descriptor List 142 * 143 * If the ProtocolDescriptorList describes a single stack, it takes the form 144 * of a data element sequence in which each element of the sequence is a 145 * protocol descriptor. Each protocol descriptor is, in turn, a data element 146 * sequence whose first element is a UUID identifying the protocol and whose 147 * successive elements are protocol-specific parameters. The protocol 148 * descriptors are listed in order from the lowest layer protocol to the 149 * highest layer protocol used to gain access to the service. If it is possible 150 * for more than one kind of protocol stack to be used to gain access to the 151 * service, the ProtocolDescriptorList takes the form of a data element 152 * alternative where each member is a data element sequence as described above. 153 */ 154 155 static void 156 print_protocol_descriptor(uint8_t const *start, uint8_t const *end) 157 { 158 union { 159 uint8_t uint8; 160 uint16_t uint16; 161 uint32_t uint32; 162 uint64_t uint64; 163 int128_t int128; 164 } value; 165 uint32_t type, len, param; 166 167 /* Get Protocol UUID */ 168 SDP_GET8(type, start); 169 switch (type) { 170 case SDP_DATA_UUID16: 171 SDP_GET16(value.uint16, start); 172 fprintf(stdout, "\t%s (%#4.4x)\n", sdp_uuid2desc(value.uint16), 173 value.uint16); 174 break; 175 176 case SDP_DATA_UUID32: 177 SDP_GET32(value.uint32, start); 178 fprintf(stdout, "\t%#8.8x\n", value.uint32); 179 break; 180 181 case SDP_DATA_UUID128: 182 SDP_GET128(&value.int128, start); 183 fprintf(stdout, "\t%#8.8x-%4.4x-%4.4x-%4.4x-%4.4x%8.8x\n", 184 *(uint32_t *)&value.int128.b[0], 185 *(uint16_t *)&value.int128.b[4], 186 *(uint16_t *)&value.int128.b[6], 187 *(uint16_t *)&value.int128.b[8], 188 *(uint16_t *)&value.int128.b[10], 189 *(uint32_t *)&value.int128.b[12]); 190 break; 191 192 default: 193 fprintf(stderr, "Invalid Protocol Descriptor. " \ 194 "Not a UUID, type=%#x\n", type); 195 return; 196 /* NOT REACHED */ 197 } 198 199 /* Protocol specific parameters */ 200 for (param = 1; start < end; param ++) { 201 fprintf(stdout, "\t\tProtocol specific parameter #%d: ", param); 202 203 SDP_GET8(type, start); 204 switch (type) { 205 case SDP_DATA_NIL: 206 fprintf(stdout, "nil\n"); 207 break; 208 209 case SDP_DATA_UINT8: 210 case SDP_DATA_INT8: 211 case SDP_DATA_BOOL: 212 SDP_GET8(value.uint8, start); 213 fprintf(stdout, "u/int8/bool %u\n", value.uint8); 214 break; 215 216 case SDP_DATA_UINT16: 217 case SDP_DATA_INT16: 218 case SDP_DATA_UUID16: 219 SDP_GET16(value.uint16, start); 220 fprintf(stdout, "u/int/uuid16 %u\n", value.uint16); 221 break; 222 223 case SDP_DATA_UINT32: 224 case SDP_DATA_INT32: 225 case SDP_DATA_UUID32: 226 SDP_GET32(value.uint32, start); 227 fprintf(stdout, "u/int/uuid32 %u\n", value.uint32); 228 break; 229 230 case SDP_DATA_UINT64: 231 case SDP_DATA_INT64: 232 SDP_GET64(value.uint64, start); 233 fprintf(stdout, "u/int64 %ju\n", value.uint64); 234 break; 235 236 case SDP_DATA_UINT128: 237 case SDP_DATA_INT128: 238 case SDP_DATA_UUID128: 239 SDP_GET128(&value.int128, start); 240 fprintf(stdout, "u/int/uuid128 %#8.8x-%4.4x-%4.4x-%4.4x-%4.4x%8.8x\n", 241 *(uint32_t *)&value.int128.b[0], 242 *(uint16_t *)&value.int128.b[4], 243 *(uint16_t *)&value.int128.b[6], 244 *(uint16_t *)&value.int128.b[8], 245 *(uint16_t *)&value.int128.b[10], 246 *(uint32_t *)&value.int128.b[12]); 247 break; 248 249 case SDP_DATA_STR8: 250 case SDP_DATA_URL8: 251 SDP_GET8(len, start); 252 fprintf(stdout, "%*.*s\n", len, len, (char *) start); 253 start += len; 254 break; 255 256 case SDP_DATA_STR16: 257 case SDP_DATA_URL16: 258 SDP_GET16(len, start); 259 fprintf(stdout, "%*.*s\n", len, len, (char *) start); 260 start += len; 261 break; 262 263 case SDP_DATA_STR32: 264 case SDP_DATA_URL32: 265 SDP_GET32(len, start); 266 fprintf(stdout, "%*.*s\n", len, len, (char *) start); 267 start += len; 268 break; 269 270 case SDP_DATA_SEQ8: 271 case SDP_DATA_ALT8: 272 SDP_GET8(len, start); 273 for (; len > 0; start ++, len --) 274 fprintf(stdout, "%#2.2x ", *start); 275 fprintf(stdout, "\n"); 276 break; 277 278 case SDP_DATA_SEQ16: 279 case SDP_DATA_ALT16: 280 SDP_GET16(len, start); 281 for (; len > 0; start ++, len --) 282 fprintf(stdout, "%#2.2x ", *start); 283 fprintf(stdout, "\n"); 284 break; 285 286 case SDP_DATA_SEQ32: 287 case SDP_DATA_ALT32: 288 SDP_GET32(len, start); 289 for (; len > 0; start ++, len --) 290 fprintf(stdout, "%#2.2x ", *start); 291 fprintf(stdout, "\n"); 292 break; 293 294 default: 295 fprintf(stderr, "Invalid Protocol Descriptor. " \ 296 "Unknown data type: %#02x\n", type); 297 return; 298 /* NOT REACHED */ 299 } 300 } 301 } /* print_protocol_descriptor */ 302 303 static void 304 print_protocol_descriptor_list(uint8_t const *start, uint8_t const *end) 305 { 306 uint32_t type, len; 307 308 if (end - start < 2) { 309 fprintf(stderr, "Invalid Protocol Descriptor List. " \ 310 "Too short, len=%zd\n", end - start); 311 return; 312 } 313 314 SDP_GET8(type, start); 315 switch (type) { 316 case SDP_DATA_SEQ8: 317 SDP_GET8(len, start); 318 break; 319 320 case SDP_DATA_SEQ16: 321 SDP_GET16(len, start); 322 break; 323 324 case SDP_DATA_SEQ32: 325 SDP_GET32(len, start); 326 break; 327 328 default: 329 fprintf(stderr, "Invalid Protocol Descriptor List. " \ 330 "Not a sequence, type=%#x\n", type); 331 return; 332 /* NOT REACHED */ 333 } 334 335 while (start < end) { 336 SDP_GET8(type, start); 337 switch (type) { 338 case SDP_DATA_SEQ8: 339 SDP_GET8(len, start); 340 break; 341 342 case SDP_DATA_SEQ16: 343 SDP_GET16(len, start); 344 break; 345 346 case SDP_DATA_SEQ32: 347 SDP_GET32(len, start); 348 break; 349 350 default: 351 fprintf(stderr, "Invalid Protocol Descriptor List. " \ 352 "Not a sequence, type=%#x\n", type); 353 return; 354 /* NOT REACHED */ 355 } 356 357 print_protocol_descriptor(start, start + len); 358 start += len; 359 } 360 } /* print_protocol_descriptor_list */ 361 362 /* 363 * Print Bluetooth Profile Descriptor List 364 * 365 * The BluetoothProfileDescriptorList attribute consists of a data element 366 * sequence in which each element is a profile descriptor that contains 367 * information about a Bluetooth profile to which the service represented by 368 * this service record conforms. Each profile descriptor is a data element 369 * sequence whose first element is the UUID assigned to the profile and whose 370 * second element is a 16-bit profile version number. Each version of a profile 371 * is assigned a 16-bit unsigned integer profile version number, which consists 372 * of two 8-bit fields. The higher-order 8 bits contain the major version 373 * number field and the lower-order 8 bits contain the minor version number 374 * field. 375 */ 376 377 static void 378 print_bluetooth_profile_descriptor_list(uint8_t const *start, uint8_t const *end) 379 { 380 uint32_t type, len, value; 381 382 if (end - start < 2) { 383 fprintf(stderr, "Invalid Bluetooth Profile Descriptor List. " \ 384 "Too short, len=%zd\n", end - start); 385 return; 386 } 387 388 SDP_GET8(type, start); 389 switch (type) { 390 case SDP_DATA_SEQ8: 391 SDP_GET8(len, start); 392 break; 393 394 case SDP_DATA_SEQ16: 395 SDP_GET16(len, start); 396 break; 397 398 case SDP_DATA_SEQ32: 399 SDP_GET32(len, start); 400 break; 401 402 default: 403 fprintf(stderr, "Invalid Bluetooth Profile Descriptor List. " \ 404 "Not a sequence, type=%#x\n", type); 405 return; 406 /* NOT REACHED */ 407 } 408 409 while (start < end) { 410 SDP_GET8(type, start); 411 switch (type) { 412 case SDP_DATA_SEQ8: 413 SDP_GET8(len, start); 414 break; 415 416 case SDP_DATA_SEQ16: 417 SDP_GET16(len, start); 418 break; 419 420 case SDP_DATA_SEQ32: 421 SDP_GET32(len, start); 422 break; 423 424 default: 425 fprintf(stderr, "Invalid Bluetooth Profile " \ 426 "Descriptor List. " \ 427 "Not a sequence, type=%#x\n", type); 428 return; 429 /* NOT REACHED */ 430 } 431 432 /* Get UUID */ 433 SDP_GET8(type, start); 434 switch (type) { 435 case SDP_DATA_UUID16: 436 SDP_GET16(value, start); 437 fprintf(stdout, "\t%s (%#4.4x) ", 438 sdp_uuid2desc(value), value); 439 break; 440 441 case SDP_DATA_UUID32: 442 SDP_GET32(value, start); 443 fprintf(stdout, "\t%#8.8x ", value); 444 break; 445 446 case SDP_DATA_UUID128: { 447 int128_t uuid; 448 449 SDP_GET128(&uuid, start); 450 fprintf(stdout, "\t%#8.8x-%4.4x-%4.4x-%4.4x-%4.4x%8.8x ", 451 *(uint32_t *)&uuid.b[0], 452 *(uint16_t *)&uuid.b[4], 453 *(uint16_t *)&uuid.b[6], 454 *(uint16_t *)&uuid.b[8], 455 *(uint16_t *)&uuid.b[10], 456 *(uint32_t *)&uuid.b[12]); 457 } break; 458 459 default: 460 fprintf(stderr, "Invalid Bluetooth Profile " \ 461 "Descriptor List. " \ 462 "Not a UUID, type=%#x\n", type); 463 return; 464 /* NOT REACHED */ 465 } 466 467 /* Get version */ 468 SDP_GET8(type, start); 469 if (type != SDP_DATA_UINT16) { 470 fprintf(stderr, "Invalid Bluetooth Profile " \ 471 "Descriptor List. " \ 472 "Invalid version type=%#x\n", type); 473 return; 474 } 475 476 SDP_GET16(value, start); 477 fprintf(stdout, "ver. %d.%d\n", 478 (value >> 8) & 0xff, value & 0xff); 479 } 480 } /* print_bluetooth_profile_descriptor_list */ 481 482 /* Perform SDP search command */ 483 static int 484 do_sdp_search(void *xs, int argc, char **argv) 485 { 486 char *ep = NULL; 487 int32_t n, type, value; 488 uint16_t service; 489 490 /* Parse command line arguments */ 491 switch (argc) { 492 case 1: 493 n = strtoul(argv[0], &ep, 16); 494 if (*ep != 0) { 495 switch (tolower(argv[0][0])) { 496 case 'c': /* CIP/CTP */ 497 switch (tolower(argv[0][1])) { 498 case 'i': 499 service = SDP_SERVICE_CLASS_COMMON_ISDN_ACCESS; 500 break; 501 502 case 't': 503 service = SDP_SERVICE_CLASS_CORDLESS_TELEPHONY; 504 break; 505 506 default: 507 return (USAGE); 508 /* NOT REACHED */ 509 } 510 break; 511 512 case 'd': /* DialUp Networking */ 513 service = SDP_SERVICE_CLASS_DIALUP_NETWORKING; 514 break; 515 516 case 'f': /* Fax/OBEX File Transfer */ 517 switch (tolower(argv[0][1])) { 518 case 'a': 519 service = SDP_SERVICE_CLASS_FAX; 520 break; 521 522 case 't': 523 service = SDP_SERVICE_CLASS_OBEX_FILE_TRANSFER; 524 break; 525 526 default: 527 return (USAGE); 528 /* NOT REACHED */ 529 } 530 break; 531 532 case 'g': /* GN */ 533 service = SDP_SERVICE_CLASS_GN; 534 break; 535 536 case 'h': /* Headset/HID */ 537 switch (tolower(argv[0][1])) { 538 case 'i': 539 service = SDP_SERVICE_CLASS_HUMAN_INTERFACE_DEVICE; 540 break; 541 542 case 's': 543 service = SDP_SERVICE_CLASS_HEADSET; 544 break; 545 546 default: 547 return (USAGE); 548 /* NOT REACHED */ 549 } 550 break; 551 552 case 'l': /* LAN Access Using PPP */ 553 service = SDP_SERVICE_CLASS_LAN_ACCESS_USING_PPP; 554 break; 555 556 case 'n': /* NAP */ 557 service = SDP_SERVICE_CLASS_NAP; 558 break; 559 560 case 'o': /* OBEX Object Push */ 561 service = SDP_SERVICE_CLASS_OBEX_OBJECT_PUSH; 562 break; 563 564 case 's': /* Serial Port */ 565 service = SDP_SERVICE_CLASS_SERIAL_PORT; 566 break; 567 568 default: 569 return (USAGE); 570 /* NOT REACHED */ 571 } 572 } else 573 service = (uint16_t) n; 574 break; 575 576 default: 577 return (USAGE); 578 } 579 580 if (service < SDP_SERVICE_CLASS_SERVICE_DISCOVERY_SERVER) 581 return (USAGE); 582 583 /* Initialize attribute values array */ 584 for (n = 0; n < values_len; n ++) { 585 values[n].flags = SDP_ATTR_INVALID; 586 values[n].attr = 0; 587 values[n].vlen = BSIZE; 588 values[n].value = buffer[n]; 589 } 590 591 /* Do SDP Service Search Attribute Request */ 592 n = sdp_search(xs, 1, &service, attrs_len, attrs, values_len, values); 593 if (n != 0) 594 return (ERROR); 595 596 /* Print attributes values */ 597 for (n = 0; n < values_len; n ++) { 598 if (values[n].flags != SDP_ATTR_OK) 599 break; 600 601 switch (values[n].attr) { 602 case SDP_ATTR_SERVICE_RECORD_HANDLE: 603 fprintf(stdout, "\n"); 604 if (values[n].vlen == 5) { 605 SDP_GET8(type, values[n].value); 606 if (type == SDP_DATA_UINT32) { 607 SDP_GET32(value, values[n].value); 608 fprintf(stdout, "Record Handle: " \ 609 "%#8.8x\n", value); 610 } else 611 fprintf(stderr, "Invalid type=%#x " \ 612 "Record Handle " \ 613 "attribute!\n", type); 614 } else 615 fprintf(stderr, "Invalid size=%d for Record " \ 616 "Handle attribute\n", 617 values[n].vlen); 618 break; 619 620 case SDP_ATTR_SERVICE_CLASS_ID_LIST: 621 fprintf(stdout, "Service Class ID List:\n"); 622 print_service_class_id_list(values[n].value, 623 values[n].value + values[n].vlen); 624 break; 625 626 case SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST: 627 fprintf(stdout, "Protocol Descriptor List:\n"); 628 print_protocol_descriptor_list(values[n].value, 629 values[n].value + values[n].vlen); 630 break; 631 632 case SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST: 633 fprintf(stdout, "Bluetooth Profile Descriptor List:\n"); 634 print_bluetooth_profile_descriptor_list(values[n].value, 635 values[n].value + values[n].vlen); 636 break; 637 638 default: 639 fprintf(stderr, "Unexpected attribute ID=%#4.4x\n", 640 values[n].attr); 641 break; 642 } 643 } 644 645 return (OK); 646 } /* do_sdp_search */ 647 648 /* Perform SDP browse command */ 649 static int 650 do_sdp_browse(void *xs, int argc, char **argv) 651 { 652 #undef _STR 653 #undef STR 654 #define _STR(x) #x 655 #define STR(x) _STR(x) 656 657 static char const * const av[] = { 658 STR(SDP_SERVICE_CLASS_PUBLIC_BROWSE_GROUP), 659 NULL 660 }; 661 662 switch (argc) { 663 case 0: 664 argc = 1; 665 argv = (char **) av; 666 /* FALL THROUGH */ 667 case 1: 668 return (do_sdp_search(xs, argc, argv)); 669 } 670 671 return (USAGE); 672 } /* do_sdp_browse */ 673 674 /* List of SDP commands */ 675 struct sdp_command sdp_commands[] = { 676 { 677 "Browse [<Group>]", 678 "Browse for services. The <Group> parameter is a 16-bit UUID of the group\n" \ 679 "to browse. If omitted <Group> is set to Public Browse Group.\n\n" \ 680 "\t<Group> - xxxx; 16-bit UUID of the group to browse\n", 681 do_sdp_browse 682 }, 683 { 684 "Search <Service>", 685 "Search for the <Service>. The <Service> parameter is a 16-bit UUID of the\n" \ 686 "service to search for. For some services it is possible to use service name\n"\ 687 "instead of service UUID\n\n" \ 688 "\t<Service> - xxxx; 16-bit UUID of the service to search for\n\n" \ 689 "\tKnown service names\n" \ 690 "\t===================\n" \ 691 "\tCIP - Common ISDN Access\n" \ 692 "\tCTP - Cordless Telephony\n" \ 693 "\tDUN - DialUp Networking\n" \ 694 "\tFAX - Fax\n" \ 695 "\tFTRN - OBEX File Transfer\n" \ 696 "\tGN - GN\n" \ 697 "\tHID - Human Interface Device\n" \ 698 "\tHSET - Headset\n" \ 699 "\tLAN - LAN Access Using PPP\n" \ 700 "\tNAP - Network Access Point\n" \ 701 "\tOPUSH - OBEX Object Push\n" \ 702 "\tSP - Serial Port\n", 703 do_sdp_search 704 }, 705 { NULL, NULL, NULL } 706 }; 707 708