/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2001-2002 Nokia Corporation * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> * Copyright (C) 2002-2010 Marcel Holtmann <marcel@holtmann.org> * Copyright (C) 2002-2003 Stephen Crane <steve.crane@rococosoft.com> * Copyright (C) 2002-2003 Jean Tourrilhes <jt@hpl.hp.com> * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #include <stdio.h> #include <errno.h> #include <ctype.h> #include <stdlib.h> #include <string.h> #include <getopt.h> #include <sys/socket.h> #include <bluetooth/bluetooth.h> #include <bluetooth/hci.h> #include <bluetooth/hci_lib.h> #include <bluetooth/sdp.h> #include <bluetooth/sdp_lib.h> #include <netinet/in.h> #include "sdp-xml.h" #ifndef APPLE_AGENT_SVCLASS_ID #define APPLE_AGENT_SVCLASS_ID 0x2112 #endif #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 */ #define XML_VIEW 3 /* Display xml 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 }, { 0x112a, "UDI-MT", NULL, 0 }, { 0x112b, "UDI-TA", NULL, 0 }, { 0x112c, "Audio/Video", NULL, 0 }, { 0x112d, "SIM Access (SAP)", NULL, 0 }, { 0x112e, "Phonebook Access (PBAP) - PCE", NULL, 0 }, { 0x112f, "Phonebook Access (PBAP) - PSE", NULL, 0 }, { 0x1130, "Phonebook Access (PBAP)", 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 }, /* ... */ { 0x1303, "VideoSource", NULL, 0 }, { 0x1304, "VideoSink", NULL, 0 }, { 0x1305, "VideoDistribution", NULL, 0 }, { 0x1400, "HDP", NULL, 0 }, { 0x1401, "HDPSource", NULL, 0 }, { 0x1402, "HDPSink", NULL, 0 }, { 0x2112, "AppleAgent", 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 > (int) 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 = value; uint16_t attrId; struct service_context *service = (struct service_context *) userData; struct attrib_context context; struct attrib_def *attrDef = NULL; int i; if (!sdpdata) return; 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 */ sdp_data_printf(sdpdata, &context, 2); /* 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; 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%016jx\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 %jd\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: hex = 0; for (i = 0; i < data->unitSize; i++) { if (i == (data->unitSize - 1) && data->val.str[i] == '\0') break; if (!isprint(data->val.str[i])) { hex = 1; break; } } if (hex) { printf("Data"); for (i = 0; i < data->unitSize; i++) printf(" %02x", (unsigned char) data->val.str[i]); } else { printf("String "); for (i = 0; i < data->unitSize; i++) printf("%c", data->val.str[i]); } printf("\n"); 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; if (!data) return; /* 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); print_raw_data(data, 2); } 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; int ret; /* 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); sdp_list_free(attrid_list, NULL); 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 */ ret = sdp_device_record_update(sess, &interface, rec); if (ret < 0) printf("Service Record update failed (%d).\n", errno); sdp_record_free(rec); return ret; } static struct option set_options[] = { { "help", 0, 0, 'h' }, { 0, 0, 0, 0 } }; static const 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("%s", set_help); return -1; } } argc -= optind; argv += optind; if (argc < 3) { printf("%s", 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, ret = 0; /* 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); sdp_list_free(attrid_list, NULL); 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 = (uint32_t *) 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 */ ret = sdp_device_record_update(session, &interface, rec); if (ret < 0) printf("Service Record update failed (%d).\n", errno); } else printf("Failed to create pSequenceHolder\n"); /* Cleanup */ for (i = 0; i < argc; i++) free(allocArray[i]); free(dtdArray); free(valueArray); free(allocArray); sdp_record_free(rec); return ret; } static struct option seq_options[] = { { "help", 0, 0, 'h' }, { 0, 0, 0, 0 } }; static const 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("%s", seq_help); return -1; } } argc -= optind; argv += optind; if (argc < 3) { printf("%s", 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); if (uuid->type != SDP_UUID128) printf(" \"%s\" (0x%s)\n", ServiceClassUUID_str, UUID_str); else printf(" UUID 128: %s\n", 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 { uint32_t handle; char *name; char *provider; char *desc; unsigned int class; unsigned int profile; uint16_t psm; uint8_t channel; uint8_t network; } 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(&record, 0, sizeof(sdp_record_t)); record.handle = si->handle; 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", "BlueZ", "COM Port"); sdp_set_url_attr(&record, "http://www.bluez.org/", "http://www.bluez.org/", "http://www.bluez.org/"); sdp_set_service_id(&record, sp_uuid); sdp_set_service_ttl(&record, 0xffff); sdp_set_service_avail(&record, 0xff); sdp_set_record_state(&record, 0x00001234); if (sdp_device_record_register(session, &interface, &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 : 2; sdp_data_t *channel; int ret = 0; memset(&record, 0, sizeof(sdp_record_t)); record.handle = si->handle; 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_device_record_register(session, &interface, &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_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(&record, 0, sizeof(sdp_record_t)); record.handle = si->handle; 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_device_record_register(session, &interface, &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_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 : 4; sdp_data_t *channel; int ret = 0; memset(&record, 0, sizeof(sdp_record_t)); record.handle = si->handle; 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_device_record_register(session, &interface, &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(&record, 0, sizeof(sdp_record_t)); record.handle = si->handle; 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_device_record_register(session, &interface, &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_headset_ag(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 : 7; sdp_data_t *channel; uint8_t netid = si->network ? si->network : 0x01; // ???? profile document sdp_data_t *network = sdp_data_alloc(SDP_UINT8, &netid); int ret = 0; memset(&record, 0, sizeof(sdp_record_t)); record.handle = si->handle; 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_AGW_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, "Voice Gateway", 0, 0); sdp_attr_add(&record, SDP_ATTR_EXTERNAL_NETWORK, network); if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); ret = -1; goto end; } printf("Headset AG 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 : 6; uint16_t u16 = 0x31; sdp_data_t *channel, *features; int ret = 0; memset(&record, 0, sizeof(sdp_record_t)); record.handle = si->handle; 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_device_record_register(session, &interface, &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_handsfree_ag(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 : 7; uint16_t u16 = 0x17; #ifdef ANDROID u16 = 0x07; #endif sdp_data_t *channel, *features; uint8_t netid = si->network ? si->network : 0x01; // ???? profile document sdp_data_t *network = sdp_data_alloc(SDP_UINT8, &netid); int ret = 0; memset(&record, 0, sizeof(sdp_record_t)); record.handle = si->handle; 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_AGW_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 = 0x0105; 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, "Voice Gateway", 0, 0); sdp_attr_add(&record, SDP_ATTR_EXTERNAL_NETWORK, network); if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); ret = -1; goto end; } printf("Handsfree AG 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 : 8; uint16_t u16 = 0x31; sdp_data_t *channel, *features; int ret = 0; memset((void *)&record, 0, sizeof(sdp_record_t)); record.handle = si->handle; 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_device_record_register(session, &interface, &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_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 : 9; sdp_data_t *channel; #ifdef ANDROID uint8_t formats[] = { 0x01, 0x02, 0xff }; #else uint8_t formats[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0xff }; #endif void *dtds[sizeof(formats)], *values[sizeof(formats)]; unsigned int i; uint8_t dtd = SDP_UINT8; sdp_data_t *sflist; int ret = 0; memset(&record, 0, sizeof(sdp_record_t)); record.handle = si->handle; 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_device_record_register(session, &interface, &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_pbap(sdp_session_t *session, svc_info_t *si) { sdp_list_t *svclass_id, *pfseq, *apseq, *root; uuid_t root_uuid, pbap_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 : 19; sdp_data_t *channel; uint8_t formats[] = {0x01}; uint8_t dtd = SDP_UINT8; sdp_data_t *sflist; int ret = 0; memset(&record, 0, sizeof(sdp_record_t)); record.handle = si->handle; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(0, &root_uuid); sdp_set_browse_groups(&record, root); sdp_uuid16_create(&pbap_uuid, PBAP_PSE_SVCLASS_ID); svclass_id = sdp_list_append(0, &pbap_uuid); sdp_set_service_classes(&record, svclass_id); sdp_uuid16_create(&profile[0].uuid, PBAP_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); sflist = sdp_data_alloc(dtd,formats); sdp_attr_add(&record, SDP_ATTR_SUPPORTED_REPOSITORIES, sflist); sdp_set_info_attr(&record, "OBEX Phonebook Access Server", 0, 0); if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); ret = -1; goto end; } printf("PBAP 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: 10; sdp_data_t *channel; int ret = 0; memset(&record, 0, sizeof(sdp_record_t)); record.handle = si->handle; 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_device_record_register(session, &interface, &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_directprint(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 : 12; sdp_data_t *channel; int ret = 0; memset(&record, 0, sizeof(sdp_record_t)); record.handle = si->handle; 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, DIRECT_PRINTING_SVCLASS_ID); svclass_id = sdp_list_append(0, &opush_uuid); sdp_set_service_classes(&record, svclass_id); sdp_uuid16_create(&profile[0].uuid, BASIC_PRINTING_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); sdp_set_info_attr(&record, "Direct Printing", 0, 0); if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); ret = -1; goto end; } printf("Direct Printing 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(&record, 0, sizeof(sdp_record_t)); record.handle = si->handle; 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_device_record_register(session, &interface, &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(&record, 0, sizeof(sdp_record_t)); record.handle = si->handle; 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_device_record_register(session, &interface, &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(&record, 0, sizeof(sdp_record_t)); record.handle = si->handle; 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_device_record_register(session, &interface, &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_hid_keyb(sdp_session_t *session, svc_info_t *si) { sdp_record_t record; sdp_list_t *svclass_id, *pfseq, *apseq, *root; uuid_t root_uuid, hidkb_uuid, l2cap_uuid, hidp_uuid; sdp_profile_desc_t profile[1]; sdp_list_t *aproto, *proto[3]; sdp_data_t *psm, *lang_lst, *lang_lst2, *hid_spec_lst, *hid_spec_lst2; unsigned int i; uint8_t dtd = SDP_UINT16; uint8_t dtd2 = SDP_UINT8; uint8_t dtd_data = SDP_TEXT_STR8; void *dtds[2]; void *values[2]; void *dtds2[2]; void *values2[2]; int leng[2]; uint8_t hid_spec_type = 0x22; uint16_t hid_attr_lang[] = { 0x409, 0x100 }; static const uint16_t ctrl = 0x11; static const uint16_t intr = 0x13; static const uint16_t hid_attr[] = { 0x100, 0x111, 0x40, 0x0d, 0x01, 0x01 }; static const uint16_t hid_attr2[] = { 0x0, 0x01, 0x100, 0x1f40, 0x01, 0x01 }; const uint8_t hid_spec[] = { 0x05, 0x01, // usage page 0x09, 0x06, // keyboard 0xa1, 0x01, // key codes 0x85, 0x01, // minimum 0x05, 0x07, // max 0x19, 0xe0, // logical min 0x29, 0xe7, // logical max 0x15, 0x00, // report size 0x25, 0x01, // report count 0x75, 0x01, // input data variable absolute 0x95, 0x08, // report count 0x81, 0x02, // report size 0x75, 0x08, 0x95, 0x01, 0x81, 0x01, 0x75, 0x01, 0x95, 0x05, 0x05, 0x08, 0x19, 0x01, 0x29, 0x05, 0x91, 0x02, 0x75, 0x03, 0x95, 0x01, 0x91, 0x01, 0x75, 0x08, 0x95, 0x06, 0x15, 0x00, 0x26, 0xff, 0x00, 0x05, 0x07, 0x19, 0x00, 0x2a, 0xff, 0x00, 0x81, 0x00, 0x75, 0x01, 0x95, 0x01, 0x15, 0x00, 0x25, 0x01, 0x05, 0x0c, 0x09, 0xb8, 0x81, 0x06, 0x09, 0xe2, 0x81, 0x06, 0x09, 0xe9, 0x81, 0x02, 0x09, 0xea, 0x81, 0x02, 0x75, 0x01, 0x95, 0x04, 0x81, 0x01, 0xc0 // end tag }; memset(&record, 0, sizeof(sdp_record_t)); record.handle = si->handle; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(0, &root_uuid); sdp_set_browse_groups(&record, root); add_lang_attr(&record); sdp_uuid16_create(&hidkb_uuid, HID_SVCLASS_ID); svclass_id = sdp_list_append(0, &hidkb_uuid); sdp_set_service_classes(&record, svclass_id); sdp_uuid16_create(&profile[0].uuid, HID_PROFILE_ID); profile[0].version = 0x0100; pfseq = sdp_list_append(0, profile); sdp_set_profile_descs(&record, pfseq); /* protocols */ sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); proto[1] = sdp_list_append(0, &l2cap_uuid); psm = sdp_data_alloc(SDP_UINT16, &ctrl); proto[1] = sdp_list_append(proto[1], psm); apseq = sdp_list_append(0, proto[1]); sdp_uuid16_create(&hidp_uuid, HIDP_UUID); proto[2] = sdp_list_append(0, &hidp_uuid); apseq = sdp_list_append(apseq, proto[2]); aproto = sdp_list_append(0, apseq); sdp_set_access_protos(&record, aproto); /* additional protocols */ proto[1] = sdp_list_append(0, &l2cap_uuid); psm = sdp_data_alloc(SDP_UINT16, &intr); proto[1] = sdp_list_append(proto[1], psm); apseq = sdp_list_append(0, proto[1]); sdp_uuid16_create(&hidp_uuid, HIDP_UUID); proto[2] = sdp_list_append(0, &hidp_uuid); apseq = sdp_list_append(apseq, proto[2]); aproto = sdp_list_append(0, apseq); sdp_set_add_access_protos(&record, aproto); sdp_set_info_attr(&record, "HID Keyboard", NULL, NULL); for (i = 0; i < sizeof(hid_attr) / 2; i++) sdp_attr_add_new(&record, SDP_ATTR_HID_DEVICE_RELEASE_NUMBER + i, SDP_UINT16, &hid_attr[i]); dtds[0] = &dtd2; values[0] = &hid_spec_type; dtds[1] = &dtd_data; values[1] = (uint8_t *) hid_spec; leng[0] = 0; leng[1] = sizeof(hid_spec); hid_spec_lst = sdp_seq_alloc_with_length(dtds, values, leng, 2); hid_spec_lst2 = sdp_data_alloc(SDP_SEQ8, hid_spec_lst); sdp_attr_add(&record, SDP_ATTR_HID_DESCRIPTOR_LIST, hid_spec_lst2); for (i = 0; i < sizeof(hid_attr_lang) / 2; i++) { dtds2[i] = &dtd; values2[i] = &hid_attr_lang[i]; } lang_lst = sdp_seq_alloc(dtds2, values2, sizeof(hid_attr_lang) / 2); lang_lst2 = sdp_data_alloc(SDP_SEQ8, lang_lst); sdp_attr_add(&record, SDP_ATTR_HID_LANG_ID_BASE_LIST, lang_lst2); sdp_attr_add_new(&record, SDP_ATTR_HID_SDP_DISABLE, SDP_UINT16, &hid_attr2[0]); for (i = 0; i < sizeof(hid_attr2) / 2 - 1; i++) sdp_attr_add_new(&record, SDP_ATTR_HID_REMOTE_WAKEUP + i, SDP_UINT16, &hid_attr2[i + 1]); if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); return -1; } printf("HID keyboard service registered\n"); return 0; } static int add_hid_wiimote(sdp_session_t *session, svc_info_t *si) { sdp_record_t record; sdp_list_t *svclass_id, *pfseq, *apseq, *root; uuid_t root_uuid, hid_uuid, l2cap_uuid, hidp_uuid; sdp_profile_desc_t profile[1]; sdp_list_t *aproto, *proto[3]; sdp_data_t *psm, *lang_lst, *lang_lst2, *hid_spec_lst, *hid_spec_lst2; unsigned int i; uint8_t dtd = SDP_UINT16; uint8_t dtd2 = SDP_UINT8; uint8_t dtd_data = SDP_TEXT_STR8; void *dtds[2]; void *values[2]; void *dtds2[2]; void *values2[2]; int leng[2]; uint8_t hid_spec_type = 0x22; uint16_t hid_attr_lang[] = { 0x409, 0x100 }; uint16_t ctrl = 0x11, intr = 0x13; uint16_t hid_release = 0x0100, parser_version = 0x0111; uint8_t subclass = 0x04, country = 0x33; uint8_t virtual_cable = 0, reconnect = 1, sdp_disable = 0; uint8_t battery = 1, remote_wakeup = 1; uint16_t profile_version = 0x0100, superv_timeout = 0x0c80; uint8_t norm_connect = 0, boot_device = 0; const uint8_t hid_spec[] = { 0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x10, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x01, 0x06, 0x00, 0xff, 0x09, 0x01, 0x91, 0x00, 0x85, 0x11, 0x95, 0x01, 0x09, 0x01, 0x91, 0x00, 0x85, 0x12, 0x95, 0x02, 0x09, 0x01, 0x91, 0x00, 0x85, 0x13, 0x95, 0x01, 0x09, 0x01, 0x91, 0x00, 0x85, 0x14, 0x95, 0x01, 0x09, 0x01, 0x91, 0x00, 0x85, 0x15, 0x95, 0x01, 0x09, 0x01, 0x91, 0x00, 0x85, 0x16, 0x95, 0x15, 0x09, 0x01, 0x91, 0x00, 0x85, 0x17, 0x95, 0x06, 0x09, 0x01, 0x91, 0x00, 0x85, 0x18, 0x95, 0x15, 0x09, 0x01, 0x91, 0x00, 0x85, 0x19, 0x95, 0x01, 0x09, 0x01, 0x91, 0x00, 0x85, 0x1a, 0x95, 0x01, 0x09, 0x01, 0x91, 0x00, 0x85, 0x20, 0x95, 0x06, 0x09, 0x01, 0x81, 0x00, 0x85, 0x21, 0x95, 0x15, 0x09, 0x01, 0x81, 0x00, 0x85, 0x22, 0x95, 0x04, 0x09, 0x01, 0x81, 0x00, 0x85, 0x30, 0x95, 0x02, 0x09, 0x01, 0x81, 0x00, 0x85, 0x31, 0x95, 0x05, 0x09, 0x01, 0x81, 0x00, 0x85, 0x32, 0x95, 0x0a, 0x09, 0x01, 0x81, 0x00, 0x85, 0x33, 0x95, 0x11, 0x09, 0x01, 0x81, 0x00, 0x85, 0x34, 0x95, 0x15, 0x09, 0x01, 0x81, 0x00, 0x85, 0x35, 0x95, 0x15, 0x09, 0x01, 0x81, 0x00, 0x85, 0x36, 0x95, 0x15, 0x09, 0x01, 0x81, 0x00, 0x85, 0x37, 0x95, 0x15, 0x09, 0x01, 0x81, 0x00, 0x85, 0x3d, 0x95, 0x15, 0x09, 0x01, 0x81, 0x00, 0x85, 0x3e, 0x95, 0x15, 0x09, 0x01, 0x81, 0x00, 0x85, 0x3f, 0x95, 0x15, 0x09, 0x01, 0x81, 0x00, 0xc0, 0x00 }; memset(&record, 0, sizeof(sdp_record_t)); record.handle = si->handle; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(NULL, &root_uuid); sdp_set_browse_groups(&record, root); sdp_uuid16_create(&hid_uuid, HID_SVCLASS_ID); svclass_id = sdp_list_append(NULL, &hid_uuid); sdp_set_service_classes(&record, svclass_id); sdp_uuid16_create(&profile[0].uuid, HID_PROFILE_ID); profile[0].version = 0x0100; pfseq = sdp_list_append(NULL, profile); sdp_set_profile_descs(&record, pfseq); sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); proto[1] = sdp_list_append(0, &l2cap_uuid); psm = sdp_data_alloc(SDP_UINT16, &ctrl); proto[1] = sdp_list_append(proto[1], psm); apseq = sdp_list_append(0, proto[1]); sdp_uuid16_create(&hidp_uuid, HIDP_UUID); proto[2] = sdp_list_append(0, &hidp_uuid); apseq = sdp_list_append(apseq, proto[2]); aproto = sdp_list_append(0, apseq); sdp_set_access_protos(&record, aproto); proto[1] = sdp_list_append(0, &l2cap_uuid); psm = sdp_data_alloc(SDP_UINT16, &intr); proto[1] = sdp_list_append(proto[1], psm); apseq = sdp_list_append(0, proto[1]); sdp_uuid16_create(&hidp_uuid, HIDP_UUID); proto[2] = sdp_list_append(0, &hidp_uuid); apseq = sdp_list_append(apseq, proto[2]); aproto = sdp_list_append(0, apseq); sdp_set_add_access_protos(&record, aproto); add_lang_attr(&record); sdp_set_info_attr(&record, "Nintendo RVL-CNT-01", "Nintendo", "Nintendo RVL-CNT-01"); sdp_attr_add_new(&record, SDP_ATTR_HID_DEVICE_RELEASE_NUMBER, SDP_UINT16, &hid_release); sdp_attr_add_new(&record, SDP_ATTR_HID_PARSER_VERSION, SDP_UINT16, &parser_version); sdp_attr_add_new(&record, SDP_ATTR_HID_DEVICE_SUBCLASS, SDP_UINT8, &subclass); sdp_attr_add_new(&record, SDP_ATTR_HID_COUNTRY_CODE, SDP_UINT8, &country); sdp_attr_add_new(&record, SDP_ATTR_HID_VIRTUAL_CABLE, SDP_BOOL, &virtual_cable); sdp_attr_add_new(&record, SDP_ATTR_HID_RECONNECT_INITIATE, SDP_BOOL, &reconnect); dtds[0] = &dtd2; values[0] = &hid_spec_type; dtds[1] = &dtd_data; values[1] = (uint8_t *) hid_spec; leng[0] = 0; leng[1] = sizeof(hid_spec); hid_spec_lst = sdp_seq_alloc_with_length(dtds, values, leng, 2); hid_spec_lst2 = sdp_data_alloc(SDP_SEQ8, hid_spec_lst); sdp_attr_add(&record, SDP_ATTR_HID_DESCRIPTOR_LIST, hid_spec_lst2); for (i = 0; i < sizeof(hid_attr_lang) / 2; i++) { dtds2[i] = &dtd; values2[i] = &hid_attr_lang[i]; } lang_lst = sdp_seq_alloc(dtds2, values2, sizeof(hid_attr_lang) / 2); lang_lst2 = sdp_data_alloc(SDP_SEQ8, lang_lst); sdp_attr_add(&record, SDP_ATTR_HID_LANG_ID_BASE_LIST, lang_lst2); sdp_attr_add_new(&record, SDP_ATTR_HID_SDP_DISABLE, SDP_BOOL, &sdp_disable); sdp_attr_add_new(&record, SDP_ATTR_HID_BATTERY_POWER, SDP_BOOL, &battery); sdp_attr_add_new(&record, SDP_ATTR_HID_REMOTE_WAKEUP, SDP_BOOL, &remote_wakeup); sdp_attr_add_new(&record, SDP_ATTR_HID_PROFILE_VERSION, SDP_UINT16, &profile_version); sdp_attr_add_new(&record, SDP_ATTR_HID_SUPERVISION_TIMEOUT, SDP_UINT16, &superv_timeout); sdp_attr_add_new(&record, SDP_ATTR_HID_NORMALLY_CONNECTABLE, SDP_BOOL, &norm_connect); sdp_attr_add_new(&record, SDP_ATTR_HID_BOOT_DEVICE, SDP_BOOL, &boot_device); if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); return -1; } printf("Wii-Mote service registered\n"); return 0; } static int add_cip(sdp_session_t *session, svc_info_t *si) { sdp_list_t *svclass_id, *pfseq, *apseq, *root; uuid_t root_uuid, l2cap, cmtp, cip; sdp_profile_desc_t profile[1]; sdp_list_t *aproto, *proto[2]; sdp_record_t record; uint16_t psm = si->psm ? si->psm : 0x1001; uint8_t netid = si->network ? si->network : 0x02; // 0x02 = ISDN, 0x03 = GSM sdp_data_t *network = sdp_data_alloc(SDP_UINT8, &netid); int ret = 0; memset(&record, 0, sizeof(sdp_record_t)); record.handle = si->handle; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(0, &root_uuid); sdp_set_browse_groups(&record, root); sdp_uuid16_create(&cip, CIP_SVCLASS_ID); svclass_id = sdp_list_append(0, &cip); sdp_set_service_classes(&record, svclass_id); sdp_uuid16_create(&profile[0].uuid, CIP_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]); proto[0] = sdp_list_append(proto[0], sdp_data_alloc(SDP_UINT16, &psm)); apseq = sdp_list_append(0, proto[0]); sdp_uuid16_create(&cmtp, CMTP_UUID); proto[1] = sdp_list_append(0, &cmtp); 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, "Common ISDN Access", 0, 0); if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); ret = -1; goto end; } printf("CIP 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_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 = si->network ? si->network : 0x02; // 0x01-0x07 cf. p120 profile document sdp_data_t *network = sdp_data_alloc(SDP_UINT8, &netid); int ret = 0; memset(&record, 0, sizeof(sdp_record_t)); record.handle = si->handle; 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_device_record_register(session, &interface, &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(&record, 0, sizeof(sdp_record_t)); record.handle = si->handle; 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_device_record_register(session, &interface, &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(&record, 0, sizeof(sdp_record_t)); record.handle = si->handle; 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_device_record_register(session, &interface, &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 int add_avrct(sdp_session_t *session, svc_info_t *si) { sdp_list_t *svclass_id, *pfseq, *apseq, *root; uuid_t root_uuid, l2cap, avctp, avrct; sdp_profile_desc_t profile[1]; sdp_list_t *aproto, *proto[2]; sdp_record_t record; sdp_data_t *psm, *version, *features; uint16_t lp = 0x0017, ver = 0x0100, feat = 0x000f; int ret = 0; memset(&record, 0, sizeof(sdp_record_t)); record.handle = si->handle; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(0, &root_uuid); sdp_set_browse_groups(&record, root); sdp_uuid16_create(&avrct, AV_REMOTE_SVCLASS_ID); svclass_id = sdp_list_append(0, &avrct); sdp_set_service_classes(&record, svclass_id); sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_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(&avctp, AVCTP_UUID); proto[1] = sdp_list_append(0, &avctp); 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); features = sdp_data_alloc(SDP_UINT16, &feat); sdp_attr_add(&record, SDP_ATTR_SUPPORTED_FEATURES, features); sdp_set_info_attr(&record, "AVRCP CT", 0, 0); if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); ret = -1; goto done; } printf("Remote control 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_avrtg(sdp_session_t *session, svc_info_t *si) { sdp_list_t *svclass_id, *pfseq, *apseq, *root; uuid_t root_uuid, l2cap, avctp, avrtg; sdp_profile_desc_t profile[1]; sdp_list_t *aproto, *proto[2]; sdp_record_t record; sdp_data_t *psm, *version, *features; uint16_t lp = 0x0017, ver = 0x0100, feat = 0x000f; int ret = 0; memset(&record, 0, sizeof(sdp_record_t)); record.handle = si->handle; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(0, &root_uuid); sdp_set_browse_groups(&record, root); sdp_uuid16_create(&avrtg, AV_REMOTE_TARGET_SVCLASS_ID); svclass_id = sdp_list_append(0, &avrtg); sdp_set_service_classes(&record, svclass_id); sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_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(&avctp, AVCTP_UUID); proto[1] = sdp_list_append(0, &avctp); 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); features = sdp_data_alloc(SDP_UINT16, &feat); sdp_attr_add(&record, SDP_ATTR_SUPPORTED_FEATURES, features); sdp_set_info_attr(&record, "AVRCP TG", 0, 0); if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); ret = -1; goto done; } printf("Remote target 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_udi_ue(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: 18; memset(&record, 0, sizeof(record)); record.handle = si->handle; 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(&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_uuid16_create(&svclass_uuid, UDI_MT_SVCLASS_ID); svclass = sdp_list_append(NULL, &svclass_uuid); sdp_set_service_classes(&record, svclass); sdp_list_free(svclass, NULL); sdp_set_info_attr(&record, "UDI UE", NULL, NULL); if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); return -1; } printf("UDI UE service registered\n"); return 0; } static int add_udi_te(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: 19; memset(&record, 0, sizeof(record)); record.handle = si->handle; 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(&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_uuid16_create(&svclass_uuid, UDI_TA_SVCLASS_ID); svclass = sdp_list_append(NULL, &svclass_uuid); sdp_set_service_classes(&record, svclass); sdp_list_free(svclass, NULL); sdp_set_info_attr(&record, "UDI TE", NULL, NULL); if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); return -1; } printf("UDI TE 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 = si->handle; 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_device_record_register(session, &interface, &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; } static unsigned char syncmls_uuid[] = { 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x02, 0xEE, 0x00, 0x00, 0x02 }; static unsigned char syncmlc_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 = si->handle; 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 *) syncmlc_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_device_record_register(session, &interface, &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 async_uuid[] = { 0x03, 0x50, 0x27, 0x8F, 0x3D, 0xCA, 0x4E, 0x62, 0x83, 0x1D, 0xA4, 0x11, 0x65, 0xFF, 0x90, 0x6C }; static int add_activesync(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: 21; memset(&record, 0, sizeof(record)); record.handle = si->handle; 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 *) async_uuid); svclass = sdp_list_append(NULL, &svclass_uuid); sdp_set_service_classes(&record, svclass); sdp_set_info_attr(&record, "Microsoft ActiveSync", NULL, NULL); if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); return -1; } printf("ActiveSync service record registered\n"); return 0; } static unsigned char hotsync_uuid[] = { 0xD8, 0x0C, 0xF9, 0xEA, 0x13, 0x4C, 0x11, 0xD5, 0x83, 0xCE, 0x00, 0x30, 0x65, 0x7C, 0x54, 0x3C }; static int add_hotsync(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: 22; memset(&record, 0, sizeof(record)); record.handle = si->handle; 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 *) hotsync_uuid); svclass = sdp_list_append(NULL, &svclass_uuid); sdp_set_service_classes(&record, svclass); sdp_set_info_attr(&record, "PalmOS HotSync", NULL, NULL); if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); return -1; } printf("HotSync service record registered\n"); return 0; } static unsigned char palmos_uuid[] = { 0xF5, 0xBE, 0xB6, 0x51, 0x41, 0x71, 0x40, 0x51, 0xAC, 0xF5, 0x6C, 0xA7, 0x20, 0x22, 0x42, 0xF0 }; static int add_palmos(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 = si->handle; 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 *) palmos_uuid); svclass = sdp_list_append(NULL, &svclass_uuid); sdp_set_service_classes(&record, svclass); if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); return -1; } printf("PalmOS 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 = si->handle; 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_device_record_register(session, &interface, &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 = si->handle; 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_device_record_register(session, &interface, &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 nftp_uuid[] = { 0x00, 0x00, 0x50, 0x05, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x02, 0xEE, 0x00, 0x00, 0x01 }; static unsigned char nsyncml_uuid[] = { 0x00, 0x00, 0x56, 0x01, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x02, 0xEE, 0x00, 0x00, 0x01 }; static unsigned char ngage_uuid[] = { 0x00, 0x00, 0x13, 0x01, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x02, 0xEE, 0x00, 0x00, 0x01 }; static unsigned char apple_uuid[] = { 0xf0, 0x72, 0x2e, 0x20, 0x0f, 0x8b, 0x4e, 0x90, 0x8c, 0xc2, 0x1b, 0x46, 0xf5, 0xf2, 0xef, 0xe2 }; static int add_apple(sdp_session_t *session, svc_info_t *si) { sdp_record_t record; sdp_list_t *root; uuid_t root_uuid; uint32_t attr783 = 0x00000000; uint32_t attr785 = 0x00000002; uint16_t attr786 = 0x1234; memset(&record, 0, sizeof(record)); record.handle = si->handle; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(NULL, &root_uuid); sdp_set_browse_groups(&record, root); sdp_attr_add_new(&record, 0x0780, SDP_UUID128, (void *) apple_uuid); sdp_attr_add_new(&record, 0x0781, SDP_TEXT_STR8, (void *) "Macmini"); sdp_attr_add_new(&record, 0x0782, SDP_TEXT_STR8, (void *) "PowerMac10,1"); sdp_attr_add_new(&record, 0x0783, SDP_UINT32, (void *) &attr783); sdp_attr_add_new(&record, 0x0784, SDP_TEXT_STR8, (void *) "1.6.6f22"); sdp_attr_add_new(&record, 0x0785, SDP_UINT32, (void *) &attr785); sdp_attr_add_new(&record, 0x0786, SDP_UUID16, (void *) &attr786); sdp_set_info_attr(&record, "Apple Macintosh Attributes", NULL, NULL); if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); return -1; } printf("Apple attribute service registered\n"); return 0; } static int add_isync(sdp_session_t *session, svc_info_t *si) { sdp_record_t record; sdp_list_t *root, *svclass, *proto; uuid_t root_uuid, svclass_uuid, serial_uuid, l2cap_uuid, rfcomm_uuid; uint8_t channel = si->channel ? si->channel : 16; memset(&record, 0, sizeof(record)); record.handle = si->handle; 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_uuid16_create(&serial_uuid, SERIAL_PORT_SVCLASS_ID); svclass = sdp_list_append(NULL, &serial_uuid); sdp_uuid16_create(&svclass_uuid, APPLE_AGENT_SVCLASS_ID); svclass = sdp_list_append(svclass, &svclass_uuid); sdp_set_service_classes(&record, svclass); sdp_set_info_attr(&record, "AppleAgent", "Bluetooth acceptor", "Apple Computer Ltd."); if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); return -1; } printf("Apple iSync service registered\n"); return 0; } static int add_semchla(sdp_session_t *session, svc_info_t *si) { sdp_record_t record; sdp_profile_desc_t profile; sdp_list_t *root, *svclass, *proto, *profiles; uuid_t root_uuid, service_uuid, l2cap_uuid, semchla_uuid; uint16_t psm = 0xf0f9; memset(&record, 0, sizeof(record)); record.handle = si->handle; 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( sdp_list_append(NULL, &l2cap_uuid), sdp_data_alloc(SDP_UINT16, &psm))); sdp_uuid32_create(&semchla_uuid, 0x8e770300); proto = sdp_list_append(proto, sdp_list_append(NULL, &semchla_uuid)); sdp_set_access_protos(&record, sdp_list_append(NULL, proto)); sdp_uuid32_create(&service_uuid, 0x8e771301); svclass = sdp_list_append(NULL, &service_uuid); sdp_set_service_classes(&record, svclass); sdp_uuid32_create(&profile.uuid, 0x8e771302); // Headset //sdp_uuid32_create(&profile.uuid, 0x8e771303); // Phone profile.version = 0x0100; profiles = sdp_list_append(NULL, &profile); sdp_set_profile_descs(&record, profiles); sdp_set_info_attr(&record, "SEMC HLA", NULL, NULL); if (sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST) < 0) { printf("Service Record registration failed\n"); return -1; } /* SEMC High Level Authentication */ printf("SEMC HLA service registered\n"); return 0; } static int add_gatt(sdp_session_t *session, svc_info_t *si) { sdp_list_t *svclass_id, *apseq, *proto[2], *profiles, *root, *aproto; uuid_t root_uuid, proto_uuid, gatt_uuid, l2cap; sdp_profile_desc_t profile; sdp_record_t record; sdp_data_t *psm, *sh, *eh; uint16_t att_psm = 27, start = 0x0001, end = 0x000f; int ret; memset(&record, 0, sizeof(sdp_record_t)); record.handle = si->handle; 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(&gatt_uuid, GENERIC_ATTRIB_SVCLASS_ID); svclass_id = sdp_list_append(NULL, &gatt_uuid); sdp_set_service_classes(&record, svclass_id); sdp_list_free(svclass_id, NULL); sdp_uuid16_create(&profile.uuid, GENERIC_ATTRIB_PROFILE_ID); profile.version = 0x0100; profiles = sdp_list_append(NULL, &profile); sdp_set_profile_descs(&record, profiles); sdp_list_free(profiles, NULL); sdp_uuid16_create(&l2cap, L2CAP_UUID); proto[0] = sdp_list_append(NULL, &l2cap); psm = sdp_data_alloc(SDP_UINT16, &att_psm); proto[0] = sdp_list_append(proto[0], psm); apseq = sdp_list_append(NULL, proto[0]); sdp_uuid16_create(&proto_uuid, ATT_UUID); proto[1] = sdp_list_append(NULL, &proto_uuid); sh = sdp_data_alloc(SDP_UINT16, &start); proto[1] = sdp_list_append(proto[1], sh); eh = sdp_data_alloc(SDP_UINT16, &end); proto[1] = sdp_list_append(proto[1], eh); apseq = sdp_list_append(apseq, proto[1]); aproto = sdp_list_append(NULL, apseq); sdp_set_access_protos(&record, aproto); sdp_set_info_attr(&record, "Generic Attribute Profile", "BlueZ", NULL); sdp_set_url_attr(&record, "http://www.bluez.org/", "http://www.bluez.org/", "http://www.bluez.org/"); sdp_set_service_id(&record, gatt_uuid); ret = sdp_device_record_register(session, &interface, &record, SDP_RECORD_PERSIST); if (ret < 0) printf("Service Record registration failed\n"); else printf("Generic Attribute Profile Service registered\n"); sdp_data_free(psm); sdp_data_free(sh); sdp_data_free(eh); sdp_list_free(proto[0], NULL); sdp_list_free(proto[1], NULL); sdp_list_free(apseq, NULL); sdp_list_free(aproto, NULL); return ret; } struct { char *name; uint32_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 }, { "PRINT", DIRECT_PRINTING_SVCLASS_ID, add_directprint }, { "HS", HEADSET_SVCLASS_ID, add_headset }, { "HSAG", HEADSET_AGW_SVCLASS_ID, add_headset_ag }, { "HF", HANDSFREE_SVCLASS_ID, add_handsfree }, { "HFAG", HANDSFREE_AGW_SVCLASS_ID, add_handsfree_ag}, { "SAP", SAP_SVCLASS_ID, add_simaccess }, { "PBAP", PBAP_SVCLASS_ID, add_pbap, }, { "NAP", NAP_SVCLASS_ID, add_nap }, { "GN", GN_SVCLASS_ID, add_gn }, { "PANU", PANU_SVCLASS_ID, add_panu }, { "HCRP", HCR_SVCLASS_ID, NULL }, { "HID", HID_SVCLASS_ID, NULL }, { "KEYB", HID_SVCLASS_ID, add_hid_keyb }, { "WIIMOTE", HID_SVCLASS_ID, add_hid_wiimote }, { "CIP", CIP_SVCLASS_ID, add_cip }, { "CTP", CORDLESS_TELEPHONY_SVCLASS_ID, add_ctp }, { "A2SRC", AUDIO_SOURCE_SVCLASS_ID, add_a2source }, { "A2SNK", AUDIO_SINK_SVCLASS_ID, add_a2sink }, { "AVRCT", AV_REMOTE_SVCLASS_ID, add_avrct }, { "AVRTG", AV_REMOTE_TARGET_SVCLASS_ID, add_avrtg }, { "UDIUE", UDI_MT_SVCLASS_ID, add_udi_ue }, { "UDITE", UDI_TA_SVCLASS_ID, add_udi_te }, { "SEMCHLA", 0x8e771301, add_semchla }, { "SR1", 0, add_sr1, sr1_uuid }, { "SYNCML", 0, add_syncml, syncmlc_uuid }, { "SYNCMLSERV", 0, NULL, syncmls_uuid }, { "ACTIVESYNC", 0, add_activesync, async_uuid }, { "HOTSYNC", 0, add_hotsync, hotsync_uuid }, { "PALMOS", 0, add_palmos, palmos_uuid }, { "NOKID", 0, add_nokiaid, nokid_uuid }, { "PCSUITE", 0, add_pcsuite, pcsuite_uuid }, { "NFTP", 0, NULL, nftp_uuid }, { "NSYNCML", 0, NULL, nsyncml_uuid }, { "NGAGE", 0, NULL, ngage_uuid }, { "APPLE", 0, add_apple, apple_uuid }, { "ISYNC", APPLE_AGENT_SVCLASS_ID, add_isync, }, { "GATT", GENERIC_ATTRIB_SVCLASS_ID, add_gatt, }, { 0 } }; /* Add local service */ static int add_service(bdaddr_t *bdaddr, svc_info_t *si) { sdp_session_t *sess; int i, ret = -1; if (!si->name) return -1; sess = sdp_connect(&interface, BDADDR_LOCAL, SDP_RETRY_IF_BUSY); if (!sess) return -1; for (i = 0; service[i].name; i++) if (!strcasecmp(service[i].name, si->name)) { if (service[i].add) ret = service[i].add(sess, si); goto done; } printf("Unknown service name: %s\n", si->name); done: free(si->name); sdp_close(sess); return ret; } static struct option add_options[] = { { "help", 0, 0, 'h' }, { "handle", 1, 0, 'r' }, { "psm", 1, 0, 'p' }, { "channel", 1, 0, 'c' }, { "network", 1, 0, 'n' }, { 0, 0, 0, 0 } }; static const char *add_help = "Usage:\n" "\tadd [--handle=RECORD_HANDLE --channel=CHANNEL] service\n"; static int cmd_add(int argc, char **argv) { svc_info_t si; int opt; memset(&si, 0, sizeof(si)); si.handle = 0xffffffff; for_each_opt(opt, add_options, 0) { switch (opt) { case 'r': if (strncasecmp(optarg, "0x", 2)) si.handle = atoi(optarg); else si.handle = strtol(optarg + 2, NULL, 16); break; case 'p': if (strncasecmp(optarg, "0x", 2)) si.psm = atoi(optarg); else si.psm = strtol(optarg + 2, NULL, 16); break; case 'c': if (strncasecmp(optarg, "0x", 2)) si.channel = atoi(optarg); else si.channel = strtol(optarg + 2, NULL, 16); break; case 'n': if (strncasecmp(optarg, "0x", 2)) si.network = atoi(optarg); else si.network = strtol(optarg + 2, NULL, 16); break; default: printf("%s", add_help); return -1; } } argc -= optind; argv += optind; if (argc < 1) { printf("%s", 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_device_record_unregister(sess, &interface, 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 const 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("%s", del_help); return -1; } } argc -= optind; argv += optind; if (argc < 1) { printf("%s", 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); } static void doprintf(void *data, const char *str) { printf("%s", str); } /* * 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; case XML_VIEW: /* Display raw XML tree */ convert_sdp_record_to_xml(rec, 0, doprintf); 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' }, { "xml", 0, 0, 'x' }, { "uuid", 1, 0, 'u' }, { "l2cap", 0, 0, 'l' }, { 0, 0, 0, 0 } }; static const char *browse_help = "Usage:\n" "\tbrowse [--tree] [--raw] [--xml] [--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 'x': context.view = XML_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("%s", 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' }, { "xml", 0, 0, 'x' }, { 0, 0, 0, 0} }; static const char *search_help = "Usage:\n" "\tsearch [--bdaddr bdaddr] [--tree] [--raw] [--xml] 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; uint32_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; case 'x': context.view = XML_VIEW; break; default: printf("%s", search_help); return -1; } } argc -= optind; argv += optind; if (argc < 1) { printf("%s", search_help); return -1; } /* Note : we need to find a way to support search combining * multiple services */ 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) { if (class & 0xffff0000) sdp_uuid32_create(&context.group, class); else { uint16_t class16 = class & 0xffff; sdp_uuid16_create(&context.group, class16); } } 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... */ 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; case XML_VIEW: /* Display raw XML tree */ convert_sdp_record_to_xml(rec, 0, doprintf); 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' }, { "xml", 0, 0, 'x' }, { 0, 0, 0, 0 } }; static const char *records_help = "Usage:\n" "\trecords [--tree] [--raw] [--xml] bdaddr\n"; /* * Request possible SDP service records */ static int cmd_records(int argc, char **argv) { struct search_context context; uint32_t base[] = { 0x10000, 0x10300, 0x10500, 0x1002e, 0x110b, 0x90000, 0x2008000, 0x4000000, 0x100000, 0x1000000, 0x4f491100, 0x4f491200 }; bdaddr_t bdaddr; unsigned int i, n, num = 32; int opt, err = 0; /* 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; case 'x': context.view = XML_VIEW; break; default: printf("%s", records_help); return -1; } } argc -= optind; argv += optind; if (argc < 1) { printf("%s", 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' }, { "xml", 0, 0, 'x' }, { 0, 0, 0, 0 } }; static const char *get_help = "Usage:\n" "\tget [--tree] [--raw] [--xml] [--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; case 'x': context.view = XML_VIEW; break; default: printf("%s", get_help); return -1; } } argc -= optind; argv += optind; if (argc < 1) { printf("%s", 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> [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; }