Update Class of Device handling to the latest management interface

This patch updates the class of device handling code to match the latest
management interface. A notable feature is that UUID to service class
mapping policy remains in user space with the help of the svc_hint
parameter of the add_uuid command. This is to make it easy to update the
mapping table when new profiles come along.
diff --git a/doc/mgmt-api.txt b/doc/mgmt-api.txt
index 24c7070..d964a70 100644
--- a/doc/mgmt-api.txt
+++ b/doc/mgmt-api.txt
@@ -122,6 +122,7 @@
 	Command Code:		0x0009
 	Command Parameters:	Controller_Index (2 Octets)
 				UUID (16 Octets)
+				SVC_Hint (1 octet)
 	Return Paramters:	Controller_Index (2 Octets)
 
 
@@ -134,6 +135,25 @@
 	Return Paramters:	Controller_Index (2 Octets)
 
 
+Set Device Class
+================
+
+	Command Code:		0x000B
+	Command Parameters:	Controller_Index (2 Octets)
+				Major_Class (1 octet)
+				Minor_Class (1 octed)
+	Return Paramters:	Controller_Index (2 Octets)
+
+
+Set Service Cache Command
+=========================
+
+	Command Code:		0x000C
+	Command Parameters:	Controller_Index (2 Octets)
+				Enable (1 octet)
+	Return Paramters:	Controller_Index (2 Octets)
+
+
 Read Tracing Buffer Size Command
 ================================
 
diff --git a/lib/mgmt.h b/lib/mgmt.h
index a382c53..6bdefab 100644
--- a/lib/mgmt.h
+++ b/lib/mgmt.h
@@ -81,14 +81,31 @@
 
 #define MGMT_OP_SET_PAIRABLE		0x0008
 
-struct mgmt_cp_uuid {
+#define MGMT_OP_ADD_UUID		0x0009
+struct mgmt_cp_add_uuid {
+	uint16_t index;
+	uint8_t uuid[16];
+	uint8_t svc_hint;
+} __packed;
+
+#define MGMT_OP_REMOVE_UUID		0x000A
+struct mgmt_cp_remove_uuid {
 	uint16_t index;
 	uint8_t uuid[16];
 } __packed;
 
-#define MGMT_OP_ADD_UUID		0x0009
+#define MGMT_OP_SET_DEV_CLASS		0x000B
+struct mgmt_cp_set_dev_class {
+	uint16_t index;
+	uint8_t major;
+	uint8_t minor;
+} __packed;
 
-#define MGMT_OP_REMOVE_UUID		0x000A
+#define MGMT_OP_SET_SERVICE_CACHE	0x000C
+struct mgmt_cp_set_service_cache {
+	uint16_t index;
+	uint8_t enable;
+} __packed;
 
 #define MGMT_EV_CMD_COMPLETE		0x0001
 struct mgmt_ev_cmd_complete {
diff --git a/plugins/hciops.c b/plugins/hciops.c
index 4d8850f..fec7e16 100644
--- a/plugins/hciops.c
+++ b/plugins/hciops.c
@@ -63,6 +63,11 @@
 	PENDING_NAME,
 };
 
+struct uuid_info {
+	uuid_t uuid;
+	uint8_t svc_hint;
+};
+
 static int max_dev = -1;
 static struct dev_info {
 	int sk;
@@ -1037,10 +1042,10 @@
 	uuid128 = ptr + 2;
 
 	for (; list; list = list->next) {
-		uuid_t *uuid = list->data;
-		uint8_t *uuid128_data = uuid->value.uuid128.data;
+		struct uuid_info *uuid = list->data;
+		uint8_t *uuid128_data = uuid->uuid.value.uuid128.data;
 
-		if (uuid->type != SDP_UUID128)
+		if (uuid->uuid.type != SDP_UUID128)
 			continue;
 
 		/* Stop if not enough space to put next UUID128 */
@@ -1136,15 +1141,15 @@
 
 	/* Group all UUID16 types */
 	for (l = dev->uuids; l != NULL; l = g_slist_next(l)) {
-		uuid_t *uuid = l->data;
+		struct uuid_info *uuid = l->data;
 
-		if (uuid->type != SDP_UUID16)
+		if (uuid->uuid.type != SDP_UUID16)
 			continue;
 
-		if (uuid->value.uuid16 < 0x1100)
+		if (uuid->uuid.value.uuid16 < 0x1100)
 			continue;
 
-		if (uuid->value.uuid16 == PNP_INFO_SVCLASS_ID)
+		if (uuid->uuid.value.uuid16 == PNP_INFO_SVCLASS_ID)
 			continue;
 
 		/* Stop if not enough space to put next UUID16 */
@@ -1155,13 +1160,13 @@
 
 		/* Check for duplicates */
 		for (i = 0; i < uuid_count; i++)
-			if (uuid16[i] == uuid->value.uuid16)
+			if (uuid16[i] == uuid->uuid.value.uuid16)
 				break;
 
 		if (i < uuid_count)
 			continue;
 
-		uuid16[uuid_count++] = uuid->value.uuid16;
+		uuid16[uuid_count++] = uuid->uuid.value.uuid16;
 		eir_len += sizeof(uint16_t);
 	}
 
@@ -3021,48 +3026,6 @@
 	return 0;
 }
 
-static uint8_t get_uuid_mask(uint16_t uuid16)
-{
-	switch (uuid16) {
-	case DIALUP_NET_SVCLASS_ID:
-	case CIP_SVCLASS_ID:
-		return 0x42;	/* Telephony & Networking */
-	case IRMC_SYNC_SVCLASS_ID:
-	case OBEX_OBJPUSH_SVCLASS_ID:
-	case OBEX_FILETRANS_SVCLASS_ID:
-	case IRMC_SYNC_CMD_SVCLASS_ID:
-	case PBAP_PSE_SVCLASS_ID:
-		return 0x10;	/* Object Transfer */
-	case HEADSET_SVCLASS_ID:
-	case HANDSFREE_SVCLASS_ID:
-		return 0x20;	/* Audio */
-	case CORDLESS_TELEPHONY_SVCLASS_ID:
-	case INTERCOM_SVCLASS_ID:
-	case FAX_SVCLASS_ID:
-	case SAP_SVCLASS_ID:
-	/*
-	 * Setting the telephony bit for the handsfree audio gateway
-	 * role is not required by the HFP specification, but the
-	 * Nokia 616 carkit is just plain broken! It will refuse
-	 * pairing without this bit set.
-	 */
-	case HANDSFREE_AGW_SVCLASS_ID:
-		return 0x40;	/* Telephony */
-	case AUDIO_SOURCE_SVCLASS_ID:
-	case VIDEO_SOURCE_SVCLASS_ID:
-		return 0x08;	/* Capturing */
-	case AUDIO_SINK_SVCLASS_ID:
-	case VIDEO_SINK_SVCLASS_ID:
-		return 0x04;	/* Rendering */
-	case PANU_SVCLASS_ID:
-	case NAP_SVCLASS_ID:
-	case GN_SVCLASS_ID:
-		return 0x02;	/* Networking */
-	default:
-		return 0;
-	}
-}
-
 static uint8_t generate_service_class(int index)
 {
 	struct dev_info *dev = &devs[index];
@@ -3070,12 +3033,9 @@
 	uint8_t val = 0;
 
 	for (l = dev->uuids; l != NULL; l = g_slist_next(l)) {
-		uuid_t *uuid = l->data;
+		struct uuid_info *uuid = l->data;
 
-		if (uuid->type != SDP_UUID16)
-			continue;
-
-		val |= get_uuid_mask(uuid->value.uuid16);
+		val |= uuid->svc_hint;
 	}
 
 	return val;
@@ -3117,13 +3077,18 @@
 	return err;
 }
 
-static int hciops_add_uuid(int index, uuid_t *uuid)
+static int hciops_add_uuid(int index, uuid_t *uuid, uint8_t svc_hint)
 {
 	struct dev_info *dev = &devs[index];
+	struct uuid_info *info;
 
 	DBG("hci%d", index);
 
-	dev->uuids = g_slist_append(dev->uuids, g_memdup(uuid, sizeof(*uuid)));
+	info = g_new0(struct uuid_info, 1);
+	memcpy(&info->uuid, uuid, sizeof(*uuid));
+	info->svc_hint = svc_hint;
+
+	dev->uuids = g_slist_append(dev->uuids, info);
 
 	return update_service_classes(index);
 }
diff --git a/plugins/mgmtops.c b/plugins/mgmtops.c
index 28b42bb..e33c049 100644
--- a/plugins/mgmtops.c
+++ b/plugins/mgmtops.c
@@ -410,17 +410,44 @@
 		memcpy(uuid128, uuid, sizeof(*uuid));
 }
 
-static int mgmt_uuid_op(int index, uint16_t op, uuid_t *uuid)
+static int mgmt_add_uuid(int index, uuid_t *uuid, uint8_t svc_hint)
 {
-	char buf[MGMT_HDR_SIZE + sizeof(struct mgmt_cp_uuid)];
+	char buf[MGMT_HDR_SIZE + sizeof(struct mgmt_cp_add_uuid)];
 	struct mgmt_hdr *hdr = (void *) buf;
-	struct mgmt_cp_uuid *cp = (void *) &buf[sizeof(*hdr)];
+	struct mgmt_cp_add_uuid *cp = (void *) &buf[sizeof(*hdr)];
 	uuid_t uuid128;
 
+	DBG("index %d", index);
+
 	uuid_to_uuid128(&uuid128, uuid);
 
 	memset(buf, 0, sizeof(buf));
-	hdr->opcode = htobs(op);
+	hdr->opcode = htobs(MGMT_OP_ADD_UUID);
+	hdr->len = htobs(sizeof(*cp));
+
+	cp->index = htobs(index);
+	memcpy(cp->uuid, uuid128.value.uuid128.data, 16);
+	cp->svc_hint = svc_hint;
+
+	if (write(mgmt_sock, buf, sizeof(buf)) < 0)
+		return -errno;
+
+	return 0;
+}
+
+static int mgmt_remove_uuid(int index, uuid_t *uuid)
+{
+	char buf[MGMT_HDR_SIZE + sizeof(struct mgmt_cp_remove_uuid)];
+	struct mgmt_hdr *hdr = (void *) buf;
+	struct mgmt_cp_remove_uuid *cp = (void *) &buf[sizeof(*hdr)];
+	uuid_t uuid128;
+
+	DBG("index %d", index);
+
+	uuid_to_uuid128(&uuid128, uuid);
+
+	memset(buf, 0, sizeof(buf));
+	hdr->opcode = htobs(MGMT_OP_REMOVE_UUID);
 	hdr->len = htobs(sizeof(*cp));
 
 	cp->index = htobs(index);
@@ -439,7 +466,7 @@
 	memset(&uuid_any, 0, sizeof(uuid_any));
 	uuid_any.type = SDP_UUID128;
 
-	return mgmt_uuid_op(index, MGMT_OP_REMOVE_UUID, &uuid_any);
+	return mgmt_remove_uuid(index, &uuid_any);
 }
 
 static void read_index_list_complete(int sk, void *buf, size_t len)
@@ -497,6 +524,8 @@
 		return;
 	}
 
+	mgmt_set_mode(index, MGMT_OP_SET_SERVICE_CACHE, 1);
+
 	info = &controllers[index];
 	info->type = rp->type;
 	info->enabled = rp->powered;
@@ -701,6 +730,12 @@
 	case MGMT_OP_REMOVE_UUID:
 		DBG("remove_uuid complete");
 		break;
+	case MGMT_OP_SET_DEV_CLASS:
+		DBG("set_dev_class complete");
+		break;
+	case MGMT_OP_SET_SERVICE_CACHE:
+		DBG("set_service_cache complete");
+		break;
 	default:
 		error("Unknown command complete for opcode %u", opcode);
 		break;
@@ -879,8 +914,24 @@
 
 static int mgmt_set_dev_class(int index, uint8_t major, uint8_t minor)
 {
+	char buf[MGMT_HDR_SIZE + sizeof(struct mgmt_cp_set_dev_class)];
+	struct mgmt_hdr *hdr = (void *) buf;
+	struct mgmt_cp_set_dev_class *cp = (void *) &buf[sizeof(*hdr)];
+
 	DBG("index %d major %u minor %u", index, major, minor);
-	return -ENOSYS;
+
+	memset(buf, 0, sizeof(buf));
+	hdr->opcode = htobs(MGMT_OP_SET_DEV_CLASS);
+	hdr->len = htobs(sizeof(*cp));
+
+	cp->index = htobs(index);
+	cp->major = major;
+	cp->minor = minor;
+
+	if (write(mgmt_sock, buf, sizeof(buf)) < 0)
+		return -errno;
+
+	return 0;
 }
 
 static int mgmt_set_limited_discoverable(int index, gboolean limited)
@@ -1123,22 +1174,10 @@
 	return -ENOSYS;
 }
 
-static int mgmt_add_uuid(int index, uuid_t *uuid)
-{
-	DBG("index %d", index);
-	return mgmt_uuid_op(index, MGMT_OP_ADD_UUID, uuid);
-}
-
-static int mgmt_remove_uuid(int index, uuid_t *uuid)
-{
-	DBG("index %d", index);
-	return mgmt_uuid_op(index, MGMT_OP_REMOVE_UUID, uuid);
-}
-
 static int mgmt_disable_cod_cache(int index)
 {
 	DBG("index %d", index);
-	return -ENOSYS;
+	return mgmt_set_mode(index, MGMT_OP_SET_SERVICE_CACHE, 0);
 }
 
 static int mgmt_restore_powered(int index)
diff --git a/src/adapter.c b/src/adapter.c
index 87a8beb..ab4791c 100644
--- a/src/adapter.c
+++ b/src/adapter.c
@@ -1004,6 +1004,51 @@
 	g_strfreev(uuids);
 }
 
+static uint8_t get_uuid_mask(uuid_t *uuid)
+{
+	if (uuid->type != SDP_UUID16)
+		return 0;
+
+	switch (uuid->value.uuid16) {
+	case DIALUP_NET_SVCLASS_ID:
+	case CIP_SVCLASS_ID:
+		return 0x42;	/* Telephony & Networking */
+	case IRMC_SYNC_SVCLASS_ID:
+	case OBEX_OBJPUSH_SVCLASS_ID:
+	case OBEX_FILETRANS_SVCLASS_ID:
+	case IRMC_SYNC_CMD_SVCLASS_ID:
+	case PBAP_PSE_SVCLASS_ID:
+		return 0x10;	/* Object Transfer */
+	case HEADSET_SVCLASS_ID:
+	case HANDSFREE_SVCLASS_ID:
+		return 0x20;	/* Audio */
+	case CORDLESS_TELEPHONY_SVCLASS_ID:
+	case INTERCOM_SVCLASS_ID:
+	case FAX_SVCLASS_ID:
+	case SAP_SVCLASS_ID:
+	/*
+	 * Setting the telephony bit for the handsfree audio gateway
+	 * role is not required by the HFP specification, but the
+	 * Nokia 616 carkit is just plain broken! It will refuse
+	 * pairing without this bit set.
+	 */
+	case HANDSFREE_AGW_SVCLASS_ID:
+		return 0x40;	/* Telephony */
+	case AUDIO_SOURCE_SVCLASS_ID:
+	case VIDEO_SOURCE_SVCLASS_ID:
+		return 0x08;	/* Capturing */
+	case AUDIO_SINK_SVCLASS_ID:
+	case VIDEO_SINK_SVCLASS_ID:
+		return 0x04;	/* Rendering */
+	case PANU_SVCLASS_ID:
+	case NAP_SVCLASS_ID:
+	case GN_SVCLASS_ID:
+		return 0x02;	/* Networking */
+	default:
+		return 0;
+	}
+}
+
 static int uuid_cmp(const void *a, const void *b)
 {
 	const sdp_record_t *rec = a;
@@ -1025,8 +1070,10 @@
 	adapter->services = sdp_list_insert_sorted(adapter->services, rec,
 								record_sort);
 
-	if (new_uuid)
-		adapter_ops->add_uuid(adapter->dev_id, &rec->svclass);
+	if (new_uuid) {
+		uint8_t svc_hint = get_uuid_mask(&rec->svclass);
+		adapter_ops->add_uuid(adapter->dev_id, &rec->svclass, svc_hint);
+	}
 
 	adapter_emit_uuids_updated(adapter);
 }
diff --git a/src/adapter.h b/src/adapter.h
index 250c65e..b66be9a 100644
--- a/src/adapter.h
+++ b/src/adapter.h
@@ -234,7 +234,7 @@
 							gpointer user_data);
 	int (*set_did) (int index, uint16_t vendor, uint16_t product,
 							uint16_t version);
-	int (*add_uuid) (int index, uuid_t *uuid);
+	int (*add_uuid) (int index, uuid_t *uuid, uint8_t svc_hint);
 	int (*remove_uuid) (int index, uuid_t *uuid);
 	int (*disable_cod_cache) (int index);
 	int (*restore_powered) (int index);