// SPDX-License-Identifier: LGPL-2.1-or-later
/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2013-2014  Intel Corporation
 *
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <endian.h>
#include <stdbool.h>
#include <sys/socket.h>

#include "lib/bluetooth.h"
#include "lib/hci.h"

#include "src/shared/util.h"
#include "src/shared/crypto.h"
#include "src/shared/ecc.h"
#include "monitor/bt.h"
#include "bthost.h"

#define SMP_CID		0x0006
#define SMP_BREDR_CID	0x0007

#define L2CAP_FC_SMP_BREDR	0x80

#define SMP_PASSKEY_ENTRY_FAILED	0x01
#define SMP_OOB_NOT_AVAIL		0x02
#define SMP_AUTH_REQUIREMENTS		0x03
#define SMP_CONFIRM_FAILED		0x04
#define SMP_PAIRING_NOTSUPP		0x05
#define SMP_ENC_KEY_SIZE		0x06
#define SMP_CMD_NOTSUPP			0x07
#define SMP_UNSPECIFIED			0x08
#define SMP_REPEATED_ATTEMPTS		0x09
#define SMP_INVALID_PARAMS		0x0a
#define SMP_DHKEY_CHECK_FAILED		0x0b
#define SMP_NUMERIC_COMP_FAILED		0x0c
#define SMP_BREDR_PAIRING_IN_PROGRESS	0x0d

#define DIST_ENC_KEY	0x01
#define DIST_ID_KEY	0x02
#define DIST_SIGN	0x04
#define DIST_LINK_KEY	0x08

#define SC_NO_DIST	(DIST_ENC_KEY | DIST_LINK_KEY)

#define MAX_IO_CAP	0x04

#define SMP_AUTH_NONE		0x00
#define SMP_AUTH_BONDING	0x01
#define SMP_AUTH_MITM		0x04
#define SMP_AUTH_SC		0x08
#define SMP_AUTH_KEYPRESS	0x10

struct smp {
	struct bthost *bthost;
	struct smp_conn *conn;
	struct bt_crypto *crypto;
};

struct smp_conn {
	struct smp *smp;
	uint16_t handle;
	uint8_t addr_type;
	bool out;
	bool sc;
	bool initiator;
	uint8_t method;
	uint8_t local_key_dist;
	uint8_t remote_key_dist;
	uint8_t ia[6];
	uint8_t ia_type;
	uint8_t ra[6];
	uint8_t ra_type;
	uint8_t tk[16];
	uint8_t prnd[16];
	uint8_t rrnd[16];
	uint8_t pcnf[16];
	uint8_t preq[7];
	uint8_t prsp[7];
	uint8_t ltk[16];

	uint8_t local_sk[32];
	uint8_t local_pk[64];
	uint8_t remote_pk[64];
	uint8_t dhkey[32];
	uint8_t mackey[16];

	uint8_t passkey_notify;
	uint8_t passkey_round;
};

enum {
	JUST_WORKS,
	JUST_CFM,
	REQ_PASSKEY,
	CFM_PASSKEY,
	REQ_OOB,
	DSP_PASSKEY,
	OVERLAP,
};

static const uint8_t gen_method[5][5] = {
	{ JUST_WORKS,  JUST_CFM,    REQ_PASSKEY, JUST_WORKS, REQ_PASSKEY },
	{ JUST_WORKS,  JUST_CFM,    REQ_PASSKEY, JUST_WORKS, REQ_PASSKEY },
	{ CFM_PASSKEY, CFM_PASSKEY, REQ_PASSKEY, JUST_WORKS, CFM_PASSKEY },
	{ JUST_WORKS,  JUST_CFM,    JUST_WORKS,  JUST_WORKS, JUST_CFM    },
	{ CFM_PASSKEY, CFM_PASSKEY, REQ_PASSKEY, JUST_WORKS, OVERLAP     },
};

static const uint8_t sc_method[5][5] = {
	{ JUST_WORKS,  JUST_CFM,    REQ_PASSKEY, JUST_WORKS, REQ_PASSKEY },
	{ JUST_WORKS,  CFM_PASSKEY, REQ_PASSKEY, JUST_WORKS, CFM_PASSKEY },
	{ DSP_PASSKEY, DSP_PASSKEY, REQ_PASSKEY, JUST_WORKS, DSP_PASSKEY },
	{ JUST_WORKS,  JUST_CFM,    JUST_WORKS,  JUST_WORKS, JUST_CFM    },
	{ DSP_PASSKEY, CFM_PASSKEY, REQ_PASSKEY, JUST_WORKS, CFM_PASSKEY },
};

static uint8_t get_auth_method(struct smp_conn *conn, uint8_t local_io,
							uint8_t remote_io)
{
	/* If either side has unknown io_caps, use JUST_CFM (which gets
	 * converted later to JUST_WORKS if we're initiators.
	 */
	if (local_io > MAX_IO_CAP || remote_io > MAX_IO_CAP)
		return JUST_CFM;

	if (conn->sc)
		return sc_method[remote_io][local_io];

	return gen_method[remote_io][local_io];
}

static uint8_t sc_select_method(struct smp_conn *conn)
{
	struct bt_l2cap_smp_pairing_request *local, *remote;
	uint8_t local_mitm, remote_mitm, local_io, remote_io, method;

	if (conn->out) {
		local = (void *) &conn->preq[1];
		remote = (void *) &conn->prsp[1];
	} else {
		local = (void *) &conn->prsp[1];
		remote = (void *) &conn->preq[1];
	}

	local_io = local->io_capa;
	remote_io = remote->io_capa;

	local_mitm = (local->auth_req & SMP_AUTH_MITM);
	remote_mitm = (remote->auth_req & SMP_AUTH_MITM);

	/* If either side wants MITM, look up the method from the table,
	 * otherwise use JUST WORKS.
	 */
	if (local_mitm || remote_mitm)
		method = get_auth_method(conn, local_io, remote_io);
	else
		method = JUST_WORKS;

	/* Don't confirm locally initiated pairing attempts */
	if (method == JUST_CFM && conn->initiator)
		method = JUST_WORKS;

	return method;
}

static uint8_t key_dist(struct bthost *host)
{
	if (!bthost_bredr_capable(host))
		return (DIST_ENC_KEY | DIST_ID_KEY | DIST_SIGN);

	return (DIST_ENC_KEY | DIST_ID_KEY | DIST_SIGN | DIST_LINK_KEY);
}

static void smp_send(struct smp_conn *conn, uint8_t smp_cmd, const void *data,
								uint8_t len)
{
	struct iovec iov[2];
	uint16_t cid;

	iov[0].iov_base = &smp_cmd;
	iov[0].iov_len = 1;

	iov[1].iov_base = (void *) data;
	iov[1].iov_len = len;

	if (conn->addr_type == BDADDR_BREDR)
		cid = SMP_BREDR_CID;
	else
		cid = SMP_CID;

	bthost_send_cid_v(conn->smp->bthost, conn->handle, cid, iov, 2);
}

static bool send_public_key(struct smp_conn *conn)
{
	if (!ecc_make_key(conn->local_pk, conn->local_sk))
		return false;

	smp_send(conn, BT_L2CAP_SMP_PUBLIC_KEY, conn->local_pk, 64);

	return true;
}

static void sc_dhkey_check(struct smp_conn *conn)
{
	uint8_t io_cap[3], r[16], a[7], b[7], *local_addr, *remote_addr;
	struct bt_l2cap_smp_dhkey_check check;

	memcpy(a, conn->ia, 6);
	memcpy(b, conn->ra, 6);
	a[6] = conn->ia_type;
	b[6] = conn->ra_type;

	if (conn->out) {
		local_addr = a;
		remote_addr = b;
		memcpy(io_cap, &conn->preq[1], 3);
	} else {
		local_addr = b;
		remote_addr = a;
		memcpy(io_cap, &conn->prsp[1], 3);
	}

	memset(r, 0, sizeof(r));

	bt_crypto_f6(conn->smp->crypto, conn->mackey, conn->prnd, conn->rrnd,
				r, io_cap, local_addr, remote_addr, check.e);

	smp_send(conn, BT_L2CAP_SMP_DHKEY_CHECK, &check, sizeof(check));
}

static void sc_mackey_and_ltk(struct smp_conn *conn)
{
	uint8_t *na, *nb, a[7], b[7];

	if (conn->out) {
		na = conn->prnd;
		nb = conn->rrnd;
	} else {
		na = conn->rrnd;
		nb = conn->prnd;
	}

	memcpy(a, conn->ia, 6);
	memcpy(b, conn->ra, 6);
	a[6] = conn->ia_type;
	b[6] = conn->ra_type;

	bt_crypto_f5(conn->smp->crypto, conn->dhkey, na, nb, a, b,
						conn->mackey, conn->ltk);
}

static uint8_t sc_passkey_send_confirm(struct smp_conn *conn)
{
	struct bt_l2cap_smp_pairing_confirm cfm;
	uint8_t r;

	r = ((conn->passkey_notify >> conn->passkey_round) & 0x01);
	r |= 0x80;

	if (!bt_crypto_f4(conn->smp->crypto, conn->local_pk, conn->remote_pk,
					conn->prnd, r, cfm.value))
		return SMP_UNSPECIFIED;

	smp_send(conn, BT_L2CAP_SMP_PAIRING_CONFIRM, &cfm, sizeof(cfm));

	return 0;
}

static uint8_t sc_passkey_round(struct smp_conn *conn, uint8_t smp_op)
{
	uint8_t cfm[16], r;

	/* Ignore the PDU if we've already done 20 rounds (0 - 19) */
	if (conn->passkey_round >= 20)
		return 0;

	switch (smp_op) {
	case BT_L2CAP_SMP_PAIRING_RANDOM:
		r = ((conn->passkey_notify >> conn->passkey_round) & 0x01);
		r |= 0x80;

		if (!bt_crypto_f4(conn->smp->crypto, conn->remote_pk,
					conn->local_pk, conn->rrnd, r, cfm))
			return SMP_UNSPECIFIED;

		if (memcmp(conn->pcnf, cfm, 16))
			return SMP_CONFIRM_FAILED;

		conn->passkey_round++;

		if (conn->passkey_round == 20) {
			/* Generate MacKey and LTK */
			sc_mackey_and_ltk(conn);
		}

		/* The round is only complete when the initiator
		 * receives pairing random.
		 */
		if (!conn->out) {
			smp_send(conn, BT_L2CAP_SMP_PAIRING_RANDOM,
					conn->prnd, sizeof(conn->prnd));
			return 0;
		}

		/* Start the next round */
		if (conn->passkey_round != 20)
			return sc_passkey_round(conn, 0);

		/* Passkey rounds are complete - start DHKey Check */
		sc_dhkey_check(conn);

		break;

	case BT_L2CAP_SMP_PAIRING_CONFIRM:
		if (conn->out) {
			smp_send(conn, BT_L2CAP_SMP_PAIRING_RANDOM,
					conn->prnd, sizeof(conn->prnd));
			return 0;
		}

		return sc_passkey_send_confirm(conn);

	case BT_L2CAP_SMP_PUBLIC_KEY:
	default:
		/* Initiating device starts the round */
		if (!conn->out)
			return 0;

		return sc_passkey_send_confirm(conn);
	}

	return 0;
}

static bool verify_random(struct smp_conn *conn, const uint8_t rnd[16])
{
	uint8_t confirm[16];

	if (!bt_crypto_c1(conn->smp->crypto, conn->tk, conn->rrnd, conn->prsp,
				conn->preq, conn->ia_type, conn->ia,
				conn->ra_type, conn->ra, confirm))
		return false;

	if (memcmp(conn->pcnf, confirm, sizeof(conn->pcnf)) != 0) {
		bthost_debug(conn->smp->bthost,
					"Confirmation values don't match");
		return false;
	}

	if (conn->out) {
		bt_crypto_s1(conn->smp->crypto, conn->tk, conn->rrnd,
							conn->prnd, conn->ltk);
		bthost_le_start_encrypt(conn->smp->bthost, conn->handle,
								conn->ltk);
	} else {
		bt_crypto_s1(conn->smp->crypto, conn->tk, conn->prnd,
							conn->rrnd, conn->ltk);
	}

	return true;
}

static void distribute_keys(struct smp_conn *conn)
{
	uint8_t buf[16];

	if (conn->local_key_dist & DIST_ENC_KEY) {
		memset(buf, 0, sizeof(buf));
		smp_send(conn, BT_L2CAP_SMP_ENCRYPT_INFO, buf, sizeof(buf));
		smp_send(conn, BT_L2CAP_SMP_CENTRAL_IDENT, buf, 10);
	}

	if (conn->local_key_dist & DIST_ID_KEY) {
		memset(buf, 0, sizeof(buf));
		smp_send(conn, BT_L2CAP_SMP_IDENT_INFO, buf, sizeof(buf));

		memset(buf, 0, sizeof(buf));

		if (conn->out) {
			buf[0] = conn->ia_type;
			memcpy(&buf[1], conn->ia, 6);
		} else {
			buf[0] = conn->ra_type;
			memcpy(&buf[1], conn->ra, 6);
		}

		smp_send(conn, BT_L2CAP_SMP_IDENT_ADDR_INFO, buf, 7);
	}

	if (conn->local_key_dist & DIST_SIGN) {
		memset(buf, 0, sizeof(buf));
		smp_send(conn, BT_L2CAP_SMP_SIGNING_INFO, buf, sizeof(buf));
	}
}

static void pairing_req(struct smp_conn *conn, const void *data, uint16_t len)
{
	struct bthost *bthost = conn->smp->bthost;
	struct bt_l2cap_smp_pairing_response rsp;

	memcpy(conn->preq, data, sizeof(conn->preq));

	if (conn->addr_type == BDADDR_BREDR) {
		rsp.io_capa	= 0x00;
		rsp.oob_data	= 0x00;
		rsp.auth_req	= 0x00;
	} else {
		rsp.io_capa	= bthost_get_io_capability(bthost);
		rsp.oob_data	= 0x00;
		rsp.auth_req	= bthost_get_auth_req(bthost);
	}

	rsp.max_key_size	= 0x10;
	rsp.init_key_dist	= conn->preq[5] & key_dist(bthost);
	rsp.resp_key_dist	= conn->preq[6] & key_dist(bthost);

	conn->prsp[0] = BT_L2CAP_SMP_PAIRING_RESPONSE;
	memcpy(&conn->prsp[1], &rsp, sizeof(rsp));

	conn->local_key_dist	= rsp.resp_key_dist;
	conn->remote_key_dist	= rsp.init_key_dist;

	if (((conn->prsp[3] & 0x08) && (conn->preq[3] & 0x08)) ||
					conn->addr_type == BDADDR_BREDR) {
		conn->sc = true;
		conn->local_key_dist &= ~SC_NO_DIST;
		conn->remote_key_dist &= ~SC_NO_DIST;
	}

	smp_send(conn, BT_L2CAP_SMP_PAIRING_RESPONSE, &rsp, sizeof(rsp));

	if (conn->addr_type == BDADDR_BREDR)
		distribute_keys(conn);
}

static void pairing_rsp(struct smp_conn *conn, const void *data, uint16_t len)
{
	struct smp *smp = conn->smp;
	uint8_t cfm[16];

	memcpy(conn->prsp, data, sizeof(conn->prsp));

	conn->local_key_dist = conn->prsp[5];
	conn->remote_key_dist = conn->prsp[6];

	if (conn->addr_type == BDADDR_BREDR) {
		conn->local_key_dist &= ~SC_NO_DIST;
		conn->remote_key_dist &= ~SC_NO_DIST;
		distribute_keys(conn);
		return;
	}

	if (((conn->prsp[3] & 0x08) && (conn->preq[3] & 0x08)) ||
					conn->addr_type == BDADDR_BREDR) {
		conn->sc = true;
		conn->local_key_dist &= ~SC_NO_DIST;
		conn->remote_key_dist &= ~SC_NO_DIST;
		if (conn->addr_type == BDADDR_BREDR)
			distribute_keys(conn);
		else
			send_public_key(conn);
		return;
	}

	bt_crypto_c1(smp->crypto, conn->tk, conn->prnd, conn->prsp,
			conn->preq, conn->ia_type, conn->ia,
			conn->ra_type, conn->ra, cfm);

	smp_send(conn, BT_L2CAP_SMP_PAIRING_CONFIRM, cfm, sizeof(cfm));
}
static void sc_check_confirm(struct smp_conn *conn)
{
	if (conn->method == REQ_PASSKEY || conn->method == DSP_PASSKEY) {
		sc_passkey_round(conn, BT_L2CAP_SMP_PAIRING_CONFIRM);
		return;
	}

	if (conn->out)
		smp_send(conn, BT_L2CAP_SMP_PAIRING_RANDOM, conn->prnd,
							sizeof(conn->prnd));
}

static void pairing_cfm(struct smp_conn *conn, const void *data, uint16_t len)
{
	uint8_t rsp[16];

	memcpy(conn->pcnf, data + 1, 16);

	if (conn->sc) {
		sc_check_confirm(conn);
		return;
	}

	if (conn->out) {
		memset(rsp, 0, sizeof(rsp));
		smp_send(conn, BT_L2CAP_SMP_PAIRING_RANDOM, conn->prnd,
					sizeof(conn->prnd));
	} else {
		bt_crypto_c1(conn->smp->crypto, conn->tk, conn->prnd,
				conn->prsp, conn->preq, conn->ia_type,
				conn->ia, conn->ra_type, conn->ra, rsp);
		smp_send(conn, BT_L2CAP_SMP_PAIRING_CONFIRM, rsp, sizeof(rsp));
	}
}

static uint8_t sc_random(struct smp_conn *conn)
{
	/* Passkey entry has special treatment */
	if (conn->method == REQ_PASSKEY || conn->method == DSP_PASSKEY)
		return sc_passkey_round(conn, BT_L2CAP_SMP_PAIRING_RANDOM);

	if (conn->out) {
		uint8_t cfm[16];

		bt_crypto_f4(conn->smp->crypto, conn->remote_pk,
					conn->local_pk, conn->rrnd, 0, cfm);

		if (memcmp(conn->pcnf, cfm, 16))
			return 0x04; /* Confirm Value Failed */
	} else {
		smp_send(conn, BT_L2CAP_SMP_PAIRING_RANDOM, conn->prnd, 16);
	}

	sc_mackey_and_ltk(conn);

	if (conn->out)
		sc_dhkey_check(conn);

	return 0;
}

static void pairing_rnd(struct smp_conn *conn, const void *data, uint16_t len)
{
	memcpy(conn->rrnd, data + 1, 16);

	if (conn->sc) {
		uint8_t reason = sc_random(conn);
		if (reason)
			smp_send(conn, BT_L2CAP_SMP_PAIRING_FAILED, &reason,
							sizeof(reason));
		return;
	}

	if (!verify_random(conn, data + 1))
		return;

	if (conn->out)
		return;

	smp_send(conn, BT_L2CAP_SMP_PAIRING_RANDOM, conn->prnd,
				sizeof(conn->prnd));
}

static void encrypt_info(struct smp_conn *conn, const void *data, uint16_t len)
{
}

static void central_ident(struct smp_conn *conn, const void *data, uint16_t len)
{
	conn->remote_key_dist &= ~DIST_ENC_KEY;

	if (conn->out && !conn->remote_key_dist)
		distribute_keys(conn);
}

static void ident_addr_info(struct smp_conn *conn, const void *data,
								uint16_t len)
{
}

static void ident_info(struct smp_conn *conn, const void *data, uint16_t len)
{
	conn->remote_key_dist &= ~DIST_ID_KEY;

	if (conn->out && !conn->remote_key_dist)
		distribute_keys(conn);
}

static void signing_info(struct smp_conn *conn, const void *data, uint16_t len)
{
	conn->remote_key_dist &= ~DIST_SIGN;

	if (conn->out && !conn->remote_key_dist)
		distribute_keys(conn);
}

static void public_key(struct smp_conn *conn, const void *data, uint16_t len)
{
	struct smp *smp = conn->smp;
	uint8_t buf[16];

	memcpy(conn->remote_pk, data + 1, 64);

	if (!conn->out) {
		if (!send_public_key(conn))
			return;
	}

	if (!ecdh_shared_secret(conn->remote_pk, conn->local_sk, conn->dhkey))
		return;

	conn->method = sc_select_method(conn);

	if (conn->method == DSP_PASSKEY || conn->method == REQ_PASSKEY) {
		sc_passkey_round(conn, BT_L2CAP_SMP_PUBLIC_KEY);
		return;
	}

	if (conn->out)
		return;

	if (!bt_crypto_f4(smp->crypto, conn->local_pk, conn->remote_pk,
							conn->prnd, 0, buf))
		return;

	smp_send(conn, BT_L2CAP_SMP_PAIRING_CONFIRM, buf, sizeof(buf));
}

static void dhkey_check(struct smp_conn *conn, const void *data, uint16_t len)
{
	const struct bt_l2cap_smp_dhkey_check *cmd = data + 1;
	uint8_t a[7], b[7], *local_addr, *remote_addr;
	uint8_t io_cap[3], r[16], e[16];

	memcpy(a, &conn->ia, 6);
	memcpy(b, &conn->ra, 6);
	a[6] = conn->ia_type;
	b[6] = conn->ra_type;

	if (conn->out) {
		local_addr = a;
		remote_addr = b;
		memcpy(io_cap, &conn->prsp[1], 3);
	} else {
		local_addr = b;
		remote_addr = a;
		memcpy(io_cap, &conn->preq[1], 3);
	}

	memset(r, 0, sizeof(r));

	if (conn->method == REQ_PASSKEY || conn->method == DSP_PASSKEY)
		put_le32(conn->passkey_notify, r);

	if (!bt_crypto_f6(conn->smp->crypto, conn->mackey, conn->rrnd,
			conn->prnd, r, io_cap, remote_addr, local_addr, e))
		return;

	if (memcmp(cmd->e, e, 16)) {
		uint8_t reason = 0x0b; /* DHKey Check Failed */
		smp_send(conn, BT_L2CAP_SMP_PAIRING_FAILED, &reason,
							sizeof(reason));
	}

	if (conn->out)
		bthost_le_start_encrypt(conn->smp->bthost, conn->handle,
								conn->ltk);
	else
		sc_dhkey_check(conn);
}

void smp_pair(void *conn_data, uint8_t io_cap, uint8_t auth_req)
{
	struct smp_conn *conn = conn_data;
	struct bt_l2cap_smp_pairing_request req;

	req.io_capa		= io_cap;
	req.oob_data		= 0x00;
	req.auth_req		= auth_req;
	req.max_key_size	= 0x10;
	req.init_key_dist	= key_dist(conn->smp->bthost);
	req.resp_key_dist	= key_dist(conn->smp->bthost);

	conn->preq[0] = BT_L2CAP_SMP_PAIRING_REQUEST;
	memcpy(&conn->preq[1], &req, sizeof(req));

	smp_send(conn, BT_L2CAP_SMP_PAIRING_REQUEST, &req, sizeof(req));
}

void smp_data(void *conn_data, const void *data, uint16_t len)
{
	struct smp_conn *conn = conn_data;
	uint8_t opcode;

	if (len < 1) {
		bthost_debug(conn->smp->bthost, "Received too small SMP PDU");
		return;
	}

	if (conn->addr_type == BDADDR_BREDR) {
		bthost_debug(conn->smp->bthost,
					"Received BR/EDR SMP data on LE link");
		return;
	}

	opcode = *((const uint8_t *) data);

	switch (opcode) {
	case BT_L2CAP_SMP_PAIRING_REQUEST:
		pairing_req(conn, data, len);
		break;
	case BT_L2CAP_SMP_PAIRING_RESPONSE:
		pairing_rsp(conn, data, len);
		break;
	case BT_L2CAP_SMP_PAIRING_CONFIRM:
		pairing_cfm(conn, data, len);
		break;
	case BT_L2CAP_SMP_PAIRING_RANDOM:
		pairing_rnd(conn, data, len);
		break;
	case BT_L2CAP_SMP_ENCRYPT_INFO:
		encrypt_info(conn, data, len);
		break;
	case BT_L2CAP_SMP_CENTRAL_IDENT:
		central_ident(conn, data, len);
		break;
	case BT_L2CAP_SMP_IDENT_ADDR_INFO:
		ident_addr_info(conn, data, len);
		break;
	case BT_L2CAP_SMP_IDENT_INFO:
		ident_info(conn, data, len);
		break;
	case BT_L2CAP_SMP_SIGNING_INFO:
		signing_info(conn, data, len);
		break;
	case BT_L2CAP_SMP_PUBLIC_KEY:
		public_key(conn, data, len);
		break;
	case BT_L2CAP_SMP_DHKEY_CHECK:
		dhkey_check(conn, data, len);
		break;
	default:
		break;
	}
}

void smp_bredr_data(void *conn_data, const void *data, uint16_t len)
{
	struct smp_conn *conn = conn_data;
	uint8_t opcode;

	if (len < 1) {
		bthost_debug(conn->smp->bthost, "Received too small SMP PDU");
		return;
	}

	if (conn->addr_type != BDADDR_BREDR) {
		bthost_debug(conn->smp->bthost,
					"Received LE SMP data on BR/EDR link");
		return;
	}

	opcode = *((const uint8_t *) data);

	switch (opcode) {
	case BT_L2CAP_SMP_PAIRING_REQUEST:
		pairing_req(conn, data, len);
		break;
	case BT_L2CAP_SMP_PAIRING_RESPONSE:
		pairing_rsp(conn, data, len);
		break;
	default:
		break;
	}
}

int smp_get_ltk(void *smp_data, uint64_t rand, uint16_t ediv, uint8_t *ltk)
{
	struct smp_conn *conn = smp_data;
	static const uint8_t no_ltk[16] = { 0 };

	if (!memcmp(conn->ltk, no_ltk, 16))
		return -ENOENT;

	memcpy(ltk, conn->ltk, 16);

	return 0;
}

static void smp_conn_bredr(struct smp_conn *conn, uint8_t encrypt)
{
	struct smp *smp = conn->smp;
	struct bt_l2cap_smp_pairing_request req;
	uint64_t fixed_chan;

	if (encrypt != 0x02)
		return;

	conn->sc = true;

	if (!conn->out)
		return;

	fixed_chan = bthost_conn_get_fixed_chan(smp->bthost, conn->handle);
	if (!(fixed_chan & L2CAP_FC_SMP_BREDR))
		return;

	memset(&req, 0, sizeof(req));
	req.max_key_size = 0x10;
	req.init_key_dist = key_dist(smp->bthost);
	req.resp_key_dist = key_dist(smp->bthost);

	smp_send(conn, BT_L2CAP_SMP_PAIRING_REQUEST, &req, sizeof(req));
}

void smp_conn_encrypted(void *conn_data, uint8_t encrypt)
{
	struct smp_conn *conn = conn_data;

	if (!encrypt)
		return;

	if (conn->addr_type == BDADDR_BREDR) {
		smp_conn_bredr(conn, encrypt);
		return;
	}

	if (conn->out && conn->remote_key_dist)
		return;

	distribute_keys(conn);
}

static uint8_t type2hci(uint8_t addr_type)
{
	switch (addr_type) {
	case BDADDR_BREDR:
	case BDADDR_LE_PUBLIC:
		return LE_PUBLIC_ADDRESS;
	case BDADDR_LE_RANDOM:
		return LE_RANDOM_ADDRESS;
	}

	return 0x00;
}

void *smp_conn_add(void *smp_data, uint16_t handle,
			const uint8_t *ia, uint8_t ia_type,
			const uint8_t *ra, uint8_t ra_type, bool conn_init)
{
	struct smp *smp = smp_data;
	struct smp_conn *conn;
	char ia_str[18], ra_str[18];

	conn = malloc(sizeof(struct smp_conn));
	if (!conn)
		return NULL;

	memset(conn, 0, sizeof(*conn));

	conn->smp = smp;
	conn->handle = handle;
	conn->out = conn_init;
	conn->addr_type = conn_init ? ia_type : ra_type;

	conn->ia_type = type2hci(ia_type);
	conn->ra_type = type2hci(ra_type);
	memcpy(conn->ia, ia, 6);
	memcpy(conn->ra, ra, 6);

	ba2str((bdaddr_t *) ia, ia_str);
	ba2str((bdaddr_t *) ra, ra_str);

	bthost_debug(smp->bthost, "ia %s type 0x%02x ra %s type 0x%02x",
					ia_str, ia_type, ra_str, ra_type);

	bt_crypto_random_bytes(smp->crypto, conn->prnd, sizeof(conn->prnd));

	return conn;
}

void smp_conn_del(void *conn_data)
{
	struct smp_conn *conn = conn_data;

	free(conn);
}

void *smp_start(struct bthost *bthost)
{
	struct smp *smp;

	smp = malloc(sizeof(struct smp));
	if (!smp)
		return NULL;

	memset(smp, 0, sizeof(*smp));

	smp->crypto = bt_crypto_new();
	if (!smp->crypto) {
		free(smp);
		return NULL;
	}

	smp->bthost = bthost;

	return smp;
}

void smp_stop(void *smp_data)
{
	struct smp *smp = smp_data;

	bt_crypto_unref(smp->crypto);

	free(smp);
}
