mgmt: add initial support for link key handling
diff --git a/doc/mgmt-api.txt b/doc/mgmt-api.txt
index d964a70..cb00bd2 100644
--- a/doc/mgmt-api.txt
+++ b/doc/mgmt-api.txt
@@ -154,6 +154,34 @@
 	Return Paramters:	Controller_Index (2 Octets)
 
 
+Load Keys Command
+=================
+
+	Command Code:		0x000D
+	Command Parameters:	Controller_Index (2 Octets)
+				Debug_Keys (1 Octet)
+				Key_Count (2 Octets)
+				Key1 {
+					Address (6 Octets)
+					Type (1 Octet)
+					Value (16 Octets)
+					PIN_Length (1 Octet)
+				}
+				Key2 { }
+				...
+	Return Paramters:	Controller_Index (2 Octets)
+
+
+Remove Key Command
+==================
+
+	Command Code:		0x000D
+	Command Parameters:	Controller_Index (2 Octets)
+				Address (6 Octets)
+				Disconnect (1 Octet)
+	Return Paramters:	Controller_Index (2 Octets)
+
+
 Read Tracing Buffer Size Command
 ================================
 
@@ -269,3 +297,16 @@
 Event Code		0x0009
 Event Parameters	Controller_Index (2 Octets)
 			Pairable (1 Octet)
+
+New Key Event
+=============
+
+Event Code		0x000A
+Event Parameters	Controller_Index (2 Octets)
+			Key {
+				Address (6 Octets)
+				Type (1 Octet)
+				Value (16 Octets)
+				PIN_Length (1 Octet)
+			}
+			Old_Key_Type (1 Octet)
diff --git a/lib/mgmt.h b/lib/mgmt.h
index 6bdefab..bd4bd96 100644
--- a/lib/mgmt.h
+++ b/lib/mgmt.h
@@ -107,6 +107,28 @@
 	uint8_t enable;
 } __packed;
 
+struct mgmt_key_info {
+	bdaddr_t bdaddr;
+	uint8_t type;
+	uint8_t val[16];
+	uint8_t pin_len;
+} __packed;
+
+#define MGMT_OP_LOAD_KEYS		0x000D
+struct mgmt_cp_load_keys {
+	uint16_t index;
+	uint8_t debug_keys;
+	uint16_t key_count;
+	struct mgmt_key_info keys[0];
+} __packed;
+
+#define MGMT_OP_REMOVE_KEY		0x000E
+struct mgmt_cp_remove_key {
+	uint16_t index;
+	bdaddr_t bdaddr;
+	uint8_t disconnect;
+} __packed;
+
 #define MGMT_EV_CMD_COMPLETE		0x0001
 struct mgmt_ev_cmd_complete {
 	uint16_t opcode;
@@ -142,3 +164,10 @@
 #define MGMT_EV_CONNECTABLE		0x0008
 
 #define MGMT_EV_PAIRABLE		0x0009
+
+#define MGMT_EV_NEW_KEY			0x000A
+struct mgmt_ev_new_key {
+	uint16_t index;
+	struct mgmt_key_info key;
+	uint8_t old_key_type;
+} __packed;
diff --git a/plugins/mgmtops.c b/plugins/mgmtops.c
index e33c049..c082586 100644
--- a/plugins/mgmtops.c
+++ b/plugins/mgmtops.c
@@ -400,6 +400,33 @@
 	btd_adapter_pairable_changed(adapter, info->pairable);
 }
 
+static void mgmt_new_key(int sk, void *buf, size_t len)
+{
+	struct mgmt_ev_new_key *ev = buf;
+	struct controller_info *info;
+	uint16_t index;
+
+	if (len < sizeof(*ev)) {
+		error("Too small new_key event");
+		return;
+	}
+
+	index = btohs(bt_get_unaligned(&ev->index));
+
+	DBG("Controller %u new key of type %u", index, ev->key.type);
+
+	if (index > max_index) {
+		error("Unexpected index %u in new_key event", index);
+		return;
+	}
+
+	info = &controllers[index];
+
+	btd_event_link_key_notify(&info->bdaddr, &ev->key.bdaddr,
+					ev->key.val, ev->key.type,
+					ev->key.pin_len, ev->old_key_type);
+}
+
 static void uuid_to_uuid128(uuid_t *uuid128, const uuid_t *uuid)
 {
 	if (uuid->type == SDP_UUID16)
@@ -736,6 +763,12 @@
 	case MGMT_OP_SET_SERVICE_CACHE:
 		DBG("set_service_cache complete");
 		break;
+	case MGMT_OP_LOAD_KEYS:
+		DBG("load_keys complete");
+		break;
+	case MGMT_OP_REMOVE_KEY:
+		DBG("remove_key complete");
+		break;
 	default:
 		error("Unknown command complete for opcode %u", opcode);
 		break;
@@ -842,6 +875,9 @@
 	case MGMT_EV_PAIRABLE:
 		mgmt_pairable(sk, buf + MGMT_HDR_SIZE, len);
 		break;
+	case MGMT_EV_NEW_KEY:
+		mgmt_new_key(sk, buf + MGMT_HDR_SIZE, len);
+		break;
 	default:
 		error("Unknown Management opcode %u", opcode);
 		break;
@@ -1089,12 +1125,26 @@
 
 static int mgmt_remove_bonding(int index, bdaddr_t *bdaddr)
 {
+	char buf[MGMT_HDR_SIZE + sizeof(struct mgmt_cp_remove_key)];
+	struct mgmt_hdr *hdr = (void *) buf;
+	struct mgmt_cp_remove_key *cp = (void *) &buf[sizeof(*hdr)];
 	char addr[18];
 
 	ba2str(bdaddr, addr);
 	DBG("index %d addr %s", index, addr);
 
-	return -ENOSYS;
+	memset(buf, 0, sizeof(buf));
+	hdr->opcode = htobs(MGMT_OP_REMOVE_KEY);
+	hdr->len = htobs(sizeof(*cp));
+
+	cp->index = htobs(index);
+	bacpy(&cp->bdaddr, bdaddr);
+	cp->disconnect = 1;
+
+	if (write(mgmt_sock, buf, sizeof(buf)) < 0)
+		return -errno;
+
+	return 0;
 }
 
 static int mgmt_request_authentication(int index, uint16_t handle)
@@ -1188,9 +1238,52 @@
 
 static int mgmt_load_keys(int index, GSList *keys, gboolean debug_keys)
 {
-	DBG("index %d keys %d debug_keys %d", index, g_slist_length(keys),
-								debug_keys);
-	return -ENOSYS;
+	char *buf;
+	struct mgmt_hdr *hdr;
+	struct mgmt_cp_load_keys *cp;
+	struct mgmt_key_info *key;
+	size_t key_count, cp_size;
+	GSList *l;
+	int err;
+
+	key_count = g_slist_length(keys);
+
+	DBG("index %d keys %zu debug_keys %d", index, key_count, debug_keys);
+
+	cp_size = sizeof(*cp) + (key_count * sizeof(*key));
+
+	buf = g_try_malloc0(sizeof(*hdr) + cp_size);
+	if (buf == NULL)
+		return -ENOMEM;
+
+	memset(buf, 0, sizeof(buf));
+
+	hdr = (void *) buf;
+	hdr->opcode = htobs(MGMT_OP_LOAD_KEYS);
+	hdr->len = htobs(cp_size);
+
+	cp = (void *) (buf + sizeof(*hdr));
+	cp->index = htobs(index);
+	cp->debug_keys = debug_keys;
+	cp->key_count = htobs(key_count);
+
+	for (l = keys, key = cp->keys; l != NULL; l = g_slist_next(l), key++) {
+		struct link_key_info *info = l->data;
+
+		bacpy(&key->bdaddr, &info->bdaddr);
+		key->type = info->type;
+		memcpy(key->val, info->key, 16);
+		key->pin_len = info->pin_len;
+	}
+
+	if (write(mgmt_sock, buf, sizeof(*hdr) + cp_size) < 0)
+		err = -errno;
+	else
+		err = 0;
+
+	g_free(buf);
+
+	return err;
 }
 
 static struct btd_adapter_ops mgmt_ops = {