diff options
| author | Marcel Holtmann <marcel@holtmann.org> | 2004-03-31 20:14:52 +0000 | 
|---|---|---|
| committer | Marcel Holtmann <marcel@holtmann.org> | 2004-03-31 20:14:52 +0000 | 
| commit | 280cca9f112cbc2f86a9b7653a2ad003769e4fcb (patch) | |
| tree | 30ecb081c685a2a6156f61a21a97d8f4f19b473a | |
| parent | e474c968aa61a2cfdddb31372f27c420f0b03e08 (diff) | |
Add sdptool utility
| -rw-r--r-- | tools/Makefile.am | 7 | ||||
| -rw-r--r-- | tools/sdptool.1 | 116 | ||||
| -rw-r--r-- | tools/sdptool.c | 2072 | 
3 files changed, 2192 insertions, 3 deletions
| diff --git a/tools/Makefile.am b/tools/Makefile.am index 180c0cc1..4ddeb5c9 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -5,12 +5,13 @@  mandir = $(prefix)/usr/share/man  sbin_PROGRAMS = hciattach hciconfig -bin_PROGRAMS  = hcitool l2ping + +bin_PROGRAMS = hcitool l2ping sdptool  hciconfig_SOURCES = hciconfig.c csr.h csr.c -man_MANS      = hciattach.8 hciconfig.8 hcitool.1 l2ping.1 +man_MANS = hciattach.8 hciconfig.8 hcitool.1 l2ping.1 sdptool.1  noinst_PROGRAMS	= ppporc -EXTRA_DIST    = $(man_MANS) +EXTRA_DIST = $(man_MANS) diff --git a/tools/sdptool.1 b/tools/sdptool.1 new file mode 100644 index 00000000..8f938f92 --- /dev/null +++ b/tools/sdptool.1 @@ -0,0 +1,116 @@ +.\" $Header$ +.\" +.\"	transcript compatibility for postscript use. +.\" +.\"	synopsis:  .P! <file.ps> +.\" +.de P! +.fl +\!!1 setgray +.fl +\\&.\" +.fl +\!!0 setgray +.fl			\" force out current output buffer +\!!save /psv exch def currentpoint translate 0 0 moveto +\!!/showpage{}def +.fl			\" prolog +.sy sed -e 's/^/!/' \\$1\" bring in postscript file +\!!psv restore +. +.de pF +.ie     \\*(f1 .ds f1 \\n(.f +.el .ie \\*(f2 .ds f2 \\n(.f +.el .ie \\*(f3 .ds f3 \\n(.f +.el .ie \\*(f4 .ds f4 \\n(.f +.el .tm ? font overflow +.ft \\$1 +.. +.de fP +.ie     !\\*(f4 \{\ +.	ft \\*(f4 +.	ds f4\" +'	br \} +.el .ie !\\*(f3 \{\ +.	ft \\*(f3 +.	ds f3\" +'	br \} +.el .ie !\\*(f2 \{\ +.	ft \\*(f2 +.	ds f2\" +'	br \} +.el .ie !\\*(f1 \{\ +.	ft \\*(f1 +.	ds f1\" +'	br \} +.el .tm ? font underflow +.. +.ds f1\" +.ds f2\" +.ds f3\" +.ds f4\" +'\" t  +.ta 8n 16n 24n 32n 40n 48n 56n 64n 72n   +.TH "sdptool" "1"  +.SH "NAME"  +sdptool \(em control and interrogate SDP servers  +.SH "SYNOPSIS"  +.PP  +\fBsdptool\fR [\fIoptions\fR]  {\fIcommand\fR}  [\fIcommand parameters\fR \&...]   +.SH "DESCRIPTION"  +.PP  +\fBsdptool\fR provides the interface for  +performing SDP queries on Bluetooth devices, and administering a  +local \fBsdpd\fR.  +.SH "COMMANDS"  +.PP  +The following commands are available.  +.IP "\fBsearch [--bdaddr bdaddr] [--tree] service\fP" 10  +Search for services..  +.IP "" 10  +Known service names are SP< DUN, LAN, FAX, OPUSH,  +FTRN, NAP, GN, HID, CIP.  +.IP "\fBbrowse [--tree] [bdaddr]\fP" 10  +Browse all available services on the device  +specified by a Bluetooth address as a parameter.  +.IP "\fBadd [ --channel=N ]\fP" 10  +Add a service to the local   +\fBsdpd\fR.  +.IP "" 10  +You can specify a channel to add the service on  +using the \fB--channel\fP option.  +.IP "\fBdel record_handle\fP" 10  +Remove a service from the local   +\fBsdpd\fR.  +.IP "\fBget [--tree] [--bdaddr bdaddr] record_handle\fP" 10  +Retrieve a service from the local   +\fBsdpd\fR.  +.IP "\fBsetattr record_handle attrib_id attrib_value\fP" 10  +Set or add an attribute to an SDP record.  +  +.IP "\fBsetseq record_handle attrib_id attrib_values\fP" 10  +Set or add an attribute sequence to an  +SDP record.  +.SH "OPTIONS"  +.IP "\fB--help\fP" 10  +Displays help on using sdptool.  +  +.SH "EXAMPLES"  +.PP  +sdptool browse 00:80:98:24:15:6D  +.PP  +sdptool add DUN  +.PP  +sdptool del LAN  +.SH "BUGS"  +.PP  +Documentation needs improving.  +.SH "AUTHOR"  +.PP  +Maxim Krasnyansky <maxk@qualcomm.com>. Man page written  +by Edd Dumbill <ejad@debian.org>.  +  +.SH "SEE ALSO"  +.PP  +sdpd(8) +.\" created by instant / docbook-to-man, Thu 15 Jan 2004, 21:01  diff --git a/tools/sdptool.c b/tools/sdptool.c new file mode 100644 index 00000000..5340ab1b --- /dev/null +++ b/tools/sdptool.c @@ -0,0 +1,2072 @@ +/* +   Service Discovery Protocol (SDP) +   Copyright (C) 2002 Maxim Krasnyansky <maxk@qualcomm.com> +   Copyright (C) 2002 Stephen Crane <steve.crane@rococosoft.com> +     +   Based on original SDP implementation by Nokia Corporation. +   Copyright (C) 2001,2002 Nokia Corporation. +   Original author Guruprasad Krishnamurthy <guruprasad.krishnamurthy@nokia.com> +     +   This program is free software; you can redistribute it and/or modify +   it under the terms of the GNU General Public License version 2 as +   published by the Free Software Foundation; +     +   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +   OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. +   IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY CLAIM, +   OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER +   RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +   NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE +   USE OR PERFORMANCE OF THIS SOFTWARE. +     +   ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, COPYRIGHTS, +   TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS SOFTWARE IS DISCLAIMED. +*/ + +/* + * $Id$ + */ + +#include <stdlib.h> +#include <stdio.h> +#include <getopt.h> +#include <errno.h> + +#include <bluetooth/sdp.h> +#include <bluetooth/sdp_lib.h> +#include <bluetooth/sdp_internal.h> + +#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 + */ +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); +} + +/* Pass args to the inquiry/search handler */ +struct search_context { +	char 		*svc;		/* Service */ +	uuid_t		group;		/* Browse group */ +	int		tree;		/* Display full attribute tree */ +	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 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 -- */ +  { 0x1, "SDP (Service Discovery Protocol)", NULL, 0 }, +  { 0x2, "UDP", NULL, 0 }, +  { 0x3, "RFCOMM", NULL, 0 }, +  { 0x4, "TCP", NULL, 0 }, +  { 0x5, "TCS-BIN", NULL, 0 }, +  { 0x6, "TCS-AT", NULL, 0 }, +  { 0x8, "OBEX", NULL, 0 }, +  { 0x9, "IP", NULL, 0 }, +  { 0xA, "FTP", NULL, 0 }, +  { 0xC, "HTTP", NULL, 0 }, +  { 0xE, "WSP", NULL, 0 }, +  { 0xF, "BNEP (PAN/BNEP)", NULL, 0 }, +  { 0x10, "UPnP/ESDP", NULL, 0 }, +  { 0x11, "HIDP", NULL, 0 }, +  { 0x12, "HardcopyControlChannel", NULL, 0 }, +  { 0x14, "HardcopyDataChannel", NULL, 0 }, +  { 0x16, "HardcopyNotification", NULL, 0 }, +  { 0x17, "AVCTP", NULL, 0 }, +  { 0x19, "AVDTP", NULL, 0 }, +  { 0x1B, "CMTP", NULL, 0 }, +  { 0x1D, "UDI_C-Plane", NULL, 0 }, +  { 0x100, "L2CAP", NULL, 0 }, +  /* -- Services -- */ +  { 0x1000, "ServiceDiscoveryServerServiceClassID (SDP)", +    sdp_attrib_names, sizeof(sdp_attrib_names)/sizeof(struct attrib_def) }, +  { 0x1001, "BrowseGroupDescriptorServiceClassID (SDP)", +    browse_attrib_names, +    sizeof(browse_attrib_names)/sizeof(struct attrib_def) }, +  { 0x1002, "PublicBrowseGroup (SDP)", 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 }, +  /* ... */ +  { 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 }, +  /* ... */ +  { 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)", NULL, 0 }, +  { 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 }, +  /* ... */ +  { 0x1200, "PnPInformation", NULL, 0 }, +  { 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 }, +}; + +const int uuid16_max = sizeof(uuid16_names)/sizeof(struct uuid_def); + +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... + */ +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) { +			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). + */ +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->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: +		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. + */ +void sdp_attr_printf_func(void *value, void *userData) +{ +	sdp_data_t *sdpdata = NULL; +	uint16_t attrId; +	struct service_context *service = (struct service_context *) userData; +	struct attrib_context context; +	struct attrib_def *attrDef = NULL; +	int i; + +	sdpdata = (sdp_data_t *)value; +	attrId = sdpdata->attrId; +	/* Search amongst the generic attributes */ +	for (i = 0; i < attrib_max; i++) +		if (attrib_names[i].num == attrId) { +			attrDef = &attrib_names[i]; +			break; +		} +	/* Search amongst the specific attributes of this service */ +	if ((attrDef == NULL) && +	   (service->service != NULL) && +	   (service->service->attribs != NULL)) { +		struct attrib_def *svc_attribs = service->service->attribs; +		int		svc_attrib_max = service->service->attrib_max; +		for (i = 0; i < svc_attrib_max; i++) +			if (svc_attribs[i].num == attrId) { +				attrDef = &svc_attribs[i]; +				break; +			} +	} + +	if (attrDef) +		printf("Attribute Identifier : 0x%x - %s\n", +		       attrId, attrDef->name); +	else +		printf("Attribute Identifier : 0x%x\n", attrId); +	/* Build context */ +	context.service = service->service; +	context.attrib = attrDef; +	context.member_index = 0; +	/* Parse attribute members */ +	if (sdpdata) +		sdp_data_printf(sdpdata, &context, 2); +	else +		printf("  NULL value\n"); +	/* Update service */ +	service->service = context.service; +} + +/* + * Main entry point of this library. Parse a SDP record. + * We assume the record has already been read, parsed and cached + * locally. Jean II + */ +void sdp_printf_service_attr(sdp_record_t *rec) +{ +	if (rec && rec->attrlist) { +		struct service_context service = { NULL }; +		sdp_list_foreach(rec->attrlist, sdp_attr_printf_func, &service); +	} +} + +/* + * Set attributes with single values in SDP record + * Jean II + */ +int set_attrib(sdp_session_t *sess, uint32_t handle, uint16_t attrib, char *value)  +{ +	sdp_list_t *attrid_list; +	uint32_t range = 0x0000ffff; +	sdp_record_t *rec; +		 +	/* Get the old SDP record */ +	attrid_list = sdp_list_append(NULL, &range); +	rec = sdp_service_attr_req(sess, handle, SDP_ATTR_REQ_RANGE, attrid_list); + +	if (!rec) { +		printf("Service get request failed.\n"); +		return -1; +	} + +	/* Check the type of attribute */ +	if (!strncasecmp(value, "u0x", 3)) { +		/* UUID16 */ +		uint16_t value_int = 0; +		uuid_t value_uuid; +		value_int = strtoul(value + 3, NULL, 16); +		sdp_uuid16_create(&value_uuid, value_int); +		printf("Adding attrib 0x%X uuid16 0x%X to record 0x%X\n", +		       attrib, value_int, handle); + +		sdp_attr_add_new(rec, attrib, SDP_UUID16, &value_uuid.value.uuid16); +	} else if (!strncasecmp(value, "0x", 2)) { +		/* Int */ +		uint32_t value_int;   +		value_int = strtoul(value + 2, NULL, 16); +		printf("Adding attrib 0x%X int 0x%X to record 0x%X\n", +		       attrib, value_int, handle); + +		sdp_attr_add_new(rec, attrib, SDP_UINT32, &value_int); +	} else { +		/* String */ +		printf("Adding attrib 0x%X string \"%s\" to record 0x%X\n", +		       attrib, value, handle); + +		/* Add/Update our attribute to the record */ +		sdp_attr_add_new(rec, attrib, SDP_TEXT_STR8, value); +	} + +	/* Update on the server */ +	if (sdp_record_update(sess, rec)) { +		printf("Service Record update failed (%d).\n", errno); +		return -1; +	} +	return 0; +} + +static struct option set_options[] = { +	{"help",    0,0, 'h'}, +	{0, 0, 0, 0} +}; + +static char *set_help =  +	"Usage:\n" +	"\tget record_handle attrib_id attrib_value\n"; + +/* + * Add an attribute to an existing SDP record on the local SDP server + */ +int cmd_setattr(int argc, char **argv) +{ +	int opt, status; +	uint32_t handle; +	uint16_t attrib; +	sdp_session_t *sess; + +	for_each_opt(opt, set_options, NULL) { +		switch(opt) { +		default: +			printf(set_help); +			return -1; +		} +	} +	argc -= optind; +	argv += optind; + +	if (argc < 3) { +		printf(set_help); +		return -1; +	} + +	/* Convert command line args */ +	handle = strtoul(argv[0], NULL, 16); +	attrib = strtoul(argv[1], NULL, 16); + +	/* Do it */ +	sess = sdp_connect(BDADDR_ANY, BDADDR_LOCAL, 0); +	if (!sess) +		return -1; +	status = set_attrib(sess, handle, attrib, argv[2]); +	sdp_close(sess); +	return status; +} + +/* + * We do only simple data sequences. Sequence of sequences is a pain ;-) + * Jean II + */ +int set_attribseq(sdp_session_t *session, uint32_t handle, uint16_t attrib, int argc, char **argv) +{ +	sdp_list_t *attrid_list; +	uint32_t range = 0x0000ffff; +	sdp_record_t *rec; +	sdp_data_t *pSequenceHolder = NULL; +	void **dtdArray; +	void **valueArray; +	void **allocArray; +	uint8_t uuid16 = SDP_UUID16; +	uint8_t uint32 = SDP_UINT32; +	uint8_t str8 = SDP_TEXT_STR8; +	int i; + +	/* Get the old SDP record */ +	attrid_list = sdp_list_append(NULL, &range); +	rec = sdp_service_attr_req(session, handle, SDP_ATTR_REQ_RANGE, attrid_list); + +	if (!rec) { +		printf("Service get request failed.\n"); +		return -1; +	} + +	/* Create arrays */ +	dtdArray = (void **)malloc(argc * sizeof(void *)); +	valueArray = (void **)malloc(argc * sizeof(void *)); +	allocArray = (void **)malloc(argc * sizeof(void *)); + +	/* Loop on all args, add them in arrays */ +	for (i = 0; i < argc; i++) { +		/* Check the type of attribute */ +		if (!strncasecmp(argv[i], "u0x", 3)) { +			/* UUID16 */ +			uint16_t value_int = strtoul((argv[i]) + 3, NULL, 16); +			uuid_t *value_uuid = (uuid_t *)malloc(sizeof(uuid_t)); +			allocArray[i] = value_uuid; +			sdp_uuid16_create(value_uuid, value_int); + +			printf("Adding uuid16 0x%X to record 0x%X\n", +			       value_int, handle); +			dtdArray[i] = &uuid16; +			valueArray[i] = &value_uuid->value.uuid16; +		} else if (!strncasecmp(argv[i], "0x", 2)) { +			/* Int */ +			uint32_t *value_int = (int *)malloc(sizeof(int)); +			allocArray[i] = value_int; +			*value_int = strtoul((argv[i]) + 2, NULL, 16); + +			printf("Adding int 0x%X to record 0x%X\n", +			       *value_int, handle); +			dtdArray[i] = &uint32; +			valueArray[i] = value_int; +		} else { +			/* String */ +			printf("Adding string \"%s\" to record 0x%X\n", +			       argv[i], handle); +			dtdArray[i] = &str8; +			valueArray[i] = argv[i]; +		} +	} + +	/* Add this sequence to the attrib list */ +	pSequenceHolder = sdp_seq_alloc(dtdArray, valueArray, argc); +	if (pSequenceHolder) { +		sdp_attr_replace(rec, attrib, pSequenceHolder); + +		/* Update on the server */ +		if (sdp_record_update(session, rec)) { +			printf("Service Record update failed (%d).\n", errno); +			return -1; +		} +	} else { +		printf("Failed to create pSequenceHolder\n"); +	} + +	/* Cleanup */ +	for (i = 0; i < argc; i++) +		free(allocArray[i]); +	free(dtdArray); +	free(valueArray); + +	return 0; +} + +static struct option seq_options[] = { +	{"help",    0,0, 'h'}, +	{0, 0, 0, 0} +}; + +static char *seq_help =  +	"Usage:\n" +	"\tget record_handle attrib_id attrib_values\n"; + +/* + * Add an attribute sequence to an existing SDP record + * on the local SDP server + */ +int cmd_setseq(int argc, char **argv) +{ +	int opt, status; +	uint32_t handle; +	uint16_t attrib; +	sdp_session_t *sess; + +	for_each_opt(opt, seq_options, NULL) { +		switch(opt) { +		default: +			printf(seq_help); +			return -1; +		} +	} +	argc -= optind; +	argv += optind; + +	if (argc < 3) { +		printf(seq_help); +		return -1; +	} + +	/* Convert command line args */ +	handle = strtoul(argv[0], NULL, 16); +	attrib = strtoul(argv[1], NULL, 16); + +	argc -= 2; +	argv += 2; + +	/* Do it */ +	sess = sdp_connect(BDADDR_ANY, BDADDR_LOCAL, 0); +	if (!sess) +		return -1; +	status = set_attribseq(sess, handle, attrib, argc, argv); +	sdp_close(sess); +	return status; +} + +static void print_service_class(void *value, void *userData) +{ +	char ServiceClassUUID_str[MAX_LEN_SERVICECLASS_UUID_STR]; +	uuid_t *uuid = (uuid_t *)value; + +	sdp_uuid2strn(uuid, UUID_str, MAX_LEN_UUID_STR); +	sdp_svclass_uuid2strn(uuid, ServiceClassUUID_str, MAX_LEN_SERVICECLASS_UUID_STR); +	printf("  \"%s\" (0x%s)\n", ServiceClassUUID_str, UUID_str); +} + +static void print_service_desc(void *value, void *user) +{ +	char str[MAX_LEN_PROTOCOL_UUID_STR]; +	sdp_data_t *p = (sdp_data_t *)value, *s; +	int i = 0, proto = 0; +	 +	for (; p; p = p->next, i++) { +		switch (p->dtd) { +		case SDP_UUID16: +		case SDP_UUID32: +		case SDP_UUID128: +			sdp_uuid2strn(&p->val.uuid, UUID_str, MAX_LEN_UUID_STR); +			sdp_proto_uuid2strn(&p->val.uuid, str, sizeof(str)); +			proto = sdp_uuid_to_proto(&p->val.uuid); +			printf("  \"%s\" (0x%s)\n", str, UUID_str); +			break; +		case SDP_UINT8: +			if (proto == RFCOMM_UUID) +				printf("    Channel: %d\n", p->val.uint8); +			else +				printf("    uint8: 0x%x\n", p->val.uint8); +			break; +		case SDP_UINT16: +			if (proto == L2CAP_UUID) { +				if (i == 1) +					printf("    PSM: %d\n", p->val.uint16); +				else +					printf("    Version: 0x%04x\n", p->val.uint16); +			} else if (proto == BNEP_UUID) +				if (i == 1) +					printf("    Version: 0x%04x\n", p->val.uint16); +				else +					printf("    uint16: 0x%x\n", p->val.uint16); +			else +				printf("    uint16: 0x%x\n", p->val.uint16); +			break; +		case SDP_SEQ16: +			printf("    SEQ16:"); +			for (s = p->val.dataseq; s; s = s->next) +				printf(" %x", s->val.uint16); +			printf("\n"); +			break; +		case SDP_SEQ8: +			printf("    SEQ8:"); +			for (s = p->val.dataseq; s; s = s->next) +				printf(" %x", s->val.uint8); +			printf("\n"); +			break; +		default: +			printf("    FIXME: dtd=0%x\n", p->dtd); +			break; +		} +	} +} + +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); +} + +void print_access_protos(value, userData) +{ +	sdp_list_t *protDescSeq = (sdp_list_t *)value; +	sdp_list_foreach(protDescSeq, print_service_desc, 0); +} + +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. + */ +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_free(proto, (sdp_free_func_t)sdp_data_free); +	} +	if (sdp_get_lang_attr(rec, &list) == 0) { +		printf("Language Base Attr List:\n"); +		sdp_list_foreach(list, print_lang_attr, 0); +		sdp_list_free(list, free); +	} +	if (sdp_get_profile_descs(rec, &list) == 0) { +		printf("Profile Descriptor List:\n"); +		sdp_list_foreach(list, print_profile_desc, 0); +		sdp_list_free(list, free); +	} +} + +/* + * Support for Service (de)registration + */ +typedef struct { +	char *name; +	char *provider; +	char *desc; +	 +	unsigned int class; +	unsigned int profile; +	unsigned int channel; +} svc_info_t; + +static int add_sp(sdp_session_t *session, svc_info_t *si) +{ +	sdp_list_t *svclass_id, *apseq, *proto[2], *profiles, *root, *aproto; +	uuid_t root_uuid, sp_uuid, l2cap, rfcomm; +	sdp_profile_desc_t profile; +	sdp_record_t record; +	uint8_t u8 = si->channel? si->channel: 1; +	sdp_data_t *channel; +	int ret = 0; + +	memset((void *)&record, 0, sizeof(sdp_record_t)); +	record.handle = 0xffffffff; +	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); +	root = sdp_list_append(0, &root_uuid); +	sdp_set_browse_groups(&record, root); +	sdp_list_free(root, 0); + +	sdp_uuid16_create(&sp_uuid, SERIAL_PORT_SVCLASS_ID); +	svclass_id = sdp_list_append(0, &sp_uuid); +	sdp_set_service_classes(&record, svclass_id); +	sdp_list_free(svclass_id, 0); + +	sdp_uuid16_create(&profile.uuid, SERIAL_PORT_PROFILE_ID); +	profile.version = 0x0100; +	profiles = sdp_list_append(0, &profile); +	sdp_set_profile_descs(&record, profiles); +	sdp_list_free(profiles, 0); + +	sdp_uuid16_create(&l2cap, L2CAP_UUID); +	proto[0] = sdp_list_append(0, &l2cap); +	apseq = sdp_list_append(0, proto[0]); + +	sdp_uuid16_create(&rfcomm, RFCOMM_UUID); +	proto[1] = sdp_list_append(0, &rfcomm); +	channel = sdp_data_alloc(SDP_UINT8, &u8); +	proto[1] = sdp_list_append(proto[1], channel); +	apseq = sdp_list_append(apseq, proto[1]); + +	aproto = sdp_list_append(0, apseq); +	sdp_set_access_protos(&record, aproto); + +	sdp_set_info_attr(&record, "Serial Port", 0, 0); + +	if (0 > sdp_record_register(session, &record, SDP_RECORD_PERSIST)) { +		printf("Service Record registration failed\n"); +		ret = -1; +		goto end; +	} +	printf("Serial Port service registered\n"); +end: +	sdp_data_free(channel); +	sdp_list_free(proto[0], 0); +	sdp_list_free(proto[1], 0); +	sdp_list_free(apseq, 0); +	sdp_list_free(aproto, 0); +	return ret; +} + +static int add_dun(sdp_session_t *session, svc_info_t *si) +{ +	sdp_list_t *svclass_id, *pfseq, *apseq, *root, *aproto; +	uuid_t rootu, dun, gn, l2cap, rfcomm; +	sdp_profile_desc_t profile; +	sdp_list_t *proto[2]; +	sdp_record_t record; +	uint8_t u8 = si->channel? si->channel: 1; +	sdp_data_t *channel; +	int ret = 0; + +	memset((void *)&record, 0, sizeof(sdp_record_t)); +	record.handle = 0xffffffff; +	sdp_uuid16_create(&rootu, PUBLIC_BROWSE_GROUP); +	root = sdp_list_append(0, &rootu); +	sdp_set_browse_groups(&record, root); + +	sdp_uuid16_create(&dun, DIALUP_NET_SVCLASS_ID); +	svclass_id = sdp_list_append(0, &dun); +	sdp_uuid16_create(&gn,  GENERIC_NETWORKING_SVCLASS_ID); +	svclass_id = sdp_list_append(svclass_id, &gn); +	sdp_set_service_classes(&record, svclass_id); + +	sdp_uuid16_create(&profile.uuid, DIALUP_NET_PROFILE_ID); +	profile.version = 0x0100; +	pfseq = sdp_list_append(0, &profile); +	sdp_set_profile_descs(&record, pfseq); + +	sdp_uuid16_create(&l2cap, L2CAP_UUID); +	proto[0] = sdp_list_append(0, &l2cap); +	apseq = sdp_list_append(0, proto[0]); + +	sdp_uuid16_create(&rfcomm, RFCOMM_UUID); +	proto[1] = sdp_list_append(0, &rfcomm); +	channel = sdp_data_alloc(SDP_UINT8, &u8); +	proto[1] = sdp_list_append(proto[1], channel); +	apseq = sdp_list_append(apseq, proto[1]); + +	aproto = sdp_list_append(0, apseq); +	sdp_set_access_protos(&record, aproto); + +	sdp_set_info_attr(&record, "Dial-Up Networking", 0, 0); + +	if (0 > sdp_record_register(session, &record, SDP_RECORD_PERSIST)) { +		printf("Service Record registration failed\n"); +		ret = -1; +		goto end; +	} +	printf("Dial-Up Networking service registered\n"); +end: +	sdp_data_free(channel); +	sdp_list_free(proto[0], 0); +	sdp_list_free(proto[1], 0); +	sdp_list_free(apseq, 0); +	sdp_list_free(aproto, 0); +	return ret; +} + +static int add_lan(sdp_session_t *session, svc_info_t *si) +{ +	sdp_list_t *svclass_id, *pfseq, *apseq, *root; +	uuid_t root_uuid, svclass_uuid, l2cap_uuid, rfcomm_uuid; +	sdp_profile_desc_t profile; +	sdp_list_t *aproto, *proto[2]; +	sdp_record_t record; +	uint8_t u8 = si->channel? si->channel: 2; +	sdp_data_t *channel; +	int ret = 0; + +	memset((void *)&record, 0, sizeof(sdp_record_t)); +	record.handle = 0xffffffff; +	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); +	root = sdp_list_append(0, &root_uuid); +	sdp_set_browse_groups(&record, root); + +	sdp_uuid16_create(&svclass_uuid, LAN_ACCESS_SVCLASS_ID); +	svclass_id = sdp_list_append(0, &svclass_uuid); +	sdp_set_service_classes(&record, svclass_id); + +	sdp_uuid16_create(&profile.uuid, LAN_ACCESS_PROFILE_ID); +	profile.version = 0x0100; +	pfseq = sdp_list_append(0, &profile); +	sdp_set_profile_descs(&record, pfseq); + +	sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); +	proto[0] = sdp_list_append(0, &l2cap_uuid); +	apseq = sdp_list_append(0, proto[0]); + +	sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); +	proto[1] = sdp_list_append(0, &rfcomm_uuid); +	channel = sdp_data_alloc(SDP_UINT8, &u8); +	proto[1] = sdp_list_append(proto[1], channel); +	apseq = sdp_list_append(apseq, proto[1]); + +	aproto = sdp_list_append(0, apseq); +	sdp_set_access_protos(&record, aproto); + +	sdp_set_info_attr(&record, "LAN Access over PPP", 0, 0); + +	if (0 > sdp_record_register(session, &record, SDP_RECORD_PERSIST)) { +		printf("Service Record registration failed\n"); +		ret = -1; +		goto end; +	} +	printf("LAN Access service registered\n"); +end: +	sdp_data_free(channel); +	sdp_list_free(proto[0], 0); +	sdp_list_free(proto[1], 0); +	sdp_list_free(apseq, 0); +	sdp_list_free(aproto, 0); +	return ret; +} + + +static int add_headset(sdp_session_t *session, svc_info_t *si) +{ +	sdp_list_t *svclass_id, *pfseq, *apseq, *root; +	uuid_t root_uuid, svclass_uuid, ga_svclass_uuid, l2cap_uuid, rfcomm_uuid; +	sdp_profile_desc_t profile; +	sdp_list_t *aproto, *proto[2]; +	sdp_record_t record; +	uint8_t u8 = si->channel? si->channel: 5; +	sdp_data_t *channel; +	int ret = 0; + +	memset((void *)&record, 0, sizeof(sdp_record_t)); +	record.handle = 0xffffffff; +	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); +	root = sdp_list_append(0, &root_uuid); +	sdp_set_browse_groups(&record, root); + +	sdp_uuid16_create(&svclass_uuid, HEADSET_SVCLASS_ID); +	svclass_id = sdp_list_append(0, &svclass_uuid); +	sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID); +	svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid); +	sdp_set_service_classes(&record, svclass_id); + +	sdp_uuid16_create(&profile.uuid, HEADSET_PROFILE_ID); +	profile.version = 0x0100; +	pfseq = sdp_list_append(0, &profile); +	sdp_set_profile_descs(&record, pfseq); + +	sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); +	proto[0] = sdp_list_append(0, &l2cap_uuid); +	apseq = sdp_list_append(0, proto[0]); + +	sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); +	proto[1] = sdp_list_append(0, &rfcomm_uuid); +	channel = sdp_data_alloc(SDP_UINT8, &u8); +	proto[1] = sdp_list_append(proto[1], channel); +	apseq = sdp_list_append(apseq, proto[1]); + +	aproto = sdp_list_append(0, apseq); +	sdp_set_access_protos(&record, aproto); + +	sdp_set_info_attr(&record, "Headset", 0, 0); + +	if (0 > sdp_record_register(session, &record, SDP_RECORD_PERSIST)) { +		printf("Service Record registration failed\n"); +		ret = -1; +		goto end; +	} +	printf("Headset service registered\n"); +end: +	sdp_data_free(channel); +	sdp_list_free(proto[0], 0); +	sdp_list_free(proto[1], 0); +	sdp_list_free(apseq, 0); +	sdp_list_free(aproto, 0); +	return ret; +} + +static int add_handsfree(sdp_session_t *session, svc_info_t *si) +{ +	sdp_list_t *svclass_id, *pfseq, *apseq, *root; +	uuid_t root_uuid, svclass_uuid, ga_svclass_uuid, l2cap_uuid, rfcomm_uuid; +	sdp_profile_desc_t profile; +	sdp_list_t *aproto, *proto[2]; +	sdp_record_t record; +	uint8_t u8 = si->channel? si->channel: 3; +	uint16_t u16 = 0x31; +	sdp_data_t *channel, *features;	 +	int ret = 0; + +	memset((void *)&record, 0, sizeof(sdp_record_t)); +	record.handle = 0xffffffff; +	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); +	root = sdp_list_append(0, &root_uuid); +	sdp_set_browse_groups(&record, root); + +	sdp_uuid16_create(&svclass_uuid, HANDSFREE_SVCLASS_ID); +	svclass_id = sdp_list_append(0, &svclass_uuid); +	sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID); +	svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid); +	sdp_set_service_classes(&record, svclass_id); + +	sdp_uuid16_create(&profile.uuid, HANDSFREE_PROFILE_ID); +	profile.version = 0x0101; +	pfseq = sdp_list_append(0, &profile); +	sdp_set_profile_descs(&record, pfseq); + +	sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); +	proto[0] = sdp_list_append(0, &l2cap_uuid); +	apseq = sdp_list_append(0, proto[0]); + +	sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); +	proto[1] = sdp_list_append(0, &rfcomm_uuid); +	channel = sdp_data_alloc(SDP_UINT8, &u8); +	proto[1] = sdp_list_append(proto[1], channel); +	apseq = sdp_list_append(apseq, proto[1]); + +	features = sdp_data_alloc(SDP_UINT16, &u16); +	sdp_attr_add(&record, SDP_SUPPORTED_FEATURES, features); + +	aproto = sdp_list_append(0, apseq); +	sdp_set_access_protos(&record, aproto); + +	sdp_set_info_attr(&record, "", 0, 0); + +	if (0 > sdp_record_register(session, &record, SDP_RECORD_PERSIST)) { +		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_fax(sdp_session_t *session, svc_info_t *si) +{ +	sdp_list_t *svclass_id, *pfseq, *apseq, *root; +	uuid_t root_uuid, fax_uuid, tel_uuid, l2cap_uuid, rfcomm_uuid; +	sdp_profile_desc_t profile; +	sdp_list_t *aproto, *proto[2]; +	sdp_record_t record; +	uint8_t u8 = si->channel? si->channel: 3; +	sdp_data_t *channel; +	int ret = 0; + +	memset((void *)&record, 0, sizeof(sdp_record_t)); +	record.handle = 0xffffffff; +	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); +	root = sdp_list_append(0, &root_uuid); +	sdp_set_browse_groups(&record, root); + +	sdp_uuid16_create(&fax_uuid, FAX_SVCLASS_ID); +	svclass_id = sdp_list_append(0, &fax_uuid); +	sdp_uuid16_create(&tel_uuid, GENERIC_TELEPHONY_SVCLASS_ID); +	svclass_id = sdp_list_append(svclass_id, &tel_uuid); +	sdp_set_service_classes(&record, svclass_id); + +	sdp_uuid16_create(&profile.uuid, FAX_PROFILE_ID); +	profile.version = 0x0100; +	pfseq = sdp_list_append(0, &profile); +	sdp_set_profile_descs(&record, pfseq); + +	sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); +	proto[0] = sdp_list_append(0, &l2cap_uuid); +	apseq = sdp_list_append(0, proto[0]); + +	sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); +	proto[1] = sdp_list_append(0, &rfcomm_uuid); +	channel = sdp_data_alloc(SDP_UINT8, &u8); +	proto[1] = sdp_list_append(proto[1], channel); +	apseq  = sdp_list_append(apseq, proto[1]); + +	aproto = sdp_list_append(0, apseq); +	sdp_set_access_protos(&record, aproto); + +	sdp_set_info_attr(&record, "Fax", 0, 0); + +	if (0 > sdp_record_register(session, &record, SDP_RECORD_PERSIST)) { +		printf("Service Record registration failed\n"); +		ret = -1; +		goto end; +	} +	printf("Fax service registered\n"); +end: +	sdp_data_free(channel); +	sdp_list_free(proto[0], 0); +	sdp_list_free(proto[1], 0); +	sdp_list_free(apseq, 0); +	sdp_list_free(aproto, 0); +	return ret; +} + +static int add_opush(sdp_session_t *session, svc_info_t *si) +{ +	sdp_list_t *svclass_id, *pfseq, *apseq, *root; +	uuid_t root_uuid, opush_uuid, l2cap_uuid, rfcomm_uuid, obex_uuid; +	sdp_profile_desc_t profile[1]; +	sdp_list_t *aproto, *proto[3]; +	sdp_record_t record; +	uint8_t chan = si->channel? si->channel: 4; +	sdp_data_t *channel; +	uint8_t formats[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; +	//uint8_t formats[] = { 0xff }; +	void *dtds[sizeof(formats)], *values[sizeof(formats)]; +	int i; +	uint8_t dtd = SDP_UINT8; +	sdp_data_t *sflist; +	int ret = 0; + +	memset((void *)&record, 0, sizeof(sdp_record_t)); +	record.handle = 0xffffffff; +	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); +	root = sdp_list_append(0, &root_uuid); +	sdp_set_browse_groups(&record, root); + +	sdp_uuid16_create(&opush_uuid, OBEX_OBJPUSH_SVCLASS_ID); +	svclass_id = sdp_list_append(0, &opush_uuid); +	sdp_set_service_classes(&record, svclass_id); + +	sdp_uuid16_create(&profile[0].uuid, OBEX_OBJPUSH_PROFILE_ID); +	profile[0].version = 0x0100; +	pfseq = sdp_list_append(0, profile); +	sdp_set_profile_descs(&record, pfseq); + +	sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); +	proto[0] = sdp_list_append(0, &l2cap_uuid); +	apseq = sdp_list_append(0, proto[0]); + +	sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); +	proto[1] = sdp_list_append(0, &rfcomm_uuid); +	channel = sdp_data_alloc(SDP_UINT8, &chan); +	proto[1] = sdp_list_append(proto[1], channel); +	apseq = sdp_list_append(apseq, proto[1]); + +	sdp_uuid16_create(&obex_uuid, OBEX_UUID); +	proto[2] = sdp_list_append(0, &obex_uuid); +	apseq = sdp_list_append(apseq, proto[2]); + +	aproto = sdp_list_append(0, apseq); +	sdp_set_access_protos(&record, aproto); + +	for (i = 0; i < sizeof(formats); i++) { +		dtds[i] = &dtd; +		values[i] = &formats[i]; +	} +	sflist = sdp_seq_alloc(dtds, values, sizeof(formats)); +	sdp_attr_add(&record, SDP_ATTR_SUPPORTED_FORMATS_LIST, sflist); + +	sdp_set_info_attr(&record, "OBEX Object Push", 0, 0); + +	if (0 > sdp_record_register(session, &record, SDP_RECORD_PERSIST)) { +		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_file_trans(sdp_session_t *session, svc_info_t *si) +{ +	sdp_list_t *svclass_id, *pfseq, *apseq, *root; +	uuid_t root_uuid, ftrn_uuid, l2cap_uuid, rfcomm_uuid, obex_uuid; +	sdp_profile_desc_t profile[1]; +	sdp_list_t *aproto, *proto[3]; +	sdp_record_t record; +	uint8_t u8 = si->channel? si->channel: 4; +	sdp_data_t *channel; +	int ret = 0; + +	memset((void *)&record, 0, sizeof(sdp_record_t)); +	record.handle = 0xffffffff; +	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); +	root = sdp_list_append(0, &root_uuid); +	sdp_set_browse_groups(&record, root); + +	sdp_uuid16_create(&ftrn_uuid, OBEX_FILETRANS_SVCLASS_ID); +	svclass_id = sdp_list_append(0, &ftrn_uuid); +	sdp_set_service_classes(&record, svclass_id); + +	sdp_uuid16_create(&profile[0].uuid, OBEX_FILETRANS_PROFILE_ID); +	profile[0].version = 0x0100; +	pfseq = sdp_list_append(0, &profile[0]); +	sdp_set_profile_descs(&record, pfseq); + +	sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); +	proto[0] = sdp_list_append(0, &l2cap_uuid); +	apseq = sdp_list_append(0, proto[0]); + +	sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); +	proto[1] = sdp_list_append(0, &rfcomm_uuid); +	channel = sdp_data_alloc(SDP_UINT8, &u8); +	proto[1] = sdp_list_append(proto[1], channel); +	apseq = sdp_list_append(apseq, proto[1]); + +	sdp_uuid16_create(&obex_uuid, OBEX_UUID); +	proto[2] = sdp_list_append(0, &obex_uuid); +	apseq = sdp_list_append(apseq, proto[2]); + +	aproto = sdp_list_append(0, apseq); +	sdp_set_access_protos(&record, aproto); + +	sdp_set_info_attr(&record, "OBEX File Transfer", 0, 0); + +	if (0 > sdp_record_register(session, &record, SDP_RECORD_PERSIST)) { +		printf("Service Record registration failed\n"); +		ret = -1; +		goto end; +	} +	printf("OBEX File Transfer service registered\n"); +end: +	sdp_data_free(channel); +	sdp_list_free(proto[0], 0); +	sdp_list_free(proto[1], 0); +	sdp_list_free(proto[2], 0); +	sdp_list_free(apseq, 0); +	sdp_list_free(aproto, 0); +	return ret; +} + +static int add_nap(sdp_session_t *session, svc_info_t *si) +{ +	sdp_list_t *svclass_id, *pfseq, *apseq, *root; +	uuid_t root_uuid, ftrn_uuid, l2cap_uuid, bnep_uuid; +	sdp_profile_desc_t profile[1]; +	sdp_list_t *aproto, *proto[2]; +	sdp_record_t record; +	uint16_t lp = 0x000f, ver = 0x0100; +	sdp_data_t *psm, *version; +	int ret = 0; + +	memset((void *)&record, 0, sizeof(sdp_record_t)); +	record.handle = 0xffffffff; +	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); +	root = sdp_list_append(0, &root_uuid); +	sdp_set_browse_groups(&record, root); + +	sdp_uuid16_create(&ftrn_uuid, NAP_SVCLASS_ID); +	svclass_id = sdp_list_append(0, &ftrn_uuid); +	sdp_set_service_classes(&record, svclass_id); + +	sdp_uuid16_create(&profile[0].uuid, NAP_PROFILE_ID); +	profile[0].version = 0x0100; +	pfseq = sdp_list_append(0, &profile[0]); +	sdp_set_profile_descs(&record, pfseq); + +	sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); +	proto[0] = sdp_list_append(0, &l2cap_uuid); +	psm = sdp_data_alloc(SDP_UINT16, &lp); +	proto[0] = sdp_list_append(proto[0], psm); +	apseq = sdp_list_append(0, proto[0]); + +	sdp_uuid16_create(&bnep_uuid, BNEP_UUID); +	proto[1] = sdp_list_append(0, &bnep_uuid); +	version  = sdp_data_alloc(SDP_UINT16, &ver); +	proto[1] = sdp_list_append(proto[1], version); + +	{ +		uint16_t ptype[4] = { 0x0010, 0x0020, 0x0030, 0x0040 }; +		sdp_data_t *head, *pseq; +		int p; + +		for (p = 0, head = NULL; p < 4; p++) { +			sdp_data_t *data = sdp_data_alloc(SDP_UINT16, &ptype[p]); +			head = sdp_seq_append(head, data); +		} +		pseq = sdp_data_alloc(SDP_SEQ16, head); +		proto[1] = sdp_list_append(proto[1], pseq); +	} + +	apseq = sdp_list_append(apseq, proto[1]); + +	aproto = sdp_list_append(0, apseq); +	sdp_set_access_protos(&record, aproto); + +	sdp_set_info_attr(&record, "Network Access Point Service", 0, 0); + +	if (0 > sdp_record_register(session, &record, SDP_RECORD_PERSIST)) { +		printf("Service Record registration failed\n"); +		ret = -1; +		goto end; +	} +	printf("NAP service registered\n"); +end: +	sdp_data_free(version); +	sdp_data_free(psm); +	sdp_list_free(proto[0], 0); +	sdp_list_free(proto[1], 0); +	sdp_list_free(apseq, 0); +	sdp_list_free(aproto, 0); +	return ret; +} + +static int add_gn(sdp_session_t *session, svc_info_t *si) +{ +	sdp_list_t *svclass_id, *pfseq, *apseq, *root; +	uuid_t root_uuid, ftrn_uuid, l2cap_uuid, bnep_uuid; +	sdp_profile_desc_t profile[1]; +	sdp_list_t *aproto, *proto[2]; +	sdp_record_t record; +	uint16_t lp = 0x000f, ver = 0x0100; +	sdp_data_t *psm, *version; +	int ret = 0; + +	memset((void *)&record, 0, sizeof(sdp_record_t)); +	record.handle = 0xffffffff; +	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); +	root = sdp_list_append(0, &root_uuid); +	sdp_set_browse_groups(&record, root); + +	sdp_uuid16_create(&ftrn_uuid, GN_SVCLASS_ID); +	svclass_id = sdp_list_append(0, &ftrn_uuid); +	sdp_set_service_classes(&record, svclass_id); + +	sdp_uuid16_create(&profile[0].uuid, GN_PROFILE_ID); +	profile[0].version = 0x0100; +	pfseq = sdp_list_append(0, &profile[0]); +	sdp_set_profile_descs(&record, pfseq); + +	sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); +	proto[0] = sdp_list_append(0, &l2cap_uuid); +	psm = sdp_data_alloc(SDP_UINT16, &lp); +	proto[0] = sdp_list_append(proto[0], psm); +	apseq = sdp_list_append(0, proto[0]); + +	sdp_uuid16_create(&bnep_uuid, BNEP_UUID); +	proto[1] = sdp_list_append(0, &bnep_uuid); +	version = sdp_data_alloc(SDP_UINT16, &ver); +	proto[1] = sdp_list_append(proto[1], version); +	apseq = sdp_list_append(apseq, proto[1]); + +	aproto = sdp_list_append(0, apseq); +	sdp_set_access_protos(&record, aproto); + +	sdp_set_info_attr(&record, "Group Network Service", 0, 0); + +	if (0 > sdp_record_register(session, &record, SDP_RECORD_PERSIST)) { +		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_hid(sdp_session_t *sess, svc_info_t *si) +{ +	return -1; +} + +static int add_cip(sdp_session_t *sess, svc_info_t *si) +{ +	return -1; +} + +static int add_ctp(sdp_session_t *session, svc_info_t *si) +{ +	sdp_list_t *svclass_id, *pfseq, *apseq, *root; +	uuid_t root_uuid, l2cap, tcsbin, ctp; +	sdp_profile_desc_t profile[1]; +	sdp_list_t *aproto, *proto[2]; +	sdp_record_t record; +	uint8_t netid = 0x02; // 0x01-0x07 cf. p120 profile document +	sdp_data_t *network = sdp_data_alloc(SDP_UINT8, &netid); +	int ret = 0; + +	memset((void *)&record, 0, sizeof(sdp_record_t)); +	record.handle = 0xffffffff; +	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); +	root = sdp_list_append(0, &root_uuid); +	sdp_set_browse_groups(&record, root); + +	sdp_uuid16_create(&ctp, CORDLESS_TELEPHONY_SVCLASS_ID); +	svclass_id = sdp_list_append(0, &ctp); +	sdp_set_service_classes(&record, svclass_id); + +	sdp_uuid16_create(&profile[0].uuid, CORDLESS_TELEPHONY_PROFILE_ID); +	profile[0].version = 0x0100; +	pfseq = sdp_list_append(0, &profile[0]); +	sdp_set_profile_descs(&record, pfseq); + +	sdp_uuid16_create(&l2cap, L2CAP_UUID); +	proto[0] = sdp_list_append(0, &l2cap); +	apseq = sdp_list_append(0, proto[0]); + +	sdp_uuid16_create(&tcsbin, TCS_BIN_UUID); +	proto[1] = sdp_list_append(0, &tcsbin); +	apseq = sdp_list_append(apseq, proto[1]); + +	aproto = sdp_list_append(0, apseq); +	sdp_set_access_protos(&record, aproto); + +	sdp_attr_add(&record, SDP_EXTERNAL_NETWORK, network); + +	sdp_set_info_attr(&record, "Cordless Telephony", 0, 0); + +	if (0 > sdp_record_register(session, &record, SDP_RECORD_PERSIST)) { +		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; +} + +struct { +	char     *name; +	uint16_t class; +	int      (*add)(sdp_session_t *sess, svc_info_t *si); +} service[] = { +	{ "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 }, +	{ "FTRN",  OBEX_FILETRANS_SVCLASS_ID, add_file_trans }, + +	{ "HS",    HEADSET_SVCLASS_ID,        add_headset   }, +	{ "HF",    HANDSFREE_SVCLASS_ID,      add_handsfree }, + +	{ "NAP",   NAP_SVCLASS_ID,            add_nap   }, +	{ "GN",    GN_SVCLASS_ID,             add_gn    }, + +	{ "HID",   HID_SVCLASS_ID,            add_hid   }, +	{ "CIP",   CIP_SVCLASS_ID,            add_cip   }, +	{ "CTP",   CORDLESS_TELEPHONY_SVCLASS_ID, add_ctp }, + +	{ 0 } +}; + +/* Add local service */ +int add_service(bdaddr_t *bdaddr, svc_info_t *si) +{ +	int i; +	sdp_session_t *sess = sdp_connect(&interface, BDADDR_LOCAL, SDP_RETRY_IF_BUSY); + +	if (!sess) +		return -1; +	if (si->name) +		for (i=0; service[i].name; i++) +			if (!strcasecmp(service[i].name, si->name)) { +				int ret = service[i].add(sess, si); +				free(si->name); +				sdp_close(sess); +				return ret; +			} +	printf("Unknown service name: %s\n", si->name); +	free(si->name); +	sdp_close(sess); +	return -1; +} + +static struct option add_options[] = { +	{"help",    0,0, 'h'}, +	{"channel", 1,0, 'c'}, +	{0, 0, 0, 0} +}; + +static char *add_help =  +	"Usage:\n" +	"\tadd [--channel=CHAN] service\n"; + +int cmd_add(int argc, char **argv) +{ +	svc_info_t si; +	int opt; + +	memset(&si, 0, sizeof(si)); +	for_each_opt(opt, add_options, 0) { +		switch(opt) { +		case 'c': +			si.channel = atoi(optarg); +			break; +		default: +			printf(add_help); +			return -1; +		} +	} +	argc -= optind; +	argv += optind; + +	if (argc < 1) { +		printf(add_help); +		return -1; +	} + +	si.name = strdup(argv[0]); +	return add_service(0, &si); +} + +/* Delete local service */ +int del_service(bdaddr_t *bdaddr, void *arg)  +{ +	uint32_t handle, range = 0x0000ffff; +	sdp_list_t *attr; +	sdp_session_t *sess; +	sdp_record_t *rec; + +	if (!arg) {  +		printf("Record handle was not specified.\n"); +		return -1; +	} +	sess = sdp_connect(&interface, BDADDR_LOCAL, SDP_RETRY_IF_BUSY); +	if (!sess) { +		printf("No local SDP server!\n"); +		return -1; +	} +	handle = strtoul((char *)arg, 0, 16); +	attr = sdp_list_append(0, &range); +	rec = sdp_service_attr_req(sess, handle, SDP_ATTR_REQ_RANGE, attr); +	sdp_list_free(attr, 0); +	if (!rec) { +		printf("Service Record not found.\n"); +		sdp_close(sess); +		return -1; +	} +	if (sdp_record_unregister(sess, rec)) { +		printf("Failed to unregister service record: %s\n", strerror(errno)); +		sdp_close(sess); +		return -1; +	} +	printf("Service Record deleted.\n"); +	sdp_close(sess); +	return 0; +} + +static struct option del_options[] = { +	{"help",    0,0, 'h'}, +	{0, 0, 0, 0} +}; + +static char *del_help =  +	"Usage:\n" +	"\tdel record_handle\n"; + +int cmd_del(int argc, char **argv) +{ +	int opt; + +	for_each_opt(opt, del_options, 0) { +		switch(opt) { +		default: +			printf(del_help); +			return -1; +		} +	} +	argc -= optind; +	argv += optind; + +	if (argc < 1) { +		printf(del_help); +		return -1; +	} +	return del_service(0, argv[0]); +} + +/* + * Perform an inquiry and search/browse all peer found. + */ +static void inquiry(handler_t handler, void *arg) +{ +	inquiry_info ii[20]; +	uint8_t count = 0; +	int i; + +	printf("Inquiring ...\n"); +	if (sdp_general_inquiry(ii, 20, 8, &count) < 0) { +		printf("Inquiry failed\n"); +		return; +	} +	for (i=0; i<count; i++) +		handler(&ii[i].bdaddr, arg); +} + +/* + * Search for a specific SDP service + */ +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->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; + +		if (context->tree) { +			/* Display full tree */ +			sdp_printf_service_attr(rec); +		} else { +			/* Display user friendly form */ +			print_service_attr(rec); +		} +		printf("\n"); +		 +		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'}, +	{0, 0, 0, 0} +}; + +static char *browse_help =  +	"Usage:\n" +	"\tbrowse [--tree] [bdaddr]\n"; + +/* + * Browse the full SDP database (i.e. list all services starting from the + * root/top-level). + */ +int cmd_browse(int argc, char **argv) +{ +	struct search_context context; +	int opt; + +	/* 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.tree = 1; +			break; +		default: +			printf(browse_help); +			return -1; +		} +	} +	argc -= optind; +	argv += optind; + +	if (argc >= 1) { +		bdaddr_t bdaddr; +		estr2ba(argv[0], &bdaddr); +		return do_search(&bdaddr, &context); +	} +	return do_search(0, &context); +} + +static struct option search_options[] = { +	{"help",    0,0, 'h'}, +	{"bdaddr",  1,0, 'b'}, +	{"tree",    0,0, 't'}, +	{0, 0, 0, 0} +}; + +static char *search_help =  +	"Usage:\n" +	"\tsearch [--bdaddr bdaddr] [--tree] 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) + */ +int cmd_search(int argc, char **argv) +{ +	struct search_context context; +	uint16_t class = 0; +	bdaddr_t bdaddr; +	int has_addr = 0; +	int i; +	int opt; + +	/* Initialise context */ +	memset(&context, '\0', sizeof(struct search_context)); + +	for_each_opt(opt, search_options, 0) { +		switch(opt) { +		case 'b': +			estr2ba(optarg, &bdaddr); +			has_addr = 1; +			break; +		case 't': +			context.tree = 1; +			break; +		default: +			printf(search_help); +			return -1; +		} +	} +	argc -= optind; +	argv += optind; + +	if (argc < 1) { +		printf(search_help); +		return -1; +	} +	/* Note : we need to find a way to support search combining +	 * multiple services - Jean II */ +	context.svc = strdup(argv[0]); +	if (!strncasecmp(context.svc, "0x", 2)) { +		int num; +		/* This is a UUID16, just convert to int */ +		sscanf(context.svc + 2, "%X", &num); +		class = num; +		printf("Class 0x%X\n", class); +	} else { +		/* Convert class name to an UUID */ + +		for (i=0; service[i].name; i++) +			if (strcasecmp(context.svc, service[i].name) == 0) { +				class = service[i].class; +				break; +			} +		if (!class) { +			printf("Unknown service %s\n", context.svc); +			return -1; +		} +	}	 + +	sdp_uuid16_create(&context.group, class); + +	if (has_addr) +		return do_search(&bdaddr, &context); +	return do_search(0, &context); +} + +/* + * Show how to get a specific SDP record by its handle. + * Not really useful to the user, just show how it can be done... + * Jean II + */ +int get_service(bdaddr_t *bdaddr, struct search_context *context)  +{ +	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) { +		printf("Service get request failed.\n"); +		return -1; +	} +	if (context->tree) { +		/* Display full tree */ +		sdp_printf_service_attr(rec); +	} else { +		/* Display user friendly form */ +		print_service_attr(rec); +	} +	printf("\n"); +	sdp_record_free(rec); +	return 0; +} + +static struct option get_options[] = { +	{"help",    0,0, 'h'}, +	{"bdaddr",  1,0, 'b'}, +	{"tree",    0,0, 't'}, +	{0, 0, 0, 0} +}; + +static char *get_help =  +	"Usage:\n" +	"\tget [--tree] [--bdaddr bdaddr] record_handle\n"; + +/* + * Get a specific SDP record on the local SDP server + */ +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.tree = 1; +			break; +		default: +			printf(get_help); +			return -1; +		} +	} +	argc -= optind; +	argv += optind; + +	if (argc < 1) { +		printf(get_help); +		return -1; +	} +	/* Convert command line parameters */ +	context.handle = strtoul(argv[0], 0, 16); + +	return get_service(has_addr? &bdaddr: BDADDR_LOCAL, &context); +} + +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" }, +	{ "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; + +	printf("sdptool - SDP Tool v%s\n", VERSION); +	printf("Usage:\n" +		"\tsdptool [options] <command> [command parameters]\n"); +	printf("Options:\n" +		"\t--help\t\tDisplay help\n" +		"\t--source\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); +	printf("\n"); +} + +static struct option main_options[] = { +	{"help",   0, 0, 'h'}, +	{"source", 1, 0, 'S'}, +	{0, 0, 0, 0} +}; + +int main(int argc, char **argv) +{ +	int opt, i; + +	bacpy(&interface, BDADDR_ANY); +	while ((opt=getopt_long(argc, argv, "+hS:", main_options, 0)) != -1) { +		switch(opt) { +		case 'S': +			str2ba(optarg, &interface); +			break; +		case 'h': +		default: +			usage(); +			return -1; +		} +	} +	argc -= optind; +	argv += optind; +	optind = 0; + +	if (argc < 1) { +		usage(); +		return -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; +} | 
