/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2001-2002 Nokia Corporation * Copyright (C) 2002-2003 Maxim Krasnyansky * Copyright (C) 2002-2005 Marcel Holtmann * Copyright (C) 2002-2003 Stephen Crane * Copyright (C) 2002-2003 Jean Tourrilhes * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation; * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. * IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY * CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, * COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS * SOFTWARE IS DISCLAIMED. * * * $Id$ */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #define for_each_opt(opt, long, short) while ((opt=getopt_long(argc, argv, short ? short:"+", long, 0)) != -1) /* * Convert a string to a BDADDR, with a few "enhancements" - Jean II */ static int estr2ba(char *str, bdaddr_t *ba) { /* Only trap "local", "any" is already dealt with */ if(!strcmp(str, "local")) { bacpy(ba, BDADDR_LOCAL); return 0; } return str2ba(str, ba); } #define DEFAULT_VIEW 0 /* Display only known attribute */ #define TREE_VIEW 1 /* Display full attribute tree */ #define RAW_VIEW 2 /* Display raw tree */ /* Pass args to the inquiry/search handler */ struct search_context { char *svc; /* Service */ uuid_t group; /* Browse group */ int view; /* View mode */ uint32_t handle; /* Service record handle */ }; typedef int (*handler_t)(bdaddr_t *bdaddr, struct search_context *arg); static char UUID_str[MAX_LEN_UUID_STR]; static bdaddr_t interface; /* Definition of attribute members */ struct member_def { char *name; }; /* Definition of an attribute */ struct attrib_def { int num; /* Numeric ID - 16 bits */ char *name; /* User readable name */ struct member_def *members; /* Definition of attribute args */ int member_max; /* Max of attribute arg definitions */ }; /* Definition of a service or protocol */ struct uuid_def { int num; /* Numeric ID - 16 bits */ char *name; /* User readable name */ struct attrib_def *attribs; /* Specific attribute definitions */ int attrib_max; /* Max of attribute definitions */ }; /* Context information about current attribute */ struct attrib_context { struct uuid_def *service; /* Service UUID, if known */ struct attrib_def *attrib; /* Description of the attribute */ int member_index; /* Index of current attribute member */ }; /* Context information about the whole service */ struct service_context { struct uuid_def *service; /* Service UUID, if known */ }; /* Allow us to do nice formatting of the lists */ static char *indent_spaces = " "; /* ID of the service attribute. * Most attributes after 0x200 are defined based on the service, so * we need to find what is the service (which is messy) - Jean II */ #define SERVICE_ATTR 0x1 /* Definition of the optional arguments in protocol list */ static struct member_def protocol_members[] = { { "Protocol" }, { "Channel/Port" }, { "Version" }, }; /* Definition of the optional arguments in profile list */ static struct member_def profile_members[] = { { "Profile" }, { "Version" }, }; /* Definition of the optional arguments in Language list */ static struct member_def language_members[] = { { "Code ISO639" }, { "Encoding" }, { "Base Offset" }, }; /* Name of the various common attributes. See BT assigned numbers */ static struct attrib_def attrib_names[] = { { 0x0, "ServiceRecordHandle", NULL, 0 }, { 0x1, "ServiceClassIDList", NULL, 0 }, { 0x2, "ServiceRecordState", NULL, 0 }, { 0x3, "ServiceID", NULL, 0 }, { 0x4, "ProtocolDescriptorList", protocol_members, sizeof(protocol_members)/sizeof(struct member_def) }, { 0x5, "BrowseGroupList", NULL, 0 }, { 0x6, "LanguageBaseAttributeIDList", language_members, sizeof(language_members)/sizeof(struct member_def) }, { 0x7, "ServiceInfoTimeToLive", NULL, 0 }, { 0x8, "ServiceAvailability", NULL, 0 }, { 0x9, "BluetoothProfileDescriptorList", profile_members, sizeof(profile_members)/sizeof(struct member_def) }, { 0xA, "DocumentationURL", NULL, 0 }, { 0xB, "ClientExecutableURL", NULL, 0 }, { 0xC, "IconURL", NULL, 0 }, { 0xD, "AdditionalProtocolDescriptorLists", NULL, 0 }, /* Definitions after that are tricky (per profile or offset) */ }; const int attrib_max = sizeof(attrib_names)/sizeof(struct attrib_def); /* Name of the various SPD attributes. See BT assigned numbers */ static struct attrib_def sdp_attrib_names[] = { { 0x200, "VersionNumberList", NULL, 0 }, { 0x201, "ServiceDatabaseState", NULL, 0 }, }; /* Name of the various SPD attributes. See BT assigned numbers */ static struct attrib_def browse_attrib_names[] = { { 0x200, "GroupID", NULL, 0 }, }; /* Name of the various Device ID attributes. See Device Id spec. */ static struct attrib_def did_attrib_names[] = { { 0x200, "SpecificationID", NULL, 0 }, { 0x201, "VendorID", NULL, 0 }, { 0x202, "ProductID", NULL, 0 }, { 0x203, "Version", NULL, 0 }, { 0x204, "PrimaryRecord", NULL, 0 }, { 0x205, "VendorIDSource", NULL, 0 }, }; /* Name of the various HID attributes. See HID spec. */ static struct attrib_def hid_attrib_names[] = { { 0x200, "DeviceReleaseNum", NULL, 0 }, { 0x201, "ParserVersion", NULL, 0 }, { 0x202, "DeviceSubclass", NULL, 0 }, { 0x203, "CountryCode", NULL, 0 }, { 0x204, "VirtualCable", NULL, 0 }, { 0x205, "ReconnectInitiate", NULL, 0 }, { 0x206, "DescriptorList", NULL, 0 }, { 0x207, "LangIDBaseList", NULL, 0 }, { 0x208, "SDPDisable", NULL, 0 }, { 0x209, "BatteryPower", NULL, 0 }, { 0x20a, "RemoteWakeup", NULL, 0 }, { 0x20b, "ProfileVersion", NULL, 0 }, { 0x20c, "SupervisionTimeout", NULL, 0 }, { 0x20d, "NormallyConnectable", NULL, 0 }, { 0x20e, "BootDevice", NULL, 0 }, }; /* Name of the various PAN attributes. See BT assigned numbers */ /* Note : those need to be double checked - Jean II */ static struct attrib_def pan_attrib_names[] = { { 0x200, "IpSubnet", NULL, 0 }, /* Obsolete ??? */ { 0x30A, "SecurityDescription", NULL, 0 }, { 0x30B, "NetAccessType", NULL, 0 }, { 0x30C, "MaxNetAccessrate", NULL, 0 }, { 0x30D, "IPv4Subnet", NULL, 0 }, { 0x30E, "IPv6Subnet", NULL, 0 }, }; /* Name of the various Generic-Audio attributes. See BT assigned numbers */ /* Note : totally untested - Jean II */ static struct attrib_def audio_attrib_names[] = { { 0x302, "Remote audio volume control", NULL, 0 }, }; /* Same for the UUIDs. See BT assigned numbers */ static struct uuid_def uuid16_names[] = { /* -- Protocols -- */ { 0x0001, "SDP", NULL, 0 }, { 0x0002, "UDP", NULL, 0 }, { 0x0003, "RFCOMM", NULL, 0 }, { 0x0004, "TCP", NULL, 0 }, { 0x0005, "TCS-BIN", NULL, 0 }, { 0x0006, "TCS-AT", NULL, 0 }, { 0x0008, "OBEX", NULL, 0 }, { 0x0009, "IP", NULL, 0 }, { 0x000a, "FTP", NULL, 0 }, { 0x000c, "HTTP", NULL, 0 }, { 0x000e, "WSP", NULL, 0 }, { 0x000f, "BNEP", NULL, 0 }, { 0x0010, "UPnP/ESDP", NULL, 0 }, { 0x0011, "HIDP", NULL, 0 }, { 0x0012, "HardcopyControlChannel", NULL, 0 }, { 0x0014, "HardcopyDataChannel", NULL, 0 }, { 0x0016, "HardcopyNotification", NULL, 0 }, { 0x0017, "AVCTP", NULL, 0 }, { 0x0019, "AVDTP", NULL, 0 }, { 0x001b, "CMTP", NULL, 0 }, { 0x001d, "UDI_C-Plane", NULL, 0 }, { 0x0100, "L2CAP", NULL, 0 }, /* -- Services -- */ { 0x1000, "ServiceDiscoveryServerServiceClassID", sdp_attrib_names, sizeof(sdp_attrib_names)/sizeof(struct attrib_def) }, { 0x1001, "BrowseGroupDescriptorServiceClassID", browse_attrib_names, sizeof(browse_attrib_names)/sizeof(struct attrib_def) }, { 0x1002, "PublicBrowseGroup", NULL, 0 }, { 0x1101, "SerialPort", NULL, 0 }, { 0x1102, "LANAccessUsingPPP", NULL, 0 }, { 0x1103, "DialupNetworking (DUN)", NULL, 0 }, { 0x1104, "IrMCSync", NULL, 0 }, { 0x1105, "OBEXObjectPush", NULL, 0 }, { 0x1106, "OBEXFileTransfer", NULL, 0 }, { 0x1107, "IrMCSyncCommand", NULL, 0 }, { 0x1108, "Headset", audio_attrib_names, sizeof(audio_attrib_names)/sizeof(struct attrib_def) }, { 0x1109, "CordlessTelephony", NULL, 0 }, { 0x110a, "AudioSource", NULL, 0 }, { 0x110b, "AudioSink", NULL, 0 }, { 0x110c, "RemoteControlTarget", NULL, 0 }, { 0x110d, "AdvancedAudio", NULL, 0 }, { 0x110e, "RemoteControl", NULL, 0 }, { 0x110f, "VideoConferencing", NULL, 0 }, { 0x1110, "Intercom", NULL, 0 }, { 0x1111, "Fax", NULL, 0 }, { 0x1112, "HeadsetAudioGateway", NULL, 0 }, { 0x1113, "WAP", NULL, 0 }, { 0x1114, "WAP Client", NULL, 0 }, { 0x1115, "PANU (PAN/BNEP)", pan_attrib_names, sizeof(pan_attrib_names)/sizeof(struct attrib_def) }, { 0x1116, "NAP (PAN/BNEP)", pan_attrib_names, sizeof(pan_attrib_names)/sizeof(struct attrib_def) }, { 0x1117, "GN (PAN/BNEP)", pan_attrib_names, sizeof(pan_attrib_names)/sizeof(struct attrib_def) }, { 0x1118, "DirectPrinting (BPP)", NULL, 0 }, { 0x1119, "ReferencePrinting (BPP)", NULL, 0 }, { 0x111a, "Imaging (BIP)", NULL, 0 }, { 0x111b, "ImagingResponder (BIP)", NULL, 0 }, { 0x111c, "ImagingAutomaticArchive (BIP)", NULL, 0 }, { 0x111d, "ImagingReferencedObjects (BIP)", NULL, 0 }, { 0x111e, "Handsfree", NULL, 0 }, { 0x111f, "HandsfreeAudioGateway", NULL, 0 }, { 0x1120, "DirectPrintingReferenceObjectsService (BPP)", NULL, 0 }, { 0x1121, "ReflectedUI (BPP)", NULL, 0 }, { 0x1122, "BasicPrinting (BPP)", NULL, 0 }, { 0x1123, "PrintingStatus (BPP)", NULL, 0 }, { 0x1124, "HumanInterfaceDeviceService (HID)", hid_attrib_names, sizeof(hid_attrib_names)/sizeof(struct attrib_def) }, { 0x1125, "HardcopyCableReplacement (HCR)", NULL, 0 }, { 0x1126, "HCR_Print (HCR)", NULL, 0 }, { 0x1127, "HCR_Scan (HCR)", NULL, 0 }, { 0x1128, "Common ISDN Access (CIP)", NULL, 0 }, { 0x1129, "VideoConferencingGW (VCP)", NULL, 0 }, { 0x112d, "SIM Access (SAP)", NULL, 0 }, /* ... */ { 0x1200, "PnPInformation", did_attrib_names, sizeof(did_attrib_names)/sizeof(struct attrib_def) }, { 0x1201, "GenericNetworking", NULL, 0 }, { 0x1202, "GenericFileTransfer", NULL, 0 }, { 0x1203, "GenericAudio", audio_attrib_names, sizeof(audio_attrib_names)/sizeof(struct attrib_def) }, { 0x1204, "GenericTelephony", NULL, 0 }, }; static const int uuid16_max = sizeof(uuid16_names)/sizeof(struct uuid_def); static void sdp_data_printf(sdp_data_t *, struct attrib_context *, int); /* * Parse a UUID. * The BT assigned numbers only list UUID16, so I'm not sure the * other types will ever get used... */ static void sdp_uuid_printf(uuid_t *uuid, struct attrib_context *context, int indent) { if (uuid) { if (uuid->type == SDP_UUID16) { uint16_t uuidNum = uuid->value.uuid16; struct uuid_def *uuidDef = NULL; int i; for (i = 0; i < uuid16_max; i++) if (uuid16_names[i].num == uuidNum) { uuidDef = &uuid16_names[i]; break; } /* Check if it's the service attribute */ if (context->attrib && context->attrib->num == SERVICE_ATTR) { /* We got the service ID !!! */ context->service = uuidDef; } if (uuidDef) printf("%.*sUUID16 : 0x%.4x - %s\n", indent, indent_spaces, uuidNum, uuidDef->name); else printf("%.*sUUID16 : 0x%.4x\n", indent, indent_spaces, uuidNum); } else if (uuid->type == SDP_UUID32) { struct uuid_def *uuidDef = NULL; int i; if (!(uuid->value.uuid32 & 0xffff0000)) { uint16_t uuidNum = uuid->value.uuid32; for (i = 0; i < uuid16_max; i++) if (uuid16_names[i].num == uuidNum) { uuidDef = &uuid16_names[i]; break; } } if (uuidDef) printf("%.*sUUID32 : 0x%.8x - %s\n", indent, indent_spaces, uuid->value.uuid32, uuidDef->name); else printf("%.*sUUID32 : 0x%.8x\n", indent, indent_spaces, uuid->value.uuid32); } else if (uuid->type == SDP_UUID128) { unsigned int data0; unsigned short data1; unsigned short data2; unsigned short data3; unsigned int data4; unsigned short data5; memcpy(&data0, &uuid->value.uuid128.data[0], 4); memcpy(&data1, &uuid->value.uuid128.data[4], 2); memcpy(&data2, &uuid->value.uuid128.data[6], 2); memcpy(&data3, &uuid->value.uuid128.data[8], 2); memcpy(&data4, &uuid->value.uuid128.data[10], 4); memcpy(&data5, &uuid->value.uuid128.data[14], 2); printf("%.*sUUID128 : 0x%.8x-%.4x-%.4x-%.4x-%.8x-%.4x\n", indent, indent_spaces, ntohl(data0), ntohs(data1), ntohs(data2), ntohs(data3), ntohl(data4), ntohs(data5)); } else printf("%.*sEnum type of UUID not set\n", indent, indent_spaces); } else printf("%.*sNull passed to print UUID\n", indent, indent_spaces); } /* * Parse a sequence of data elements (i.e. a list) */ static void printf_dataseq(sdp_data_t * pData, struct attrib_context *context, int indent) { sdp_data_t *sdpdata = NULL; sdpdata = pData; if (sdpdata) { context->member_index = 0; do { sdp_data_printf(sdpdata, context, indent + 2); sdpdata = sdpdata->next; context->member_index++; } while (sdpdata); } else { printf("%.*sBroken dataseq link\n", indent, indent_spaces); } } /* * Parse a single data element (either in the attribute or in a data * sequence). */ static void sdp_data_printf(sdp_data_t *sdpdata, struct attrib_context *context, int indent) { char *member_name = NULL; /* Find member name. Almost black magic ;-) */ if (context && context->attrib && context->attrib->members && context->member_index < context->attrib->member_max) { member_name = context->attrib->members[context->member_index].name; } switch (sdpdata->dtd) { case SDP_DATA_NIL: printf("%.*sNil\n", indent, indent_spaces); break; case SDP_BOOL: case SDP_UINT8: case SDP_UINT16: case SDP_UINT32: case SDP_UINT64: case SDP_UINT128: case SDP_INT8: case SDP_INT16: case SDP_INT32: case SDP_INT64: case SDP_INT128: if (member_name) { printf("%.*s%s (Integer) : 0x%x\n", indent, indent_spaces, member_name, sdpdata->val.uint32); } else { printf("%.*sInteger : 0x%x\n", indent, indent_spaces, sdpdata->val.uint32); } break; case SDP_UUID16: case SDP_UUID32: case SDP_UUID128: //printf("%.*sUUID\n", indent, indent_spaces); sdp_uuid_printf(&sdpdata->val.uuid, context, indent); break; case SDP_TEXT_STR8: case SDP_TEXT_STR16: case SDP_TEXT_STR32: if (sdpdata->unitSize > strlen(sdpdata->val.str)) { int i; printf("%.*sData :", indent, indent_spaces); for (i = 0; i < sdpdata->unitSize; i++) printf(" %02x", (unsigned char) sdpdata->val.str[i]); printf("\n"); } else printf("%.*sText : \"%s\"\n", indent, indent_spaces, sdpdata->val.str); break; case SDP_URL_STR8: case SDP_URL_STR16: case SDP_URL_STR32: printf("%.*sURL : %s\n", indent, indent_spaces, sdpdata->val.str); break; case SDP_SEQ8: case SDP_SEQ16: case SDP_SEQ32: printf("%.*sData Sequence\n", indent, indent_spaces); printf_dataseq(sdpdata->val.dataseq, context, indent); break; case SDP_ALT8: case SDP_ALT16: case SDP_ALT32: printf("%.*sData Sequence Alternates\n", indent, indent_spaces); printf_dataseq(sdpdata->val.dataseq, context, indent); break; } } /* * Parse a single attribute. */ static void print_tree_attr_func(void *value, void *userData) { sdp_data_t *sdpdata = NULL; uint16_t attrId; struct service_context *service = (struct service_context *) userData; struct attrib_context context; struct attrib_def *attrDef = NULL; int i; sdpdata = (sdp_data_t *)value; attrId = sdpdata->attrId; /* Search amongst the generic attributes */ for (i = 0; i < attrib_max; i++) if (attrib_names[i].num == attrId) { attrDef = &attrib_names[i]; break; } /* Search amongst the specific attributes of this service */ if ((attrDef == NULL) && (service->service != NULL) && (service->service->attribs != NULL)) { struct attrib_def *svc_attribs = service->service->attribs; int svc_attrib_max = service->service->attrib_max; for (i = 0; i < svc_attrib_max; i++) if (svc_attribs[i].num == attrId) { attrDef = &svc_attribs[i]; break; } } if (attrDef) printf("Attribute Identifier : 0x%x - %s\n", attrId, attrDef->name); else printf("Attribute Identifier : 0x%x\n", attrId); /* Build context */ context.service = service->service; context.attrib = attrDef; context.member_index = 0; /* Parse attribute members */ if (sdpdata) sdp_data_printf(sdpdata, &context, 2); else printf(" NULL value\n"); /* Update service */ service->service = context.service; } /* * Main entry point of this library. Parse a SDP record. * We assume the record has already been read, parsed and cached * locally. Jean II */ static void print_tree_attr(sdp_record_t *rec) { if (rec && rec->attrlist) { struct service_context service = { NULL }; sdp_list_foreach(rec->attrlist, print_tree_attr_func, &service); } } static void print_raw_data(sdp_data_t *data, int indent) { struct uuid_def *def; char *str; int i, hex; if (!data) return; for (i = 0; i < indent; i++) printf("\t"); switch (data->dtd) { case SDP_DATA_NIL: printf("NIL\n"); break; case SDP_BOOL: printf("Bool %s\n", data->val.uint8 ? "True" : "False"); break; case SDP_UINT8: printf("UINT8 0x%02x\n", data->val.uint8); break; case SDP_UINT16: printf("UINT16 0x%04x\n", data->val.uint16); break; case SDP_UINT32: printf("UINT32 0x%08x\n", data->val.uint32); break; case SDP_UINT64: printf("UINT64 0x%016llx\n", data->val.uint64); break; case SDP_UINT128: printf("UINT128 ...\n"); break; case SDP_INT8: printf("INT8 %d\n", data->val.int8); break; case SDP_INT16: printf("INT16 %d\n", data->val.int16); break; case SDP_INT32: printf("INT32 %d\n", data->val.int32); break; case SDP_INT64: printf("INT64 %lld\n", data->val.int64); break; case SDP_INT128: printf("INT128 ...\n"); break; case SDP_UUID16: case SDP_UUID32: case SDP_UUID128: switch (data->val.uuid.type) { case SDP_UUID16: def = NULL; for (i = 0; i < uuid16_max; i++) if (uuid16_names[i].num == data->val.uuid.value.uuid16) { def = &uuid16_names[i]; break; } if (def) printf("UUID16 0x%04x - %s\n", data->val.uuid.value.uuid16, def->name); else printf("UUID16 0x%04x\n", data->val.uuid.value.uuid16); break; case SDP_UUID32: def = NULL; if (!(data->val.uuid.value.uuid32 & 0xffff0000)) { uint16_t value = data->val.uuid.value.uuid32; for (i = 0; i < uuid16_max; i++) if (uuid16_names[i].num == value) { def = &uuid16_names[i]; break; } } if (def) printf("UUID32 0x%08x - %s\n", data->val.uuid.value.uuid32, def->name); else printf("UUID32 0x%08x\n", data->val.uuid.value.uuid32); break; case SDP_UUID128: printf("UUID128 "); for (i = 0; i < 16; i++) { switch (i) { case 4: case 6: case 8: case 10: printf("-"); break; } printf("%02x", (unsigned char ) data->val.uuid.value.uuid128.data[i]); } printf("\n"); break; default: printf("UUID type 0x%02x\n", data->val.uuid.type); break; } break; case SDP_TEXT_STR8: case SDP_TEXT_STR16: case SDP_TEXT_STR32: str = data->val.str; if (data->unitSize > strlen(str) + 1) { hex = 0; for (i = 0; i < data->unitSize - 1; i++) if (!isprint(str[i])) { hex = 1; break; } if (str[data->unitSize - 1] != '\0') hex = 1; } else hex = 0; if (hex) { printf("String"); for (i = 0; i < data->unitSize; i++) printf(" %02x", (unsigned char) str[i]); printf("\n"); } else printf("String %s\n", str); break; case SDP_URL_STR8: case SDP_URL_STR16: case SDP_URL_STR32: printf("URL %s\n", data->val.str); break; case SDP_SEQ8: case SDP_SEQ16: case SDP_SEQ32: printf("Sequence\n"); print_raw_data(data->val.dataseq, indent + 1); break; case SDP_ALT8: case SDP_ALT16: case SDP_ALT32: printf("Alternate\n"); print_raw_data(data->val.dataseq, indent + 1); break; default: printf("Unknown type 0x%02x\n", data->dtd); break; } print_raw_data(data->next, indent); } static void print_raw_attr_func(void *value, void *userData) { sdp_data_t *data = (sdp_data_t *) value; struct attrib_def *def = NULL; int i; /* Search amongst the generic attributes */ for (i = 0; i < attrib_max; i++) if (attrib_names[i].num == data->attrId) { def = &attrib_names[i]; break; } if (def) printf("\tAttribute 0x%04x - %s\n", data->attrId, def->name); else printf("\tAttribute 0x%04x\n", data->attrId); if (data) print_raw_data(data, 2); else printf(" NULL value\n"); } static void print_raw_attr(sdp_record_t *rec) { if (rec && rec->attrlist) { printf("Sequence\n"); sdp_list_foreach(rec->attrlist, print_raw_attr_func, 0); } } /* * Set attributes with single values in SDP record * Jean II */ static int set_attrib(sdp_session_t *sess, uint32_t handle, uint16_t attrib, char *value) { sdp_list_t *attrid_list; uint32_t range = 0x0000ffff; sdp_record_t *rec; /* Get the old SDP record */ attrid_list = sdp_list_append(NULL, &range); rec = sdp_service_attr_req(sess, handle, SDP_ATTR_REQ_RANGE, attrid_list); if (!rec) { printf("Service get request failed.\n"); return -1; } /* Check the type of attribute */ if (!strncasecmp(value, "u0x", 3)) { /* UUID16 */ uint16_t value_int = 0; uuid_t value_uuid; value_int = strtoul(value + 3, NULL, 16); sdp_uuid16_create(&value_uuid, value_int); printf("Adding attrib 0x%X uuid16 0x%X to record 0x%X\n", attrib, value_int, handle); sdp_attr_add_new(rec, attrib, SDP_UUID16, &value_uuid.value.uuid16); } else if (!strncasecmp(value, "0x", 2)) { /* Int */ uint32_t value_int; value_int = strtoul(value + 2, NULL, 16); printf("Adding attrib 0x%X int 0x%X to record 0x%X\n", attrib, value_int, handle); sdp_attr_add_new(rec, attrib, SDP_UINT32, &value_int); } else { /* String */ printf("Adding attrib 0x%X string \"%s\" to record 0x%X\n", attrib, value, handle); /* Add/Update our attribute to the record */ sdp_attr_add_new(rec, attrib, SDP_TEXT_STR8, value); } /* Update on the server */ if (sdp_record_update(sess, rec)) { printf("Service Record update failed (%d).\n", errno); return -1; } return 0; } static struct option set_options[] = { { "help", 0,0, 'h' }, { 0, 0, 0, 0 } }; static char *set_help = "Usage:\n" "\tget record_handle attrib_id attrib_value\n"; /* * Add an attribute to an existing SDP record on the local SDP server */ static int cmd_setattr(int argc, char **argv) { int opt, status; uint32_t handle; uint16_t attrib; sdp_session_t *sess; for_each_opt(opt, set_options, NULL) { switch(opt) { default: printf(set_help); return -1; } } argc -= optind; argv += optind; if (argc < 3) { printf(set_help); return -1; } /* Convert command line args */ handle = strtoul(argv[0], NULL, 16); attrib = strtoul(argv[1], NULL, 16); /* Do it */ sess = sdp_connect(BDADDR_ANY, BDADDR_LOCAL, 0); if (!sess) return -1; status = set_attrib(sess, handle, attrib, argv[2]); sdp_close(sess); return status; } /* * We do only simple data sequences. Sequence of sequences is a pain ;-) * Jean II */ static int set_attribseq(sdp_session_t *session, uint32_t handle, uint16_t attrib, int argc, char **argv) { sdp_list_t *attrid_list; uint32_t range = 0x0000ffff; sdp_record_t *rec; sdp_data_t *pSequenceHolder = NULL; void **dtdArray; void **valueArray; void **allocArray; uint8_t uuid16 = SDP_UUID16; uint8_t uint32 = SDP_UINT32; uint8_t str8 = SDP_TEXT_STR8; int i; /* Get the old SDP record */ attrid_list = sdp_list_append(NULL, &range); rec = sdp_service_attr_req(session, handle, SDP_ATTR_REQ_RANGE, attrid_list); if (!rec) { printf("Service get request failed.\n"); return -1; } /* Create arrays */ dtdArray = (void **)malloc(argc * sizeof(void *)); valueArray = (void **)malloc(argc * sizeof(void *)); allocArray = (void **)malloc(argc * sizeof(void *)); /* Loop on all args, add them in arrays */ for (i = 0; i < argc; i++) { /* Check the type of attribute */ if (!strncasecmp(argv[i], "u0x", 3)) { /* UUID16 */ uint16_t value_int = strtoul((argv[i]) + 3, NULL, 16); uuid_t *value_uuid = (uuid_t *)malloc(sizeof(uuid_t)); allocArray[i] = value_uuid; sdp_uuid16_create(value_uuid, value_int); printf("Adding uuid16 0x%X to record 0x%X\n", value_int, handle); dtdArray[i] = &uuid16; valueArray[i] = &value_uuid->value.uuid16; } else if (!strncasecmp(argv[i], "0x", 2)) { /* Int */ uint32_t *value_int = (int *)malloc(sizeof(int)); allocArray[i] = value_int; *value_int = strtoul((argv[i]) + 2, NULL, 16); printf("Adding int 0x%X to record 0x%X\n", *value_int, handle); dtdArray[i] = &uint32; valueArray[i] = value_int; } else { /* String */ printf("Adding string \"%s\" to record 0x%X\n", argv[i], handle); dtdArray[i] = &str8; valueArray[i] = argv[i]; } } /* Add this sequence to the attrib list */ pSequenceHolder = sdp_seq_alloc(dtdArray, valueArray, argc); if (pSequenceHolder) { sdp_attr_replace(rec, attrib, pSequenceHolder); /* Update on the server */ if (sdp_record_update(session, rec)) { printf("Service Record update failed (%d).\n", errno); return -1; } } else { printf("Failed to create pSequenceHolder\n"); } /* Cleanup */ for (i = 0; i < argc; i++) free(allocArray[i]); free(dtdArray); free(valueArray); return 0; } static struct option seq_options[] = { { "help", 0,0, 'h' }, { 0, 0, 0, 0 } }; static char *seq_help = "Usage:\n" "\tget record_handle attrib_id attrib_values\n"; /* * Add an attribute sequence to an existing SDP record * on the local SDP server */ static int cmd_setseq(int argc, char **argv) { int opt, status; uint32_t handle; uint16_t attrib; sdp_session_t *sess; for_each_opt(opt, seq_options, NULL) { switch(opt) { default: printf(seq_help); return -1; } } argc -= optind; argv += optind; if (argc < 3) { printf(seq_help); return -1; } /* Convert command line args */ handle = strtoul(argv[0], NULL, 16); attrib = strtoul(argv[1], NULL, 16); argc -= 2; argv += 2; /* Do it */ sess = sdp_connect(BDADDR_ANY, BDADDR_LOCAL, 0); if (!sess) return -1; status = set_attribseq(sess, handle, attrib, argc, argv); sdp_close(sess); return status; } static void print_service_class(void *value, void *userData) { char ServiceClassUUID_str[MAX_LEN_SERVICECLASS_UUID_STR]; uuid_t *uuid = (uuid_t *)value; sdp_uuid2strn(uuid, UUID_str, MAX_LEN_UUID_STR); sdp_svclass_uuid2strn(uuid, ServiceClassUUID_str, MAX_LEN_SERVICECLASS_UUID_STR); printf(" \"%s\" (0x%s)\n", ServiceClassUUID_str, UUID_str); } static void print_service_desc(void *value, void *user) { char str[MAX_LEN_PROTOCOL_UUID_STR]; sdp_data_t *p = (sdp_data_t *)value, *s; int i = 0, proto = 0; for (; p; p = p->next, i++) { switch (p->dtd) { case SDP_UUID16: case SDP_UUID32: case SDP_UUID128: sdp_uuid2strn(&p->val.uuid, UUID_str, MAX_LEN_UUID_STR); sdp_proto_uuid2strn(&p->val.uuid, str, sizeof(str)); proto = sdp_uuid_to_proto(&p->val.uuid); printf(" \"%s\" (0x%s)\n", str, UUID_str); break; case SDP_UINT8: if (proto == RFCOMM_UUID) printf(" Channel: %d\n", p->val.uint8); else printf(" uint8: 0x%x\n", p->val.uint8); break; case SDP_UINT16: if (proto == L2CAP_UUID) { if (i == 1) printf(" PSM: %d\n", p->val.uint16); else printf(" Version: 0x%04x\n", p->val.uint16); } else if (proto == BNEP_UUID) if (i == 1) printf(" Version: 0x%04x\n", p->val.uint16); else printf(" uint16: 0x%x\n", p->val.uint16); else printf(" uint16: 0x%x\n", p->val.uint16); break; case SDP_SEQ16: printf(" SEQ16:"); for (s = p->val.dataseq; s; s = s->next) printf(" %x", s->val.uint16); printf("\n"); break; case SDP_SEQ8: printf(" SEQ8:"); for (s = p->val.dataseq; s; s = s->next) printf(" %x", s->val.uint8); printf("\n"); break; default: printf(" FIXME: dtd=0%x\n", p->dtd); break; } } } static void print_lang_attr(void *value, void *user) { sdp_lang_attr_t *lang = (sdp_lang_attr_t *)value; printf(" code_ISO639: 0x%02x\n", lang->code_ISO639); printf(" encoding: 0x%02x\n", lang->encoding); printf(" base_offset: 0x%02x\n", lang->base_offset); } static void print_access_protos(void *value, void *userData) { sdp_list_t *protDescSeq = (sdp_list_t *)value; sdp_list_foreach(protDescSeq, print_service_desc, 0); } static void print_profile_desc(void *value, void *userData) { sdp_profile_desc_t *desc = (sdp_profile_desc_t *)value; char str[MAX_LEN_PROFILEDESCRIPTOR_UUID_STR]; sdp_uuid2strn(&desc->uuid, UUID_str, MAX_LEN_UUID_STR); sdp_profile_uuid2strn(&desc->uuid, str, MAX_LEN_PROFILEDESCRIPTOR_UUID_STR); printf(" \"%s\" (0x%s)\n", str, UUID_str); if (desc->version) printf(" Version: 0x%04x\n", desc->version); } /* * Parse a SDP record in user friendly form. */ static void print_service_attr(sdp_record_t *rec) { sdp_list_t *list = 0, *proto = 0; sdp_record_print(rec); printf("Service RecHandle: 0x%x\n", rec->handle); if (sdp_get_service_classes(rec, &list) == 0) { printf("Service Class ID List:\n"); sdp_list_foreach(list, print_service_class, 0); sdp_list_free(list, free); } if (sdp_get_access_protos(rec, &proto) == 0) { printf("Protocol Descriptor List:\n"); sdp_list_foreach(proto, print_access_protos, 0); sdp_list_foreach(proto, (sdp_list_func_t)sdp_list_free, 0); sdp_list_free(proto, 0); } if (sdp_get_lang_attr(rec, &list) == 0) { printf("Language Base Attr List:\n"); sdp_list_foreach(list, print_lang_attr, 0); sdp_list_free(list, free); } if (sdp_get_profile_descs(rec, &list) == 0) { printf("Profile Descriptor List:\n"); sdp_list_foreach(list, print_profile_desc, 0); sdp_list_free(list, free); } } /* * Support for Service (de)registration */ typedef struct { char *name; char *provider; char *desc; unsigned int class; unsigned int profile; unsigned int channel; } svc_info_t; static void add_lang_attr(sdp_record_t *r) { sdp_lang_attr_t base_lang; sdp_list_t *langs = 0; /* UTF-8 MIBenum (http://www.iana.org/assignments/character-sets) */ base_lang.code_ISO639 = (0x65 << 8) | 0x6e; base_lang.encoding = 106; base_lang.base_offset = SDP_PRIMARY_LANG_BASE; langs = sdp_list_append(0, &base_lang); sdp_set_lang_attr(r, langs); sdp_list_free(langs, 0); } static int add_sp(sdp_session_t *session, svc_info_t *si) { sdp_list_t *svclass_id, *apseq, *proto[2], *profiles, *root, *aproto; uuid_t root_uuid, sp_uuid, l2cap, rfcomm; sdp_profile_desc_t profile; sdp_record_t record; uint8_t u8 = si->channel? si->channel: 1; sdp_data_t *channel; int ret = 0; memset((void *)&record, 0, sizeof(sdp_record_t)); record.handle = 0xffffffff; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(0, &root_uuid); sdp_set_browse_groups(&record, root); sdp_list_free(root, 0); sdp_uuid16_create(&sp_uuid, SERIAL_PORT_SVCLASS_ID); svclass_id = sdp_list_append(0, &sp_uuid); sdp_set_service_classes(&record, svclass_id); sdp_list_free(svclass_id, 0); sdp_uuid16_create(&profile.uuid, SERIAL_PORT_PROFILE_ID); profile.version = 0x0100; profiles = sdp_list_append(0, &profile); sdp_set_profile_descs(&record, profiles); sdp_list_free(profiles, 0); sdp_uuid16_create(&l2cap, L2CAP_UUID); proto[0] = sdp_list_append(0, &l2cap); apseq = sdp_list_append(0, proto[0]); sdp_uuid16_create(&rfcomm, RFCOMM_UUID); proto[1] = sdp_list_append(0, &rfcomm); channel = sdp_data_alloc(SDP_UINT8, &u8); proto[1] = sdp_list_append(proto[1], channel); apseq = sdp_list_append(apseq, proto[1]); aproto = sdp_list_append(0, apseq); sdp_set_access_protos(&record, aproto); add_lang_attr(&record); sdp_set_info_attr(&record, "Serial Port", 0, "COM Port"); if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); ret = -1; goto end; } printf("Serial Port service registered\n"); end: sdp_data_free(channel); sdp_list_free(proto[0], 0); sdp_list_free(proto[1], 0); sdp_list_free(apseq, 0); sdp_list_free(aproto, 0); return ret; } static int add_dun(sdp_session_t *session, svc_info_t *si) { sdp_list_t *svclass_id, *pfseq, *apseq, *root, *aproto; uuid_t rootu, dun, gn, l2cap, rfcomm; sdp_profile_desc_t profile; sdp_list_t *proto[2]; sdp_record_t record; uint8_t u8 = si->channel? si->channel: 1; sdp_data_t *channel; int ret = 0; memset((void *)&record, 0, sizeof(sdp_record_t)); record.handle = 0xffffffff; sdp_uuid16_create(&rootu, PUBLIC_BROWSE_GROUP); root = sdp_list_append(0, &rootu); sdp_set_browse_groups(&record, root); sdp_uuid16_create(&dun, DIALUP_NET_SVCLASS_ID); svclass_id = sdp_list_append(0, &dun); sdp_uuid16_create(&gn, GENERIC_NETWORKING_SVCLASS_ID); svclass_id = sdp_list_append(svclass_id, &gn); sdp_set_service_classes(&record, svclass_id); sdp_uuid16_create(&profile.uuid, DIALUP_NET_PROFILE_ID); profile.version = 0x0100; pfseq = sdp_list_append(0, &profile); sdp_set_profile_descs(&record, pfseq); sdp_uuid16_create(&l2cap, L2CAP_UUID); proto[0] = sdp_list_append(0, &l2cap); apseq = sdp_list_append(0, proto[0]); sdp_uuid16_create(&rfcomm, RFCOMM_UUID); proto[1] = sdp_list_append(0, &rfcomm); channel = sdp_data_alloc(SDP_UINT8, &u8); proto[1] = sdp_list_append(proto[1], channel); apseq = sdp_list_append(apseq, proto[1]); aproto = sdp_list_append(0, apseq); sdp_set_access_protos(&record, aproto); sdp_set_info_attr(&record, "Dial-Up Networking", 0, 0); if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); ret = -1; goto end; } printf("Dial-Up Networking service registered\n"); end: sdp_data_free(channel); sdp_list_free(proto[0], 0); sdp_list_free(proto[1], 0); sdp_list_free(apseq, 0); sdp_list_free(aproto, 0); return ret; } static int add_lan(sdp_session_t *session, svc_info_t *si) { sdp_list_t *svclass_id, *pfseq, *apseq, *root; uuid_t root_uuid, svclass_uuid, l2cap_uuid, rfcomm_uuid; sdp_profile_desc_t profile; sdp_list_t *aproto, *proto[2]; sdp_record_t record; uint8_t u8 = si->channel? si->channel: 2; sdp_data_t *channel; int ret = 0; memset((void *)&record, 0, sizeof(sdp_record_t)); record.handle = 0xffffffff; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(0, &root_uuid); sdp_set_browse_groups(&record, root); sdp_uuid16_create(&svclass_uuid, LAN_ACCESS_SVCLASS_ID); svclass_id = sdp_list_append(0, &svclass_uuid); sdp_set_service_classes(&record, svclass_id); sdp_uuid16_create(&profile.uuid, LAN_ACCESS_PROFILE_ID); profile.version = 0x0100; pfseq = sdp_list_append(0, &profile); sdp_set_profile_descs(&record, pfseq); sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); proto[0] = sdp_list_append(0, &l2cap_uuid); apseq = sdp_list_append(0, proto[0]); sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); proto[1] = sdp_list_append(0, &rfcomm_uuid); channel = sdp_data_alloc(SDP_UINT8, &u8); proto[1] = sdp_list_append(proto[1], channel); apseq = sdp_list_append(apseq, proto[1]); aproto = sdp_list_append(0, apseq); sdp_set_access_protos(&record, aproto); sdp_set_info_attr(&record, "LAN Access over PPP", 0, 0); if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); ret = -1; goto end; } printf("LAN Access service registered\n"); end: sdp_data_free(channel); sdp_list_free(proto[0], 0); sdp_list_free(proto[1], 0); sdp_list_free(apseq, 0); sdp_list_free(aproto, 0); return ret; } static int add_headset(sdp_session_t *session, svc_info_t *si) { sdp_list_t *svclass_id, *pfseq, *apseq, *root; uuid_t root_uuid, svclass_uuid, ga_svclass_uuid, l2cap_uuid, rfcomm_uuid; sdp_profile_desc_t profile; sdp_list_t *aproto, *proto[2]; sdp_record_t record; uint8_t u8 = si->channel? si->channel: 5; sdp_data_t *channel; int ret = 0; memset((void *)&record, 0, sizeof(sdp_record_t)); record.handle = 0xffffffff; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(0, &root_uuid); sdp_set_browse_groups(&record, root); sdp_uuid16_create(&svclass_uuid, HEADSET_SVCLASS_ID); svclass_id = sdp_list_append(0, &svclass_uuid); sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID); svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid); sdp_set_service_classes(&record, svclass_id); sdp_uuid16_create(&profile.uuid, HEADSET_PROFILE_ID); profile.version = 0x0100; pfseq = sdp_list_append(0, &profile); sdp_set_profile_descs(&record, pfseq); sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); proto[0] = sdp_list_append(0, &l2cap_uuid); apseq = sdp_list_append(0, proto[0]); sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); proto[1] = sdp_list_append(0, &rfcomm_uuid); channel = sdp_data_alloc(SDP_UINT8, &u8); proto[1] = sdp_list_append(proto[1], channel); apseq = sdp_list_append(apseq, proto[1]); aproto = sdp_list_append(0, apseq); sdp_set_access_protos(&record, aproto); sdp_set_info_attr(&record, "Headset", 0, 0); if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); ret = -1; goto end; } printf("Headset service registered\n"); end: sdp_data_free(channel); sdp_list_free(proto[0], 0); sdp_list_free(proto[1], 0); sdp_list_free(apseq, 0); sdp_list_free(aproto, 0); return ret; } static int add_handsfree(sdp_session_t *session, svc_info_t *si) { sdp_list_t *svclass_id, *pfseq, *apseq, *root; uuid_t root_uuid, svclass_uuid, ga_svclass_uuid, l2cap_uuid, rfcomm_uuid; sdp_profile_desc_t profile; sdp_list_t *aproto, *proto[2]; sdp_record_t record; uint8_t u8 = si->channel? si->channel: 3; uint16_t u16 = 0x31; sdp_data_t *channel, *features; int ret = 0; memset((void *)&record, 0, sizeof(sdp_record_t)); record.handle = 0xffffffff; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(0, &root_uuid); sdp_set_browse_groups(&record, root); sdp_uuid16_create(&svclass_uuid, HANDSFREE_SVCLASS_ID); svclass_id = sdp_list_append(0, &svclass_uuid); sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID); svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid); sdp_set_service_classes(&record, svclass_id); sdp_uuid16_create(&profile.uuid, HANDSFREE_PROFILE_ID); profile.version = 0x0101; pfseq = sdp_list_append(0, &profile); sdp_set_profile_descs(&record, pfseq); sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); proto[0] = sdp_list_append(0, &l2cap_uuid); apseq = sdp_list_append(0, proto[0]); sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); proto[1] = sdp_list_append(0, &rfcomm_uuid); channel = sdp_data_alloc(SDP_UINT8, &u8); proto[1] = sdp_list_append(proto[1], channel); apseq = sdp_list_append(apseq, proto[1]); features = sdp_data_alloc(SDP_UINT16, &u16); sdp_attr_add(&record, SDP_ATTR_SUPPORTED_FEATURES, features); aproto = sdp_list_append(0, apseq); sdp_set_access_protos(&record, aproto); sdp_set_info_attr(&record, "Handsfree", 0, 0); if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); ret = -1; goto end; } printf("Handsfree service registered\n"); end: sdp_data_free(channel); sdp_list_free(proto[0], 0); sdp_list_free(proto[1], 0); sdp_list_free(apseq, 0); sdp_list_free(aproto, 0); return ret; } static int add_simaccess(sdp_session_t *session, svc_info_t *si) { sdp_list_t *svclass_id, *pfseq, *apseq, *root; uuid_t root_uuid, svclass_uuid, ga_svclass_uuid, l2cap_uuid, rfcomm_uuid; sdp_profile_desc_t profile; sdp_list_t *aproto, *proto[2]; sdp_record_t record; uint8_t u8 = si->channel? si->channel: 3; uint16_t u16 = 0x31; sdp_data_t *channel, *features; int ret = 0; memset((void *)&record, 0, sizeof(sdp_record_t)); record.handle = 0xffffffff; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(0, &root_uuid); sdp_set_browse_groups(&record, root); sdp_uuid16_create(&svclass_uuid, SAP_SVCLASS_ID); svclass_id = sdp_list_append(0, &svclass_uuid); sdp_uuid16_create(&ga_svclass_uuid, GENERIC_TELEPHONY_SVCLASS_ID); svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid); sdp_set_service_classes(&record, svclass_id); sdp_uuid16_create(&profile.uuid, SAP_PROFILE_ID); profile.version = 0x0101; pfseq = sdp_list_append(0, &profile); sdp_set_profile_descs(&record, pfseq); sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); proto[0] = sdp_list_append(0, &l2cap_uuid); apseq = sdp_list_append(0, proto[0]); sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); proto[1] = sdp_list_append(0, &rfcomm_uuid); channel = sdp_data_alloc(SDP_UINT8, &u8); proto[1] = sdp_list_append(proto[1], channel); apseq = sdp_list_append(apseq, proto[1]); features = sdp_data_alloc(SDP_UINT16, &u16); sdp_attr_add(&record, SDP_ATTR_SUPPORTED_FEATURES, features); aproto = sdp_list_append(0, apseq); sdp_set_access_protos(&record, aproto); sdp_set_info_attr(&record, "SIM Access", 0, 0); if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); ret = -1; goto end; } printf("SIM Access service registered\n"); end: sdp_data_free(channel); sdp_list_free(proto[0], 0); sdp_list_free(proto[1], 0); sdp_list_free(apseq, 0); sdp_list_free(aproto, 0); return ret; } static int add_fax(sdp_session_t *session, svc_info_t *si) { sdp_list_t *svclass_id, *pfseq, *apseq, *root; uuid_t root_uuid, fax_uuid, tel_uuid, l2cap_uuid, rfcomm_uuid; sdp_profile_desc_t profile; sdp_list_t *aproto, *proto[2]; sdp_record_t record; uint8_t u8 = si->channel? si->channel: 3; sdp_data_t *channel; int ret = 0; memset((void *)&record, 0, sizeof(sdp_record_t)); record.handle = 0xffffffff; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(0, &root_uuid); sdp_set_browse_groups(&record, root); sdp_uuid16_create(&fax_uuid, FAX_SVCLASS_ID); svclass_id = sdp_list_append(0, &fax_uuid); sdp_uuid16_create(&tel_uuid, GENERIC_TELEPHONY_SVCLASS_ID); svclass_id = sdp_list_append(svclass_id, &tel_uuid); sdp_set_service_classes(&record, svclass_id); sdp_uuid16_create(&profile.uuid, FAX_PROFILE_ID); profile.version = 0x0100; pfseq = sdp_list_append(0, &profile); sdp_set_profile_descs(&record, pfseq); sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); proto[0] = sdp_list_append(0, &l2cap_uuid); apseq = sdp_list_append(0, proto[0]); sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); proto[1] = sdp_list_append(0, &rfcomm_uuid); channel = sdp_data_alloc(SDP_UINT8, &u8); proto[1] = sdp_list_append(proto[1], channel); apseq = sdp_list_append(apseq, proto[1]); aproto = sdp_list_append(0, apseq); sdp_set_access_protos(&record, aproto); sdp_set_info_attr(&record, "Fax", 0, 0); if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); ret = -1; goto end; } printf("Fax service registered\n"); end: sdp_data_free(channel); sdp_list_free(proto[0], 0); sdp_list_free(proto[1], 0); sdp_list_free(apseq, 0); sdp_list_free(aproto, 0); return ret; } static int add_opush(sdp_session_t *session, svc_info_t *si) { sdp_list_t *svclass_id, *pfseq, *apseq, *root; uuid_t root_uuid, opush_uuid, l2cap_uuid, rfcomm_uuid, obex_uuid; sdp_profile_desc_t profile[1]; sdp_list_t *aproto, *proto[3]; sdp_record_t record; uint8_t chan = si->channel? si->channel: 4; sdp_data_t *channel; uint8_t formats[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; //uint8_t formats[] = { 0xff }; void *dtds[sizeof(formats)], *values[sizeof(formats)]; int i; uint8_t dtd = SDP_UINT8; sdp_data_t *sflist; int ret = 0; memset((void *)&record, 0, sizeof(sdp_record_t)); record.handle = 0xffffffff; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(0, &root_uuid); sdp_set_browse_groups(&record, root); sdp_uuid16_create(&opush_uuid, OBEX_OBJPUSH_SVCLASS_ID); svclass_id = sdp_list_append(0, &opush_uuid); sdp_set_service_classes(&record, svclass_id); sdp_uuid16_create(&profile[0].uuid, OBEX_OBJPUSH_PROFILE_ID); profile[0].version = 0x0100; pfseq = sdp_list_append(0, profile); sdp_set_profile_descs(&record, pfseq); sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); proto[0] = sdp_list_append(0, &l2cap_uuid); apseq = sdp_list_append(0, proto[0]); sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); proto[1] = sdp_list_append(0, &rfcomm_uuid); channel = sdp_data_alloc(SDP_UINT8, &chan); proto[1] = sdp_list_append(proto[1], channel); apseq = sdp_list_append(apseq, proto[1]); sdp_uuid16_create(&obex_uuid, OBEX_UUID); proto[2] = sdp_list_append(0, &obex_uuid); apseq = sdp_list_append(apseq, proto[2]); aproto = sdp_list_append(0, apseq); sdp_set_access_protos(&record, aproto); for (i = 0; i < sizeof(formats); i++) { dtds[i] = &dtd; values[i] = &formats[i]; } sflist = sdp_seq_alloc(dtds, values, sizeof(formats)); sdp_attr_add(&record, SDP_ATTR_SUPPORTED_FORMATS_LIST, sflist); sdp_set_info_attr(&record, "OBEX Object Push", 0, 0); if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); ret = -1; goto end; } printf("OBEX Object Push service registered\n"); end: sdp_data_free(channel); sdp_list_free(proto[0], 0); sdp_list_free(proto[1], 0); sdp_list_free(proto[2], 0); sdp_list_free(apseq, 0); sdp_list_free(aproto, 0); return ret; } static int add_ftp(sdp_session_t *session, svc_info_t *si) { sdp_list_t *svclass_id, *pfseq, *apseq, *root; uuid_t root_uuid, ftrn_uuid, l2cap_uuid, rfcomm_uuid, obex_uuid; sdp_profile_desc_t profile[1]; sdp_list_t *aproto, *proto[3]; sdp_record_t record; uint8_t u8 = si->channel? si->channel: 4; sdp_data_t *channel; int ret = 0; memset((void *)&record, 0, sizeof(sdp_record_t)); record.handle = 0xffffffff; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(0, &root_uuid); sdp_set_browse_groups(&record, root); sdp_uuid16_create(&ftrn_uuid, OBEX_FILETRANS_SVCLASS_ID); svclass_id = sdp_list_append(0, &ftrn_uuid); sdp_set_service_classes(&record, svclass_id); sdp_uuid16_create(&profile[0].uuid, OBEX_FILETRANS_PROFILE_ID); profile[0].version = 0x0100; pfseq = sdp_list_append(0, &profile[0]); sdp_set_profile_descs(&record, pfseq); sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); proto[0] = sdp_list_append(0, &l2cap_uuid); apseq = sdp_list_append(0, proto[0]); sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); proto[1] = sdp_list_append(0, &rfcomm_uuid); channel = sdp_data_alloc(SDP_UINT8, &u8); proto[1] = sdp_list_append(proto[1], channel); apseq = sdp_list_append(apseq, proto[1]); sdp_uuid16_create(&obex_uuid, OBEX_UUID); proto[2] = sdp_list_append(0, &obex_uuid); apseq = sdp_list_append(apseq, proto[2]); aproto = sdp_list_append(0, apseq); sdp_set_access_protos(&record, aproto); sdp_set_info_attr(&record, "OBEX File Transfer", 0, 0); if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); ret = -1; goto end; } printf("OBEX File Transfer service registered\n"); end: sdp_data_free(channel); sdp_list_free(proto[0], 0); sdp_list_free(proto[1], 0); sdp_list_free(proto[2], 0); sdp_list_free(apseq, 0); sdp_list_free(aproto, 0); return ret; } static int add_nap(sdp_session_t *session, svc_info_t *si) { sdp_list_t *svclass_id, *pfseq, *apseq, *root; uuid_t root_uuid, ftrn_uuid, l2cap_uuid, bnep_uuid; sdp_profile_desc_t profile[1]; sdp_list_t *aproto, *proto[2]; sdp_record_t record; uint16_t lp = 0x000f, ver = 0x0100; sdp_data_t *psm, *version; int ret = 0; memset((void *)&record, 0, sizeof(sdp_record_t)); record.handle = 0xffffffff; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(0, &root_uuid); sdp_set_browse_groups(&record, root); sdp_uuid16_create(&ftrn_uuid, NAP_SVCLASS_ID); svclass_id = sdp_list_append(0, &ftrn_uuid); sdp_set_service_classes(&record, svclass_id); sdp_uuid16_create(&profile[0].uuid, NAP_PROFILE_ID); profile[0].version = 0x0100; pfseq = sdp_list_append(0, &profile[0]); sdp_set_profile_descs(&record, pfseq); sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); proto[0] = sdp_list_append(0, &l2cap_uuid); psm = sdp_data_alloc(SDP_UINT16, &lp); proto[0] = sdp_list_append(proto[0], psm); apseq = sdp_list_append(0, proto[0]); sdp_uuid16_create(&bnep_uuid, BNEP_UUID); proto[1] = sdp_list_append(0, &bnep_uuid); version = sdp_data_alloc(SDP_UINT16, &ver); proto[1] = sdp_list_append(proto[1], version); { uint16_t ptype[4] = { 0x0010, 0x0020, 0x0030, 0x0040 }; sdp_data_t *head, *pseq; int p; for (p = 0, head = NULL; p < 4; p++) { sdp_data_t *data = sdp_data_alloc(SDP_UINT16, &ptype[p]); head = sdp_seq_append(head, data); } pseq = sdp_data_alloc(SDP_SEQ16, head); proto[1] = sdp_list_append(proto[1], pseq); } apseq = sdp_list_append(apseq, proto[1]); aproto = sdp_list_append(0, apseq); sdp_set_access_protos(&record, aproto); sdp_set_info_attr(&record, "Network Access Point Service", 0, 0); if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); ret = -1; goto end; } printf("NAP service registered\n"); end: sdp_data_free(version); sdp_data_free(psm); sdp_list_free(proto[0], 0); sdp_list_free(proto[1], 0); sdp_list_free(apseq, 0); sdp_list_free(aproto, 0); return ret; } static int add_gn(sdp_session_t *session, svc_info_t *si) { sdp_list_t *svclass_id, *pfseq, *apseq, *root; uuid_t root_uuid, ftrn_uuid, l2cap_uuid, bnep_uuid; sdp_profile_desc_t profile[1]; sdp_list_t *aproto, *proto[2]; sdp_record_t record; uint16_t lp = 0x000f, ver = 0x0100; sdp_data_t *psm, *version; int ret = 0; memset((void *)&record, 0, sizeof(sdp_record_t)); record.handle = 0xffffffff; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(0, &root_uuid); sdp_set_browse_groups(&record, root); sdp_uuid16_create(&ftrn_uuid, GN_SVCLASS_ID); svclass_id = sdp_list_append(0, &ftrn_uuid); sdp_set_service_classes(&record, svclass_id); sdp_uuid16_create(&profile[0].uuid, GN_PROFILE_ID); profile[0].version = 0x0100; pfseq = sdp_list_append(0, &profile[0]); sdp_set_profile_descs(&record, pfseq); sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); proto[0] = sdp_list_append(0, &l2cap_uuid); psm = sdp_data_alloc(SDP_UINT16, &lp); proto[0] = sdp_list_append(proto[0], psm); apseq = sdp_list_append(0, proto[0]); sdp_uuid16_create(&bnep_uuid, BNEP_UUID); proto[1] = sdp_list_append(0, &bnep_uuid); version = sdp_data_alloc(SDP_UINT16, &ver); proto[1] = sdp_list_append(proto[1], version); apseq = sdp_list_append(apseq, proto[1]); aproto = sdp_list_append(0, apseq); sdp_set_access_protos(&record, aproto); sdp_set_info_attr(&record, "Group Network Service", 0, 0); if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); ret = -1; goto end; } printf("GN service registered\n"); end: sdp_data_free(version); sdp_data_free(psm); sdp_list_free(proto[0], 0); sdp_list_free(proto[1], 0); sdp_list_free(apseq, 0); sdp_list_free(aproto, 0); return ret; } static int add_panu(sdp_session_t *session, svc_info_t *si) { sdp_list_t *svclass_id, *pfseq, *apseq, *root; uuid_t root_uuid, ftrn_uuid, l2cap_uuid, bnep_uuid; sdp_profile_desc_t profile[1]; sdp_list_t *aproto, *proto[2]; sdp_record_t record; uint16_t lp = 0x000f, ver = 0x0100; sdp_data_t *psm, *version; int ret = 0; memset((void *)&record, 0, sizeof(sdp_record_t)); record.handle = 0xffffffff; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(NULL, &root_uuid); sdp_set_browse_groups(&record, root); sdp_list_free(root, NULL); sdp_uuid16_create(&ftrn_uuid, PANU_SVCLASS_ID); svclass_id = sdp_list_append(NULL, &ftrn_uuid); sdp_set_service_classes(&record, svclass_id); sdp_list_free(svclass_id, NULL); sdp_uuid16_create(&profile[0].uuid, PANU_PROFILE_ID); profile[0].version = 0x0100; pfseq = sdp_list_append(NULL, &profile[0]); sdp_set_profile_descs(&record, pfseq); sdp_list_free(pfseq, NULL); sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); proto[0] = sdp_list_append(NULL, &l2cap_uuid); psm = sdp_data_alloc(SDP_UINT16, &lp); proto[0] = sdp_list_append(proto[0], psm); apseq = sdp_list_append(NULL, proto[0]); sdp_uuid16_create(&bnep_uuid, BNEP_UUID); proto[1] = sdp_list_append(NULL, &bnep_uuid); version = sdp_data_alloc(SDP_UINT16, &ver); proto[1] = sdp_list_append(proto[1], version); apseq = sdp_list_append(apseq, proto[1]); aproto = sdp_list_append(NULL, apseq); sdp_set_access_protos(&record, aproto); sdp_set_info_attr(&record, "PAN User", NULL, NULL); if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); ret = -1; goto end; } printf("PANU service registered\n"); end: sdp_data_free(version); sdp_data_free(psm); sdp_list_free(proto[0], 0); sdp_list_free(proto[1], 0); sdp_list_free(apseq, 0); sdp_list_free(aproto, 0); return ret; } static int add_ctp(sdp_session_t *session, svc_info_t *si) { sdp_list_t *svclass_id, *pfseq, *apseq, *root; uuid_t root_uuid, l2cap, tcsbin, ctp; sdp_profile_desc_t profile[1]; sdp_list_t *aproto, *proto[2]; sdp_record_t record; uint8_t netid = 0x02; // 0x01-0x07 cf. p120 profile document sdp_data_t *network = sdp_data_alloc(SDP_UINT8, &netid); int ret = 0; memset((void *)&record, 0, sizeof(sdp_record_t)); record.handle = 0xffffffff; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(0, &root_uuid); sdp_set_browse_groups(&record, root); sdp_uuid16_create(&ctp, CORDLESS_TELEPHONY_SVCLASS_ID); svclass_id = sdp_list_append(0, &ctp); sdp_set_service_classes(&record, svclass_id); sdp_uuid16_create(&profile[0].uuid, CORDLESS_TELEPHONY_PROFILE_ID); profile[0].version = 0x0100; pfseq = sdp_list_append(0, &profile[0]); sdp_set_profile_descs(&record, pfseq); sdp_uuid16_create(&l2cap, L2CAP_UUID); proto[0] = sdp_list_append(0, &l2cap); apseq = sdp_list_append(0, proto[0]); sdp_uuid16_create(&tcsbin, TCS_BIN_UUID); proto[1] = sdp_list_append(0, &tcsbin); apseq = sdp_list_append(apseq, proto[1]); aproto = sdp_list_append(0, apseq); sdp_set_access_protos(&record, aproto); sdp_attr_add(&record, SDP_ATTR_EXTERNAL_NETWORK, network); sdp_set_info_attr(&record, "Cordless Telephony", 0, 0); if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); ret = -1; goto end; } printf("CTP service registered\n"); end: sdp_list_free(proto[0], 0); sdp_list_free(proto[1], 0); sdp_list_free(apseq, 0); sdp_list_free(aproto, 0); sdp_data_free(network); return ret; } static int add_a2source(sdp_session_t *session, svc_info_t *si) { sdp_list_t *svclass_id, *pfseq, *apseq, *root; uuid_t root_uuid, l2cap, avdtp, a2src; sdp_profile_desc_t profile[1]; sdp_list_t *aproto, *proto[2]; sdp_record_t record; sdp_data_t *psm, *version; uint16_t lp = 0x0019, ver = 0x0100; int ret = 0; memset((void *)&record, 0, sizeof(sdp_record_t)); record.handle = 0xffffffff; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(0, &root_uuid); sdp_set_browse_groups(&record, root); sdp_uuid16_create(&a2src, AUDIO_SOURCE_SVCLASS_ID); svclass_id = sdp_list_append(0, &a2src); sdp_set_service_classes(&record, svclass_id); sdp_uuid16_create(&profile[0].uuid, ADVANCED_AUDIO_PROFILE_ID); profile[0].version = 0x0100; pfseq = sdp_list_append(0, &profile[0]); sdp_set_profile_descs(&record, pfseq); sdp_uuid16_create(&l2cap, L2CAP_UUID); proto[0] = sdp_list_append(0, &l2cap); psm = sdp_data_alloc(SDP_UINT16, &lp); proto[0] = sdp_list_append(proto[0], psm); apseq = sdp_list_append(0, proto[0]); sdp_uuid16_create(&avdtp, AVDTP_UUID); proto[1] = sdp_list_append(0, &avdtp); version = sdp_data_alloc(SDP_UINT16, &ver); proto[1] = sdp_list_append(proto[1], version); apseq = sdp_list_append(apseq, proto[1]); aproto = sdp_list_append(0, apseq); sdp_set_access_protos(&record, aproto); sdp_set_info_attr(&record, "Audio Source", 0, 0); if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); ret = -1; goto done; } printf("Audio source service registered\n"); done: sdp_list_free(proto[0], 0); sdp_list_free(proto[1], 0); sdp_list_free(apseq, 0); sdp_list_free(aproto, 0); return ret; } static int add_a2sink(sdp_session_t *session, svc_info_t *si) { sdp_list_t *svclass_id, *pfseq, *apseq, *root; uuid_t root_uuid, l2cap, avdtp, a2snk; sdp_profile_desc_t profile[1]; sdp_list_t *aproto, *proto[2]; sdp_record_t record; sdp_data_t *psm, *version; uint16_t lp = 0x0019, ver = 0x0100; int ret = 0; memset((void *)&record, 0, sizeof(sdp_record_t)); record.handle = 0xffffffff; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(0, &root_uuid); sdp_set_browse_groups(&record, root); sdp_uuid16_create(&a2snk, AUDIO_SINK_SVCLASS_ID); svclass_id = sdp_list_append(0, &a2snk); sdp_set_service_classes(&record, svclass_id); sdp_uuid16_create(&profile[0].uuid, ADVANCED_AUDIO_PROFILE_ID); profile[0].version = 0x0100; pfseq = sdp_list_append(0, &profile[0]); sdp_set_profile_descs(&record, pfseq); sdp_uuid16_create(&l2cap, L2CAP_UUID); proto[0] = sdp_list_append(0, &l2cap); psm = sdp_data_alloc(SDP_UINT16, &lp); proto[0] = sdp_list_append(proto[0], psm); apseq = sdp_list_append(0, proto[0]); sdp_uuid16_create(&avdtp, AVDTP_UUID); proto[1] = sdp_list_append(0, &avdtp); version = sdp_data_alloc(SDP_UINT16, &ver); proto[1] = sdp_list_append(proto[1], version); apseq = sdp_list_append(apseq, proto[1]); aproto = sdp_list_append(0, apseq); sdp_set_access_protos(&record, aproto); sdp_set_info_attr(&record, "Audio Sink", 0, 0); if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); ret = -1; goto done; } printf("Audio sink service registered\n"); done: sdp_list_free(proto[0], 0); sdp_list_free(proto[1], 0); sdp_list_free(apseq, 0); sdp_list_free(aproto, 0); return ret; } static unsigned char syncml_uuid[] = { 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x02, 0xEE, 0x00, 0x00, 0x02 }; static int add_syncml(sdp_session_t *session, svc_info_t *si) { sdp_record_t record; sdp_list_t *root, *svclass, *proto; uuid_t root_uuid, svclass_uuid, l2cap_uuid, rfcomm_uuid, obex_uuid; uint8_t channel = si->channel? si->channel: 15; memset(&record, 0, sizeof(record)); record.handle = 0xffffffff; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(NULL, &root_uuid); sdp_set_browse_groups(&record, root); sdp_uuid128_create(&svclass_uuid, (void *) syncml_uuid); svclass = sdp_list_append(NULL, &svclass_uuid); sdp_set_service_classes(&record, svclass); sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); proto = sdp_list_append(NULL, sdp_list_append(NULL, &l2cap_uuid)); sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); proto = sdp_list_append(proto, sdp_list_append( sdp_list_append(NULL, &rfcomm_uuid), sdp_data_alloc(SDP_UINT8, &channel))); sdp_uuid16_create(&obex_uuid, OBEX_UUID); proto = sdp_list_append(proto, sdp_list_append(NULL, &obex_uuid)); sdp_set_access_protos(&record, sdp_list_append(NULL, proto)); sdp_set_info_attr(&record, "SyncML Client", NULL, NULL); if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); return -1; } printf("SyncML Client service record registered\n"); return 0; } static unsigned char nokid_uuid[] = { 0x00, 0x00, 0x55, 0x55, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x02, 0xEE, 0x00, 0x00, 0x01 }; static int add_nokiaid(sdp_session_t *session, svc_info_t *si) { sdp_record_t record; sdp_list_t *root, *svclass; uuid_t root_uuid, svclass_uuid; uint16_t verid = 0x005f; sdp_data_t *version = sdp_data_alloc(SDP_UINT16, &verid); memset(&record, 0, sizeof(record)); record.handle = 0xffffffff; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(NULL, &root_uuid); sdp_set_browse_groups(&record, root); sdp_uuid128_create(&svclass_uuid, (void *) nokid_uuid); svclass = sdp_list_append(NULL, &svclass_uuid); sdp_set_service_classes(&record, svclass); sdp_attr_add(&record, SDP_ATTR_SERVICE_VERSION, version); if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); sdp_data_free(version); return -1; } printf("Nokia ID service record registered\n"); return 0; } static unsigned char pcsuite_uuid[] = { 0x00, 0x00, 0x50, 0x02, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x02, 0xEE, 0x00, 0x00, 0x01 }; static int add_pcsuite(sdp_session_t *session, svc_info_t *si) { sdp_record_t record; sdp_list_t *root, *svclass, *proto; uuid_t root_uuid, svclass_uuid, l2cap_uuid, rfcomm_uuid; uint8_t channel = si->channel? si->channel: 14; memset(&record, 0, sizeof(record)); record.handle = 0xffffffff; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(NULL, &root_uuid); sdp_set_browse_groups(&record, root); sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); proto = sdp_list_append(NULL, sdp_list_append(NULL, &l2cap_uuid)); sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); proto = sdp_list_append(proto, sdp_list_append( sdp_list_append(NULL, &rfcomm_uuid), sdp_data_alloc(SDP_UINT8, &channel))); sdp_set_access_protos(&record, sdp_list_append(NULL, proto)); sdp_uuid128_create(&svclass_uuid, (void *) pcsuite_uuid); svclass = sdp_list_append(NULL, &svclass_uuid); sdp_set_service_classes(&record, svclass); sdp_set_info_attr(&record, "Nokia PC Suite", NULL, NULL); if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); return -1; } printf("Nokia PC Suite service registered\n"); return 0; } static unsigned char sr1_uuid[] = { 0xbc, 0x19, 0x9c, 0x24, 0x95, 0x8b, 0x4c, 0xc0, 0xa2, 0xcb, 0xfd, 0x8a, 0x30, 0xbf, 0x32, 0x06 }; static int add_sr1(sdp_session_t *session, svc_info_t *si) { sdp_record_t record; sdp_list_t *root, *svclass; uuid_t root_uuid, svclass_uuid; memset(&record, 0, sizeof(record)); record.handle = 0xffffffff; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(NULL, &root_uuid); sdp_set_browse_groups(&record, root); sdp_uuid128_create(&svclass_uuid, (void *) sr1_uuid); svclass = sdp_list_append(NULL, &svclass_uuid); sdp_set_service_classes(&record, svclass); sdp_set_info_attr(&record, "TOSHIBA SR-1", NULL, NULL); if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); return -1; } printf("Toshiba Speech Recognition SR-1 service record registered\n"); return 0; } struct { char *name; uint16_t class; int (*add)(sdp_session_t *sess, svc_info_t *si); unsigned char *uuid; } service[] = { { "DID", PNP_INFO_SVCLASS_ID, NULL, }, { "SP", SERIAL_PORT_SVCLASS_ID, add_sp }, { "DUN", DIALUP_NET_SVCLASS_ID, add_dun }, { "LAN", LAN_ACCESS_SVCLASS_ID, add_lan }, { "FAX", FAX_SVCLASS_ID, add_fax }, { "OPUSH", OBEX_OBJPUSH_SVCLASS_ID, add_opush }, { "FTP", OBEX_FILETRANS_SVCLASS_ID, add_ftp }, { "HS", HEADSET_SVCLASS_ID, add_headset }, { "HF", HANDSFREE_SVCLASS_ID, add_handsfree }, { "SAP", SAP_SVCLASS_ID, add_simaccess }, { "NAP", NAP_SVCLASS_ID, add_nap }, { "GN", GN_SVCLASS_ID, add_gn }, { "PANU", PANU_SVCLASS_ID, add_panu }, { "HID", HID_SVCLASS_ID, NULL }, { "CIP", CIP_SVCLASS_ID, NULL }, { "CTP", CORDLESS_TELEPHONY_SVCLASS_ID, add_ctp }, { "A2SRC", AUDIO_SOURCE_SVCLASS_ID, add_a2source }, { "A2SNK", AUDIO_SINK_SVCLASS_ID, add_a2sink }, { "SYNCML", 0, add_syncml, syncml_uuid }, { "NOKID", 0, add_nokiaid, nokid_uuid }, { "PCSUITE", 0, add_pcsuite, pcsuite_uuid }, { "SR1", 0, add_sr1, sr1_uuid }, { 0 } }; /* Add local service */ static int add_service(bdaddr_t *bdaddr, svc_info_t *si) { int i; sdp_session_t *sess = sdp_connect(&interface, BDADDR_LOCAL, SDP_RETRY_IF_BUSY); if (!sess) return -1; if (si->name) for (i = 0; service[i].name; i++) if (!strcasecmp(service[i].name, si->name)) { int ret = -1; if (service[i].add) ret = service[i].add(sess, si); free(si->name); sdp_close(sess); return ret; } printf("Unknown service name: %s\n", si->name); free(si->name); sdp_close(sess); return -1; } static struct option add_options[] = { { "help", 0,0, 'h' }, { "channel", 1,0, 'c' }, { 0, 0, 0, 0} }; static char *add_help = "Usage:\n" "\tadd [--channel=CHAN] service\n"; static int cmd_add(int argc, char **argv) { svc_info_t si; int opt; memset(&si, 0, sizeof(si)); for_each_opt(opt, add_options, 0) { switch (opt) { case 'c': si.channel = atoi(optarg); break; default: printf(add_help); return -1; } } argc -= optind; argv += optind; if (argc < 1) { printf(add_help); return -1; } si.name = strdup(argv[0]); return add_service(0, &si); } /* Delete local service */ static int del_service(bdaddr_t *bdaddr, void *arg) { uint32_t handle, range = 0x0000ffff; sdp_list_t *attr; sdp_session_t *sess; sdp_record_t *rec; if (!arg) { printf("Record handle was not specified.\n"); return -1; } sess = sdp_connect(&interface, BDADDR_LOCAL, SDP_RETRY_IF_BUSY); if (!sess) { printf("No local SDP server!\n"); return -1; } handle = strtoul((char *)arg, 0, 16); attr = sdp_list_append(0, &range); rec = sdp_service_attr_req(sess, handle, SDP_ATTR_REQ_RANGE, attr); sdp_list_free(attr, 0); if (!rec) { printf("Service Record not found.\n"); sdp_close(sess); return -1; } if (sdp_record_unregister(sess, rec)) { printf("Failed to unregister service record: %s\n", strerror(errno)); sdp_close(sess); return -1; } printf("Service Record deleted.\n"); sdp_close(sess); return 0; } static struct option del_options[] = { { "help", 0,0, 'h' }, { 0, 0, 0, 0 } }; static char *del_help = "Usage:\n" "\tdel record_handle\n"; static int cmd_del(int argc, char **argv) { int opt; for_each_opt(opt, del_options, 0) { switch (opt) { default: printf(del_help); return -1; } } argc -= optind; argv += optind; if (argc < 1) { printf(del_help); return -1; } return del_service(NULL, argv[0]); } /* * Perform an inquiry and search/browse all peer found. */ static void inquiry(handler_t handler, void *arg) { inquiry_info ii[20]; uint8_t count = 0; int i; printf("Inquiring ...\n"); if (sdp_general_inquiry(ii, 20, 8, &count) < 0) { printf("Inquiry failed\n"); return; } for (i = 0; i < count; i++) handler(&ii[i].bdaddr, arg); } /* * Search for a specific SDP service */ static int do_search(bdaddr_t *bdaddr, struct search_context *context) { sdp_list_t *attrid, *search, *seq, *next; uint32_t range = 0x0000ffff; char str[20]; sdp_session_t *sess; if (!bdaddr) { inquiry(do_search, context); return 0; } sess = sdp_connect(&interface, bdaddr, SDP_RETRY_IF_BUSY); ba2str(bdaddr, str); if (!sess) { printf("Failed to connect to SDP server on %s: %s\n", str, strerror(errno)); return -1; } if (context->view != RAW_VIEW) { if (context->svc) printf("Searching for %s on %s ...\n", context->svc, str); else printf("Browsing %s ...\n", str); } attrid = sdp_list_append(0, &range); search = sdp_list_append(0, &context->group); if (sdp_service_search_attr_req(sess, search, SDP_ATTR_REQ_RANGE, attrid, &seq)) { printf("Service Search failed: %s\n", strerror(errno)); sdp_close(sess); return -1; } sdp_list_free(attrid, 0); sdp_list_free(search, 0); for (; seq; seq = next) { sdp_record_t *rec = (sdp_record_t *) seq->data; struct search_context sub_context; switch (context->view) { case DEFAULT_VIEW: /* Display user friendly form */ print_service_attr(rec); printf("\n"); break; case TREE_VIEW: /* Display full tree */ print_tree_attr(rec); printf("\n"); break; default: /* Display raw tree */ print_raw_attr(rec); break; } if (sdp_get_group_id(rec, &sub_context.group) != -1) { /* Set the subcontext for browsing the sub tree */ memcpy(&sub_context, context, sizeof(struct search_context)); /* Browse the next level down if not done */ if (sub_context.group.value.uuid16 != context->group.value.uuid16) do_search(bdaddr, &sub_context); } next = seq->next; free(seq); sdp_record_free(rec); } sdp_close(sess); return 0; } static struct option browse_options[] = { { "help", 0, 0, 'h' }, { "tree", 0, 0, 't' }, { "raw", 0, 0, 'r' }, { "uuid", 1, 0, 'u' }, { "l2cap", 0, 0, 'l' }, { 0, 0, 0, 0 } }; static char *browse_help = "Usage:\n" "\tbrowse [--tree] [--raw] [--uuid uuid] [--l2cap] [bdaddr]\n"; /* * Browse the full SDP database (i.e. list all services starting from the * root/top-level). */ static int cmd_browse(int argc, char **argv) { struct search_context context; int opt, num; /* Initialise context */ memset(&context, '\0', sizeof(struct search_context)); /* We want to browse the top-level/root */ sdp_uuid16_create(&context.group, PUBLIC_BROWSE_GROUP); for_each_opt(opt, browse_options, 0) { switch (opt) { case 't': context.view = TREE_VIEW; break; case 'r': context.view = RAW_VIEW; break; case 'u': if (sscanf(optarg, "%i", &num) != 1 || num < 0 || num > 0xffff) { printf("Invalid uuid %s\n", optarg); return -1; } sdp_uuid16_create(&context.group, num); break; case 'l': sdp_uuid16_create(&context.group, L2CAP_UUID); break; default: printf(browse_help); return -1; } } argc -= optind; argv += optind; if (argc >= 1) { bdaddr_t bdaddr; estr2ba(argv[0], &bdaddr); return do_search(&bdaddr, &context); } return do_search(NULL, &context); } static struct option search_options[] = { { "help", 0,0, 'h' }, { "bdaddr", 1,0, 'b' }, { "tree", 0,0, 't' }, { "raw", 0, 0, 'r' }, { 0, 0, 0, 0} }; static char *search_help = "Usage:\n" "\tsearch [--bdaddr bdaddr] [--tree] [--raw] SERVICE\n" "SERVICE is a name (string) or UUID (0x1002)\n"; /* * Search for a specific SDP service * * Note : we should support multiple services on the command line : * sdptool search 0x0100 0x000f 0x1002 * (this would search a service supporting both L2CAP and BNEP directly in * the top level browse group) */ static int cmd_search(int argc, char **argv) { struct search_context context; unsigned char *uuid = NULL; uint16_t class = 0; bdaddr_t bdaddr; int has_addr = 0; int i; int opt; /* Initialise context */ memset(&context, '\0', sizeof(struct search_context)); for_each_opt(opt, search_options, 0) { switch (opt) { case 'b': estr2ba(optarg, &bdaddr); has_addr = 1; break; case 't': context.view = TREE_VIEW; break; case 'r': context.view = RAW_VIEW; break; default: printf(search_help); return -1; } } argc -= optind; argv += optind; if (argc < 1) { printf(search_help); return -1; } /* Note : we need to find a way to support search combining * multiple services - Jean II */ context.svc = strdup(argv[0]); if (!strncasecmp(context.svc, "0x", 2)) { int num; /* This is a UUID16, just convert to int */ sscanf(context.svc + 2, "%X", &num); class = num; printf("Class 0x%X\n", class); } else { /* Convert class name to an UUID */ for (i = 0; service[i].name; i++) if (strcasecmp(context.svc, service[i].name) == 0) { class = service[i].class; uuid = service[i].uuid; break; } if (!class && !uuid) { printf("Unknown service %s\n", context.svc); return -1; } } if (class) sdp_uuid16_create(&context.group, class); else sdp_uuid128_create(&context.group, uuid); if (has_addr) return do_search(&bdaddr, &context); return do_search(NULL, &context); } /* * Show how to get a specific SDP record by its handle. * Not really useful to the user, just show how it can be done... * Jean II */ static int get_service(bdaddr_t *bdaddr, struct search_context *context, int quite) { sdp_list_t *attrid; uint32_t range = 0x0000ffff; sdp_record_t *rec; sdp_session_t *session = sdp_connect(&interface, bdaddr, SDP_RETRY_IF_BUSY); if (!session) { char str[20]; ba2str(bdaddr, str); printf("Failed to connect to SDP server on %s: %s\n", str, strerror(errno)); return -1; } attrid = sdp_list_append(0, &range); rec = sdp_service_attr_req(session, context->handle, SDP_ATTR_REQ_RANGE, attrid); sdp_list_free(attrid, 0); sdp_close(session); if (!rec) { if (!quite) { printf("Service get request failed.\n"); return -1; } else return 0; } switch (context->view) { case DEFAULT_VIEW: /* Display user friendly form */ print_service_attr(rec); printf("\n"); break; case TREE_VIEW: /* Display full tree */ print_tree_attr(rec); printf("\n"); break; default: /* Display raw tree */ print_raw_attr(rec); break; } sdp_record_free(rec); return 0; } static struct option records_options[] = { { "help", 0, 0, 'h' }, { "tree", 0, 0, 't' }, { "raw", 0, 0, 'r' }, { 0, 0, 0, 0 } }; static char *records_help = "Usage:\n" "\trecords [--tree] [--raw] bdaddr\n"; /* * Request possible SDP service records */ static int cmd_records(int argc, char **argv) { struct search_context context; uint32_t base[] = { 0x10000, 0x1002e, 0x110b }; bdaddr_t bdaddr; int i, n, opt, err = 0, num = 32; /* Initialise context */ memset(&context, '\0', sizeof(struct search_context)); for_each_opt(opt, records_options, 0) { switch (opt) { case 't': context.view = TREE_VIEW; break; case 'r': context.view = RAW_VIEW; break; default: printf(records_help); return -1; } } argc -= optind; argv += optind; if (argc < 1) { printf(records_help); return -1; } /* Convert command line parameters */ estr2ba(argv[0], &bdaddr); for (i = 0; i < sizeof(base) / sizeof(uint32_t); i++) for (n = 0; n < num; n++) { context.handle = base[i] + n; err = get_service(&bdaddr, &context, 1); if (err < 0) goto done; } done: return 0; } static struct option get_options[] = { { "help", 0, 0, 'h' }, { "bdaddr", 1, 0, 'b' }, { "tree", 0, 0, 't' }, { "raw", 0, 0, 'r' }, { 0, 0, 0, 0 } }; static char *get_help = "Usage:\n" "\tget [--tree] [--raw] [--bdaddr bdaddr] record_handle\n"; /* * Get a specific SDP record on the local SDP server */ static int cmd_get(int argc, char **argv) { struct search_context context; bdaddr_t bdaddr; int has_addr = 0; int opt; /* Initialise context */ memset(&context, '\0', sizeof(struct search_context)); for_each_opt(opt, get_options, 0) { switch (opt) { case 'b': estr2ba(optarg, &bdaddr); has_addr = 1; break; case 't': context.view = TREE_VIEW; break; case 'r': context.view = RAW_VIEW; break; default: printf(get_help); return -1; } } argc -= optind; argv += optind; if (argc < 1) { printf(get_help); return -1; } /* Convert command line parameters */ context.handle = strtoul(argv[0], 0, 16); return get_service(has_addr ? &bdaddr : BDADDR_LOCAL, &context, 0); } static struct { char *cmd; int (*func)(int argc, char **argv); char *doc; } command[] = { { "search", cmd_search, "Search for a service" }, { "browse", cmd_browse, "Browse all available services" }, { "records", cmd_records, "Request all records" }, { "add", cmd_add, "Add local service" }, { "del", cmd_del, "Delete local service" }, { "get", cmd_get, "Get local service" }, { "setattr", cmd_setattr, "Set/Add attribute to a SDP record" }, { "setseq", cmd_setseq, "Set/Add attribute sequence to a SDP record" }, { 0, 0, 0 } }; static void usage(void) { int i, pos = 0; printf("sdptool - SDP tool v%s\n", VERSION); printf("Usage:\n" "\tsdptool [options] [command parameters]\n"); printf("Options:\n" "\t-h\t\tDisplay help\n" "\t-i\t\tSpecify source interface\n"); printf("Commands:\n"); for (i = 0; command[i].cmd; i++) printf("\t%-4s\t\t%s\n", command[i].cmd, command[i].doc); printf("\nServices:\n\t"); for (i = 0; service[i].name; i++) { printf("%s ", service[i].name); pos += strlen(service[i].name) + 1; if (pos > 60) { printf("\n\t"); pos = 0; } } printf("\n"); } static struct option main_options[] = { { "help", 0, 0, 'h' }, { "device", 1, 0, 'i' }, { 0, 0, 0, 0 } }; int main(int argc, char **argv) { int i, opt; bacpy(&interface, BDADDR_ANY); while ((opt=getopt_long(argc, argv, "+i:h", main_options, NULL)) != -1) { switch(opt) { case 'i': if (!strncmp(optarg, "hci", 3)) hci_devba(atoi(optarg + 3), &interface); else str2ba(optarg, &interface); break; case 'h': usage(); exit(0); default: exit(1); } } argc -= optind; argv += optind; optind = 0; if (argc < 1) { usage(); exit(1); } for (i = 0; command[i].cmd; i++) if (strncmp(command[i].cmd, argv[0], 4) == 0) return command[i].func(argc, argv); return -1; }