diff options
| -rw-r--r-- | hcid/dbus-adapter.c | 13 | ||||
| -rw-r--r-- | hcid/dbus-sdp.c | 457 | ||||
| -rw-r--r-- | hcid/dbus.h | 2 | 
3 files changed, 414 insertions, 58 deletions
diff --git a/hcid/dbus-adapter.c b/hcid/dbus-adapter.c index 94d2c9ba..4dcd629f 100644 --- a/hcid/dbus-adapter.c +++ b/hcid/dbus-adapter.c @@ -986,6 +986,16 @@ static DBusHandlerResult handle_dev_set_name_req(DBusConnection *conn, DBusMessa  	return send_reply_and_unref(conn, reply);  } +static DBusHandlerResult handle_dev_get_remote_svc_rec(DBusConnection *conn, DBusMessage *msg, void *data) +{ +	return get_remote_svc_rec(conn, msg, data); +} + +static DBusHandlerResult handle_dev_get_remote_svc_handles(DBusConnection *conn, DBusMessage *msg, void *data) +{ +	return get_remote_svc_handles(conn, msg, data); +} +  static DBusHandlerResult handle_dev_get_remote_version_req(DBusConnection *conn, DBusMessage *msg, void *data)  {  	struct hci_dbus_data *dbus_data = data; @@ -2404,6 +2414,9 @@ static struct service_data dev_services[] = {  	{ "GetName",					handle_dev_get_name_req			},  	{ "SetName",					handle_dev_set_name_req			}, +	{ "GetRemoteServiceRecord",			handle_dev_get_remote_svc_rec		}, +	{ "GetRemoteServiceHandles",			handle_dev_get_remote_svc_handles	}, +  	{ "GetRemoteVersion",				handle_dev_get_remote_version_req	},  	{ "GetRemoteRevision",				handle_dev_get_remote_revision_req	},  	{ "GetRemoteManufacturer",			handle_dev_get_remote_manufacturer_req	}, diff --git a/hcid/dbus-sdp.c b/hcid/dbus-sdp.c index 8e6b3ac9..e0ad16d4 100644 --- a/hcid/dbus-sdp.c +++ b/hcid/dbus-sdp.c @@ -52,6 +52,10 @@  #define MAX_IDENTIFIER_LEN	29	/* "XX:XX:XX:XX:XX:XX/0xYYYYYYYY\0" */ +/* FIXME: debug purpose */ +#define SERVICE_SEARCH_ATTR_REQ 1 + +  struct service_provider {  	char *owner;	/* null for remote services or unique name if local */  	char *prov;	/* remote Bluetooth address that provides the service */ @@ -63,18 +67,21 @@ struct service_record {  	sdp_record_t *record;  }; -struct pending_connect { +struct transaction_context {  	DBusConnection *conn;  	DBusMessage *rq; -	char *svc; -	char *dst; +	sdp_session_t *session;  }; -struct transaction_context { + +typedef int connect_cb_t (struct transaction_context *t); +struct pending_connect {  	DBusConnection *conn;  	DBusMessage *rq; -	sdp_session_t *session; +	char *dst; +	connect_cb_t *conn_cb;  }; +  /* FIXME:  move to a common file */  typedef struct {  	char            *name; @@ -149,7 +156,7 @@ static struct slist *sdp_cache = NULL;  static struct slist *pending_connects  = NULL;  static struct pending_connect *pending_connect_new(DBusConnection *conn, DBusMessage *msg, -							const char *dst, const char *svc) +							const char *dst, connect_cb_t *cb)  {  	struct pending_connect *c; @@ -160,11 +167,6 @@ static struct pending_connect *pending_connect_new(DBusConnection *conn, DBusMes  	memset(c, 0, sizeof(*c)); -	if (svc) { -		c->svc = strdup(svc); -		if (!c->svc) -			goto fail; -	}  	if (dst) {  		c->dst = strdup(dst);  		if (!c->dst) @@ -173,12 +175,11 @@ static struct pending_connect *pending_connect_new(DBusConnection *conn, DBusMes  	c->conn = dbus_connection_ref(conn);  	c->rq = dbus_message_ref(msg); +	c->conn_cb = cb;  	return c;  fail: -	if (c->svc) -		free(c->svc);  	if (c->dst)  		free(c->dst);  	free(c); @@ -191,9 +192,6 @@ static void pending_connect_free(struct pending_connect *c)  	if (!c)  		return; -	if (c->svc) -		free(c->svc); -  	if (c->rq)  		dbus_message_unref(c->rq); @@ -474,6 +472,136 @@ fail:  	return retval;  } +static void remote_svc_rec_completed_cb(uint8_t type, uint16_t err, uint8_t *rsp, size_t size, void *udata) +{ +	struct transaction_context *ctxt = udata; +	char identifier[MAX_IDENTIFIER_LEN]; +	sdp_record_t *rec = NULL; +	DBusMessage *reply; +	DBusMessageIter iter, array_iter; +	const char *dst; +	int i, scanned; + +	if (!ctxt) +		return; + +	if (type == SDP_ERROR_RSP) { +		error_sdp_failed(ctxt->conn, ctxt->rq, err); +		return; +	} + +	/* check response PDU ID */ +	if (type != SDP_SVC_ATTR_RSP) { +		error("SDP error: %s(%d)", strerror(EPROTO), EPROTO); +		error_failed(ctxt->conn, ctxt->rq, EPROTO); +		return; +	} + +	dbus_message_get_args(ctxt->rq, NULL, +			DBUS_TYPE_STRING, &dst, +			DBUS_TYPE_INVALID); + +	reply = dbus_message_new_method_return(ctxt->rq); +	dbus_message_iter_init_append(reply, &iter); +	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, +			DBUS_TYPE_BYTE_AS_STRING, &array_iter); + +	/*  +	 * FIXME: check rsp? How check for I/O error or wrong transaction id? +	 * check the type value(Zero) is not reasonable! +	 */ + +	rec = sdp_extract_pdu(rsp, &scanned); +	if (rec == NULL) { +		error("SVC REC is null"); +		goto done; +	} + +	sdp_cache_append(NULL, dst, rec); +	snprintf(identifier, MAX_IDENTIFIER_LEN, "%s/0x%x", dst, rec->handle); + +	/* FIXME: avoid seg fault / out of bound */ +	for (i = 0; i < size; i++) +		dbus_message_iter_append_basic(&array_iter, +				DBUS_TYPE_BYTE, &rsp[i]); + +done: +	dbus_message_iter_close_container(&iter, &array_iter); +	send_reply_and_unref(ctxt->conn, reply); +} + +static void remote_svc_handles_completed_cb(uint8_t type, uint16_t err, uint8_t *rsp, size_t size, void *udata) +{ +	struct transaction_context *ctxt = udata; +	char identifier[MAX_IDENTIFIER_LEN]; +	DBusMessage *reply; +	DBusMessageIter iter, array_iter; +	const char *dst; +	uint8_t *pdata; +	uint8_t dataType = 0; +	int scanned, seqlen = 0; + +	if (!ctxt) +		return; + +	if (type == SDP_ERROR_RSP) { +		error_sdp_failed(ctxt->conn, ctxt->rq, err); +		return; +	} + +	/* check response PDU ID */ +	if (type != SDP_SVC_SEARCH_ATTR_RSP) { +		error("SDP error: %s(%d)", strerror(EPROTO), EPROTO); +		error_failed(ctxt->conn, ctxt->rq, EPROTO); +		return; +	} + +	dbus_message_get_args(ctxt->rq, NULL, +			DBUS_TYPE_STRING, &dst, +			DBUS_TYPE_INVALID); + +	reply = dbus_message_new_method_return(ctxt->rq); +	dbus_message_iter_init_append(reply, &iter); +	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, +			DBUS_TYPE_UINT32_AS_STRING, &array_iter); + +	/*  +	 * FIXME: check rsp? How check for I/O error or wrong transaction id? +	 * check the type value(Zero) is not reasonable! +	 */ +	pdata = rsp; +	scanned = sdp_extract_seqtype(pdata, &dataType, &seqlen); + +	if (!scanned || !seqlen) +		goto done; + +	pdata += scanned; +	do { +		int recsize = 0; +		sdp_record_t *rec = sdp_extract_pdu(pdata, &recsize); +		if (rec == NULL) { +			error("SVC REC is null"); +			goto done; +		} +		if (!recsize) { +			sdp_record_free(rec); +			break; +		} + +		scanned += recsize; +		pdata += recsize; + +	        sdp_cache_append(NULL, dst, rec); +		snprintf(identifier, MAX_IDENTIFIER_LEN, "%s/0x%x", dst, rec->handle); +		dbus_message_iter_append_basic(&array_iter, +				DBUS_TYPE_UINT32, &rec->handle); +	} while (scanned < size); + +done: +	dbus_message_iter_close_container(&iter, &array_iter); +	send_reply_and_unref(ctxt->conn, reply); +} +  static void search_completed_cb(uint8_t type, uint16_t err, uint8_t *rsp, size_t size, void *udata)  {  	struct transaction_context *ctxt = udata; @@ -483,8 +611,13 @@ static void search_completed_cb(uint8_t type, uint16_t err, uint8_t *rsp, size_t  	DBusMessageIter iter, array_iter;  	const char *dst;  	uint8_t *pdata; +	int scanned = 0; +#ifdef SERVICE_SEARCH_ATTR_REQ  	uint8_t dataType = 0; -	int scanned, seqlen = 0; +	int seqlen = 0; +#else +	int csrc, tsrc;  +#endif  	if (!ctxt)  		return; @@ -495,7 +628,11 @@ static void search_completed_cb(uint8_t type, uint16_t err, uint8_t *rsp, size_t  	}  	/* check response PDU ID */ +#ifdef SERVICE_SEARCH_ATTR_REQ  	if (type != SDP_SVC_SEARCH_ATTR_RSP) { +#else +	if (type != SDP_SVC_SEARCH_RSP) { +#endif  		error("SDP error: %s(%d)", strerror(EPROTO), EPROTO);  		error_failed(ctxt->conn, ctxt->rq, EPROTO);  		return; @@ -515,12 +652,31 @@ static void search_completed_cb(uint8_t type, uint16_t err, uint8_t *rsp, size_t  	 * check the type value(Zero) is not reasonable!  	 */  	pdata = rsp; + +#ifdef SERVICE_SEARCH_ATTR_REQ  	scanned = sdp_extract_seqtype(pdata, &dataType, &seqlen);  	if (!scanned || !seqlen)  		goto done;  	pdata += scanned; +#else +	// net service record match count +	tsrc = ntohs(bt_get_unaligned((uint16_t *) pdata)); +	pdata += sizeof(uint16_t); +	scanned += sizeof(uint16_t); +	csrc = ntohs(bt_get_unaligned((uint16_t *) pdata)); +	pdata += sizeof(uint16_t); +	scanned += sizeof(uint16_t); + +	debug("Total svc count: %d\n", tsrc); +	debug("Current svc count: %d\n", csrc); + +	if (!csrc) +		goto done; +#endif + +#ifdef SERVICE_SEARCH_ATTR_REQ  	do {  		int recsize = 0;  		sdp_record_t *rec = sdp_extract_pdu(pdata, &recsize); @@ -541,7 +697,19 @@ static void search_completed_cb(uint8_t type, uint16_t err, uint8_t *rsp, size_t  		dbus_message_iter_append_basic(&array_iter,  				DBUS_TYPE_STRING, &ptr);  	} while (scanned < size); +#else +	do { +		uint32_t handle = ntohl(bt_get_unaligned((uint32_t*)pdata)); +		scanned+= sizeof(uint32_t); +		pdata+=sizeof(uint32_t); + +		snprintf(identifier, MAX_IDENTIFIER_LEN, "%s/0x%x", dst, handle); + +		dbus_message_iter_append_basic(&array_iter, +				DBUS_TYPE_STRING, &ptr); +	} while (--tsrc); +#endif  done:  	dbus_message_iter_close_container(&iter, &array_iter);  	send_reply_and_unref(ctxt->conn, reply); @@ -570,12 +738,8 @@ static gboolean sdp_client_connect_cb(GIOChannel *chan, GIOCondition cond, void  {  	struct pending_connect *c = udata;  	struct transaction_context *ctxt = NULL; -	sdp_list_t *search = NULL, *attrids = NULL; -	uint32_t range = 0x0000ffff; -	uint16_t class;  	int err = 0, sk = 0;  	socklen_t len; -	uuid_t uuid;  	sk = g_io_channel_unix_get_fd(chan); @@ -583,51 +747,31 @@ static gboolean sdp_client_connect_cb(GIOChannel *chan, GIOCondition cond, void  	if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &err, &len) < 0) {  		error("getsockopt(): %s, (%d)", strerror(errno), errno); -		error_connection_attempt_failed(c->conn, c->rq, errno); +		err = errno;  		goto fail;  	}  	if (err != 0) {  		error("connect(): %s(%d)", strerror(err), err); -		error_connection_attempt_failed(c->conn, c->rq, err);  		goto fail;  	}  	ctxt = malloc(sizeof(*ctxt));  	if (!ctxt) { -		error_failed(c->conn, c->rq, ENOMEM); +		err = ENOMEM;  		goto fail;  	} -	memset(ctxt, 0, sizeof(*ctxt)); -	/* FIXME: the sdp search list pattern should be created outside */ -	if (!c->svc) { -		sdp_uuid16_create(&uuid, PUBLIC_BROWSE_GROUP); -		search = sdp_list_append(0, &uuid); -	} else { -		class = sdp_str2svclass(c->svc); -		sdp_uuid16_create(&uuid, class); -		search = sdp_list_append(0, &uuid); -	} +	memset(ctxt, 0, sizeof(*ctxt));  	ctxt->conn = dbus_connection_ref(c->conn);  	ctxt->rq = dbus_message_ref(c->rq);  	ctxt->session = sdp_create(sk, 0); -	if (!ctxt->session) { -		error("error: %s (%d)", strerror(errno), errno); -		error_failed(c->conn, c->rq, errno); -		goto fail; -	} - -	sdp_set_notify(ctxt->session, search_completed_cb, ctxt); -	attrids = sdp_list_append(NULL, &range); -	/* Create/send the search request and set the callback to indicate the request completion */ -	if (sdp_service_search_attr_async(ctxt->session, search, SDP_ATTR_REQ_RANGE, attrids) < 0) { -		error("send request failed: %s (%d)", strerror(errno), errno); -		error_failed(c->conn, c->rq, errno); +	/* set the complete transaction callback and starts the search request */ +	err = c->conn_cb(ctxt); +	if (err)  		goto fail; -	}  	/* set the callback responsible for update the transaction data */  	g_io_add_watch_full(chan, 0, G_IO_IN, search_process_cb, @@ -635,23 +779,20 @@ static gboolean sdp_client_connect_cb(GIOChannel *chan, GIOCondition cond, void  	goto done;  fail: +	if (err) +		error_connection_attempt_failed(c->conn, c->rq, err); +  	if (ctxt)  		transaction_context_free(ctxt);  done: -	if (search) -		sdp_list_free(search, NULL); - -	if (attrids) -		sdp_list_free(attrids, NULL); -  	pending_connects = slist_remove(pending_connects, c);  	pending_connect_free(c);  	return FALSE;  } -static int search_request(DBusConnection *conn, DBusMessage *msg, uint16_t dev_id, -				const char *dst, const char *svc, int *err) +static int connect_request(DBusConnection *conn, DBusMessage *msg, uint16_t dev_id, +				const char *dst, connect_cb_t *cb, int *err)  {  	struct pending_connect *c = NULL;  	GIOChannel *chan = NULL; @@ -685,7 +826,7 @@ static int search_request(DBusConnection *conn, DBusMessage *msg, uint16_t dev_i  	sa.l2_psm = htobs(SDP_PSM);  	str2ba(dst, &sa.l2_bdaddr); -	c = pending_connect_new(conn, msg, dst, svc); +	c = pending_connect_new(conn, msg, dst, cb);  	if (!c) {  		if (err)  			*err = ENOMEM; @@ -719,6 +860,158 @@ fail:  	return -1;  } +static int remote_svc_rec_conn_cb(struct transaction_context *ctxt) +{ +	sdp_list_t *attrids = NULL; +	uint32_t range = 0x0000ffff; +	const char *dst; +	uint32_t handle; +	int err = 0; + +	if (sdp_set_notify(ctxt->session, remote_svc_rec_completed_cb, ctxt) < 0) { +		error("Invalid session data!"); +		err = EINVAL; +		goto fail; +	} + +	dbus_message_get_args(ctxt->rq, NULL, +			DBUS_TYPE_STRING, &dst, +			DBUS_TYPE_UINT32, &handle, +			DBUS_TYPE_INVALID); + +	attrids = sdp_list_append(NULL, &range); +	/* Create/send the search request and set the callback to indicate the request completion */ +	if (sdp_service_attr_async(ctxt->session, handle, SDP_ATTR_REQ_RANGE, attrids) < 0) { +		error("send request failed: %s (%d)", strerror(errno), errno); +		err = errno; +		goto fail; +	} + +fail: +	if (attrids) +		sdp_list_free(attrids, NULL); + +	return err; +} + +DBusHandlerResult get_remote_svc_rec(DBusConnection *conn, DBusMessage *msg, void *data) +{ +	struct hci_dbus_data *dbus_data = data; +	const char *dst; +	uint32_t handle; +	int err = 0; + +	if (!dbus_message_get_args(msg, NULL, +			DBUS_TYPE_STRING, &dst, +			DBUS_TYPE_UINT32, &handle, +			DBUS_TYPE_INVALID)) +		return error_invalid_arguments(conn, msg); + +	if (find_pending_connect(dst)) +		return error_service_search_in_progress(conn, msg); + +	if (connect_request(conn, msg, dbus_data->dev_id, dst, remote_svc_rec_conn_cb, &err) < 0) { +		error("Search request failed: %s (%d)", strerror(err), err); +		return error_failed(conn, msg, err); +	} + +	return DBUS_HANDLER_RESULT_HANDLED; +} + +static int remote_svc_handles_conn_cb(struct transaction_context *ctxt) +{ +	sdp_list_t *search = NULL, *attrids = NULL; +	uuid_t uuid; +	uint32_t range = 0x0000ffff; +	int err = 0; + +	if (sdp_set_notify(ctxt->session, remote_svc_handles_completed_cb, ctxt) < 0) { +		error("Invalid session data!"); +		err = EINVAL; +		goto fail; +	} + +	sdp_uuid16_create(&uuid, PUBLIC_BROWSE_GROUP); +	search = sdp_list_append(0, &uuid); + +	attrids = sdp_list_append(NULL, &range); +	/* Create/send the search request and set the callback to indicate the request completion */ +	if (sdp_service_search_attr_async(ctxt->session, search, SDP_ATTR_REQ_RANGE, attrids) < 0) { +		error("send request failed: %s (%d)", strerror(errno), errno); +		err = errno; +		goto fail; +	} + +fail: +	if (search) +		sdp_list_free(search, NULL); + +	if (attrids) +		sdp_list_free(attrids, NULL); + +	return err; +} + +DBusHandlerResult get_remote_svc_handles(DBusConnection *conn, DBusMessage *msg, void *data) +{ +	struct hci_dbus_data *dbus_data = data; +	const char *dst; +	int err = 0; + +	if (!dbus_message_get_args(msg, NULL, +			DBUS_TYPE_STRING, &dst, +			DBUS_TYPE_INVALID)) +		return error_invalid_arguments(conn, msg); + +	if (find_pending_connect(dst)) +		return error_service_search_in_progress(conn, msg); + +	if (connect_request(conn, msg, dbus_data->dev_id, dst, remote_svc_handles_conn_cb, &err) < 0) { +		error("Search request failed: %s (%d)", strerror(err), err); +		return error_failed(conn, msg, err); +	} + +	return DBUS_HANDLER_RESULT_HANDLED; +} + +static int get_identifiers_conn_cb(struct transaction_context *ctxt) +{ +	sdp_list_t *search = NULL, *attrids = NULL; +	uuid_t uuid; +	uint32_t range = 0x0000ffff; +	int err = 0; + +	if (sdp_set_notify(ctxt->session, search_completed_cb, ctxt) < 0) { +		error("Invalid session data!"); +		err = -EINVAL; +		goto fail; +	} + +	sdp_uuid16_create(&uuid, PUBLIC_BROWSE_GROUP); +	search = sdp_list_append(0, &uuid); + +	attrids = sdp_list_append(NULL, &range); +	/* Create/send the search request and set the callback to indicate the request completion */ +#ifdef SERVICE_SEARCH_ATTR_REQ +	if (sdp_service_search_attr_async(ctxt->session, search, SDP_ATTR_REQ_RANGE, attrids) < 0) { +#else +	if (sdp_service_search_async(ctxt->session, search, 64) < 0) { +#endif +		error("send request failed: %s (%d)", strerror(errno), errno); +		err = -errno; +		goto fail; +	} + +fail: +	if (search) +		sdp_list_free(search, NULL); + +	if (attrids) +		sdp_list_free(attrids, NULL); + +	return err; +} +  static DBusHandlerResult get_identifiers(DBusConnection *conn,  						DBusMessage *msg, void *data)  { @@ -731,10 +1024,11 @@ static DBusHandlerResult get_identifiers(DBusConnection *conn,  			DBUS_TYPE_INVALID))  		return error_invalid_arguments(conn, msg); +	/* in progress is not working properly */  	if (find_pending_connect(dst))  		return error_service_search_in_progress(conn, msg); -	if (search_request(conn, msg, dbus_data->dev_id, dst, NULL, &err) < 0) { +	if (connect_request(conn, msg, dbus_data->dev_id, dst, get_identifiers_conn_cb, &err) < 0) {  		error("Search request failed: %s (%d)", strerror(err), err);  		return error_failed(conn, msg, err);  	} @@ -742,6 +1036,52 @@ static DBusHandlerResult get_identifiers(DBusConnection *conn,  	return DBUS_HANDLER_RESULT_HANDLED;  } +static int get_identifiers_by_service_conn_cb(struct transaction_context *ctxt) +{ +	sdp_list_t *search = NULL, *attrids = NULL; +	uint32_t range = 0x0000ffff; +	const char *dst, *svc; +	uuid_t uuid; +	uint16_t class; +	int err = 0; + +	if (sdp_set_notify(ctxt->session, search_completed_cb, ctxt) < 0) { +		error("Invalid session data!"); +		err = -EINVAL; +		goto fail; +	} + +	dbus_message_get_args(ctxt->rq, NULL, +				DBUS_TYPE_STRING, &dst, +				DBUS_TYPE_STRING, &svc, +				DBUS_TYPE_INVALID); + +	class = sdp_str2svclass(svc); +	sdp_uuid16_create(&uuid, class); +	search = sdp_list_append(0, &uuid); + +	attrids = sdp_list_append(NULL, &range); +	/* Create/send the search request and set the callback to indicate the request completion */ +#ifdef SERVICE_SEARCH_ATTR_REQ +	if (sdp_service_search_attr_async(ctxt->session, search, SDP_ATTR_REQ_RANGE, attrids) < 0) { +#else +	if (sdp_service_search_async(ctxt->session, search, 64) < 0) { +#endif +		error("send request failed: %s (%d)", strerror(errno), errno); +		err = -errno; +		goto fail; +	} + +fail: +	if (search) +		sdp_list_free(search, NULL); + +	if (attrids) +		sdp_list_free(attrids, NULL); + +	return err; +} +  static DBusHandlerResult get_identifiers_by_service(DBusConnection *conn,  						DBusMessage *msg, void *data)  { @@ -823,7 +1163,8 @@ search_request:  	if (find_pending_connect(dst))  		return error_service_search_in_progress(conn, msg); -	if (search_request(conn, msg, dbus_data->dev_id, dst, svc, &err) < 0) { +	if (connect_request(conn, msg, dbus_data->dev_id, dst, +				get_identifiers_by_service_conn_cb, &err) < 0) {  		error("Search request failed: %s (%d)", strerror(err), err);  		return error_failed(conn, msg, err);  	} diff --git a/hcid/dbus.h b/hcid/dbus.h index 7a446caf..4dc0ff51 100644 --- a/hcid/dbus.h +++ b/hcid/dbus.h @@ -196,6 +196,8 @@ DBusHandlerResult handle_security_method(DBusConnection *conn, DBusMessage *msg,  DBusHandlerResult handle_rfcomm_method(DBusConnection *conn, DBusMessage *msg, void *data);  DBusHandlerResult handle_sdp_method(DBusConnection *conn, DBusMessage *msg, void *data); +DBusHandlerResult get_remote_svc_handles(DBusConnection *conn, DBusMessage *msg, void *data); +DBusHandlerResult get_remote_svc_rec(DBusConnection *conn, DBusMessage *msg, void *data);  DBusHandlerResult simple_introspect(DBusConnection *conn, DBusMessage *msg, void *data);  | 
